Skip to content

Commit 0644e0f

Browse files
authored
Merge pull request #286 from geoffw0/wrongtype16
CPP: Fix WrongTypeFormatArguments.ql char16_t * issues (and others)
2 parents 4720c5a + 99816d7 commit 0644e0f

File tree

26 files changed

+360
-44
lines changed

26 files changed

+360
-44
lines changed

change-notes/1.19/analysis-cpp.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
| **Query** | **Expected impact** | **Change** |
1414
|----------------------------|------------------------|------------------------------------------------------------------|
1515
| Resource not released in destructor | Fewer false positive results | Placement new is now excluded from the query. |
16-
| Wrong type of arguments to formatting function | Fewer false positive results | False positive results involving typedefs have been removed. |
1716
| Missing return statement (`cpp/missing-return`) | Visible by default | The precision of this query has been increased from 'medium' to 'high', which makes it visible by default in LGTM. It was 'medium' in release 1.17 and 1.18 because it had false positives due to an extractor bug that was fixed in 1.18. |
17+
| Wrong type of arguments to formatting function | Fewer false positive results | False positive results involving typedefs have been removed. Expected argument types are determined more accurately, especially for wide string and pointer types. Custom (non-standard) formatting functions are also identified more accurately. |
1818

1919
## Changes to QL libraries
2020

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ private predicate formattingFunctionCallExpectedType(FormattingFunctionCall ffc,
2525
ffc.getTarget() = f and
2626
f.getFormatParameterIndex() = i and
2727
ffc.getArgument(i) = fl and
28-
fl.getConversionType(pos) = expected
28+
fl.getConversionType(pos) = expected and
29+
count(fl.getConversionType(pos)) = 1
2930
)
3031
}
3132

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

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,32 @@ class FormatLiteral extends Literal {
220220
getUse().getTarget().(FormattingFunction).isWideCharDefault()
221221
}
222222

223+
/**
224+
* Gets the default character type expected for `%s` by this format literal. Typically
225+
* `char` or `wchar_t`.
226+
*/
227+
Type getDefaultCharType() {
228+
result = getUse().getTarget().(FormattingFunction).getDefaultCharType()
229+
}
230+
231+
/**
232+
* Gets the non-default character type expected for `%S` by this format literal. Typically
233+
* `wchar_t` or `char`. On some snapshots there may be multiple results where we can't tell
234+
* which is correct for a particular function.
235+
*/
236+
Type getNonDefaultCharType() {
237+
result = getUse().getTarget().(FormattingFunction).getNonDefaultCharType()
238+
}
239+
240+
/**
241+
* Gets the wide character type for this format literal. This is usually `wchar_t`. On some
242+
* snapshots there may be multiple results where we can't tell which is correct for a
243+
* particular function.
244+
*/
245+
Type getWideCharType() {
246+
result = getUse().getTarget().(FormattingFunction).getWideCharType()
247+
}
248+
223249
/**
224250
* Holds if this `FormatLiteral` is in a context that supports
225251
* Microsoft rules and extensions.
@@ -629,7 +655,6 @@ class FormatLiteral extends Literal {
629655
result = getConversionType2(n) or
630656
result = getConversionType3(n) or
631657
result = getConversionType4(n) or
632-
result = getConversionType5(n) or
633658
result = getConversionType6(n) or
634659
result = getConversionType7(n) or
635660
result = getConversionType8(n) or
@@ -696,33 +721,35 @@ class FormatLiteral extends Literal {
696721
}
697722

698723
/**
699-
* Gets the 'effective' string type character, that is, 's' (meaning a char string) or
700-
* 'S' (meaning a wide string).
701-
* - in the base case this is the same as the format type character.
702-
* - for a `wprintf` or similar function call, the meanings are reversed.
703-
* - the size prefixes 'l'/'w' (long) and 'h' (short) override the
704-
* type character to effectively 'S' or 's' respectively.
705-
*/
706-
private string getEffectiveStringConversionChar(int n) {
707-
exists(string len, string conv | this.parseConvSpec(n, _, _, _, _, _, len, conv) and (conv = "s" or conv = "S") |
708-
(len = "l" and result = "S") or
709-
(len = "w" and result = "S") or
710-
(len = "h" and result = "s") or
711-
(len != "l" and len != "w" and len != "h" and (result = "s" or result = "S") and (if isWideCharDefault() then result != conv else result = conv))
712-
)
713-
}
714-
724+
* Gets the string type required by the nth conversion specifier.
725+
* - in the base case this is the default for the formatting function
726+
* (e.g. `char` for `printf`, `wchar_t` for `wprintf`).
727+
* - the `%S` format character reverses wideness.
728+
* - the size prefixes 'l'/'w' and 'h' override the type character
729+
* to wide or single-byte characters respectively.
730+
*/
715731
private Type getConversionType4(int n) {
716-
exists(string cnv, CharType t | cnv = this.getEffectiveStringConversionChar(n) |
717-
cnv="s" and t = result.(PointerType).getBaseType()
718-
and not t.isExplicitlySigned()
719-
and not t.isExplicitlyUnsigned()
720-
)
721-
}
722-
723-
private Type getConversionType5(int n) {
724-
exists(string cnv | cnv = this.getEffectiveStringConversionChar(n) |
725-
cnv="S" and result.(PointerType).getBaseType().hasName("wchar_t")
732+
exists(string len, string conv |
733+
this.parseConvSpec(n, _, _, _, _, _, len, conv) and
734+
(
735+
(
736+
(conv = "s" or conv = "S") and
737+
len = "h" and
738+
result.(PointerType).getBaseType() instanceof PlainCharType
739+
) or (
740+
(conv = "s" or conv = "S") and
741+
(len = "l" or len = "w") and
742+
result.(PointerType).getBaseType() = getWideCharType()
743+
) or (
744+
conv = "s" and
745+
(len != "l" and len != "w" and len != "h") and
746+
result.(PointerType).getBaseType() = getDefaultCharType()
747+
) or (
748+
conv = "S" and
749+
(len != "l" and len != "w" and len != "h") and
750+
result.(PointerType).getBaseType() = getNonDefaultCharType()
751+
)
752+
)
726753
)
727754
}
728755

cpp/ql/src/semmle/code/cpp/models/implementations/Printf.qll

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ class Printf extends FormattingFunction {
1212
hasGlobalName("wprintf") or
1313
hasGlobalName("wprintf_s") or
1414
hasGlobalName("g_printf")
15-
)
15+
) and
16+
not exists(getDefinition().getFile().getRelativePath())
1617
}
1718

