@@ -4,13 +4,22 @@ import semmle.code.java.dataflow.ExternalFlow
44import semmle.code.java.dataflow.FlowSummary
55import semmle.code.java.dataflow.internal.FlowSummaryImpl
66
7+ /**
8+ * A CSV row to generate tests for. Users should extend this to define their input rows.
9+ */
710bindingset [ this ]
811abstract class CsvRow extends string { }
912
13+ /**
14+ * Returns type of parameter `i` of `callable`, including the type of `this` for parameter -1.
15+ */
1016Type getParameterType ( Private:: External:: SummarizedCallableExternal callable , int i ) {
1117 if i = - 1 then result = callable .getDeclaringType ( ) else result = callable .getParameterType ( i )
1218}
1319
20+ /**
21+ * Returns a zero value of primitive type `t`.
22+ */
1423string getZero ( PrimitiveType t ) {
1524 t .hasName ( "float" ) and result = "0.0f"
1625 or
@@ -29,6 +38,9 @@ string getZero(PrimitiveType t) {
2938 t .hasName ( "long" ) and result = "0L"
3039}
3140
41+ /**
42+ * Holds if `c` may require disambiguation from an overload with the same argument count.
43+ */
3244predicate mayBeAmbiguous ( Callable c ) {
3345 exists ( Callable other , string package , string type , string name |
3446 c .hasQualifiedName ( package , type , name ) and
@@ -38,14 +50,23 @@ predicate mayBeAmbiguous(Callable c) {
3850 )
3951}
4052
53+ /**
54+ * Returns the `content` wrapped by `component`, if any.
55+ */
4156Content getContent ( SummaryComponent component ) { component = SummaryComponent:: content ( result ) }
4257
58+ /**
59+ * Returns a valid Java token naming the field `fc`.
60+ */
4361string getFieldToken ( FieldContent fc ) {
4462 result =
4563 fc .getField ( ) .getDeclaringType ( ) .getSourceDeclaration ( ) .getName ( ) + "_" +
4664 fc .getField ( ) .getName ( )
4765}
4866
67+ /**
68+ * Returns a token suitable for incorporation into a Java method name describing content `c`.
69+ */
4970string contentToken ( Content c ) {
5071 c instanceof ArrayContent and result = "ArrayElement"
5172 or
@@ -58,24 +79,38 @@ string contentToken(Content c) {
5879 result = getFieldToken ( c )
5980}
6081
82+ /**
83+ * Returns the outermost type enclosing type `t` (which may be `t` itself).
84+ */
6185RefType getRootType ( RefType t ) {
6286 if t instanceof NestedType
6387 then result = getRootType ( t .( NestedType ) .getEnclosingType ( ) )
6488 else result = t
6589}
6690
91+ /**
92+ * Returns `t`'s first upper bound if `t` is a type variable; otherwise returns `t`.
93+ */
6794RefType replaceTypeVariable ( RefType t ) {
6895 if t instanceof TypeVariable
6996 then result = t .( TypeVariable ) .getFirstUpperBoundType ( )
7097 else result = t
7198}
7299
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+ */
73103Type getRootSourceDeclaration ( Type t ) {
74104 if t instanceof RefType
75105 then result = getRootType ( replaceTypeVariable ( t ) ) .getSourceDeclaration ( )
76106 else result = t
77107}
78108
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+ */
79114newtype TRowTestSnippet =
80115 MkSnippet (
81116 CsvRow row , Private:: External:: SummarizedCallableExternal callable , SummaryComponentStack input ,
@@ -84,6 +119,10 @@ newtype TRowTestSnippet =
84119 callable .propagatesFlowForRow ( input , output , preservesValue , row )
85120 }
86121
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+ */
87126class RowTestSnippet extends TRowTestSnippet {
88127 string row ;
89128 Private:: External:: SummarizedCallableExternal callable ;
@@ -105,19 +144,28 @@ class RowTestSnippet extends TRowTestSnippet {
105144 baseOutput + " / " + preservesValue
106145 }
107146
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+ */
108151 string getFiller ( int argIdx ) {
109152 exists ( Type t | t = callable .getParameterType ( argIdx ) |
110153 t instanceof RefType and
111154 (
112155 if mayBeAmbiguous ( callable )
113- then result = "(" + getShortNameIfPossible ( t . ( RefType ) . getSourceDeclaration ( ) ) + ")null"
156+ then result = "(" + getShortNameIfPossible ( t ) + ")null"
114157 else result = "null"
115158 )
116159 or
117160 result = getZero ( t )
118161 )
119162 }
120163
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+ */
121169 string getArgument ( int i ) {
122170 ( i = - 1 or exists ( callable .getParameter ( i ) ) ) and
123171 if baseInput = SummaryComponentStack:: argument ( i )
@@ -131,7 +179,11 @@ class RowTestSnippet extends TRowTestSnippet {
131179 )
132180 }
133181
182+ /**
183+ * Returns a statement invoking `callable`, passing `input` and capturing `output` as needed.
184+ */
134185 string makeCall ( ) {
186+ // For example, one of:
135187 // out = in.method(filler);
136188 // or
137189 // out = filler.method(filler, in, filler);
@@ -159,22 +211,26 @@ class RowTestSnippet extends TRowTestSnippet {
159211 then invokePrefix = "new "
160212 else
161213 if callable .( Method ) .isStatic ( )
162- then
163- invokePrefix =
164- getShortNameIfPossible ( callable .getDeclaringType ( ) .getSourceDeclaration ( ) ) + "."
214+ then invokePrefix = getShortNameIfPossible ( callable .getDeclaringType ( ) ) + "."
165215 else invokePrefix = this .getArgument ( - 1 ) + "."
166216 ) and
167217 args = concat ( int i | i >= 0 | this .getArgument ( i ) , ", " order by i ) and
168218 result = storePrefix + invokePrefix + callable .getName ( ) + "(" + args + ")"
169219 )
170220 }
171221
222+ /**
223+ * Returns an inline test expectation appropriate to this CSV row.
224+ */
172225 string getExpectation ( ) {
173226 preservesValue = true and result = "// $hasValueFlow"
174227 or
175228 preservesValue = false and result = "// $hasTaintFlow"
176229 }
177230
231+ /**
232+ * Returns a declaration and initialisation of a variable named `instance` if required; otherwise returns an empty string.
233+ */
178234 string getInstancePrefix ( ) {
179235 if
180236 callable instanceof Method and
@@ -187,6 +243,9 @@ class RowTestSnippet extends TRowTestSnippet {
187243 else result = ""
188244 }
189245
246+ /**
247+ * Returns the type of the output for this test.
248+ */
190249 Type getOutputType ( ) {
191250 if baseOutput = SummaryComponentStack:: return ( )
192251 then result = callable .getReturnType ( )
@@ -197,15 +256,26 @@ class RowTestSnippet extends TRowTestSnippet {
197256 )
198257 }
199258
259+ /**
260+ * Returns the type of the input for this test.
261+ */
200262 Type getInputType ( ) {
201263 exists ( int i |
202264 baseInput = SummaryComponentStack:: argument ( i ) and
203265 result = getParameterType ( callable , i )
204266 )
205267 }
206268
269+ /**
270+ * Returns the Java name for the type of the input to this test.
271+ */
207272 string getInputTypeString ( ) { result = getShortNameIfPossible ( this .getInputType ( ) ) }
208273
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+ */
209279 string getInput ( SummaryComponentStack componentStack ) {
210280 componentStack = input .drop ( _) and
211281 (
@@ -218,6 +288,11 @@ class RowTestSnippet extends TRowTestSnippet {
218288 )
219289 }
220290
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+ */
221296 string getOutput ( SummaryComponentStack componentStack ) {
222297 componentStack = output .drop ( _) and
223298 (
@@ -230,6 +305,9 @@ class RowTestSnippet extends TRowTestSnippet {
230305 )
231306 }
232307
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+ */
233311 string getASupportMethod ( ) {
234312 result =
235313 "Object newWith" + contentToken ( getContent ( input .drop ( _) .head ( ) ) ) +
@@ -239,6 +317,12 @@ class RowTestSnippet extends TRowTestSnippet {
239317 "(Object container) { return null; }"
240318 }
241319
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+ */
242326 string getASupportMethodModel ( ) {
243327 exists ( SummaryComponent c , string contentSsvDescription |
244328 c = input .drop ( _) .head ( ) and c = Private:: External:: interpretComponent ( contentSsvDescription )
@@ -257,6 +341,10 @@ class RowTestSnippet extends TRowTestSnippet {
257341 )
258342 }
259343
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+ */
260348 Type getADesiredImport ( ) {
261349 result =
262350 getRootSourceDeclaration ( [
@@ -267,6 +355,10 @@ class RowTestSnippet extends TRowTestSnippet {
267355 mayBeAmbiguous ( callable ) and result = getRootSourceDeclaration ( callable .getAParamType ( ) )
268356 }
269357
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+ */
270362 string getATestSnippetForRow ( string row_ ) {
271363 row_ = row and
272364 result =
@@ -277,6 +369,9 @@ class RowTestSnippet extends TRowTestSnippet {
277369 }
278370}
279371
372+ /**
373+ * Holds if type `t` does not clash with another type we want to import that has the same base name.
374+ */
280375predicate isImportable ( Type t ) {
281376 t = any ( RowTestSnippet r ) .getADesiredImport ( ) and
282377 t =
@@ -288,6 +383,11 @@ predicate isImportable(Type t) {
288383 )
289384}
290385
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+ */
291391string getShortNameIfPossible ( Type t ) {
292392 getRootSourceDeclaration ( t ) = any ( RowTestSnippet r ) .getADesiredImport ( ) and
293393 if t instanceof RefType
@@ -303,6 +403,9 @@ string getShortNameIfPossible(Type t) {
303403 else result = t .getName ( )
304404}
305405
406+ /**
407+ * Returns an import statement to include in the test case header.
408+ */
306409string getAnImportStatement ( ) {
307410 exists ( RefType t |
308411 t = any ( RowTestSnippet r ) .getADesiredImport ( ) and
@@ -313,14 +416,23 @@ string getAnImportStatement() {
313416 )
314417}
315418
419+ /**
420+ * Returns a support method to include in the generated test class.
421+ */
316422string getASupportMethod ( ) {
317423 result = "Object source() { return null; }" or
318424 result = "void sink(Object o) { }" or
319425 result = any ( RowTestSnippet r ) .getASupportMethod ( )
320426}
321427
428+ /**
429+ * Returns a CSV specification of the taint-/value-propagation behaviour of a test support method (`get` or `newWith` method).
430+ */
322431query string getASupportMethodModel ( ) { result = any ( RowTestSnippet r ) .getASupportMethodModel ( ) }
323432
433+ /**
434+ * Gets a Java file body testing all `CsvRow` instances in scope against whatever classes and methods they resolve against.
435+ */
324436query string getTestCase ( ) {
325437 result =
326438 "package generatedtest;\n\n" + concat ( getAnImportStatement ( ) + "\n" ) +
0 commit comments