Skip to content

Commit c92f505

Browse files
committed
Correct the behavior of va_arg checking in C++
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 uses custom logic if the type compatibility check fails. 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); // false positive warning 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 enough" types for va_arg.
1 parent c25572b commit c92f505

File tree

2 files changed

+68
-3
lines changed

2 files changed

+68
-3
lines changed

clang/lib/Sema/SemaExpr.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15752,8 +15752,46 @@ ExprResult Sema::BuildVAArgExpr(SourceLocation BuiltinLoc,
1575215752
QualType PromoteType;
1575315753
if (TInfo->getType()->isPromotableIntegerType()) {
1575415754
PromoteType = Context.getPromotedIntegerType(TInfo->getType());
15755-
if (Context.typesAreCompatible(PromoteType, TInfo->getType()))
15755+
// [cstdarg.syn]p1 defers the C++ behavior to what the C standard says,
15756+
// and C2x 7.16.1.1p2 says, in part:
15757+
// If type is not compatible with the type of the actual next argument
15758+
// (as promoted according to the default argument promotions), the
15759+
// behavior is undefined, except for the following cases:
15760+
// - both types are pointers to qualified or unqualified versions of
15761+
// compatible types;
15762+
// - one type is a signed integer type, the other type is the
15763+
// corresponding unsigned integer type, and the value is
15764+
// representable in both types;
15765+
// - one type is pointer to qualified or unqualified void and the
15766+
// other is a pointer to a qualified or unqualified character type.
15767+
// Given that type compatibility is the primary requirement (ignoring
15768+
// qualifications), you would think we could call typesAreCompatible()
15769+
// directly to test this. However, in C++, that checks for *same type*,
15770+
// which causes false positives when passing an enumeration type to
15771+
// va_arg. Instead, get the underlying type of the enumeration and pass
15772+
// that.
15773+
QualType UnderlyingType = TInfo->getType();
15774+
if (const auto *ET = UnderlyingType->getAs<EnumType>())
15775+
UnderlyingType = ET->getDecl()->getIntegerType();
15776+
if (Context.typesAreCompatible(PromoteType, UnderlyingType,
15777+
/*CompareUnqualified*/ true))
1575615778
PromoteType = QualType();
15779+
15780+
// If the types are still not compatible, we need to test whether the
15781+
// promoted type and the underlying type are the same except for
15782+
// signedness. Ask the AST for the correctly corresponding type and see
15783+
// if that's compatible.
15784+
if (!PromoteType.isNull() &&
15785+
PromoteType->isUnsignedIntegerType() !=
15786+
UnderlyingType->isUnsignedIntegerType()) {
15787+
UnderlyingType =
15788+
UnderlyingType->isUnsignedIntegerType()
15789+
? Context.getCorrespondingSignedType(UnderlyingType)
15790+
: Context.getCorrespondingUnsignedType(UnderlyingType);
15791+
if (Context.typesAreCompatible(PromoteType, UnderlyingType,
15792+
/*CompareUnqualified*/ true))
15793+
PromoteType = QualType();
15794+
}
1575715795
}
1575815796
if (TInfo->getType()->isSpecificBuiltinType(BuiltinType::Float))
1575915797
PromoteType = Context.DoubleTy;

clang/test/SemaCXX/varargs.cpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %clang_cc1 -std=c++03 -verify %s
2-
// RUN: %clang_cc1 -std=c++11 -verify %s
1+
// RUN: %clang_cc1 -std=c++03 -Wno-c++11-extensions -triple i386-pc-unknown -verify %s
2+
// RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin9 -verify %s
33

44
__builtin_va_list ap;
55

@@ -28,6 +28,33 @@ void record_context(int a, ...) {
2828
};
2929
}
3030

31+
// Ensure the correct behavior for promotable type UB checking.
32+
void promotable(int a, ...) {
33+
enum Unscoped1 { One = 0x7FFFFFFF };
34+
(void)__builtin_va_arg(ap, Unscoped1); // ok
35+
36+
enum Unscoped2 { Two = 0xFFFFFFFF };
37+
(void)__builtin_va_arg(ap, Unscoped2); // ok
38+
39+
enum class Scoped { Three };
40+
(void)__builtin_va_arg(ap, Scoped); // ok
41+
42+
enum Fixed : int { Four };
43+
(void)__builtin_va_arg(ap, Fixed); // ok
44+
45+
enum FixedSmall : char { Five };
46+
(void)__builtin_va_arg(ap, FixedSmall); // expected-warning {{second argument to 'va_arg' is of promotable type 'FixedSmall'; this va_arg has undefined behavior because arguments will be promoted to 'int'}}
47+
48+
enum FixedLarge : long long { Six };
49+
(void)__builtin_va_arg(ap, FixedLarge); // ok
50+
51+
// Ensure that qualifiers are ignored.
52+
(void)__builtin_va_arg(ap, const volatile int); // ok
53+
54+
// Ensure that signed vs unsigned doesn't matter either.
55+
(void)__builtin_va_arg(ap, unsigned int);
56+
}
57+
3158
#if __cplusplus >= 201103L
3259
// We used to have bugs identifying the correct enclosing function scope in a
3360
// lambda.

0 commit comments

Comments
 (0)