Merge branch 'main' into thirdpartyapitelemtry

This commit is contained in:
Benjamin Muskalla
2021-09-09 11:11:42 +02:00
1260 changed files with 38662 additions and 12339 deletions

View File

@@ -37,8 +37,8 @@ where
// Remove local classes defined in the dead method - they are reported separately as a dead
// class. We keep anonymous class counts, because anonymous classes are not reported
// separately.
sum(LocalClass localClass |
localClass.getLocalClassDeclStmt().getEnclosingCallable() = deadMethod
sum(LocalClassOrInterface localClass |
localClass.getLocalTypeDeclStmt().getEnclosingCallable() = deadMethod
|
localClass.getNumberOfLinesOfCode()
)

View File

@@ -122,8 +122,8 @@ where
not abortsControlFlow(s) and
// Exclude the double semicolon case `if (cond) s;;`.
not t instanceof EmptyStmt and
// `LocalClassDeclStmt`s yield false positives since their `Location` doesn't include the `class` keyword.
not t instanceof LocalClassDeclStmt
// `LocalTypeDeclStmt`s yield false positives since their `Location` doesn't include the `class` keyword.
not t instanceof LocalTypeDeclStmt
select s,
"Indentation suggests that $@ belongs to $@, but this is not the case; consider adding braces or adjusting indentation.",
t, "the next statement", c, "the control structure"

View File

@@ -5,7 +5,7 @@
* @kind problem
* @problem.severity error
* @security-severity 8.8
* @precision high
* @precision medium
* @id java/concatenated-sql-query
* @tags security
* external/cwe/cwe-089

View File

@@ -0,0 +1,4 @@
byte[] iv = new byte[16]; // all zeroes
GCMParameterSpec params = new GCMParameterSpec(128, iv);
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key, params);

View File

@@ -0,0 +1,6 @@
byte[] iv = new byte[16];
SecureRandom random = SecureRandom.getInstanceStrong();
random.nextBytes(iv);
GCMParameterSpec params = new GCMParameterSpec(128, iv);
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key, params);

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
A cipher needs an initialization vector (IV) when it is used in certain modes
such as CBC or GCM. Under the same secret key, IVs should be unique and ideally unpredictable.
Given a secret key, if the same IV is used for encryption, the same plaintexts result in the same ciphertexts.
This lets an attacker learn if the same data pieces are transferred or stored,
or this can help the attacker run a dictionary attack.
</p>
</overview>
<recommendation>
<p>
Use a random IV generated by <code>SecureRandom</code>.
</p>
</recommendation>
<example>
<p>
The following example initializes a cipher with a static IV which is unsafe:
</p>
<sample src="BadStaticInitializationVector.java" />
<p>
The next example initializes a cipher with a random IV:
</p>
<sample src="GoodRandomInitializationVector.java" />
</example>
<references>
<li>
Wikipedia:
<a href="https://en.wikipedia.org/wiki/Initialization_vector">Initialization vector</a>.
</li>
<li>
National Institute of Standards and Technology:
<a href="https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf">Recommendation for Block Cipher Modes of Operation</a>.
</li>
<li>
National Institute of Standards and Technology:
<a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf">FIPS 140-2: Security Requirements for Cryptographic Modules</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,26 @@
/**
* @name Using a static initialization vector for encryption
* @description A cipher needs an initialization vector (IV) in some cases,
* for example, when CBC or GCM modes are used. IVs are used to randomize the encryption,
* therefore they should be unique and ideally unpredictable.
* Otherwise, the same plaintexts result in same ciphertexts under a given secret key.
* If a static IV is used for encryption, this lets an attacker learn
* if the same data pieces are transferred or stored,
* or this can help the attacker run a dictionary attack.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id java/static-initialization-vector
* @tags security
* external/cwe/cwe-329
* external/cwe/cwe-1204
*/
import java
import experimental.semmle.code.java.security.StaticInitializationVectorQuery
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink, StaticInitializationVectorConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "A $@ should not be used for encryption.", source.getNode(),
"static initialization vector"

View File

@@ -0,0 +1,165 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.TaintTracking2
/**
* Holds if `array` is initialized only with constants.
*/
private predicate initializedWithConstants(ArrayCreationExpr array) {
// creating an array without an initializer, for example `new byte[8]`
not exists(array.getInit())
or
// creating a multidimensional array with an initializer like `{ new byte[8], new byte[16] }`
// This works around https://github.com/github/codeql/issues/6552 -- change me once there is
// a better way to distinguish nested initializers that create zero-filled arrays
// (e.g. `new byte[1]`) from those with an initializer list (`new byte[] { 1 }` or just `{ 1 }`)
array.getInit().getAnInit().getAChildExpr() instanceof IntegerLiteral
or
// creating an array wit an initializer like `new byte[] { 1, 2 }`
forex(Expr element | element = array.getInit().getAnInit() |
element instanceof CompileTimeConstantExpr
)
}
/**
* An expression that creates a byte array that is initialized with constants.
*/
private class StaticByteArrayCreation extends ArrayCreationExpr {
StaticByteArrayCreation() {
this.getType().(Array).getElementType().(PrimitiveType).getName() = "byte" and
initializedWithConstants(this)
}
}
/** An expression that updates `array`. */
private class ArrayUpdate extends Expr {
Expr array;
ArrayUpdate() {
exists(Assignment assign |
assign = this and
assign.getDest().(ArrayAccess).getArray() = array and
not assign.getSource() instanceof CompileTimeConstantExpr
)
or
exists(StaticMethodAccess ma |
ma.getMethod().hasQualifiedName("java.lang", "System", "arraycopy") and
ma = this and
ma.getArgument(2) = array
)
or
exists(MethodAccess ma, Method m |
m = ma.getMethod() and
ma = this and
ma.getArgument(0) = array
|
m.hasQualifiedName("java.io", "InputStream", "read") or
m.hasQualifiedName("java.nio", "ByteBuffer", "get") or
m.hasQualifiedName("java.security", "SecureRandom", "nextBytes") or
m.hasQualifiedName("java.util", "Random", "nextBytes")
)
}
/** Returns the updated array. */
Expr getArray() { result = array }
}
/**
* A config that tracks dataflow from creating an array to an operation that updates it.
*/
private class ArrayUpdateConfig extends TaintTracking2::Configuration {
ArrayUpdateConfig() { this = "ArrayUpdateConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof StaticByteArrayCreation
}
override predicate isSink(DataFlow::Node sink) {
exists(ArrayUpdate update | update.getArray() = sink.asExpr())
}
}
/**
* A source that defines an array that doesn't get updated.
*/
private class StaticInitializationVectorSource extends DataFlow::Node {
StaticInitializationVectorSource() {
exists(StaticByteArrayCreation array | array = this.asExpr() |
not exists(ArrayUpdateConfig config | config.hasFlow(DataFlow2::exprNode(array), _))
)
}
}
/**
* A config that tracks initialization of a cipher for encryption.
*/
private class EncryptionModeConfig extends TaintTracking2::Configuration {
EncryptionModeConfig() { this = "EncryptionModeConfig" }
override predicate isSource(DataFlow::Node source) {
source
.asExpr()
.(FieldRead)
.getField()
.hasQualifiedName("javax.crypto", "Cipher", "ENCRYPT_MODE")
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
ma.getArgument(0) = sink.asExpr()
)
}
}
/**
* A sink that initializes a cipher for encryption with unsafe parameters.
*/
private class EncryptionInitializationSink extends DataFlow::Node {
EncryptionInitializationSink() {
exists(MethodAccess ma, Method m, EncryptionModeConfig config | m = ma.getMethod() |
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
m.getParameterType(2)
.(RefType)
.hasQualifiedName("java.security.spec", "AlgorithmParameterSpec") and
ma.getArgument(2) = this.asExpr() and
config.hasFlowToExpr(ma.getArgument(0))
)
}
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step
* that creates cipher's parameters with initialization vector.
*/
private predicate createInitializationVectorSpecStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(ConstructorCall cc, RefType type |
cc = toNode.asExpr() and type = cc.getConstructedType()
|
type.hasQualifiedName("javax.crypto.spec", "IvParameterSpec") and
cc.getArgument(0) = fromNode.asExpr()
or
type.hasQualifiedName("javax.crypto.spec", ["GCMParameterSpec", "RC2ParameterSpec"]) and
cc.getArgument(1) = fromNode.asExpr()
or
type.hasQualifiedName("javax.crypto.spec", "RC5ParameterSpec") and
cc.getArgument(3) = fromNode.asExpr()
)
}
/**
* A config that tracks dataflow to initializing a cipher with a static initialization vector.
*/
class StaticInitializationVectorConfig extends TaintTracking::Configuration {
StaticInitializationVectorConfig() { this = "StaticInitializationVectorConfig" }
override predicate isSource(DataFlow::Node source) {
source instanceof StaticInitializationVectorSource
}
override predicate isSink(DataFlow::Node sink) { sink instanceof EncryptionInitializationSink }
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
createInitializationVectorSpecStep(fromNode, toNode)
}
}

