From 998d4bae5c00ed7b0028cc7f2db5c0c29ae0d647 Mon Sep 17 00:00:00 2001 From: SkyCipherr Date: Thu, 12 Nov 2026 11:06:45 +0800 Subject: [PATCH 1/2] C++: suggest specifiers for simple integer format mismatches --- .../Format/WrongTypeFormatArguments.ql | 39 ++++++++++++++++++- .../2026-11-12-integer-format-suggestions.md | 4 ++ .../Linux/WrongTypeFormatArguments.expected | 18 +++++++++ .../Linux/WrongTypeFormatArguments.qlref | 1 + .../Linux/integer-hints.c | 35 +++++++++++++++++ .../IntegerFormatSuggestions/Linux/options | 1 + .../WrongTypeFormatArguments.expected | 18 +++++++++ .../Microsoft/WrongTypeFormatArguments.qlref | 1 + .../Microsoft/integer-hints.c | 35 +++++++++++++++++ .../Microsoft/options | 1 + .../WrongTypeFormatArguments.expected | 4 +- .../WrongTypeFormatArguments.expected | 8 ++-- .../WrongTypeFormatArguments.expected | 6 +-- .../WrongTypeFormatArguments.expected | 6 +-- .../WrongTypeFormatArguments.expected | 6 +-- 15 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 cpp/ql/src/change-notes/2026-11-12-integer-format-suggestions.md create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/WrongTypeFormatArguments.expected create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/WrongTypeFormatArguments.qlref create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/integer-hints.c create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/options create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/WrongTypeFormatArguments.expected create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/WrongTypeFormatArguments.qlref create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/integer-hints.c create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/options diff --git a/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql b/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql index 5842b9474f74..58253b4b1154 100644 --- a/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql +++ b/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql @@ -152,6 +152,43 @@ predicate trivialConversion(ExpectedType expected, Type actual) { */ int sizeof_IntType() { exists(IntType it | result = it.getSize()) } +/** + * Gets a suggested specifier for a simple decimal integer conversion. + * Use the promoted argument type, and do not suggest changing width or precision arguments. + * Decorated, positional, and non-decimal conversions are deliberately left unchanged. + */ +private string suggestedIntegerSpecifier(FormattingFunctionCall ffc, int n, Expr arg, Type actual) { + exists(FormatLiteral format, IntegralType t, string length, string conversion | + format = ffc.getFormat() and + ffc.getConversionArgument(n) = arg and + format.getConvSpec(n).regexpMatch("%(l|ll)?[diu]") and + t = actual.getUnspecifiedType() and + ( + t instanceof IntType and length = "" + or + t instanceof LongType and length = "l" + or + t instanceof LongLongType and length = "ll" + ) and + ( + if t.isUnsigned() + then conversion = "u" + else + if format.getConversionChar(n) = "i" + then conversion = "i" + else conversion = "d" + ) and + result = "%" + length + conversion + ) +} + +bindingset[ffc, n, arg, actual] +private string integerFormatSuggestion(FormattingFunctionCall ffc, int n, Expr arg, Type actual) { + if exists(suggestedIntegerSpecifier(ffc, n, arg, actual)) + then result = " Consider using '" + suggestedIntegerSpecifier(ffc, n, arg, actual) + "' instead." + else result = "" +} + from FormattingFunctionCall ffc, int n, Expr arg, Type expected, Type actual where ( @@ -178,4 +215,4 @@ where count(ffc.getTarget().getFormatParameterIndex()) = 1 select arg, "This format specifier for type '" + expected.getName() + "' does not match the argument type '" + - actual.getUnspecifiedType().getName() + "'." + actual.getUnspecifiedType().getName() + "'." + integerFormatSuggestion(ffc, n, arg, actual) diff --git a/cpp/ql/src/change-notes/2026-11-12-integer-format-suggestions.md b/cpp/ql/src/change-notes/2026-11-12-integer-format-suggestions.md new file mode 100644 index 000000000000..1bca7a8169f3 --- /dev/null +++ b/cpp/ql/src/change-notes/2026-11-12-integer-format-suggestions.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `cpp/wrong-type-format-argument` query now suggests a matching format specifier for simple decimal integer conversions with mismatched `int`, `long`, or `long long` arguments. The set of reported results is unchanged. diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/WrongTypeFormatArguments.expected b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/WrongTypeFormatArguments.expected new file mode 100644 index 000000000000..c1c1c29e417a --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/WrongTypeFormatArguments.expected @@ -0,0 +1,18 @@ +| integer-hints.c:7:16:7:17 | ul | This format specifier for type 'int' does not match the argument type 'unsigned long'. Consider using '%lu' instead. | +| integer-hints.c:8:16:8:17 | ll | This format specifier for type 'int' does not match the argument type 'long long'. Consider using '%lld' instead. | +| integer-hints.c:9:16:9:18 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| integer-hints.c:10:16:10:17 | ll | This format specifier for type 'int' does not match the argument type 'long long'. Consider using '%lli' instead. | +| integer-hints.c:11:18:11:18 | i | This format specifier for type 'long long' does not match the argument type 'int'. Consider using '%d' instead. | +| integer-hints.c:12:18:12:19 | ui | This format specifier for type 'unsigned long long' does not match the argument type 'unsigned int'. Consider using '%u' instead. | +| integer-hints.c:15:26:15:28 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| integer-hints.c:16:19:16:20 | ll | This format specifier for type 'int' does not match the argument type 'long long'. Consider using '%lld' instead. | +| integer-hints.c:16:23:16:25 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| integer-hints.c:26:18:26:19 | ll | This format specifier for type 'int' does not match the argument type 'long long'. | +| integer-hints.c:27:17:27:18 | ll | This format specifier for type 'int' does not match the argument type 'long long'. | +| integer-hints.c:28:18:28:19 | ll | This format specifier for type 'int' does not match the argument type 'long long'. | +| integer-hints.c:29:18:29:19 | ll | This format specifier for type 'int' does not match the argument type 'long long'. | +| integer-hints.c:30:16:30:18 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| integer-hints.c:31:16:31:16 | d | This format specifier for type 'int' does not match the argument type 'double'. | +| integer-hints.c:32:16:32:17 | ll | This format specifier for type 'char *' does not match the argument type 'long long'. | +| integer-hints.c:33:16:33:17 | ll | This format specifier for type 'void *' does not match the argument type 'long long'. | +| integer-hints.c:34:16:34:16 | i | This format specifier for type 'double' does not match the argument type 'int'. | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/WrongTypeFormatArguments.qlref b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/WrongTypeFormatArguments.qlref new file mode 100644 index 000000000000..9244006903c7 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/WrongTypeFormatArguments.qlref @@ -0,0 +1 @@ +query: Likely Bugs/Format/WrongTypeFormatArguments.ql diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/integer-hints.c b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/integer-hints.c new file mode 100644 index 000000000000..61574d504d65 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/integer-hints.c @@ -0,0 +1,35 @@ +// Independent reproduction of github/codeql#15028. Compile only: these +// deliberately mismatched printf calls must not be executed. +int printf(const char *format, ...); + +void integer_hints(int i, unsigned int ui, long l, unsigned long ul, + long long ll, unsigned long long ull, short s, double d) { + printf("%d", ul); // Original issue; alert depends on the target ABI. + printf("%d", ll); // Suggest %lld. + printf("%d", ull); // Suggest %llu. + printf("%i", ll); // Suggest %lli. + printf("%lld", i); // Suggest %d. + printf("%llu", ui); // Suggest %u. + printf("%lld", l); // On LLP64, suggest %ld. + printf("%llu", ul); // On LLP64, suggest %lu. + printf("value=%d end", ull); // Suggest %llu for the embedded conversion. + printf("%d %d", ll, ull); // Distinct suggestions for two arguments. + + printf("%d", i); // Correct: no alert. + printf("%u", ui); // Correct: no alert. + printf("%ld", l); // Correct: no alert. + printf("%lu", ul); // Correct: no alert. + printf("%lld", ll); // Correct: no alert. + printf("%llu", ull); // Correct: no alert. + printf("%d", s); // Correct after default argument promotion. + + printf("%08d", ll); // Existing alert; decorated formats out of scope. + printf("%*d", ll, i); // Width argument mismatch; no conversion suggestion. + printf("%.*d", ll, i); // Precision argument mismatch; no suggestion. + printf("%1$d", ll); // Positional format out of scope. + printf("%x", ull); // Hexadecimal formatting out of scope. + printf("%d", d); // Non-integer actual type: no suggestion. + printf("%s", ll); // Non-integer conversion: no suggestion. + printf("%p", ll); // Pointer conversion: no suggestion. + printf("%f", i); // Floating-point conversion: no suggestion. +} diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/options b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/options new file mode 100644 index 000000000000..fcea28bbb584 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Linux/options @@ -0,0 +1 @@ +semmle-extractor-options: --clang --edg --signed_chars diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/WrongTypeFormatArguments.expected b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/WrongTypeFormatArguments.expected new file mode 100644 index 000000000000..e1dd974286ab --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/WrongTypeFormatArguments.expected @@ -0,0 +1,18 @@ +| integer-hints.c:8:16:8:17 | ll | This format specifier for type 'int' does not match the argument type 'long long'. Consider using '%lld' instead. | +| integer-hints.c:9:16:9:18 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| integer-hints.c:10:16:10:17 | ll | This format specifier for type 'int' does not match the argument type 'long long'. Consider using '%lli' instead. | +| integer-hints.c:11:18:11:18 | i | This format specifier for type 'long long' does not match the argument type 'int'. Consider using '%d' instead. | +| integer-hints.c:12:18:12:19 | ui | This format specifier for type 'unsigned long long' does not match the argument type 'unsigned int'. Consider using '%u' instead. | +| integer-hints.c:13:18:13:18 | l | This format specifier for type 'long long' does not match the argument type 'long'. Consider using '%ld' instead. | +| integer-hints.c:14:18:14:19 | ul | This format specifier for type 'unsigned long long' does not match the argument type 'unsigned long'. Consider using '%lu' instead. | +| integer-hints.c:15:26:15:28 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| integer-hints.c:16:19:16:20 | ll | This format specifier for type 'int' does not match the argument type 'long long'. Consider using '%lld' instead. | +| integer-hints.c:16:23:16:25 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| integer-hints.c:26:18:26:19 | ll | This format specifier for type 'int' does not match the argument type 'long long'. | +| integer-hints.c:27:17:27:18 | ll | This format specifier for type 'int' does not match the argument type 'long long'. | +| integer-hints.c:28:18:28:19 | ll | This format specifier for type 'int' does not match the argument type 'long long'. | +| integer-hints.c:29:18:29:19 | ll | This format specifier for type 'int' does not match the argument type 'long long'. | +| integer-hints.c:30:16:30:18 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| integer-hints.c:31:16:31:16 | d | This format specifier for type 'int' does not match the argument type 'double'. | +| integer-hints.c:32:16:32:17 | ll | This format specifier for type 'char *' does not match the argument type 'long long'. | +| integer-hints.c:34:16:34:16 | i | This format specifier for type 'double' does not match the argument type 'int'. | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/WrongTypeFormatArguments.qlref b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/WrongTypeFormatArguments.qlref new file mode 100644 index 000000000000..9244006903c7 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/WrongTypeFormatArguments.qlref @@ -0,0 +1 @@ +query: Likely Bugs/Format/WrongTypeFormatArguments.ql diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/integer-hints.c b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/integer-hints.c new file mode 100644 index 000000000000..61574d504d65 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/integer-hints.c @@ -0,0 +1,35 @@ +// Independent reproduction of github/codeql#15028. Compile only: these +// deliberately mismatched printf calls must not be executed. +int printf(const char *format, ...); + +void integer_hints(int i, unsigned int ui, long l, unsigned long ul, + long long ll, unsigned long long ull, short s, double d) { + printf("%d", ul); // Original issue; alert depends on the target ABI. + printf("%d", ll); // Suggest %lld. + printf("%d", ull); // Suggest %llu. + printf("%i", ll); // Suggest %lli. + printf("%lld", i); // Suggest %d. + printf("%llu", ui); // Suggest %u. + printf("%lld", l); // On LLP64, suggest %ld. + printf("%llu", ul); // On LLP64, suggest %lu. + printf("value=%d end", ull); // Suggest %llu for the embedded conversion. + printf("%d %d", ll, ull); // Distinct suggestions for two arguments. + + printf("%d", i); // Correct: no alert. + printf("%u", ui); // Correct: no alert. + printf("%ld", l); // Correct: no alert. + printf("%lu", ul); // Correct: no alert. + printf("%lld", ll); // Correct: no alert. + printf("%llu", ull); // Correct: no alert. + printf("%d", s); // Correct after default argument promotion. + + printf("%08d", ll); // Existing alert; decorated formats out of scope. + printf("%*d", ll, i); // Width argument mismatch; no conversion suggestion. + printf("%.*d", ll, i); // Precision argument mismatch; no suggestion. + printf("%1$d", ll); // Positional format out of scope. + printf("%x", ull); // Hexadecimal formatting out of scope. + printf("%d", d); // Non-integer actual type: no suggestion. + printf("%s", ll); // Non-integer conversion: no suggestion. + printf("%p", ll); // Pointer conversion: no suggestion. + printf("%f", i); // Floating-point conversion: no suggestion. +} diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/options b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/options new file mode 100644 index 000000000000..668c53efd7db --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/IntegerFormatSuggestions/Microsoft/options @@ -0,0 +1 @@ +semmle-extractor-options: --microsoft --edg --target --edg win64 diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Buildless/WrongTypeFormatArguments.expected b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Buildless/WrongTypeFormatArguments.expected index 8eefcc95a24f..600cf9e8254c 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Buildless/WrongTypeFormatArguments.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Buildless/WrongTypeFormatArguments.expected @@ -1,3 +1,3 @@ -| second.cpp:26:18:26:39 | ... - ... | This format specifier for type 'int' does not match the argument type 'long'. | -| second.cpp:29:18:29:39 | ... - ... | This format specifier for type 'unsigned int' does not match the argument type 'long'. | +| second.cpp:26:18:26:39 | ... - ... | This format specifier for type 'int' does not match the argument type 'long'. Consider using '%ld' instead. | +| second.cpp:29:18:29:39 | ... - ... | This format specifier for type 'unsigned int' does not match the argument type 'long'. Consider using '%ld' instead. | | tests.c:7:18:7:18 | 1 | This format specifier for type 'char *' does not match the argument type 'int'. | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_signed_chars/WrongTypeFormatArguments.expected b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_signed_chars/WrongTypeFormatArguments.expected index f3963eff2b02..8e291793dbfd 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_signed_chars/WrongTypeFormatArguments.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_signed_chars/WrongTypeFormatArguments.expected @@ -1,16 +1,16 @@ | format.h:16:59:16:61 | str | This format specifier for type 'int' does not match the argument type 'char *'. | | format.h:16:64:16:64 | i | This format specifier for type 'double' does not match the argument type 'int'. | | format.h:16:67:16:67 | d | This format specifier for type 'char *' does not match the argument type 'double'. | -| linux.cpp:15:24:15:41 | call to get_template_value | This format specifier for type 'int' does not match the argument type 'long'. | +| linux.cpp:15:24:15:41 | call to get_template_value | This format specifier for type 'int' does not match the argument type 'long'. Consider using '%ld' instead. | | linux_c.c:11:15:11:18 | str3 | This format specifier for type 'char *' does not match the argument type 'short *'. | -| pri_macros.h:15:35:15:40 | my_u64 | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| pri_macros.h:15:35:15:40 | my_u64 | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | | printf1.h:12:27:12:27 | i | This format specifier for type 'double' does not match the argument type 'int'. | | printf1.h:18:18:18:18 | i | This format specifier for type 'void *' does not match the argument type 'int'. | | printf1.h:25:22:25:22 | i | This format specifier for type 'char *' does not match the argument type 'int'. | | printf1.h:27:19:27:20 | cs | This format specifier for type 'int' does not match the argument type 'char *'. | | printf1.h:38:18:38:30 | MYONETHOUSAND | This format specifier for type 'char *' does not match the argument type 'int'. | -| printf1.h:44:18:44:20 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. | -| printf1.h:45:18:45:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| printf1.h:44:18:44:20 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| printf1.h:45:18:45:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | | printf1.h:46:18:46:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | | printf1.h:113:17:113:17 | d | This format specifier for type 'long double' does not match the argument type 'double'. | | printf1.h:114:18:114:18 | d | This format specifier for type 'long double' does not match the argument type 'double'. | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_unsigned_chars/WrongTypeFormatArguments.expected b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_unsigned_chars/WrongTypeFormatArguments.expected index 45bf793082eb..20d32bce8f46 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_unsigned_chars/WrongTypeFormatArguments.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_unsigned_chars/WrongTypeFormatArguments.expected @@ -1,14 +1,14 @@ | format.h:16:59:16:61 | str | This format specifier for type 'int' does not match the argument type 'char *'. | | format.h:16:64:16:64 | i | This format specifier for type 'double' does not match the argument type 'int'. | | format.h:16:67:16:67 | d | This format specifier for type 'char *' does not match the argument type 'double'. | -| pri_macros.h:15:35:15:40 | my_u64 | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| pri_macros.h:15:35:15:40 | my_u64 | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | | printf1.h:12:27:12:27 | i | This format specifier for type 'double' does not match the argument type 'int'. | | printf1.h:18:18:18:18 | i | This format specifier for type 'void *' does not match the argument type 'int'. | | printf1.h:25:22:25:22 | i | This format specifier for type 'char *' does not match the argument type 'int'. | | printf1.h:27:19:27:20 | cs | This format specifier for type 'int' does not match the argument type 'char *'. | | printf1.h:38:18:38:30 | MYONETHOUSAND | This format specifier for type 'char *' does not match the argument type 'int'. | -| printf1.h:44:18:44:20 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. | -| printf1.h:45:18:45:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| printf1.h:44:18:44:20 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| printf1.h:45:18:45:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | | printf1.h:46:18:46:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | | printf1.h:130:18:130:18 | 0 | This format specifier for type 'void *' does not match the argument type 'int'. | | printf1.h:168:19:168:19 | i | This format specifier for type 'long long' does not match the argument type 'int'. | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Microsoft/WrongTypeFormatArguments.expected b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Microsoft/WrongTypeFormatArguments.expected index 6e17cecc33b9..0eaebd2c8420 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Microsoft/WrongTypeFormatArguments.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Microsoft/WrongTypeFormatArguments.expected @@ -1,14 +1,14 @@ | format.h:16:59:16:61 | str | This format specifier for type 'int' does not match the argument type 'char *'. | | format.h:16:64:16:64 | i | This format specifier for type 'double' does not match the argument type 'int'. | | format.h:16:67:16:67 | d | This format specifier for type 'char *' does not match the argument type 'double'. | -| pri_macros.h:15:35:15:40 | my_u64 | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| pri_macros.h:15:35:15:40 | my_u64 | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | | printf1.h:12:27:12:27 | i | This format specifier for type 'double' does not match the argument type 'int'. | | printf1.h:18:18:18:18 | i | This format specifier for type 'void *' does not match the argument type 'int'. | | printf1.h:25:22:25:22 | i | This format specifier for type 'char *' does not match the argument type 'int'. | | printf1.h:27:19:27:20 | cs | This format specifier for type 'int' does not match the argument type 'char *'. | | printf1.h:38:18:38:30 | MYONETHOUSAND | This format specifier for type 'char *' does not match the argument type 'int'. | -| printf1.h:44:18:44:20 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. | -| printf1.h:45:18:45:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| printf1.h:44:18:44:20 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| printf1.h:45:18:45:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | | printf1.h:46:18:46:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | | printf1.h:62:19:62:20 | ul | This format specifier for type 'size_t' does not match the argument type 'unsigned long'. | | printf1.h:68:19:68:21 | sst | This format specifier for type 'size_t' does not match the argument type 'long'. | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Microsoft_no_wchar/WrongTypeFormatArguments.expected b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Microsoft_no_wchar/WrongTypeFormatArguments.expected index 0958cba59e72..85d549be5e58 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Microsoft_no_wchar/WrongTypeFormatArguments.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Microsoft_no_wchar/WrongTypeFormatArguments.expected @@ -1,14 +1,14 @@ | format.h:16:59:16:61 | str | This format specifier for type 'int' does not match the argument type 'char *'. | | format.h:16:64:16:64 | i | This format specifier for type 'double' does not match the argument type 'int'. | | format.h:16:67:16:67 | d | This format specifier for type 'char *' does not match the argument type 'double'. | -| pri_macros.h:15:35:15:40 | my_u64 | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| pri_macros.h:15:35:15:40 | my_u64 | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | | printf1.h:12:27:12:27 | i | This format specifier for type 'double' does not match the argument type 'int'. | | printf1.h:18:18:18:18 | i | This format specifier for type 'void *' does not match the argument type 'int'. | | printf1.h:25:22:25:22 | i | This format specifier for type 'char *' does not match the argument type 'int'. | | printf1.h:27:19:27:20 | cs | This format specifier for type 'int' does not match the argument type 'char *'. | | printf1.h:38:18:38:30 | MYONETHOUSAND | This format specifier for type 'char *' does not match the argument type 'int'. | -| printf1.h:44:18:44:20 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. | -| printf1.h:45:18:45:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | +| printf1.h:44:18:44:20 | ull | This format specifier for type 'int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | +| printf1.h:45:18:45:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. Consider using '%llu' instead. | | printf1.h:46:18:46:20 | ull | This format specifier for type 'unsigned int' does not match the argument type 'unsigned long long'. | | printf1.h:62:19:62:20 | ul | This format specifier for type 'size_t' does not match the argument type 'unsigned long'. | | printf1.h:68:19:68:21 | sst | This format specifier for type 'size_t' does not match the argument type 'long'. | From 0dc2c48c80c213382d750116c46e3a265d9cf418 Mon Sep 17 00:00:00 2001 From: SkyCipherr Date: Sat, 14 Nov 2026 21:14:31 +0800 Subject: [PATCH 2/2] C++: provide integer format specifier suggestions in Printf library --- .../2026-11-14-integer-format-specifiers.md | 4 + cpp/ql/lib/semmle/code/cpp/commons/Printf.qll | 36 +++++++++ .../Format/WrongTypeFormatArguments.ql | 40 ++-------- .../integerConversionSpecifier/Linux/options | 1 + .../Linux/suggestedSpecifier.expected | 61 +++++++++++++++ .../Linux/suggestedSpecifier.ql | 15 ++++ .../integerConversionSpecifier/Linux/test.c | 1 + .../Microsoft/options | 1 + .../Microsoft/suggestedSpecifier.expected | 61 +++++++++++++++ .../Microsoft/suggestedSpecifier.ql | 15 ++++ .../Microsoft/test.c | 1 + .../printf/integerConversionSpecifier/cases.h | 75 +++++++++++++++++++ 12 files changed, 279 insertions(+), 32 deletions(-) create mode 100644 cpp/ql/lib/change-notes/2026-11-14-integer-format-specifiers.md create mode 100644 cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/options create mode 100644 cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/suggestedSpecifier.expected create mode 100644 cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/suggestedSpecifier.ql create mode 100644 cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/test.c create mode 100644 cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/options create mode 100644 cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/suggestedSpecifier.expected create mode 100644 cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/suggestedSpecifier.ql create mode 100644 cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/test.c create mode 100644 cpp/ql/test/library-tests/printf/integerConversionSpecifier/cases.h diff --git a/cpp/ql/lib/change-notes/2026-11-14-integer-format-specifiers.md b/cpp/ql/lib/change-notes/2026-11-14-integer-format-specifiers.md new file mode 100644 index 000000000000..436614bc147a --- /dev/null +++ b/cpp/ql/lib/change-notes/2026-11-14-integer-format-specifiers.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added `FormatLiteral.getSuggestedIntegerConversionSpecifier` to `semmle.code.cpp.commons.Printf`. It provides matching specifiers for plain decimal integer conversions and promoted `int`, `long`, and `long long` argument types, including unsigned variants. diff --git a/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll b/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll index dfda2f4796c5..f08338194815 100644 --- a/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll +++ b/cpp/ql/lib/semmle/code/cpp/commons/Printf.qll @@ -704,6 +704,42 @@ class FormatLiteral extends Literal instanceof StringLiteral { */ string getConversionChar(int n) { this.parseConvSpec(n, _, _, _, _, _, _, result) } + /** + * Gets a suggested replacement for the `n`th conversion specifier for an + * argument of type `type`, after default argument promotions. + * + * This only handles plain decimal integer conversions (`d`, `i`, and `u`, + * optionally with `l` or `ll`) and the signed and unsigned variants of `int`, + * `long`, and `long long`. There is no result for other types or conversions, + * or for conversions with flags, width, precision, or positional parameters. + * The result includes `%` and preserves `i` for signed integer arguments. + * + * This does not check whether the existing specifier matches `type`. + */ + string getSuggestedIntegerConversionSpecifier(int n, Type type) { + exists(IntegralType t, string length, string conversion, string originalConversion | + this.parseConvSpec(n, _, "", "", "", "", ["", "l", "ll"], originalConversion) and + originalConversion = ["d", "i", "u"] and + t = type.getUnspecifiedType() and + ( + t instanceof IntType and length = "" + or + t instanceof LongType and length = "l" + or + t instanceof LongLongType and length = "ll" + ) and + ( + if t.isUnsigned() + then conversion = "u" + else + if originalConversion = "i" + then conversion = "i" + else conversion = "d" + ) and + result = "%" + length + conversion + ) + } + /** * Gets the size of pointers in the target this formatting function is * compiled for. diff --git a/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql b/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql index 58253b4b1154..c1b246cde609 100644 --- a/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql +++ b/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql @@ -152,40 +152,16 @@ predicate trivialConversion(ExpectedType expected, Type actual) { */ int sizeof_IntType() { exists(IntType it | result = it.getSize()) } -/** - * Gets a suggested specifier for a simple decimal integer conversion. - * Use the promoted argument type, and do not suggest changing width or precision arguments. - * Decorated, positional, and non-decimal conversions are deliberately left unchanged. - */ -private string suggestedIntegerSpecifier(FormattingFunctionCall ffc, int n, Expr arg, Type actual) { - exists(FormatLiteral format, IntegralType t, string length, string conversion | - format = ffc.getFormat() and - ffc.getConversionArgument(n) = arg and - format.getConvSpec(n).regexpMatch("%(l|ll)?[diu]") and - t = actual.getUnspecifiedType() and - ( - t instanceof IntType and length = "" - or - t instanceof LongType and length = "l" - or - t instanceof LongLongType and length = "ll" - ) and - ( - if t.isUnsigned() - then conversion = "u" - else - if format.getConversionChar(n) = "i" - then conversion = "i" - else conversion = "d" - ) and - result = "%" + length + conversion - ) -} - bindingset[ffc, n, arg, actual] private string integerFormatSuggestion(FormattingFunctionCall ffc, int n, Expr arg, Type actual) { - if exists(suggestedIntegerSpecifier(ffc, n, arg, actual)) - then result = " Consider using '" + suggestedIntegerSpecifier(ffc, n, arg, actual) + "' instead." + if + ffc.getConversionArgument(n) = arg and + exists(ffc.getFormat().(FormatLiteral).getSuggestedIntegerConversionSpecifier(n, actual)) + then + result = + " Consider using '" + + ffc.getFormat().(FormatLiteral).getSuggestedIntegerConversionSpecifier(n, actual) + + "' instead." else result = "" } diff --git a/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/options b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/options new file mode 100644 index 000000000000..fcea28bbb584 --- /dev/null +++ b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/options @@ -0,0 +1 @@ +semmle-extractor-options: --clang --edg --signed_chars diff --git a/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/suggestedSpecifier.expected b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/suggestedSpecifier.expected new file mode 100644 index 000000000000..7fd732531929 --- /dev/null +++ b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/suggestedSpecifier.expected @@ -0,0 +1,61 @@ +| ../cases.h:11:10:11:13 | %d | 0 | %d | %d | +| ../cases.h:12:10:12:13 | %d | 0 | %u | %u | +| ../cases.h:13:10:13:13 | %d | 0 | %ld | %ld | +| ../cases.h:14:10:14:13 | %d | 0 | %lu | %lu | +| ../cases.h:15:10:15:13 | %d | 0 | %lld | %lld | +| ../cases.h:16:10:16:13 | %d | 0 | %llu | %llu | +| ../cases.h:17:10:17:13 | %i | 0 | %i | %i | +| ../cases.h:18:10:18:13 | %i | 0 | %u | %u | +| ../cases.h:19:10:19:13 | %i | 0 | %li | %li | +| ../cases.h:20:10:20:13 | %i | 0 | %lu | %lu | +| ../cases.h:21:10:21:13 | %i | 0 | %lli | %lli | +| ../cases.h:22:10:22:13 | %i | 0 | %llu | %llu | +| ../cases.h:23:10:23:13 | %u | 0 | %d | %d | +| ../cases.h:24:10:24:14 | %ld | 0 | %d | %d | +| ../cases.h:25:10:25:14 | %li | 0 | %u | %u | +| ../cases.h:26:10:26:14 | %lu | 0 | %lld | %lld | +| ../cases.h:27:10:27:15 | %lld | 0 | %ld | %ld | +| ../cases.h:28:10:28:15 | %lli | 0 | %llu | %llu | +| ../cases.h:29:10:29:15 | %llu | 0 | %lu | %lu | +| ../cases.h:30:10:30:13 | %d | 0 | %lu | %lu | +| ../cases.h:32:10:32:15 | %lld | 0 | %d | | +| ../cases.h:33:10:33:15 | %lld | 0 | %d | | +| ../cases.h:34:10:34:15 | %lld | 0 | %d | | +| ../cases.h:35:10:35:15 | %lld | 0 | %d | | +| ../cases.h:36:10:36:15 | %lld | 0 | | | +| ../cases.h:38:10:38:23 | value=%d end | 0 | %llu | %llu | +| ../cases.h:39:10:39:19 | %% %d %i | 0 | %ld | %ld | +| ../cases.h:39:10:39:19 | %% %d %i | 1 | %llu | %llu | +| ../cases.h:42:10:42:14 | %+d | 0 | | | +| ../cases.h:43:10:43:14 | % d | 0 | | | +| ../cases.h:44:10:44:14 | %-d | 0 | | | +| ../cases.h:45:10:45:14 | %#d | 0 | | | +| ../cases.h:46:10:46:14 | %'d | 0 | | | +| ../cases.h:47:10:47:15 | %08d | 0 | | | +| ../cases.h:48:10:48:15 | %12d | 0 | | | +| ../cases.h:49:10:49:14 | %.d | 0 | | | +| ../cases.h:50:10:50:15 | %.3d | 0 | | | +| ../cases.h:51:10:51:14 | %*d | 0 | | | +| ../cases.h:52:10:52:15 | %.*d | 0 | | | +| ../cases.h:53:10:53:15 | %1$d | 0 | | | +| ../cases.h:54:10:54:20 | %2$d %1$i | 0 | | | +| ../cases.h:54:10:54:20 | %2$d %1$i | 1 | | | +| ../cases.h:56:10:56:15 | %hhd | 0 | | | +| ../cases.h:57:10:57:14 | %hd | 0 | | | +| ../cases.h:58:10:58:14 | %jd | 0 | | | +| ../cases.h:59:10:59:14 | %zd | 0 | | | +| ../cases.h:60:10:60:14 | %td | 0 | | | +| ../cases.h:61:10:61:14 | %qd | 0 | | | +| ../cases.h:62:10:62:16 | %I64d | 0 | | | +| ../cases.h:63:10:63:13 | %x | 0 | | | +| ../cases.h:64:10:64:13 | %X | 0 | | | +| ../cases.h:65:10:65:13 | %o | 0 | | | +| ../cases.h:66:10:66:13 | %c | 0 | | | +| ../cases.h:67:10:67:13 | %s | 0 | | | +| ../cases.h:68:10:68:13 | %p | 0 | | | +| ../cases.h:69:10:69:13 | %f | 0 | | | +| ../cases.h:70:10:70:13 | %n | 0 | | | +| ../cases.h:71:10:71:13 | %d | 0 | | | +| ../cases.h:72:10:72:13 | %d | 0 | | | +| ../cases.h:73:10:73:13 | %d | 0 | | | +| ../cases.h:74:10:74:14 | %kd | 0 | | | diff --git a/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/suggestedSpecifier.ql b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/suggestedSpecifier.ql new file mode 100644 index 000000000000..b8fbd970b81a --- /dev/null +++ b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/suggestedSpecifier.ql @@ -0,0 +1,15 @@ +import semmle.code.cpp.commons.Printf + +from FormatLiteral format, int n +where exists(format.getConvSpecOffset(n)) +select format, n, + concat(Expr arg | + arg = format.getUse().getConversionArgument(n) + | + format.getSuggestedIntegerConversionSpecifier(n, arg.getFullyConverted().getType()), ", " + ), + concat(Expr arg | + arg = format.getUse().getConversionArgument(n) + | + format.getSuggestedIntegerConversionSpecifier(n, arg.getUnconverted().getType()), ", " + ) diff --git a/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/test.c b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/test.c new file mode 100644 index 000000000000..9878439493e6 --- /dev/null +++ b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Linux/test.c @@ -0,0 +1 @@ +#include "../cases.h" diff --git a/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/options b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/options new file mode 100644 index 000000000000..668c53efd7db --- /dev/null +++ b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/options @@ -0,0 +1 @@ +semmle-extractor-options: --microsoft --edg --target --edg win64 diff --git a/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/suggestedSpecifier.expected b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/suggestedSpecifier.expected new file mode 100644 index 000000000000..7fd732531929 --- /dev/null +++ b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/suggestedSpecifier.expected @@ -0,0 +1,61 @@ +| ../cases.h:11:10:11:13 | %d | 0 | %d | %d | +| ../cases.h:12:10:12:13 | %d | 0 | %u | %u | +| ../cases.h:13:10:13:13 | %d | 0 | %ld | %ld | +| ../cases.h:14:10:14:13 | %d | 0 | %lu | %lu | +| ../cases.h:15:10:15:13 | %d | 0 | %lld | %lld | +| ../cases.h:16:10:16:13 | %d | 0 | %llu | %llu | +| ../cases.h:17:10:17:13 | %i | 0 | %i | %i | +| ../cases.h:18:10:18:13 | %i | 0 | %u | %u | +| ../cases.h:19:10:19:13 | %i | 0 | %li | %li | +| ../cases.h:20:10:20:13 | %i | 0 | %lu | %lu | +| ../cases.h:21:10:21:13 | %i | 0 | %lli | %lli | +| ../cases.h:22:10:22:13 | %i | 0 | %llu | %llu | +| ../cases.h:23:10:23:13 | %u | 0 | %d | %d | +| ../cases.h:24:10:24:14 | %ld | 0 | %d | %d | +| ../cases.h:25:10:25:14 | %li | 0 | %u | %u | +| ../cases.h:26:10:26:14 | %lu | 0 | %lld | %lld | +| ../cases.h:27:10:27:15 | %lld | 0 | %ld | %ld | +| ../cases.h:28:10:28:15 | %lli | 0 | %llu | %llu | +| ../cases.h:29:10:29:15 | %llu | 0 | %lu | %lu | +| ../cases.h:30:10:30:13 | %d | 0 | %lu | %lu | +| ../cases.h:32:10:32:15 | %lld | 0 | %d | | +| ../cases.h:33:10:33:15 | %lld | 0 | %d | | +| ../cases.h:34:10:34:15 | %lld | 0 | %d | | +| ../cases.h:35:10:35:15 | %lld | 0 | %d | | +| ../cases.h:36:10:36:15 | %lld | 0 | | | +| ../cases.h:38:10:38:23 | value=%d end | 0 | %llu | %llu | +| ../cases.h:39:10:39:19 | %% %d %i | 0 | %ld | %ld | +| ../cases.h:39:10:39:19 | %% %d %i | 1 | %llu | %llu | +| ../cases.h:42:10:42:14 | %+d | 0 | | | +| ../cases.h:43:10:43:14 | % d | 0 | | | +| ../cases.h:44:10:44:14 | %-d | 0 | | | +| ../cases.h:45:10:45:14 | %#d | 0 | | | +| ../cases.h:46:10:46:14 | %'d | 0 | | | +| ../cases.h:47:10:47:15 | %08d | 0 | | | +| ../cases.h:48:10:48:15 | %12d | 0 | | | +| ../cases.h:49:10:49:14 | %.d | 0 | | | +| ../cases.h:50:10:50:15 | %.3d | 0 | | | +| ../cases.h:51:10:51:14 | %*d | 0 | | | +| ../cases.h:52:10:52:15 | %.*d | 0 | | | +| ../cases.h:53:10:53:15 | %1$d | 0 | | | +| ../cases.h:54:10:54:20 | %2$d %1$i | 0 | | | +| ../cases.h:54:10:54:20 | %2$d %1$i | 1 | | | +| ../cases.h:56:10:56:15 | %hhd | 0 | | | +| ../cases.h:57:10:57:14 | %hd | 0 | | | +| ../cases.h:58:10:58:14 | %jd | 0 | | | +| ../cases.h:59:10:59:14 | %zd | 0 | | | +| ../cases.h:60:10:60:14 | %td | 0 | | | +| ../cases.h:61:10:61:14 | %qd | 0 | | | +| ../cases.h:62:10:62:16 | %I64d | 0 | | | +| ../cases.h:63:10:63:13 | %x | 0 | | | +| ../cases.h:64:10:64:13 | %X | 0 | | | +| ../cases.h:65:10:65:13 | %o | 0 | | | +| ../cases.h:66:10:66:13 | %c | 0 | | | +| ../cases.h:67:10:67:13 | %s | 0 | | | +| ../cases.h:68:10:68:13 | %p | 0 | | | +| ../cases.h:69:10:69:13 | %f | 0 | | | +| ../cases.h:70:10:70:13 | %n | 0 | | | +| ../cases.h:71:10:71:13 | %d | 0 | | | +| ../cases.h:72:10:72:13 | %d | 0 | | | +| ../cases.h:73:10:73:13 | %d | 0 | | | +| ../cases.h:74:10:74:14 | %kd | 0 | | | diff --git a/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/suggestedSpecifier.ql b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/suggestedSpecifier.ql new file mode 100644 index 000000000000..b8fbd970b81a --- /dev/null +++ b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/suggestedSpecifier.ql @@ -0,0 +1,15 @@ +import semmle.code.cpp.commons.Printf + +from FormatLiteral format, int n +where exists(format.getConvSpecOffset(n)) +select format, n, + concat(Expr arg | + arg = format.getUse().getConversionArgument(n) + | + format.getSuggestedIntegerConversionSpecifier(n, arg.getFullyConverted().getType()), ", " + ), + concat(Expr arg | + arg = format.getUse().getConversionArgument(n) + | + format.getSuggestedIntegerConversionSpecifier(n, arg.getUnconverted().getType()), ", " + ) diff --git a/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/test.c b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/test.c new file mode 100644 index 000000000000..9878439493e6 --- /dev/null +++ b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/Microsoft/test.c @@ -0,0 +1 @@ +#include "../cases.h" diff --git a/cpp/ql/test/library-tests/printf/integerConversionSpecifier/cases.h b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/cases.h new file mode 100644 index 000000000000..499d7cdc3e5d --- /dev/null +++ b/cpp/ql/test/library-tests/printf/integerConversionSpecifier/cases.h @@ -0,0 +1,75 @@ +// Compile-only fixtures. Deliberately mismatched printf calls are not executed. +int printf(const char *format, ...); +typedef unsigned long UnsignedLong; +enum SmallEnum { SmallValue = 1 }; + +void integer_specifiers(int i, unsigned int ui, long l, unsigned long ul, + long long ll, unsigned long long ull, signed int si, + const UnsignedLong cul, short s, unsigned short us, + char c, _Bool b, enum SmallEnum e, float f, double d, int *p) { + // All six type categories, independently of whether the query reports a mismatch. + printf("%d", i); // EXPECT: %d + printf("%d", ui); // EXPECT: %u + printf("%d", l); // EXPECT: %ld + printf("%d", ul); // EXPECT: %lu + printf("%d", ll); // EXPECT: %lld + printf("%d", ull); // EXPECT: %llu + printf("%i", i); // EXPECT: %i + printf("%i", ui); // EXPECT: %u + printf("%i", l); // EXPECT: %li + printf("%i", ul); // EXPECT: %lu + printf("%i", ll); // EXPECT: %lli + printf("%i", ull); // EXPECT: %llu + printf("%u", i); // EXPECT: %d + printf("%ld", si); // EXPECT: %d + printf("%li", ui); // EXPECT: %u + printf("%lu", ll); // EXPECT: %lld + printf("%lld", l); // EXPECT: %ld + printf("%lli", ull); // EXPECT: %llu + printf("%llu", ul); // EXPECT: %lu + printf("%d", cul); // EXPECT: %lu + // The caller supplies promoted types; raw narrow types and enums are unsupported. + printf("%lld", s); // EXPECT: %d; RAW: none + printf("%lld", us); // EXPECT: %d; RAW: none + printf("%lld", c); // EXPECT: %d; RAW: none + printf("%lld", b); // EXPECT: %d; RAW: none + printf("%lld", e); // EXPECT: none + // Per-conversion mapping, literal text, and escaped percent signs. + printf("value=%d end", ull); // EXPECT: %llu + printf("%% %d %i", l, ull); // EXPECT: %ld,%llu + printf("%%", i); // NO_CONVERSION + // Unsupported decorators and positional conversions must have no result. + printf("%+d", ll); // EXPECT: none + printf("% d", ll); // EXPECT: none + printf("%-d", ll); // EXPECT: none + printf("%#d", ll); // EXPECT: none + printf("%'d", ll); // EXPECT: none + printf("%08d", ll); // EXPECT: none + printf("%12d", ll); // EXPECT: none + printf("%.d", ll); // EXPECT: none + printf("%.3d", ll); // EXPECT: none + printf("%*d", ll, i); // EXPECT: none + printf("%.*d", ll, i); // EXPECT: none + printf("%1$d", ll); // EXPECT: none + printf("%2$d %1$i", ll, ull); // EXPECT: none,none + // Unsupported length flags, conversions, types, and malformed formats. + printf("%hhd", ll); // EXPECT: none + printf("%hd", ll); // EXPECT: none + printf("%jd", ll); // EXPECT: none + printf("%zd", ll); // EXPECT: none + printf("%td", ll); // EXPECT: none + printf("%qd", ll); // EXPECT: none + printf("%I64d", ll); // EXPECT: none + printf("%x", ull); // EXPECT: none + printf("%X", ull); // EXPECT: none + printf("%o", ull); // EXPECT: none + printf("%c", ll); // EXPECT: none + printf("%s", ll); // EXPECT: none + printf("%p", ll); // EXPECT: none + printf("%f", i); // EXPECT: none + printf("%n", p); // EXPECT: none + printf("%d", f); // EXPECT: none + printf("%d", d); // EXPECT: none + printf("%d", p); // EXPECT: none + printf("%kd", ll); // EXPECT: none +}