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.
This commit is contained in:
Aaron Ballman 2021-06-09 07:15:15 -04:00
parent c25572bf29
commit c92f505346
2 changed files with 68 additions and 3 deletions

View File

@ -15752,8 +15752,46 @@ ExprResult Sema::BuildVAArgExpr(SourceLocation BuiltinLoc,
QualType PromoteType;
if (TInfo->getType()->isPromotableIntegerType()) {
PromoteType = Context.getPromotedIntegerType(TInfo->getType());
if (Context.typesAreCompatible(PromoteType, TInfo->getType()))
// [cstdarg.syn]p1 defers the C++ behavior to what the C standard says,
// and C2x 7.16.1.1p2 says, in part:
// If type is not compatible with the type of the actual next argument
// (as promoted according to the default argument promotions), the
// behavior is undefined, except for the following cases:
// - both types are pointers to qualified or unqualified versions of
// compatible types;
// - one type is a signed integer type, the other type is the
// corresponding unsigned integer type, and the value is
// representable in both types;
// - one type is pointer to qualified or unqualified void and the
// other is a pointer to a qualified or unqualified character type.
// Given that type compatibility is the primary requirement (ignoring
// qualifications), you would think we could call typesAreCompatible()
// directly to test this. However, in C++, that checks for *same type*,
// which causes false positives when passing an enumeration type to
// va_arg. Instead, get the underlying type of the enumeration and pass
// that.
QualType UnderlyingType = TInfo->getType();
if (const auto *ET = UnderlyingType->getAs<EnumType>())
UnderlyingType = ET->getDecl()->getIntegerType();
if (Context.typesAreCompatible(PromoteType, UnderlyingType,
/*CompareUnqualified*/ true))
PromoteType = QualType();
// If the types are still not compatible, we need to test whether the
// promoted type and the underlying type are the same except for
// signedness. Ask the AST for the correctly corresponding type and see
// if that's compatible.
if (!PromoteType.isNull() &&
PromoteType->isUnsignedIntegerType() !=
UnderlyingType->isUnsignedIntegerType()) {
UnderlyingType =
UnderlyingType->isUnsignedIntegerType()
? Context.getCorrespondingSignedType(UnderlyingType)
: Context.getCorrespondingUnsignedType(UnderlyingType);
if (Context.typesAreCompatible(PromoteType, UnderlyingType,
/*CompareUnqualified*/ true))
PromoteType = QualType();
}
}
if (TInfo->getType()->isSpecificBuiltinType(BuiltinType::Float))
PromoteType = Context.DoubleTy;

View File

@ -1,5 +1,5 @@
// RUN: %clang_cc1 -std=c++03 -verify %s
// RUN: %clang_cc1 -std=c++11 -verify %s
// RUN: %clang_cc1 -std=c++03 -Wno-c++11-extensions -triple i386-pc-unknown -verify %s
// RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin9 -verify %s
__builtin_va_list ap;
@ -28,6 +28,33 @@ void record_context(int a, ...) {
};
}
// Ensure the correct behavior for promotable type UB checking.
void promotable(int a, ...) {
enum Unscoped1 { One = 0x7FFFFFFF };
(void)__builtin_va_arg(ap, Unscoped1); // ok
enum Unscoped2 { Two = 0xFFFFFFFF };
(void)__builtin_va_arg(ap, Unscoped2); // ok
enum class Scoped { Three };
(void)__builtin_va_arg(ap, Scoped); // ok
enum Fixed : int { Four };
(void)__builtin_va_arg(ap, Fixed); // ok
enum FixedSmall : char { Five };
(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'}}
enum FixedLarge : long long { Six };
(void)__builtin_va_arg(ap, FixedLarge); // ok
// Ensure that qualifiers are ignored.
(void)__builtin_va_arg(ap, const volatile int); // ok
// Ensure that signed vs unsigned doesn't matter either.
(void)__builtin_va_arg(ap, unsigned int);
}
#if __cplusplus >= 201103L
// We used to have bugs identifying the correct enclosing function scope in a
// lambda.