View File

@@ -1,39 +0,0 @@
import java
class ExternalData extends @externalDataElement {
string getDataPath() { externalData(this, result, _, _) }
string getQueryPath() { result = getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") }
int getNumFields() { result = 1 + max(int i | externalData(this, _, i, _) | i) }
string getField(int index) { externalData(this, _, index, result) }
int getFieldAsInt(int index) { result = getField(index).toInt() }
float getFieldAsFloat(int index) { result = getField(index).toFloat() }
date getFieldAsDate(int index) { result = getField(index).toDate() }
string toString() { result = getQueryPath() + ": " + buildTupleString(0) }
private string buildTupleString(int start) {
start = getNumFields() - 1 and result = getField(start)
or
start < getNumFields() - 1 and result = getField(start) + "," + buildTupleString(start + 1)
}
}
/**
* External data with a location, and a message, as produced by tools that used to produce QLDs.
*/
class DefectExternalData extends ExternalData {
DefectExternalData() {
this.getField(0).regexpMatch("\\w+://.*:[0-9]+:[0-9]+:[0-9]+:[0-9]+$") and
this.getNumFields() = 2
}
string getURL() { result = getField(0) }
string getMessage() { result = getField(1) }
}

View File

@@ -3,5 +3,5 @@ version: 0.0.2
suites: codeql-suites
extractor: java
dependencies:
codeql/java-all: ^0.0.2
codeql/suite-helpers: ^0.0.2
codeql/java-all: "*"
codeql/suite-helpers: "*"

View File

@@ -0,0 +1,309 @@
/**
* Classes pertaining to test cases themselves.
*/
import java
private import semmle.code.java.dataflow.internal.DataFlowUtil
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dataflow.internal.FlowSummaryImpl
private import FlowTestCaseUtils
private import FlowTestCaseSupportMethods
/**
* A CSV row to generate tests for. Users should extend this to define which
* tests to generate. Rows specified here should also satisfy `SummaryModelCsv.row`.
*/
class TargetSummaryModelCsv extends Unit {
/**
* Holds if a test should be generated for `row`.
*/
abstract predicate row(string r);
}
/**
* Gets a CSV row for which a test has been requested, but `SummaryModelCsv.row` does not hold of it.
*/
query string missingSummaryModelCsv() {
any(TargetSummaryModelCsv target).row(result) and
not any(SummaryModelCsv model).row(result)
}
/**
* Returns type of parameter `i` of `callable`, including the type of `this` for parameter -1.
*/
Type getParameterType(CallableToTest callable, int i) {
if i = -1 then result = callable.getDeclaringType() else result = callable.getParameterType(i)
}
private class CallableToTest extends Callable {
CallableToTest() {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _) and
this = interpretElement(namespace, type, subtypes, name, signature, ext) and
this.isPublic() and
getRootType(this.getDeclaringType()).(RefType).isPublic()
)
}
}
/**
* A test snippet (a fragment of Java code that checks that `row` causes `callable` to propagate value/taint (according to `preservesValue`)
* 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
* override or overload of a particular method, or if the input or output specifications cover more than one argument.
*/
private newtype TTestCase =
MkTestCase(
CallableToTest callable, SummaryComponentStack input, SummaryComponentStack output, string kind,
string row
) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string inputSpec, string outputSpec
|
any(TargetSummaryModelCsv tsmc).row(row) and
summaryModel(namespace, type, subtypes, name, signature, ext, inputSpec, outputSpec, kind, row) and
callable = interpretElement(namespace, type, subtypes, name, signature, ext) and
Private::External::interpretSpec(inputSpec, input) and
Private::External::interpretSpec(outputSpec, output)
)
}
/**
* A test snippet (as `TTestCase`, except `baseInput` and `baseOutput` hold the bottom of the summary stacks
* `input` and `output` respectively (hence, `baseInput` and `baseOutput` are parameters or return values).
*/
class TestCase extends TTestCase {
CallableToTest callable;
SummaryComponentStack input;
SummaryComponentStack output;
SummaryComponentStack baseInput;
SummaryComponentStack baseOutput;
string kind;
string row;
TestCase() {
this = MkTestCase(callable, input, output, kind, row) and
baseInput = input.drop(input.length() - 1) and
baseOutput = output.drop(output.length() - 1)
}
/**
* Returns a representation of this test case's parameters suitable for debugging.
*/
string toString() {
result =
row + " / " + callable + " / " + input + " / " + output + " / " + baseInput + " / " +
baseOutput + " / " + kind
}
/**
* Returns a value to pass as `callable`'s `argIdx`th argument whose value is irrelevant to the test
* being generated. This will be a zero or a null value, perhaps typecast if we need to disambiguate overloads.
*/
string getFiller(int argIdx) {
exists(Type t | t = callable.getParameterType(argIdx) |
t instanceof RefType and
(
if mayBeAmbiguous(callable)
then result = "(" + getShortNameIfPossible(t) + ")null"
else result = "null"
)
or
result = getZero(t)
)
}
/**
* Returns the value to pass for `callable`'s `i`th argument, which may be `in` if this is the input argument for
* this test, `out` if it is the output, `instance` if this is an instance method and the instance is neither the
* input nor the output, or a zero/null filler value otherwise.
*/
string getArgument(int i) {
(i = -1 or exists(callable.getParameter(i))) and
if baseInput = SummaryComponentStack::argument(i)
then result = "in"
else
if baseOutput = SummaryComponentStack::argument(i)
then result = "out"
else
if i = -1
then result = "instance"
else result = this.getFiller(i)
}
/**
* Returns a statement invoking `callable`, passing `input` and capturing `output` as needed.
*/
string makeCall() {
// For example, one of:
// out = in.method(filler);
// or
// out = filler.method(filler, in, filler);
// or
// out = Type.method(filler, in, filler);
// or
// filler.method(filler, in, out, filler);
// or
// Type.method(filler, in, out, filler);
// or
// out = new Type(filler, in, filler);
// or
// new Type(filler, in, out, filler);
// or
// in.method(filler, out, filler);
// or
// out.method(filler, in, filler);
exists(string storePrefix, string invokePrefix, string args |
(
if
baseOutput = SummaryComponentStack::return()
or
callable instanceof Constructor and baseOutput = SummaryComponentStack::argument(-1)
then storePrefix = "out = "
else storePrefix = ""
) and
(
if callable instanceof Constructor
then invokePrefix = "new "
else
if callable.(Method).isStatic()
then invokePrefix = getShortNameIfPossible(callable.getDeclaringType()) + "."
else invokePrefix = this.getArgument(-1) + "."
) and
args = concat(int i | i >= 0 | this.getArgument(i), ", " order by i) and
result = storePrefix + invokePrefix + callable.getName() + "(" + args + ")"
)
}
/**
* Returns an inline test expectation appropriate to this CSV row.
*/
string getExpectation() {
kind = "value" and result = "// $ hasValueFlow"
or
kind = "taint" and result = "// $ hasTaintFlow"
}
/**
* Returns a declaration and initialisation of a variable named `instance` if required; otherwise returns an empty string.
*/
string getInstancePrefix() {
if
callable instanceof Method and
not callable.(Method).isStatic() and
baseOutput != SummaryComponentStack::argument(-1) and
baseInput != SummaryComponentStack::argument(-1)
then
// In this case `out` is the instance.
result = getShortNameIfPossible(callable.getDeclaringType()) + " instance = null;\n\t\t\t"
else result = ""
}
/**
* Returns the type of the output for this test.
*/
Type getOutputType() {
if baseOutput = SummaryComponentStack::return()
then result = callable.getReturnType()
else
exists(int i |
baseOutput = SummaryComponentStack::argument(i) and
result = getParameterType(callable, i)
)
}
/**
* Returns the type of the input for this test.
*/
Type getInputType() {
exists(int i |
baseInput = SummaryComponentStack::argument(i) and
result = getParameterType(callable, i)
)
}
/**
* Returns the Java name for the type of the input to this test.
*/
string getInputTypeString() { result = getShortNameIfPossible(this.getInputType()) }
/**
* Returns a call to `source()` wrapped in `newWith` methods as needed according to `input`.
* For example, if the input specification is `ArrayElement of MapValue of Argument[0]`, this
* will return `newWithMapValue(newWithArrayElement(source()))`.
*/
string getInput(SummaryComponentStack stack) {
stack = input and result = "source()"
or
exists(SummaryComponentStack s | s.tail() = stack |
// we currently only know the type if the stack is one level in
if stack = baseInput
then result = SupportMethod::genMethodFor(this.getInputType(), s).getCall(this.getInput(s))
else result = SupportMethod::genMethodForContent(s).getCall(this.getInput(s))
)
}
/**
* Returns `out` wrapped in `get` methods as needed according to `output`.
* For example, if the output specification is `ArrayElement of MapValue of Argument[0]`, this
* will return `getArrayElement(getMapValue(out))`.
*/
string getOutput(SummaryComponentStack componentStack) {
componentStack = output.drop(_) and
(
if componentStack = baseOutput
then result = "out"
else
result =
SupportMethod::getMethodForContent(componentStack)
.getCall(this.getOutput(componentStack.tail()))
)
}
/**
* 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.
*/
SupportMethod getASupportMethod() {
exists(SummaryComponentStack s | s = input.drop(_) and s.tail() != baseInput |
result = SupportMethod::genMethodForContent(s)
)
or
exists(SummaryComponentStack s | s = input.drop(_) and s.tail() = baseInput |
result = SupportMethod::genMethodFor(this.getInputType(), s)
)
or
result = SupportMethod::getMethodFor(this.getOutputType(), output)
or
result = SupportMethod::getMethodForContent(output.tail().drop(_))
}
/**
* Gets an outer class name that this test would ideally import (and will, unless it clashes with another
* type of the same name).
*/
Type getADesiredImport() {
result =
getRootSourceDeclaration([
this.getOutputType(), this.getInputType(), callable.getDeclaringType()
])
or
// Will refer to parameter types in disambiguating casts, like `(String)null`
mayBeAmbiguous(callable) and result = getRootSourceDeclaration(callable.getAParamType())
}
/**
* Gets a test snippet (test body fragment) testing this `callable` propagates value or taint from
* `input` to `output`, as specified by `row_` (which necessarily equals `row`).
*/
string getATestSnippetForRow(string row_) {
row_ = row and
result =
"\t\t{\n\t\t\t// \"" + row + "\"\n\t\t\t" + getShortNameIfPossible(this.getOutputType()) +
" out = null;\n\t\t\t" + this.getInputTypeString() + " in = (" + this.getInputTypeString() +
")" + this.getInput(baseInput) + ";\n\t\t\t" + this.getInstancePrefix() + this.makeCall() +
";\n\t\t\t" + "sink(" + this.getOutput(output) + "); " + this.getExpectation() + "\n\t\t}\n"
}
}

