Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c36b73f

Browse files
authored
Merge pull request #2232 from geoffw0/formatsymbols
CPP: Fully support n$ in format strings
2 parents 303bab6 + a4250be commit c36b73f

4 files changed

Lines changed: 205 additions & 23 deletions

File tree

change-notes/1.23/analysis-cpp.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The following changes in version 1.23 affect C/C++ analysis in all applications.
2525
| Unclear comparison precedence (`cpp/comparison-precedence`) | Fewer false positive results | False positives involving template classes and functions have been fixed. |
2626
| Comparison of narrow type with wide type in loop condition (`cpp/comparison-with-wider-type`) | Higher precision | The precision of this query has been increased to "high" as the alerts from this query have proved to be valuable on real-world projects. With this precision, results are now displayed by default in LGTM. |
2727
| Non-constant format string (`cpp/non-constant-format`) | Fewer false positive results | Fixed false positives resulting from mistmatching declarations of a formatting function. |
28+
| Wrong type of arguments to formatting function (`cpp/wrong-type-format-argument`) | More correct results and fewer false positive results | This query now understands explicitly specified argument numbers in format strings, such as the `1$` in `%1$s`. |
2829

2930
## Changes to libraries
3031

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

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,17 @@ class FormattingFunctionCall extends Expr {
131131
}
132132

