ShivyC icon indicating copy to clipboard operation
ShivyC copied to clipboard

Types cannot switch from complete to incomplete

Open ShivamSarodia opened this issue 6 years ago • 9 comments

In some cases, a type should switch from complete to incomplete, like in the code sample below.

extern int A[]; // incomplete

int f(void) {
  extern int A[5];
  sizeof(A); // complete
}

sizeof(A); // error: A is an incomplete type

int A[5];

sizeof(A); // complete

ShivyC does not permit this: once a type is completed in ShivyC, it remains completed for the rest of the translation unit. It is unclear to me whether ShivyC's behavior is incorrect or not per the letter of the spec.

ShivamSarodia avatar Apr 19 '18 04:04 ShivamSarodia

respect the rule of scope?

Piping avatar Mar 20 '19 05:03 Piping

Hi @Piping, thanks for your message! This bug is a bit tricky: the variable A is referring to the same object throughout the code snippet. That is, the extern int A[5] line does not declare a new variable A which overrides the one declared in the first line, it simply redeclares the A from line 1. That's because both have linkage extern.

ShivyC currently handles this code like so:

  1. On the first line, a symbol table entry is added for A with type incomplete integer array.
  2. When extern int A[5] is reached, the type of this entry in the symbol table is updated to complete integer array of size 5. Note that a new symbol table entry is not created. Doing so would would be equivalent to creating a new symbol. See here for the code that does this.
  3. When function scope is exited, the top level of the symbol table is popped. But this level is empty because no new variables have been declared.
  4. Now, we are outside the function scope -- but we still have A set to a complete type. This is undesirable.

One way to fix this is to add information on type completion in the top level of the symbol table rather than the level of the symbol table that contains the variable. Then, to determine the final type of an incomplete variable, the symbol table would need to be traversed from the layer containing the variable all the way to the top. Alternatively, it may be simpler to create a second stack to keep track of scoped type changes so they are easily "unapplied" when an inner scope is exited.

Let me know if this is unclear. If you'd like to contribute to fixing the bug, I'd be happy to give some more code pointers also.

ShivamSarodia avatar Mar 20 '19 06:03 ShivamSarodia

@ShivamSarodia

Thanks for your input.

Both extern int A[]; and extern int A[5]; have external linkage. But they have different storage duration. A[5] is allocated in the stack. And we can check assembily output for that from compiler explorer

And they have a different type, thus C compiler treat them differently. The A[5] mask the declaration of A[]. Phrase differently, A[]'s external linkage is not visible inside function f.

===========================

Maybe I am wrong after I checked the compiler explorer output. One possible reason is that sizeof is a compiler macro that expanded at compile time?

Piping avatar Mar 20 '19 07:03 Piping

@Piping

Hmm. I was under the impression A[5] would not mask the declaration of A[], based on 6.2.2.2 (page 36) in the standard.

What do you mean by "sizeof is a compiler macro that is expanded at compile time"?

ShivamSarodia avatar Mar 20 '19 20:03 ShivamSarodia

@ShivamSarodia

Be precise, I mean sizeof is a compile time unary operator.

And yes, those two A should be the same object in the binary.

But sizeof() only works for current scope.

Piping avatar Mar 21 '19 00:03 Piping

Gotcha, I think we agree. When exiting the function scope, ShivyC should be resetting the type to what it originally was, and this bug is that ShivyC fails to do so.

ShivamSarodia avatar Mar 21 '19 00:03 ShivamSarodia

When exiting the function scope, ShivyC should be resetting the type to what it originally was

Not really. C is a nested scope language, and a function is just one level of scoping. Any { } open a new scope (and then close it). The common way to implement that is to maintain stack of scopes, push/pop on it, and search for symbols from top down to bottom of the stack for the first match.

P.S. Cute project, thanks for it ;-).

pfalcon avatar Mar 21 '19 06:03 pfalcon

Thanks @pfalcon! That is how ShivyC implements variable scoping today. Unfortunately, the type of each object (in our case, A) is stored directly on the compiler's representation of the object and not as a separate column in the symbol table. So, because the A in the nested scope and the A in global scope are the same object, the current implementation provides no way to assign the two to have different types.

ShivamSarodia avatar Mar 21 '19 07:03 ShivamSarodia

Ah, good to know! (Sorry, it's in my queue to look into depth of ShivyC, but so far I just managed to start watching project on github to see its progress).

I'd personally still treat a case like:

extern int A[];

int f(void) {
  extern int A[5];
}

As an example of complete shadowing, i.e. global scope A has no relation whatsoever to function-scope A, the latter just shadows the former. I may be wrong of course ;-).

pfalcon avatar Mar 21 '19 07:03 pfalcon