View File

@@ -0,0 +1,393 @@
/**
* Contains predicates and classes relating to support methods for tests, such as the `source()` and `sink()`.
*/
import java
private import semmle.code.java.dataflow.internal.DataFlowUtil
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dataflow.internal.FlowSummaryImpl
private import FlowTestCaseUtils
private import FlowTestCase
/**
* Returns a valid Java token naming the field `fc`.
*/
private string getFieldToken(FieldContent fc) {
result =
fc.getField().getDeclaringType().getSourceDeclaration().getName() + "_" +
fc.getField().getName()
}
/**
* Returns a valid Java token naming the synthetic field `fc`,
* assuming that the name of that field consists only of characters valid in a Java identifier and `.`.
*/
private string getSyntheticFieldToken(SyntheticFieldContent fc) {
exists(string name, int parts |
name = fc.getField() and
parts = count(name.splitAt("."))
|
if parts = 1
then result = name
else result = name.splitAt(".", parts - 2) + "_" + name.splitAt(".", parts - 1)
)
}
/**
* Returns a token suitable for incorporation into a Java method name describing content `c`.
*/
private string contentToken(Content c) {
c instanceof ArrayContent and result = "ArrayElement"
or
c instanceof CollectionContent and result = "Element"
or
c instanceof MapKeyContent and result = "MapKey"
or
c instanceof MapValueContent and result = "MapValue"
or
result = getFieldToken(c)
or
result = getSyntheticFieldToken(c)
}
/**
* Returns the `content` wrapped by `component`, if any.
*/
private Content getContent(SummaryComponent component) {
component = SummaryComponent::content(result)
}
/** Contains utility predicates for getting relevant support methods. */
module SupportMethod {
/** Gets a generator method for the content type of the head of the component stack `c`. */
GenMethod genMethodForContent(SummaryComponentStack c) {
result = genMethodFor(any(VoidType v), c)
}
/** Gets a generator method for the type `t` and the content type of the head of the component stack `c`. */
GenMethod genMethodFor(Type t, SummaryComponentStack c) {
result = min(GenMethod g | g.appliesTo(t, getContent(c.head())) | g order by g.getPriority(), g)
}
/** Gets a getter method for the content type of the head of the component stack `c`. */
GetMethod getMethodForContent(SummaryComponentStack c) {
result = getMethodFor(any(VoidType v), c)
}
/** Gets a getter method for the type `t` and the content type of the head of the component stack `c`. */
GetMethod getMethodFor(Type t, SummaryComponentStack c) {
result = min(GetMethod g | g.appliesTo(t, getContent(c.head())) | g order by g.getPriority(), g)
}
}
/**
* A support method for tests, such as `source()` or `sink()`.
*/
bindingset[this]
abstract class SupportMethod extends string {
/** Gets an import that is required for this support method. */
string getARequiredImport() { none() }
/** Gets the Java definition of this support method, if one is necessary. */
string getDefinition() { none() }
/** Gets the priority of this support method. Lower priorities are preferred when multiple support methods apply. */
bindingset[this]
int getPriority() { result = 50 }
/**
* Gets the CSV row describing this support method if it is needed to set up the output for this test.
*
* For example, `newWithMapValue` will propagate a value from `Argument[0]` to `MapValue of ReturnValue`, and `getMapValue`
* will do the opposite.
*/
string getCsvModel() { none() }
}
/**
* The method `source()` which is considered as the source for the flow test.
*/
class SourceMethod extends SupportMethod {
SourceMethod() { this = "source" }
override string getDefinition() { result = "Object source() { return null; }" }
}
/**
* The method `sink()` which is considered as the sink for the flow test.
*/
class SinkMethod extends SupportMethod {
SinkMethod() { this = "sink" }
override string getDefinition() { result = "void sink(Object o) { }" }
}
/**
* A method for getting content from a type.
*/
bindingset[this]
abstract class GetMethod extends SupportMethod {
/**
* Holds if this get method can be used to get the content `c` from the type `t`.
*/
abstract predicate appliesTo(Type t, Content c);
/**
* Gets the call to get the content from the argument `arg`.
*/
bindingset[this, arg]
abstract string getCall(string arg);
}
private class DefaultGetMethod extends GetMethod {
Content c;
DefaultGetMethod() { this = "DefaultGet" + contentToken(c) }
string getName() { result = "get" + contentToken(c) }
override int getPriority() { result = 999 }
override predicate appliesTo(Type t, Content c1) {
c = c1 and
// suppress unused variable warning
t = [any(TestCase tc).getOutputType(), any(VoidType v)]
}
bindingset[arg]
override string getCall(string arg) { result = this.getName() + "(" + arg + ")" }
override string getDefinition() {
result = "Object get" + contentToken(c) + "(Object container) { return null; }"
}
override string getCsvModel() {
result =
"generatedtest;Test;false;" + this.getName() + ";;;" +
getComponentSpec(SummaryComponent::content(c)) + " of Argument[0];ReturnValue;value"
}
}
private class ListGetMethod extends GetMethod {
ListGetMethod() { this = "listgetmethod" }
override predicate appliesTo(Type t, Content c) {
t.(RefType).getASourceSupertype*().hasQualifiedName("java.lang", "Iterable") and
c instanceof CollectionContent
}
override string getDefinition() {
result = "<T> T getElement(Iterable<T> it) { return it.iterator().next(); }"
}
bindingset[arg]
override string getCall(string arg) { result = "getElement(" + arg + ")" }
}
private class IteratorGetMethod extends GetMethod {
IteratorGetMethod() { this = "iteratorgetmethod" }
override predicate appliesTo(Type t, Content c) {
t.(RefType).getASourceSupertype*().hasQualifiedName("java.util", "Iterator") and
c instanceof CollectionContent
}
override string getDefinition() {
result = "<T> T getElement(Iterator<T> it) { return it.next(); }"
}
bindingset[arg]
override string getCall(string arg) { result = "getElement(" + arg + ")" }
}
private class OptionalGetMethod extends GetMethod {
OptionalGetMethod() { this = "optionalgetmethod" }
override predicate appliesTo(Type t, Content c) {
t.(RefType).getSourceDeclaration().hasQualifiedName("java.util", "Optional") and
c instanceof CollectionContent
}
override string getDefinition() { result = "<T> T getElement(Optional<T> o) { return o.get(); }" }
bindingset[arg]
override string getCall(string arg) { result = "getElement(" + arg + ")" }
}
private class MapGetKeyMethod extends GetMethod {
MapGetKeyMethod() { this = "mapgetkeymethod" }
override predicate appliesTo(Type t, Content c) {
t.(RefType).getASourceSupertype*().hasQualifiedName("java.util", "Map") and
c instanceof MapKeyContent
}
override string getDefinition() {
result = "<K> K getMapKey(Map<K,?> map) { return map.keySet().iterator().next(); }"
}
bindingset[arg]
override string getCall(string arg) { result = "getMapKey(" + arg + ")" }
}
private class MapValueGetMethod extends GetMethod {
MapValueGetMethod() { this = "MapValueGetMethod" }
override predicate appliesTo(Type t, Content c) {
t.(RefType).getASourceSupertype*().hasQualifiedName("java.util", "Map") and
c instanceof MapValueContent
}
override string getDefinition() {
result = "<V> V getMapValue(Map<?,V> map) { return map.get(null); }"
}
bindingset[arg]
override string getCall(string arg) { result = "getMapValue(" + arg + ")" }
}
private class ArrayGetMethod extends GetMethod {
ArrayGetMethod() { this = "arraygetmethod" }
override predicate appliesTo(Type t, Content c) {
t instanceof Array and
c instanceof ArrayContent
}
override string getDefinition() {
result = "<T> T getArrayElement(T[] array) { return array[0]; }"
}
bindingset[arg]
override string getCall(string arg) { result = "getArrayElement(" + arg + ")" }
}
/**
* A method for generating a type with content.
*/
bindingset[this]
abstract class GenMethod extends SupportMethod {
/**
* Holds if this generator method can be used to generate a new `t` that contains content `c`.
*/
abstract predicate appliesTo(Type t, Content c);
/**
* Gets the call to generate an object with content `arg`.
*/
bindingset[this, arg]
abstract string getCall(string arg);
}
private class DefaultGenMethod extends GenMethod {
Content c;
DefaultGenMethod() { this = "DefaultGen" + contentToken(c) }
string getName() { result = "newWith" + contentToken(c) }
override int getPriority() { result = 999 }
override predicate appliesTo(Type t, Content c1) {
c = c1 and
// suppress unused variable warning
t = [any(TestCase tc).getInputType(), any(VoidType v)]
}
bindingset[arg]
override string getCall(string arg) { result = this.getName() + "(" + arg + ")" }
override string getDefinition() {
result = "Object newWith" + contentToken(c) + "(Object element) { return null; }"
}
override string getCsvModel() {
result =
"generatedtest;Test;false;" + this.getName() + ";;;Argument[0];" +
getComponentSpec(SummaryComponent::content(c)) + " of ReturnValue;value"
}
}
private class ListGenMethod extends GenMethod {
ListGenMethod() { this = "listgenmethod" }
override predicate appliesTo(Type t, Content c) {
exists(GenericType list | list.hasQualifiedName("java.util", "List") |
t = list or list.getAParameterizedType().getASupertype*() = t
) and
c instanceof CollectionContent
}
bindingset[arg]
override string getCall(string arg) { result = "List.of(" + arg + ")" }
}
private class OptionalGenMethod extends GenMethod {
OptionalGenMethod() { this = "optionalgenmethod" }
override predicate appliesTo(Type t, Content c) {
exists(GenericType list | list.hasQualifiedName("java.util", "List") |
list.getAParameterizedType().getASupertype*() = t
) and
c instanceof CollectionContent
}
bindingset[arg]
override string getCall(string arg) { result = "Optional.of(" + arg + ")" }
}
private class MapGenKeyMethod extends GenMethod {
MapGenKeyMethod() { this = "mapkeygenmethod" }
override predicate appliesTo(Type t, Content c) {
exists(GenericType map | map.hasQualifiedName("java.util", "Map") |
map.getAParameterizedType().getASupertype*() = t
) and
c instanceof MapKeyContent
}
bindingset[arg]
override string getCall(string arg) { result = "Map.of(" + arg + ", null)" }
}
private class MapGenValueMethod extends GenMethod {
MapGenValueMethod() { this = "mapvaluegenmethod" }
override predicate appliesTo(Type t, Content c) {
exists(GenericType map | map.hasQualifiedName("java.util", "Map") |
map.getAParameterizedType().getASupertype*() = t
) and
c instanceof MapValueContent
}
bindingset[arg]
override string getCall(string arg) { result = "Map.of(null, " + arg + ")" }
}
/**
* Returns a cast to type `t` if `t` is not `java.lang.Object`, or an empty string otherwise.
*/
string getConvertExprIfNotObject(RefType t) {
if t.hasQualifiedName("java.lang", "Object")
then result = ""
else result = "(" + getShortNameIfPossible(t) + ")"
}
private class ArrayGenMethod extends GenMethod {
Array type;
ArrayGenMethod() { this = type.getName() + "genmethod" }
override predicate appliesTo(Type t, Content c) {
replaceTypeVariable(t.(Array).getComponentType()) = type.getComponentType() and
c instanceof ArrayContent
}
bindingset[arg]
override string getCall(string arg) {
result =
"new " + getShortNameIfPossible(type) + "{" +
getConvertExprIfNotObject(type.getComponentType()) + arg + "}"
}
}