133133
/**
134-
* Gets the argument corresponding to the nth conversion specifier
134+
* Gets the argument corresponding to the nth conversion specifier.
135135
*/
136136
Expr getConversionArgument(int n) {
137-
exists(FormatLiteral fl, int b, int o |
137+
exists(FormatLiteral fl |
138138
fl = this.getFormat() and
139-
b = sum(int i, int toSum | i < n and toSum = fl.getNumArgNeeded(i) | toSum) and
140-
o = fl.getNumArgNeeded(n) and
141-
o > 0 and
142-
result = this.getFormatArgument(b + o - 1)
139+
(
140+
result = this.getFormatArgument(fl.getParameterFieldValue(n))
141+
or
142+
result = this.getFormatArgument(fl.getFormatArgumentIndexFor(n, 2)) and
143+
not exists(fl.getParameterFieldValue(n))
144+
)
143145
)
144146
}
145147

@@ -149,11 +151,14 @@ class FormattingFunctionCall extends Expr {
149151
* an explicit minimum field width).
150152
*/
151153
Expr getMinFieldWidthArgument(int n) {
152-
exists(FormatLiteral fl, int b |
154+
exists(FormatLiteral fl |
153155
fl = this.getFormat() and
154-
b = sum(int i, int toSum | i < n and toSum = fl.getNumArgNeeded(i) | toSum) and
155-
fl.hasImplicitMinFieldWidth(n) and
156-
result = this.getFormatArgument(b)
156+
(
157+
result = this.getFormatArgument(fl.getMinFieldWidthParameterFieldValue(n))
158+
or
159+
result = this.getFormatArgument(fl.getFormatArgumentIndexFor(n, 0)) and
160+
not exists(fl.getMinFieldWidthParameterFieldValue(n))
161+
)
157162
)
158163
}
159164

@@ -163,12 +168,14 @@ class FormattingFunctionCall extends Expr {
163168
* precision).
164169
*/
165170
Expr getPrecisionArgument(int n) {
166-
exists(FormatLiteral fl, int b, int o |
171+
exists(FormatLiteral fl |
167172
fl = this.getFormat() and
168-
b = sum(int i, int toSum | i < n and toSum = fl.getNumArgNeeded(i) | toSum) and
169-
(if fl.hasImplicitMinFieldWidth(n) then o = 1 else o = 0) and
170-
fl.hasImplicitPrecision(n) and
171-
result = this.getFormatArgument(b + o)
173+
(
174+
result = this.getFormatArgument(fl.getPrecisionParameterFieldValue(n))
175+
or
176+
result = this.getFormatArgument(fl.getFormatArgumentIndexFor(n, 1)) and
177+
not exists(fl.getPrecisionParameterFieldValue(n))
178+
)
172179
)
173180
}
174181

@@ -368,6 +375,14 @@ class FormatLiteral extends Literal {
368375
*/
369376
string getParameterField(int n) { this.parseConvSpec(n, _, result, _, _, _, _, _) }
370377

378+
/**
379+
* Gets the parameter field of the nth conversion specifier (if it has one) as a
380+
* zero-based number.
381+
*/
382+
int getParameterFieldValue(int n) {
383+
result = this.getParameterField(n).regexpCapture("([0-9]*)\\$", 1).toInt() - 1
384+
}
385+
371386
/**
372387
* Gets the flags of the nth conversion specifier.
373388
*/
@@ -437,6 +452,14 @@ class FormatLiteral extends Literal {
437452
*/
438453
int getMinFieldWidth(int n) { result = this.getMinFieldWidthOpt(n).toInt() }
439454

455+
/**
456+
* Gets the zero-based parameter number of the minimum field width of the nth
457+
* conversion specifier, if it is implicit and uses a parameter field (such as `*1$`).
458+
*/
459+
int getMinFieldWidthParameterFieldValue(int n) {
460+
result = this.getMinFieldWidthOpt(n).regexpCapture("\\*([0-9]*)\\$", 1).toInt() - 1
461+
}
462+
440463
/**
441464
* Gets the precision of the nth conversion specifier (empty string if none is given).
442465
*/
@@ -467,6 +490,14 @@ class FormatLiteral extends Literal {
467490
else result = this.getPrecisionOpt(n).regexpCapture("\\.([0-9]*)", 1).toInt()
468491
}
469492

493+
/**
494+
* Gets the zero-based parameter number of the precision of the nth conversion
495+
* specifier, if it is implicit and uses a parameter field (such as `*1$`).
496+
*/
497+
int getPrecisionParameterFieldValue(int n) {
498+
result = this.getPrecisionOpt(n).regexpCapture("\\.\\*([0-9]*)\\$", 1).toInt() - 1
499+
}
500+
470501
/**
471502
* Gets the length flag of the nth conversion specifier.
472503
*/
@@ -784,19 +815,49 @@ class FormatLiteral extends Literal {
784815
)
785816
}
786817

818+
/**
819+
* Holds if the nth conversion specifier of this format string (if `mode = 2`), it's
820+
* minimum field width (if `mode = 0`) or it's precision (if `mode = 1`) requires a
821+
* format argument.
822+
*
823+
* Most conversion specifiers require a format argument, whereas minimum field width
824+
* and precision only require a format argument if they are present and a `*` was
825+
* used for it's value in the format string.
826+
*/
827+
private predicate hasFormatArgumentIndexFor(int n, int mode) {
828+
mode = 0 and
829+
this.hasImplicitMinFieldWidth(n)
830+
or
831+
mode = 1 and
832+
this.hasImplicitPrecision(n)
833+
or
834+
mode = 2 and
835+
exists(this.getConvSpecOffset(n)) and
836+
not this.getConversionChar(n) = "m"
837+
}
838+
839+
/**
840+
* Gets the computed format argument index for the nth conversion specifier of this
841+
* format string (if `mode = 2`), it's minimum field width (if `mode = 0`) or it's
842+
* precision (if `mode = 1`). Has no result if that element is not present. Does
843+
* not account for positional arguments (`$`).
844+
*/
845+
int getFormatArgumentIndexFor(int n, int mode) {
846+
hasFormatArgumentIndexFor(n, mode) and
847+
(3 * n) + mode = rank[result + 1](int n2, int mode2 |
848+
hasFormatArgumentIndexFor(n2, mode2)
849+
|
850+
(3 * n2) + mode2
851+
)
852+
}
853+
787854
/**
788855
* Gets the number of arguments required by the nth conversion specifier
789856
* of this format string.
790857
*/
791858
int getNumArgNeeded(int n) {
792859
exists(this.getConvSpecOffset(n)) and
793-
not this.getConversionChar(n) = "%" and
794-
exists(int n1, int n2, int n3 |
795-
(if this.hasImplicitMinFieldWidth(n) then n1 = 1 else n1 = 0) and
796-
(if this.hasImplicitPrecision(n) then n2 = 1 else n2 = 0) and
797-
(if this.getConversionChar(n) = "m" then n3 = 0 else n3 = 1) and
798-
result = n1 + n2 + n3
799-
)
860+
result = count(int mode | hasFormatArgumentIndexFor(n, mode))
800861
}
801862

802863
/**
@@ -808,7 +869,7 @@ class FormatLiteral extends Literal {
808869
// At least one conversion specifier has a parameter field, in which case,
809870
// they all should have.
810871
result = max(string s | this.getParameterField(_) = s + "$" | s.toInt())
811-
else result = sum(int n, int toSum | toSum = this.getNumArgNeeded(n) | toSum)
872+
else result = count(int n, int mode | hasFormatArgumentIndexFor(n, mode))
812873
}
813874

814875
/**

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,43 @@
1616
| printf1.h:114:18:114:18 | d | This argument should be of type 'long double' but is of type 'double' |
1717
| printf1.h:147:19:147:19 | i | This argument should be of type 'long long' but is of type 'int' |
1818
| printf1.h:148:19:148:20 | ui | This argument should be of type 'unsigned long long' but is of type 'unsigned int' |
19+
| printf1.h:160:18:160:18 | i | This argument should be of type 'char *' but is of type 'int' |
20+
| printf1.h:161:21:161:21 | s | This argument should be of type 'int' but is of type 'char *' |
21+
| printf1.h:167:17:167:17 | i | This argument should be of type 'char *' but is of type 'int' |
22+
| printf1.h:168:18:168:18 | i | This argument should be of type 'char *' but is of type 'int' |
23+
| printf1.h:169:19:169:19 | i | This argument should be of type 'char *' but is of type 'int' |
24+
| printf1.h:174:17:174:17 | s | This argument should be of type 'int' but is of type 'char *' |
25+
| printf1.h:175:18:175:18 | s | This argument should be of type 'int' but is of type 'char *' |
26+
| printf1.h:176:19:176:19 | s | This argument should be of type 'int' but is of type 'char *' |
27+
| printf1.h:180:17:180:17 | s | This argument should be of type 'int' but is of type 'char *' |
28+
| printf1.h:181:20:181:20 | i | This argument should be of type 'char *' but is of type 'int' |
29+
| printf1.h:183:18:183:18 | s | This argument should be of type 'int' but is of type 'char *' |
30+
| printf1.h:184:21:184:21 | i | This argument should be of type 'char *' but is of type 'int' |
31+
| printf1.h:186:19:186:19 | s | This argument should be of type 'int' but is of type 'char *' |
32+
| printf1.h:187:22:187:22 | i | This argument should be of type 'char *' but is of type 'int' |
33+
| printf1.h:189:19:189:19 | s | This argument should be of type 'int' but is of type 'char *' |
34+
| printf1.h:190:22:190:22 | i | This argument should be of type 'char *' but is of type 'int' |
35+
| printf1.h:192:19:192:19 | s | This argument should be of type 'int' but is of type 'char *' |
36+
| printf1.h:193:22:193:22 | s | This argument should be of type 'int' but is of type 'char *' |
37+
| printf1.h:194:25:194:25 | i | This argument should be of type 'char *' but is of type 'int' |
38+
| printf1.h:198:24:198:24 | s | This argument should be of type 'int' but is of type 'char *' |
39+
| printf1.h:199:21:199:21 | i | This argument should be of type 'char *' but is of type 'int' |
40+
| printf1.h:202:26:202:26 | s | This argument should be of type 'int' but is of type 'char *' |
41+
| printf1.h:203:23:203:23 | i | This argument should be of type 'char *' but is of type 'int' |
42+
| printf1.h:206:25:206:25 | s | This argument should be of type 'int' but is of type 'char *' |
43+
| printf1.h:207:22:207:22 | i | This argument should be of type 'char *' but is of type 'int' |
44+
| printf1.h:210:26:210:26 | s | This argument should be of type 'int' but is of type 'char *' |
45+
| printf1.h:211:23:211:23 | i | This argument should be of type 'char *' but is of type 'int' |
46+
| printf1.h:214:28:214:28 | s | This argument should be of type 'int' but is of type 'char *' |
47+
| printf1.h:215:28:215:28 | s | This argument should be of type 'int' but is of type 'char *' |
48+
| printf1.h:216:25:216:25 | i | This argument should be of type 'char *' but is of type 'int' |
49+
| printf1.h:221:18:221:18 | s | This argument should be of type 'int' but is of type 'char *' |
50+
| printf1.h:222:20:222:20 | s | This argument should be of type 'int' but is of type 'char *' |
51+
| printf1.h:225:23:225:23 | i | This argument should be of type 'char *' but is of type 'int' |
52+
| printf1.h:228:24:228:24 | i | This argument should be of type 'char *' but is of type 'int' |
53+
| printf1.h:231:25:231:25 | i | This argument should be of type 'char *' but is of type 'int' |
54+
| printf1.h:234:25:234:25 | i | This argument should be of type 'char *' but is of type 'int' |
55+
| printf1.h:235:22:235:22 | s | This argument should be of type 'int' but is of type 'char *' |
1956
| real_world.h:61:21:61:22 | & ... | This argument should be of type 'int *' but is of type 'short *' |
2057
| real_world.h:62:22:62:23 | & ... | This argument should be of type 'short *' but is of type 'int *' |
2158
| real_world.h:63:22:63:24 | & ... | This argument should be of type 'short *' but is of type 'unsigned int *' |

cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_signed_chars/printf1.h

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,86 @@ void fun4()
151151
printf("%qi\n", ll); // GOOD
152152
printf("%qu\n", ull); // GOOD
153153
}
154+
155+
void complexFormatSymbols(int i, const char *s)
156+
{
157+
// positional arguments
158+
printf("%1$i", i, s); // GOOD
159+
printf("%2$s", i, s); // GOOD
160+
printf("%1$s", i, s); // BAD
161+
printf("%2$i", i, s); // BAD
162+
163+
// width / precision
164+
printf("%4i", i); // GOOD
165+
printf("%.4i", i); // GOOD
166+
printf("%4.4i", i); // GOOD
167+
printf("%4s", i); // BAD
168+
printf("%.4s", i); // BAD
169+
printf("%4.4s", i); // BAD
170+
171+
printf("%4s", s); // GOOD
172+
printf("%.4s", s); // GOOD
173+
printf("%4.4s", s); // GOOD
174+
printf("%4i", s); // BAD
175+
printf("%.4i", s); // BAD
176+
printf("%4.4i", s); // BAD
177+
178+
// variable width / precision
179+
printf("%*s", i, s); // GOOD
180+
printf("%*s", s, s); // BAD
181+
printf("%*s", i, i); // BAD
182+
printf("%.*s", i, s); // GOOD
183+
printf("%.*s", s, s); // BAD
184+
printf("%.*s", i, i); // BAD
185+
printf("%*.4s", i, s); // GOOD
186+
printf("%*.4s", s, s); // BAD
187+
printf("%*.4s", i, i); // BAD
188+
printf("%4.*s", i, s); // GOOD
189+
printf("%4.*s", s, s); // BAD
190+
printf("%4.*s", i, i); // BAD
191+
printf("%*.*s", i, i, s); // GOOD
192+
printf("%*.*s", s, i, s); // BAD
193+
printf("%*.*s", i, s, s); // BAD
194+
printf("%*.*s", i, i, i); // BAD
195+
196+
// positional arguments mixed with variable width / precision
197+
printf("%2$*1$s", i, s); // GOOD
198+
printf("%2$*2$s", i, s); // BAD
199+
printf("%1$*1$s", i, s); // BAD
200+
201+
printf("%2$*1$.4s", i, s); // GOOD
202+
printf("%2$*2$.4s", i, s); // BAD
203+
printf("%1$*1$.4s", i, s); // BAD
204+
205+
printf("%2$.*1$s", i, s); // GOOD
206+
printf("%2$.*2$s", i, s); // BAD
207+
printf("%1$.*1$s", i, s); // BAD
208+
209+
printf("%2$4.*1$s", i, s); // GOOD
210+
printf("%2$4.*2$s", i, s); // BAD
211+
printf("%1$4.*1$s", i, s); // BAD
212+
213+
printf("%2$*1$.*1$s", i, s); // GOOD
214+
printf("%2$*2$.*1$s", i, s); // BAD
215+
printf("%2$*1$.*2$s", i, s); // BAD
216+
printf("%1$*1$.*1$s", i, s); // BAD
217+
218+
// left justify flag
219+
printf("%-4s", s); // GOOD
220+
printf("%1$-4s", s); // GOOD
221+
printf("%-4i", s); // BAD
222+
printf("%1$-4i", s); // BAD
223+
224+
printf("%1$-4s", s, i); // GOOD
225+
printf("%2$-4s", s, i); // BAD
226+
227+
printf("%1$-.4s", s, i); // GOOD
228+
printf("%2$-.4s", s, i); // BAD
229+
230+
printf("%1$-4.4s", s, i); // GOOD
231+
printf("%2$-4.4s", s, i); // BAD
232+
233+
printf("%1$-*2$s", s, i); // GOOD
234+
printf("%2$-*2$s", s, i); // BAD
235+
printf("%1$-*1$s", s, i); // BAD
236+
}

0 commit comments

Comments
 (0)