YexuanXiao 7c402b8b81
Reland [Clang] Make the SizeType, SignedSizeType and PtrdiffType be named sugar types (#149613)
The checks for the 'z' and 't' format specifiers added in the original
PR #143653 had some issues and were overly strict, causing some build
failures and were consequently reverted at
4c85bf2fe8.

In the latest commit
27c58629ec,
I relaxed the checks for the 'z' and 't' format specifiers, so warnings
are now only issued when they are used with mismatched types.

The original intent of these checks was to diagnose code that assumes
the underlying type of `size_t` is `unsigned` or `unsigned long`, for
example:

```c
printf("%zu", 1ul); // Not portable, but not an error when size_t is unsigned long
```  

However, it produced a significant number of false positives. This was
partly because Clang does not treat the `typedef` `size_t` and
`__size_t` as having a common "sugar" type, and partly because a large
amount of existing code either assumes `unsigned` (or `unsigned long`)
is `size_t`, or they define the equivalent of size_t in their own way
(such as
sanitizer_internal_defs.h).2e67dcfdcd/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h (L203)
2025-07-19 03:44:14 -03:00

319 lines
16 KiB
Objective-C

// RUN: %clang_cc1 -triple %itanium_abi_triple -fsyntax-only -fblocks -verify %s
// RUN: %clang_cc1 -triple %itanium_abi_triple -fdiagnostics-parseable-fixits -fblocks %s 2>&1 | FileCheck %s
@class NSString;
extern void NSLog(NSString *, ...);
int printf(const char * restrict, ...) ;
void test_integer_correction (int x) {
printf("%d", x); // no-warning
printf("%s", x); // expected-warning{{format specifies type 'char *' but the argument has type 'int'}}
printf("%lf", x); // expected-warning{{format specifies type 'double' but the argument has type 'int'}}
// CHECK: fix-it:"{{.*}}":{10:11-10:13}:"%d"
// CHECK: fix-it:"{{.*}}":{11:11-11:14}:"%d"
NSLog(@"%d", x); // no-warning
NSLog(@"%s", x); // expected-warning{{format specifies type 'char *' but the argument has type 'int'}}
NSLog(@"%lf", x); // expected-warning{{format specifies type 'double' but the argument has type 'int'}}
NSLog(@"%@", x); // expected-warning{{format specifies type 'id' but the argument has type 'int'}}
// CHECK: fix-it:"{{.*}}":{16:11-16:13}:"%d"
// CHECK: fix-it:"{{.*}}":{17:11-17:14}:"%d"
// CHECK: fix-it:"{{.*}}":{18:11-18:13}:"%d"
}
void test_string_correction (char *x) {
printf("%d", x); // expected-warning{{format specifies type 'int' but the argument has type 'char *'}}
printf("%s", x); // no-warning
printf("%lf", x); // expected-warning{{format specifies type 'double' but the argument has type 'char *'}}
// CHECK: fix-it:"{{.*}}":{25:11-25:13}:"%s"
// CHECK: fix-it:"{{.*}}":{27:11-27:14}:"%s"
NSLog(@"%d", x); // expected-warning{{format specifies type 'int' but the argument has type 'char *'}}
NSLog(@"%s", x); // no-warning
NSLog(@"%lf", x); // expected-warning{{format specifies type 'double' but the argument has type 'char *'}}
NSLog(@"%@", x); // expected-warning{{format specifies type 'id' but the argument has type 'char *'}}
// CHECK: fix-it:"{{.*}}":{31:11-31:13}:"%s"
// CHECK: fix-it:"{{.*}}":{33:11-33:14}:"%s"
// CHECK: fix-it:"{{.*}}":{34:11-34:13}:"%s"
}
void test_object_correction (id x) {
NSLog(@"%d", x); // expected-warning{{format specifies type 'int' but the argument has type 'id'}}
NSLog(@"%s", x); // expected-warning{{format specifies type 'char *' but the argument has type 'id'}}
NSLog(@"%lf", x); // expected-warning{{format specifies type 'double' but the argument has type 'id'}}
NSLog(@"%@", x); // no-warning
// CHECK: fix-it:"{{.*}}":{41:11-41:13}:"%@"
// CHECK: fix-it:"{{.*}}":{42:11-42:13}:"%@"
// CHECK: fix-it:"{{.*}}":{43:11-43:14}:"%@"
}
typedef const struct __CFString * __attribute__((NSObject)) CFStringRef;
void test_cf_object_correction (CFStringRef x) {
NSLog(@"%d", x); // expected-warning{{format specifies type 'int' but the argument has type 'CFStringRef'}}
NSLog(@"%s", x); // expected-warning{{format specifies type 'char *' but the argument has type 'CFStringRef'}}
NSLog(@"%lf", x); // expected-warning{{format specifies type 'double' but the argument has type 'CFStringRef'}}
NSLog(@"%@", x); // no-warning
// CHECK: fix-it:"{{.*}}":{52:11-52:13}:"%@"
// CHECK: fix-it:"{{.*}}":{53:11-53:13}:"%@"
// CHECK: fix-it:"{{.*}}":{54:11-54:14}:"%@"
}
typedef void (^block_t)(void);
void test_block_correction (block_t x) {
NSLog(@"%d", x); // expected-warning{{format specifies type 'int' but the argument has type 'block_t'}}
NSLog(@"%s", x); // expected-warning{{format specifies type 'char *' but the argument has type 'block_t'}}
NSLog(@"%lf", x); // expected-warning{{format specifies type 'double' but the argument has type 'block_t'}}
NSLog(@"%@", x); // no-warning
// CHECK: fix-it:"{{.*}}":{63:11-63:13}:"%@"
// CHECK: fix-it:"{{.*}}":{64:11-64:13}:"%@"
// CHECK: fix-it:"{{.*}}":{65:11-65:14}:"%@"
}
void test_class_correction (Class x) {
NSLog(@"%d", x); // expected-warning{{format specifies type 'int' but the argument has type 'Class'}}
NSLog(@"%s", x); // expected-warning{{format specifies type 'char *' but the argument has type 'Class'}}
NSLog(@"%lf", x); // expected-warning{{format specifies type 'double' but the argument has type 'Class'}}
NSLog(@"%@", x); // no-warning
// CHECK: fix-it:"{{.*}}":{73:11-73:13}:"%@"
// CHECK: fix-it:"{{.*}}":{74:11-74:13}:"%@"
// CHECK: fix-it:"{{.*}}":{75:11-75:14}:"%@"
}
typedef enum : int { NSUTF8StringEncoding = 8 } NSStringEncoding;
void test_fixed_enum_correction(NSStringEncoding x) {
NSLog(@"%@", x); // expected-warning{{format specifies type 'id' but the argument has underlying type 'int'}}
// CHECK: fix-it:"{{.*}}":{85:11-85:13}:"%d"
}
typedef __SIZE_TYPE__ size_t;
enum SomeSize : size_t { IntegerSize = sizeof(int) };
void test_named_fixed_enum_correction(enum SomeSize x) {
NSLog(@"%@", x); // expected-warning{{format specifies type 'id' but the argument has underlying type 'size_t' (aka}}
// CHECK: fix-it:"{{.*}}":{92:11-92:13}:"%zu"
}
typedef unsigned char uint8_t;
void test_char(char c, signed char s, unsigned char u, uint8_t n) {
NSLog(@"%s", c); // expected-warning{{format specifies type 'char *' but the argument has type 'char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%lf", c); // expected-warning{{format specifies type 'double' but the argument has type 'char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%c"
NSLog(@"%@", c); // expected-warning{{format specifies type 'id' but the argument has type 'char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%c", c); // no-warning
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%s", s); // expected-warning{{format specifies type 'char *' but the argument has type 'signed char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%lf", s); // expected-warning{{format specifies type 'double' but the argument has type 'signed char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%c"
NSLog(@"%@", s); // expected-warning{{format specifies type 'id' but the argument has type 'signed char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%c", s); // no-warning
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%s", u); // expected-warning{{format specifies type 'char *' but the argument has type 'unsigned char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%lf", u); // expected-warning{{format specifies type 'double' but the argument has type 'unsigned char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%c"
NSLog(@"%@", u); // expected-warning{{format specifies type 'id' but the argument has type 'unsigned char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%c", u); // no-warning
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%s", n); // expected-warning{{format specifies type 'char *' but the argument has type 'uint8_t' (aka 'unsigned char')}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%hhu"
NSLog(@"%lf", n); // expected-warning{{format specifies type 'double' but the argument has type 'uint8_t' (aka 'unsigned char')}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%hhu"
NSLog(@"%@", n); // expected-warning{{format specifies type 'id' but the argument has type 'uint8_t' (aka 'unsigned char')}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%hhu"
NSLog(@"%c", n); // no-warning
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%hhu"
NSLog(@"%s", 'a'); // expected-warning{{format specifies type 'char *' but the argument has type 'char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%lf", 'a'); // expected-warning{{format specifies type 'double' but the argument has type 'char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%c"
NSLog(@"%@", 'a'); // expected-warning{{format specifies type 'id' but the argument has type 'char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%c", 'a'); // no-warning
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
NSLog(@"%s", 'abcd'); // expected-warning{{format specifies type 'char *' but the argument has type 'int'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
NSLog(@"%lf", 'abcd'); // expected-warning{{format specifies type 'double' but the argument has type 'int'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%d"
NSLog(@"%@", 'abcd'); // expected-warning{{format specifies type 'id' but the argument has type 'int'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
NSLog(@"%hhd", 'a'); // expected-warning{{format specifies type 'char' but the argument has type 'int'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:15}:"%d"
NSLog(@"%hhu", 'a'); // expected-warning{{format specifies type 'unsigned char' but the argument has type 'int'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:15}:"%d"
}
void multichar_constants_false_negative(void) {
// The value of a multi-character constant is implementation-defined, but
// almost certainly shouldn't be printed with %c. However, the current
// type-checker expects %c to correspond to an integer argument, because
// many C library functions like fgetc() actually return an int (using -1
// as a sentinel).
NSLog(@"%c", 'abcd'); // missing-warning{{format specifies type 'char' but the argument has type 'int'}}
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
}
void test_percent_C(void) {
const unsigned short data = 'a';
NSLog(@"%C", data); // no-warning
NSLog(@"%C", 0x260300); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'int'}}
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unsigned short)"
typedef unsigned short unichar;
NSLog(@"%C", 0x260300); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'int'}}
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unichar)"
NSLog(@"%C", data ? 0x2F0000 : 0x260300); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'int'}}
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%d"
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unichar)("
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:42-[[@LINE-3]]:42}:")"
NSLog(@"%C", 0.0); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'double'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%f"
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"(unichar)"
NSLog(@"%C", (char)0x260300);
NSLog(@"%C", 'a'); // expected-warning{{format specifies type 'unichar' (aka 'unsigned short') but the argument has type 'char'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%c"
// CHECK-NOT: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:22}:"(unichar)"
}
void testSignedness(long i, unsigned long u) {
printf("%d", u); // expected-warning{{format specifies type 'int' but the argument has type 'unsigned long'}}
printf("%i", u); // expected-warning{{format specifies type 'int' but the argument has type 'unsigned long'}}
printf("%u", i); // expected-warning{{format specifies type 'unsigned int' but the argument has type 'long'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:11-[[@LINE-4]]:13}:"%lu"
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:11-[[@LINE-4]]:13}:"%lu"
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:11-[[@LINE-4]]:13}:"%ld"
printf("%+d", u); // expected-warning{{format specifies type 'int' but the argument has type 'unsigned long'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:11-[[@LINE-2]]:14}:"%+ld"
}
void testSizeTypes(void) {
printf("%zu", 0.f); // expected-warning-re{{format specifies type 'size_t' (aka '{{.+}}') but the argument has type 'float'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%f"
printf("%zd", 0.f); // expected-warning-re{{format specifies type 'signed size_t' (aka '{{.+}}') but the argument has type 'float'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%f"
short x;
#if !defined(__ANDROID__) && !defined(__Fuchsia__)
printf("%zn", &x); // expected-warning-re{{format specifies type 'signed size_t *' (aka '{{.+}}') but the argument has type 'short *'}}
#else
printf("%zn", &x); // expected-warning-re{{format specifies type 'signed size_t *' (aka '{{.+}}') but the argument has type 'short *'}}
// expected-warning@-1 {{'%n' specifier not supported on this platform}}
#endif // !defined(__ANDROID__) && !defined(__Fuchsia__)
// PrintfSpecifier::fixType doesn't handle %n, so a fix-it is not emitted,
// see the comment in PrintfSpecifier::fixType in PrintfFormatString.cpp.
}
typedef __PTRDIFF_TYPE__ ptrdiff_t;
#define __UNSIGNED_PTRDIFF_TYPE__ \
__typeof__(_Generic((__PTRDIFF_TYPE__)0, \
long long int : (unsigned long long int)0, \
long int : (unsigned long int)0, \
int : (unsigned int)0, \
short : (unsigned short)0, \
signed char : (unsigned char)0))
void testPtrDiffTypes(void) {
__UNSIGNED_PTRDIFF_TYPE__ p1 = 0;
printf("%tu", p1); // No warning.
printf("%tu", 0.f); // expected-warning-re{{format specifies type 'unsigned ptrdiff_t' (aka '{{.+}}') but the argument has type 'float'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%f"
ptrdiff_t p2 = 0;
printf("%td", p2); // No warning.
printf("%td", 0.f); // expected-warning-re{{format specifies type 'ptrdiff_t' (aka '{{.+}}') but the argument has type 'float'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%f"
ptrdiff_t p3 = 0;
#if !defined(__ANDROID__) && !defined(__Fuchsia__)
printf("%tn", &p3); // No warning.
#else
printf("%tn", &p3); // expected-warning{{'%n' specifier not supported on this platform}}
#endif // !defined(__ANDROID__) && !defined(__Fuchsia__)
short x;
#if !defined(__ANDROID__) && !defined(__Fuchsia__)
printf("%tn", &x); // expected-warning-re{{format specifies type 'ptrdiff_t *' (aka '{{.+}}') but the argument has type 'short *'}}
// PrintfSpecifier::fixType doesn't handle %n, so a fix-it is not emitted,
// see the comment in PrintfSpecifier::fixType in PrintfFormatString.cpp.
#else
printf("%tn", &x); // expected-warning-re{{format specifies type 'ptrdiff_t *' (aka '{{.+}}') but the argument has type 'short *'}}
// expected-warning@-1 {{'%n' specifier not supported on this platform}}
#endif // !defined(__ANDROID__) && !defined(__Fuchsia__)
}
void testEnum(void) {
typedef enum {
ImplicitA = 1,
ImplicitB = 2
} Implicit;
typedef enum {
ImplicitLLA = 0,
ImplicitLLB = ~0ULL
} ImplicitLongLong;
typedef enum : short {
ExplicitA = 0,
ExplicitB
} ExplicitShort;
printf("%f", (Implicit)0); // expected-warning{{format specifies type 'double' but the argument has underlying type}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%{{[du]}}"
printf("%f", (ImplicitLongLong)0); // expected-warning{{format specifies type 'double' but the argument has underlying type}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%{{l*[du]}}"
printf("%f", (ExplicitShort)0); // expected-warning{{format specifies type 'double' but the argument has underlying type 'short'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%hd"
}