View File

@@ -0,0 +1,133 @@
/**
* Utility predicates useful for test generation.
*/
import java
private import semmle.code.java.dataflow.internal.DataFlowUtil
private import semmle.code.java.dataflow.FlowSummary
private import FlowTestCase
/**
* 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).
*/
Type getRootSourceDeclaration(Type t) {
if t instanceof RefType
then result = getRootType(replaceTypeVariable(t)).(RefType).getSourceDeclaration()
else result = t
}
/**
* Holds if type `t` does not clash with another type we want to import that has the same base name.
*/
predicate isImportable(Type t) {
t = any(TestCase tc).getADesiredImport() and
t =
unique(Type sharesBaseName |
sharesBaseName = any(TestCase tc).getADesiredImport() and
sharesBaseName.getName() = t.getName()
|
sharesBaseName
)
}
/**
* Returns `t`'s first upper bound if `t` is a type variable; otherwise returns `t`.
*/
RefType replaceTypeVariable(RefType t) {
if t instanceof TypeVariable
then result = replaceTypeVariable(t.(TypeVariable).getFirstUpperBoundType())
else result = t
}
/**
* Returns a zero value of primitive type `t`.
*/
string getZero(PrimitiveType t) {
t.hasName("float") and result = "0.0f"
or
t.hasName("double") and result = "0.0"
or
t.hasName("int") and result = "0"
or
t.hasName("boolean") and result = "false"
or
t.hasName("short") and result = "(short)0"
or
t.hasName("byte") and result = "(byte)0"
or
t.hasName("char") and result = "'\\0'"
or
t.hasName("long") and result = "0L"
}
/**
* Holds if `c` may require disambiguation from an overload with the same argument count.
*/
predicate mayBeAmbiguous(Callable c) {
exists(Callable other, string package, string type, string name |
c.hasQualifiedName(package, type, name) and
other.hasQualifiedName(package, type, name) and
other.getNumberOfParameters() = c.getNumberOfParameters() and
other != c
)
or
c.isVarargs()
}
/**
* Returns the outermost type enclosing type `t` (which may be `t` itself).
*/
Type getRootType(Type t) {
if t instanceof NestedType
then result = getRootType(t.(NestedType).getEnclosingType())
else
if t instanceof Array
then result = getRootType(t.(Array).getElementType())
else result = t
}
/**
* Returns a printable name for type `t`, stripped of generics and, if a type variable,
* replaced by its bound. Usually this is a short name, but it may be package-qualified
* if we cannot import it due to a name clash.
*/
string getShortNameIfPossible(Type t) {
if t instanceof Array
then result = getShortNameIfPossible(t.(Array).getComponentType()) + "[]"
else (
if t instanceof RefType
then
getRootSourceDeclaration(t) = any(TestCase tc).getADesiredImport() and
exists(RefType replaced, string nestedName |
replaced = replaceTypeVariable(t).getSourceDeclaration() and
nestedName = replaced.nestedName().replaceAll("$", ".")
|
if isImportable(getRootSourceDeclaration(t))
then result = nestedName
else result = replaced.getPackage().getName() + "." + nestedName
)
else result = t.getName()
)
}
/**
* Gets a string that specifies summary component `c` in a summary specification CSV row.
*/
string getComponentSpec(SummaryComponent c) {
exists(Content content |
c = SummaryComponent::content(content) and
(
content instanceof ArrayContent and result = "ArrayElement"
or
content instanceof MapValueContent and result = "MapValue"
or
content instanceof MapKeyContent and result = "MapKey"
or
content instanceof CollectionContent and result = "Element"
or
result = "Field[" + content.(FieldContent).getField().getQualifiedName() + "]"
or
result = "SyntheticField[" + content.(SyntheticFieldContent).getField() + "]"
)
)
}

