Skip to content

Commit 2b0f6a2

Browse files
smowtonSauyon Lee
authored andcommitted
Java: Generate more realistic tests
1 parent 33c727e commit 2b0f6a2

File tree

4 files changed

+323
-44
lines changed

4 files changed

+323
-44
lines changed

java/ql/src/utils/FlowTestCase.qll

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,13 @@ class TestCase extends TTestCase {
234234
string getInput(SummaryComponentStack stack) {
235235
stack = input and result = "source()"
236236
or
237-
exists(SummaryComponentStack s |
238-
result = "newWith" + contentToken(getContent(s.head())) + "(" + this.getInput(s) + ")" and
239-
stack = s.tail()
237+
exists(SummaryComponentStack s | s.tail() = stack |
238+
// we currently only know the type if the stack is one level in
239+
s = input and
240+
result = SupportMethod::genMethodFor(this.getInputType(), s).getCall(this.getInput(s))
241+
or
242+
not s = input and
243+
result = SupportMethod::genMethodForContent(s).getCall(this.getInput(s))
240244
)
241245
}
242246

@@ -252,45 +256,19 @@ class TestCase extends TTestCase {
252256
then result = "out"
253257
else
254258
result =
255-
"get" + contentToken(getContent(componentStack.head())) + "(" +
256-
this.getOutput(componentStack.tail()) + ")"
259+
SupportMethod::getMethodForContent(componentStack)
260+
.getCall(this.getOutput(componentStack.tail()))
257261
)
258262
}
259263

260264
/**
261265
* 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.
262266
*/
263-
string getASupportMethod() {
264-
result =
265-
"Object newWith" + contentToken(getContent(input.drop(_).head())) +
266-
"(Object element) { return null; }" or
267-
result =
268-
"Object get" + contentToken(getContent(output.drop(_).head())) +
269-
"(Object container) { return null; }"
270-
}
271-
272-
/**
273-
* Returns a CSV row describing a support method (`newWith` or `get` method) needed to set up the output for this test.
274-
*
275-
* For example, `newWithMapValue` will propagate a value from `Argument[0]` to `MapValue of ReturnValue`, and `getMapValue`
276-
* will do the opposite.
277-
*/
278-
string getASupportMethodModel() {
279-
exists(SummaryComponent c, string contentCsvDescription |
280-
c = input.drop(_).head() and contentCsvDescription = getComponentSpec(c)
281-
|
282-
result =
283-
"generatedtest;Test;false;newWith" + contentToken(getContent(c)) + ";;;Argument[0];" +
284-
contentCsvDescription + " of ReturnValue;value"
285-
)
286-
or
287-
exists(SummaryComponent c, string contentCsvDescription |
288-
c = output.drop(_).head() and contentCsvDescription = getComponentSpec(c)
289-
|
290-
result =
291-
"generatedtest;Test;false;get" + contentToken(getContent(c)) + ";;;" + contentCsvDescription
292-
+ " of Argument[0];ReturnValue;value"
293-
)
267+
SupportMethod getASupportMethod() {
268+
result = SupportMethod::genMethodFor(this.getInputType(), input) or
269+
result = SupportMethod::genMethodForContent(input.tail().drop(_)) or
270+
result = SupportMethod::getMethodFor(this.getOutputType(), output) or
271+
result = SupportMethod::getMethodForContent(output.tail().drop(_))
294272
}
295273

296274
/**

java/ql/src/utils/FlowTestCaseSupportMethods.qll

Lines changed: 302 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ private string getSyntheticFieldToken(SyntheticFieldContent fc) {
3232
/**
3333
* Returns a token suitable for incorporation into a Java method name describing content `c`.
3434
*/
35-
string contentToken(Content c) {
35+
private string contentToken(Content c) {
3636
c instanceof ArrayContent and result = "ArrayElement"
3737
or
3838
c instanceof CollectionContent and result = "Element"
@@ -49,4 +49,304 @@ string contentToken(Content c) {
4949
/**
5050
* Returns the `content` wrapped by `component`, if any.
5151
*/
52-
Content getContent(SummaryComponent component) { component = SummaryComponent::content(result) }
52+
private Content getContent(SummaryComponent component) {
53+
component = SummaryComponent::content(result)
54+
}
55+
56+
module SupportMethod {
57+
GenMethod genMethodForContent(SummaryComponentStack c) {
58+
result = min(GenMethod g | g.appliesTo(any(VoidType v), getContent(c.head())))
59+
}
60+
61+
GenMethod genMethodFor(Type t, SummaryComponentStack c) {
62+
result = min(GenMethod g | g.appliesTo(t, getContent(c.head())))
63+
}
64+
65+
GetMethod getMethodForContent(SummaryComponentStack c) {
66+
result = min(GetMethod g | g.appliesTo(any(VoidType v), getContent(c.head())))
67+
}
68+
69+
GetMethod getMethodFor(Type t, SummaryComponentStack c) {
70+
result = min(GetMethod g | g.appliesTo(t, getContent(c.head())))
71+
}
72+
}
73+
74+
bindingset[this]
75+
abstract class SupportMethod extends string {
76+
abstract predicate appliesTo(Type t, Content c);
77+
78+
string getARequiredImport() { none() }
79+
80+
string getDefinition() { none() }
81+
82+
bindingset[this, arg]
83+
abstract string getCall(string arg);
84+
85+
/**
86+
* Gets the CSV row describing this support method if it is needed to set up the output for this test.
87+
*
88+
* For example, `newWithMapValue` will propagate a value from `Argument[0]` to `MapValue of ReturnValue`, and `getMapValue`
89+
* will do the opposite.
90+
*/
91+
string getCsvModel() { none() }
92+
}
93+
94+
class SourceMethod extends SupportMethod {
95+
SourceMethod() { this = "source" }
96+
97+
override predicate appliesTo(Type t, Content c) { none() }
98+
99+
bindingset[arg]
100+
override string getCall(string arg) {
101+
result = "source()" and
102+
// suppress unused variable warning
103+
arg = arg
104+
}
105+
106+
override string getDefinition() { result = "Object source() { return null; }" }
107+
}
108+
109+
class SinkMethod extends SupportMethod {
110+
SinkMethod() { this = "sink" }
111+
112+
override predicate appliesTo(Type t, Content c) { none() }
113+
114+
bindingset[arg]
115+
override string getCall(string arg) { result = "sink(" + arg + ")" }
116+
117+
override string getDefinition() { result = "void sink(Object o) { }" }
118+
}
119+
120+
bindingset[this]
121+
abstract class GetMethod extends SupportMethod { }
122+
123+
private class DefaultGetMethod extends GetMethod {
124+
Content c;
125+
126+
// prefix with zzz so the default getter is always last
127+
DefaultGetMethod() { this = "zzzDefaultGet" + contentToken(c) }
128+
129+
string getName() { result = "get" + contentToken(c) }
130+
131+
override predicate appliesTo(Type t, Content c1) {
132+
c = c1 and
133+
// suppress unused variable warning
134+
t = t
135+
}
136+
137+
bindingset[arg]
138+
override string getCall(string arg) { result = this.getName() + "(" + arg + ")" }
139+
140+
override string getDefinition() {
141+
result = "Object get" + contentToken(c) + "(Object container) { return null; }"
142+
}
143+
144+
override string getCsvModel() {
145+
result =
146+
"generatedtest;Test;false;" + this.getName() + ";;;" +
147+
getComponentSpec(SummaryComponent::content(c)) + " of Argument[0];ReturnValue;value"
148+
}
149+
}
150+
151+
private class ListGetMethod extends GetMethod {
152+
ListGetMethod() { this = "listgetmethod" }
153+
154+
override predicate appliesTo(Type t, Content c) {
155+
t.(RefType).getASourceSupertype*().hasQualifiedName("java.lang", "Iterable") and
156+
c instanceof CollectionContent
157+
}
158+
159+
override string getDefinition() {
160+
result = "<T> T getElement(Iterable<T> it) { return it.iterator().next(); }"
161+
}
162+
163+
bindingset[arg]
164+
override string getCall(string arg) { result = "getElement(" + arg + ")" }
165+
}
166+
167+
private class IteratorGetMethod extends GetMethod {
168+
IteratorGetMethod() { this = "iteratorgetmethod" }
169+
170+
override predicate appliesTo(Type t, Content c) {
171+
t.(RefType).getASourceSupertype*().hasQualifiedName("java.util", "Iterator") and
172+
c instanceof CollectionContent
173+
}
174+
175+
override string getDefinition() {
176+
result = "<T> T getElement(Iterator<T> it) { return it.next(); }"
177+
}
178+
179+
bindingset[arg]
180+
override string getCall(string arg) { result = "getElement(" + arg + ")" }
181+
}
182+
183+
private class OptionalGetMethod extends GetMethod {
184+
OptionalGetMethod() { this = "optionalgetmethod" }
185+
186+
override predicate appliesTo(Type t, Content c) {
187+
t.(RefType).getSourceDeclaration().hasQualifiedName("java.util", "Optional") and
188+
c instanceof CollectionContent
189+
}
190+
191+
override string getDefinition() { result = "<T> T getElement(Optional<T> o) { return o.get(); }" }
192+
193+
bindingset[arg]
194+
override string getCall(string arg) { result = "getElement(" + arg + ")" }
195+
}
196+
197+
private class MapGetKeyMethod extends GetMethod {
198+
MapGetKeyMethod() { this = "mapgetkeymethod" }
199+
200+
override predicate appliesTo(Type t, Content c) {
201+
t.(RefType).getASourceSupertype*().hasQualifiedName("java.util", "Map") and
202+
c instanceof MapKeyContent
203+
}
204+
205+
override string getDefinition() {
206+
result = "<K> K getMapKey(Map<K,?> map) { return map.keySet().iterator().next(); }"
207+
}
208+
209+
bindingset[arg]
210+
override string getCall(string arg) { result = "getMapKey(" + arg + ")" }
211+
}
212+
213+
private class MapValueGetMethod extends GetMethod {
214+
MapValueGetMethod() { this = "MapValueGetMethod" }
215+
216+
override predicate appliesTo(Type t, Content c) {
217+
t.(RefType).getASourceSupertype*().hasQualifiedName("java.util", "Map") and
218+
c instanceof MapValueContent
219+
}
220+
221+
override string getDefinition() {
222+
result = "<V> V getMapValue(Map<?,V> map) { return map.get(null); }"
223+
}
224+
225+
bindingset[arg]
226+
override string getCall(string arg) { result = "getMapValue(" + arg + ")" }
227+
}
228+
229+
private class ArrayGetMethod extends GetMethod {
230+
ArrayGetMethod() { this = "arraygetmethod" }
231+
232+
override predicate appliesTo(Type t, Content c) {
233+
t instanceof Array and
234+
c instanceof ArrayContent
235+
}
236+
237+
override string getDefinition() {
238+
result = "<T> T getArrayElement(T[] array) { return array[0]; }"
239+
}
240+
241+
bindingset[arg]
242+
override string getCall(string arg) { result = "getArrayElement(" + arg + ")" }
243+
}
244+
245+
bindingset[this]
246+
abstract class GenMethod extends SupportMethod { }
247+
248+
private class DefaultGenMethod extends GenMethod {
249+
Content c;
250+
251+
// prefix with zzz so the default generator is always last
252+
DefaultGenMethod() { this = "zzzDefaultGen" + contentToken(c) }
253+
254+
string getName() { result = "newWith" + contentToken(c) }
255+
256+
override predicate appliesTo(Type t, Content c1) {
257+
c = c1 and
258+
// suppress unused variable warning
259+
t = t
260+
}
261+
262+
bindingset[arg]
263+
override string getCall(string arg) { result = this.getName() + "(" + arg + ")" }
264+
265+
override string getDefinition() {
266+
result = "Object newWith" + contentToken(c) + "(Object element) { return null; }"
267+
}
268+
269+
override string getCsvModel() {
270+
result =
271+
"generatedtest;Test;false;" + this.getName() + ";;;Argument[0];" +
272+
getComponentSpec(SummaryComponent::content(c)) + " of ReturnValue;value"
273+
}
274+
}
275+
276+
private class ListGenMethod extends GenMethod {
277+
ListGenMethod() { this = "listgenmethod" }
278+
279+
override predicate appliesTo(Type t, Content c) {
280+
exists(GenericType list | list.hasQualifiedName("java.util", "List") |
281+
t = list or list.getAParameterizedType().getASupertype*() = t
282+
) and
283+
c instanceof CollectionContent
284+
}
285+
286+
bindingset[arg]
287+
override string getCall(string arg) { result = "List.of(" + arg + ")" }
288+
}
289+
290+
private class OptionalGenMethod extends GenMethod {
291+
OptionalGenMethod() { this = "optionalgenmethod" }
292+
293+
override predicate appliesTo(Type t, Content c) {
294+
exists(GenericType list | list.hasQualifiedName("java.util", "List") |
295+
list.getAParameterizedType().getASupertype*() = t
296+
) and
297+
c instanceof CollectionContent
298+
}
299+
300+
bindingset[arg]
301+
override string getCall(string arg) { result = "Optional.of(" + arg + ")" }
302+
}
303+
304+
private class MapGenKeyMethod extends GenMethod {
305+
MapGenKeyMethod() { this = "mapkeygenmethod" }
306+
307+
override predicate appliesTo(Type t, Content c) {
308+
exists(GenericType map | map.hasQualifiedName("java.util", "Map") |
309+
map.getAParameterizedType().getASupertype*() = t
310+
) and
311+
c instanceof MapKeyContent
312+
}
313+
314+
bindingset[arg]
315+
override string getCall(string arg) { result = "Map.of(" + arg + ", null)" }
316+
}
317+
318+
private class MapGenValueMethod extends GenMethod {
319+
MapGenValueMethod() { this = "mapvaluegenmethod" }
320+
321+
override predicate appliesTo(Type t, Content c) {
322+
exists(GenericType map | map.hasQualifiedName("java.util", "Map") |
323+
map.getAParameterizedType().getASupertype*() = t
324+
) and
325+
c instanceof MapValueContent
326+
}
327+
328+
bindingset[arg]
329+
override string getCall(string arg) { result = "Map.of(null, " + arg + ")" }
330+
}
331+
332+
string getConvertExprIfNotObject(RefType t) {
333+
if t.hasQualifiedName("java.lang", "Object")
334+
then result = ""
335+
else result = "(" + getShortNameIfPossible(t) + ")"
336+
}
337+
338+
private class ArrayGenMethod extends GenMethod {
339+
Array type;
340+
341+
ArrayGenMethod() { this = type.getName() + "genmethod" }
342+
343+
override predicate appliesTo(Type t, Content c) {
344+
t = type and
345+
c instanceof ArrayContent
346+
}
347+
348+
bindingset[arg]
349+
override string getCall(string arg) {
350+
result = "{" + getConvertExprIfNotObject(type.getElementType()) + arg + "}"
351+
}
352+
}

java/ql/src/utils/GenerateFlowTestCase.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def qualifiedOuterNameFromCsvRow(row):
127127
os.makedirs(queryDir)
128128
qlFile = os.path.join(queryDir, "gen.ql")
129129
with open(os.path.join(queryDir, "qlpack.yml"), "w") as f:
130-
f.write("name: test-generation-query\nversion: 0.0.0\nlibraryPathDependencies: codeql/java-queries")
130+
f.write("name: test-generation-query\nversion: 0.0.0\nlibraryPathDependencies: codeql-java")
131131
with open(qlFile, "w") as f:
132132
f.write(
133133
"import java\nimport utils.GenerateFlowTestCase\n\nclass GenRow extends TargetSummaryModelCsv {\n\n\toverride predicate row(string r) {\n\t\tr = [\n")

0 commit comments

Comments
 (0)