Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
36 changes: 36 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/commons/Printf.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 14 additions & 1 deletion cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,19 @@ predicate trivialConversion(ExpectedType expected, Type actual) {
*/
int sizeof_IntType() { exists(IntType it | result = it.getSize()) }

bindingset[ffc, n, arg, actual]
private string integerFormatSuggestion(FormattingFunctionCall ffc, int n, Expr arg, Type actual) {
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 = ""
}

from FormattingFunctionCall ffc, int n, Expr arg, Type expected, Type actual
where
(
Expand All @@ -178,4 +191,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)
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
semmle-extractor-options: --clang --edg --signed_chars
Original file line number Diff line number Diff line change
@@ -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 | | |
Original file line number Diff line number Diff line change
@@ -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()), ", "
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "../cases.h"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
semmle-extractor-options: --microsoft --edg --target --edg win64
Original file line number Diff line number Diff line change
@@ -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 | | |
Original file line number Diff line number Diff line change
@@ -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()), ", "
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "../cases.h"
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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'. |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
query: Likely Bugs/Format/WrongTypeFormatArguments.ql
Original file line number Diff line number Diff line change
@@ -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.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
semmle-extractor-options: --clang --edg --signed_chars
Loading