View File

@@ -8,25 +8,9 @@ private import semmle.code.java.dataflow.internal.DataFlowUtil
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dataflow.internal.FlowSummaryImpl
/**
* A CSV row to generate tests for. Users should extend this to define which
* tests to generate. Rows specified here should also satisfy `SummaryModelCsv.row`.
*/
class TargetSummaryModelCsv extends Unit {
/**
* Holds if a test should be generated for `row`.
*/
abstract predicate row(string r);
}
/**
* Gets a CSV row for which a test has been requested, but `SummaryModelCsv.row` does not hold of it.
*/
query string missingSummaryModelCsv() {
any(TargetSummaryModelCsv target).row(result) and
not any(SummaryModelCsv model).row(result)
}
import FlowTestCase
private import FlowTestCaseSupportMethods
private import FlowTestCaseUtils
/**
* Gets a CSV row for which a test has been requested, and `SummaryModelCsv.row` does hold, but
@@ -73,477 +57,17 @@ query string noTestCaseGenerated() {
not exists(any(TestCase tc).getATestSnippetForRow(result))
}
private class CallableToTest extends Callable {
CallableToTest() {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext
|
summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _) and
this = interpretElement(namespace, type, subtypes, name, signature, ext) and
this.isPublic() and
getRootType(this.getDeclaringType()).isPublic()
)
}
}
/**
* Returns type of parameter `i` of `callable`, including the type of `this` for parameter -1.
* Gets a valid test case, i.e. one that has a test snippet.
*/
Type getParameterType(CallableToTest callable, int i) {
if i = -1 then result = callable.getDeclaringType() else result = callable.getParameterType(i)
}
/**
* Returns a zero value of primitive type `t`.
*/
string getZero(PrimitiveType t) {
t.hasName("float") and result = "0.0f"
or
t.hasName("double") and result = "0.0"
or
t.hasName("int") and result = "0"
or
t.hasName("boolean") and result = "false"
or
t.hasName("short") and result = "(short)0"
or
t.hasName("byte") and result = "(byte)0"
or
t.hasName("char") and result = "'a'"
or
t.hasName("long") and result = "0L"
}
/**
* Holds if `c` may require disambiguation from an overload with the same argument count.
*/
predicate mayBeAmbiguous(Callable c) {
exists(Callable other, string package, string type, string name |
c.hasQualifiedName(package, type, name) and
other.hasQualifiedName(package, type, name) and
other.getNumberOfParameters() = c.getNumberOfParameters() and
other != c
)
}
/**
* Returns the `content` wrapped by `component`, if any.
*/
Content getContent(SummaryComponent component) { component = SummaryComponent::content(result) }
/**
* Returns a valid Java token naming the field `fc`.
*/
string getFieldToken(FieldContent fc) {
result =
fc.getField().getDeclaringType().getSourceDeclaration().getName() + "_" +
fc.getField().getName()
}
/**
* Returns a valid Java token naming the synthetic field `fc`,
* assuming that the name of that field consists only of characters valid in a Java identifier and `.`.
*/
string getSyntheticFieldToken(SyntheticFieldContent fc) {
exists(string name, int parts |
name = fc.getField() and
parts = count(name.splitAt("."))
|
if parts = 1
then result = name
else result = name.splitAt(".", parts - 2) + "_" + name.splitAt(".", parts - 1)
)
}
/**
* Returns a token suitable for incorporation into a Java method name describing content `c`.
*/
string contentToken(Content c) {
c instanceof ArrayContent and result = "ArrayElement"
or
c instanceof CollectionContent and result = "Element"
or
c instanceof MapKeyContent and result = "MapKey"
or
c instanceof MapValueContent and result = "MapValue"
or
result = getFieldToken(c)
or
result = getSyntheticFieldToken(c)
}
/**
* Returns the outermost type enclosing type `t` (which may be `t` itself).
*/
RefType getRootType(RefType t) {
if t instanceof NestedType
then result = getRootType(t.(NestedType).getEnclosingType())
else
if t instanceof Array
then result = getRootType(t.(Array).getElementType())
else result = t
}
/**
* Returns `t`'s first upper bound if `t` is a type variable; otherwise returns `t`.
*/
RefType replaceTypeVariable(RefType t) {
if t instanceof TypeVariable
then result = replaceTypeVariable(t.(TypeVariable).getFirstUpperBoundType())
else result = t
}
/**
* 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).
*/
Type getRootSourceDeclaration(Type t) {
if t instanceof RefType
then result = getRootType(replaceTypeVariable(t)).getSourceDeclaration()
else result = t
}
/**
* A test snippet (a fragment of Java code that checks that `row` causes `callable` to propagate value/taint (according to `preservesValue`)
* 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
* override or overload of a particular method, or if the input or output specifications cover more than one argument.
*/
private newtype TTestCase =
MkTestCase(
CallableToTest callable, SummaryComponentStack input, SummaryComponentStack output, string kind,
string row
) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
string inputSpec, string outputSpec
|
any(TargetSummaryModelCsv tsmc).row(row) and
summaryModel(namespace, type, subtypes, name, signature, ext, inputSpec, outputSpec, kind, row) and
callable = interpretElement(namespace, type, subtypes, name, signature, ext) and
Private::External::interpretSpec(inputSpec, input) and
Private::External::interpretSpec(outputSpec, output)
)
}
/**
* A test snippet (as `TTestCase`, except `baseInput` and `baseOutput` hold the bottom of the summary stacks
* `input` and `output` respectively (hence, `baseInput` and `baseOutput` are parameters or return values).
*/
class TestCase extends TTestCase {
CallableToTest callable;
SummaryComponentStack input;
SummaryComponentStack output;
SummaryComponentStack baseInput;
SummaryComponentStack baseOutput;
string kind;
string row;
TestCase() {
this = MkTestCase(callable, input, output, kind, row) and
baseInput = input.drop(input.length() - 1) and
baseOutput = output.drop(output.length() - 1)
}
/**
* Returns a representation of this test case's parameters suitable for debugging.
*/
string toString() {
result =
row + " / " + callable + " / " + input + " / " + output + " / " + baseInput + " / " +
baseOutput + " / " + kind
}
/**
* Returns a value to pass as `callable`'s `argIdx`th argument whose value is irrelevant to the test
* being generated. This will be a zero or a null value, perhaps typecast if we need to disambiguate overloads.
*/
string getFiller(int argIdx) {
exists(Type t | t = callable.getParameterType(argIdx) |
t instanceof RefType and
(
if mayBeAmbiguous(callable)
then result = "(" + getShortNameIfPossible(t) + ")null"
else result = "null"
)
or
result = getZero(t)
)
}
/**
* Returns the value to pass for `callable`'s `i`th argument, which may be `in` if this is the input argument for
* this test, `out` if it is the output, `instance` if this is an instance method and the instance is neither the
* input nor the output, or a zero/null filler value otherwise.
*/
string getArgument(int i) {
(i = -1 or exists(callable.getParameter(i))) and
if baseInput = SummaryComponentStack::argument(i)
then result = "in"
else
if baseOutput = SummaryComponentStack::argument(i)
then result = "out"
else
if i = -1
then result = "instance"
else result = this.getFiller(i)
}
/**
* Returns a statement invoking `callable`, passing `input` and capturing `output` as needed.
*/
string makeCall() {
// For example, one of:
// out = in.method(filler);
// or
// out = filler.method(filler, in, filler);
// or
// out = Type.method(filler, in, filler);
// or
// filler.method(filler, in, out, filler);
// or
// Type.method(filler, in, out, filler);
// or
// out = new Type(filler, in, filler);
// or
// new Type(filler, in, out, filler);
// or
// in.method(filler, out, filler);
// or
// out.method(filler, in, filler);
exists(string storePrefix, string invokePrefix, string args |
(
if
baseOutput = SummaryComponentStack::return()
or
callable instanceof Constructor and baseOutput = SummaryComponentStack::argument(-1)
then storePrefix = "out = "
else storePrefix = ""
) and
(
if callable instanceof Constructor
then invokePrefix = "new "
else
if callable.(Method).isStatic()
then invokePrefix = getShortNameIfPossible(callable.getDeclaringType()) + "."
else invokePrefix = this.getArgument(-1) + "."
) and
args = concat(int i | i >= 0 | this.getArgument(i), ", " order by i) and
result = storePrefix + invokePrefix + callable.getName() + "(" + args + ")"
)
}
/**
* Returns an inline test expectation appropriate to this CSV row.
*/
string getExpectation() {
kind = "value" and result = "// $ hasValueFlow"
or
kind = "taint" and result = "// $ hasTaintFlow"
}
/**
* Returns a declaration and initialisation of a variable named `instance` if required; otherwise returns an empty string.
*/
string getInstancePrefix() {
if
callable instanceof Method and
not callable.(Method).isStatic() and
baseOutput != SummaryComponentStack::argument(-1) and
baseInput != SummaryComponentStack::argument(-1)
then
// In this case `out` is the instance.
result = getShortNameIfPossible(callable.getDeclaringType()) + " instance = null;\n\t\t\t"
else result = ""
}
/**
* Returns the type of the output for this test.
*/
Type getOutputType() {
if baseOutput = SummaryComponentStack::return()
then result = callable.getReturnType()
else
exists(int i |
baseOutput = SummaryComponentStack::argument(i) and
result = getParameterType(callable, i)
)
}
/**
* Returns the type of the input for this test.
*/
Type getInputType() {
exists(int i |
baseInput = SummaryComponentStack::argument(i) and
result = getParameterType(callable, i)
)
}
/**
* Returns the Java name for the type of the input to this test.
*/
string getInputTypeString() { result = getShortNameIfPossible(this.getInputType()) }
/**
* Returns a call to `source()` wrapped in `newWith` methods as needed according to `input`.
* For example, if the input specification is `ArrayElement of MapValue of Argument[0]`, this
* will return `newWithMapValue(newWithArrayElement(source()))`.
*/
string getInput(SummaryComponentStack stack) {
stack = input and result = "source()"
or
exists(SummaryComponentStack s |
result = "newWith" + contentToken(getContent(s.head())) + "(" + this.getInput(s) + ")" and
stack = s.tail()
)
}
/**
* Returns `out` wrapped in `get` methods as needed according to `output`.
* For example, if the output specification is `ArrayElement of MapValue of Argument[0]`, this
* will return `getArrayElement(getMapValue(out))`.
*/
string getOutput(SummaryComponentStack componentStack) {
componentStack = output.drop(_) and
(
if componentStack = baseOutput
then result = "out"
else
result =
"get" + contentToken(getContent(componentStack.head())) + "(" +
this.getOutput(componentStack.tail()) + ")"
)
}
/**
* 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.
*/
string getASupportMethod() {
result =
"Object newWith" + contentToken(getContent(input.drop(_).head())) +
"(Object element) { return null; }" or
result =
"Object get" + contentToken(getContent(output.drop(_).head())) +
"(Object container) { return null; }"
}
/**
* Gets a string that specifies summary component `c` in a summary specification CSV row.
*/
string getComponentSpec(SummaryComponent c) {
exists(Content content |
c = SummaryComponent::content(content) and
(
content instanceof ArrayContent and result = "ArrayElement"
or
content instanceof MapValueContent and result = "MapValue"
or
content instanceof MapKeyContent and result = "MapKey"
or
content instanceof CollectionContent and result = "Element"
or
result = "Field[" + content.(FieldContent).getField().getQualifiedName() + "]"
or
result = "SyntheticField[" + content.(SyntheticFieldContent).getField() + "]"
)
)
}
/**
* Returns a CSV row describing a support method (`newWith` or `get` method) needed to set up the output for this test.
*
* For example, `newWithMapValue` will propagate a value from `Argument[0]` to `MapValue of ReturnValue`, and `getMapValue`
* will do the opposite.
*/
string getASupportMethodModel() {
exists(SummaryComponent c, string contentCsvDescription |
c = input.drop(_).head() and contentCsvDescription = getComponentSpec(c)
|
result =
"generatedtest;Test;false;newWith" + contentToken(getContent(c)) + ";;;Argument[0];" +
contentCsvDescription + " of ReturnValue;value"
)
or
exists(SummaryComponent c, string contentCsvDescription |
c = output.drop(_).head() and contentCsvDescription = getComponentSpec(c)
|
result =
"generatedtest;Test;false;get" + contentToken(getContent(c)) + ";;;" + contentCsvDescription
+ " of Argument[0];ReturnValue;value"
)
}
/**
* Gets an outer class name that this test would ideally import (and will, unless it clashes with another
* type of the same name).
*/
Type getADesiredImport() {
result =
getRootSourceDeclaration([
this.getOutputType(), this.getInputType(), callable.getDeclaringType()
])
or
// Will refer to parameter types in disambiguating casts, like `(String)null`
mayBeAmbiguous(callable) and result = getRootSourceDeclaration(callable.getAParamType())
}
/**
* Gets a test snippet (test body fragment) testing this `callable` propagates value or taint from
* `input` to `output`, as specified by `row_` (which necessarily equals `row`).
*/
string getATestSnippetForRow(string row_) {
row_ = row and
result =
"\t\t{\n\t\t\t// \"" + row + "\"\n\t\t\t" + getShortNameIfPossible(this.getOutputType()) +
" out = null;\n\t\t\t" + this.getInputTypeString() + " in = (" + this.getInputTypeString() +
")" + this.getInput(baseInput) + ";\n\t\t\t" + this.getInstancePrefix() + this.makeCall() +
";\n\t\t\t" + "sink(" + this.getOutput(output) + "); " + this.getExpectation() + "\n\t\t}\n"
}
}
/**
* Holds if type `t` does not clash with another type we want to import that has the same base name.
*/
predicate isImportable(Type t) {
t = any(TestCase tc).getADesiredImport() and
t =
unique(Type sharesBaseName |
sharesBaseName = any(TestCase tc).getADesiredImport() and
sharesBaseName.getName() = t.getName()
|
sharesBaseName
)
}
/**
* Returns a printable name for type `t`, stripped of generics and, if a type variable,
* replaced by its bound. Usually this is a short name, but it may be package-qualified
* if we cannot import it due to a name clash.
*/
string getShortNameIfPossible(Type t) {
if t instanceof Array
then result = getShortNameIfPossible(t.(Array).getComponentType()) + "[]"
else (
getRootSourceDeclaration(t) = any(TestCase tc).getADesiredImport() and
if t instanceof RefType
then
exists(RefType replaced, string nestedName |
replaced = replaceTypeVariable(t).getSourceDeclaration() and
nestedName = replaced.nestedName().replaceAll("$", ".")
|
if isImportable(getRootSourceDeclaration(t))
then result = nestedName
else result = replaced.getPackage().getName() + "." + nestedName
)
else result = t.getName()
)
}
TestCase getAValidTestCase() { exists(result.getATestSnippetForRow(_)) }
/**
* Returns an import statement to include in the test case header.
*/
string getAnImportStatement() {
exists(RefType t |
t = any(TestCase tc).getADesiredImport() and
t = getAValidTestCase().getADesiredImport() and
isImportable(t) and
t.getPackage().getName() != "java.lang"
|
@@ -554,16 +78,16 @@ string getAnImportStatement() {
/**
* Returns a support method to include in the generated test class.
*/
string getASupportMethod() {
result = "Object source() { return null; }" or
result = "void sink(Object o) { }" or
result = any(TestCase tc).getASupportMethod()
SupportMethod getASupportMethod() {
result instanceof SourceMethod or
result instanceof SinkMethod or
result = getAValidTestCase().getASupportMethod()
}
/**
* Returns a CSV specification of the taint-/value-propagation behaviour of a test support method (`get` or `newWith` method).
*/
query string getASupportMethodModel() { result = any(TestCase tc).getASupportMethodModel() }
query string getASupportMethodModel() { result = getASupportMethod().getCsvModel() }
/**
* Gets a Java file body testing all requested CSV rows against whatever classes and methods they resolve against.
@@ -572,7 +96,8 @@ query string getTestCase() {
result =
"package generatedtest;\n\n" + concat(getAnImportStatement() + "\n") +
"\n// Test case generated by GenerateFlowTestCase.ql\npublic class Test {\n\n" +
concat("\t" + getASupportMethod() + "\n") + "\n\tpublic void test() throws Exception {\n\n" +
concat("\t" + getASupportMethod().getDefinition() + "\n") +
"\n\tpublic void test() throws Exception {\n\n" +
concat(string row, string snippet |
snippet = any(TestCase tc).getATestSnippetForRow(row)
|

View File

@@ -8,15 +8,10 @@
import java
/** A type that should be in the generated code. */
abstract private class GeneratedType extends RefType {
abstract private class GeneratedType extends ClassOrInterface {
GeneratedType() {
(
this instanceof Interface
or
this instanceof Class
) and
not this instanceof AnonymousClass and
not this instanceof LocalClass and
not this.isLocal() and
not this.getPackage() instanceof ExcludedPackage
}
@@ -134,8 +129,6 @@ private class IndirectType extends GeneratedType {
or
this = any(GeneratedType t).getSourceDeclaration()
or
exists(GeneratedType t | this = t.(BoundedType).getATypeBound().getType())
or
exists(GeneratedDeclaration decl |
decl.(Member).getDeclaringType().getSourceDeclaration() = this
)
@@ -143,8 +136,6 @@ private class IndirectType extends GeneratedType {
this.(NestedType).getEnclosingType() instanceof GeneratedType
or
exists(NestedType nt | nt instanceof GeneratedType and this = nt.getEnclosingType())
or
this = any(GeneratedType a).(Array).getComponentType()
}
}
@@ -156,6 +147,10 @@ private Type getAContainedType(Type t) {
result = t
or
result = getAContainedType(t.(ParameterizedType).getATypeArgument())
or
result = getAContainedType(t.(Array).getElementType())
or
result = getAContainedType(t.(BoundedType).getATypeBound().getType())
}
/**