1819
override int getFormatParameterIndex() { result=0 }
@@ -26,7 +27,15 @@ class Printf extends FormattingFunction {
2627
* The standard functions `fprintf`, `fwprintf` and their glib variants.
2728
*/
2829
class Fprintf extends FormattingFunction {
29-
Fprintf() { this instanceof TopLevelFunction and (hasGlobalName("fprintf") or hasGlobalName("fwprintf") or hasGlobalName("g_fprintf"))}
30+
Fprintf() {
31+
this instanceof TopLevelFunction and
32+
(
33+
hasGlobalName("fprintf") or
34+
hasGlobalName("fwprintf") or
35+
hasGlobalName("g_fprintf")
36+
) and
37+
not exists(getDefinition().getFile().getRelativePath())
38+
}
3039

3140
override int getFormatParameterIndex() { result=1 }
3241
override predicate isWideCharDefault() { hasGlobalName("fwprintf") }
@@ -47,7 +56,8 @@ class Sprintf extends FormattingFunction {
4756
hasGlobalName("g_strdup_printf") or
4857
hasGlobalName("g_sprintf") or
4958
hasGlobalName("__builtin___sprintf_chk")
50-
)
59+
) and
60+
not exists(getDefinition().getFile().getRelativePath())
5161
}
5262

5363
override predicate isWideCharDefault() {
@@ -100,7 +110,8 @@ class Snprintf extends FormattingFunction {
100110
or hasGlobalName("g_snprintf")
101111
or hasGlobalName("wnsprintf")
102112
or hasGlobalName("__builtin___snprintf_chk")
103-
)
113+
) and
114+
not exists(getDefinition().getFile().getRelativePath())
104115
}
105116

