Skip to content

Commit 4d8e882

Browse files
authored
Merge pull request github#6186 from geoffw0/formatarg
C++: Fix FPs from cpp/wrong-type-format-argument
2 parents fb57c5f + cfbfe92 commit 4d8e882

File tree

13 files changed

+78
-44
lines changed

13 files changed

+78
-44
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* The 'Wrong type of arguments to formatting function' (cpp/wrong-type-format-argument) query is now more accepting of the string and character formatting differences between Microsoft and non-Microsoft platforms. There are now fewer false positive results.

cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,32 @@ import cpp
1919
* Holds if the argument corresponding to the `pos` conversion specifier
2020
* of `ffc` is expected to have type `expected`.
2121
*/
22-
pragma[noopt]
2322
private predicate formattingFunctionCallExpectedType(
2423
FormattingFunctionCall ffc, int pos, Type expected
2524
) {
26-
exists(FormattingFunction f, int i, FormatLiteral fl |
27-
ffc instanceof FormattingFunctionCall and
28-
ffc.getTarget() = f and
29-
f.getFormatParameterIndex() = i and
30-
ffc.getArgument(i) = fl and
31-
fl.getConversionType(pos) = expected
32-
)
25+
ffc.getFormat().(FormatLiteral).getConversionType(pos) = expected
26+
}
27+
28+
/**
29+
* Holds if the argument corresponding to the `pos` conversion specifier
30+
* of `ffc` could alternatively have type `expected`, for example on a different
31+
* platform.
32+
*/
33+
private predicate formattingFunctionCallAlternateType(
34+
FormattingFunctionCall ffc, int pos, Type expected
35+
) {
36+
ffc.getFormat().(FormatLiteral).getConversionTypeAlternate(pos) = expected
3337
}
3438

