delcypher added inline comments.
================ Comment at: clang/test/Sema/warn-calls-without-prototype.c:39 + return a + b +c; +} + ---------------- aaron.ballman wrote: > delcypher wrote: > > NoQ wrote: > > > delcypher wrote: > > > > delcypher wrote: > > > > > delcypher wrote: > > > > > > @NoQ Any ideas about this? It seems kind of weird that when > > > > > > merging `not_a_prototype3` prototype with the K&R style definition > > > > > > of `not_a_prototype3` that the resulting FunctionDecl we see at the > > > > > > call site in `call_to_function_without_prototype3` is marked as not > > > > > > having a prototype. > > > > > > > > > > > > If I flip the order (see `not_a_prototype6`) then the merged > > > > > > declaration is marked as having a prototype. > > > > > > > > > > > > I'm not sure if this is a bug in `Sema::MergeFunctionDecl` or if > > > > > > this just a peculiarity of K&R style function definitions. > > > > > I suspect the problem might be here in `Sema::MergeFunctionDecl`. > > > > > > > > > > ```lang=c++ > > > > > // C: Function types need to be compatible, not identical. This > > > > > handles > > > > > // duplicate function decls like "void f(int); void f(enum X);" > > > > > properly. > > > > > if (!getLangOpts().CPlusPlus && > > > > > Context.typesAreCompatible(OldQType, NewQType)) { > > > > > const FunctionType *OldFuncType = OldQType->getAs<FunctionType>(); > > > > > const FunctionType *NewFuncType = NewQType->getAs<FunctionType>(); > > > > > const FunctionProtoType *OldProto = nullptr; > > > > > if (MergeTypeWithOld && isa<FunctionNoProtoType>(NewFuncType) && > > > > > (OldProto = dyn_cast<FunctionProtoType>(OldFuncType))) { > > > > > // The old declaration provided a function prototype, but the > > > > > // new declaration does not. Merge in the prototype. > > > > > ``` > > > > > > > > > > ` isa<FunctionNoProtoType>(NewFuncType)` is false in this particular > > > > > case, however `New` doesn't have a prototype (i.e. > > > > > `New->hasPrototype()` is false). One fix might be to replace > > > > > `isa<FunctionNoProtoType>(NewFuncType)` with > > > > > > > > > > ``` > > > > > (isa<FunctionNoProtoType>(NewFuncType) || !New->hasPrototype()) > > > > > ``` > > > > > > > > > > However, I don't really know this code well enough to know if that's > > > > > the right fix. > > > > Okay. I think the above would actually be the wrong location for a fix > > > > because in this case we don't need to go down the path that synthesizes > > > > the parameters because we already know them for both `old` and `new` in > > > > this situation. > > > > > > > > Instead I think the change would have to be in > > > > `Sema::MergeCompatibleFunctionDecls` to do something like. > > > > > > > > ```lang=c++ > > > > // If New is a K&R function definition it will be marked > > > > // as not having a prototype. If `Old` has a prototype > > > > // then to "merge" we should mark the K&R function as having a > > > > prototype. > > > > if (!getLangOpts().CPlusPlus && Old->hasPrototype() && > > > > !New->hasPrototype()) > > > > New->setHasInheritedPrototype(); > > > > ``` > > > > > > > > What I'm not sure about is if this is semantically the right thing to > > > > do. Thoughts? > > > Ok dunno but I definitely find this whole thing surprising. I'd expect > > > this example to be the entirely normal situation for this code, where it > > > sees that the new declaration has no prototype so it "inherits" it from > > > the old declaration. But you're saying that > > > > > > > `isa<FunctionNoProtoType>(NewFuncType)` is false in this particular case > > > > > > Where does that proto-type come from then? I only see this code affecting > > > the type > > > ```lang=c++ > > > 3523 if (RequiresAdjustment) { > > > 3524 const FunctionType *AdjustedType = > > > New->getType()->getAs<FunctionType>(); > > > 3525 AdjustedType = Context.adjustFunctionType(AdjustedType, > > > NewTypeInfo); > > > 3526 New->setType(QualType(AdjustedType, 0)); > > > 3527 NewQType = Context.getCanonicalType(New->getType()); > > > 3528 } > > > ``` > > > which doesn't seem to touch no-proto-types: > > > ```lang=c++ > > > 3094 const FunctionType *ASTContext::adjustFunctionType(const > > > FunctionType *T, > > > 3095 > > > FunctionType::ExtInfo Info) { > > > 3096 if (T->getExtInfo() == Info) > > > 3097 return T; > > > 3098 > > > 3099 QualType Result; > > > 3100 if (const auto *FNPT = dyn_cast<FunctionNoProtoType>(T)) { > > > 3101 Result = getFunctionNoProtoType(FNPT->getReturnType(), Info); > > > ... > > > 3107 } > > > 3108 > > > 3109 return cast<FunctionType>(Result.getTypePtr()); > > > 3110 } > > > ``` > > > So it sounds like `New->getType()` was already with prototype from the > > > start? Maybe whatever code has set that type should also have set the > > > `HasInheritedPrototype` flag? > > > Ok dunno but I definitely find this whole thing surprising. I'd expect > > > this example to be the entirely normal situation for this code, where it > > > sees that the new declaration has no prototype so it "inherits" it from > > > the old declaration. But you're saying that > > > > > > `isa<FunctionNoProtoType>(NewFuncType) `is false in this particular case > > > > Yep. > > > > ``` > > (lldb) p NewFuncType->dump() > > FunctionProtoType 0x128829430 'int (int, int)' cdecl > > |-BuiltinType 0x12782bf10 'int' > > |-BuiltinType 0x12782bf10 'int' > > `-BuiltinType 0x12782bf10 'int' > > (lldb) p OldFuncType->dump() > > FunctionProtoType 0x128829430 'int (int, int)' cdecl > > |-BuiltinType 0x12782bf10 'int' > > |-BuiltinType 0x12782bf10 'int' > > `-BuiltinType 0x12782bf10 'int' > > ``` > > > > I think we might be confusing the `FunctionNoProtoType` type and what the > > `FunctionDecl::hasPrototype()` method does. > > > > ## `FunctionNoProtoType` > > > > I'm not 100% sure about this but I suspect `FunctionNoProtoType` exists > > only for functions without any prototype that are written like this. > > > > ``` > > int function(); > > ``` > > > > which would make some sense given what this code tries to do. > > > > ```lang=c++ > > if (MergeTypeWithOld && isa<FunctionNoProtoType>(NewFuncType) && > > (OldProto = dyn_cast<FunctionProtoType>(OldFuncType))) { > > // The old declaration provided a function prototype, but the > > // new declaration does not. Merge in the prototype. > > ... > > ``` > > > > i.e. I think it's supposed to handle code like this > > > > > > ``` > > int foo(int, int, int); // prototype > > int foo(); // has no prototype, but when we merge decls it inherits the > > prototype from the previous decl. > > ``` > > > > ## `FunctionDecl::hasPrototype()` > > > > This method is implemented as > > > > ``` > > bool hasPrototype() const { > > return hasWrittenPrototype() || hasInheritedPrototype(); > > } > > ``` > > > > and those implementations get the data from data in the class without > > examining its `FunctionType`. So it seems a `FunctionDecl`'s property of > > having a prototype is independent of that FunctionDecl's `FunctionType`. > > > > ## K&R function declarations > > > > > So it sounds like New->getType() was already with prototype from the > > > start? Maybe whatever code has set that type should also have set the > > > HasInheritedPrototype flag? > > > > The nasty thing about K&R function declarations is they are treated as not > > having a prototype according to the C11 standard (6.9.1.13) . > > > > ```lang=c > > extern int max(int a, int b) > > { > > return a > b ? a : b; > > } > > ``` > > > > ```lang=c > > extern int max(a, b) > > int a, b; > > { > > return a > b ? a : b; > > } > > ``` > > > > //Here int a, b; is the declaration list for the parameters. The difference > > between these two definitions is that the first form acts as a prototype > > declaration that forces conversion of the arguments of subsequent calls to > > the function, whereas the second form does not.// > > > > As much as I hate this, given the above it makes to make that in > > `Sema::MergeFunctionDecl` `New->hasPrototype()` returns false. So in my > > example > > > > ```lang=c > > unsigned not_a_prototype3(int a, int b, int c); // "Old" has a prototype > > unsigned not_a_prototype3(a, b, c ) // "New" has no prototype > > int a; > > int b; > > int c; > > { > > return a + b +c; > > } > > ``` > > > > The question is, what should we do when trying to merge these two > > FunctionDecls? > > @NoQ Any ideas about this? It seems kind of weird that when merging > > not_a_prototype3 prototype with the K&R style definition of > > not_a_prototype3 that the resulting FunctionDecl we see at the call site in > > call_to_function_without_prototype3 is marked as not having a prototype. > > I am reasonably certain this is not a bug, but C being weird: > https://godbolt.org/z/sGj5aejrY. > > I'd have to double-check what C89 says, but IIRC, the only thing that was > specified is how to tell whether the two types are compatible, and it's left > implicit that the definition is the final arbiter of the function type. > > @aaron.ballman Thanks for chiming in. I'll update the test cases to assume this is the intended behavior. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D116635/new/ https://reviews.llvm.org/D116635 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits