mewmew/uc

parser: Distinguish between `int f()` and `int f(void)`

mewmew opened this issue · 8 comments

Apparently int f() is not equivalent to int f(void), make sure the parser handles these cases separately.

Contents of a.c:

int f();

int f(int a, int b);

int main(void) {
    return 42;
}
u@x1 ~> gcc -pedantic -o a a.c
u@x1 ~> ./a ; echo $status
42
u@x1 ~> clang -pedantic -o a a.c
u@x1 ~> ./a ; echo $status
42

Contents of b.c:

int f(int c);

int f(int a, int b);

int main(void) {
    return 42;
}
u@x1 ~> clang -pedantic -o b b.c
b.c:3:5: error: conflicting types for 'f'
int f(int a, int b);
    ^
b.c:1:5: note: previous declaration is here
int f(int c);
    ^
1 error generated.
u@x1 ~> clang -pedantic -o b b.c
b.c:3:5: error: conflicting types for 'f'
int f(int a, int b);
    ^
b.c:1:5: note: previous declaration is here
int f(int c);
    ^
1 error generated.

c.c:

int f(void);

int f(int a, int b);

int main(void) {
    return 42;
}
u@x1 ~> clang -pedantic -o c c.c
c.c:3:5: error: conflicting types for 'f'
int f(int a, int b);
    ^
c.c:1:5: note: previous declaration is here
int f(void);
    ^
1 error generated.
u@x1 ~> gcc -pedantic -o c c.c
c.c:3:5: error: conflicting types for ‘f’
 int f(int a, int b);
     ^
c.c:1:5: note: previous declaration of ‘f’ was here
 int f(void);
     ^

Test case updated in commit 0bf2297.

This issue has been marked as a future ambition. Closing for now. Re-open this topic in the future, _when the time is right_™.

Decision has been made to not allow K&R pre-prototype function declarations int f() by default, as this enables the use of function calls without arguments, which may result in undefined behaviour as seen below.

int printf(const char*,...);
int test();
int main(){
    printf("Test: %d\n", test());
}
int test(int b){
    return b+1;
}
$ CC=gcc CFLAGS=-Wall make -B b; ./b
gcc -Wall    b.c   -o b
Test: 2
$ CC=clang CFLAGS=-Wall make -B b; ./b
clang -Wall    b.c   -o b
Test: 4195797

The plan is to make int f() and int f(void) equivalent, at least for now. This will clean up a lot of corner cases. We may revisit this in the future, if support for K&R pre-prototype declarations would be desired.

If they are to be equal, should a function declaration with empty or void parameter have a param value of nil or an empty slice *[]ast.VarDecl{}?

If they are to be equal, should a function declaration with empty or void parameter have a param value of nil or an empty slice *[]ast.VarDecl{}?

I'd recon nil, as an empty slice would just waste of memory on a slice descriptor and an unnecessary object on the heap. That is of course, unless we can think an example where the resulting code in the compiler becomes more complex, or requires a special case, simply to deal with this issue. As of right now, I can't think of one. Ranging over a nil slice for instance, will simply do zero iterations as wanted.

Yes, i tried the empty slice approach, but wasn't able to get the Params empty rule to run, so I took the Params: nil approach instead and made a rule for BasicType ident "(" ")".