3539
/**
3640
* Holds if the argument corresponding to the `pos` conversion specifier
37-
* of `ffc` is expected to have type `expected` and the corresponding
38-
* argument `arg` has type `actual`.
41+
* of `ffc` is `arg` and has type `actual`.
3942
*/
4043
pragma[noopt]
41-
predicate formatArgType(FormattingFunctionCall ffc, int pos, Type expected, Expr arg, Type actual) {
44+
predicate formattingFunctionCallActualType(
45+
FormattingFunctionCall ffc, int pos, Expr arg, Type actual
46+
) {
4247
exists(Expr argConverted |
43-
formattingFunctionCallExpectedType(ffc, pos, expected) and
4448
ffc.getConversionArgument(pos) = arg and
4549
argConverted = arg.getFullyConverted() and
4650
actual = argConverted.getType()
@@ -72,7 +76,8 @@ class ExpectedType extends Type {
7276
ExpectedType() {
7377
exists(Type t |
7478
(
75-
formatArgType(_, _, t, _, _) or
79+
formattingFunctionCallExpectedType(_, _, t) or
80+
formattingFunctionCallAlternateType(_, _, t) or
7681
formatOtherArgType(_, _, t, _, _)
7782
) and
7883
this = t.getUnspecifiedType()
@@ -91,7 +96,11 @@ class ExpectedType extends Type {
9196
*/
9297
predicate trivialConversion(ExpectedType expected, Type actual) {
9398
exists(Type exp, Type act |
94-
formatArgType(_, _, exp, _, act) and
99+
(
100+
formattingFunctionCallExpectedType(_, _, exp) or
101+
formattingFunctionCallAlternateType(_, _, exp)
102+
) and
103+
formattingFunctionCallActualType(_, _, _, act) and
95104
expected = exp.getUnspecifiedType() and
96105
actual = act.getUnspecifiedType()
97106
) and
@@ -146,9 +155,13 @@ int sizeof_IntType() { exists(IntType it | result = it.getSize()) }
146155
from FormattingFunctionCall ffc, int n, Expr arg, Type expected, Type actual
147156
where
148157
(
149-
formatArgType(ffc, n, expected, arg, actual) and
158+
formattingFunctionCallExpectedType(ffc, n, expected) and
159+
formattingFunctionCallActualType(ffc, n, arg, actual) and
150160
not exists(Type anyExpected |
151-
formatArgType(ffc, n, anyExpected, arg, actual) and
161+
(
162+
formattingFunctionCallExpectedType(ffc, n, anyExpected) or
163+
formattingFunctionCallAlternateType(ffc, n, anyExpected)
164+
) and
152165
trivialConversion(anyExpected.getUnspecifiedType(), actual.getUnspecifiedType())
153166
)
154167
or

cpp/ql/src/semmle/code/cpp/File.qll

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -272,20 +272,16 @@ class File extends Container, @file {
272272
* are compiled by a Microsoft compiler are detected by this predicate.
273273
*/
274274
predicate compiledAsMicrosoft() {
275-
exists(Compilation c |
276-
c.getAFileCompiled() = this and
275+
exists(File f, Compilation c |
276+
c.getAFileCompiled() = f and
277277
(
278278
c.getAnArgument() = "--microsoft" or
279279
c.getAnArgument()
280280
.toLowerCase()
281281
.replaceAll("\\", "/")
282282
.matches(["%/cl.exe", "%/clang-cl.exe"])
283-
)
284-
)
285-
or
286-
exists(File parent |
287-
parent.compiledAsMicrosoft() and
288-
parent.getAnIncludedFile() = this
283+
) and
284+
f.getAnIncludedFile*() = this
289285
)
290286
}
291287

@@ -358,6 +354,11 @@ class File extends Container, @file {
358354
string getShortName() { files(underlyingElement(this), _, result, _, _) }
359355
}
360356

357+
/**
358+
* Holds if any file was compiled by a Microsoft compiler.
359+
*/
360+
predicate anyFileCompiledAsMicrosoft() { any(File f).compiledAsMicrosoft() }
361+
361362
/**
362363
* A C/C++ header file, as determined (mainly) by file extension.
363364
*

cpp/ql/src/semmle/code/cpp/commons/Printf.qll

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ class FormatLiteral extends Literal {
306306
* Holds if this `FormatLiteral` is in a context that supports
307307
* Microsoft rules and extensions.
308308
*/
309-
predicate isMicrosoft() { any(File f).compiledAsMicrosoft() }
309+
predicate isMicrosoft() { anyFileCompiledAsMicrosoft() }
310310

311311
/**
312312
* Gets the format string, with '%%' and '%@' replaced by '_' (to avoid processing
@@ -869,6 +869,33 @@ class FormatLiteral extends Literal {
869869
)
870870
}
871871

872+
/**
873+
* Gets an alternate argument type that would be required by the nth
874+
* conversion specifier on a Microsoft or non-Microsoft platform, opposite
875+
* to that of the snapshot. This may be useful for answering 'what might
876+
* happen' questions.
877+
*/
878+
Type getConversionTypeAlternate(int n) {
879+
exists(string len, string conv |
880+
this.parseConvSpec(n, _, _, _, _, _, len, conv) and
881+
(len != "l" and len != "w" and len != "h") and
882+
getUse().getTarget().(FormattingFunction).getFormatCharType().getSize() > 1 and // wide function
883+
(
884+
conv = "c" and
885+
result = getNonDefaultCharType()
886+
or
887+
conv = "C" and
888+
result = getDefaultCharType()
889+
or
890+
conv = "s" and
891+
result.(PointerType).getBaseType() = getNonDefaultCharType()
892+
or
893+
conv = "S" and
894+
result.(PointerType).getBaseType() = getDefaultCharType()
895+
)
896+
)
897+
}
898+
872899
/**
873900
* Holds if the nth conversion specifier of this format string (if `mode = 2`), it's
874901
* minimum field width (if `mode = 0`) or it's precision (if `mode = 1`) requires a

cpp/ql/src/semmle/code/cpp/models/interfaces/FormattingFunction.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ abstract class FormattingFunction extends ArrayFunction, TaintFunction {
5050
* Holds if this `FormattingFunction` is in a context that supports
5151
* Microsoft rules and extensions.
5252
*/
53-
predicate isMicrosoft() { any(File f).compiledAsMicrosoft() }
53+
predicate isMicrosoft() { anyFileCompiledAsMicrosoft() }
5454

5555
/**
5656
* Holds if the default meaning of `%s` is a `wchar_t *`, rather than

cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_mixed_byte_wprintf/WrongTypeFormatArguments.expected

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,8 @@
33
| tests.cpp:21:15:21:21 | Hello | This argument should be of type 'char16_t *' but is of type 'char *' |
44
| tests.cpp:21:15:21:21 | Hello | This argument should be of type 'wchar_t *' but is of type 'char *' |
55
| tests.cpp:26:17:26:24 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
6-
| tests.cpp:27:17:27:24 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
7-
| tests.cpp:29:17:29:23 | Hello | This argument should be of type 'wchar_t *' but is of type 'char *' |
86
| tests.cpp:30:17:30:24 | Hello | This argument should be of type 'wchar_t *' but is of type 'char16_t *' |
9-
| tests.cpp:34:36:34:43 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
107
| tests.cpp:35:36:35:43 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
11-
| tests.cpp:37:36:37:42 | Hello | This argument should be of type 'char16_t *' but is of type 'char *' |
128
| tests.cpp:39:36:39:43 | Hello | This argument should be of type 'char16_t *' but is of type 'wchar_t *' |
139
| tests.cpp:42:37:42:44 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
1410
| tests.cpp:43:37:43:44 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |

cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_mixed_byte_wprintf/tests.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ void tests() {
2424

2525
wprintf(L"%s", "Hello"); // GOOD
2626
wprintf(L"%s", u"Hello"); // BAD: expecting char
27-
wprintf(L"%s", L"Hello"); // BAD: expecting char
27+
wprintf(L"%s", L"Hello"); // BAD: expecting char [NOT DETECTED; correct on Microsoft platforms]
2828

29-
wprintf(L"%S", "Hello"); // BAD: expecting wchar_t
29+
wprintf(L"%S", "Hello"); // BAD: expecting wchar_t [NOT DETECTED; correct on Microsoft platforms]
3030
wprintf(L"%S", u"Hello"); // BAD: expecting wchar_t
3131
wprintf(L"%S", L"Hello"); // GOOD
3232

3333
swprintf(buffer, BUF_SIZE, u"%s", "Hello"); // GOOD
34-
swprintf(buffer, BUF_SIZE, u"%s", u"Hello"); // BAD: expecting char
34+
swprintf(buffer, BUF_SIZE, u"%s", u"Hello"); // BAD: expecting char [NOT DETECTED; correct on Microsoft platforms]
3535
swprintf(buffer, BUF_SIZE, u"%s", L"Hello"); // BAD: expecting char
3636

37-
swprintf(buffer, BUF_SIZE, u"%S", "Hello"); // BAD: expecting char16_t
37+
swprintf(buffer, BUF_SIZE, u"%S", "Hello"); // BAD: expecting char16_t [NOT DETECTED; correct on Microsoft platforms]
3838
swprintf(buffer, BUF_SIZE, u"%S", u"Hello"); // GOOD
3939
swprintf(buffer, BUF_SIZE, u"%S", L"Hello"); // BAD: expecting char16_t
4040

Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
| printf.cpp:31:31:31:37 | test | This argument should be of type 'char *' but is of type 'char16_t *' |
21
| printf.cpp:43:29:43:35 | test | This argument should be of type 'char *' but is of type 'char16_t *' |
32
| printf.cpp:50:29:50:35 | test | This argument should be of type 'char16_t *' but is of type 'wchar_t *' |

cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_two_byte_wprintf/printf.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ int sprintf(char *dest, char *format, ...);
2828
void test1() {
2929
WCHAR string[20];
3030

31-
swprintf(string, u"test %s", u"test"); // BAD: `char16_t` string parameter read as `char` string
31+
swprintf(string, u"test %s", u"test"); // BAD: `char16_t` string parameter read as `char` string [NOT DETECTED; correct on Microsoft platforms]
3232
}
3333

3434
void test2() {

cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_unsigned_chars/WrongTypeFormatArguments.expected

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
| printf1.h:45:18:45:20 | ull | This argument should be of type 'unsigned int' but is of type 'unsigned long long' |
1212
| printf1.h:46:18:46:20 | ull | This argument should be of type 'unsigned int' but is of type 'unsigned long long' |
1313
| printf1.h:130:18:130:18 | 0 | This argument should be of type 'void *' but is of type 'int' |
14-
| printf1.h:154:18:154:19 | wc | This argument should be of type 'char *' but is of type 'wchar_t *' |
15-
| printf1.h:155:18:155:18 | c | This argument should be of type 'wchar_t *' but is of type 'char *' |
1614
| printf1.h:168:19:168:19 | i | This argument should be of type 'long long' but is of type 'int' |
1715
| printf1.h:169:19:169:20 | ui | This argument should be of type 'unsigned long long' but is of type 'unsigned int' |
1816
| real_world.h:61:21:61:22 | & ... | This argument should be of type 'int *' but is of type 'short *' |

0 commit comments

Comments
 (0)