106117
override int getFormatParameterIndex() {
@@ -133,10 +144,13 @@ class Snprintf extends FormattingFunction {
133144
* in the buffer.
134145
*/
135146
predicate returnsFullFormatLength() {
136-
hasGlobalName("snprintf") or
137-
hasGlobalName("g_snprintf") or
138-
hasGlobalName("__builtin___snprintf_chk") or
139-
hasGlobalName("snprintf_s")
147+
(
148+
hasGlobalName("snprintf") or
149+
hasGlobalName("g_snprintf") or
150+
hasGlobalName("__builtin___snprintf_chk") or
151+
hasGlobalName("snprintf_s")
152+
) and
153+
not exists(getDefinition().getFile().getRelativePath())
140154
}
141155

142156
override int getSizeParameterIndex() {
@@ -158,7 +172,8 @@ class StringCchPrintf extends FormattingFunction {
158172
or hasGlobalName("StringCbPrintfEx")
159173
or hasGlobalName("StringCbPrintf_l")
160174
or hasGlobalName("StringCbPrintf_lEx")
161-
)
175+
) and
176+
not exists(getDefinition().getFile().getRelativePath())
162177
}
163178

164179
override int getFormatParameterIndex() {
@@ -187,7 +202,8 @@ class Syslog extends FormattingFunction {
187202
Syslog() {
188203
this instanceof TopLevelFunction and (
189204
hasGlobalName("syslog")
190-
)
205+
) and
206+
not exists(getDefinition().getFile().getRelativePath())
191207
}
192208

193209
override int getFormatParameterIndex() { result=1 }

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,38 @@
77
*/
88

99
import semmle.code.cpp.Function
10+
11+
private Type stripTopLevelSpecifiersOnly(Type t) {
12+
(
13+
result = stripTopLevelSpecifiersOnly(t.(SpecifiedType).getBaseType())
14+
) or (
15+
result = t and
16+
not t instanceof SpecifiedType
17+
)
18+
}
19+
20+
/**
21+
* A type that is used as a format string by any formatting function.
22+
*/
23+
Type getAFormatterWideType() {
24+
exists(FormattingFunction ff |
25+
result = stripTopLevelSpecifiersOnly(ff.getDefaultCharType()) and
26+
result.getSize() != 1
27+
)
28+
}
29+
30+
/**
31+
* A type that is used as a format string by any formatting function, or `wchar_t` if
32+
* there is none.
33+
*/
34+
private Type getAFormatterWideTypeOrDefault() {
35+
result = getAFormatterWideType() or
36+
(
37+
not exists(getAFormatterWideType()) and
38+
result instanceof Wchar_t
39+
)
40+
}
41+
1042
/**
1143
* A standard library function that uses a `printf`-like formatting string.
1244
*/
@@ -20,6 +52,43 @@ abstract class FormattingFunction extends Function {
2052
*/
2153
predicate isWideCharDefault() { none() }
2254

55+
/**
56+
* Gets the default character type expected for `%s` by this function. Typically
57+
* `char` or `wchar_t`.
58+
*/
59+
Type getDefaultCharType() {
60+
result = stripTopLevelSpecifiersOnly(getParameter(getFormatParameterIndex()).getType().
61+
getUnderlyingType().(PointerType).getBaseType())
62+
}
63+
64+
/**
65+
* Gets the non-default character type expected for `%S` by this function. Typically
66+
* `wchar_t` or `char`. On some snapshots there may be multiple results where we can't tell
67+
* which is correct for a particular function.
68+
*/
69+
Type getNonDefaultCharType() {
70+
(
71+
getDefaultCharType().getSize() = 1 and
72+
result = getAFormatterWideTypeOrDefault()
73+
) or (
74+
getDefaultCharType().getSize() > 1 and
75+
result instanceof PlainCharType
76+
)
77+
}
78+
79+
/**
80+
* Gets the wide character type for this function. This is usually `wchar_t`. On some
81+
* snapshots there may be multiple results where we can't tell which is correct for a
82+
* particular function.
83+
*/
84+
Type getWideCharType() {
85+
(
86+
result = getDefaultCharType() or
87+
result = getNonDefaultCharType()
88+
) and
89+
result.getSize() > 1
90+
}
91+
2392
/**
2493
* Gets the position at which the output parameter, if any, occurs.
2594
*/

cpp/ql/test/query-tests/Likely Bugs/Format/WrongNumberOfFormatArguments/TooManyFormatArguments.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
| custom_printf.cpp:31:5:31:12 | call to myPrintf | Format expects 2 arguments but given 3 |
2-
| custom_printf.cpp:44:2:44:7 | call to printf | Format expects 0 arguments but given 2 |
32
| macros.cpp:12:2:12:31 | call to printf | Format expects 2 arguments but given 3 |
43
| macros.cpp:16:2:16:30 | call to printf | Format expects 2 arguments but given 3 |
54
| test.c:7:2:7:7 | call to printf | Format expects 0 arguments but given 1 |

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
| custom_printf.cpp:29:5:29:12 | call to myPrintf | Format expects 2 arguments but given 1 |
2-
| custom_printf.cpp:45:2:45:7 | call to printf | Format expects 2 arguments but given 0 |
32
| macros.cpp:14:2:14:37 | call to printf | Format expects 4 arguments but given 3 |
43
| macros.cpp:21:2:21:36 | call to printf | Format expects 4 arguments but given 3 |
54
| test.c:9:2:9:7 | call to printf | Format expects 1 arguments but given 0 |

cpp/ql/test/query-tests/Likely Bugs/Format/WrongNumberOfFormatArguments/custom_printf.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ void test_custom_printf2()
4141
{
4242
// notTheFormat format ...
4343
printf(0, "%i %i", 100, 200); // GOOD
44-
printf("", "%i %i", 100, 200); // GOOD [FALSE POSITIVE]
45-
printf("%i %i", "" ); // GOOD [FALSE POSITIVE]
44+
printf("", "%i %i", 100, 200); // GOOD
45+
printf("%i %i", "" ); // GOOD
4646
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
| tests.cpp:18:15:18:22 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
2+
| tests.cpp:19:15:19:22 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
3+
| tests.cpp:25:17:25:23 | Hello | This argument should be of type 'wchar_t *' but is of type 'char *' |
4+
| tests.cpp:26:17:26:24 | Hello | This argument should be of type 'wchar_t *' but is of type 'char16_t *' |
5+
| tests.cpp:30:17:30:24 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
6+
| tests.cpp:31:17:31:24 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
7+
| tests.cpp:33:36:33:42 | Hello | This argument should be of type 'char16_t *' but is of type 'char *' |
8+
| tests.cpp:35:36:35:43 | Hello | This argument should be of type 'char16_t *' but is of type 'wchar_t *' |
9+
| tests.cpp:38:36:38:43 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
10+
| tests.cpp:39:36:39:43 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Likely Bugs/Format/WrongTypeFormatArguments.ql

0 commit comments

Comments
 (0)