@@ -4,13 +4,22 @@ import semmle.code.java.dataflow.ExternalFlow
4
4
import semmle.code.java.dataflow.FlowSummary
5
5
import semmle.code.java.dataflow.internal.FlowSummaryImpl
6
6
7
+ /**
8
+ * A CSV row to generate tests for. Users should extend this to define their input rows.
9
+ */
7
10
bindingset [ this ]
8
11
abstract class CsvRow extends string { }
9
12
13
+ /**
14
+ * Returns type of parameter `i` of `callable`, including the type of `this` for parameter -1.
15
+ */
10
16
Type getParameterType ( Private:: External:: SummarizedCallableExternal callable , int i ) {
11
17
if i = - 1 then result = callable .getDeclaringType ( ) else result = callable .getParameterType ( i )
12
18
}
13
19
20
+ /**
21
+ * Returns a zero value of primitive type `t`.
22
+ */
14
23
string getZero ( PrimitiveType t ) {
15
24
t .hasName ( "float" ) and result = "0.0f"
16
25
or
@@ -29,6 +38,9 @@ string getZero(PrimitiveType t) {
29
38
t .hasName ( "long" ) and result = "0L"
30
39
}
31
40
41
+ /**
42
+ * Holds if `c` may require disambiguation from an overload with the same argument count.
43
+ */
32
44
predicate mayBeAmbiguous ( Callable c ) {
33
45
exists ( Callable other , string package , string type , string name |
34
46
c .hasQualifiedName ( package , type , name ) and
@@ -38,14 +50,23 @@ predicate mayBeAmbiguous(Callable c) {
38
50
)
39
51
}
40
52
53
+ /**
54
+ * Returns the `content` wrapped by `component`, if any.
55
+ */
41
56
Content getContent ( SummaryComponent component ) { component = SummaryComponent:: content ( result ) }
42
57
58
+ /**
59
+ * Returns a valid Java token naming the field `fc`.
60
+ */
43
61
string getFieldToken ( FieldContent fc ) {
44
62
result =
45
63
fc .getField ( ) .getDeclaringType ( ) .getSourceDeclaration ( ) .getName ( ) + "_" +
46
64
fc .getField ( ) .getName ( )
47
65
}
48
66
67
+ /**
68
+ * Returns a token suitable for incorporation into a Java method name describing content `c`.
69
+ */
49
70
string contentToken ( Content c ) {
50
71
c instanceof ArrayContent and result = "ArrayElement"
51
72
or
@@ -58,24 +79,38 @@ string contentToken(Content c) {
58
79
result = getFieldToken ( c )
59
80
}
60
81
82
+ /**
83
+ * Returns the outermost type enclosing type `t` (which may be `t` itself).
84
+ */
61
85
RefType getRootType ( RefType t ) {
62
86
if t instanceof NestedType
63
87
then result = getRootType ( t .( NestedType ) .getEnclosingType ( ) )
64
88
else result = t
65
89
}
66
90
91
+ /**
92
+ * Returns `t`'s first upper bound if `t` is a type variable; otherwise returns `t`.
93
+ */
67
94
RefType replaceTypeVariable ( RefType t ) {
68
95
if t instanceof TypeVariable
69
96
then result = t .( TypeVariable ) .getFirstUpperBoundType ( )
70
97
else result = t
71
98
}
72
99
100
+ /**
101
+ * Returns `t`'s outermost enclosing type, in raw form (i.e. generic types are given without generic parameters, and type variables are replaced by their bounds).
102
+ */
73
103
Type getRootSourceDeclaration ( Type t ) {
74
104
if t instanceof RefType
75
105
then result = getRootType ( replaceTypeVariable ( t ) ) .getSourceDeclaration ( )
76
106
else result = t
77
107
}
78
108
109
+ /**
110
+ * A test snippet (a fragment of Java code that checks that `row` causes `callable` to propagate value/taint (according to `preservesValue`)
111
+ * from `input` to `output`). Usually there is one of these per CSV row (`row`), but there may be more if `row` describes more than one
112
+ * override or overload of a particular method, or if the input or output specifications cover more than one argument.
113
+ */
79
114
newtype TRowTestSnippet =
80
115
MkSnippet (
81
116
CsvRow row , Private:: External:: SummarizedCallableExternal callable , SummaryComponentStack input ,
@@ -84,6 +119,10 @@ newtype TRowTestSnippet =
84
119
callable .propagatesFlowForRow ( input , output , preservesValue , row )
85
120
}
86
121
122
+ /**
123
+ * A test snippet (as `TRowTestSnippet`, except `baseInput` and `baseOutput` hold the bottom of the summary stacks
124
+ * `input` and `output` respectively (hence, `baseInput` and `baseOutput` are parameters or return values).
125
+ */
87
126
class RowTestSnippet extends TRowTestSnippet {
88
127
string row ;
89
128
Private:: External:: SummarizedCallableExternal callable ;
@@ -105,19 +144,28 @@ class RowTestSnippet extends TRowTestSnippet {
105
144
baseOutput + " / " + preservesValue
106
145
}
107
146
147
+ /**
148
+ * Returns a value to pass as `callable`'s `argIdx`th argument whose value is irrelevant to the test
149
+ * being generated. This will be a zero or a null value, perhaps typecast if we need to disambiguate overloads.
150
+ */
108
151
string getFiller ( int argIdx ) {
109
152
exists ( Type t | t = callable .getParameterType ( argIdx ) |
110
153
t instanceof RefType and
111
154
(
112
155
if mayBeAmbiguous ( callable )
113
- then result = "(" + getShortNameIfPossible ( t . ( RefType ) . getSourceDeclaration ( ) ) + ")null"
156
+ then result = "(" + getShortNameIfPossible ( t ) + ")null"
114
157
else result = "null"
115
158
)
116
159
or
117
160
result = getZero ( t )
118
161
)
119
162
}
120
163
164
+ /**
165
+ * Returns the value to pass for `callable`'s `i`th argument, which may be `in` if this is the input argument for
166
+ * this test, `out` if it is the output, `instance` if this is an instance method and the instance is neither the
167
+ * input nor the output, or a zero/null filler value otherwise.
168
+ */
121
169
string getArgument ( int i ) {
122
170
( i = - 1 or exists ( callable .getParameter ( i ) ) ) and
123
171
if baseInput = SummaryComponentStack:: argument ( i )
@@ -131,7 +179,11 @@ class RowTestSnippet extends TRowTestSnippet {
131
179
)
132
180
}
133
181
182
+ /**
183
+ * Returns a statement invoking `callable`, passing `input` and capturing `output` as needed.
184
+ */
134
185
string makeCall ( ) {
186
+ // For example, one of:
135
187
// out = in.method(filler);
136
188
// or
137
189
// out = filler.method(filler, in, filler);
@@ -159,22 +211,26 @@ class RowTestSnippet extends TRowTestSnippet {
159
211
then invokePrefix = "new "
160
212
else
161
213
if callable .( Method ) .isStatic ( )
162
- then
163
- invokePrefix =
164
- getShortNameIfPossible ( callable .getDeclaringType ( ) .getSourceDeclaration ( ) ) + "."
214
+ then invokePrefix = getShortNameIfPossible ( callable .getDeclaringType ( ) ) + "."
165
215
else invokePrefix = this .getArgument ( - 1 ) + "."
166
216
) and
167
217
args = concat ( int i | i >= 0 | this .getArgument ( i ) , ", " order by i ) and
168
218
result = storePrefix + invokePrefix + callable .getName ( ) + "(" + args + ")"
169
219
)
170
220
}
171
221
222
+ /**
223
+ * Returns an inline test expectation appropriate to this CSV row.
224
+ */
172
225
string getExpectation ( ) {
173
226
preservesValue = true and result = "// $hasValueFlow"
174
227
or
175
228
preservesValue = false and result = "// $hasTaintFlow"
176
229
}
177
230
231
+ /**
232
+ * Returns a declaration and initialisation of a variable named `instance` if required; otherwise returns an empty string.
233
+ */
178
234
string getInstancePrefix ( ) {
179
235
if
180
236
callable instanceof Method and
@@ -187,6 +243,9 @@ class RowTestSnippet extends TRowTestSnippet {
187
243
else result = ""
188
244
}
189
245
246
+ /**
247
+ * Returns the type of the output for this test.
248
+ */
190
249
Type getOutputType ( ) {
191
250
if baseOutput = SummaryComponentStack:: return ( )
192
251
then result = callable .getReturnType ( )
@@ -197,15 +256,26 @@ class RowTestSnippet extends TRowTestSnippet {
197
256
)
198
257
}
199
258
259
+ /**
260
+ * Returns the type of the input for this test.
261
+ */
200
262
Type getInputType ( ) {
201
263
exists ( int i |
202
264
baseInput = SummaryComponentStack:: argument ( i ) and
203
265
result = getParameterType ( callable , i )
204
266
)
205
267
}
206
268
269
+ /**
270
+ * Returns the Java name for the type of the input to this test.
271
+ */
207
272
string getInputTypeString ( ) { result = getShortNameIfPossible ( this .getInputType ( ) ) }
208
273
274
+ /**
275
+ * Returns a call to `source()` wrapped in `newWith` methods as needed according to `input`.
276
+ * For example, if the input specification is `ArrayElement of MapValue of Argument[0]`, this
277
+ * will return `newWithArrayElement(newWithMapValue(source()))`.
278
+ */
209
279
string getInput ( SummaryComponentStack componentStack ) {
210
280
componentStack = input .drop ( _) and
211
281
(
@@ -218,6 +288,11 @@ class RowTestSnippet extends TRowTestSnippet {
218
288
)
219
289
}
220
290
291
+ /**
292
+ * Returns `out` wrapped in `get` methods as needed according to `output`.
293
+ * For example, if the output specification is `ArrayElement of MapValue of Argument[0]`, this
294
+ * will return `getArrayElement(getMapValue(out))`.
295
+ */
221
296
string getOutput ( SummaryComponentStack componentStack ) {
222
297
componentStack = output .drop ( _) and
223
298
(
@@ -230,6 +305,9 @@ class RowTestSnippet extends TRowTestSnippet {
230
305
)
231
306
}
232
307
308
+ /**
309
+ * Returns the definition of a `newWith` method needed to set up the input or a `get` method needed to set up the output for this test.
310
+ */
233
311
string getASupportMethod ( ) {
234
312
result =
235
313
"Object newWith" + contentToken ( getContent ( input .drop ( _) .head ( ) ) ) +
@@ -239,6 +317,12 @@ class RowTestSnippet extends TRowTestSnippet {
239
317
"(Object container) { return null; }"
240
318
}
241
319
320
+ /**
321
+ * Returns a CSV row describing a support method (`newWith` or `get` method) needed to set up the output for this test.
322
+ *
323
+ * For example, `newWithMapValue` will propagate a value from `Argument[0]` to `MapValue of ReturnValue`, and `getMapValue`
324
+ * will do the opposite.
325
+ */
242
326
string getASupportMethodModel ( ) {
243
327
exists ( SummaryComponent c , string contentSsvDescription |
244
328
c = input .drop ( _) .head ( ) and c = Private:: External:: interpretComponent ( contentSsvDescription )
@@ -257,6 +341,10 @@ class RowTestSnippet extends TRowTestSnippet {
257
341
)
258
342
}
259
343
344
+ /**
345
+ * Gets an outer class name that this test would ideally import (and will, unless it clashes with another
346
+ * type of the same name).
347
+ */
260
348
Type getADesiredImport ( ) {
261
349
result =
262
350
getRootSourceDeclaration ( [
@@ -267,6 +355,10 @@ class RowTestSnippet extends TRowTestSnippet {
267
355
mayBeAmbiguous ( callable ) and result = getRootSourceDeclaration ( callable .getAParamType ( ) )
268
356
}
269
357
358
+ /**
359
+ * Gets a test snippet (test body fragment) testing this `callable` propagates value or taint from
360
+ * `input` to `output`, as specified by `row_` (which necessarily equals `row`).
361
+ */
270
362
string getATestSnippetForRow ( string row_ ) {
271
363
row_ = row and
272
364
result =
@@ -277,6 +369,9 @@ class RowTestSnippet extends TRowTestSnippet {
277
369
}
278
370
}
279
371
372
+ /**
373
+ * Holds if type `t` does not clash with another type we want to import that has the same base name.
374
+ */
280
375
predicate isImportable ( Type t ) {
281
376
t = any ( RowTestSnippet r ) .getADesiredImport ( ) and
282
377
t =
@@ -288,6 +383,11 @@ predicate isImportable(Type t) {
288
383
)
289
384
}
290
385
386
+ /**
387
+ * Returns a printable name for type `t`, stripped of generics and, if a type variable,
388
+ * replaced by its bound. Usually this is a short name, but it may be package-qualified
389
+ * if we cannot import it due to a name clash.
390
+ */
291
391
string getShortNameIfPossible ( Type t ) {
292
392
getRootSourceDeclaration ( t ) = any ( RowTestSnippet r ) .getADesiredImport ( ) and
293
393
if t instanceof RefType
@@ -303,6 +403,9 @@ string getShortNameIfPossible(Type t) {
303
403
else result = t .getName ( )
304
404
}
305
405
406
+ /**
407
+ * Returns an import statement to include in the test case header.
408
+ */
306
409
string getAnImportStatement ( ) {
307
410
exists ( RefType t |
308
411
t = any ( RowTestSnippet r ) .getADesiredImport ( ) and
@@ -313,14 +416,23 @@ string getAnImportStatement() {
313
416
)
314
417
}
315
418
419
+ /**
420
+ * Returns a support method to include in the generated test class.
421
+ */
316
422
string getASupportMethod ( ) {
317
423
result = "Object source() { return null; }" or
318
424
result = "void sink(Object o) { }" or
319
425
result = any ( RowTestSnippet r ) .getASupportMethod ( )
320
426
}
321
427
428
+ /**
429
+ * Returns a CSV specification of the taint-/value-propagation behaviour of a test support method (`get` or `newWith` method).
430
+ */
322
431
query string getASupportMethodModel ( ) { result = any ( RowTestSnippet r ) .getASupportMethodModel ( ) }
323
432
433
+ /**
434
+ * Gets a Java file body testing all `CsvRow` instances in scope against whatever classes and methods they resolve against.
435
+ */
324
436
query string getTestCase ( ) {
325
437
result =
326
438
"package generatedtest;\n\n" + concat ( getAnImportStatement ( ) + "\n" ) +
0 commit comments