This is an archive of the discontinued LLVM Phabricator instance.

Correct the behavior of va_arg checking in C++
ClosedPublic

Authored by aaron.ballman on Jun 3 2021, 5:25 AM.

Details

Summary

Clang checks whether the type given to va_arg will automatically cause undefined behavior, but this check was issuing false positives for enumerations in C++. The issue turned out to be because typesAreCompatible() in C++ checks whether the types are *the same*, so this falls back on the type merging logic to see whether the types are mergable or not in both C and C++.

This issue was found by a user on code like:

typedef enum {
  CURLINFO_NONE,
  CURLINFO_EFFECTIVE_URL,
  CURLINFO_LASTONE = 60
} CURLINFO;

...

__builtin_va_arg(list, CURLINFO); // warn about CURLINFO promoting to 'int' being UB in C++ but not C

Given that C++ defers to C for the rules around va_arg, the behavior should be the same in both C and C++ and not diagnose because int and CURLINFO are compatible types.

Diff Detail

Event Timeline

aaron.ballman requested review of this revision.Jun 3 2021, 5:25 AM
aaron.ballman created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptJun 3 2021, 5:25 AM

I'm a little nervous about using C type merging in C++; it's not designed to support C++ types, so it might end up crashing or something like that. I think I'd prefer to explicitly convert enum types to their underlying type.

Update based on review feedback.

I'm a little nervous about using C type merging in C++; it's not designed to support C++ types, so it might end up crashing or something like that. I think I'd prefer to explicitly convert enum types to their underlying type.

I got lulled into thinking mergeTypes() worked for C++ because it handles references and cites the C++ standard first thing. But I see later that it also claims some C++ types are unreachable. So I switched to the underlying type for the enumeration, good suggestion!

efriedma accepted this revision.Jun 4 2021, 9:38 AM

LGTM

This revision is now accepted and ready to land.Jun 4 2021, 9:38 AM

Changed the test case to speculatively see if the CI pipeline is happy with this (I'm trying to avoid adding a triple to the test).

Added some more logic and new tests.

The CI pipeline pointed out a valid issue which has now been corrected -- C++ was not properly handling the case where one type was int and the other was unsigned int, which are compatible types in C but not the same type in C++. The test adds some triples and a new test case to ensure that we are able to consistently test this property in both directions.

@efriedma, would you mind taking a second look just to be sure you're still happy with the fix?

efriedma added inline comments.Jun 7 2021, 11:26 AM
clang/lib/Sema/SemaExpr.cpp
15783

This explanation doesn't seem right. Signed and unsigned types are never considered "compatible".

If I'm understanding correctly, the case this code addresses is promotion according to [conv.prom]p3: "A prvalue of an unscoped enumeration type whose underlying type is not fixed [...]". Somehow, the enum ends up with an unsigned underlying type, but we promote to int? And this doesn't happen in C somehow?

aaron.ballman added inline comments.Jun 7 2021, 12:45 PM
clang/lib/Sema/SemaExpr.cpp
15783

This explanation doesn't seem right. Signed and unsigned types are never considered "compatible".

Good point, I think that comment is wrong.

If I'm understanding correctly, the case this code addresses is promotion according to [conv.prom]p3: "A prvalue of an unscoped enumeration type whose underlying type is not fixed [...]". Somehow, the enum ends up with an unsigned underlying type, but we promote to int? And this doesn't happen in C somehow?

That's correct. What I am seeing is:

enum Unscoped { One = 0x7FFFFFFF };

C++:
PromoteType = Builtin (Int)
UnderlyingType = Builtin (UInt)

C:
PromoteType = Builtin (UInt)
UnderlyingType = Builtin (UInt)

enum Unscoped { One = 0xFFFFFFFF };

C++:
PromoteType = Builtin (UInt)
UnderlyingType = Builtin (UInt)

C:
PromoteType = Builtin (UInt)
UnderlyingType = Builtin (UInt)

At least on i386-pc-unknown.

So I think this code is almost correct for that test, but is over-constrained by only doing this in C++. WDYT?

efriedma added inline comments.Jun 7 2021, 1:14 PM
clang/lib/Sema/SemaExpr.cpp
15775

If we're not going to take advantage of the C notion of compatibility, might as well just check hasSameType().

15783

That makes more sense.

Not sure this particular issue can show up in C; there's a check for C++ in Sema::ActOnEnumBody. But no harm at least.

Updated the comment and removed a check for C++ mode.

aaron.ballman added inline comments.Jun 8 2021, 4:23 AM
clang/lib/Sema/SemaExpr.cpp
15775

We do still make use of that in C. For example, mergeTypes() (called by typesAreCompatible() in C mode) also does work for functions without a prototype (a distinctly C concept), special logic for merging enums and integers, qualifier handling, etc.

15789–15790

I believe I could switch to using hasSameType() here though, if you preferred.

efriedma accepted this revision.Jun 8 2021, 1:50 PM

LGTM

aaron.ballman closed this revision.Jun 9 2021, 4:19 AM

Thank you for the reviews, @efriedma! I have committed in c92f505346b80fd053ef191bbc66810c9d564b0c