mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
Merge branch 'main' into thirdpartyapitelemtry
This commit is contained in:
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
39
java/ql/src/external/ExternalArtifact.qll
vendored
39
java/ql/src/external/ExternalArtifact.qll
vendored
@@ -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) }
|
||||
}
|
||||
@@ -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: "*"
|
||||
|
||||
309
java/ql/src/utils/FlowTestCase.qll
Normal file
309
java/ql/src/utils/FlowTestCase.qll
Normal 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"
|
||||
}
|
||||
}
|
||||
393
java/ql/src/utils/FlowTestCaseSupportMethods.qll
Normal file
393
java/ql/src/utils/FlowTestCaseSupportMethods.qll
Normal 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 + "}"
|
||||
}
|
||||
}
|
||||
133
java/ql/src/utils/FlowTestCaseUtils.qll
Normal file
133
java/ql/src/utils/FlowTestCaseUtils.qll
Normal 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() + "]"
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
|
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user