diff --git a/.github/workflows/swift-integration-tests.yml b/.github/workflows/swift-integration-tests.yml index 9ded819da55..b81969f3502 100644 --- a/.github/workflows/swift-integration-tests.yml +++ b/.github/workflows/swift-integration-tests.yml @@ -32,6 +32,14 @@ jobs: - name: Build Swift extractor run: | bazel run //swift:create-extractor-pack + - name: Get Swift version + id: get_swift_version + run: | + VERSION=$(bazel run //swift/extractor -- --version | sed -ne 's/.*version \(\S*\).*/\1/p') + echo "::set-output name=version::$VERSION" + - uses: swift-actions/setup-swift@v1 + with: + swift-version: "${{steps.get_swift_version.outputs.version}}" - name: Run integration tests run: | python integration-tests/runner.py diff --git a/config/identical-files.json b/config/identical-files.json index a7c9cc239f2..96d81996a37 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -73,10 +73,11 @@ "ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplConsistency.qll", "swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImplConsistency.qll" ], - "DataFlow Java/C# Flow Summaries": [ + "DataFlow Java/C#/Ruby/Python/Swift Flow Summaries": [ "java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll", "ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll", + "python/ql/lib/semmle/python/dataflow/new/internal/FlowSummaryImpl.qll", "swift/ql/lib/codeql/swift/dataflow/internal/FlowSummaryImpl.qll" ], "SsaReadPosition Java/C#": [ @@ -532,7 +533,7 @@ "java/ql/lib/semmle/code/java/dataflow/internal/AccessPathSyntax.qll", "javascript/ql/lib/semmle/javascript/frameworks/data/internal/AccessPathSyntax.qll", "ruby/ql/lib/codeql/ruby/dataflow/internal/AccessPathSyntax.qll", - "python/ql/lib/semmle/python/frameworks/data/internal/AccessPathSyntax.qll", + "python/ql/lib/semmle/python/dataflow/new/internal/AccessPathSyntax.qll", "swift/ql/lib/codeql/swift/dataflow/internal/AccessPathSyntax.qll" ], "IncompleteUrlSubstringSanitization": [ diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/dataflow/ProductFlow.qll b/cpp/ql/lib/experimental/semmle/code/cpp/dataflow/ProductFlow.qll index c3c68b31ed2..2a6066e1b80 100644 --- a/cpp/ql/lib/experimental/semmle/code/cpp/dataflow/ProductFlow.qll +++ b/cpp/ql/lib/experimental/semmle/code/cpp/dataflow/ProductFlow.qll @@ -1,5 +1,5 @@ -import semmle.code.cpp.ir.dataflow.DataFlow -import semmle.code.cpp.ir.dataflow.DataFlow2 +import experimental.semmle.code.cpp.ir.dataflow.DataFlow +import experimental.semmle.code.cpp.ir.dataflow.DataFlow2 module ProductFlow { abstract class Configuration extends string { @@ -11,14 +11,43 @@ module ProductFlow { * * `source1` and `source2` must belong to the same callable. */ - abstract predicate isSourcePair(DataFlow::Node source1, DataFlow::Node source2); + predicate isSourcePair(DataFlow::Node source1, DataFlow::Node source2) { none() } + + /** + * Holds if `(source1, source2)` is a relevant data flow source with initial states `state1` + * and `state2`, respectively. + * + * `source1` and `source2` must belong to the same callable. + */ + predicate isSourcePair( + DataFlow::Node source1, string state1, DataFlow::Node source2, string state2 + ) { + state1 = "" and + state2 = "" and + this.isSourcePair(source1, source2) + } /** * Holds if `(sink1, sink2)` is a relevant data flow sink. * * `sink1` and `sink2` must belong to the same callable. */ - abstract predicate isSinkPair(DataFlow::Node sink1, DataFlow::Node sink2); + predicate isSinkPair(DataFlow::Node sink1, DataFlow::Node sink2) { none() } + + /** + * Holds if `(sink1, sink2)` is a relevant data flow sink with final states `state1` + * and `state2`, respectively. + * + * `sink1` and `sink2` must belong to the same callable. + */ + predicate isSinkPair( + DataFlow::Node sink1, DataFlow::FlowState state1, DataFlow::Node sink2, + DataFlow::FlowState state2 + ) { + state1 = "" and + state2 = "" and + this.isSinkPair(sink1, sink2) + } predicate hasFlowPath( DataFlow::PathNode source1, DataFlow2::PathNode source2, DataFlow::PathNode sink1, @@ -34,28 +63,28 @@ module ProductFlow { class Conf1 extends DataFlow::Configuration { Conf1() { this = "Conf1" } - override predicate isSource(DataFlow::Node source) { - exists(Configuration conf | conf.isSourcePair(source, _)) + override predicate isSource(DataFlow::Node source, string state) { + exists(Configuration conf | conf.isSourcePair(source, state, _, _)) } - override predicate isSink(DataFlow::Node sink) { - exists(Configuration conf | conf.isSinkPair(sink, _)) + override predicate isSink(DataFlow::Node sink, string state) { + exists(Configuration conf | conf.isSinkPair(sink, state, _, _)) } } class Conf2 extends DataFlow2::Configuration { Conf2() { this = "Conf2" } - override predicate isSource(DataFlow::Node source) { + override predicate isSource(DataFlow::Node source, string state) { exists(Configuration conf, DataFlow::Node source1 | - conf.isSourcePair(source1, source) and + conf.isSourcePair(source1, _, source, state) and any(Conf1 c).hasFlow(source1, _) ) } - override predicate isSink(DataFlow::Node sink) { + override predicate isSink(DataFlow::Node sink, string state) { exists(Configuration conf, DataFlow::Node sink1 | - conf.isSinkPair(sink1, sink) and any(Conf1 c).hasFlow(_, sink1) + conf.isSinkPair(sink1, _, sink, state) and any(Conf1 c).hasFlow(_, sink1) ) } } @@ -65,7 +94,7 @@ module ProductFlow { Configuration conf, DataFlow::PathNode source1, DataFlow2::PathNode source2, DataFlow::PathNode node1, DataFlow2::PathNode node2 ) { - conf.isSourcePair(node1.getNode(), node2.getNode()) and + conf.isSourcePair(node1.getNode(), _, node2.getNode(), _) and node1 = source1 and node2 = source2 or @@ -128,7 +157,7 @@ module ProductFlow { ) { exists(DataFlow::PathNode mid1, DataFlow2::PathNode mid2 | reachableInterprocEntry(conf, source1, source2, mid1, mid2) and - conf.isSinkPair(sink1.getNode(), sink2.getNode()) and + conf.isSinkPair(sink1.getNode(), _, sink2.getNode(), _) and localPathStep1*(mid1, sink1) and localPathStep2*(mid2, sink2) ) diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/rangeanalysis/Bound.qll b/cpp/ql/lib/experimental/semmle/code/cpp/rangeanalysis/Bound.qll index eba5c9b4464..abff447ca87 100644 --- a/cpp/ql/lib/experimental/semmle/code/cpp/rangeanalysis/Bound.qll +++ b/cpp/ql/lib/experimental/semmle/code/cpp/rangeanalysis/Bound.qll @@ -28,6 +28,10 @@ private newtype TBound = i.(LoadInstruction).getSourceAddress() instanceof FieldAddressInstruction or i.getAUse() instanceof ArgumentOperand + or + i instanceof PointerArithmeticInstruction + or + i.getAUse() instanceof AddressOperand ) } diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/semantic/SemanticExpr.qll b/cpp/ql/lib/experimental/semmle/code/cpp/semantic/SemanticExpr.qll index 8aeb15cab12..2ea958931da 100644 --- a/cpp/ql/lib/experimental/semmle/code/cpp/semantic/SemanticExpr.qll +++ b/cpp/ql/lib/experimental/semmle/code/cpp/semantic/SemanticExpr.qll @@ -178,11 +178,11 @@ class SemRelationalExpr extends SemBinaryExpr { } class SemAddExpr extends SemBinaryExpr { - SemAddExpr() { opcode instanceof Opcode::Add } + SemAddExpr() { opcode instanceof Opcode::Add or opcode instanceof Opcode::PointerAdd } } class SemSubExpr extends SemBinaryExpr { - SemSubExpr() { opcode instanceof Opcode::Sub } + SemSubExpr() { opcode instanceof Opcode::Sub or opcode instanceof Opcode::PointerSub } } class SemMulExpr extends SemBinaryExpr { diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/semantic/SemanticOpcode.qll b/cpp/ql/lib/experimental/semmle/code/cpp/semantic/SemanticOpcode.qll index 304d299dfba..bb56da71f73 100644 --- a/cpp/ql/lib/experimental/semmle/code/cpp/semantic/SemanticOpcode.qll +++ b/cpp/ql/lib/experimental/semmle/code/cpp/semantic/SemanticOpcode.qll @@ -65,10 +65,18 @@ module Opcode { override string toString() { result = "Add" } } + class PointerAdd extends Opcode, TPointerAdd { + override string toString() { result = "PointerAdd" } + } + class Sub extends Opcode, TSub { override string toString() { result = "Sub" } } + class PointerSub extends Opcode, TPointerSub { + override string toString() { result = "PointerSub" } + } + class Mul extends Opcode, TMul { override string toString() { result = "Mul" } } diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/semantic/analysis/RangeAnalysis.qll b/cpp/ql/lib/experimental/semmle/code/cpp/semantic/analysis/RangeAnalysis.qll index c2c83e4332d..4953b994c8e 100644 --- a/cpp/ql/lib/experimental/semmle/code/cpp/semantic/analysis/RangeAnalysis.qll +++ b/cpp/ql/lib/experimental/semmle/code/cpp/semantic/analysis/RangeAnalysis.qll @@ -223,7 +223,9 @@ private SemGuard boundFlowCond( else resultIsStrict = testIsTrue.booleanNot() ) and ( - if getTrackedTypeForSsaVariable(v) instanceof SemIntegerType + if + getTrackedTypeForSsaVariable(v) instanceof SemIntegerType or + getTrackedTypeForSsaVariable(v) instanceof SemAddressType then upper = true and strengthen = -1 or diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.cpp new file mode 100644 index 00000000000..0ad0635ae40 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.cpp @@ -0,0 +1,26 @@ +void *malloc(unsigned); +unsigned get_size(); +void write_data(const unsigned char*, const unsigned char*); + +int main(int argc, char* argv[]) { + unsigned size = get_size(); + + { + unsigned char *begin = (unsigned char*)malloc(size); + if(!begin) return -1; + + unsigned char* end = begin + size; + write_data(begin, end); + *end = '\0'; // BAD: Out-of-bounds write + } + + { + unsigned char *begin = (unsigned char*)malloc(size); + if(!begin) return -1; + + unsigned char* end = begin + size; + write_data(begin, end); + *(end - 1) = '\0'; // GOOD: writing to the last byte + } + +} \ No newline at end of file diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.qhelp new file mode 100644 index 00000000000..4f590659112 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.qhelp @@ -0,0 +1,31 @@ + + + +

The program performs an out-of-bounds read or write operation. In addition to causing program instability, techniques exist which may allow an attacker to use this vulnerability to execute arbitrary code.

+ +
+ + +

Ensure that pointer dereferences are properly guarded to ensure that they cannot be used to read or write past the end of the allocation.

+ +
+ +

The first example allocates a buffer of size size and creates a local variable that stores the location that is one byte past the end of the allocation. +This local variable is then dereferenced which results in an out-of-bounds write. +The second example subtracts one from the end variable before dereferencing it. This subtraction ensures that the write correctly updates the final byte of the allocation.

+ + +
+ + +
  • CERT C Coding Standard: +ARR30-C. Do not form or use out-of-bounds pointers or array subscripts.
  • +
  • +OWASP: +Buffer Overflow. +
  • + +
    +
    diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.ql b/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.ql new file mode 100644 index 00000000000..05327263386 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.ql @@ -0,0 +1,360 @@ +/** + * @name Invalid pointer dereference + * @description Dereferencing a pointer that points past it allocation is undefined behavior + * and may lead to security vulnerabilities. + * @kind path-problem + * @problem.severity error + * @precision high + * @id cpp/invalid-pointer-deref + * @tags reliability + * security + * external/cwe/cwe-119 + * external/cwe/cwe-125 + * external/cwe/cwe-193 + * external/cwe/cwe-787 + */ + +import cpp +import experimental.semmle.code.cpp.dataflow.ProductFlow +import experimental.semmle.code.cpp.ir.dataflow.DataFlow3 +import experimental.semmle.code.cpp.semantic.analysis.RangeAnalysis +import experimental.semmle.code.cpp.semantic.SemanticBound +import experimental.semmle.code.cpp.semantic.SemanticExprSpecific +import semmle.code.cpp.ir.IR + +pragma[nomagic] +Instruction getABoundIn(SemBound b, IRFunction func) { + result = b.getExpr(0) and + result.getEnclosingIRFunction() = func +} + +/** + * Holds if `i <= b + delta`. + */ +pragma[nomagic] +predicate bounded(Instruction i, Instruction b, int delta) { + exists(SemBound bound, IRFunction func | + semBounded(getSemanticExpr(i), bound, delta, true, _) and + b = getABoundIn(bound, func) and + i.getEnclosingIRFunction() = func + ) +} + +/** + * Holds if the combination of `n` and `state` represents an appropriate + * source for the expression `e` suitable for use-use flow. + */ +private predicate hasSizeImpl(Expr e, DataFlow::Node n, string state) { + // The simple case: If the size is a variable access with no qualifier we can just use the + // dataflow node for that expression and no state. + exists(VariableAccess va | + va = e and + not va instanceof FieldAccess and + n.asConvertedExpr() = va.getFullyConverted() and + state = "0" + ) + or + // If the size is a choice between two expressions we allow both to be nodes representing the size. + exists(ConditionalExpr cond | cond = e | hasSizeImpl([cond.getThen(), cond.getElse()], n, state)) + or + // If the size is an expression plus a constant, we pick the dataflow node of the expression and + // remember the constant in the state. + exists(Expr const, Expr nonconst | + e.(AddExpr).hasOperands(const, nonconst) and + state = const.getValue() and + hasSizeImpl(nonconst, n, _) + ) + or + exists(Expr const, Expr nonconst | + e.(SubExpr).hasOperands(const, nonconst) and + state = "-" + const.getValue() and + hasSizeImpl(nonconst, n, _) + ) +} + +/** + * Holds if `(n, state)` pair represents the source of flow for the size + * expression associated with `alloc`. + */ +predicate hasSize(AllocationExpr alloc, DataFlow::Node n, string state) { + hasSizeImpl(alloc.getSizeExpr(), n, state) +} + +/** + * A product-flow configuration for flow from an (allocation, size) pair to a + * pointer-arithmetic operation that is non-strictly upper-bounded by `allocation + size`. + * + * The goal of this query is to find patterns such as: + * ```cpp + * 1. char* begin = (char*)malloc(size); + * 2. char* end = begin + size; + * 3. for(int *p = begin; p <= end; p++) { + * 4. use(*p); + * 5. } + * ``` + * + * We do this by splitting the task up into two configurations: + * 1. `AllocToInvalidPointerConf` find flow from `malloc(size)` to `begin + size`, and + * 2. `InvalidPointerToDerefConf` finds flow from `begin + size` to an `end` (on line 3). + * + * Finally, the range-analysis library will find a load from (or store to) an address that + * is non-strictly upper-bounded by `end` (which in this case is `*p`). + */ +class AllocToInvalidPointerConf extends ProductFlow::Configuration { + AllocToInvalidPointerConf() { this = "AllocToInvalidPointerConf" } + + override predicate isSourcePair( + DataFlow::Node source1, string state1, DataFlow::Node source2, string state2 + ) { + // In the case of an allocation like + // ```cpp + // malloc(size + 1); + // ``` + // we use `state2` to remember that there was an offset (in this case an offset of `1`) added + // to the size of the allocation. This state is then checked in `isSinkPair`. + state1 = "" and + hasSize(source1.asConvertedExpr(), source2, state2) + } + + override predicate isSinkPair( + DataFlow::Node sink1, DataFlow::FlowState state1, DataFlow::Node sink2, + DataFlow::FlowState state2 + ) { + state1 = "" and + // We check that the delta computed by the range analysis matches the + // state value that we set in `isSourcePair`. + exists(int delta | + isSinkImpl(_, sink1, sink2, delta) and + state2 = delta.toString() + ) + } +} + +pragma[nomagic] +predicate pointerAddInstructionHasOperands( + PointerAddInstruction pai, Instruction left, Instruction right +) { + pai.getLeft() = left and + pai.getRight() = right +} + +/** + * Holds if `pai` is non-strictly upper bounded by `sink2 + delta` and `sink1` is the + * left operand of the pointer-arithmetic operation. + * + * For example in, + * ```cpp + * char* end = p + (size + 1); + * ``` + * We will have: + * - `pai` is `p + (size + 1)`, + * - `sink1` is `p` + * - `sink2` is `size` + * - `delta` is `1`. + */ +pragma[nomagic] +predicate pointerAddInstructionHasBounds( + PointerAddInstruction pai, DataFlow::Node sink1, Instruction sink2, int delta +) { + exists(Instruction right | + pointerAddInstructionHasOperands(pai, sink1.asInstruction(), right) and + bounded(right, sink2, delta) + ) +} + +/** + * Holds if `pai` is non-strictly upper bounded by `sink2 + delta` and `sink1` is the + * left operand of the pointer-arithmetic operation. + * + * See `pointerAddInstructionHasBounds` for an example. + */ +predicate isSinkImpl( + PointerAddInstruction pai, DataFlow::Node sink1, DataFlow::Node sink2, int delta +) { + pointerAddInstructionHasBounds(pai, sink1, sink2.asInstruction(), delta) +} + +/** + * Holds if `sink` is a sink for `InvalidPointerToDerefConf` and `i` is a `StoreInstruction` that + * writes to an address that non-strictly upper-bounds `sink`, or `i` is a `LoadInstruction` that + * reads from an address that non-strictly upper-bounds `sink`. + */ +predicate isInvalidPointerDerefSink(DataFlow::Node sink, Instruction i, string operation) { + exists(AddressOperand addr, int delta | + bounded(addr.getDef(), sink.asInstruction(), delta) and + delta >= 0 and + i.getAnOperand() = addr + | + i instanceof StoreInstruction and + operation = "write" + or + i instanceof LoadInstruction and + operation = "read" + ) +} + +/** + * A configuration to track flow from a pointer-arithmetic operation found + * by `AllocToInvalidPointerConf` to a dereference of the pointer. + */ +class InvalidPointerToDerefConf extends DataFlow3::Configuration { + InvalidPointerToDerefConf() { this = "InvalidPointerToDerefConf" } + + override predicate isSource(DataFlow::Node source) { invalidPointerToDerefSource(_, source, _) } + + override predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _) } +} + +/** + * Holds if `pai` is a pointer-arithmetic operation and `source` is a dataflow node with a + * pointer-value that is non-strictly upper bounded by `pai + delta`. + * + * For example, if `pai` is a pointer-arithmetic operation `p + size` in an expression such + * as `(p + size) + 1` and `source` is the node representing `(p + size) + 1`. In this + * case `delta` is 1. + */ +predicate invalidPointerToDerefSource( + PointerArithmeticInstruction pai, DataFlow::Node source, int delta +) { + exists(ProductFlow::Configuration conf, DataFlow::PathNode p, DataFlow::Node sink1 | + p.getNode() = sink1 and + conf.hasFlowPath(_, _, p, _) and + isSinkImpl(pai, sink1, _, _) and + bounded(source.asInstruction(), pai, delta) and + delta >= 0 + ) +} + +newtype TMergedPathNode = + // The path nodes computed by the first projection of `AllocToInvalidPointerConf` + TPathNode1(DataFlow::PathNode p) or + // The path nodes computed by `InvalidPointerToDerefConf` + TPathNode3(DataFlow3::PathNode p) or + // The read/write that uses the invalid pointer identified by `InvalidPointerToDerefConf`. + // This one is needed because the sink identified by `InvalidPointerToDerefConf` is the + // pointer, but we want to raise an alert at the dereference. + TPathNodeSink(Instruction i) { + exists(DataFlow::Node n | + any(InvalidPointerToDerefConf conf).hasFlow(_, n) and + isInvalidPointerDerefSink(n, i, _) + ) + } + +class MergedPathNode extends TMergedPathNode { + string toString() { none() } + + final DataFlow::PathNode asPathNode1() { this = TPathNode1(result) } + + final DataFlow3::PathNode asPathNode3() { this = TPathNode3(result) } + + final Instruction asSinkNode() { this = TPathNodeSink(result) } + + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + none() + } +} + +class PathNode1 extends MergedPathNode, TPathNode1 { + override string toString() { + exists(DataFlow::PathNode p | + this = TPathNode1(p) and + result = p.toString() + ) + } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.asPathNode1().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +class PathNode3 extends MergedPathNode, TPathNode3 { + override string toString() { + exists(DataFlow3::PathNode p | + this = TPathNode3(p) and + result = p.toString() + ) + } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.asPathNode3().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +class PathSinkNode extends MergedPathNode, TPathNodeSink { + override string toString() { + exists(Instruction i | + this = TPathNodeSink(i) and + result = i.toString() + ) + } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.asSinkNode() + .getLocation() + .hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +query predicate edges(MergedPathNode node1, MergedPathNode node2) { + node1.asPathNode1().getASuccessor() = node2.asPathNode1() + or + joinOn1(_, node1.asPathNode1(), node2.asPathNode3()) + or + node1.asPathNode3().getASuccessor() = node2.asPathNode3() + or + joinOn2(node1.asPathNode3(), node2.asSinkNode(), _) +} + +/** + * Holds if `p1` is a sink of `AllocToInvalidPointerConf` and `p2` is a source + * of `InvalidPointerToDerefConf`, and they are connected through `pai`. + */ +predicate joinOn1(PointerArithmeticInstruction pai, DataFlow::PathNode p1, DataFlow3::PathNode p2) { + isSinkImpl(pai, p1.getNode(), _, _) and + invalidPointerToDerefSource(pai, p2.getNode(), _) +} + +/** + * Holds if `p1` is a sink of `InvalidPointerToDerefConf` and `i` is the instruction + * that dereferences `p1`. The string `operation` describes whether the `i` is + * a `StoreInstruction` or `LoadInstruction`. + */ +predicate joinOn2(DataFlow3::PathNode p1, Instruction i, string operation) { + isInvalidPointerDerefSink(p1.getNode(), i, operation) +} + +predicate hasFlowPath( + MergedPathNode source1, MergedPathNode sink, DataFlow3::PathNode source3, + PointerArithmeticInstruction pai, string operation +) { + exists( + AllocToInvalidPointerConf conf1, InvalidPointerToDerefConf conf2, DataFlow3::PathNode sink3, + DataFlow::PathNode sink1 + | + conf1.hasFlowPath(source1.asPathNode1(), _, sink1, _) and + joinOn1(pai, sink1, source3) and + conf2.hasFlowPath(source3, sink3) and + joinOn2(sink3, sink.asSinkNode(), operation) + ) +} + +from + MergedPathNode source, MergedPathNode sink, int k, string kstr, DataFlow3::PathNode source3, + PointerArithmeticInstruction pai, string operation, Expr offset, DataFlow::Node n +where + hasFlowPath(source, sink, source3, pai, operation) and + invalidPointerToDerefSource(pai, source3.getNode(), k) and + offset = pai.getRight().getUnconvertedResultExpression() and + n = source.asPathNode1().getNode() and + if k = 0 then kstr = "" else kstr = " + " + k +select sink, source, sink, + "This " + operation + " might be out of bounds, as the pointer might be equal to $@ + $@" + kstr + + ".", n, n.toString(), offset, offset.toString() diff --git a/cpp/ql/test/experimental/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.expected b/cpp/ql/test/experimental/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.expected index ea8c8f8f577..106313c8707 100644 --- a/cpp/ql/test/experimental/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.expected +++ b/cpp/ql/test/experimental/library-tests/rangeanalysis/rangeanalysis/RangeAnalysis.expected @@ -20,10 +20,12 @@ | test.cpp:62:10:62:13 | Load: iter | test.cpp:60:17:60:17 | ValueNumberBound | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | | test.cpp:62:10:62:13 | Load: iter | test.cpp:60:17:60:17 | ValueNumberBound | 3 | true | CompareLT: ... < ... | test.cpp:61:32:61:51 | test.cpp:61:32:61:51 | | test.cpp:62:10:62:13 | Load: iter | test.cpp:61:39:61:51 | ValueNumberBound | -1 | true | CompareLT: ... < ... | test.cpp:61:32:61:51 | test.cpp:61:32:61:51 | +| test.cpp:62:10:62:13 | Load: iter | test.cpp:61:48:61:50 | ValueNumberBound | -1 | true | CompareLT: ... < ... | test.cpp:61:32:61:51 | test.cpp:61:32:61:51 | | test.cpp:67:10:67:13 | Load: iter | test.cpp:60:17:60:17 | ValueNumberBound | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | | test.cpp:67:10:67:13 | Load: iter | test.cpp:60:17:60:17 | ValueNumberBound | 3 | true | CompareLT: ... < ... | test.cpp:66:32:66:41 | test.cpp:66:32:66:41 | | test.cpp:67:10:67:13 | Load: iter | test.cpp:61:32:61:35 | ValueNumberBound | -1 | true | CompareLT: ... < ... | test.cpp:66:32:66:41 | test.cpp:66:32:66:41 | | test.cpp:67:10:67:13 | Load: iter | test.cpp:61:39:61:51 | ValueNumberBound | -1 | true | CompareLT: ... < ... | test.cpp:66:32:66:41 | test.cpp:66:32:66:41 | +| test.cpp:67:10:67:13 | Load: iter | test.cpp:61:48:61:50 | ValueNumberBound | -1 | true | CompareLT: ... < ... | test.cpp:66:32:66:41 | test.cpp:66:32:66:41 | | test.cpp:77:12:77:12 | Load: i | file://:0:0:0:0 | 0 | 0 | false | NoReason | file://:0:0:0:0 | file://:0:0:0:0 | | test.cpp:77:12:77:12 | Load: i | test.cpp:72:15:72:15 | ValueNumberBound | -1 | true | CompareLT: ... < ... | test.cpp:76:20:76:24 | test.cpp:76:20:76:24 | | test.cpp:77:12:77:12 | Load: i | test.cpp:72:22:72:22 | ValueNumberBound | -1 | true | CompareLT: ... < ... | test.cpp:76:20:76:24 | test.cpp:76:20:76:24 | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.expected index 56ae4e59a9a..dedb1d72a38 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.expected +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-119/OverrunWriteProductFlow.expected @@ -1,2 +1,2 @@ -| test.cpp:19:19:19:24 | call to malloc | test.cpp:18:17:18:20 | size | test.cpp:26:18:26:23 | string | test.cpp:26:31:26:39 | (size_t)... | -| test.cpp:19:19:19:24 | call to malloc | test.cpp:18:17:18:20 | size | test.cpp:30:18:30:23 | string | test.cpp:30:31:30:39 | (size_t)... | +| test.cpp:19:19:19:24 | call to malloc | test.cpp:18:17:18:20 | size | test.cpp:26:18:26:23 | Load | test.cpp:26:31:26:39 | Convert | +| test.cpp:19:19:19:24 | call to malloc | test.cpp:18:17:18:20 | size | test.cpp:30:18:30:23 | Load | test.cpp:30:31:30:39 | Convert | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/ArrayAccessProductFlow.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/ArrayAccessProductFlow.expected index 2564193fb2e..8b82181b9f7 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/ArrayAccessProductFlow.expected +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/ArrayAccessProductFlow.expected @@ -1,23 +1,20 @@ -| test.cpp:4:17:4:22 | call to malloc | test.cpp:4:24:4:27 | size | test.cpp:10:9:10:11 | arr | test.cpp:4:24:4:27 | size | -| test.cpp:4:17:4:22 | call to malloc | test.cpp:4:24:4:27 | size | test.cpp:10:9:10:11 | arr | test.cpp:4:24:4:27 | size | -| test.cpp:4:17:4:22 | call to malloc | test.cpp:4:24:4:27 | size | test.cpp:10:9:10:11 | arr | test.cpp:5:25:5:28 | size | -| test.cpp:4:17:4:22 | call to malloc | test.cpp:4:24:4:27 | size | test.cpp:10:9:10:11 | arr | test.cpp:5:25:5:28 | size | -| test.cpp:4:17:4:22 | call to malloc | test.cpp:4:24:4:27 | size | test.cpp:10:9:10:11 | arr | test.cpp:9:26:9:29 | size | -| test.cpp:4:17:4:22 | call to malloc | test.cpp:5:25:5:28 | size | test.cpp:10:9:10:11 | arr | test.cpp:5:25:5:28 | size | -| test.cpp:4:17:4:22 | call to malloc | test.cpp:5:25:5:28 | size | test.cpp:10:9:10:11 | arr | test.cpp:5:25:5:28 | size | -| test.cpp:4:17:4:22 | call to malloc | test.cpp:5:25:5:28 | size | test.cpp:10:9:10:11 | arr | test.cpp:9:26:9:29 | size | -| test.cpp:4:17:4:22 | call to malloc | test.cpp:9:26:9:29 | size | test.cpp:10:9:10:11 | arr | test.cpp:9:26:9:29 | size | -| test.cpp:4:17:4:22 | call to malloc | test.cpp:9:26:9:29 | size | test.cpp:10:9:10:11 | arr | test.cpp:9:26:9:29 | size | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | p | test.cpp:55:5:55:19 | Store | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | p | test.cpp:55:16:55:19 | size | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | p | test.cpp:55:16:55:19 | size | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | p | test.cpp:56:20:56:23 | size | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:56:20:56:23 | size | test.cpp:63:13:63:13 | p | test.cpp:56:20:56:23 | size | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:56:20:56:23 | size | test.cpp:63:13:63:13 | p | test.cpp:56:20:56:23 | size | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:58:29:58:32 | size | test.cpp:63:13:63:13 | p | test.cpp:58:29:58:32 | size | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:58:29:58:32 | size | test.cpp:63:13:63:13 | p | test.cpp:58:29:58:32 | size | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:62:30:62:33 | size | test.cpp:63:13:63:13 | p | test.cpp:62:30:62:33 | size | -| test.cpp:56:13:56:18 | call to malloc | test.cpp:62:30:62:33 | size | test.cpp:63:13:63:13 | p | test.cpp:62:30:62:33 | size | -| test.cpp:70:14:70:19 | call to malloc | test.cpp:69:17:69:20 | size | test.cpp:83:14:83:14 | p | test.cpp:82:31:82:34 | size | -| test.cpp:70:14:70:19 | call to malloc | test.cpp:69:17:69:20 | size | test.cpp:93:14:93:14 | p | test.cpp:88:30:88:33 | size | -| test.cpp:70:14:70:19 | call to malloc | test.cpp:69:17:69:20 | size | test.cpp:93:14:93:14 | p | test.cpp:92:31:92:34 | size | +| test.cpp:4:17:4:22 | call to malloc | test.cpp:4:24:4:27 | size | test.cpp:10:9:10:11 | Load | test.cpp:5:25:5:28 | Load | +| test.cpp:4:17:4:22 | call to malloc | test.cpp:4:24:4:27 | size | test.cpp:10:9:10:11 | Load | test.cpp:9:26:9:29 | Load | +| test.cpp:4:17:4:22 | call to malloc | test.cpp:5:25:5:28 | size | test.cpp:10:9:10:11 | Load | test.cpp:5:25:5:28 | Load | +| test.cpp:4:17:4:22 | call to malloc | test.cpp:5:25:5:28 | size | test.cpp:10:9:10:11 | Load | test.cpp:9:26:9:29 | Load | +| test.cpp:4:17:4:22 | call to malloc | test.cpp:9:26:9:29 | size | test.cpp:10:9:10:11 | Load | test.cpp:9:26:9:29 | Load | +| test.cpp:22:13:22:18 | call to malloc | test.cpp:21:16:21:19 | size | test.cpp:35:13:35:13 | Load | test.cpp:30:29:30:32 | Load | +| test.cpp:22:13:22:18 | call to malloc | test.cpp:21:16:21:19 | size | test.cpp:35:13:35:13 | Load | test.cpp:34:30:34:33 | Load | +| test.cpp:22:13:22:18 | call to malloc | test.cpp:21:16:21:19 | size | test.cpp:45:13:45:13 | Load | test.cpp:40:29:40:32 | Load | +| test.cpp:22:13:22:18 | call to malloc | test.cpp:21:16:21:19 | size | test.cpp:45:13:45:13 | Load | test.cpp:44:30:44:33 | Load | +| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | Load | test.cpp:55:5:55:19 | Store | +| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | Load | test.cpp:55:5:55:19 | Store | +| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | Load | test.cpp:55:16:55:19 | Load | +| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | Load | test.cpp:56:20:56:23 | Load | +| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | Load | test.cpp:58:29:58:32 | Load | +| test.cpp:56:13:56:18 | call to malloc | test.cpp:55:16:55:19 | size | test.cpp:63:13:63:13 | Load | test.cpp:62:30:62:33 | Load | +| test.cpp:56:13:56:18 | call to malloc | test.cpp:58:29:58:32 | size | test.cpp:63:13:63:13 | Load | test.cpp:58:29:58:32 | Load | +| test.cpp:56:13:56:18 | call to malloc | test.cpp:62:30:62:33 | size | test.cpp:63:13:63:13 | Load | test.cpp:62:30:62:33 | Load | +| test.cpp:70:14:70:19 | call to malloc | test.cpp:69:17:69:20 | size | test.cpp:83:14:83:14 | Load | test.cpp:82:31:82:34 | Load | +| test.cpp:70:14:70:19 | call to malloc | test.cpp:69:17:69:20 | size | test.cpp:93:14:93:14 | Load | test.cpp:88:30:88:33 | Load | +| test.cpp:70:14:70:19 | call to malloc | test.cpp:69:17:69:20 | size | test.cpp:93:14:93:14 | Load | test.cpp:92:31:92:34 | Load | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/test.cpp index b3c4341f9b8..f35379db3e4 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/test.cpp +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/array-access/test.cpp @@ -32,7 +32,7 @@ void test2(int size) { } for (int i = 0; i <= arr.size; i++) { - arr.p[i] = i; // BAD [NOT DETECTED] + arr.p[i] = i; // BAD } } @@ -42,7 +42,7 @@ void test3_callee(array_t arr) { } for (int i = 0; i <= arr.size; i++) { - arr.p[i] = i; // BAD [NOT DETECTED] + arr.p[i] = i; // BAD } } diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected new file mode 100644 index 00000000000..494713b124b --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.expected @@ -0,0 +1,627 @@ +edges +| test.cpp:4:15:4:20 | call to malloc | test.cpp:5:15:5:15 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:5:15:5:22 | ... + ... | +| test.cpp:5:15:5:15 | Load | test.cpp:5:15:5:22 | ... + ... | +| test.cpp:5:15:5:15 | Load | test.cpp:5:15:5:22 | ... + ... | +| test.cpp:5:15:5:15 | Load | test.cpp:5:15:5:22 | ... + ... | +| test.cpp:5:15:5:15 | Load | test.cpp:5:15:5:22 | Store | +| test.cpp:5:15:5:15 | Load | test.cpp:5:15:5:22 | Store | +| test.cpp:5:15:5:15 | Load | test.cpp:5:15:5:22 | Store | +| test.cpp:5:15:5:15 | Load | test.cpp:5:15:5:22 | Store | +| test.cpp:5:15:5:15 | Load | test.cpp:6:15:6:15 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:6:15:6:15 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:6:15:6:15 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:6:15:6:15 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:7:16:7:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:7:16:7:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:7:16:7:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:7:16:7:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:8:16:8:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:8:16:8:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:8:16:8:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:8:16:8:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:8:16:8:20 | ... + ... | +| test.cpp:5:15:5:15 | Load | test.cpp:8:16:8:20 | ... + ... | +| test.cpp:5:15:5:15 | Load | test.cpp:8:16:8:20 | ... + ... | +| test.cpp:5:15:5:15 | Load | test.cpp:8:16:8:20 | ... + ... | +| test.cpp:5:15:5:15 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:5:15:5:15 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:5:15:5:22 | Store | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:5:15:5:22 | Store | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:15:6:15 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:6:15:6:15 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:7:16:7:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:7:16:7:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:16:8:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:8:16:8:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:9:16:9:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:9:16:9:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:10:16:10:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:10:16:10:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:11:16:11:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:11:16:11:16 | Load | +| test.cpp:5:15:5:22 | ... + ... | test.cpp:12:16:12:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:5:15:5:22 | Store | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:5:15:5:22 | Store | test.cpp:6:15:6:15 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:6:15:6:15 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:7:16:7:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:7:16:7:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:5:15:5:22 | Store | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:5:15:5:22 | Store | test.cpp:8:16:8:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:8:16:8:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:9:16:9:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:9:16:9:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:10:16:10:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:10:16:10:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:11:16:11:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:11:16:11:16 | Load | +| test.cpp:5:15:5:22 | Store | test.cpp:12:16:12:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:6:15:6:15 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:6:15:6:15 | Load | test.cpp:7:16:7:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:7:16:7:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:6:15:6:15 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:6:15:6:15 | Load | test.cpp:8:16:8:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:8:16:8:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:6:15:6:15 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:7:16:7:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:7:16:7:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:7:16:7:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:7:16:7:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:7:16:7:16 | Load | test.cpp:8:16:8:16 | Load | +| test.cpp:7:16:7:16 | Load | test.cpp:8:16:8:16 | Load | +| test.cpp:7:16:7:16 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:7:16:7:16 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:7:16:7:16 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:7:16:7:16 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:7:16:7:16 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:7:16:7:16 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:7:16:7:16 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:8:16:8:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:8:16:8:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:8:16:8:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:8:16:8:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:8:16:8:16 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:8:16:8:16 | Load | test.cpp:9:16:9:16 | Load | +| test.cpp:8:16:8:16 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:8:16:8:16 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:8:16:8:16 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:8:16:8:16 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:8:16:8:16 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:8:16:8:20 | ... + ... | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:8:16:8:20 | ... + ... | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:9:16:9:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:9:16:9:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:9:16:9:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:9:16:9:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:9:16:9:16 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:9:16:9:16 | Load | test.cpp:10:16:10:16 | Load | +| test.cpp:9:16:9:16 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:9:16:9:16 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:9:16:9:16 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:10:16:10:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:10:16:10:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:10:16:10:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:10:16:10:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:10:16:10:16 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:10:16:10:16 | Load | test.cpp:11:16:11:16 | Load | +| test.cpp:10:16:10:16 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:11:16:11:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:11:16:11:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:11:16:11:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:11:16:11:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:11:16:11:16 | Load | test.cpp:12:16:12:16 | Load | +| test.cpp:12:16:12:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:12:16:12:16 | Load | test.cpp:6:14:6:15 | Load: * ... | +| test.cpp:12:16:12:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:12:16:12:16 | Load | test.cpp:8:14:8:21 | Load: * ... | +| test.cpp:16:15:16:20 | call to malloc | test.cpp:17:15:17:15 | Load | +| test.cpp:17:15:17:15 | Load | test.cpp:17:15:17:22 | ... + ... | +| test.cpp:17:15:17:15 | Load | test.cpp:17:15:17:22 | ... + ... | +| test.cpp:17:15:17:15 | Load | test.cpp:17:15:17:22 | ... + ... | +| test.cpp:17:15:17:15 | Load | test.cpp:17:15:17:22 | ... + ... | +| test.cpp:17:15:17:15 | Load | test.cpp:20:16:20:20 | ... + ... | +| test.cpp:17:15:17:15 | Load | test.cpp:20:16:20:20 | ... + ... | +| test.cpp:17:15:17:15 | Load | test.cpp:20:16:20:20 | ... + ... | +| test.cpp:17:15:17:15 | Load | test.cpp:20:16:20:20 | ... + ... | +| test.cpp:17:15:17:22 | ... + ... | test.cpp:20:14:20:21 | Load: * ... | +| test.cpp:17:15:17:22 | ... + ... | test.cpp:20:14:20:21 | Load: * ... | +| test.cpp:20:16:20:20 | ... + ... | test.cpp:20:14:20:21 | Load: * ... | +| test.cpp:20:16:20:20 | ... + ... | test.cpp:20:14:20:21 | Load: * ... | +| test.cpp:28:15:28:20 | call to malloc | test.cpp:29:15:29:15 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:29:15:29:28 | ... + ... | +| test.cpp:29:15:29:15 | Load | test.cpp:29:15:29:28 | ... + ... | +| test.cpp:29:15:29:15 | Load | test.cpp:29:15:29:28 | ... + ... | +| test.cpp:29:15:29:15 | Load | test.cpp:29:15:29:28 | ... + ... | +| test.cpp:29:15:29:15 | Load | test.cpp:29:15:29:28 | Store | +| test.cpp:29:15:29:15 | Load | test.cpp:29:15:29:28 | Store | +| test.cpp:29:15:29:15 | Load | test.cpp:29:15:29:28 | Store | +| test.cpp:29:15:29:15 | Load | test.cpp:29:15:29:28 | Store | +| test.cpp:29:15:29:15 | Load | test.cpp:30:15:30:15 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:30:15:30:15 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:30:15:30:15 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:30:15:30:15 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:31:16:31:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:31:16:31:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:31:16:31:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:31:16:31:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:32:16:32:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:32:16:32:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:32:16:32:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:32:16:32:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:32:16:32:20 | ... + ... | +| test.cpp:29:15:29:15 | Load | test.cpp:32:16:32:20 | ... + ... | +| test.cpp:29:15:29:15 | Load | test.cpp:32:16:32:20 | ... + ... | +| test.cpp:29:15:29:15 | Load | test.cpp:32:16:32:20 | ... + ... | +| test.cpp:29:15:29:15 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:29:15:29:15 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:29:15:29:28 | Store | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:29:15:29:28 | Store | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:15:30:15 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:30:15:30:15 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:31:16:31:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:31:16:31:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:16:32:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:32:16:32:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:33:16:33:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:33:16:33:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:34:16:34:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:34:16:34:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:35:16:35:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:35:16:35:16 | Load | +| test.cpp:29:15:29:28 | ... + ... | test.cpp:36:16:36:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:29:15:29:28 | Store | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:29:15:29:28 | Store | test.cpp:30:15:30:15 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:30:15:30:15 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:31:16:31:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:31:16:31:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:29:15:29:28 | Store | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:29:15:29:28 | Store | test.cpp:32:16:32:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:32:16:32:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:33:16:33:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:33:16:33:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:34:16:34:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:34:16:34:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:35:16:35:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:35:16:35:16 | Load | +| test.cpp:29:15:29:28 | Store | test.cpp:36:16:36:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:30:15:30:15 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:30:15:30:15 | Load | test.cpp:31:16:31:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:31:16:31:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:30:15:30:15 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:30:15:30:15 | Load | test.cpp:32:16:32:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:32:16:32:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:30:15:30:15 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:31:16:31:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:31:16:31:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:31:16:31:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:31:16:31:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:31:16:31:16 | Load | test.cpp:32:16:32:16 | Load | +| test.cpp:31:16:31:16 | Load | test.cpp:32:16:32:16 | Load | +| test.cpp:31:16:31:16 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:31:16:31:16 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:31:16:31:16 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:31:16:31:16 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:31:16:31:16 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:31:16:31:16 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:31:16:31:16 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:32:16:32:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:32:16:32:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:32:16:32:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:32:16:32:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:32:16:32:16 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:32:16:32:16 | Load | test.cpp:33:16:33:16 | Load | +| test.cpp:32:16:32:16 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:32:16:32:16 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:32:16:32:16 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:32:16:32:16 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:32:16:32:16 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:32:16:32:20 | ... + ... | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:32:16:32:20 | ... + ... | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:33:16:33:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:33:16:33:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:33:16:33:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:33:16:33:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:33:16:33:16 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:33:16:33:16 | Load | test.cpp:34:16:34:16 | Load | +| test.cpp:33:16:33:16 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:33:16:33:16 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:33:16:33:16 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:34:16:34:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:34:16:34:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:34:16:34:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:34:16:34:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:34:16:34:16 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:34:16:34:16 | Load | test.cpp:35:16:35:16 | Load | +| test.cpp:34:16:34:16 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:35:16:35:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:35:16:35:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:35:16:35:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:35:16:35:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:35:16:35:16 | Load | test.cpp:36:16:36:16 | Load | +| test.cpp:36:16:36:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:36:16:36:16 | Load | test.cpp:30:14:30:15 | Load: * ... | +| test.cpp:36:16:36:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:36:16:36:16 | Load | test.cpp:32:14:32:21 | Load: * ... | +| test.cpp:40:15:40:20 | call to malloc | test.cpp:41:15:41:15 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:41:15:41:28 | ... + ... | +| test.cpp:41:15:41:15 | Load | test.cpp:41:15:41:28 | ... + ... | +| test.cpp:41:15:41:15 | Load | test.cpp:41:15:41:28 | ... + ... | +| test.cpp:41:15:41:15 | Load | test.cpp:41:15:41:28 | ... + ... | +| test.cpp:41:15:41:15 | Load | test.cpp:41:15:41:28 | Store | +| test.cpp:41:15:41:15 | Load | test.cpp:41:15:41:28 | Store | +| test.cpp:41:15:41:15 | Load | test.cpp:41:15:41:28 | Store | +| test.cpp:41:15:41:15 | Load | test.cpp:41:15:41:28 | Store | +| test.cpp:41:15:41:15 | Load | test.cpp:42:15:42:15 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:42:15:42:15 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:42:15:42:15 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:42:15:42:15 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:43:16:43:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:43:16:43:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:43:16:43:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:43:16:43:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:44:16:44:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:44:16:44:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:44:16:44:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:44:16:44:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:44:16:44:20 | ... + ... | +| test.cpp:41:15:41:15 | Load | test.cpp:44:16:44:20 | ... + ... | +| test.cpp:41:15:41:15 | Load | test.cpp:44:16:44:20 | ... + ... | +| test.cpp:41:15:41:15 | Load | test.cpp:44:16:44:20 | ... + ... | +| test.cpp:41:15:41:15 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:41:15:41:15 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:41:15:41:28 | Store | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:41:15:41:28 | Store | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:15:42:15 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:42:15:42:15 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:43:16:43:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:43:16:43:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:16:44:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:44:16:44:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:45:16:45:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:45:16:45:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:46:16:46:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:46:16:46:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:47:16:47:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:47:16:47:16 | Load | +| test.cpp:41:15:41:28 | ... + ... | test.cpp:48:16:48:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:41:15:41:28 | Store | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:41:15:41:28 | Store | test.cpp:42:15:42:15 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:42:15:42:15 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:43:16:43:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:43:16:43:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:41:15:41:28 | Store | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:41:15:41:28 | Store | test.cpp:44:16:44:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:44:16:44:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:45:16:45:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:45:16:45:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:46:16:46:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:46:16:46:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:47:16:47:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:47:16:47:16 | Load | +| test.cpp:41:15:41:28 | Store | test.cpp:48:16:48:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:42:15:42:15 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:42:15:42:15 | Load | test.cpp:43:16:43:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:43:16:43:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:42:15:42:15 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:42:15:42:15 | Load | test.cpp:44:16:44:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:44:16:44:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:42:15:42:15 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:43:16:43:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:43:16:43:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:43:16:43:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:43:16:43:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:43:16:43:16 | Load | test.cpp:44:16:44:16 | Load | +| test.cpp:43:16:43:16 | Load | test.cpp:44:16:44:16 | Load | +| test.cpp:43:16:43:16 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:43:16:43:16 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:43:16:43:16 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:43:16:43:16 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:43:16:43:16 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:43:16:43:16 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:43:16:43:16 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:44:16:44:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:44:16:44:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:44:16:44:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:44:16:44:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:44:16:44:16 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:44:16:44:16 | Load | test.cpp:45:16:45:16 | Load | +| test.cpp:44:16:44:16 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:44:16:44:16 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:44:16:44:16 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:44:16:44:16 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:44:16:44:16 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:44:16:44:20 | ... + ... | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:44:16:44:20 | ... + ... | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:45:16:45:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:45:16:45:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:45:16:45:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:45:16:45:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:45:16:45:16 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:45:16:45:16 | Load | test.cpp:46:16:46:16 | Load | +| test.cpp:45:16:45:16 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:45:16:45:16 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:45:16:45:16 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:46:16:46:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:46:16:46:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:46:16:46:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:46:16:46:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:46:16:46:16 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:46:16:46:16 | Load | test.cpp:47:16:47:16 | Load | +| test.cpp:46:16:46:16 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:47:16:47:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:47:16:47:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:47:16:47:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:47:16:47:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:47:16:47:16 | Load | test.cpp:48:16:48:16 | Load | +| test.cpp:48:16:48:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:48:16:48:16 | Load | test.cpp:42:14:42:15 | Load: * ... | +| test.cpp:48:16:48:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:48:16:48:16 | Load | test.cpp:44:14:44:21 | Load: * ... | +| test.cpp:51:7:51:14 | VariableAddress indirection | test.cpp:62:39:62:39 | Load | +| test.cpp:51:7:51:14 | VariableAddress indirection | test.cpp:66:39:66:39 | Load | +| test.cpp:51:7:51:14 | VariableAddress indirection | test.cpp:70:38:70:38 | Load | +| test.cpp:51:33:51:35 | Load indirection | test.cpp:60:34:60:37 | mk_array output argument | +| test.cpp:52:19:52:24 | call to malloc | test.cpp:51:7:51:14 | VariableAddress indirection | +| test.cpp:52:19:52:24 | call to malloc | test.cpp:53:12:53:16 | Load | +| test.cpp:53:5:53:23 | Store | test.cpp:51:33:51:35 | Load indirection | +| test.cpp:53:12:53:16 | Load | test.cpp:53:5:53:23 | Store | +| test.cpp:53:12:53:16 | Load | test.cpp:53:5:53:23 | Store | +| test.cpp:53:12:53:16 | Load | test.cpp:53:12:53:23 | ... + ... | +| test.cpp:53:12:53:16 | Load | test.cpp:53:12:53:23 | ... + ... | +| test.cpp:53:12:53:23 | ... + ... | test.cpp:51:33:51:35 | Load indirection | +| test.cpp:60:34:60:37 | mk_array output argument | test.cpp:62:32:62:34 | Load | +| test.cpp:60:34:60:37 | mk_array output argument | test.cpp:66:32:66:34 | Load | +| test.cpp:60:34:60:37 | mk_array output argument | test.cpp:70:31:70:33 | Load | +| test.cpp:62:32:62:34 | Load | test.cpp:67:9:67:14 | Store: ... = ... | +| test.cpp:62:32:62:34 | Load | test.cpp:67:9:67:14 | Store: ... = ... | +| test.cpp:66:32:66:34 | Load | test.cpp:67:9:67:14 | Store: ... = ... | +| test.cpp:66:32:66:34 | Load | test.cpp:67:9:67:14 | Store: ... = ... | +| test.cpp:70:31:70:33 | Load | test.cpp:67:9:67:14 | Store: ... = ... | +| test.cpp:70:31:70:33 | Load | test.cpp:67:9:67:14 | Store: ... = ... | +| test.cpp:80:9:80:16 | VariableAddress indirection [begin] | test.cpp:91:20:91:22 | arr indirection [begin] | +| test.cpp:80:9:80:16 | VariableAddress indirection [begin] | test.cpp:95:20:95:22 | arr indirection [begin] | +| test.cpp:80:9:80:16 | VariableAddress indirection [begin] | test.cpp:99:20:99:22 | arr indirection [begin] | +| test.cpp:80:9:80:16 | VariableAddress indirection [begin] | test.cpp:119:18:119:25 | call to mk_array [begin] | +| test.cpp:80:9:80:16 | VariableAddress indirection [end] | test.cpp:91:36:91:38 | arr indirection [end] | +| test.cpp:80:9:80:16 | VariableAddress indirection [end] | test.cpp:95:36:95:38 | arr indirection [end] | +| test.cpp:80:9:80:16 | VariableAddress indirection [end] | test.cpp:99:35:99:37 | arr indirection [end] | +| test.cpp:80:9:80:16 | VariableAddress indirection [end] | test.cpp:119:18:119:25 | call to mk_array [end] | +| test.cpp:82:5:82:28 | Store | test.cpp:82:9:82:13 | arr indirection [post update] [begin] | +| test.cpp:82:9:82:13 | arr indirection [post update] [begin] | test.cpp:80:9:80:16 | VariableAddress indirection [begin] | +| test.cpp:82:9:82:13 | arr indirection [post update] [begin] | test.cpp:83:15:83:17 | arr indirection [begin] | +| test.cpp:82:17:82:22 | call to malloc | test.cpp:82:5:82:28 | Store | +| test.cpp:83:5:83:30 | Store | test.cpp:83:9:83:11 | arr indirection [post update] [end] | +| test.cpp:83:9:83:11 | arr indirection [post update] [end] | test.cpp:80:9:80:16 | VariableAddress indirection [end] | +| test.cpp:83:15:83:17 | arr indirection [begin] | test.cpp:83:19:83:23 | begin | +| test.cpp:83:15:83:30 | ... + ... | test.cpp:83:5:83:30 | Store | +| test.cpp:83:19:83:23 | Load | test.cpp:83:5:83:30 | Store | +| test.cpp:83:19:83:23 | Load | test.cpp:83:5:83:30 | Store | +| test.cpp:83:19:83:23 | Load | test.cpp:83:15:83:30 | ... + ... | +| test.cpp:83:19:83:23 | Load | test.cpp:83:15:83:30 | ... + ... | +| test.cpp:83:19:83:23 | begin | test.cpp:83:19:83:23 | Load | +| test.cpp:91:20:91:22 | arr indirection [begin] | test.cpp:91:24:91:28 | begin | +| test.cpp:91:20:91:22 | arr indirection [begin] | test.cpp:91:47:91:47 | Load | +| test.cpp:91:24:91:28 | begin | test.cpp:91:47:91:47 | Load | +| test.cpp:91:36:91:38 | arr indirection [end] | test.cpp:91:40:91:42 | end | +| test.cpp:91:40:91:42 | Load | test.cpp:96:9:96:14 | Store: ... = ... | +| test.cpp:91:40:91:42 | Load | test.cpp:96:9:96:14 | Store: ... = ... | +| test.cpp:91:40:91:42 | end | test.cpp:91:40:91:42 | Load | +| test.cpp:95:20:95:22 | arr indirection [begin] | test.cpp:95:24:95:28 | begin | +| test.cpp:95:20:95:22 | arr indirection [begin] | test.cpp:95:47:95:47 | Load | +| test.cpp:95:24:95:28 | begin | test.cpp:95:47:95:47 | Load | +| test.cpp:95:36:95:38 | arr indirection [end] | test.cpp:95:40:95:42 | end | +| test.cpp:95:40:95:42 | Load | test.cpp:96:9:96:14 | Store: ... = ... | +| test.cpp:95:40:95:42 | Load | test.cpp:96:9:96:14 | Store: ... = ... | +| test.cpp:95:40:95:42 | end | test.cpp:95:40:95:42 | Load | +| test.cpp:99:20:99:22 | arr indirection [begin] | test.cpp:99:24:99:28 | begin | +| test.cpp:99:20:99:22 | arr indirection [begin] | test.cpp:99:46:99:46 | Load | +| test.cpp:99:24:99:28 | begin | test.cpp:99:46:99:46 | Load | +| test.cpp:99:35:99:37 | arr indirection [end] | test.cpp:99:39:99:41 | end | +| test.cpp:99:39:99:41 | Load | test.cpp:96:9:96:14 | Store: ... = ... | +| test.cpp:99:39:99:41 | Load | test.cpp:96:9:96:14 | Store: ... = ... | +| test.cpp:99:39:99:41 | end | test.cpp:99:39:99:41 | Load | +| test.cpp:104:27:104:29 | arr [begin] | test.cpp:105:20:105:22 | arr indirection [begin] | +| test.cpp:104:27:104:29 | arr [begin] | test.cpp:109:20:109:22 | arr indirection [begin] | +| test.cpp:104:27:104:29 | arr [begin] | test.cpp:113:20:113:22 | arr indirection [begin] | +| test.cpp:104:27:104:29 | arr [end] | test.cpp:105:36:105:38 | arr indirection [end] | +| test.cpp:104:27:104:29 | arr [end] | test.cpp:109:36:109:38 | arr indirection [end] | +| test.cpp:104:27:104:29 | arr [end] | test.cpp:113:35:113:37 | arr indirection [end] | +| test.cpp:105:20:105:22 | arr indirection [begin] | test.cpp:105:24:105:28 | begin | +| test.cpp:105:20:105:22 | arr indirection [begin] | test.cpp:105:47:105:47 | Load | +| test.cpp:105:24:105:28 | begin | test.cpp:105:47:105:47 | Load | +| test.cpp:105:36:105:38 | arr indirection [end] | test.cpp:105:40:105:42 | end | +| test.cpp:105:40:105:42 | Load | test.cpp:110:9:110:14 | Store: ... = ... | +| test.cpp:105:40:105:42 | Load | test.cpp:110:9:110:14 | Store: ... = ... | +| test.cpp:105:40:105:42 | end | test.cpp:105:40:105:42 | Load | +| test.cpp:109:20:109:22 | arr indirection [begin] | test.cpp:109:24:109:28 | begin | +| test.cpp:109:20:109:22 | arr indirection [begin] | test.cpp:109:47:109:47 | Load | +| test.cpp:109:24:109:28 | begin | test.cpp:109:47:109:47 | Load | +| test.cpp:109:36:109:38 | arr indirection [end] | test.cpp:109:40:109:42 | end | +| test.cpp:109:40:109:42 | Load | test.cpp:110:9:110:14 | Store: ... = ... | +| test.cpp:109:40:109:42 | Load | test.cpp:110:9:110:14 | Store: ... = ... | +| test.cpp:109:40:109:42 | end | test.cpp:109:40:109:42 | Load | +| test.cpp:113:20:113:22 | arr indirection [begin] | test.cpp:113:24:113:28 | begin | +| test.cpp:113:20:113:22 | arr indirection [begin] | test.cpp:113:46:113:46 | Load | +| test.cpp:113:24:113:28 | begin | test.cpp:113:46:113:46 | Load | +| test.cpp:113:35:113:37 | arr indirection [end] | test.cpp:113:39:113:41 | end | +| test.cpp:113:39:113:41 | Load | test.cpp:110:9:110:14 | Store: ... = ... | +| test.cpp:113:39:113:41 | Load | test.cpp:110:9:110:14 | Store: ... = ... | +| test.cpp:113:39:113:41 | end | test.cpp:113:39:113:41 | Load | +| test.cpp:119:18:119:25 | call to mk_array [begin] | test.cpp:104:27:104:29 | arr [begin] | +| test.cpp:119:18:119:25 | call to mk_array [end] | test.cpp:104:27:104:29 | arr [end] | +| test.cpp:124:15:124:20 | call to malloc | test.cpp:125:5:125:17 | Store | +| test.cpp:124:15:124:20 | call to malloc | test.cpp:126:15:126:15 | Load | +| test.cpp:125:5:125:17 | Store | test.cpp:125:9:125:13 | arr indirection [post update] [begin] | +| test.cpp:125:9:125:13 | arr indirection [post update] [begin] | test.cpp:129:11:129:13 | arr indirection [begin] | +| test.cpp:125:9:125:13 | arr indirection [post update] [begin] | test.cpp:133:11:133:13 | arr indirection [begin] | +| test.cpp:125:9:125:13 | arr indirection [post update] [begin] | test.cpp:137:11:137:13 | arr indirection [begin] | +| test.cpp:129:11:129:13 | arr indirection [begin] | test.cpp:129:15:129:19 | begin | +| test.cpp:129:15:129:19 | begin | test.cpp:129:15:129:19 | Load | +| test.cpp:133:11:133:13 | arr indirection [begin] | test.cpp:133:15:133:19 | begin | +| test.cpp:133:15:133:19 | begin | test.cpp:133:15:133:19 | Load | +| test.cpp:137:11:137:13 | arr indirection [begin] | test.cpp:137:15:137:19 | begin | +| test.cpp:137:15:137:19 | begin | test.cpp:137:15:137:19 | Load | +| test.cpp:141:10:141:19 | VariableAddress indirection [begin] | test.cpp:150:20:150:29 | Call indirection [begin] | +| test.cpp:141:10:141:19 | VariableAddress indirection [begin] | test.cpp:180:19:180:28 | call to mk_array_p indirection [begin] | +| test.cpp:141:10:141:19 | VariableAddress indirection [end] | test.cpp:150:20:150:29 | Call indirection [end] | +| test.cpp:141:10:141:19 | VariableAddress indirection [end] | test.cpp:180:19:180:28 | call to mk_array_p indirection [end] | +| test.cpp:143:5:143:29 | Store | test.cpp:143:10:143:14 | Load indirection [post update] [begin] | +| test.cpp:143:10:143:14 | Load indirection [post update] [begin] | test.cpp:141:10:141:19 | VariableAddress indirection [begin] | +| test.cpp:143:10:143:14 | Load indirection [post update] [begin] | test.cpp:144:16:144:18 | Load indirection [begin] | +| test.cpp:143:18:143:23 | call to malloc | test.cpp:143:5:143:29 | Store | +| test.cpp:144:5:144:32 | Store | test.cpp:144:10:144:12 | Load indirection [post update] [end] | +| test.cpp:144:10:144:12 | Load indirection [post update] [end] | test.cpp:141:10:141:19 | VariableAddress indirection [end] | +| test.cpp:144:16:144:18 | Load indirection [begin] | test.cpp:144:21:144:25 | begin | +| test.cpp:144:16:144:32 | ... + ... | test.cpp:144:5:144:32 | Store | +| test.cpp:144:21:144:25 | Load | test.cpp:144:5:144:32 | Store | +| test.cpp:144:21:144:25 | Load | test.cpp:144:5:144:32 | Store | +| test.cpp:144:21:144:25 | Load | test.cpp:144:16:144:32 | ... + ... | +| test.cpp:144:21:144:25 | Load | test.cpp:144:16:144:32 | ... + ... | +| test.cpp:144:21:144:25 | begin | test.cpp:144:21:144:25 | Load | +| test.cpp:150:20:150:29 | Call indirection [begin] | test.cpp:152:20:152:22 | Load indirection [begin] | +| test.cpp:150:20:150:29 | Call indirection [begin] | test.cpp:156:20:156:22 | Load indirection [begin] | +| test.cpp:150:20:150:29 | Call indirection [begin] | test.cpp:160:20:160:22 | Load indirection [begin] | +| test.cpp:150:20:150:29 | Call indirection [end] | test.cpp:156:37:156:39 | Load indirection [end] | +| test.cpp:152:20:152:22 | Load indirection [begin] | test.cpp:152:25:152:29 | begin | +| test.cpp:152:20:152:22 | Load indirection [begin] | test.cpp:152:49:152:49 | Load | +| test.cpp:152:25:152:29 | begin | test.cpp:152:49:152:49 | Load | +| test.cpp:156:20:156:22 | Load indirection [begin] | test.cpp:156:25:156:29 | begin | +| test.cpp:156:20:156:22 | Load indirection [begin] | test.cpp:156:49:156:49 | Load | +| test.cpp:156:25:156:29 | begin | test.cpp:156:49:156:49 | Load | +| test.cpp:156:37:156:39 | Load indirection [end] | test.cpp:156:42:156:44 | end | +| test.cpp:156:42:156:44 | Load | test.cpp:157:9:157:14 | Store: ... = ... | +| test.cpp:156:42:156:44 | Load | test.cpp:157:9:157:14 | Store: ... = ... | +| test.cpp:156:42:156:44 | end | test.cpp:156:42:156:44 | Load | +| test.cpp:160:20:160:22 | Load indirection [begin] | test.cpp:160:25:160:29 | begin | +| test.cpp:160:20:160:22 | Load indirection [begin] | test.cpp:160:48:160:48 | Load | +| test.cpp:160:25:160:29 | begin | test.cpp:160:48:160:48 | Load | +| test.cpp:165:29:165:31 | arr indirection [begin] | test.cpp:166:20:166:22 | Load indirection [begin] | +| test.cpp:165:29:165:31 | arr indirection [begin] | test.cpp:170:20:170:22 | Load indirection [begin] | +| test.cpp:165:29:165:31 | arr indirection [begin] | test.cpp:174:20:174:22 | Load indirection [begin] | +| test.cpp:165:29:165:31 | arr indirection [end] | test.cpp:166:37:166:39 | Load indirection [end] | +| test.cpp:165:29:165:31 | arr indirection [end] | test.cpp:170:37:170:39 | Load indirection [end] | +| test.cpp:165:29:165:31 | arr indirection [end] | test.cpp:174:36:174:38 | Load indirection [end] | +| test.cpp:166:20:166:22 | Load indirection [begin] | test.cpp:166:25:166:29 | begin | +| test.cpp:166:20:166:22 | Load indirection [begin] | test.cpp:166:49:166:49 | Load | +| test.cpp:166:25:166:29 | begin | test.cpp:166:49:166:49 | Load | +| test.cpp:166:37:166:39 | Load indirection [end] | test.cpp:166:42:166:44 | end | +| test.cpp:166:42:166:44 | Load | test.cpp:171:9:171:14 | Store: ... = ... | +| test.cpp:166:42:166:44 | Load | test.cpp:171:9:171:14 | Store: ... = ... | +| test.cpp:166:42:166:44 | end | test.cpp:166:42:166:44 | Load | +| test.cpp:170:20:170:22 | Load indirection [begin] | test.cpp:170:25:170:29 | begin | +| test.cpp:170:20:170:22 | Load indirection [begin] | test.cpp:170:49:170:49 | Load | +| test.cpp:170:25:170:29 | begin | test.cpp:170:49:170:49 | Load | +| test.cpp:170:37:170:39 | Load indirection [end] | test.cpp:170:42:170:44 | end | +| test.cpp:170:42:170:44 | Load | test.cpp:171:9:171:14 | Store: ... = ... | +| test.cpp:170:42:170:44 | Load | test.cpp:171:9:171:14 | Store: ... = ... | +| test.cpp:170:42:170:44 | end | test.cpp:170:42:170:44 | Load | +| test.cpp:174:20:174:22 | Load indirection [begin] | test.cpp:174:25:174:29 | begin | +| test.cpp:174:20:174:22 | Load indirection [begin] | test.cpp:174:48:174:48 | Load | +| test.cpp:174:25:174:29 | begin | test.cpp:174:48:174:48 | Load | +| test.cpp:174:36:174:38 | Load indirection [end] | test.cpp:174:41:174:43 | end | +| test.cpp:174:41:174:43 | Load | test.cpp:171:9:171:14 | Store: ... = ... | +| test.cpp:174:41:174:43 | Load | test.cpp:171:9:171:14 | Store: ... = ... | +| test.cpp:174:41:174:43 | end | test.cpp:174:41:174:43 | Load | +| test.cpp:180:19:180:28 | call to mk_array_p indirection [begin] | test.cpp:165:29:165:31 | arr indirection [begin] | +| test.cpp:180:19:180:28 | call to mk_array_p indirection [end] | test.cpp:165:29:165:31 | arr indirection [end] | +| test.cpp:188:15:188:20 | call to malloc | test.cpp:189:15:189:15 | Load | +#select +| test.cpp:6:14:6:15 | Load: * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:6:14:6:15 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size | +| test.cpp:8:14:8:21 | Load: * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:8:14:8:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size | +| test.cpp:8:14:8:21 | Load: * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:8:14:8:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size | +| test.cpp:20:14:20:21 | Load: * ... | test.cpp:16:15:16:20 | call to malloc | test.cpp:20:14:20:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:16:15:16:20 | call to malloc | call to malloc | test.cpp:17:19:17:22 | size | size | +| test.cpp:30:14:30:15 | Load: * ... | test.cpp:28:15:28:20 | call to malloc | test.cpp:30:14:30:15 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:28:15:28:20 | call to malloc | call to malloc | test.cpp:29:20:29:27 | ... + ... | ... + ... | +| test.cpp:32:14:32:21 | Load: * ... | test.cpp:28:15:28:20 | call to malloc | test.cpp:32:14:32:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:28:15:28:20 | call to malloc | call to malloc | test.cpp:29:20:29:27 | ... + ... | ... + ... | +| test.cpp:32:14:32:21 | Load: * ... | test.cpp:28:15:28:20 | call to malloc | test.cpp:32:14:32:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:28:15:28:20 | call to malloc | call to malloc | test.cpp:29:20:29:27 | ... + ... | ... + ... | +| test.cpp:42:14:42:15 | Load: * ... | test.cpp:40:15:40:20 | call to malloc | test.cpp:42:14:42:15 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:40:15:40:20 | call to malloc | call to malloc | test.cpp:41:20:41:27 | ... - ... | ... - ... | +| test.cpp:44:14:44:21 | Load: * ... | test.cpp:40:15:40:20 | call to malloc | test.cpp:44:14:44:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:40:15:40:20 | call to malloc | call to malloc | test.cpp:41:20:41:27 | ... - ... | ... - ... | +| test.cpp:44:14:44:21 | Load: * ... | test.cpp:40:15:40:20 | call to malloc | test.cpp:44:14:44:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:40:15:40:20 | call to malloc | call to malloc | test.cpp:41:20:41:27 | ... - ... | ... - ... | +| test.cpp:67:9:67:14 | Store: ... = ... | test.cpp:52:19:52:24 | call to malloc | test.cpp:67:9:67:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:52:19:52:24 | call to malloc | call to malloc | test.cpp:53:20:53:23 | size | size | +| test.cpp:96:9:96:14 | Store: ... = ... | test.cpp:82:17:82:22 | call to malloc | test.cpp:96:9:96:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:82:17:82:22 | call to malloc | call to malloc | test.cpp:83:27:83:30 | size | size | +| test.cpp:110:9:110:14 | Store: ... = ... | test.cpp:82:17:82:22 | call to malloc | test.cpp:110:9:110:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:82:17:82:22 | call to malloc | call to malloc | test.cpp:83:27:83:30 | size | size | +| test.cpp:157:9:157:14 | Store: ... = ... | test.cpp:143:18:143:23 | call to malloc | test.cpp:157:9:157:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:143:18:143:23 | call to malloc | call to malloc | test.cpp:144:29:144:32 | size | size | +| test.cpp:171:9:171:14 | Store: ... = ... | test.cpp:143:18:143:23 | call to malloc | test.cpp:171:9:171:14 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:143:18:143:23 | call to malloc | call to malloc | test.cpp:144:29:144:32 | size | size | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.qlref new file mode 100644 index 00000000000..76da29dc7a0 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/InvalidPointerDeref.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-193/InvalidPointerDeref.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp new file mode 100644 index 00000000000..809c348c0b0 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-193/pointer-deref/test.cpp @@ -0,0 +1,191 @@ +char *malloc(int size); + +void test1(int size) { + char* p = malloc(size); + char* q = p + size; + char a = *q; // BAD + char b = *(q - 1); // GOOD + char c = *(q + 1); // BAD + char d = *(q + size); // BAD [NOT DETECTED] + char e = *(q - size); // GOOD + char f = *(q + size + 1); // BAD [NOT DETECTED] + char g = *(q - size - 1); // GOOD +} + +void test2(int size) { + char* p = malloc(size); + char* q = p + size - 1; + char a = *q; // GOOD + char b = *(q - 1); // GOOD + char c = *(q + 1); // BAD + char d = *(q + size); // BAD [NOT DETECTED] + char e = *(q - size); // GOOD + char f = *(q + size + 1); // BAD [NOT DETECTED] + char g = *(q - size - 1); // GOOD +} + +void test3(int size) { + char* p = malloc(size + 1); + char* q = p + (size + 1); + char a = *q; // BAD + char b = *(q - 1); // GOOD + char c = *(q + 1); // BAD + char d = *(q + size); // BAD [NOT DETECTED] + char e = *(q - size); // GOOD + char f = *(q + size + 1); // BAD [NOT DETECTED] + char g = *(q - size - 1); // GOOD +} + +void test4(int size) { + char* p = malloc(size - 1); + char* q = p + (size - 1); + char a = *q; // BAD + char b = *(q - 1); // GOOD + char c = *(q + 1); // BAD + char d = *(q + size); // BAD [NOT DETECTED] + char e = *(q - size); // GOOD + char f = *(q + size + 1); // BAD [NOT DETECTED] + char g = *(q - size - 1); // GOOD +} + +char* mk_array(int size, char** end) { + char* begin = malloc(size); + *end = begin + size; + + return begin; +} + +void test5(int size) { + char* end; + char* begin = mk_array(size, &end); + + for (char* p = begin; p != end; ++p) { + *p = 0; // GOOD + } + + for (char* p = begin; p <= end; ++p) { + *p = 0; // BAD + } + + for (char* p = begin; p < end; ++p) { + *p = 0; // GOOD + } +} + +struct array_t { + char* begin; + char* end; +}; + +array_t mk_array(int size) { + array_t arr; + arr.begin = malloc(size); + arr.end = arr.begin + size; + + return arr; +} + +void test6(int size) { + array_t arr = mk_array(size); + + for (char* p = arr.begin; p != arr.end; ++p) { + *p = 0; // GOOD + } + + for (char* p = arr.begin; p <= arr.end; ++p) { + *p = 0; // BAD + } + + for (char* p = arr.begin; p < arr.end; ++p) { + *p = 0; // GOOD + } +} + +void test7_callee(array_t arr) { + for (char* p = arr.begin; p != arr.end; ++p) { + *p = 0; // GOOD + } + + for (char* p = arr.begin; p <= arr.end; ++p) { + *p = 0; // BAD + } + + for (char* p = arr.begin; p < arr.end; ++p) { + *p = 0; // GOOD + } +} + +void test7(int size) { + test7_callee(mk_array(size)); +} + +void test8(int size) { + array_t arr; + char* p = malloc(size); + arr.begin = p; + arr.end = p + size; + + for (int i = 0; i < arr.end - arr.begin; i++) { + *(arr.begin + i) = 0; // GOOD + } + + for (int i = 0; i != arr.end - arr.begin; i++) { + *(arr.begin + i) = 0; // GOOD + } + + for (int i = 0; i <= arr.end - arr.begin; i++) { + *(arr.begin + i) = 0; // BAD [NOT DETECTED] + } +} + +array_t *mk_array_p(int size) { + array_t *arr = (array_t*) malloc(sizeof(array_t)); + arr->begin = malloc(size); + arr->end = arr->begin + size; + + return arr; +} + +void test9(int size) { + array_t *arr = mk_array_p(size); + + for (char* p = arr->begin; p != arr->end; ++p) { + *p = 0; // GOOD + } + + for (char* p = arr->begin; p <= arr->end; ++p) { + *p = 0; // BAD + } + + for (char* p = arr->begin; p < arr->end; ++p) { + *p = 0; // GOOD + } +} + +void test10_callee(array_t *arr) { + for (char* p = arr->begin; p != arr->end; ++p) { + *p = 0; // GOOD + } + + for (char* p = arr->begin; p <= arr->end; ++p) { + *p = 0; // BAD + } + + for (char* p = arr->begin; p < arr->end; ++p) { + *p = 0; // GOOD + } +} + +void test10(int size) { + test10_callee(mk_array_p(size)); +} + +void deref_plus_one(char* q) { + char a = *(q + 1); // BAD [NOT DETECTED] +} + +void test11(unsigned size) { + char *p = malloc(size); + char *q = p + size - 1; + deref_plus_one(q); +} \ No newline at end of file diff --git a/docs/query-metadata-style-guide.md b/docs/query-metadata-style-guide.md index 5660a08f72e..9b4b291a75a 100644 --- a/docs/query-metadata-style-guide.md +++ b/docs/query-metadata-style-guide.md @@ -179,7 +179,15 @@ The select clause of each alert query defines the alert message that is displaye * The message should factually describe the problem that is being highlighted–it should not contain recommendations about how to fix the problem or value judgements. * Program element references should be in 'single quotes' to distinguish them from ordinary words. Quotes are not needed around substitutions (`$@`). * Avoid constant alert message strings and include some context, if possible. For example, `The class 'Foo' is duplicated as 'Bar'.` is preferable to `This class is duplicated here.` +* If a reference to the current location can't be avoided use "this location" instead of "here". For example, `Bad thing at this location.` is preferable to `Bad thing here.`. This avoids the "click here" anti-pattern. +* For path queries, if possible, try to follow the template: `This path depends on a [user-provided value].`, or alternatively (if the first option doesn't work) `[User-provided value] flows to this location and is used in a path.`. +* Taint tracking queries generally have a sink that "depends on" the source, and dataflow queries generally have a source that "flows to" the sink. + +### Links in alert messages + * Where you reference another program element, link to it if possible using a substitution (`$@`). Links should be used inline in the sentence, rather than as parenthesised lists or appositions. +* Avoid using link texts that don't describe what they link to. For example, rewrite `This sensitive data is written to a logfile unescaped [here]` to `This sensitive data is [written to a logfile unescaped]`. +* Make link text as concise and precise as possible. For example, avoid starting a link text with an indefinite article (a, an). `Path construction depends on a [user-provided value]` is preferable to `Path construction depends on [a user-provided value]`. (Where the square brackets indicate a link.) See [the W3C guide on link texts](https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html) for further information. * When a message contains multiple links, construct a sentence that has the most variable link (that is, the link with most targets) last. For further information, see [Defining the results of a query](https://codeql.github.com/docs/writing-codeql-queries/defining-the-results-of-a-query/). For examples of select clauses and alert messages, see the query source files at the following pages: diff --git a/go/ql/lib/semmle/go/security/InsecureRandomness.qll b/go/ql/lib/semmle/go/security/InsecureRandomness.qll index f2db5860073..6de3071b598 100644 --- a/go/ql/lib/semmle/go/security/InsecureRandomness.qll +++ b/go/ql/lib/semmle/go/security/InsecureRandomness.qll @@ -29,5 +29,7 @@ module InsecureRandomness { /** Holds if `sink` is a sink for this configuration with kind `kind`. */ predicate isSink(Sink sink, string kind) { kind = sink.getKind() } + + override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } } } diff --git a/go/ql/lib/semmle/go/security/Xss.qll b/go/ql/lib/semmle/go/security/Xss.qll index e9301646f77..98b6da02fe8 100644 --- a/go/ql/lib/semmle/go/security/Xss.qll +++ b/go/ql/lib/semmle/go/security/Xss.qll @@ -6,8 +6,11 @@ import go /** Provides classes and predicates shared between the XSS queries. */ module SharedXss { - /** A data flow source for XSS vulnerabilities. */ - abstract class Source extends DataFlow::Node { } + /** + * DEPRECATED: This class is not used. + * A data flow source for XSS vulnerabilities. + */ + abstract deprecated class Source extends DataFlow::Node { } /** A data flow sink for XSS vulnerabilities. */ abstract class Sink extends DataFlow::Node { diff --git a/java/ql/lib/change-notes/2022-04-01-annotation-deprecations.md b/java/ql/lib/change-notes/2022-04-01-annotation-deprecations.md new file mode 100644 index 00000000000..8c8046670a1 --- /dev/null +++ b/java/ql/lib/change-notes/2022-04-01-annotation-deprecations.md @@ -0,0 +1,7 @@ +--- +category: deprecated +--- +* The predicate `Annotation.getAValue()` has been deprecated because it might lead to obtaining the value of the wrong annotation element by accident. `getValue(string)` (or one of the value type specific predicates) should be used to explicitly specify the name of the annotation element. +* The predicate `Annotation.getAValue(string)` has been renamed to `getAnArrayValue(string)`. +* The predicate `SuppressWarningsAnnotation.getASuppressedWarningLiteral()` has been deprecated because it unnecessarily restricts the result type; `getASuppressedWarning()` should be used instead. +* The predicates `TargetAnnotation.getATargetExpression()` and `RetentionAnnotation.getRetentionPolicyExpression()` have been deprecated because getting the enum constant read expression is rarely useful, instead the corresponding predicates for getting the name of the referenced enum constants should be used. diff --git a/java/ql/lib/change-notes/2022-04-01-annotation-features.md b/java/ql/lib/change-notes/2022-04-01-annotation-features.md new file mode 100644 index 00000000000..3a2d6e2561c --- /dev/null +++ b/java/ql/lib/change-notes/2022-04-01-annotation-features.md @@ -0,0 +1,9 @@ +--- +category: feature +--- +* The predicates of the CodeQL class `Annotation` have been improved: + * Convenience value type specific predicates have been added, such as `getEnumConstantValue(string)` or `getStringValue(string)`. + * Convenience predicates for elements with array values have been added, such as `getAnEnumConstantArrayValue(string)`. While the behavior of the existing predicates has not changed, usage of them should be reviewed (or replaced with the newly added predicate) to make sure they work correctly for elements with array values. + * Some internal CodeQL usage of the `Annotation` predicates has been adjusted and corrected; this might affect the results of some queries. +* New predicates have been added to the CodeQL class `Annotatable` to support getting declared and associated annotations. As part of that, `hasAnnotation()` has been changed to also consider inherited annotations, to be consistent with `hasAnnotation(string, string)` and `getAnAnnotation()`. The newly added predicate `hasDeclaredAnnotation()` can be used as replacement for the old functionality. +* New predicates have been added to the CodeQL class `AnnotationType` to simplify getting information about usage of JDK meta-annotations, such as `@Retention`. diff --git a/java/ql/lib/semmle/code/Location.qll b/java/ql/lib/semmle/code/Location.qll index c906b9f2407..256b831a8ad 100644 --- a/java/ql/lib/semmle/code/Location.qll +++ b/java/ql/lib/semmle/code/Location.qll @@ -216,7 +216,7 @@ private predicate fixedHasLocation(Top l, Location loc, File f) { min(Location candidateLoc | hasLocation(l, candidateLoc) | - candidateLoc order by candidateLoc.getFile().toString() + candidateLoc order by candidateLoc.getFile().getAbsolutePath() ) and not hasSourceLocation(l, _, _) and locations_default(loc, f, _, _, _, _) diff --git a/java/ql/lib/semmle/code/java/Annotation.qll b/java/ql/lib/semmle/code/java/Annotation.qll index 0b83d3b435e..28f994053a2 100644 --- a/java/ql/lib/semmle/code/java/Annotation.qll +++ b/java/ql/lib/semmle/code/java/Annotation.qll @@ -44,12 +44,100 @@ class Annotation extends @annotation, Expr { result = this.getType().getAnnotationElement(name) } - /** Gets a value of an annotation element. */ - Expr getAValue() { filteredAnnotValue(this, _, result) } + /** + * DEPRECATED: Getting the value of _any_ annotation element is error-prone because + * it could lead to selecting the value of the wrong element by accident (for example + * when an annotation type is extended in the future). Prefer the predicate `getValue(string)` + * and explicitly specify the element name. Use `getValue(_)` if it is really desired to + * get the value of any element. + * + * Gets a value of an annotation element. This includes default values in case + * no explicit value is specified. For elements with an array value type this + * might have an `ArrayInit` as result. To properly handle array values, prefer + * the predicate `getAnArrayValue`. + */ + deprecated Expr getAValue() { filteredAnnotValue(this, _, result) } - /** Gets the value of the annotation element with the specified `name`. */ + /** + * Gets the value of the annotation element with the specified `name`. + * This includes default values in case no explicit value is specified. + * For elements with an array value type this might get an `ArrayInit` instance. + * To properly handle array values, prefer the predicate `getAnArrayValue`. + */ Expr getValue(string name) { filteredAnnotValue(this, this.getAnnotationElement(name), result) } + /** + * Gets the value of the annotation element, if its type is not an array. + * This guarantees that for consistency even elements of type array with a + * single value have no result, to prevent accidental error-prone usage. + */ + private Expr getNonArrayValue(string name) { + result = this.getValue(name) and + not this.getAnnotationElement(name).getType() instanceof Array + } + + /** + * If the value type of the annotation element with the specified `name` is an enum type, + * gets the enum constant used as value for that element. This includes default values in + * case no explicit value is specified. + * + * If the element value type is an enum type array, use `getAnEnumConstantArrayValue`. + */ + EnumConstant getEnumConstantValue(string name) { + result = this.getNonArrayValue(name).(FieldRead).getField() + } + + /** + * If the value type of the annotation element with the specified `name` is `String`, + * gets the string value used for that element. This includes default values in case no + * explicit value is specified. + * + * If the element value type is a string array, use `getAStringArrayValue`. + */ + string getStringValue(string name) { + // Uses CompileTimeConstantExpr instead of StringLiteral because this can for example + // be a read from a final variable as well. + result = this.getNonArrayValue(name).(CompileTimeConstantExpr).getStringValue() + } + + /** + * If the value type of the annotation element with the specified `name` is `int` or + * a smaller integral type or `char`, gets the int value used for that element. + * This includes default values in case no explicit value is specified. + * + * If the element value type is an `int` array or an array of a smaller integral + * type or `char`, use `getAnIntArrayValue`. + */ + int getIntValue(string name) { + // Uses CompileTimeConstantExpr instead of IntegerLiteral because this can for example + // be a read from a final variable as well. + result = this.getNonArrayValue(name).(CompileTimeConstantExpr).getIntValue() and + // Verify that type is integral; ignore floating point elements with IntegerLiteral as value + this.getAnnotationElement(name).getType().hasName(["byte", "short", "int", "char"]) + } + + /** + * If the value type of the annotation element with the specified `name` is `boolean`, + * gets the boolean value used for that element. This includes default values in case + * no explicit value is specified. + */ + boolean getBooleanValue(string name) { + // Uses CompileTimeConstantExpr instead of BooleanLiteral because this can for example + // be a read from a final variable as well. + result = this.getNonArrayValue(name).(CompileTimeConstantExpr).getBooleanValue() + } + + /** + * If the value type of the annotation element with the specified `name` is `java.lang.Class`, + * gets the type referred to by that `Class`. This includes default values in case no explicit + * value is specified. + * + * If the element value type is a `Class` array, use `getATypeArrayValue`. + */ + Type getTypeValue(string name) { + result = this.getNonArrayValue(name).(TypeLiteral).getReferencedType() + } + /** Gets the element being annotated. */ Element getTarget() { result = this.getAnnotatedElement() } @@ -60,16 +148,83 @@ class Annotation extends @annotation, Expr { /** * Gets a value of the annotation element with the specified `name`, which must be declared as an array - * type. + * type. This includes default values in case no explicit value is specified. * - * If the annotation element is defined with an array initializer, then the returned value will - * be one of the elements of that array. Otherwise, the returned value will be the single - * expression defined for the value. + * If the annotation element is defined with an array initializer, then the result will be one of the + * elements of that array. Otherwise, the result will be the single expression used as value. */ - Expr getAValue(string name) { + Expr getAnArrayValue(string name) { result = this.getArrayValue(name, _) } + + /** + * DEPRECATED: Predicate has been renamed to `getAnArrayValue` + */ + deprecated Expr getAValue(string name) { result = this.getAnArrayValue(name) } + + /** + * Gets a value of the annotation element with the specified `name`, which must be declared as an enum + * type array. This includes default values in case no explicit value is specified. + * + * If the annotation element is defined with an array initializer, then the result will be one of the + * elements of that array. Otherwise, the result will be the single expression used as value. + */ + EnumConstant getAnEnumConstantArrayValue(string name) { + result = this.getAnArrayValue(name).(FieldRead).getField() + } + + /** + * Gets a value of the annotation element with the specified `name`, which must be declared as a string + * array. This includes default values in case no explicit value is specified. + * + * If the annotation element is defined with an array initializer, then the result will be one of the + * elements of that array. Otherwise, the result will be the single expression used as value. + */ + string getAStringArrayValue(string name) { + result = this.getAnArrayValue(name).(CompileTimeConstantExpr).getStringValue() + } + + /** + * Gets a value of the annotation element with the specified `name`, which must be declared as an `int` + * array or an array of a smaller integral type or `char`. This includes default values in case no + * explicit value is specified. + * + * If the annotation element is defined with an array initializer, then the result will be one of the + * elements of that array. Otherwise, the result will be the single expression used as value. + */ + int getAnIntArrayValue(string name) { + result = this.getAnArrayValue(name).(CompileTimeConstantExpr).getIntValue() and + // Verify that type is integral; ignore floating point elements with IntegerLiteral as value + this.getAnnotationElement(name).getType().hasName(["byte[]", "short[]", "int[]", "char[]"]) + } + + /** + * Gets a value of the annotation element with the specified `name`, which must be declared as a `Class` + * array. This includes default values in case no explicit value is specified. + * + * If the annotation element is defined with an array initializer, then the result will be one of the + * elements of that array. Otherwise, the result will be the single expression used as value. + */ + Type getATypeArrayValue(string name) { + result = this.getAnArrayValue(name).(TypeLiteral).getReferencedType() + } + + /** + * Gets the value at a given index of the annotation element with the specified `name`, which must be + * declared as an array type. This includes default values in case no explicit value is specified. + * + * If the annotation element is defined with an array initializer, then the result will be the element + * at the given index of that array, starting at 0. Otherwise, the result will be the single expression + * defined for the value and the `index` will be 0. + */ + Expr getArrayValue(string name, int index) { this.getType().getAnnotationElement(name).getType() instanceof Array and exists(Expr value | value = this.getValue(name) | - if value instanceof ArrayInit then result = value.(ArrayInit).getAnInit() else result = value + if value instanceof ArrayInit + then + // TODO: Currently reports incorrect index values in some cases, see https://github.com/github/codeql/issues/8645 + result = value.(ArrayInit).getInit(index) + else ( + index = 0 and result = value + ) ) } @@ -99,19 +254,86 @@ private predicate sourceAnnotValue(Annotation a, Method m, Expr val) { /** An abstract representation of language elements that can be annotated. */ class Annotatable extends Element { - /** Holds if this element has an annotation. */ - predicate hasAnnotation() { exists(Annotation a | a.getAnnotatedElement() = this) } + /** + * Holds if this element has an annotation, including inherited annotations. + * The retention policy of the annotation type is not considered. + */ + predicate hasAnnotation() { exists(this.getAnAnnotation()) } - /** Holds if this element has the specified annotation. */ + /** + * Holds if this element has a declared annotation, excluding inherited annotations. + * The retention policy of the annotation type is not considered. + */ + predicate hasDeclaredAnnotation() { exists(this.getADeclaredAnnotation()) } + + /** + * Holds if this element has the specified annotation, including inherited + * annotations. The retention policy of the annotation type is not considered. + */ predicate hasAnnotation(string package, string name) { exists(AnnotationType at | at = this.getAnAnnotation().getType() | at.nestedName() = name and at.getPackage().getName() = package ) } - /** Gets an annotation that applies to this element. */ + /** + * Gets an annotation that applies to this element, including inherited annotations. + * The results only include _direct_ annotations; _indirect_ annotations, that is + * repeated annotations in an (implicit) container annotation, are not included. + * The retention policy of the annotation type is not considered. + */ cached - Annotation getAnAnnotation() { result.getAnnotatedElement() = this } + Annotation getAnAnnotation() { + // This predicate is overridden by Class to consider inherited annotations + result = this.getADeclaredAnnotation() + } + + /** + * Gets an annotation that is declared on this element, excluding inherited annotations. + * The retention policy of the annotation type is not considered. + */ + Annotation getADeclaredAnnotation() { result.getAnnotatedElement() = this } + + /** Gets an _indirect_ (= repeated) annotation. */ + private Annotation getAnIndirectAnnotation() { + // 'indirect' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html + exists(AnnotationType t, Annotation containerAnn | + t = result.getType() and + containerAnn = this.getADeclaredAnnotation() and + containerAnn.getType() = t.getContainingAnnotationType() + | + result = containerAnn.getAnArrayValue("value") + ) + } + + private Annotation getADeclaredAssociatedAnnotation(AnnotationType t) { + // Direct or indirect annotation + result.getType() = t and + result = [this.getADeclaredAnnotation(), this.getAnIndirectAnnotation()] + } + + private Annotation getAnAssociatedAnnotation(AnnotationType t) { + // 'associated' as defined by https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/AnnotatedElement.html + if exists(this.getADeclaredAssociatedAnnotation(t)) + then result = this.getADeclaredAssociatedAnnotation(t) + else ( + // Only if neither a direct nor an indirect annotation is present look for an inherited one + t.isInherited() and + // @Inherited only works for classes; cast to Annotatable is necessary because predicate is private + result = this.(Class).getASupertype().(Class).(Annotatable).getAnAssociatedAnnotation(t) + ) + } + + /** + * Gets an annotation _associated_ with this element, that is: + * - An annotation directly present on this element, or + * - An annotation indirectly present on this element (in the form of a repeated annotation), or + * - If an annotation of a type is neither directly nor indirectly present + * the result is an associated inherited annotation (recursively) + * + * The retention policy of the annotation type is not considered. + */ + Annotation getAnAssociatedAnnotation() { result = this.getAnAssociatedAnnotation(_) } /** * Holds if this or any enclosing `Annotatable` has a `@SuppressWarnings("")` @@ -128,6 +350,11 @@ class Annotatable extends Element { or this.(NestedClass).getEnclosingType().suppressesWarningsAbout(category) or + this.(LocalClassOrInterface) + .getLocalTypeDeclStmt() + .getEnclosingCallable() + .suppressesWarningsAbout(category) + or this.(LocalVariableDecl).getCallable().suppressesWarningsAbout(category) } } @@ -146,10 +373,79 @@ class AnnotationType extends Interface { /** Holds if this annotation type is annotated with the meta-annotation `@Inherited`. */ predicate isInherited() { - exists(Annotation ann | - ann.getAnnotatedElement() = this and - ann.getType().hasQualifiedName("java.lang.annotation", "Inherited") - ) + this.getADeclaredAnnotation().getType().hasQualifiedName("java.lang.annotation", "Inherited") + } + + /** Holds if this annotation type is annotated with the meta-annotation `@Documented`. */ + predicate isDocumented() { + this.getADeclaredAnnotation().getType().hasQualifiedName("java.lang.annotation", "Documented") + } + + /** + * Gets the retention policy of this annotation type, that is, the name of one of the + * enum constants of `java.lang.annotation.RetentionPolicy`. If this annotation type + * has no `@Retention` annotation, the result is `CLASS`. + */ + string getRetentionPolicy() { + if this.getADeclaredAnnotation() instanceof RetentionAnnotation + then result = this.getADeclaredAnnotation().(RetentionAnnotation).getRetentionPolicy() + else + // If not explicitly specified retention is CLASS + result = "CLASS" + } + + /** + * Holds if the element type is a possible target for this annotation type. + * The `elementType` is the name of one of the `java.lang.annotation.ElementType` + * enum constants. + * + * If this annotation type has no `@Target` annotation, it is considered to be applicable + * in all declaration contexts. This matches the behavior of the latest Java versions + * but differs from the behavior of older Java versions. This predicate must only be + * called with names of `ElementType` enum constants; for other values it might hold + * erroneously. + */ + bindingset[elementType] + predicate isATargetType(string elementType) { + /* + * Note: Cannot use a predicate with string as result because annotation type without + * explicit @Target can be applied in all declaration contexts, requiring to hardcode + * element types here; then the results could become outdated if this predicate is not + * updated for future JDK versions, or it could have irritating results, e.g. RECORD_COMPONENT + * for a database created for Java 8. + * + * Could in theory read java.lang.annotation.ElementType constants from database, but might + * be brittle in case ElementType is not present in the database for whatever reason. + */ + + if this.getADeclaredAnnotation() instanceof TargetAnnotation + then elementType = this.getADeclaredAnnotation().(TargetAnnotation).getATargetElementType() + else + /* + * Behavior for missing @Target annotation changed between Java versions. In older Java + * versions it allowed usage in most (but not all) declaration contexts. Then for Java 14 + * JDK-8231435 changed it to allow usage in all declaration and type contexts. In Java 17 + * it was changed by JDK-8261610 to only allow usage in all declaration contexts, but not + * in type contexts anymore. However, during these changes javac did not always comply with + * the specification, see for example JDK-8254023. + * + * For simplicity pretend the latest behavior defined by the JLS applied in all versions; + * that means any declaration context is allowed, but type contexts (represented by TYPE_USE, + * see JLS 17 section 9.6.4.1) are not allowed. + */ + + elementType != "TYPE_USE" + } + + /** Holds if this annotation type is annotated with the meta-annotation `@Repeatable`. */ + predicate isRepeatable() { this.getADeclaredAnnotation() instanceof RepeatableAnnotation } + + /** + * If this annotation type is annotated with the meta-annotation `@Repeatable`, + * gets the annotation type which acts as _containing annotation type_. + */ + AnnotationType getContainingAnnotationType() { + result = this.getADeclaredAnnotation().(RepeatableAnnotation).getContainingType() } } diff --git a/java/ql/lib/semmle/code/java/Dependency.qll b/java/ql/lib/semmle/code/java/Dependency.qll index a2bd8a84fdc..17236c6d05e 100644 --- a/java/ql/lib/semmle/code/java/Dependency.qll +++ b/java/ql/lib/semmle/code/java/Dependency.qll @@ -71,7 +71,8 @@ predicate depends(RefType t, RefType dep) { a.getAnnotatedElement().(Member).getDeclaringType() = t | usesType(a.getType(), dep) or - usesType(a.getAValue().getType(), dep) + usesType(a.getValue(_).getType(), dep) or + usesType(a.getAnArrayValue(_).getType(), dep) ) or // the type accessed in an `instanceof` expression in `t`. diff --git a/java/ql/lib/semmle/code/java/DependencyCounts.qll b/java/ql/lib/semmle/code/java/DependencyCounts.qll index b775e5fcf06..1010be48055 100644 --- a/java/ql/lib/semmle/code/java/DependencyCounts.qll +++ b/java/ql/lib/semmle/code/java/DependencyCounts.qll @@ -90,7 +90,7 @@ predicate numDepends(RefType t, RefType dep, int value) { | elem = a and usesType(a.getType(), dep) or - elem = a.getAValue() and + elem = [a.getValue(_), a.getAnArrayValue(_)] and elem.getFile().getExtension() = "java" and usesType(elem.(Expr).getType(), dep) ) diff --git a/java/ql/lib/semmle/code/java/JDKAnnotations.qll b/java/ql/lib/semmle/code/java/JDKAnnotations.qll index 2dff70c4d8e..502aef09075 100644 --- a/java/ql/lib/semmle/code/java/JDKAnnotations.qll +++ b/java/ql/lib/semmle/code/java/JDKAnnotations.qll @@ -18,14 +18,16 @@ class OverrideAnnotation extends Annotation { class SuppressWarningsAnnotation extends Annotation { SuppressWarningsAnnotation() { this.getType().hasQualifiedName("java.lang", "SuppressWarnings") } - /** Gets the `StringLiteral` of a warning suppressed by this annotation. */ - StringLiteral getASuppressedWarningLiteral() { - result = this.getAValue() or - result = this.getAValue().(ArrayInit).getAnInit() - } + /** + * DEPRECATED: This predicate restricts the results to `StringLiteral`; prefer `getASuppressedWarning()` + * to get the name of a suppressed warning. + * + * Gets the `StringLiteral` of a warning suppressed by this annotation. + */ + deprecated StringLiteral getASuppressedWarningLiteral() { result = this.getAnArrayValue("value") } /** Gets the name of a warning suppressed by this annotation. */ - string getASuppressedWarning() { result = this.getASuppressedWarningLiteral().getValue() } + string getASuppressedWarning() { result = this.getAStringArrayValue("value") } } /** A `@Target` annotation. */ @@ -33,18 +35,15 @@ class TargetAnnotation extends Annotation { TargetAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Target") } /** + * DEPRECATED: Getting the field access expression is rarely useful. Use `getATargetElementType()` + * to get the name of the target element. + * * Gets a target expression within this annotation. * * For example, the field access `ElementType.FIELD` is a target expression in * `@Target({ElementType.FIELD, ElementType.METHOD})`. */ - Expr getATargetExpression() { - not result instanceof ArrayInit and - ( - result = this.getAValue() or - result = this.getAValue().(ArrayInit).getAnInit() - ) - } + deprecated Expr getATargetExpression() { result = this.getAnArrayValue("value") } /** * Gets the name of a target element type. @@ -52,14 +51,7 @@ class TargetAnnotation extends Annotation { * For example, `METHOD` is the name of a target element type in * `@Target({ElementType.FIELD, ElementType.METHOD})`. */ - string getATargetElementType() { - exists(EnumConstant ec | - ec = this.getATargetExpression().(VarAccess).getVariable() and - ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "ElementType") - | - result = ec.getName() - ) - } + string getATargetElementType() { result = this.getAnEnumConstantArrayValue("value").getName() } } /** A `@Retention` annotation. */ @@ -67,12 +59,15 @@ class RetentionAnnotation extends Annotation { RetentionAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Retention") } /** + * DEPRECATED: Getting the field access expression is rarely useful. Use `getRetentionPolicy()` + * to get the name of the retention policy. + * * Gets the retention policy expression within this annotation. * * For example, the field access `RetentionPolicy.RUNTIME` is the * retention policy expression in `@Retention(RetentionPolicy.RUNTIME)`. */ - Expr getRetentionPolicyExpression() { result = this.getValue("value") } + deprecated Expr getRetentionPolicyExpression() { result = this.getValue("value") } /** * Gets the name of the retention policy of this annotation. @@ -80,14 +75,18 @@ class RetentionAnnotation extends Annotation { * For example, `RUNTIME` is the name of the retention policy * in `@Retention(RetentionPolicy.RUNTIME)`. */ - string getRetentionPolicy() { - exists(EnumConstant ec | - ec = this.getRetentionPolicyExpression().(VarAccess).getVariable() and - ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "RetentionPolicy") - | - result = ec.getName() - ) - } + string getRetentionPolicy() { result = this.getEnumConstantValue("value").getName() } +} + +/** A `@Repeatable` annotation. */ +class RepeatableAnnotation extends Annotation { + RepeatableAnnotation() { this.getType().hasQualifiedName("java.lang.annotation", "Repeatable") } + + /** + * Gets the annotation type which acts as _containing type_, grouping multiple + * repeatable annotations together. + */ + AnnotationType getContainingType() { result = this.getTypeValue("value") } } /** @@ -119,11 +118,7 @@ abstract class NonReflectiveAnnotation extends Annotation { } library class StandardNonReflectiveAnnotation extends NonReflectiveAnnotation { StandardNonReflectiveAnnotation() { - exists(AnnotationType anntp | anntp = this.getType() | - anntp.hasQualifiedName("java.lang", "Override") or - anntp.hasQualifiedName("java.lang", "Deprecated") or - anntp.hasQualifiedName("java.lang", "SuppressWarnings") or - anntp.hasQualifiedName("java.lang", "SafeVarargs") - ) + this.getType() + .hasQualifiedName("java.lang", ["Override", "Deprecated", "SuppressWarnings", "SafeVarargs"]) } } diff --git a/java/ql/lib/semmle/code/java/PrintAst.qll b/java/ql/lib/semmle/code/java/PrintAst.qll index 9d88550faa3..b2937d67940 100644 --- a/java/ql/lib/semmle/code/java/PrintAst.qll +++ b/java/ql/lib/semmle/code/java/PrintAst.qll @@ -120,7 +120,7 @@ private newtype TPrintAstNode = shouldPrint(lvde, _) and lvde.getParent() instanceof SingleLocalVarDeclParent } or TAnnotationsNode(Annotatable ann) { - shouldPrint(ann, _) and ann.hasAnnotation() and not partOfAnnotation(ann) + shouldPrint(ann, _) and ann.hasDeclaredAnnotation() and not partOfAnnotation(ann) } or TParametersNode(Callable c) { shouldPrint(c, _) and not c.hasNoParameters() } or TBaseTypesNode(ClassOrInterface ty) { shouldPrint(ty, _) } or diff --git a/java/ql/lib/semmle/code/java/UnitTests.qll b/java/ql/lib/semmle/code/java/UnitTests.qll index e56b9a6dc23..38f37fa4ff0 100644 --- a/java/ql/lib/semmle/code/java/UnitTests.qll +++ b/java/ql/lib/semmle/code/java/UnitTests.qll @@ -161,15 +161,13 @@ class TestNGTestMethod extends Method { exists(TestNGTestAnnotation testAnnotation | testAnnotation = this.getAnAnnotation() and // The data provider must have the same name as the referenced data provider - result.getDataProviderName() = - testAnnotation.getValue("dataProvider").(StringLiteral).getValue() + result.getDataProviderName() = testAnnotation.getStringValue("dataProvider") | // Either the data provider should be on the current class, or a supertype this.getDeclaringType().getAnAncestor() = result.getDeclaringType() or // Or the data provider class should be declared - result.getDeclaringType() = - testAnnotation.getValue("dataProviderClass").(TypeLiteral).getReferencedType() + result.getDeclaringType() = testAnnotation.getTypeValue("dataProviderClass") ) } } @@ -227,9 +225,7 @@ class TestNGListenersAnnotation extends TestNGAnnotation { /** * Gets a listener defined in this annotation. */ - TestNGListenerImpl getAListener() { - result = this.getAValue("value").(TypeLiteral).getReferencedType() - } + TestNGListenerImpl getAListener() { result = this.getATypeArrayValue("value") } } /** diff --git a/java/ql/lib/semmle/code/java/frameworks/JAXB.qll b/java/ql/lib/semmle/code/java/frameworks/JAXB.qll index 8fe204ad846..c9c9aee71b5 100644 --- a/java/ql/lib/semmle/code/java/frameworks/JAXB.qll +++ b/java/ql/lib/semmle/code/java/frameworks/JAXB.qll @@ -60,7 +60,7 @@ class JaxbType extends Class { this.getAnAnnotation() = a and a.getType().(JaxbAnnotationType).hasName("XmlAccessorType") | - result.getAnAccess() = a.getValue("value") + result = a.getEnumConstantValue("value") ) } diff --git a/java/ql/lib/semmle/code/java/frameworks/JUnitAnnotations.qll b/java/ql/lib/semmle/code/java/frameworks/JUnitAnnotations.qll index 8716f7d625c..d74fe683f06 100644 --- a/java/ql/lib/semmle/code/java/frameworks/JUnitAnnotations.qll +++ b/java/ql/lib/semmle/code/java/frameworks/JUnitAnnotations.qll @@ -64,5 +64,5 @@ class RunWithAnnotation extends Annotation { /** * Gets the runner that will be used. */ - Type getRunner() { result = this.getValue("value").(TypeLiteral).getReferencedType() } + Type getRunner() { result = this.getTypeValue("value") } } diff --git a/java/ql/lib/semmle/code/java/frameworks/JaxWS.qll b/java/ql/lib/semmle/code/java/frameworks/JaxWS.qll index ab83bab8049..c60c3ff0369 100644 --- a/java/ql/lib/semmle/code/java/frameworks/JaxWS.qll +++ b/java/ql/lib/semmle/code/java/frameworks/JaxWS.qll @@ -296,11 +296,7 @@ class JaxRSProducesAnnotation extends JaxRSAnnotation { /** * Gets a declared content type that can be produced by this resource. */ - Expr getADeclaredContentTypeExpr() { - result = this.getAValue() and not result instanceof ArrayInit - or - result = this.getAValue().(ArrayInit).getAnInit() - } + Expr getADeclaredContentTypeExpr() { result = this.getAnArrayValue("value") } } /** An `@Consumes` annotation that describes content types can be consumed by this resource. */ diff --git a/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll b/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll index 19744ca2c68..6c16bb168bb 100644 --- a/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll +++ b/java/ql/lib/semmle/code/java/frameworks/MyBatis.qll @@ -85,9 +85,7 @@ class IbatisSqlOperationAnnotation extends Annotation { /** * Gets this annotation's SQL statement string. */ - string getSqlValue() { - result = this.getAValue("value").(CompileTimeConstantExpr).getStringValue() - } + string getSqlValue() { result = this.getAStringArrayValue("value") } } /** diff --git a/java/ql/lib/semmle/code/java/frameworks/javaee/Persistence.qll b/java/ql/lib/semmle/code/java/frameworks/javaee/Persistence.qll index 82569754394..e60659426e5 100644 --- a/java/ql/lib/semmle/code/java/frameworks/javaee/Persistence.qll +++ b/java/ql/lib/semmle/code/java/frameworks/javaee/Persistence.qll @@ -33,12 +33,12 @@ class PersistentEntity extends RefType { } /** - * Gets the access type for this entity as defined by a `@javax.persistence.Access` annotation, if any. + * Gets the access type for this entity as defined by a `@javax.persistence.Access` annotation, + * if any, in lower case. */ string getAccessTypeFromAnnotation() { exists(AccessAnnotation accessType | accessType = this.getAnAnnotation() | - result = - accessType.getValue("value").(FieldRead).getField().(EnumConstant).getName().toLowerCase() + result = accessType.getEnumConstantValue("value").getName().toLowerCase() ) } } diff --git a/java/ql/lib/semmle/code/java/frameworks/spring/SpringAutowire.qll b/java/ql/lib/semmle/code/java/frameworks/spring/SpringAutowire.qll index b99dafecc65..1dd6dfd292f 100644 --- a/java/ql/lib/semmle/code/java/frameworks/spring/SpringAutowire.qll +++ b/java/ql/lib/semmle/code/java/frameworks/spring/SpringAutowire.qll @@ -311,9 +311,7 @@ class SpringQualifierDefinitionAnnotation extends Annotation { /** * Gets the value of the qualifier field for this qualifier. */ - string getQualifierValue() { - result = this.getValue("value").(CompileTimeConstantExpr).getStringValue() - } + string getQualifierValue() { result = this.getStringValue("value") } } /** @@ -325,9 +323,7 @@ class SpringQualifierAnnotation extends Annotation { /** * Gets the value of the qualifier field for this qualifier. */ - string getQualifierValue() { - result = this.getValue("value").(CompileTimeConstantExpr).getStringValue() - } + string getQualifierValue() { result = this.getStringValue("value") } /** * Gets the bean definition in an XML file that this qualifier resolves to, if any. @@ -350,9 +346,7 @@ class SpringResourceAnnotation extends Annotation { /** * Gets the specified name value, if any. */ - string getNameValue() { - result = this.getValue("name").(CompileTimeConstantExpr).getStringValue() - } + string getNameValue() { result = this.getStringValue("name") } /** * Gets the bean definition in an XML file that the resource resolves to, if any. diff --git a/java/ql/lib/semmle/code/java/frameworks/spring/SpringComponentScan.qll b/java/ql/lib/semmle/code/java/frameworks/spring/SpringComponentScan.qll index 1c037adefd6..f3380c45458 100644 --- a/java/ql/lib/semmle/code/java/frameworks/spring/SpringComponentScan.qll +++ b/java/ql/lib/semmle/code/java/frameworks/spring/SpringComponentScan.qll @@ -40,16 +40,10 @@ class SpringComponentScan extends Annotation { */ string getBasePackages() { // "value" and "basePackages" are synonymous, and are simple strings - result = this.getAValue("basePackages").(StringLiteral).getValue() + result = this.getAStringArrayValue(["basePackages", "value"]) or - result = this.getAValue("value").(StringLiteral).getValue() - or - exists(TypeLiteral typeLiteral | - // Base package classes are type literals whose package should be considered a base package. - typeLiteral = this.getAValue("basePackageClasses") - | - result = typeLiteral.getReferencedType().(RefType).getPackage().getName() - ) + // Base package classes are type literals whose package should be considered a base package. + result = this.getATypeArrayValue("basePackageClasses").(RefType).getPackage().getName() } } @@ -144,8 +138,7 @@ class SpringComponent extends RefType { if exists(this.getComponentAnnotation().getValue("value")) then // If the name has been specified in the component annotation, use that. - result = - this.getComponentAnnotation().getValue("value").(CompileTimeConstantExpr).getStringValue() + result = this.getComponentAnnotation().getStringValue("value") else // Otherwise use the name of the class, with the initial letter lower cased. exists(string name | name = this.getName() | @@ -204,7 +197,7 @@ class SpringComponent extends RefType { .getType() .hasQualifiedName("org.springframework.context.annotation", "Profile") | - result = profileAnnotation.getAValue("value").(StringLiteral).getValue() + result = profileAnnotation.getAStringArrayValue("value") ) } } diff --git a/java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll b/java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll index 53829d02752..d40d1608969 100644 --- a/java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll +++ b/java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll @@ -154,9 +154,7 @@ class SpringRequestMappingMethod extends SpringControllerMethod { } /** Gets the "value" @RequestMapping annotation value, if present. */ - string getValue() { - result = requestMappingAnnotation.getValue("value").(CompileTimeConstantExpr).getStringValue() - } + string getValue() { result = requestMappingAnnotation.getStringValue("value") } /** Holds if this is considered an `@ResponseBody` method. */ predicate isResponseBody() { diff --git a/java/ql/lib/semmle/code/java/frameworks/struts/StrutsAnnotations.qll b/java/ql/lib/semmle/code/java/frameworks/struts/StrutsAnnotations.qll index 27ada4d6ff0..d97415354b3 100644 --- a/java/ql/lib/semmle/code/java/frameworks/struts/StrutsAnnotations.qll +++ b/java/ql/lib/semmle/code/java/frameworks/struts/StrutsAnnotations.qll @@ -34,5 +34,5 @@ class StrutsActionsAnnotation extends StrutsAnnotation { /** * Gets an Action annotation contained in this Actions annotation. */ - StrutsActionAnnotation getAnAction() { result = this.getAValue("value") } + StrutsActionAnnotation getAnAction() { result = this.getAnArrayValue("value") } } diff --git a/java/ql/src/AlertSuppressionAnnotations.ql b/java/ql/src/AlertSuppressionAnnotations.ql index aa9fd275ad1..7f0ee74a0d8 100644 --- a/java/ql/src/AlertSuppressionAnnotations.ql +++ b/java/ql/src/AlertSuppressionAnnotations.ql @@ -23,7 +23,7 @@ class SuppressionAnnotation extends SuppressWarningsAnnotation { string text; SuppressionAnnotation() { - text = this.getASuppressedWarningLiteral().getValue() and + text = this.getASuppressedWarning() and exists(getAnnotationText(text)) } diff --git a/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.ql b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.ql index 22729eebd66..2bffa1ea4f4 100644 --- a/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.ql +++ b/java/ql/src/Likely Bugs/Reflection/AnnotationPresentCheck.ql @@ -19,8 +19,5 @@ where m.getNumberOfParameters() = 1 and c.getArgument(0).getType() = p and p.getATypeArgument() = t and - not exists(RetentionAnnotation a | - t.getAnAnnotation() = a and - a.getAValue().(VarAccess).getVariable().hasName("RUNTIME") - ) + t.getRetentionPolicy() != "RUNTIME" select c, "Call to isAnnotationPresent where no annotation has the RUNTIME retention policy." diff --git a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll index bb16099ddc3..cc1552b59b9 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-352/JsonpInjectionLib.qll @@ -47,8 +47,8 @@ class SpringControllerRequestMappingGetMethod extends SpringControllerGetMethod .getType() .hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping") and ( - this.getAnAnnotation().getValue("method").(VarAccess).getVariable().getName() = "GET" or - this.getAnAnnotation().getValue("method").(ArrayInit).getSize() = 0 //Java code example: @RequestMapping(value = "test") + this.getAnAnnotation().getAnEnumConstantArrayValue("method").getName() = "GET" or + not exists(this.getAnAnnotation().getAnArrayValue("method")) //Java code example: @RequestMapping(value = "test") ) and not this.getAParamType().getName() = "MultipartFile" } diff --git a/java/ql/test/library-tests/annotation-arrays/test.ql b/java/ql/test/library-tests/annotation-arrays/test.ql index 4c0dfd89b7b..4598687c915 100644 --- a/java/ql/test/library-tests/annotation-arrays/test.ql +++ b/java/ql/test/library-tests/annotation-arrays/test.ql @@ -4,6 +4,6 @@ from Field f, Annotation ann, Expr value, Expr valueChild where f.getDeclaringType().fromSource() and ann = f.getAnAnnotation() and - value = ann.getAValue() and + value = ann.getValue(_) and valueChild.getParent() = value select f, ann, value, valueChild diff --git a/java/ql/test/library-tests/annotations/Annotatable.expected b/java/ql/test/library-tests/annotations/Annotatable.expected new file mode 100644 index 00000000000..7708e78a305 --- /dev/null +++ b/java/ql/test/library-tests/annotations/Annotatable.expected @@ -0,0 +1,101 @@ +declaredAnnotation +| Annotatable.java:8:16:8:40 | CustomInheritedAnnotation | Annotatable.java:7:5:7:14 | Inherited | +| Annotatable.java:14:11:14:22 | WithDeclared | Annotatable.java:12:5:12:21 | CustomAnnotation | +| Annotatable.java:14:11:14:22 | WithDeclared | Annotatable.java:13:5:13:38 | CustomInheritedAnnotation | +| Annotatable.java:17:14:17:31 | methodWithDeclared | Annotatable.java:16:9:16:49 | CustomInheritedAnnotation | +| Annotatable.java:22:14:22:31 | methodWithDeclared | Annotatable.java:21:9:21:17 | Override | +| Annotatable.java:30:11:30:41 | SubclassDeclaringSameAnnotation | Annotatable.java:29:5:29:37 | CustomInheritedAnnotation | +| Annotatable.java:35:15:35:35 | InterfaceWithDeclared | Annotatable.java:34:5:34:38 | CustomInheritedAnnotation | +| Annotatable.java:47:16:47:35 | RepeatableAnnotation | Annotatable.java:46:5:46:42 | Repeatable | +| Annotatable.java:52:16:52:43 | InheritedContainerAnnotation | Annotatable.java:51:5:51:14 | Inherited | +| Annotatable.java:58:16:58:44 | InheritedRepeatableAnnotation | Annotatable.java:56:5:56:14 | Inherited | +| Annotatable.java:58:16:58:44 | InheritedRepeatableAnnotation | Annotatable.java:57:5:57:51 | Repeatable | +| Annotatable.java:63:16:63:44 | InheritedContainerAnnotation2 | Annotatable.java:62:5:62:14 | Inherited | +| Annotatable.java:71:16:71:47 | NonInheritedRepeatableAnnotation | Annotatable.java:70:5:70:52 | Repeatable | +| Annotatable.java:78:11:78:30 | WithAssociatedSingle | Annotatable.java:75:5:75:33 | RepeatableAnnotation | +| Annotatable.java:78:11:78:30 | WithAssociatedSingle | Annotatable.java:76:5:76:42 | InheritedRepeatableAnnotation | +| Annotatable.java:78:11:78:30 | WithAssociatedSingle | Annotatable.java:77:5:77:45 | NonInheritedRepeatableAnnotation | +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.class:0:0:0:0 | ContainerAnnotation | +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation | +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation2 | +| Annotatable.java:92:11:92:44 | SubclassOfSingleWithEmptyContainer | Annotatable.java:90:5:90:37 | InheritedContainerAnnotation | +| Annotatable.java:92:11:92:44 | SubclassOfSingleWithEmptyContainer | Annotatable.java:91:5:91:38 | InheritedContainerAnnotation2 | +| Annotatable.java:98:15:98:37 | InterfaceWithAssociated | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation | +| Annotatable.java:113:11:113:32 | WithAssociatedMultiple | Annotatable.class:0:0:0:0 | ContainerAnnotation | +| Annotatable.java:113:11:113:32 | WithAssociatedMultiple | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation | +| Annotatable.java:113:11:113:32 | WithAssociatedMultiple | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation2 | +| Annotatable.java:116:14:116:39 | methodWithAssociatedSingle | Annotatable.java:115:9:115:53 | InheritedRepeatableAnnotation | +| Annotatable.java:121:14:121:33 | methodWithAssociated | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation | +| Annotatable.java:126:14:126:39 | methodWithAssociatedSingle | Annotatable.java:125:9:125:17 | Override | +| Annotatable.java:129:14:129:33 | methodWithAssociated | Annotatable.java:128:9:128:17 | Override | +| Annotatable.java:138:11:138:28 | SubclassWithSingle | Annotatable.java:135:5:135:34 | RepeatableAnnotation | +| Annotatable.java:138:11:138:28 | SubclassWithSingle | Annotatable.java:136:5:136:43 | InheritedRepeatableAnnotation | +| Annotatable.java:138:11:138:28 | SubclassWithSingle | Annotatable.java:137:5:137:46 | NonInheritedRepeatableAnnotation | +| Annotatable.java:144:11:144:46 | SubclassOfMultipleWithEmptyContainer | Annotatable.java:142:5:142:37 | InheritedContainerAnnotation | +| Annotatable.java:144:11:144:46 | SubclassOfMultipleWithEmptyContainer | Annotatable.java:143:5:143:38 | InheritedContainerAnnotation2 | +| Annotatable.java:150:35:153:5 | {...} | Annotatable.java:151:9:151:53 | InheritedRepeatableAnnotation | +| Annotatable.java:150:35:153:5 | {...} | Annotatable.java:152:9:152:53 | InheritedRepeatableAnnotation | +| Annotatable.java:154:11:154:45 | ExplicitContainerAndSingleContained | Annotatable.java:148:5:148:44 | InheritedRepeatableAnnotation | +| Annotatable.java:154:11:154:45 | ExplicitContainerAndSingleContained | Annotatable.java:150:5:153:6 | InheritedContainerAnnotation | +| Annotatable.java:160:16:160:41 | NestedAnnotationContainer1 | Annotatable.java:159:5:159:14 | Inherited | +| Annotatable.java:166:16:166:32 | NestedAnnotation1 | Annotatable.java:164:5:164:14 | Inherited | +| Annotatable.java:166:16:166:32 | NestedAnnotation1 | Annotatable.java:165:5:165:49 | Repeatable | +| Annotatable.java:172:16:172:32 | NestedAnnotation2 | Annotatable.java:170:5:170:14 | Inherited | +| Annotatable.java:172:16:172:32 | NestedAnnotation2 | Annotatable.java:171:5:171:40 | Repeatable | +| Annotatable.java:182:11:182:30 | WithNestedAssociated | Annotatable.class:0:0:0:0 | NestedAnnotationContainer1 | +| Annotatable.java:182:11:182:30 | WithNestedAssociated | Annotatable.java:177:5:177:27 | NestedAnnotation2 | +| Annotatable.java:189:11:189:48 | WithNestedAssociatedExplicitContainers | Annotatable.class:0:0:0:0 | NestedAnnotationContainer1 | +annotationAdditional +| Annotatable.java:20:11:20:18 | Subclass | Annotatable.java:13:5:13:38 | CustomInheritedAnnotation | +| Annotatable.java:26:11:26:21 | SubSubclass | Annotatable.java:13:5:13:38 | CustomInheritedAnnotation | +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.java:76:5:76:42 | InheritedRepeatableAnnotation | +| Annotatable.java:92:11:92:44 | SubclassOfSingleWithEmptyContainer | Annotatable.java:76:5:76:42 | InheritedRepeatableAnnotation | +| Annotatable.java:124:11:124:32 | WithAssociatedSubclass | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation | +| Annotatable.java:124:11:124:32 | WithAssociatedSubclass | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation2 | +| Annotatable.java:133:11:133:35 | WithAssociatedSubSubclass | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation | +| Annotatable.java:133:11:133:35 | WithAssociatedSubSubclass | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation2 | +| Annotatable.java:138:11:138:28 | SubclassWithSingle | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation | +| Annotatable.java:138:11:138:28 | SubclassWithSingle | Annotatable.class:0:0:0:0 | InheritedContainerAnnotation2 | +| Annotatable.java:156:11:156:35 | ExplicitContainerSubclass | Annotatable.java:148:5:148:44 | InheritedRepeatableAnnotation | +| Annotatable.java:156:11:156:35 | ExplicitContainerSubclass | Annotatable.java:150:5:153:6 | InheritedContainerAnnotation | +| Annotatable.java:184:11:184:38 | WithNestedAssociatedSubclass | Annotatable.class:0:0:0:0 | NestedAnnotationContainer1 | +| Annotatable.java:184:11:184:38 | WithNestedAssociatedSubclass | Annotatable.java:177:5:177:27 | NestedAnnotation2 | +| Annotatable.java:191:11:191:56 | WithNestedAssociatedExplicitContainersSubclass | Annotatable.class:0:0:0:0 | NestedAnnotationContainer1 | +bugAnnotationAdditional +associatedAnnotationAdditional +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.class:0:0:0:0 | NonInheritedRepeatableAnnotation | +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.class:0:0:0:0 | NonInheritedRepeatableAnnotation | +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.class:0:0:0:0 | RepeatableAnnotation | +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.class:0:0:0:0 | RepeatableAnnotation | +| Annotatable.java:98:15:98:37 | InterfaceWithAssociated | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:98:15:98:37 | InterfaceWithAssociated | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:113:11:113:32 | WithAssociatedMultiple | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:113:11:113:32 | WithAssociatedMultiple | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:113:11:113:32 | WithAssociatedMultiple | Annotatable.class:0:0:0:0 | NonInheritedRepeatableAnnotation | +| Annotatable.java:113:11:113:32 | WithAssociatedMultiple | Annotatable.class:0:0:0:0 | NonInheritedRepeatableAnnotation | +| Annotatable.java:113:11:113:32 | WithAssociatedMultiple | Annotatable.class:0:0:0:0 | RepeatableAnnotation | +| Annotatable.java:113:11:113:32 | WithAssociatedMultiple | Annotatable.class:0:0:0:0 | RepeatableAnnotation | +| Annotatable.java:121:14:121:33 | methodWithAssociated | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:121:14:121:33 | methodWithAssociated | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:124:11:124:32 | WithAssociatedSubclass | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:124:11:124:32 | WithAssociatedSubclass | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:133:11:133:35 | WithAssociatedSubSubclass | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:133:11:133:35 | WithAssociatedSubSubclass | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:144:11:144:46 | SubclassOfMultipleWithEmptyContainer | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:144:11:144:46 | SubclassOfMultipleWithEmptyContainer | Annotatable.class:0:0:0:0 | InheritedRepeatableAnnotation | +| Annotatable.java:154:11:154:45 | ExplicitContainerAndSingleContained | Annotatable.java:151:9:151:53 | InheritedRepeatableAnnotation | +| Annotatable.java:154:11:154:45 | ExplicitContainerAndSingleContained | Annotatable.java:152:9:152:53 | InheritedRepeatableAnnotation | +| Annotatable.java:156:11:156:35 | ExplicitContainerSubclass | Annotatable.java:151:9:151:53 | InheritedRepeatableAnnotation | +| Annotatable.java:156:11:156:35 | ExplicitContainerSubclass | Annotatable.java:152:9:152:53 | InheritedRepeatableAnnotation | +| Annotatable.java:182:11:182:30 | WithNestedAssociated | Annotatable.class:0:0:0:0 | NestedAnnotation1 | +| Annotatable.java:182:11:182:30 | WithNestedAssociated | Annotatable.class:0:0:0:0 | NestedAnnotation1 | +| Annotatable.java:184:11:184:38 | WithNestedAssociatedSubclass | Annotatable.class:0:0:0:0 | NestedAnnotation1 | +| Annotatable.java:184:11:184:38 | WithNestedAssociatedSubclass | Annotatable.class:0:0:0:0 | NestedAnnotation1 | +| Annotatable.java:189:11:189:48 | WithNestedAssociatedExplicitContainers | Annotatable.class:0:0:0:0 | NestedAnnotation1 | +| Annotatable.java:189:11:189:48 | WithNestedAssociatedExplicitContainers | Annotatable.class:0:0:0:0 | NestedAnnotation1 | +| Annotatable.java:191:11:191:56 | WithNestedAssociatedExplicitContainersSubclass | Annotatable.class:0:0:0:0 | NestedAnnotation1 | +| Annotatable.java:191:11:191:56 | WithNestedAssociatedExplicitContainersSubclass | Annotatable.class:0:0:0:0 | NestedAnnotation1 | +associatedAnnotationNotInherited +| Annotatable.java:86:11:86:30 | SubclassWithMultiple | Annotatable.java:76:5:76:42 | InheritedRepeatableAnnotation | diff --git a/java/ql/test/library-tests/annotations/Annotatable.java b/java/ql/test/library-tests/annotations/Annotatable.java new file mode 100644 index 00000000000..e73d48bb24d --- /dev/null +++ b/java/ql/test/library-tests/annotations/Annotatable.java @@ -0,0 +1,192 @@ +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; + +class Annotatable { + @interface CustomAnnotation {} + + @Inherited + @interface CustomInheritedAnnotation { + String value(); + } + + @CustomAnnotation + @CustomInheritedAnnotation("base") + class WithDeclared { + // Annotations on methods are not inherited + @CustomInheritedAnnotation("base-method") + void methodWithDeclared() {} + } + + class Subclass extends WithDeclared { + @Override + void methodWithDeclared() {} + } + + // Inheritance from super-superclass + class SubSubclass extends Subclass {} + + // Prevents inheriting annotation of same type + @CustomInheritedAnnotation("sub") + class SubclassDeclaringSameAnnotation extends WithDeclared {} + + + // Annotations on interfaces are not inherited + @CustomInheritedAnnotation("base") + interface InterfaceWithDeclared {} + + interface ExtendingInterface extends InterfaceWithDeclared {} + + class ImplementingInterface implements InterfaceWithDeclared {} + + + @interface ContainerAnnotation { + RepeatableAnnotation[] value(); + } + + @Repeatable(ContainerAnnotation.class) + @interface RepeatableAnnotation { + String value(); + } + + @Inherited + @interface InheritedContainerAnnotation { + InheritedRepeatableAnnotation[] value(); + } + + @Inherited + @Repeatable(InheritedContainerAnnotation.class) + @interface InheritedRepeatableAnnotation { + String value(); + } + + @Inherited + @interface InheritedContainerAnnotation2 { + NonInheritedRepeatableAnnotation[] value(); + } + + // Container is marked as @Inherited, but this annotation type is not + // This is allowed, but means that associated annotations will not be inherited, + // see java.lang.reflect.AnnotatedElement documentation + @Repeatable(InheritedContainerAnnotation2.class) + @interface NonInheritedRepeatableAnnotation { + String value(); + } + + @RepeatableAnnotation("base") + @InheritedRepeatableAnnotation("base") + @NonInheritedRepeatableAnnotation("base") + class WithAssociatedSingle {} + + @RepeatableAnnotation("sub-1") + @RepeatableAnnotation("sub-2") + @InheritedRepeatableAnnotation("sub-1") + @InheritedRepeatableAnnotation("sub-2") + @NonInheritedRepeatableAnnotation("sub-1") + @NonInheritedRepeatableAnnotation("sub-2") + class SubclassWithMultiple extends WithAssociatedSingle {} + + + // Empty container annotations have no effect; annotations are inherited from superclass + @InheritedContainerAnnotation({}) + @InheritedContainerAnnotation2({}) + class SubclassOfSingleWithEmptyContainer extends WithAssociatedSingle {} + + + // Annotations on interfaces are not inherited + @InheritedRepeatableAnnotation("base-1") + @InheritedRepeatableAnnotation("base-2") + interface InterfaceWithAssociated {} + + interface ExtendingInterfaceWithAssociated extends InterfaceWithAssociated {} + + class ImplementingInterfaceWithAssociated implements InterfaceWithAssociated {} + + + @RepeatableAnnotation("base-1") + @RepeatableAnnotation("base-2") + @InheritedRepeatableAnnotation("base-1") + @InheritedRepeatableAnnotation("base-2") + // These annotations are not inherited, but their (implicit) container annotation + // is inherited + @NonInheritedRepeatableAnnotation("base-1") + @NonInheritedRepeatableAnnotation("base-2") + class WithAssociatedMultiple { + // Annotations on methods are not inherited + @InheritedRepeatableAnnotation("base-method") + void methodWithAssociatedSingle() {} + + // Annotations on methods are not inherited + @InheritedRepeatableAnnotation("base-method-1") + @InheritedRepeatableAnnotation("base-method-2") + void methodWithAssociated() {} + } + + class WithAssociatedSubclass extends WithAssociatedMultiple { + @Override + void methodWithAssociatedSingle() {} + + @Override + void methodWithAssociated() {} + } + + // Inheritance from super-superclass + class WithAssociatedSubSubclass extends WithAssociatedSubclass {} + + @RepeatableAnnotation("sub-1") + @InheritedRepeatableAnnotation("sub-1") + @NonInheritedRepeatableAnnotation("sub-1") + class SubclassWithSingle extends WithAssociatedMultiple {} + + + // Empty container annotations have no effect; associated annotations are inherited from superclass + @InheritedContainerAnnotation({}) + @InheritedContainerAnnotation2({}) + class SubclassOfMultipleWithEmptyContainer extends WithAssociatedMultiple {} + + + // This annotation exists on its own without a container + @InheritedRepeatableAnnotation("single") + // TODO: Has currently spurious results for ArrayInit due to https://github.com/github/codeql/issues/8647 + @InheritedContainerAnnotation({ + @InheritedRepeatableAnnotation("container-1"), + @InheritedRepeatableAnnotation("container-2") + }) + class ExplicitContainerAndSingleContained {} + + class ExplicitContainerSubclass extends ExplicitContainerAndSingleContained {} + + + @Inherited + @interface NestedAnnotationContainer1 { + NestedAnnotation1[] value(); + } + + @Inherited + @Repeatable(NestedAnnotationContainer1.class) + @interface NestedAnnotation1 { + NestedAnnotation2[] value(); + } + + @Inherited + @Repeatable(NestedAnnotation1.class) + @interface NestedAnnotation2 { + String value(); + } + + // This annotation exists on its own without a container + @NestedAnnotation2("1") + // But these are nested inside an implicit @NestedAnnotationContainer1 + // Nested repeated annotations (@NestedAnnotation2) are not considered associated + @NestedAnnotation1({@NestedAnnotation2("1-1"), @NestedAnnotation2("1-2")}) + @NestedAnnotation1({@NestedAnnotation2("2-1"), @NestedAnnotation2("2-2")}) + class WithNestedAssociated {} + + class WithNestedAssociatedSubclass extends WithNestedAssociated {} + + // Nested repeated annotations (@NestedAnnotation2) are not considered associated + @NestedAnnotation1({@NestedAnnotation2("1-1"), @NestedAnnotation2("1-2")}) + @NestedAnnotation1({@NestedAnnotation2("2-1"), @NestedAnnotation2("2-2")}) + class WithNestedAssociatedExplicitContainers {} + + class WithNestedAssociatedExplicitContainersSubclass extends WithNestedAssociatedExplicitContainers {} +} diff --git a/java/ql/test/library-tests/annotations/Annotatable.ql b/java/ql/test/library-tests/annotations/Annotatable.ql new file mode 100644 index 00000000000..12745eb42fc --- /dev/null +++ b/java/ql/test/library-tests/annotations/Annotatable.ql @@ -0,0 +1,34 @@ +import java + +class RelevantAnnotatable extends Annotatable { + RelevantAnnotatable() { + getCompilationUnit().hasName("Annotatable") and getCompilationUnit().fromSource() + } +} + +query Annotation declaredAnnotation(RelevantAnnotatable a) { result = a.getADeclaredAnnotation() } + +/** Note: Only has the annotations as result which are not also considered _declared_. */ +query Annotation annotationAdditional(RelevantAnnotatable a) { + result = a.getAnAnnotation() and not result = a.getADeclaredAnnotation() +} + +/** Sanity check to verify that `getADeclaredAnnotation()` is a subset of `getAnAnnotation()` */ +query Annotation bugAnnotationAdditional(RelevantAnnotatable a) { + result = a.getADeclaredAnnotation() and not result = a.getAnAnnotation() +} + +/** Note: Only has the annotations as result which are not part of `getAnAnnotation()`. */ +query Annotation associatedAnnotationAdditional(RelevantAnnotatable a) { + result = a.getAnAssociatedAnnotation() and not result = a.getAnAnnotation() +} + +/** + * Covers all results of `getAnAssociatedAnnotation()` which are not also a result of `getAnAnnotation()`. + * This should only be the case for a base class using an inheritable annotation `A` and a subclass which + * has an annotation `CA` of the container type of `A`. In that case `A` is not considered _associated_ + * and the _indirect_ annotations from `CA` are considered instead. + */ +query Annotation associatedAnnotationNotInherited(RelevantAnnotatable a) { + result = a.getAnAnnotation() and not result = a.getAnAssociatedAnnotation() +} diff --git a/java/ql/test/library-tests/annotations/Annotation-values.expected b/java/ql/test/library-tests/annotations/Annotation-values.expected new file mode 100644 index 00000000000..55c86f3a124 --- /dev/null +++ b/java/ql/test/library-tests/annotations/Annotation-values.expected @@ -0,0 +1,215 @@ +value +| AnnotationValues.java:39:5:39:17 | SingleValues | annotationValue | AnnotationValues.class:0:0:0:0 | CustomAnnotation | +| AnnotationValues.java:39:5:39:17 | SingleValues | booleanValue | AnnotationValues.class:0:0:0:0 | false | +| AnnotationValues.java:39:5:39:17 | SingleValues | byteValue | AnnotationValues.class:0:0:0:0 | -1 | +| AnnotationValues.java:39:5:39:17 | SingleValues | charValue | AnnotationValues.class:0:0:0:0 | \uffff | +| AnnotationValues.java:39:5:39:17 | SingleValues | classValue | AnnotationValues.class:0:0:0:0 | AnnotationValues.class | +| AnnotationValues.java:39:5:39:17 | SingleValues | doubleValue | AnnotationValues.class:0:0:0:0 | -1.0 | +| AnnotationValues.java:39:5:39:17 | SingleValues | enumValue | AnnotationValues.class:0:0:0:0 | DEFAULT | +| AnnotationValues.java:39:5:39:17 | SingleValues | floatValue | AnnotationValues.class:0:0:0:0 | -1.0 | +| AnnotationValues.java:39:5:39:17 | SingleValues | intValue | AnnotationValues.class:0:0:0:0 | -1 | +| AnnotationValues.java:39:5:39:17 | SingleValues | longValue | AnnotationValues.class:0:0:0:0 | -1 | +| AnnotationValues.java:39:5:39:17 | SingleValues | shortValue | AnnotationValues.class:0:0:0:0 | -1 | +| AnnotationValues.java:39:5:39:17 | SingleValues | stringValue | AnnotationValues.class:0:0:0:0 | "\u0000" | +| AnnotationValues.java:42:5:55:5 | SingleValues | annotationValue | AnnotationValues.java:54:27:54:53 | CustomAnnotation | +| AnnotationValues.java:42:5:55:5 | SingleValues | booleanValue | AnnotationValues.java:49:24:49:27 | true | +| AnnotationValues.java:42:5:55:5 | SingleValues | byteValue | AnnotationValues.java:43:21:43:21 | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | charValue | AnnotationValues.java:50:21:50:21 | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | classValue | AnnotationValues.java:52:22:52:39 | SingleValues.class | +| AnnotationValues.java:42:5:55:5 | SingleValues | doubleValue | AnnotationValues.java:48:23:48:23 | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | enumValue | AnnotationValues.java:53:21:53:32 | CustomEnum.A | +| AnnotationValues.java:42:5:55:5 | SingleValues | floatValue | AnnotationValues.java:47:22:47:22 | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | intValue | AnnotationValues.java:45:20:45:20 | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | longValue | AnnotationValues.java:46:21:46:21 | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | shortValue | AnnotationValues.java:44:22:44:22 | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | stringValue | AnnotationValues.java:51:23:51:25 | "a" | +| AnnotationValues.java:54:27:54:53 | CustomAnnotation | value | AnnotationValues.java:54:45:54:52 | "single" | +| AnnotationValues.java:58:5:71:5 | SingleValues | annotationValue | AnnotationValues.java:70:27:70:53 | CustomAnnotation | +| AnnotationValues.java:58:5:71:5 | SingleValues | booleanValue | AnnotationValues.java:65:24:65:30 | BOOLEAN | +| AnnotationValues.java:58:5:71:5 | SingleValues | byteValue | AnnotationValues.java:59:21:59:24 | BYTE | +| AnnotationValues.java:58:5:71:5 | SingleValues | charValue | AnnotationValues.java:66:21:66:24 | CHAR | +| AnnotationValues.java:58:5:71:5 | SingleValues | classValue | AnnotationValues.java:68:22:68:39 | SingleValues.class | +| AnnotationValues.java:58:5:71:5 | SingleValues | doubleValue | AnnotationValues.java:64:23:64:28 | DOUBLE | +| AnnotationValues.java:58:5:71:5 | SingleValues | enumValue | AnnotationValues.java:69:21:69:32 | CustomEnum.A | +| AnnotationValues.java:58:5:71:5 | SingleValues | floatValue | AnnotationValues.java:63:22:63:26 | FLOAT | +| AnnotationValues.java:58:5:71:5 | SingleValues | intValue | AnnotationValues.java:61:20:61:22 | INT | +| AnnotationValues.java:58:5:71:5 | SingleValues | longValue | AnnotationValues.java:62:21:62:24 | LONG | +| AnnotationValues.java:58:5:71:5 | SingleValues | shortValue | AnnotationValues.java:60:22:60:26 | SHORT | +| AnnotationValues.java:58:5:71:5 | SingleValues | stringValue | AnnotationValues.java:67:23:67:28 | STRING | +| AnnotationValues.java:70:27:70:53 | CustomAnnotation | value | AnnotationValues.java:70:45:70:52 | "single" | +| AnnotationValues.java:90:5:90:16 | ArrayValues | annotationValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | booleanValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | byteValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | charValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | classValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | doubleValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | enumValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | floatValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | intValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | longValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | shortValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:90:5:90:16 | ArrayValues | stringValues | AnnotationValues.class:0:0:0:0 | {...} | +| AnnotationValues.java:93:5:106:5 | ArrayValues | annotationValues | AnnotationValues.java:105:28:105:54 | {...} | +| AnnotationValues.java:93:5:106:5 | ArrayValues | booleanValues | AnnotationValues.java:100:25:100:28 | true | +| AnnotationValues.java:93:5:106:5 | ArrayValues | byteValues | AnnotationValues.java:94:22:94:22 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | charValues | AnnotationValues.java:101:22:101:24 | 'a' | +| AnnotationValues.java:93:5:106:5 | ArrayValues | classValues | AnnotationValues.java:103:23:103:39 | ArrayValues.class | +| AnnotationValues.java:93:5:106:5 | ArrayValues | doubleValues | AnnotationValues.java:99:24:99:24 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | enumValues | AnnotationValues.java:104:22:104:33 | CustomEnum.A | +| AnnotationValues.java:93:5:106:5 | ArrayValues | floatValues | AnnotationValues.java:98:23:98:23 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | intValues | AnnotationValues.java:96:21:96:21 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | longValues | AnnotationValues.java:97:22:97:22 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | shortValues | AnnotationValues.java:95:23:95:23 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | stringValues | AnnotationValues.java:102:24:102:26 | "a" | +| AnnotationValues.java:105:28:105:54 | CustomAnnotation | value | AnnotationValues.java:105:46:105:53 | "single" | +| AnnotationValues.java:109:5:122:5 | ArrayValues | annotationValues | AnnotationValues.java:121:28:121:84 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | booleanValues | AnnotationValues.java:116:25:116:40 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | byteValues | AnnotationValues.java:110:22:110:30 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | charValues | AnnotationValues.java:117:22:117:32 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | classValues | AnnotationValues.java:119:23:119:61 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | doubleValues | AnnotationValues.java:115:24:115:34 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | enumValues | AnnotationValues.java:120:22:120:49 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | floatValues | AnnotationValues.java:114:23:114:32 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | intValues | AnnotationValues.java:112:21:112:28 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | longValues | AnnotationValues.java:113:22:113:30 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | shortValues | AnnotationValues.java:111:23:111:32 | {...} | +| AnnotationValues.java:109:5:122:5 | ArrayValues | stringValues | AnnotationValues.java:118:24:118:33 | {...} | +| AnnotationValues.java:121:29:121:54 | CustomAnnotation | value | AnnotationValues.java:121:47:121:53 | "first" | +| AnnotationValues.java:121:57:121:83 | CustomAnnotation | value | AnnotationValues.java:121:75:121:82 | "second" | +enumConstantValue +| AnnotationValues.java:39:5:39:17 | SingleValues | enumValue | AnnotationValues.java:14:9:14:15 | DEFAULT | +| AnnotationValues.java:42:5:55:5 | SingleValues | enumValue | AnnotationValues.java:15:9:15:9 | A | +| AnnotationValues.java:58:5:71:5 | SingleValues | enumValue | AnnotationValues.java:15:9:15:9 | A | +stringValue +| AnnotationValues.java:39:5:39:17 | SingleValues | stringValue | \u0000 | +| AnnotationValues.java:42:5:55:5 | SingleValues | stringValue | a | +| AnnotationValues.java:54:27:54:53 | CustomAnnotation | value | single | +| AnnotationValues.java:58:5:71:5 | SingleValues | stringValue | b | +| AnnotationValues.java:70:27:70:53 | CustomAnnotation | value | single | +| AnnotationValues.java:105:28:105:54 | CustomAnnotation | value | single | +| AnnotationValues.java:121:29:121:54 | CustomAnnotation | value | first | +| AnnotationValues.java:121:57:121:83 | CustomAnnotation | value | second | +intValue +| AnnotationValues.java:39:5:39:17 | SingleValues | byteValue | -1 | +| AnnotationValues.java:39:5:39:17 | SingleValues | charValue | 65535 | +| AnnotationValues.java:39:5:39:17 | SingleValues | intValue | -1 | +| AnnotationValues.java:39:5:39:17 | SingleValues | shortValue | -1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | byteValue | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | charValue | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | intValue | 1 | +| AnnotationValues.java:42:5:55:5 | SingleValues | shortValue | 1 | +| AnnotationValues.java:58:5:71:5 | SingleValues | byteValue | 2 | +| AnnotationValues.java:58:5:71:5 | SingleValues | charValue | 98 | +| AnnotationValues.java:58:5:71:5 | SingleValues | intValue | 2 | +| AnnotationValues.java:58:5:71:5 | SingleValues | shortValue | 2 | +booleanValue +| AnnotationValues.java:39:5:39:17 | SingleValues | booleanValue | false | +| AnnotationValues.java:42:5:55:5 | SingleValues | booleanValue | true | +| AnnotationValues.java:58:5:71:5 | SingleValues | booleanValue | true | +typeValue +| AnnotationValues.java:39:5:39:17 | SingleValues | classValue | AnnotationValues.java:1:7:1:22 | AnnotationValues | +| AnnotationValues.java:42:5:55:5 | SingleValues | classValue | AnnotationValues.java:23:16:23:27 | SingleValues | +| AnnotationValues.java:58:5:71:5 | SingleValues | classValue | AnnotationValues.java:23:16:23:27 | SingleValues | +arrayValue +| AnnotationValues.java:90:5:90:16 | ArrayValues | annotationValues | 0 | AnnotationValues.class:0:0:0:0 | CustomAnnotation | +| AnnotationValues.java:90:5:90:16 | ArrayValues | booleanValues | 0 | AnnotationValues.class:0:0:0:0 | false | +| AnnotationValues.java:90:5:90:16 | ArrayValues | byteValues | 0 | AnnotationValues.class:0:0:0:0 | -1 | +| AnnotationValues.java:90:5:90:16 | ArrayValues | charValues | 0 | AnnotationValues.class:0:0:0:0 | \uffff | +| AnnotationValues.java:90:5:90:16 | ArrayValues | classValues | 0 | AnnotationValues.class:0:0:0:0 | AnnotationValues.class | +| AnnotationValues.java:90:5:90:16 | ArrayValues | doubleValues | 0 | AnnotationValues.class:0:0:0:0 | -1.0 | +| AnnotationValues.java:90:5:90:16 | ArrayValues | enumValues | 0 | AnnotationValues.class:0:0:0:0 | DEFAULT | +| AnnotationValues.java:90:5:90:16 | ArrayValues | floatValues | 0 | AnnotationValues.class:0:0:0:0 | -1.0 | +| AnnotationValues.java:90:5:90:16 | ArrayValues | intValues | 0 | AnnotationValues.class:0:0:0:0 | -1 | +| AnnotationValues.java:90:5:90:16 | ArrayValues | longValues | 0 | AnnotationValues.class:0:0:0:0 | -1 | +| AnnotationValues.java:90:5:90:16 | ArrayValues | shortValues | 0 | AnnotationValues.class:0:0:0:0 | -1 | +| AnnotationValues.java:90:5:90:16 | ArrayValues | stringValues | 0 | AnnotationValues.class:0:0:0:0 | "\u0000" | +| AnnotationValues.java:93:5:106:5 | ArrayValues | annotationValues | 0 | AnnotationValues.java:105:28:105:54 | CustomAnnotation | +| AnnotationValues.java:93:5:106:5 | ArrayValues | booleanValues | 0 | AnnotationValues.java:100:25:100:28 | true | +| AnnotationValues.java:93:5:106:5 | ArrayValues | byteValues | 0 | AnnotationValues.java:94:22:94:22 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | charValues | 0 | AnnotationValues.java:101:22:101:24 | 'a' | +| AnnotationValues.java:93:5:106:5 | ArrayValues | classValues | 0 | AnnotationValues.java:103:23:103:39 | ArrayValues.class | +| AnnotationValues.java:93:5:106:5 | ArrayValues | doubleValues | 0 | AnnotationValues.java:99:24:99:24 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | enumValues | 0 | AnnotationValues.java:104:22:104:33 | CustomEnum.A | +| AnnotationValues.java:93:5:106:5 | ArrayValues | floatValues | 0 | AnnotationValues.java:98:23:98:23 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | intValues | 0 | AnnotationValues.java:96:21:96:21 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | longValues | 0 | AnnotationValues.java:97:22:97:22 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | shortValues | 0 | AnnotationValues.java:95:23:95:23 | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | stringValues | 0 | AnnotationValues.java:102:24:102:26 | "a" | +| AnnotationValues.java:109:5:122:5 | ArrayValues | annotationValues | 0 | AnnotationValues.java:121:29:121:54 | CustomAnnotation | +| AnnotationValues.java:109:5:122:5 | ArrayValues | annotationValues | 1 | AnnotationValues.java:121:57:121:83 | CustomAnnotation | +| AnnotationValues.java:109:5:122:5 | ArrayValues | booleanValues | 0 | AnnotationValues.class:0:0:0:0 | false | +| AnnotationValues.java:109:5:122:5 | ArrayValues | booleanValues | 1 | AnnotationValues.class:0:0:0:0 | true | +| AnnotationValues.java:109:5:122:5 | ArrayValues | booleanValues | -1 | AnnotationValues.java:116:26:116:30 | false | +| AnnotationValues.java:109:5:122:5 | ArrayValues | booleanValues | -2 | AnnotationValues.java:116:33:116:39 | BOOLEAN | +| AnnotationValues.java:109:5:122:5 | ArrayValues | byteValues | 0 | AnnotationValues.class:0:0:0:0 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | byteValues | 1 | AnnotationValues.class:0:0:0:0 | 2 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | byteValues | -1 | AnnotationValues.java:110:23:110:23 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | byteValues | -2 | AnnotationValues.java:110:26:110:29 | BYTE | +| AnnotationValues.java:109:5:122:5 | ArrayValues | charValues | 0 | AnnotationValues.class:0:0:0:0 | a | +| AnnotationValues.java:109:5:122:5 | ArrayValues | charValues | 1 | AnnotationValues.class:0:0:0:0 | b | +| AnnotationValues.java:109:5:122:5 | ArrayValues | charValues | -1 | AnnotationValues.java:117:23:117:25 | 'a' | +| AnnotationValues.java:109:5:122:5 | ArrayValues | charValues | -2 | AnnotationValues.java:117:28:117:31 | CHAR | +| AnnotationValues.java:109:5:122:5 | ArrayValues | classValues | 0 | AnnotationValues.class:0:0:0:0 | SingleValues.class | +| AnnotationValues.java:109:5:122:5 | ArrayValues | classValues | 1 | AnnotationValues.class:0:0:0:0 | ArrayValues.class | +| AnnotationValues.java:109:5:122:5 | ArrayValues | classValues | -1 | AnnotationValues.java:119:24:119:41 | SingleValues.class | +| AnnotationValues.java:109:5:122:5 | ArrayValues | classValues | -2 | AnnotationValues.java:119:44:119:60 | ArrayValues.class | +| AnnotationValues.java:109:5:122:5 | ArrayValues | doubleValues | 0 | AnnotationValues.class:0:0:0:0 | 1.0 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | doubleValues | 1 | AnnotationValues.class:0:0:0:0 | 2.0 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | doubleValues | -1 | AnnotationValues.java:115:25:115:25 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | doubleValues | -2 | AnnotationValues.java:115:28:115:33 | DOUBLE | +| AnnotationValues.java:109:5:122:5 | ArrayValues | enumValues | 0 | AnnotationValues.class:0:0:0:0 | A | +| AnnotationValues.java:109:5:122:5 | ArrayValues | enumValues | 1 | AnnotationValues.class:0:0:0:0 | B | +| AnnotationValues.java:109:5:122:5 | ArrayValues | enumValues | -1 | AnnotationValues.java:120:23:120:34 | CustomEnum.A | +| AnnotationValues.java:109:5:122:5 | ArrayValues | enumValues | -2 | AnnotationValues.java:120:37:120:48 | CustomEnum.B | +| AnnotationValues.java:109:5:122:5 | ArrayValues | floatValues | 0 | AnnotationValues.class:0:0:0:0 | 1.0 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | floatValues | 1 | AnnotationValues.class:0:0:0:0 | 2.0 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | floatValues | -1 | AnnotationValues.java:114:24:114:24 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | floatValues | -2 | AnnotationValues.java:114:27:114:31 | FLOAT | +| AnnotationValues.java:109:5:122:5 | ArrayValues | intValues | 0 | AnnotationValues.class:0:0:0:0 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | intValues | 1 | AnnotationValues.class:0:0:0:0 | 2 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | intValues | -1 | AnnotationValues.java:112:22:112:22 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | intValues | -2 | AnnotationValues.java:112:25:112:27 | INT | +| AnnotationValues.java:109:5:122:5 | ArrayValues | longValues | 0 | AnnotationValues.class:0:0:0:0 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | longValues | 1 | AnnotationValues.class:0:0:0:0 | 2 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | longValues | -1 | AnnotationValues.java:113:23:113:23 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | longValues | -2 | AnnotationValues.java:113:26:113:29 | LONG | +| AnnotationValues.java:109:5:122:5 | ArrayValues | shortValues | 0 | AnnotationValues.class:0:0:0:0 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | shortValues | 1 | AnnotationValues.class:0:0:0:0 | 2 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | shortValues | -1 | AnnotationValues.java:111:24:111:24 | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | shortValues | -2 | AnnotationValues.java:111:27:111:31 | SHORT | +| AnnotationValues.java:109:5:122:5 | ArrayValues | stringValues | 0 | AnnotationValues.class:0:0:0:0 | "a" | +| AnnotationValues.java:109:5:122:5 | ArrayValues | stringValues | 1 | AnnotationValues.class:0:0:0:0 | "b" | +| AnnotationValues.java:109:5:122:5 | ArrayValues | stringValues | -1 | AnnotationValues.java:118:25:118:27 | "a" | +| AnnotationValues.java:109:5:122:5 | ArrayValues | stringValues | -2 | AnnotationValues.java:118:30:118:32 | "b" | +enumConstantArrayValue +| AnnotationValues.java:90:5:90:16 | ArrayValues | enumValues | AnnotationValues.java:14:9:14:15 | DEFAULT | +| AnnotationValues.java:93:5:106:5 | ArrayValues | enumValues | AnnotationValues.java:15:9:15:9 | A | +| AnnotationValues.java:109:5:122:5 | ArrayValues | enumValues | AnnotationValues.java:15:9:15:9 | A | +| AnnotationValues.java:109:5:122:5 | ArrayValues | enumValues | AnnotationValues.java:16:9:16:9 | B | +stringArrayValue +| AnnotationValues.java:90:5:90:16 | ArrayValues | stringValues | \u0000 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | stringValues | a | +| AnnotationValues.java:109:5:122:5 | ArrayValues | stringValues | a | +| AnnotationValues.java:109:5:122:5 | ArrayValues | stringValues | b | +intArrayValue +| AnnotationValues.java:90:5:90:16 | ArrayValues | byteValues | -1 | +| AnnotationValues.java:90:5:90:16 | ArrayValues | charValues | 65535 | +| AnnotationValues.java:90:5:90:16 | ArrayValues | intValues | -1 | +| AnnotationValues.java:90:5:90:16 | ArrayValues | shortValues | -1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | byteValues | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | charValues | 97 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | intValues | 1 | +| AnnotationValues.java:93:5:106:5 | ArrayValues | shortValues | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | byteValues | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | byteValues | 2 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | charValues | 97 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | charValues | 98 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | intValues | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | intValues | 2 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | shortValues | 1 | +| AnnotationValues.java:109:5:122:5 | ArrayValues | shortValues | 2 | +typeArrayValue +| AnnotationValues.java:90:5:90:16 | ArrayValues | classValues | AnnotationValues.java:1:7:1:22 | AnnotationValues | +| AnnotationValues.java:93:5:106:5 | ArrayValues | classValues | AnnotationValues.java:74:16:74:26 | ArrayValues | +| AnnotationValues.java:109:5:122:5 | ArrayValues | classValues | AnnotationValues.java:23:16:23:27 | SingleValues | +| AnnotationValues.java:109:5:122:5 | ArrayValues | classValues | AnnotationValues.java:74:16:74:26 | ArrayValues | diff --git a/java/ql/test/library-tests/annotations/Annotation-values.ql b/java/ql/test/library-tests/annotations/Annotation-values.ql new file mode 100644 index 00000000000..65e7be6650c --- /dev/null +++ b/java/ql/test/library-tests/annotations/Annotation-values.ql @@ -0,0 +1,37 @@ +import java + +class RelevantAnnotation extends Annotation { + RelevantAnnotation() { + getCompilationUnit().hasName("AnnotationValues") and getCompilationUnit().fromSource() + } +} + +query Expr value(RelevantAnnotation a, string name) { result = a.getValue(name) } + +query EnumConstant enumConstantValue(RelevantAnnotation a, string name) { + result = a.getEnumConstantValue(name) +} + +query string stringValue(RelevantAnnotation a, string name) { result = a.getStringValue(name) } + +query int intValue(RelevantAnnotation a, string name) { result = a.getIntValue(name) } + +query boolean booleanValue(RelevantAnnotation a, string name) { result = a.getBooleanValue(name) } + +query Type typeValue(RelevantAnnotation a, string name) { result = a.getTypeValue(name) } + +query Expr arrayValue(RelevantAnnotation a, string name, int index) { + result = a.getArrayValue(name, index) +} + +query EnumConstant enumConstantArrayValue(RelevantAnnotation a, string name) { + result = a.getAnEnumConstantArrayValue(name) +} + +query string stringArrayValue(RelevantAnnotation a, string name) { + result = a.getAStringArrayValue(name) +} + +query int intArrayValue(RelevantAnnotation a, string name) { result = a.getAnIntArrayValue(name) } + +query Type typeArrayValue(RelevantAnnotation a, string name) { result = a.getATypeArrayValue(name) } diff --git a/java/ql/test/library-tests/annotations/AnnotationType.expected b/java/ql/test/library-tests/annotations/AnnotationType.expected new file mode 100644 index 00000000000..77d9abf5016 --- /dev/null +++ b/java/ql/test/library-tests/annotations/AnnotationType.expected @@ -0,0 +1,13 @@ +annotationType +| AnnotationType.java:11:16:11:34 | InheritedAnnotation | inherited | | CLASS | +| AnnotationType.java:14:16:14:35 | DocumentedAnnotation | documented | | CLASS | +| AnnotationType.java:16:16:16:34 | ContainerAnnotation | | | CLASS | +| AnnotationType.java:21:16:21:35 | RepeatableAnnotation | repeatable | | CLASS | +| AnnotationType.java:25:16:25:26 | EmptyTarget | | | CLASS | +| AnnotationType.java:28:16:28:27 | SingleTarget | | ANNOTATION_TYPE | CLASS | +| AnnotationType.java:43:16:43:25 | AllTargets | | ANNOTATION_TYPE,CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,MODULE,PACKAGE,PARAMETER,RECORD_COMPONENT,TYPE_PARAMETER,TYPE_USE | CLASS | +| AnnotationType.java:47:16:47:29 | ClassRetention | | | CLASS | +| AnnotationType.java:50:16:50:31 | RuntimeRetention | | | RUNTIME | +| AnnotationType.java:53:16:53:30 | SourceRetention | | | SOURCE | +containingAnnotationType +| AnnotationType.java:21:16:21:35 | RepeatableAnnotation | AnnotationType.java:16:16:16:34 | ContainerAnnotation | diff --git a/java/ql/test/library-tests/annotations/AnnotationType.java b/java/ql/test/library-tests/annotations/AnnotationType.java new file mode 100644 index 00000000000..94498ce36f2 --- /dev/null +++ b/java/ql/test/library-tests/annotations/AnnotationType.java @@ -0,0 +1,54 @@ +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +class AnnotationType { + @Inherited + @interface InheritedAnnotation {} + + @Documented + @interface DocumentedAnnotation {} + + @interface ContainerAnnotation { + RepeatableAnnotation[] value(); + } + + @Repeatable(ContainerAnnotation.class) + @interface RepeatableAnnotation {} + + + @Target({}) + @interface EmptyTarget {} + + @Target(ElementType.ANNOTATION_TYPE) + @interface SingleTarget {} + + @Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.METHOD, + ElementType.MODULE, + ElementType.PACKAGE, + ElementType.PARAMETER, + ElementType.RECORD_COMPONENT, + ElementType.TYPE_PARAMETER, + ElementType.TYPE_USE + }) + @interface AllTargets {} + + + @Retention(RetentionPolicy.CLASS) + @interface ClassRetention {} + + @Retention(RetentionPolicy.RUNTIME) + @interface RuntimeRetention {} + + @Retention(RetentionPolicy.SOURCE) + @interface SourceRetention {} +} diff --git a/java/ql/test/library-tests/annotations/AnnotationType.ql b/java/ql/test/library-tests/annotations/AnnotationType.ql new file mode 100644 index 00000000000..268bc876448 --- /dev/null +++ b/java/ql/test/library-tests/annotations/AnnotationType.ql @@ -0,0 +1,46 @@ +import java + +class RelevantAnnotationType extends AnnotationType { + RelevantAnnotationType() { getCompilationUnit().hasName("AnnotationType") } +} + +query predicate annotationType( + RelevantAnnotationType t, string flagsString, string targets, string retentionPolicy +) { + flagsString = + concat(string s | + t.isInherited() and s = "inherited" + or + t.isDocumented() and s = "documented" + or + t.isRepeatable() and s = "repeatable" + | + s, "," order by s + ) and + ( + // Workaround to test if no explicit @Target is specified; in that case any string except + // TYPE_USE, which represents type contexts, is considered a target because it might be + // added to ElementType in a future JDK version + if t.isATargetType("") + then + if t.isATargetType("TYPE_USE") + then targets = "BUG: Includes TYPE_USE" + else targets = "" + else + targets = + concat(string s | + exists(EnumConstant elementType | + elementType.getDeclaringType().hasQualifiedName("java.lang.annotation", "ElementType") and + s = elementType.getName() and + t.isATargetType(s) + ) + | + s, "," order by s + ) + ) and + retentionPolicy = t.getRetentionPolicy() +} + +query AnnotationType containingAnnotationType(RelevantAnnotationType t) { + result = t.getContainingAnnotationType() +} diff --git a/java/ql/test/library-tests/annotations/AnnotationValues.java b/java/ql/test/library-tests/annotations/AnnotationValues.java new file mode 100644 index 00000000000..a40085fe015 --- /dev/null +++ b/java/ql/test/library-tests/annotations/AnnotationValues.java @@ -0,0 +1,124 @@ +class AnnotationValues { + private static final byte BYTE = 2; + private static final short SHORT = 2; + private static final int INT = 2; + private static final long LONG = 2; + private static final float FLOAT = 2; + private static final double DOUBLE = 2; + private static final boolean BOOLEAN = true; + private static final char CHAR = 'b'; + + private static final String STRING = "b"; + + enum CustomEnum { + DEFAULT, + A, + B + } + + @interface CustomAnnotation { + String value(); + } + + @interface SingleValues { + byte byteValue() default -1; + short shortValue() default -1; + int intValue() default -1; + long longValue() default -1; + float floatValue() default -1; + double doubleValue() default -1; + boolean booleanValue() default false; + char charValue() default '\uFFFF'; + + String stringValue() default "\0"; + Class classValue() default AnnotationValues.class; + CustomEnum enumValue() default CustomEnum.DEFAULT; + CustomAnnotation annotationValue() default @CustomAnnotation("default"); + } + + @SingleValues + private int singleValuesDefault; + + @SingleValues( + byteValue = 1, + shortValue = 1, + intValue = 1, + longValue = 1, + floatValue = 1, + doubleValue = 1, + booleanValue = true, + charValue = 1, + stringValue = "a", + classValue = SingleValues.class, + enumValue = CustomEnum.A, + annotationValue = @CustomAnnotation("single") + ) + private int singleValues; + + @SingleValues( + byteValue = BYTE, + shortValue = SHORT, + intValue = INT, + longValue = LONG, + floatValue = FLOAT, + doubleValue = DOUBLE, + booleanValue = BOOLEAN, + charValue = CHAR, + stringValue = STRING, + classValue = SingleValues.class, + enumValue = CustomEnum.A, + annotationValue = @CustomAnnotation("single") + ) + private int singleValuesConstants; + + @interface ArrayValues { + byte[] byteValues() default -1; + short[] shortValues() default -1; + int[] intValues() default -1; + long[] longValues() default -1; + float[] floatValues() default -1; + double[] doubleValues() default -1; + boolean[] booleanValues() default false; + char[] charValues() default '\uFFFF'; + + String[] stringValues() default "\0"; + Class[] classValues() default AnnotationValues.class; + CustomEnum[] enumValues() default CustomEnum.DEFAULT; + CustomAnnotation[] annotationValues() default @CustomAnnotation("default"); + } + + @ArrayValues + private int arrayValuesDefault; + + @ArrayValues( + byteValues = 1, + shortValues = 1, + intValues = 1, + longValues = 1, + floatValues = 1, + doubleValues = 1, + booleanValues = true, + charValues = 'a', + stringValues = "a", + classValues = ArrayValues.class, + enumValues = CustomEnum.A, + annotationValues = @CustomAnnotation("single") + ) + private int arrayValuesSingleExpr; + + @ArrayValues( + byteValues = {1, BYTE}, + shortValues = {1, SHORT}, + intValues = {1, INT}, + longValues = {1, LONG}, + floatValues = {1, FLOAT}, + doubleValues = {1, DOUBLE}, + booleanValues = {false, BOOLEAN}, + charValues = {'a', CHAR}, + stringValues = {"a", "b"}, + classValues = {SingleValues.class, ArrayValues.class}, + enumValues = {CustomEnum.A, CustomEnum.B}, + annotationValues = {@CustomAnnotation("first"), @CustomAnnotation("second")} + ) + private int arrayValues; +} diff --git a/java/ql/test/library-tests/annotations/options b/java/ql/test/library-tests/annotations/options new file mode 100644 index 00000000000..fc57fe025b9 --- /dev/null +++ b/java/ql/test/library-tests/annotations/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -source 16 -target 16 diff --git a/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll index d7f620f6431..5147bcf7f2a 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/TaintTracking.qll @@ -984,7 +984,7 @@ module TaintTracking { * * `` is one of: `contains`, `has`, `hasOwnProperty` * - * Note that the `includes` method is covered by `StringInclusionSanitizer`. + * Note that the `includes` method is covered by `MembershipTestSanitizer`. */ class WhitelistContainmentCallSanitizer extends AdditionalSanitizerGuardNode, DataFlow::MethodCallNode { @@ -1171,7 +1171,7 @@ module TaintTracking { /** * A check of form `x.indexOf(y) > 0` or similar, which sanitizes `y` in the "then" branch. * - * The more typical case of `x.indexOf(y) >= 0` is covered by `StringInclusionSanitizer`. + * The more typical case of `x.indexOf(y) >= 0` is covered by `MembershipTestSanitizer`. */ class PositiveIndexOfSanitizer extends AdditionalSanitizerGuardNode, DataFlow::ValueNode { MethodCallExpr indexOf; diff --git a/javascript/ql/src/Security/CWE-367/FileSystemRace.ql b/javascript/ql/src/Security/CWE-367/FileSystemRace.ql index be410f92621..b3fb4c04ac1 100644 --- a/javascript/ql/src/Security/CWE-367/FileSystemRace.ql +++ b/javascript/ql/src/Security/CWE-367/FileSystemRace.ql @@ -18,32 +18,44 @@ import javascript * A call that checks a property of some file. */ class FileCheck extends DataFlow::CallNode { + string member; + FileCheck() { - this = - NodeJSLib::FS::moduleMember([ - "open", "openSync", "exists", "existsSync", "stat", "statSync", "lstat", "lstatSync", - "fstat", "fstatSync", "access", "accessSync" - ]).getACall() + member = + [ + "open", "openSync", "exists", "existsSync", "stat", "statSync", "lstat", "lstatSync", + "fstat", "fstatSync", "access", "accessSync" + ] and + this = NodeJSLib::FS::moduleMember(member).getACall() } DataFlow::Node getPathArgument() { result = this.getArgument(0) } + + /** Holds if this call is a simple existence check for a file. */ + predicate isExistsCheck() { member = ["exists", "existsSync"] } } /** * A call that modifies or otherwise interacts with a file. */ class FileUse extends DataFlow::CallNode { + string member; + FileUse() { - this = - NodeJSLib::FS::moduleMember([ - // these are the six methods that accept file paths and file descriptors - "readFile", "readFileSync", "writeFile", "writeFileSync", "appendFile", "appendFileSync", - // don't use "open" after e.g. "access" - "open", "openSync" - ]).getACall() + member = + [ + // these are the six methods that accept file paths and file descriptors + "readFile", "readFileSync", "writeFile", "writeFileSync", "appendFile", "appendFileSync", + // don't use "open" after e.g. "access" + "open", "openSync" + ] and + this = NodeJSLib::FS::moduleMember(member).getACall() } DataFlow::Node getPathArgument() { result = this.getArgument(0) } + + /** Holds if this call reads from a file. */ + predicate isFileRead() { member = ["readFile", "readFileSync"] } } /** @@ -101,5 +113,6 @@ from FileCheck check, FileUse use where checkAndUseOnSame(check, use) and useAfterCheck(check, use) and + not (check.isExistsCheck() and use.isFileRead()) and // a read after an exists check is fine not getAFileHandle(DataFlow::TypeTracker::end()).flowsTo(use.getPathArgument()) select use, "The file may have changed since it $@.", check, "was checked" diff --git a/javascript/ql/test/query-tests/Security/CWE-367/tst.js b/javascript/ql/test/query-tests/Security/CWE-367/tst.js index 9682f2bd8a3..a0dc22eab3c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-367/tst.js +++ b/javascript/ql/test/query-tests/Security/CWE-367/tst.js @@ -36,3 +36,8 @@ fs.access("myfile", (err) => { // .... }); }); + +const filePath3 = createFile(); +if (fs.existsSync(filePath3)) { + fs.readFileSync(filePath3); // OK - a read after an existence check is OK +} diff --git a/misc/bazel/workspace.bzl b/misc/bazel/workspace.bzl index 7b89bff5693..3231cd4eaa6 100644 --- a/misc/bazel/workspace.bzl +++ b/misc/bazel/workspace.bzl @@ -26,6 +26,14 @@ def codeql_workspace(repository_name = "codeql"): sha256 = sha256, ) + http_archive( + name = "fishhook", + url = "https://github.com/facebook/fishhook/archive/aadc161ac3b80db07a9908851839a17ba63a9eb1.zip", + build_file = "@%s//swift/tools/fishhook:BUILD.fishhook.bazel" % repository_name, + strip_prefix = "fishhook-aadc161ac3b80db07a9908851839a17ba63a9eb1", + sha256 = "9f2cdee6dcc2039d4c47d25ab5141fe0678ce6ed27ef482cab17fe9fa38a30ce", + ) + maybe( repo_rule = http_archive, name = "rules_pkg", diff --git a/python/ql/lib/design.md b/python/ql/lib/design.md new file mode 100644 index 00000000000..a390f50beee --- /dev/null +++ b/python/ql/lib/design.md @@ -0,0 +1,24 @@ +# The Python libraries + +The Python libraries are a collection of libraries for analysing Python code. +Everythng can be imported by importing `python.qll`. + +## The analysis layers + +The analysis is built up in layers. the stack looks like this: + +- AST (coms from the extractor) +- Control flow graph (CFG) (built by the extractor) +- SSA +- Call graph +- Data flow + +## Avoiding non-monotonic recursion + +Given the many interactivg layers, it is imprtant to decie which predicates are allowed to be mutually recursive in order to avoid non-monotonic recursion when negation is used to express the predicates. +As an example, we have defined local source as those whcih do not receive local flow. This means that the local flow relation is not allowed to be recursive with anything depending on local sources. + +Some particular reatrictions to keep in mind: + +- Typetracking needs to use a local flow step not including summaries +- Typetracking needs to use a call graph not including summaries diff --git a/python/ql/lib/semmle/python/ApiGraphs.qll b/python/ql/lib/semmle/python/ApiGraphs.qll index 1b1851114bf..786d45e4d8e 100644 --- a/python/ql/lib/semmle/python/ApiGraphs.qll +++ b/python/ql/lib/semmle/python/ApiGraphs.qll @@ -380,6 +380,21 @@ module API { not m.matches("%.%") } + /** + * Holds if an import of module `m` exists. + * + * This is determined without referring to `Node`, + * allowing this predicate to be used in a negative + * context when constructing new nodes. + */ + predicate moduleImportExists(string m) { + Impl::isImported(m) and + // restrict `moduleImport` so it will never give results for a dotted name. Note + // that we cannot move this logic to the `MkModuleImport` construction, since we + // need the intermediate API graph nodes for the prefixes in `import foo.bar.baz`. + not m.matches("%.%") + } + /** Gets a node corresponding to the built-in with the given name, if any. */ Node builtin(string n) { result = moduleImport("builtins").getMember(n) } @@ -605,14 +620,38 @@ module API { * * Ignores relative imports, such as `from ..foo.bar import baz`. */ - private predicate imports(DataFlow::Node imp, string name) { + private predicate imports(DataFlow::CfgNode imp, string name) { exists(PY::ImportExprNode iexpr | - imp.asCfgNode() = iexpr and + imp.getNode() = iexpr and not iexpr.getNode().isRelative() and name = iexpr.getNode().getImportedModuleName() ) } + /** + * Holds if the module `name` is imported. + * + * This is determined syntactically. + */ + cached + predicate isImported(string name) { + // Ignore the following module name for Python 2, as we alias `__builtin__` to `builtins` elsewhere + (name != "__builtin__" or PY::major_version() = 3) and + ( + exists(PY::ImportExpr iexpr | + not iexpr.isRelative() and + name = iexpr.getImportedModuleName() + ) + or + // When we `import foo.bar.baz` we want to create API graph nodes also for the prefixes + // `foo` and `foo.bar`: + name = any(PY::ImportExpr e | not e.isRelative()).getAnImportedModuleName() + ) + or + // The `builtins` module should always be implicitly available + name = "builtins" + } + private import semmle.python.dataflow.new.internal.Builtins private import semmle.python.dataflow.new.internal.ImportStar @@ -631,7 +670,7 @@ module API { */ private TApiNode potential_import_star_base(PY::Scope s) { exists(DataFlow::Node n | - n.asCfgNode() = ImportStar::potentialImportStarBase(s) and + n.(DataFlow::CfgNode).getNode() = ImportStar::potentialImportStarBase(s) and use(result, n) ) } @@ -653,17 +692,17 @@ module API { or // TODO: I had expected `DataFlow::AttrWrite` to contain the attribute writes from a dict, that's how JS works. exists(PY::Dict dict, PY::KeyValuePair item | - dict = pred.asExpr() and + dict = pred.(DataFlow::ExprNode).getNode().getNode() and dict.getItem(_) = item and lbl = Label::member(item.getKey().(PY::StrConst).getS()) and - rhs.asExpr() = item.getValue() + rhs.(DataFlow::ExprNode).getNode().getNode() = item.getValue() ) or - exists(PY::CallableExpr fn | fn = pred.asExpr() | + exists(PY::CallableExpr fn | fn = pred.(DataFlow::ExprNode).getNode().getNode() | not fn.getInnerScope().isAsync() and lbl = Label::return() and exists(PY::Return ret | - rhs.asExpr() = ret.getValue() and + rhs.(DataFlow::ExprNode).getNode().getNode() = ret.getValue() and ret.getScope() = fn.getInnerScope() ) ) @@ -716,9 +755,9 @@ module API { // "benign" and let subclasses edges flow through anyway. // see example in https://github.com/django/django/blob/c2250cfb80e27cdf8d098428824da2800a18cadf/tests/auth_tests/test_views.py#L40-L46 ( - ref.asExpr() = clsExpr + ref.(DataFlow::ExprNode).getNode().getNode() = clsExpr or - ref.asExpr() = clsExpr.getADecoratorCall() + ref.(DataFlow::ExprNode).getNode().getNode() = clsExpr.getADecoratorCall() ) ) or @@ -731,7 +770,7 @@ module API { ) or exists(DataFlow::Node def, PY::CallableExpr fn | - rhs(base, def) and fn = trackDefNode(def).asExpr() + rhs(base, def) and fn = trackDefNode(def).(DataFlow::ExprNode).getNode().getNode() | exists(int i, int offset | if exists(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf()) @@ -739,18 +778,19 @@ module API { else offset = 0 | lbl = Label::parameter(i - offset) and - ref.asExpr() = fn.getInnerScope().getArg(i) + ref.(DataFlow::ExprNode).getNode().getNode() = fn.getInnerScope().getArg(i) ) or exists(string name, PY::Parameter param | lbl = Label::keywordParameter(name) and param = fn.getInnerScope().getArgByName(name) and not param.isSelf() and - ref.asExpr() = param + ref.(DataFlow::ExprNode).getNode().getNode() = param ) or lbl = Label::selfParameter() and - ref.asExpr() = any(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf()) + ref.(DataFlow::ExprNode).getNode().getNode() = + any(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf()) ) or // Built-ins, treated as members of the module `builtins` @@ -762,7 +802,7 @@ module API { base = potential_import_star_base(s) and lbl = Label::member(any(string name | - ImportStar::namePossiblyDefinedInImportStar(ref.asCfgNode(), name, s) + ImportStar::namePossiblyDefinedInImportStar(ref.(DataFlow::CfgNode).getNode(), name, s) )) ) or @@ -854,7 +894,7 @@ module API { DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) { Stages::TypeTracking::ref() and result = trackUseNode(src, DataFlow::TypeTracker::end()) and - not result instanceof DataFlow::ModuleVariableNode + result instanceof DataFlow::ExprNode } /** @@ -1044,7 +1084,7 @@ module API { ApiLabel memberFromRef(DataFlow::AttrRef ref) { result = member(ref.getAttributeName()) or - not exists(ref.getAttributeName()) and + ref.unknownAttribute() and result = unknownMember() } diff --git a/python/ql/lib/semmle/python/dataflow/new/FlowSummary.qll b/python/ql/lib/semmle/python/dataflow/new/FlowSummary.qll new file mode 100644 index 00000000000..14b4b6d4796 --- /dev/null +++ b/python/ql/lib/semmle/python/dataflow/new/FlowSummary.qll @@ -0,0 +1,107 @@ +/** Provides classes and predicates for defining flow summaries. */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.frameworks.data.ModelsAsData +private import semmle.python.ApiGraphs +private import internal.FlowSummaryImpl as Impl +private import internal.DataFlowUtil +private import internal.DataFlowPrivate + +// import all instances below +private module Summaries { + private import semmle.python.Frameworks +} + +class SummaryComponent = Impl::Public::SummaryComponent; + +/** Provides predicates for constructing summary components. */ +module SummaryComponent { + private import Impl::Public::SummaryComponent as SC + + predicate parameter = SC::parameter/1; + + predicate argument = SC::argument/1; + + predicate content = SC::content/1; + + /** Gets a summary component that represents a list element. */ + SummaryComponent listElement() { result = content(any(ListElementContent c)) } + + /** Gets a summary component that represents the return value of a call. */ + SummaryComponent return() { result = SC::return(any(ReturnKind rk)) } +} + +class SummaryComponentStack = Impl::Public::SummaryComponentStack; + +/** Provides predicates for constructing stacks of summary components. */ +module SummaryComponentStack { + private import Impl::Public::SummaryComponentStack as SCS + + predicate singleton = SCS::singleton/1; + + predicate push = SCS::push/2; + + predicate argument = SCS::argument/1; + + /** Gets a singleton stack representing the return value of a call. */ + SummaryComponentStack return() { result = singleton(SummaryComponent::return()) } +} + +/** A callable with a flow summary, identified by a unique string. */ +abstract class SummarizedCallable extends LibraryCallable, Impl::Public::SummarizedCallable { + bindingset[this] + SummarizedCallable() { any() } + + /** + * Same as + * + * ```ql + * propagatesFlow( + * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + * ) + * ``` + * + * but uses an external (string) representation of the input and output stacks. + */ + pragma[nomagic] + predicate propagatesFlowExt(string input, string output, boolean preservesValue) { none() } +} + +class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack; +// // This gives access to getNodeFromPath, which is not constrained to `CallNode`s +// // as `resolvedSummaryBase` is. +// private import semmle.python.frameworks.data.internal.ApiGraphModels as AGM +// +// private class SummarizedCallableFromModel extends SummarizedCallable { +// string package; +// string type; +// string path; +// SummarizedCallableFromModel() { +// ModelOutput::relevantSummaryModel(package, type, path, _, _, _) and +// this = package + ";" + type + ";" + path +// } +// override CallCfgNode getACall() { +// exists(API::CallNode base | +// ModelOutput::resolvedSummaryBase(package, type, path, base) and +// result = base.getACall() +// ) +// } +// override ArgumentNode getACallback() { +// exists(API::Node base | +// base = AGM::getNodeFromPath(package, type, path) and +// result = base.getAValueReachableFromSource() +// ) +// } +// override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { +// exists(string kind | +// ModelOutput::relevantSummaryModel(package, type, path, input, output, kind) +// | +// kind = "value" and +// preservesValue = true +// or +// kind = "taint" and +// preservesValue = false +// ) +// } +// } diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/AccessPathSyntax.qll b/python/ql/lib/semmle/python/dataflow/new/internal/AccessPathSyntax.qll similarity index 100% rename from python/ql/lib/semmle/python/frameworks/data/internal/AccessPathSyntax.qll rename to python/ql/lib/semmle/python/dataflow/new/internal/AccessPathSyntax.qll diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll b/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll index 49c8c7e5ab5..ff88dd47d35 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/Attributes.qll @@ -40,7 +40,7 @@ abstract class AttrRef extends Node { or exists(LocalSourceNode nodeFrom | nodeFrom.flowsTo(this.getAttributeNameExpr()) and - attrName = nodeFrom.asExpr().(StrConst).getText() + attrName = nodeFrom.(CfgNode).getNode().getNode().(StrConst).getText() ) } @@ -50,6 +50,9 @@ abstract class AttrRef extends Node { * better results. */ abstract string getAttributeName(); + + /** Holds if a name could not be determined for this attribute. */ + predicate unknownAttribute() { not exists(this.getAttributeName()) } } /** @@ -175,7 +178,7 @@ private class SetAttrCallAsAttrWrite extends AttrWrite, CfgNode { override ExprNode getAttributeNameExpr() { result.asCfgNode() = node.getName() } override string getAttributeName() { - result = this.getAttributeNameExpr().asExpr().(StrConst).getText() + result = this.getAttributeNameExpr().(CfgNode).getNode().getNode().(StrConst).getText() } } @@ -251,7 +254,7 @@ private class GetAttrCallAsAttrRead extends AttrRead, CfgNode { override ExprNode getAttributeNameExpr() { result.asCfgNode() = node.getName() } override string getAttributeName() { - result = this.getAttributeNameExpr().asExpr().(StrConst).getText() + result = this.getAttributeNameExpr().(CfgNode).getNode().getNode().(StrConst).getText() } } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/Builtins.qll b/python/ql/lib/semmle/python/dataflow/new/internal/Builtins.qll index 84fbf9398eb..9ed9e7d7a2b 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/Builtins.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/Builtins.qll @@ -60,9 +60,9 @@ module Builtins { * Currently this is an over-approximation, and may not account for things like overwriting a * built-in with a different value. */ - DataFlow::Node likelyBuiltin(string name) { + DataFlow::CfgNode likelyBuiltin(string name) { exists(Module m | - result.asCfgNode() = + result.getNode() = any(NameNode n | possible_builtin_accessed_in_module(n, name, m) and not possible_builtin_defined_in_module(name, m) diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatchPointsTo.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatchPointsTo.qll index 1d50a12364f..09ba8238155 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatchPointsTo.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatchPointsTo.qll @@ -7,15 +7,22 @@ private import python private import DataFlowPublic private import semmle.python.SpecialMethods +private import FlowSummaryImpl as FlowSummaryImpl /** A parameter position represented by an integer. */ class ParameterPosition extends int { ParameterPosition() { exists(any(DataFlowCallable c).getParameter(this)) } + + /** Holds if this position represents a positional parameter at position `pos`. */ + predicate isPositional(int pos) { this = pos } // with the current representation, all parameters are positional } /** An argument position represented by an integer. */ class ArgumentPosition extends int { - ArgumentPosition() { exists(any(DataFlowCall c).getArg(this)) } + ArgumentPosition() { this in [-2, -1] or exists(any(Call c).getArg(this)) } + + /** Holds if this position represents a positional argument at position `pos`. */ + predicate isPositional(int pos) { this = pos } // with the current representation, all arguments are positional } /** Holds if arguments at position `apos` match parameters at position `ppos`. */ @@ -96,7 +103,7 @@ module ArgumentPassing { * Used to limit the size of predicates. */ predicate connects(CallNode call, CallableValue callable) { - exists(DataFlowCall c | + exists(NormalCall c | call = c.getNode() and callable = c.getCallable().getCallableValue() ) @@ -268,6 +275,18 @@ module ArgumentPassing { import ArgumentPassing +/** A callable defined in library code, identified by a unique string. */ +abstract class LibraryCallable extends string { + bindingset[this] + LibraryCallable() { any() } + + /** Gets a call to this library callable. */ + abstract CallCfgNode getACall(); + + /** Gets a data-flow node, where this library callable is used as a call-back. */ + abstract ArgumentNode getACallback(); +} + /** * IPA type for DataFlowCallable. * @@ -282,27 +301,33 @@ newtype TDataFlowCallable = callable instanceof ClassValue } or TLambda(Function lambda) { lambda.isLambda() } or - TModule(Module m) + TModule(Module m) or + TLibraryCallable(LibraryCallable callable) /** A callable. */ -abstract class DataFlowCallable extends TDataFlowCallable { +class DataFlowCallable extends TDataFlowCallable { /** Gets a textual representation of this element. */ - abstract string toString(); + string toString() { result = "DataFlowCallable" } /** Gets a call to this callable. */ - abstract CallNode getACall(); + CallNode getACall() { none() } /** Gets the scope of this callable */ - abstract Scope getScope(); + Scope getScope() { none() } /** Gets the specified parameter of this callable */ - abstract NameNode getParameter(int n); + NameNode getParameter(int n) { none() } /** Gets the name of this callable. */ - abstract string getName(); + string getName() { none() } - /** Gets a callable value for this callable, if one exists. */ - abstract CallableValue getCallableValue(); + /** Gets a callable value for this callable, if any. */ + CallableValue getCallableValue() { none() } + + /** Gets the underlying library callable, if any. */ + LibraryCallable asLibraryCallable() { this = TLibraryCallable(result) } + + Location getLocation() { none() } } /** A class representing a callable value. */ @@ -343,6 +368,8 @@ class DataFlowLambda extends DataFlowCallable, TLambda { override FunctionValue getCallableValue() { result.getOrigin().getNode() = lambda.getDefinition() } + + Expr getDefinition() { result = lambda.getDefinition() } } /** A class representing the scope in which a `ModuleVariableNode` appears. */ @@ -364,6 +391,27 @@ class DataFlowModuleScope extends DataFlowCallable, TModule { override CallableValue getCallableValue() { none() } } +class LibraryCallableValue extends DataFlowCallable, TLibraryCallable { + LibraryCallable callable; + + LibraryCallableValue() { this = TLibraryCallable(callable) } + + override string toString() { result = callable.toString() } + + override CallNode getACall() { result = callable.getACall().getNode() } + + /** Gets a data-flow node, where this library callable is used as a call-back. */ + ArgumentNode getACallback() { result = callable.getACallback() } + + override Scope getScope() { none() } + + override NameNode getParameter(int n) { none() } + + override string getName() { result = callable } + + override LibraryCallable asLibraryCallable() { result = callable } +} + /** * IPA type for DataFlowCall. * @@ -379,90 +427,140 @@ class DataFlowModuleScope extends DataFlowCallable, TModule { * TODO: Add `TClassMethodCall` mapping `cls` appropriately. */ newtype TDataFlowCall = - TFunctionCall(CallNode call) { call = any(FunctionValue f).getAFunctionCall() } or - /** Bound methods need to make room for the explicit self parameter */ - TMethodCall(CallNode call) { call = any(FunctionValue f).getAMethodCall() } or - TClassCall(CallNode call) { call = any(ClassValue c | not c.isAbsent()).getACall() } or - TSpecialCall(SpecialMethodCallNode special) + /** + * Includes function calls, method calls, class calls and library calls. + * All these will be associated with a `CallNode`. + */ + TNormalCall(CallNode call) or + /** + * Includes calls to special methods. + * These will be associated with a `SpecialMethodCallNode`. + */ + TSpecialCall(SpecialMethodCallNode special) or + /** A synthesized call inside a summarized callable */ + TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, Node receiver) { + FlowSummaryImpl::Private::summaryCallbackRange(c, receiver) + } -/** A call. */ +/** A call found in the program source (as opposed to a synthesised summary call). */ +class TExtractedDataFlowCall = TSpecialCall or TNormalCall; + +/** A call that is taken into account by the global data flow computation. */ abstract class DataFlowCall extends TDataFlowCall { /** Gets a textual representation of this element. */ abstract string toString(); - /** Get the callable to which this call goes. */ + /** Get the callable to which this call goes, if such exists. */ abstract DataFlowCallable getCallable(); /** * Gets the argument to this call that will be sent - * to the `n`th parameter of the callable. + * to the `n`th parameter of the callable, if any. */ abstract Node getArg(int n); - /** Get the control flow node representing this call. */ + /** Get the control flow node representing this call, if any. */ abstract ControlFlowNode getNode(); /** Gets the enclosing callable of this call. */ abstract DataFlowCallable getEnclosingCallable(); /** Gets the location of this dataflow call. */ - Location getLocation() { result = this.getNode().getLocation() } + abstract Location getLocation(); + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** A call found in the program source (as opposed to a synthesised call). */ +abstract class ExtractedDataFlowCall extends DataFlowCall, TExtractedDataFlowCall { + final override Location getLocation() { result = this.getNode().getLocation() } + + abstract override DataFlowCallable getCallable(); + + abstract override Node getArg(int n); + + abstract override ControlFlowNode getNode(); +} + +/** A call associated with a `CallNode`. */ +class NormalCall extends ExtractedDataFlowCall, TNormalCall { + CallNode call; + + NormalCall() { this = TNormalCall(call) } + + override string toString() { result = call.toString() } + + abstract override Node getArg(int n); + + override CallNode getNode() { result = call } + + abstract override DataFlowCallable getCallable(); + + override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getNode().getScope() } } /** - * A call to a function/lambda. + * A call to a function. * This excludes calls to bound methods, classes, and special methods. * Bound method calls and class calls insert an argument for the explicit * `self` parameter, and special method calls have special argument passing. */ -class FunctionCall extends DataFlowCall, TFunctionCall { - CallNode call; - DataFlowCallable callable; +class FunctionCall extends NormalCall { + DataFlowCallableValue callable; FunctionCall() { - this = TFunctionCall(call) and + call = any(FunctionValue f).getAFunctionCall() and call = callable.getACall() } - override string toString() { result = call.toString() } + override Node getArg(int n) { result = getArg(call, TNoShift(), callable.getCallableValue(), n) } + + override DataFlowCallable getCallable() { result = callable } +} + +/** A call to a lambda. */ +class LambdaCall extends NormalCall { + DataFlowLambda callable; + + LambdaCall() { + call = callable.getACall() and + callable = TLambda(any(Function f)) + } override Node getArg(int n) { result = getArg(call, TNoShift(), callable.getCallableValue(), n) } - override ControlFlowNode getNode() { result = call } - override DataFlowCallable getCallable() { result = callable } - - override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getNode().getScope() } } /** * Represents a call to a bound method call. * The node representing the instance is inserted as argument to the `self` parameter. */ -class MethodCall extends DataFlowCall, TMethodCall { - CallNode call; +class MethodCall extends NormalCall { FunctionValue bm; - MethodCall() { - this = TMethodCall(call) and - call = bm.getACall() - } + MethodCall() { call = bm.getAMethodCall() } private CallableValue getCallableValue() { result = bm } - override string toString() { result = call.toString() } - override Node getArg(int n) { n > 0 and result = getArg(call, TShiftOneUp(), this.getCallableValue(), n) or n = 0 and result = TCfgNode(call.getFunction().(AttrNode).getObject()) } - override ControlFlowNode getNode() { result = call } - override DataFlowCallable getCallable() { result = TCallableValue(this.getCallableValue()) } - - override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getScope() } } /** @@ -471,34 +569,27 @@ class MethodCall extends DataFlowCall, TMethodCall { * That makes the call node be the post-update node holding the value of the object * after the constructor has run. */ -class ClassCall extends DataFlowCall, TClassCall { - CallNode call; +class ClassCall extends NormalCall { ClassValue c; ClassCall() { - this = TClassCall(call) and + not c.isAbsent() and call = c.getACall() } private CallableValue getCallableValue() { c.getScope().getInitMethod() = result.getScope() } - override string toString() { result = call.toString() } - override Node getArg(int n) { n > 0 and result = getArg(call, TShiftOneUp(), this.getCallableValue(), n) or n = 0 and result = TSyntheticPreUpdateNode(TCfgNode(call)) } - override ControlFlowNode getNode() { result = call } - override DataFlowCallable getCallable() { result = TCallableValue(this.getCallableValue()) } - - override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getScope() } } /** A call to a special method. */ -class SpecialCall extends DataFlowCall, TSpecialCall { +class SpecialCall extends ExtractedDataFlowCall, TSpecialCall { SpecialMethodCallNode special; SpecialCall() { this = TSpecialCall(special) } @@ -518,8 +609,172 @@ class SpecialCall extends DataFlowCall, TSpecialCall { } } +/** + * A call to a summarized callable, a `LibraryCallable`. + * + * We currently exclude all resolved calls. This means that a call to, say, `map`, which + * is a `ClassCall`, cannot currently be given a summary. + * We hope to lift this restriction in the future and include all potential calls to summaries + * in this class. + */ +class LibraryCall extends NormalCall { + LibraryCall() { + // TODO: share this with `resolvedCall` + not ( + call = any(DataFlowCallableValue cv).getACall() + or + call = any(DataFlowLambda l).getACall() + or + // TODO: this should be covered by `DataFlowCallableValue`, but a `ClassValue` is not a `CallableValue`. + call = any(ClassValue c).getACall() + ) + } + + // TODO: Implement Python calling convention? + override Node getArg(int n) { result = TCfgNode(call.getArg(n)) } + + // We cannot refer to a `LibraryCallable` here, + // as that could in turn refer to type tracking. + // This call will be tied to a `LibraryCallable` via + // `getViableCallabe` when the global data flow is assembled. + override DataFlowCallable getCallable() { none() } +} + +/** + * A synthesized call inside a callable with a flow summary. + * + * For example, in + * ```python + * map(lambda x: x + 1, [1, 2, 3]) + * ``` + * + * there is a synthesized call to the lambda argument inside `map`. + */ +class SummaryCall extends DataFlowCall, TSummaryCall { + private FlowSummaryImpl::Public::SummarizedCallable c; + private Node receiver; + + SummaryCall() { this = TSummaryCall(c, receiver) } + + /** Gets the data flow node that this call targets. */ + Node getReceiver() { result = receiver } + + override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c } + + override DataFlowCallable getCallable() { none() } + + override Node getArg(int n) { none() } + + override ControlFlowNode getNode() { none() } + + override string toString() { result = "[summary] call to " + receiver + " in " + c } + + override Location getLocation() { none() } +} + +/** + * The value of a parameter at function entry, viewed as a node in a data + * flow graph. + */ +abstract class ParameterNodeImpl extends Node { + abstract Parameter getParameter(); + + /** + * Holds if this node is the parameter of callable `c` at the + * (zero-based) index `i`. + */ + abstract predicate isParameterOf(DataFlowCallable c, int i); +} + +/** A parameter for a library callable with a flow summary. */ +class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode { + private FlowSummaryImpl::Public::SummarizedCallable sc; + private int pos; + + SummaryParameterNode() { this = TSummaryParameterNode(sc, pos) } + + override Parameter getParameter() { none() } + + override predicate isParameterOf(DataFlowCallable c, int i) { + sc = c.asLibraryCallable() and i = pos + } + + override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = sc } + + override string toString() { result = "parameter " + pos + " of " + sc } + + // Hack to return "empty location" + override predicate hasLocationInfo( + string file, int startline, int startcolumn, int endline, int endcolumn + ) { + file = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } +} + +/** A data-flow node used to model flow summaries. */ +class SummaryNode extends Node, TSummaryNode { + private FlowSummaryImpl::Public::SummarizedCallable c; + private FlowSummaryImpl::Private::SummaryNodeState state; + + SummaryNode() { this = TSummaryNode(c, state) } + + override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c } + + override string toString() { result = "[summary] " + state + " in " + c } + + // Hack to return "empty location" + override predicate hasLocationInfo( + string file, int startline, int startcolumn, int endline, int endcolumn + ) { + file = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } +} + +private class SummaryReturnNode extends SummaryNode, ReturnNode { + private ReturnKind rk; + + SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this, rk) } + + override ReturnKind getKind() { result = rk } +} + +private class SummaryArgumentNode extends SummaryNode, ArgumentNode { + SummaryArgumentNode() { FlowSummaryImpl::Private::summaryArgumentNode(_, this, _) } + + override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) { + FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos) + } +} + +private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode { + private Node pre; + + SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) } + + override Node getPreUpdateNode() { result = pre } +} + /** Gets a viable run-time target for the call `call`. */ -DataFlowCallable viableCallable(DataFlowCall call) { result = call.getCallable() } +DataFlowCallable viableCallable(ExtractedDataFlowCall call) { + result = call.getCallable() + or + // A call to a library callable with a flow summary + // In this situation we can not resolve the callable from the call, + // as that would make data flow depend on type tracking. + // Instead we reolve the call from the summary. + exists(LibraryCallable callable | + result = TLibraryCallable(callable) and + call.getNode() = callable.getACall().getNode() + ) +} private newtype TReturnKind = TNormalReturnKind() @@ -533,26 +788,51 @@ class ReturnKind extends TReturnKind { } /** A data flow node that represents a value returned by a callable. */ -class ReturnNode extends CfgNode { - Return ret; - - // See `TaintTrackingImplementation::returnFlowStep` - ReturnNode() { node = ret.getValue().getAFlowNode() } - +abstract class ReturnNode extends Node { /** Gets the kind of this return node. */ ReturnKind getKind() { any() } } -/** A data flow node that represents the output of a call. */ -class OutNode extends CfgNode { - OutNode() { node instanceof CallNode } +/** A data flow node that represents a value returned by a callable. */ +class ExtractedReturnNode extends ReturnNode, CfgNode { + // See `TaintTrackingImplementation::returnFlowStep` + ExtractedReturnNode() { node = any(Return ret).getValue().getAFlowNode() } + + override ReturnKind getKind() { any() } +} + +/** A data-flow node that represents the output of a call. */ +abstract class OutNode extends Node { + /** Gets the underlying call, where this node is a corresponding output of kind `kind`. */ + abstract DataFlowCall getCall(ReturnKind kind); +} + +private module OutNodes { + /** + * A data-flow node that reads a value returned directly by a callable. + */ + class ExprOutNode extends OutNode, ExprNode { + private DataFlowCall call; + + ExprOutNode() { call.(ExtractedDataFlowCall).getNode() = this.getNode() } + + override DataFlowCall getCall(ReturnKind kind) { + result = call and + kind = kind + } + } + + private class SummaryOutNode extends SummaryNode, OutNode { + SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) } + + override DataFlowCall getCall(ReturnKind kind) { + FlowSummaryImpl::Private::summaryOutNode(result, this, kind) + } + } } /** * Gets a node that can read the value returned from `call` with return kind * `kind`. */ -OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { - call.getNode() = result.getNode() and - kind = TNormalReturnKind() -} +OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll index a4756f8bb4c..be44f39f13c 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll @@ -2,6 +2,7 @@ private import python private import DataFlowPublic private import semmle.python.essa.SsaCompute private import semmle.python.dataflow.new.internal.ImportResolution +private import FlowSummaryImpl as FlowSummaryImpl // Since we allow extra data-flow steps from modeled frameworks, we import these // up-front, to ensure these are included. This provides a more seamless experience from // a user point of view, since they don't need to know they need to import a specific @@ -21,7 +22,7 @@ import DataFlowDispatchPointsTo DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() } /** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */ -predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) { +predicate isParameterNode(ParameterNodeImpl p, DataFlowCallable c, ParameterPosition pos) { p.isParameterOf(c, pos) } @@ -77,7 +78,7 @@ module SyntheticPreUpdateNode { * that is mapped to the `self` parameter. That way, constructor calls represent the value of the * object after the constructor (currently only `__init__`) has run. */ - CfgNode objectCreationNode() { result.getNode().(CallNode) = any(ClassCall c).getNode() } + CfgNode objectCreationNode() { result.getNode() = any(ClassCall c).getNode() } } import SyntheticPreUpdateNode @@ -87,6 +88,8 @@ deprecated module syntheticPostUpdateNode = SyntheticPostUpdateNode; /** A module collecting the different reasons for synthesising a post-update node. */ module SyntheticPostUpdateNode { + private import semmle.python.SpecialMethods + /** A post-update node is synthesized for all nodes which satisfy `NeedsSyntheticPostUpdateNode`. */ class SyntheticPostUpdateNode extends PostUpdateNode, TSyntheticPostUpdateNode { NeedsSyntheticPostUpdateNode pre; @@ -136,6 +139,8 @@ module SyntheticPostUpdateNode { Node argumentPreUpdateNode() { result = any(FunctionCall c).getArg(_) or + result = any(LambdaCall c).getArg(_) + or // Avoid argument 0 of method calls as those have read post-update nodes. exists(MethodCall c, int n | n > 0 | result = c.getArg(n)) or @@ -145,11 +150,18 @@ module SyntheticPostUpdateNode { exists(ClassCall c, int n | n > 0 | result = c.getArg(n)) or // any argument of any call that we have not been able to resolve - exists(CallNode call | not call = any(DataFlowCall c).getNode() | + exists(CallNode call | not resolvedCall(call) | result.(CfgNode).getNode() in [call.getArg(_), call.getArgByName(_)] ) } + /** Holds if `call` can be resolved as a normal call */ + private predicate resolvedCall(CallNode call) { + call = any(DataFlowCallableValue cv).getACall() + or + call = any(DataFlowLambda l).getACall() + } + /** Gets the pre-update node associated with a store. This is used for when an object might have its value changed after a store. */ CfgNode storePreUpdateNode() { exists(Attribute a | @@ -287,10 +299,22 @@ module EssaFlow { * This is the local flow predicate that is used as a building block in global * data flow. * + * It includes flow steps from flow summaries. + */ +predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { + simpleLocalFlowStepForTypetracking(nodeFrom, nodeTo) + or + summaryFlowSteps(nodeFrom, nodeTo) +} + +/** + * This is the local flow predicate that is used as a building block in + * type tracking, it does _not_ include steps from flow summaries. + * * Local flow can happen either at import time, when the module is initialised * or at runtime when callables in the module are called. */ -predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { +predicate simpleLocalFlowStepForTypetracking(Node nodeFrom, Node nodeTo) { // If there is local flow out of a node `node`, we want flow // both out of `node` and any post-update node of `node`. exists(Node node | @@ -326,6 +350,34 @@ predicate runtimeLocalFlowStep(Node nodeFrom, Node nodeTo) { EssaFlow::essaFlowStep(nodeFrom, nodeTo) } +predicate summaryFlowSteps(Node nodeFrom, Node nodeTo) { + // If there is local flow out of a node `node`, we want flow + // both out of `node` and any post-update node of `node`. + exists(Node node | + nodeFrom = update(node) and + ( + importTimeSummaryFlowStep(node, nodeTo) or + runtimeSummaryFlowStep(node, nodeTo) + ) + ) +} + +predicate importTimeSummaryFlowStep(Node nodeFrom, Node nodeTo) { + // As a proxy for whether statements can be executed at import time, + // we check if they appear at the top level. + // This will miss statements inside functions called from the top level. + isTopLevel(nodeFrom) and + isTopLevel(nodeTo) and + FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true) +} + +predicate runtimeSummaryFlowStep(Node nodeFrom, Node nodeTo) { + // Anything not at the top level can be executed at runtime. + not isTopLevel(nodeFrom) and + not isTopLevel(nodeTo) and + FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true) +} + /** `ModuleVariable`s are accessed via jump steps at runtime. */ predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) { // Module variable read @@ -474,6 +526,8 @@ predicate storeStep(Node nodeFrom, Content c, Node nodeTo) { matchStoreStep(nodeFrom, c, nodeTo) or any(Orm::AdditionalOrmSteps es).storeStep(nodeFrom, c, nodeTo) + or + FlowSummaryImpl::Private::Steps::summaryStoreStep(nodeFrom, c, nodeTo) } /** @@ -667,6 +721,8 @@ predicate readStep(Node nodeFrom, Content c, Node nodeTo) { attributeReadStep(nodeFrom, c, nodeTo) or kwUnpackReadStep(nodeFrom, c, nodeTo) + or + FlowSummaryImpl::Private::Steps::summaryReadStep(nodeFrom, c, nodeTo) } /** Data flows from a sequence to a subscript of the sequence. */ @@ -791,6 +847,8 @@ predicate clearsContent(Node n, Content c) { matchClearStep(n, c) or attributeClearStep(n, c) + or + FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c) } /** @@ -842,15 +900,38 @@ int accessPathLimit() { result = 5 } predicate forceHighPrecision(Content c) { none() } /** Holds if `n` should be hidden from path explanations. */ -predicate nodeIsHidden(Node n) { none() } +predicate nodeIsHidden(Node n) { + n instanceof SummaryNode + or + n instanceof SummaryParameterNode +} class LambdaCallKind = Unit; /** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */ -predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() } +predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { + // lambda + kind = kind and + creation.asExpr() = c.(DataFlowLambda).getDefinition() + or + // normal function + exists(FunctionDef def | + def.defines(creation.asVar().getSourceVariable()) and + def.getDefinedFunction() = c.(DataFlowCallableValue).getCallableValue().getScope() + ) + or + // summarized function + exists(Call call | + creation.asExpr() = call.getAnArg() and + creation = c.(LibraryCallableValue).getACallback() + ) +} /** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */ -predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() } +predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { + receiver = call.(SummaryCall).getReceiver() and + exists(kind) +} /** Extra data-flow steps needed for lambda flow analysis. */ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() } @@ -862,4 +943,6 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves * One example would be to allow flow like `p.foo = p.bar;`, which is disallowed * by default as a heuristic. */ -predicate allowParameterReturnInSelf(ParameterNode p) { none() } +predicate allowParameterReturnInSelf(ParameterNode p) { + FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p) +} diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll index 8b0357d3f3d..4a00d0aafc3 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll @@ -9,6 +9,7 @@ import Attributes import LocalSources private import semmle.python.essa.SsaCompute private import semmle.python.dataflow.new.internal.ImportStar +private import FlowSummaryImpl as FlowSummaryImpl /** * IPA type for data flow nodes. @@ -100,7 +101,17 @@ newtype TNode = // // So for now we live with having these synthetic ORM nodes for _all_ classes, which // is a bit wasteful, but we don't think it will hurt too much. - TSyntheticOrmModelNode(Class cls) + TSyntheticOrmModelNode(Class cls) or + TSummaryNode( + FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state + ) { + FlowSummaryImpl::Private::summaryNodeRange(c, state) + } or + TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) { + FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos) + } + +class TParameterNode = TCfgNode or TSummaryParameterNode; /** Helper for `Node::getEnclosingCallable`. */ private DataFlowCallable getCallableScope(Scope s) { @@ -277,40 +288,56 @@ ExprNode exprNode(DataFlowExpr e) { result.getNode().getNode() = e } * The value of a parameter at function entry, viewed as a node in a data * flow graph. */ -class ParameterNode extends CfgNode, LocalSourceNode { +class ParameterNode extends Node, TParameterNode instanceof ParameterNodeImpl { + /** Gets the parameter corresponding to this node, if any. */ + final Parameter getParameter() { result = super.getParameter() } +} + +/** A parameter node found in the source code (not in a summary). */ +class ExtractedParameterNode extends ParameterNodeImpl, CfgNode { + //, LocalSourceNode { ParameterDefinition def; - ParameterNode() { + ExtractedParameterNode() { node = def.getDefiningNode() and // Disregard parameters that we cannot resolve // TODO: Make this unnecessary exists(DataFlowCallable c | node = c.getParameter(_)) } - /** - * Holds if this node is the parameter of callable `c` at the - * (zero-based) index `i`. - */ - predicate isParameterOf(DataFlowCallable c, int i) { node = c.getParameter(i) } + override predicate isParameterOf(DataFlowCallable c, int i) { node = c.getParameter(i) } override DataFlowCallable getEnclosingCallable() { this.isParameterOf(result, _) } /** Gets the `Parameter` this `ParameterNode` represents. */ - Parameter getParameter() { result = def.getParameter() } + override Parameter getParameter() { result = def.getParameter() } } +class LocalSourceParameterNode extends ExtractedParameterNode, LocalSourceNode { } + /** Gets a node corresponding to parameter `p`. */ -ParameterNode parameterNode(Parameter p) { result.getParameter() = p } +ExtractedParameterNode parameterNode(Parameter p) { result.getParameter() = p } /** A data flow node that represents a call argument. */ -class ArgumentNode extends Node { - ArgumentNode() { this = any(DataFlowCall c).getArg(_) } - +abstract class ArgumentNode extends Node { /** Holds if this argument occurs at the given position in the given call. */ - predicate argumentOf(DataFlowCall call, int pos) { this = call.getArg(pos) } + abstract predicate argumentOf(DataFlowCall call, ArgumentPosition pos); - /** Gets the call in which this node is an argument. */ - final DataFlowCall getCall() { this.argumentOf(result, _) } + /** Gets the call in which this node is an argument, if any. */ + final ExtractedDataFlowCall getCall() { this.argumentOf(result, _) } +} + +/** A data flow node that represents a call argument found in the source code. */ +class ExtractedArgumentNode extends ArgumentNode { + ExtractedArgumentNode() { this = any(ExtractedDataFlowCall c).getArg(_) } + + final override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) { + this.extractedArgumentOf(call, pos) + } + + predicate extractedArgumentOf(ExtractedDataFlowCall call, ArgumentPosition pos) { + this = call.getArg(pos) + } } /** @@ -390,7 +417,7 @@ class ModuleVariableNode extends Node, TModuleVariableNode { /** Gets an `EssaNode` that corresponds to an assignment of this global variable. */ EssaNode getAWrite() { - result.asVar().getDefinition().(EssaNodeDefinition).definedBy(var, any(DefinitionNode defn)) + result.getVar().getDefinition().(EssaNodeDefinition).definedBy(var, any(DefinitionNode defn)) } /** Gets the possible values of the variable at the end of import time */ diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowUtil.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowUtil.qll index ca10138dd58..fc697d45524 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowUtil.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowUtil.qll @@ -5,12 +5,20 @@ private import python private import DataFlowPrivate import DataFlowPublic +private import FlowSummaryImpl as FlowSummaryImpl /** * Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local * (intra-procedural) step. */ -predicate localFlowStep(Node nodeFrom, Node nodeTo) { simpleLocalFlowStep(nodeFrom, nodeTo) } +predicate localFlowStep(Node nodeFrom, Node nodeTo) { + simpleLocalFlowStep(nodeFrom, nodeTo) + or + // Simple flow through library code is included in the exposed local + // step relation, even though flow is technically inter-procedural. + // This is a convention followed across languages. + FlowSummaryImpl::Private::Steps::summaryThroughStepValue(nodeFrom, nodeTo, _) +} /** * Holds if data flows from `source` to `sink` in zero or more local diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/FlowSummaryImpl.qll b/python/ql/lib/semmle/python/dataflow/new/internal/FlowSummaryImpl.qll new file mode 100644 index 00000000000..d857cdaa359 --- /dev/null +++ b/python/ql/lib/semmle/python/dataflow/new/internal/FlowSummaryImpl.qll @@ -0,0 +1,1247 @@ +/** + * Provides classes and predicates for defining flow summaries. + * + * The definitions in this file are language-independent, and language-specific + * definitions are passed in via the `DataFlowImplSpecific` and + * `FlowSummaryImplSpecific` modules. + */ + +private import FlowSummaryImplSpecific +private import DataFlowImplSpecific::Private +private import DataFlowImplSpecific::Public +private import DataFlowImplCommon + +/** Provides classes and predicates for defining flow summaries. */ +module Public { + private import Private + + /** + * A component used in a flow summary. + * + * Either a parameter or an argument at a given position, a specific + * content type, or a return kind. + */ + class SummaryComponent extends TSummaryComponent { + /** Gets a textual representation of this summary component. */ + string toString() { + exists(ContentSet c | this = TContentSummaryComponent(c) and result = c.toString()) + or + exists(ContentSet c | this = TWithoutContentSummaryComponent(c) and result = "without " + c) + or + exists(ContentSet c | this = TWithContentSummaryComponent(c) and result = "with " + c) + or + exists(ArgumentPosition pos | + this = TParameterSummaryComponent(pos) and result = "parameter " + pos + ) + or + exists(ParameterPosition pos | + this = TArgumentSummaryComponent(pos) and result = "argument " + pos + ) + or + exists(ReturnKind rk | this = TReturnSummaryComponent(rk) and result = "return (" + rk + ")") + } + } + + /** Provides predicates for constructing summary components. */ + module SummaryComponent { + /** Gets a summary component for content `c`. */ + SummaryComponent content(ContentSet c) { result = TContentSummaryComponent(c) } + + /** Gets a summary component where data is not allowed to be stored in `c`. */ + SummaryComponent withoutContent(ContentSet c) { result = TWithoutContentSummaryComponent(c) } + + /** Gets a summary component where data must be stored in `c`. */ + SummaryComponent withContent(ContentSet c) { result = TWithContentSummaryComponent(c) } + + /** Gets a summary component for a parameter at position `pos`. */ + SummaryComponent parameter(ArgumentPosition pos) { result = TParameterSummaryComponent(pos) } + + /** Gets a summary component for an argument at position `pos`. */ + SummaryComponent argument(ParameterPosition pos) { result = TArgumentSummaryComponent(pos) } + + /** Gets a summary component for a return of kind `rk`. */ + SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) } + } + + /** + * A (non-empty) stack of summary components. + * + * A stack is used to represent where data is read from (input) or where it + * is written to (output). For example, an input stack `[Field f, Argument 0]` + * means that data is read from field `f` from the `0`th argument, while an + * output stack `[Field g, Return]` means that data is written to the field + * `g` of the returned object. + */ + class SummaryComponentStack extends TSummaryComponentStack { + /** Gets the head of this stack. */ + SummaryComponent head() { + this = TSingletonSummaryComponentStack(result) or + this = TConsSummaryComponentStack(result, _) + } + + /** Gets the tail of this stack, if any. */ + SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) } + + /** Gets the length of this stack. */ + int length() { + this = TSingletonSummaryComponentStack(_) and result = 1 + or + result = 1 + this.tail().length() + } + + /** Gets the stack obtained by dropping the first `i` elements, if any. */ + SummaryComponentStack drop(int i) { + i = 0 and result = this + or + result = this.tail().drop(i - 1) + } + + /** Holds if this stack contains summary component `c`. */ + predicate contains(SummaryComponent c) { c = this.drop(_).head() } + + /** Gets the bottom element of this stack. */ + SummaryComponent bottom() { + this = TSingletonSummaryComponentStack(result) or result = this.tail().bottom() + } + + /** Gets a textual representation of this stack. */ + string toString() { + exists(SummaryComponent head, SummaryComponentStack tail | + head = this.head() and + tail = this.tail() and + result = tail + "." + head + ) + or + exists(SummaryComponent c | + this = TSingletonSummaryComponentStack(c) and + result = c.toString() + ) + } + } + + /** Provides predicates for constructing stacks of summary components. */ + module SummaryComponentStack { + /** Gets a singleton stack containing `c`. */ + SummaryComponentStack singleton(SummaryComponent c) { + result = TSingletonSummaryComponentStack(c) + } + + /** + * Gets the stack obtained by pushing `head` onto `tail`. + * + * Make sure to override `RequiredSummaryComponentStack::required()` in order + * to ensure that the constructed stack exists. + */ + SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) { + result = TConsSummaryComponentStack(head, tail) + } + + /** Gets a singleton stack for an argument at position `pos`. */ + SummaryComponentStack argument(ParameterPosition pos) { + result = singleton(SummaryComponent::argument(pos)) + } + + /** Gets a singleton stack representing a return of kind `rk`. */ + SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) } + } + + private predicate noComponentSpecificCsv(SummaryComponent sc) { + not exists(getComponentSpecificCsv(sc)) + } + + /** Gets a textual representation of this component used for flow summaries. */ + private string getComponentCsv(SummaryComponent sc) { + result = getComponentSpecificCsv(sc) + or + noComponentSpecificCsv(sc) and + ( + exists(ArgumentPosition pos | + sc = TParameterSummaryComponent(pos) and + result = "Parameter[" + getArgumentPositionCsv(pos) + "]" + ) + or + exists(ParameterPosition pos | + sc = TArgumentSummaryComponent(pos) and + result = "Argument[" + getParameterPositionCsv(pos) + "]" + ) + or + sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue" + ) + } + + /** Gets a textual representation of this stack used for flow summaries. */ + string getComponentStackCsv(SummaryComponentStack stack) { + exists(SummaryComponent head, SummaryComponentStack tail | + head = stack.head() and + tail = stack.tail() and + result = getComponentStackCsv(tail) + "." + getComponentCsv(head) + ) + or + exists(SummaryComponent c | + stack = TSingletonSummaryComponentStack(c) and + result = getComponentCsv(c) + ) + } + + /** + * A class that exists for QL technical reasons only (the IPA type used + * to represent component stacks needs to be bounded). + */ + class RequiredSummaryComponentStack extends Unit { + /** + * Holds if the stack obtained by pushing `head` onto `tail` is required. + */ + abstract predicate required(SummaryComponent head, SummaryComponentStack tail); + } + + /** A callable with a flow summary. */ + abstract class SummarizedCallable extends SummarizedCallableBase { + bindingset[this] + SummarizedCallable() { any() } + + /** + * Holds if data may flow from `input` to `output` through this callable. + * + * `preservesValue` indicates whether this is a value-preserving step + * or a taint-step. + * + * Input specifications are restricted to stacks that end with + * `SummaryComponent::argument(_)`, preceded by zero or more + * `SummaryComponent::return(_)` or `SummaryComponent::content(_)` components. + * + * Output specifications are restricted to stacks that end with + * `SummaryComponent::return(_)` or `SummaryComponent::argument(_)`. + * + * Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero + * or more `SummaryComponent::content(_)` components. + * + * Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an + * optional `SummaryComponent::parameter(_)` component, which in turn can be preceded + * by zero or more `SummaryComponent::content(_)` components. + */ + pragma[nomagic] + predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + none() + } + + /** + * Holds if the summary is auto generated. + */ + predicate isAutoGenerated() { none() } + } + + /** A callable with a flow summary stating there is no flow via the callable. */ + class NegativeSummarizedCallable extends SummarizedCallableBase { + NegativeSummarizedCallable() { negativeSummaryElement(this, _) } + + /** + * Holds if the negative summary is auto generated. + */ + predicate isAutoGenerated() { negativeSummaryElement(this, true) } + } +} + +/** + * Provides predicates for compiling flow summaries down to atomic local steps, + * read steps, and store steps. + */ +module Private { + private import Public + import AccessPathSyntax + + newtype TSummaryComponent = + TContentSummaryComponent(ContentSet c) or + TParameterSummaryComponent(ArgumentPosition pos) or + TArgumentSummaryComponent(ParameterPosition pos) or + TReturnSummaryComponent(ReturnKind rk) or + TWithoutContentSummaryComponent(ContentSet c) or + TWithContentSummaryComponent(ContentSet c) + + private TParameterSummaryComponent thisParam() { + result = TParameterSummaryComponent(instanceParameterPosition()) + } + + newtype TSummaryComponentStack = + TSingletonSummaryComponentStack(SummaryComponent c) or + TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) { + any(RequiredSummaryComponentStack x).required(head, tail) + or + any(RequiredSummaryComponentStack x).required(TParameterSummaryComponent(_), tail) and + head = thisParam() + or + derivedFluentFlowPush(_, _, _, head, tail, _) + } + + pragma[nomagic] + private predicate summary( + SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output, + boolean preservesValue + ) { + c.propagatesFlow(input, output, preservesValue) + or + // observe side effects of callbacks on input arguments + c.propagatesFlow(output, input, preservesValue) and + preservesValue = true and + isCallbackParameter(input) and + isContentOfArgument(output, _) + or + // flow from the receiver of a callback into the instance-parameter + exists(SummaryComponentStack s, SummaryComponentStack callbackRef | + c.propagatesFlow(s, _, _) or c.propagatesFlow(_, s, _) + | + callbackRef = s.drop(_) and + (isCallbackParameter(callbackRef) or callbackRef.head() = TReturnSummaryComponent(_)) and + input = callbackRef.tail() and + output = TConsSummaryComponentStack(thisParam(), input) and + preservesValue = true + ) + or + exists(SummaryComponentStack arg, SummaryComponentStack return | + derivedFluentFlow(c, input, arg, return, preservesValue) + | + arg.length() = 1 and + output = return + or + exists(SummaryComponent head, SummaryComponentStack tail | + derivedFluentFlowPush(c, input, arg, head, tail, 0) and + output = SummaryComponentStack::push(head, tail) + ) + ) + or + // Chain together summaries where values get passed into callbacks along the way + exists(SummaryComponentStack mid, boolean preservesValue1, boolean preservesValue2 | + c.propagatesFlow(input, mid, preservesValue1) and + c.propagatesFlow(mid, output, preservesValue2) and + mid.drop(mid.length() - 2) = + SummaryComponentStack::push(TParameterSummaryComponent(_), + SummaryComponentStack::singleton(TArgumentSummaryComponent(_))) and + preservesValue = preservesValue1.booleanAnd(preservesValue2) + ) + } + + /** + * Holds if `c` has a flow summary from `input` to `arg`, where `arg` + * writes to (contents of) arguments at position `pos`, and `c` has a + * value-preserving flow summary from the arguments at position `pos` + * to a return value (`return`). + * + * In such a case, we derive flow from `input` to (contents of) the return + * value. + * + * As an example, this simplifies modeling of fluent methods: + * for `StringBuilder.append(x)` with a specified value flow from qualifier to + * return value and taint flow from argument 0 to the qualifier, then this + * allows us to infer taint flow from argument 0 to the return value. + */ + pragma[nomagic] + private predicate derivedFluentFlow( + SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg, + SummaryComponentStack return, boolean preservesValue + ) { + exists(ParameterPosition pos | + summary(c, input, arg, preservesValue) and + isContentOfArgument(arg, pos) and + summary(c, SummaryComponentStack::argument(pos), return, true) and + return.bottom() = TReturnSummaryComponent(_) + ) + } + + pragma[nomagic] + private predicate derivedFluentFlowPush( + SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg, + SummaryComponent head, SummaryComponentStack tail, int i + ) { + derivedFluentFlow(c, input, arg, tail, _) and + head = arg.drop(i).head() and + i = arg.length() - 2 + or + exists(SummaryComponent head0, SummaryComponentStack tail0 | + derivedFluentFlowPush(c, input, arg, head0, tail0, i + 1) and + head = arg.drop(i).head() and + tail = SummaryComponentStack::push(head0, tail0) + ) + } + + private predicate isCallbackParameter(SummaryComponentStack s) { + s.head() = TParameterSummaryComponent(_) and exists(s.tail()) + } + + private predicate isContentOfArgument(SummaryComponentStack s, ParameterPosition pos) { + s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail(), pos) + or + s = SummaryComponentStack::argument(pos) + } + + private predicate outputState(SummarizedCallable c, SummaryComponentStack s) { + summary(c, _, s, _) + or + exists(SummaryComponentStack out | + outputState(c, out) and + out.head() = TContentSummaryComponent(_) and + s = out.tail() + ) + or + // Add the argument node corresponding to the requested post-update node + inputState(c, s) and isCallbackParameter(s) + } + + private predicate inputState(SummarizedCallable c, SummaryComponentStack s) { + summary(c, s, _, _) + or + exists(SummaryComponentStack inp | inputState(c, inp) and s = inp.tail()) + or + exists(SummaryComponentStack out | + outputState(c, out) and + out.head() = TParameterSummaryComponent(_) and + s = out.tail() + ) + } + + private newtype TSummaryNodeState = + TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or + TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) } + + /** + * A state used to break up (complex) flow summaries into atomic flow steps. + * For a flow summary + * + * ```ql + * propagatesFlow( + * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + * ) + * ``` + * + * the following states are used: + * + * - `TSummaryNodeInputState(SummaryComponentStack s)`: + * this state represents that the components in `s` _have been read_ from the + * input. + * - `TSummaryNodeOutputState(SummaryComponentStack s)`: + * this state represents that the components in `s` _remain to be written_ to + * the output. + */ + class SummaryNodeState extends TSummaryNodeState { + /** Holds if this state is a valid input state for `c`. */ + pragma[nomagic] + predicate isInputState(SummarizedCallable c, SummaryComponentStack s) { + this = TSummaryNodeInputState(s) and + inputState(c, s) + } + + /** Holds if this state is a valid output state for `c`. */ + pragma[nomagic] + predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) { + this = TSummaryNodeOutputState(s) and + outputState(c, s) + } + + /** Gets a textual representation of this state. */ + string toString() { + exists(SummaryComponentStack s | + this = TSummaryNodeInputState(s) and + result = "read: " + s + ) + or + exists(SummaryComponentStack s | + this = TSummaryNodeOutputState(s) and + result = "to write: " + s + ) + } + } + + /** + * Holds if `state` represents having read from a parameter at position + * `pos` in `c`. In this case we are not synthesizing a data-flow node, + * but instead assume that a relevant parameter node already exists. + */ + private predicate parameterReadState( + SummarizedCallable c, SummaryNodeState state, ParameterPosition pos + ) { + state.isInputState(c, SummaryComponentStack::argument(pos)) + } + + /** + * Holds if a synthesized summary node is needed for the state `state` in summarized + * callable `c`. + */ + predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) { + state.isInputState(c, _) and + not parameterReadState(c, state, _) + or + state.isOutputState(c, _) + } + + pragma[noinline] + private Node summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) { + exists(SummaryNodeState state | state.isInputState(c, s) | + result = summaryNode(c, state) + or + exists(ParameterPosition pos | + parameterReadState(c, state, pos) and + result.(ParamNode).isParameterOf(inject(c), pos) + ) + ) + } + + pragma[noinline] + private Node summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) { + exists(SummaryNodeState state | + state.isOutputState(c, s) and + result = summaryNode(c, state) + ) + } + + /** + * Holds if a write targets `post`, which is a post-update node for a + * parameter at position `pos` in `c`. + */ + private predicate isParameterPostUpdate(Node post, SummarizedCallable c, ParameterPosition pos) { + post = summaryNodeOutputState(c, SummaryComponentStack::argument(pos)) + } + + /** Holds if a parameter node at position `pos` is required for `c`. */ + predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) { + parameterReadState(c, _, pos) + or + isParameterPostUpdate(_, c, pos) + } + + private predicate callbackOutput( + SummarizedCallable c, SummaryComponentStack s, Node receiver, ReturnKind rk + ) { + any(SummaryNodeState state).isInputState(c, s) and + s.head() = TReturnSummaryComponent(rk) and + receiver = summaryNodeInputState(c, s.tail()) + } + + private predicate callbackInput( + SummarizedCallable c, SummaryComponentStack s, Node receiver, ArgumentPosition pos + ) { + any(SummaryNodeState state).isOutputState(c, s) and + s.head() = TParameterSummaryComponent(pos) and + receiver = summaryNodeInputState(c, s.tail()) + } + + /** Holds if a call targeting `receiver` should be synthesized inside `c`. */ + predicate summaryCallbackRange(SummarizedCallable c, Node receiver) { + callbackOutput(c, _, receiver, _) + or + callbackInput(c, _, receiver, _) + } + + /** + * Gets the type of synthesized summary node `n`. + * + * The type is computed based on the language-specific predicates + * `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and + * `getCallbackReturnType()`. + */ + DataFlowType summaryNodeType(Node n) { + exists(Node pre | + summaryPostUpdateNode(n, pre) and + result = getNodeType(pre) + ) + or + exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() | + n = summaryNodeInputState(c, s) and + ( + exists(ContentSet cont | result = getContentType(cont) | + head = TContentSummaryComponent(cont) or + head = TWithContentSummaryComponent(cont) + ) + or + exists(ContentSet cont | + head = TWithoutContentSummaryComponent(cont) and + result = getNodeType(summaryNodeInputState(c, s.tail())) + ) + or + exists(ReturnKind rk | + head = TReturnSummaryComponent(rk) and + result = + getCallbackReturnType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c), + s.tail())), rk) + ) + ) + or + n = summaryNodeOutputState(c, s) and + ( + exists(ContentSet cont | + head = TContentSummaryComponent(cont) and result = getContentType(cont) + ) + or + s.length() = 1 and + exists(ReturnKind rk | + head = TReturnSummaryComponent(rk) and + result = getReturnType(c, rk) + ) + or + exists(ArgumentPosition pos | head = TParameterSummaryComponent(pos) | + result = + getCallbackParameterType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c), + s.tail())), pos) + ) + ) + ) + } + + /** Holds if summary node `out` contains output of kind `rk` from call `c`. */ + predicate summaryOutNode(DataFlowCall c, Node out, ReturnKind rk) { + exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver | + callbackOutput(callable, s, receiver, rk) and + out = summaryNodeInputState(callable, s) and + c = summaryDataFlowCall(receiver) + ) + } + + /** Holds if summary node `arg` is at position `pos` in the call `c`. */ + predicate summaryArgumentNode(DataFlowCall c, Node arg, ArgumentPosition pos) { + exists(SummarizedCallable callable, SummaryComponentStack s, Node receiver | + callbackInput(callable, s, receiver, pos) and + arg = summaryNodeOutputState(callable, s) and + c = summaryDataFlowCall(receiver) + ) + } + + /** Holds if summary node `post` is a post-update node with pre-update node `pre`. */ + predicate summaryPostUpdateNode(Node post, Node pre) { + exists(SummarizedCallable c, ParameterPosition pos | + isParameterPostUpdate(post, c, pos) and + pre.(ParamNode).isParameterOf(inject(c), pos) + ) + or + exists(SummarizedCallable callable, SummaryComponentStack s | + callbackInput(callable, s, _, _) and + pre = summaryNodeOutputState(callable, s) and + post = summaryNodeInputState(callable, s) + ) + } + + /** Holds if summary node `ret` is a return node of kind `rk`. */ + predicate summaryReturnNode(Node ret, ReturnKind rk) { + exists(SummarizedCallable callable, SummaryComponentStack s | + ret = summaryNodeOutputState(callable, s) and + s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk)) + ) + } + + /** + * Holds if flow is allowed to pass from parameter `p`, to a return + * node, and back out to `p`. + */ + predicate summaryAllowParameterReturnInSelf(ParamNode p) { + exists(SummarizedCallable c, ParameterPosition ppos | p.isParameterOf(inject(c), ppos) | + exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents | + summary(c, inputContents, outputContents, _) and + inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and + outputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) + ) + ) + } + + /** Provides a compilation of flow summaries to atomic data-flow steps. */ + module Steps { + /** + * Holds if there is a local step from `pred` to `succ`, which is synthesized + * from a flow summary. + */ + predicate summaryLocalStep(Node pred, Node succ, boolean preservesValue) { + exists( + SummarizedCallable c, SummaryComponentStack inputContents, + SummaryComponentStack outputContents + | + summary(c, inputContents, outputContents, preservesValue) and + pred = summaryNodeInputState(c, inputContents) and + succ = summaryNodeOutputState(c, outputContents) + | + preservesValue = true + or + preservesValue = false and not summary(c, inputContents, outputContents, true) + ) + or + exists(SummarizedCallable c, SummaryComponentStack s | + pred = summaryNodeInputState(c, s.tail()) and + succ = summaryNodeInputState(c, s) and + s.head() = [SummaryComponent::withContent(_), SummaryComponent::withoutContent(_)] and + preservesValue = true + ) + } + + /** + * Holds if there is a read step of content `c` from `pred` to `succ`, which + * is synthesized from a flow summary. + */ + predicate summaryReadStep(Node pred, ContentSet c, Node succ) { + exists(SummarizedCallable sc, SummaryComponentStack s | + pred = summaryNodeInputState(sc, s.tail()) and + succ = summaryNodeInputState(sc, s) and + SummaryComponent::content(c) = s.head() + ) + } + + /** + * Holds if there is a store step of content `c` from `pred` to `succ`, which + * is synthesized from a flow summary. + */ + predicate summaryStoreStep(Node pred, ContentSet c, Node succ) { + exists(SummarizedCallable sc, SummaryComponentStack s | + pred = summaryNodeOutputState(sc, s) and + succ = summaryNodeOutputState(sc, s.tail()) and + SummaryComponent::content(c) = s.head() + ) + } + + /** + * Holds if values stored inside content `c` are cleared at `n`. `n` is a + * synthesized summary node, so in order for values to be cleared at calls + * to the relevant method, it is important that flow does not pass over + * the argument, either via use-use flow or def-use flow. + * + * Example: + * + * ``` + * a.b = taint; + * a.clearB(); // assume we have a flow summary for `clearB` that clears `b` on the qualifier + * sink(a.b); + * ``` + * + * In the above, flow should not pass from `a` on the first line (or the second + * line) to `a` on the third line. Instead, there will be synthesized flow from + * `a` on line 2 to the post-update node for `a` on that line (via an intermediate + * node where field `b` is cleared). + */ + predicate summaryClearsContent(Node n, ContentSet c) { + exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack | + n = summaryNode(sc, state) and + state.isInputState(sc, stack) and + stack.head() = SummaryComponent::withoutContent(c) + ) + } + + /** + * Holds if the value that is being tracked is expected to be stored inside + * content `c` at `n`. + */ + predicate summaryExpectsContent(Node n, ContentSet c) { + exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack | + n = summaryNode(sc, state) and + state.isInputState(sc, stack) and + stack.head() = SummaryComponent::withContent(c) + ) + } + + pragma[noinline] + private predicate viableParam( + DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, ParamNode p + ) { + exists(DataFlowCallable c | + c = inject(sc) and + p.isParameterOf(c, ppos) and + c = viableCallable(call) + ) + } + + pragma[nomagic] + private ParamNode summaryArgParam0(DataFlowCall call, ArgNode arg, SummarizedCallable sc) { + exists(ParameterPosition ppos | + argumentPositionMatch(call, arg, ppos) and + viableParam(call, sc, ppos, result) + ) + } + + /** + * Holds if use-use flow starting from `arg` should be prohibited. + * + * This is the case when `arg` is the argument of a call that targets a + * flow summary where the corresponding parameter either clears contents + * or expects contents. + */ + pragma[nomagic] + predicate prohibitsUseUseFlow(ArgNode arg, SummarizedCallable sc) { + exists(ParamNode p, Node mid, ParameterPosition ppos, Node ret | + p = summaryArgParam0(_, arg, sc) and + p.isParameterOf(_, pragma[only_bind_into](ppos)) and + summaryLocalStep(p, mid, true) and + summaryLocalStep(mid, ret, true) and + isParameterPostUpdate(ret, _, pragma[only_bind_into](ppos)) + | + summaryClearsContent(mid, _) or + summaryExpectsContent(mid, _) + ) + } + + bindingset[ret] + private ParamNode summaryArgParam( + ArgNode arg, ReturnNodeExt ret, OutNodeExt out, SummarizedCallable sc + ) { + exists(DataFlowCall call, ReturnKindExt rk | + result = summaryArgParam0(call, arg, sc) and + ret.getKind() = pragma[only_bind_into](rk) and + out = pragma[only_bind_into](rk).getAnOutNode(call) + ) + } + + /** + * Holds if `arg` flows to `out` using a simple value-preserving flow + * summary, that is, a flow summary without reads and stores. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summaryThroughStepValue(ArgNode arg, Node out, SummarizedCallable sc) { + exists(ReturnKind rk, ReturnNode ret, DataFlowCall call | + summaryLocalStep(summaryArgParam0(call, arg, sc), ret, true) and + ret.getKind() = pragma[only_bind_into](rk) and + out = getAnOutNode(call, pragma[only_bind_into](rk)) + ) + } + + /** + * Holds if `arg` flows to `out` using a simple flow summary involving taint + * step, that is, a flow summary without reads and stores. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summaryThroughStepTaint(ArgNode arg, Node out, SummarizedCallable sc) { + exists(ReturnNodeExt ret | summaryLocalStep(summaryArgParam(arg, ret, out, sc), ret, false)) + } + + /** + * Holds if there is a read(+taint) of `c` from `arg` to `out` using a + * flow summary. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summaryGetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) { + exists(Node mid, ReturnNodeExt ret | + summaryReadStep(summaryArgParam(arg, ret, out, sc), c, mid) and + summaryLocalStep(mid, ret, _) + ) + } + + /** + * Holds if there is a (taint+)store of `arg` into content `c` of `out` using a + * flow summary. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summarySetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) { + exists(Node mid, ReturnNodeExt ret | + summaryLocalStep(summaryArgParam(arg, ret, out, sc), mid, _) and + summaryStoreStep(mid, c, ret) + ) + } + } + + /** + * Provides a means of translating externally (e.g., CSV) defined flow + * summaries into a `SummarizedCallable`s. + */ + module External { + /** Holds if `spec` is a relevant external specification. */ + private predicate relevantSpec(string spec) { + summaryElement(_, spec, _, _, _) or + summaryElement(_, _, spec, _, _) or + sourceElement(_, spec, _, _) or + sinkElement(_, spec, _, _) + } + + private class AccessPathRange extends AccessPath::Range { + AccessPathRange() { relevantSpec(this) } + } + + /** Holds if specification component `c` parses as parameter `n`. */ + predicate parseParam(AccessPathToken token, ArgumentPosition pos) { + token.getName() = "Parameter" and + pos = parseParamBody(token.getAnArgument()) + } + + /** Holds if specification component `c` parses as argument `n`. */ + predicate parseArg(AccessPathToken token, ParameterPosition pos) { + token.getName() = "Argument" and + pos = parseArgBody(token.getAnArgument()) + } + + private SummaryComponent interpretComponent(AccessPathToken token) { + exists(ParameterPosition pos | + parseArg(token, pos) and result = SummaryComponent::argument(pos) + ) + or + exists(ArgumentPosition pos | + parseParam(token, pos) and result = SummaryComponent::parameter(pos) + ) + or + token = "ReturnValue" and result = SummaryComponent::return(getReturnValueKind()) + or + result = interpretComponentSpecific(token) + } + + /** + * Holds if `spec` specifies summary component stack `stack`. + */ + predicate interpretSpec(AccessPath spec, SummaryComponentStack stack) { + interpretSpec(spec, spec.getNumToken(), stack) + } + + /** Holds if the first `n` tokens of `spec` resolves to `stack`. */ + private predicate interpretSpec(AccessPath spec, int n, SummaryComponentStack stack) { + n = 1 and + stack = SummaryComponentStack::singleton(interpretComponent(spec.getToken(0))) + or + exists(SummaryComponent head, SummaryComponentStack tail | + interpretSpec(spec, n, head, tail) and + stack = SummaryComponentStack::push(head, tail) + ) + } + + /** Holds if the first `n` tokens of `spec` resolves to `head` followed by `tail` */ + private predicate interpretSpec( + AccessPath spec, int n, SummaryComponent head, SummaryComponentStack tail + ) { + interpretSpec(spec, n - 1, tail) and + head = interpretComponent(spec.getToken(n - 1)) + } + + private class MkStack extends RequiredSummaryComponentStack { + override predicate required(SummaryComponent head, SummaryComponentStack tail) { + interpretSpec(_, _, head, tail) + } + } + + private class SummarizedCallableExternal extends SummarizedCallable { + SummarizedCallableExternal() { summaryElement(this, _, _, _, _) } + + private predicate relevantSummaryElementGenerated( + AccessPath inSpec, AccessPath outSpec, string kind + ) { + summaryElement(this, inSpec, outSpec, kind, true) and + not summaryElement(this, _, _, _, false) + } + + private predicate relevantSummaryElement(AccessPath inSpec, AccessPath outSpec, string kind) { + summaryElement(this, inSpec, outSpec, kind, false) + or + this.relevantSummaryElementGenerated(inSpec, outSpec, kind) + } + + override predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + exists(AccessPath inSpec, AccessPath outSpec, string kind | + this.relevantSummaryElement(inSpec, outSpec, kind) and + interpretSpec(inSpec, input) and + interpretSpec(outSpec, output) + | + kind = "value" and preservesValue = true + or + kind = "taint" and preservesValue = false + ) + } + + override predicate isAutoGenerated() { this.relevantSummaryElementGenerated(_, _, _) } + } + + /** Holds if component `c` of specification `spec` cannot be parsed. */ + predicate invalidSpecComponent(AccessPath spec, string c) { + c = spec.getToken(_) and + not exists(interpretComponent(c)) + } + + private predicate inputNeedsReference(AccessPathToken c) { + c.getName() = "Argument" or + inputNeedsReferenceSpecific(c) + } + + private predicate outputNeedsReference(AccessPathToken c) { + c.getName() = ["Argument", "ReturnValue"] or + outputNeedsReferenceSpecific(c) + } + + private predicate sourceElementRef(InterpretNode ref, AccessPath output, string kind) { + exists(SourceOrSinkElement e | + sourceElement(e, output, kind, _) and + if outputNeedsReference(output.getToken(0)) + then e = ref.getCallTarget() + else e = ref.asElement() + ) + } + + private predicate sinkElementRef(InterpretNode ref, AccessPath input, string kind) { + exists(SourceOrSinkElement e | + sinkElement(e, input, kind, _) and + if inputNeedsReference(input.getToken(0)) + then e = ref.getCallTarget() + else e = ref.asElement() + ) + } + + /** Holds if the first `n` tokens of `output` resolve to the given interpretation. */ + private predicate interpretOutput( + AccessPath output, int n, InterpretNode ref, InterpretNode node + ) { + sourceElementRef(ref, output, _) and + n = 0 and + ( + if output = "" + then + // Allow language-specific interpretation of the empty access path + interpretOutputSpecific("", ref, node) + else node = ref + ) + or + exists(InterpretNode mid, AccessPathToken c | + interpretOutput(output, n - 1, ref, mid) and + c = output.getToken(n - 1) + | + exists(ArgumentPosition apos, ParameterPosition ppos | + node.asNode().(PostUpdateNode).getPreUpdateNode().(ArgNode).argumentOf(mid.asCall(), apos) and + parameterMatch(ppos, apos) + | + c = "Argument" or parseArg(c, ppos) + ) + or + exists(ArgumentPosition apos, ParameterPosition ppos | + node.asNode().(ParamNode).isParameterOf(mid.asCallable(), ppos) and + parameterMatch(ppos, apos) + | + c = "Parameter" or parseParam(c, apos) + ) + or + c = "ReturnValue" and + node.asNode() = getAnOutNodeExt(mid.asCall(), TValueReturn(getReturnValueKind())) + or + interpretOutputSpecific(c, mid, node) + ) + } + + /** Holds if the first `n` tokens of `input` resolve to the given interpretation. */ + private predicate interpretInput(AccessPath input, int n, InterpretNode ref, InterpretNode node) { + sinkElementRef(ref, input, _) and + n = 0 and + ( + if input = "" + then + // Allow language-specific interpretation of the empty access path + interpretInputSpecific("", ref, node) + else node = ref + ) + or + exists(InterpretNode mid, AccessPathToken c | + interpretInput(input, n - 1, ref, mid) and + c = input.getToken(n - 1) + | + exists(ArgumentPosition apos, ParameterPosition ppos | + node.asNode().(ArgNode).argumentOf(mid.asCall(), apos) and + parameterMatch(ppos, apos) + | + c = "Argument" or parseArg(c, ppos) + ) + or + exists(ReturnNodeExt ret | + c = "ReturnValue" and + ret = node.asNode() and + ret.getKind().(ValueReturnKind).getKind() = getReturnValueKind() and + mid.asCallable() = getNodeEnclosingCallable(ret) + ) + or + interpretInputSpecific(c, mid, node) + ) + } + + /** + * Holds if `node` is specified as a source with the given kind in a CSV flow + * model. + */ + predicate isSourceNode(InterpretNode node, string kind) { + exists(InterpretNode ref, AccessPath output | + sourceElementRef(ref, output, kind) and + interpretOutput(output, output.getNumToken(), ref, node) + ) + } + + /** + * Holds if `node` is specified as a sink with the given kind in a CSV flow + * model. + */ + predicate isSinkNode(InterpretNode node, string kind) { + exists(InterpretNode ref, AccessPath input | + sinkElementRef(ref, input, kind) and + interpretInput(input, input.getNumToken(), ref, node) + ) + } + } + + /** Provides a query predicate for outputting a set of relevant flow summaries. */ + module TestOutput { + /** A flow summary to include in the `summary/1` query predicate. */ + abstract class RelevantSummarizedCallable instanceof SummarizedCallable { + /** Gets the string representation of this callable used by `summary/1`. */ + abstract string getCallableCsv(); + + /** Holds if flow is propagated between `input` and `output`. */ + predicate relevantSummary( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + super.propagatesFlow(input, output, preservesValue) + } + + string toString() { result = super.toString() } + } + + /** A flow summary to include in the `negativeSummary/1` query predicate. */ + abstract class RelevantNegativeSummarizedCallable instanceof NegativeSummarizedCallable { + /** Gets the string representation of this callable used by `summary/1`. */ + abstract string getCallableCsv(); + + string toString() { result = super.toString() } + } + + /** Render the kind in the format used in flow summaries. */ + private string renderKind(boolean preservesValue) { + preservesValue = true and result = "value" + or + preservesValue = false and result = "taint" + } + + private string renderProvenance(SummarizedCallable c) { + if c.isAutoGenerated() then result = "generated" else result = "manual" + } + + private string renderProvenanceNegative(NegativeSummarizedCallable c) { + if c.isAutoGenerated() then result = "generated" else result = "manual" + } + + /** + * A query predicate for outputting flow summaries in semi-colon separated format in QL tests. + * The syntax is: "namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind;provenance"", + * ext is hardcoded to empty. + */ + query predicate summary(string csv) { + exists( + RelevantSummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output, + boolean preservesValue + | + c.relevantSummary(input, output, preservesValue) and + csv = + c.getCallableCsv() // Callable information + + getComponentStackCsv(input) + ";" // input + + getComponentStackCsv(output) + ";" // output + + renderKind(preservesValue) + ";" // kind + + renderProvenance(c) // provenance + ) + } + + /** + * Holds if a negative flow summary `csv` exists (semi-colon separated format). Used for testing purposes. + * The syntax is: "namespace;type;name;signature;provenance"", + */ + query predicate negativeSummary(string csv) { + exists(RelevantNegativeSummarizedCallable c | + csv = + c.getCallableCsv() // Callable information + + renderProvenanceNegative(c) // provenance + ) + } + } + + /** + * Provides query predicates for rendering the generated data flow graph for + * a summarized callable. + * + * Import this module into a `.ql` file of `@kind graph` to render the graph. + * The graph is restricted to callables from `RelevantSummarizedCallable`. + */ + module RenderSummarizedCallable { + /** A summarized callable to include in the graph. */ + abstract class RelevantSummarizedCallable instanceof SummarizedCallable { + string toString() { result = super.toString() } + } + + private newtype TNodeOrCall = + MkNode(Node n) { + exists(RelevantSummarizedCallable c | + n = summaryNode(c, _) + or + n.(ParamNode).isParameterOf(inject(c), _) + ) + } or + MkCall(DataFlowCall call) { + call = summaryDataFlowCall(_) and + call.getEnclosingCallable() = inject(any(RelevantSummarizedCallable c)) + } + + private class NodeOrCall extends TNodeOrCall { + Node asNode() { this = MkNode(result) } + + DataFlowCall asCall() { this = MkCall(result) } + + string toString() { + result = this.asNode().toString() + or + result = this.asCall().toString() + } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.asNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + or + this.asCall().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + query predicate nodes(NodeOrCall n, string key, string val) { + key = "semmle.label" and val = n.toString() + } + + private predicate edgesComponent(NodeOrCall a, NodeOrCall b, string value) { + exists(boolean preservesValue | + Private::Steps::summaryLocalStep(a.asNode(), b.asNode(), preservesValue) and + if preservesValue = true then value = "value" else value = "taint" + ) + or + exists(ContentSet c | + Private::Steps::summaryReadStep(a.asNode(), c, b.asNode()) and + value = "read (" + c + ")" + or + Private::Steps::summaryStoreStep(a.asNode(), c, b.asNode()) and + value = "store (" + c + ")" + or + Private::Steps::summaryClearsContent(a.asNode(), c) and + b = a and + value = "clear (" + c + ")" + or + Private::Steps::summaryExpectsContent(a.asNode(), c) and + b = a and + value = "expect (" + c + ")" + ) + or + summaryPostUpdateNode(b.asNode(), a.asNode()) and + value = "post-update" + or + b.asCall() = summaryDataFlowCall(a.asNode()) and + value = "receiver" + or + exists(ArgumentPosition pos | + summaryArgumentNode(b.asCall(), a.asNode(), pos) and + value = "argument (" + pos + ")" + ) + } + + query predicate edges(NodeOrCall a, NodeOrCall b, string key, string value) { + key = "semmle.label" and + value = strictconcat(string s | edgesComponent(a, b, s) | s, " / ") + } + } +} diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/FlowSummaryImplSpecific.qll b/python/ql/lib/semmle/python/dataflow/new/internal/FlowSummaryImplSpecific.qll new file mode 100644 index 00000000000..2f12e3a03d9 --- /dev/null +++ b/python/ql/lib/semmle/python/dataflow/new/internal/FlowSummaryImplSpecific.qll @@ -0,0 +1,225 @@ +/** + * Provides Python specific classes and predicates for defining flow summaries. + * + * Flow summaries are defined for callables that are not extracted. + * Such callables go by different names in different parts of our codebase: + * + * - in `FlowSummary.qll`, which is user facing, they are called `SummarizedCallable`s. + * These contain summaries, implemented by the user via the predicates `propagatesFlow` and `propagatesFlowExt`. + * + * - in the data flow layer, they are called `LibraryCallable`s (as in the Ruby codebase). + * These are identified by strings and has predicates for finding calls to them. + * + * Having both extracted and non-extracted callables means that we now have three types of calls: + * - Extracted calls to extracted callables, either `NormalCall` or `SpecialCall`. These are handled by standard data flow. + * - Extracted calls to non-extracted callables, `LibraryCall`. These are handled by loking up the relevant summary when the + * global data flwo graph is connected up via `getViableCallable`. + * - Non-extracted calls, `SummaryCall`. These are synthesised by the flow summary framework. + * + * The first two can be referred to as `ExtractedDataFlowCall`. In fact, `LibraryCall` is a subclass of `NormalCall`, where + * `getCallable` is set to `none()`. The member predicate `ExtractedDataFlowCall::getCallable` is _not_ the mechanism for + * call resolution in global data flow. That mechanism is `getViableCallable`. + * Resolving a call to a non-extracted callable goes via `LibraryCallable::getACall`, which may involve type tracking. + * To avoid that type tracking becomes mutualy recursive with data flow, type tracking must use a call graph not including summaries. + * Type tracking sees the callgraph given by `ExtractedDataFlowCall::getACallable`. + * + * We do not support summaries of special methods via the special methods framework, + * the summary would have to identify the call. + * + * We might, while we still extract the standard library, want to support flow summaries of + * extracted callables, so that we can model part of the standard library with flow summaries. + * For this to work, we have be careful with the enclosing callable predicate. + */ + +private import python +private import DataFlowPrivate +private import DataFlowPublic +private import DataFlowImplCommon +private import FlowSummaryImpl::Private +private import FlowSummaryImpl::Public +private import semmle.python.dataflow.new.FlowSummary as FlowSummary + +class SummarizedCallableBase = string; + +/** View a `SummarizedCallable` as a `DataFlowCallable`. */ +DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c } + +/** Gets the parameter position of the instance parameter. */ +ArgumentPosition instanceParameterPosition() { none() } // disables implicit summary flow to `this` for callbacks + +/** Gets the synthesized summary data-flow node for the given values. */ +Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = TSummaryNode(c, state) } + +/** Gets the synthesized data-flow call for `receiver`. */ +SummaryCall summaryDataFlowCall(Node receiver) { receiver = result.getReceiver() } + +/** Gets the type of content `c`. */ +DataFlowType getContentType(Content c) { any() } + +/** Gets the return type of kind `rk` for callable `c`. */ +bindingset[c, rk] +DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() } + +/** + * Gets the type of the `i`th parameter in a synthesized call that targets a + * callback of type `t`. + */ +bindingset[t, i] +DataFlowType getCallbackParameterType(DataFlowType t, int i) { any() } + +/** + * Gets the return type of kind `rk` in a synthesized call that targets a + * callback of type `t`. + */ +DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { any() } + +/** + * Holds if an external flow summary exists for `c` with input specification + * `input`, output specification `output`, kind `kind`, and a flag `generated` + * stating whether the summary is autogenerated. + */ +predicate summaryElement( + FlowSummary::SummarizedCallable c, string input, string output, string kind, boolean generated +) { + exists(boolean preservesValue | + c.propagatesFlowExt(input, output, preservesValue) and + (if preservesValue = true then kind = "value" else kind = "taint") and + generated = false + ) +} + +/** + * Holds if a negative flow summary exists for `c`, which means that there is no + * flow through `c`. The flag `generated` states whether the summary is autogenerated. + * Note. Negative flow summaries has not been implemented for Python. + */ +predicate negativeSummaryElement(FlowSummary::SummarizedCallable c, boolean generated) { none() } + +/** + * Gets the summary component for specification component `c`, if any. + * + * This covers all the Python-specific components of a flow summary. + */ +SummaryComponent interpretComponentSpecific(AccessPathToken c) { + c = "ListElement" and + result = FlowSummary::SummaryComponent::listElement() +} + +/** Gets the textual representation of a summary component in the format used for flow summaries. */ +string getComponentSpecificCsv(SummaryComponent sc) { + sc = TContentSummaryComponent(any(ListElementContent c)) and + result = "ListElement" +} + +/** Gets the textual representation of a parameter position in the format used for flow summaries. */ +string getParameterPositionCsv(ParameterPosition pos) { result = pos.toString() } + +/** Gets the textual representation of an argument position in the format used for flow summaries. */ +string getArgumentPositionCsv(ArgumentPosition pos) { result = pos.toString() } + +/** Holds if input specification component `c` needs a reference. */ +predicate inputNeedsReferenceSpecific(string c) { none() } + +/** Holds if output specification component `c` needs a reference. */ +predicate outputNeedsReferenceSpecific(string c) { none() } + +/** Gets the return kind corresponding to specification `"ReturnValue"`. */ +ReturnKind getReturnValueKind() { any() } + +/** + * All definitions in this module are required by the shared implementation + * (for source/sink interpretation), but they are unused for Python, where + * we rely on API graphs instead. + */ +private module UnusedSourceSinkInterpretation { + /** + * Holds if an external source specification exists for `n` with output specification + * `output`, kind `kind`, and a flag `generated` stating whether the source specification is + * autogenerated. + */ + predicate sourceElement(AstNode n, string output, string kind, boolean generated) { none() } + + /** + * Holds if an external sink specification exists for `n` with input specification + * `input`, kind `kind` and a flag `generated` stating whether the sink specification is + * autogenerated. + */ + predicate sinkElement(AstNode n, string input, string kind, boolean generated) { none() } + + class SourceOrSinkElement = AstNode; + + /** An entity used to interpret a source/sink specification. */ + class InterpretNode extends AstNode_ { + // InterpretNode is going away, this is just a dummy implementation. + // However, we have some old location tests picking them up, so we + // explicitly define them to not exist. + InterpretNode() { none() } + + /** Gets the element that this node corresponds to, if any. */ + SourceOrSinkElement asElement() { none() } + + /** Gets the data-flow node that this node corresponds to, if any. */ + Node asNode() { none() } + + /** Gets the call that this node corresponds to, if any. */ + DataFlowCall asCall() { none() } + + /** Gets the callable that this node corresponds to, if any. */ + DataFlowCallable asCallable() { none() } + + /** Gets the target of this call, if any. */ + SourceOrSinkElement getCallTarget() { none() } + } + + /** Provides additional sink specification logic. */ + predicate interpretOutputSpecific(string c, InterpretNode mid, InterpretNode node) { none() } + + /** Provides additional source specification logic. */ + predicate interpretInputSpecific(string c, InterpretNode mid, InterpretNode node) { none() } +} + +import UnusedSourceSinkInterpretation + +module ParsePositions { + private import FlowSummaryImpl + + private predicate isParamBody(string body) { + exists(AccessPathToken tok | + tok.getName() = "Parameter" and + body = tok.getAnArgument() + ) + } + + private predicate isArgBody(string body) { + exists(AccessPathToken tok | + tok.getName() = "Argument" and + body = tok.getAnArgument() + ) + } + + predicate isParsedParameterPosition(string c, int i) { + isParamBody(c) and + i = AccessPath::parseInt(c) + } + + predicate isParsedArgumentPosition(string c, int i) { + isArgBody(c) and + i = AccessPath::parseInt(c) + } +} + +/** Gets the argument position obtained by parsing `X` in `Parameter[X]`. */ +ArgumentPosition parseParamBody(string s) { + exists(int i | + ParsePositions::isParsedParameterPosition(s, i) and + result.isPositional(i) + ) +} + +/** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */ +ParameterPosition parseArgBody(string s) { + exists(int i | + ParsePositions::isParsedArgumentPosition(s, i) and + result.isPositional(i) + ) +} diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/IterableUnpacking.qll b/python/ql/lib/semmle/python/dataflow/new/internal/IterableUnpacking.qll index 4bf73dba0a3..f576529e3c6 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/IterableUnpacking.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/IterableUnpacking.qll @@ -244,7 +244,7 @@ class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget instan */ predicate iterableUnpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) { exists(AssignmentTarget target | - nodeFrom.asExpr() = target.getValue() and + nodeFrom.(CfgNode).getNode().getNode() = target.getValue() and nodeTo = TIterableSequenceNode(target) ) } @@ -255,7 +255,7 @@ predicate iterableUnpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) { */ predicate iterableUnpackingForReadStep(CfgNode nodeFrom, Content c, Node nodeTo) { exists(ForTarget target | - nodeFrom.asExpr() = target.getSource() and + nodeFrom.getNode().getNode() = target.getSource() and target instanceof SequenceNode and nodeTo = TIterableSequenceNode(target) ) and @@ -273,7 +273,7 @@ predicate iterableUnpackingForReadStep(CfgNode nodeFrom, Content c, Node nodeTo) predicate iterableUnpackingTupleFlowStep(Node nodeFrom, Node nodeTo) { exists(UnpackingAssignmentSequenceTarget target | nodeFrom = TIterableSequenceNode(target) and - nodeTo.asCfgNode() = target + nodeTo.(CfgNode).getNode() = target ) } @@ -305,7 +305,7 @@ predicate iterableUnpackingConvertingReadStep(Node nodeFrom, Content c, Node nod predicate iterableUnpackingConvertingStoreStep(Node nodeFrom, Content c, Node nodeTo) { exists(UnpackingAssignmentSequenceTarget target | nodeFrom = TIterableElementNode(target) and - nodeTo.asCfgNode() = target and + nodeTo.(CfgNode).getNode() = target and exists(int index | exists(target.getElement(index)) | c.(TupleElementContent).getIndex() = index ) @@ -331,7 +331,7 @@ predicate iterableUnpackingElementReadStep(Node nodeFrom, Content c, Node nodeTo not exists(target.getAnElement().(StarredNode)) and starIndex = -1 | - nodeFrom.asCfgNode() = target and + nodeFrom.(CfgNode).getNode() = target and element = target.getElement(index) and ( if starIndex = -1 or index < starIndex diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll b/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll index 08d8dff2a02..473d85d0851 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll @@ -36,7 +36,7 @@ class LocalSourceNode extends Node { LocalSourceNode() { Stages::DataFlow::ref() and this instanceof ExprNode and - not simpleLocalFlowStep(_, this) + not simpleLocalFlowStepForTypetracking(_, this) or // We include all module variable nodes, as these act as stepping stones between writes and // reads of global variables. Without them, type tracking based on `LocalSourceNode`s would be diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/MatchUnpacking.qll b/python/ql/lib/semmle/python/dataflow/new/internal/MatchUnpacking.qll index 948536d5598..1447b726f2a 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/MatchUnpacking.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/MatchUnpacking.qll @@ -69,8 +69,8 @@ predicate matchSubjectFlowStep(Node nodeFrom, Node nodeTo) { subject = match.getSubject() and target = match.getCase(_).(Case).getPattern() | - nodeFrom.asExpr() = subject and - nodeTo.asCfgNode().getNode() = target + nodeFrom.(CfgNode).getNode().getNode() = subject and + nodeTo.(CfgNode).getNode().getNode() = target ) } @@ -84,12 +84,13 @@ predicate matchAsFlowStep(Node nodeFrom, Node nodeTo) { // That way, information can propagate from the interior pattern to the alias. // // the subject flows to the interior pattern - nodeFrom.asCfgNode().getNode() = subject and - nodeTo.asCfgNode().getNode() = subject.getPattern() + nodeFrom.(CfgNode).getNode().getNode() = subject and + nodeTo.(CfgNode).getNode().getNode() = subject.getPattern() or // the interior pattern flows to the alias - nodeFrom.asCfgNode().getNode() = subject.getPattern() and - nodeTo.asVar().getDefinition().(PatternAliasDefinition).getDefiningNode().getNode() = alias + nodeFrom.(CfgNode).getNode().getNode() = subject.getPattern() and + nodeTo.(EssaNode).getVar().getDefinition().(PatternAliasDefinition).getDefiningNode().getNode() = + alias ) } @@ -99,8 +100,8 @@ predicate matchAsFlowStep(Node nodeFrom, Node nodeTo) { */ predicate matchOrFlowStep(Node nodeFrom, Node nodeTo) { exists(MatchOrPattern subject, Pattern pattern | pattern = subject.getAPattern() | - nodeFrom.asCfgNode().getNode() = subject and - nodeTo.asCfgNode().getNode() = pattern + nodeFrom.(CfgNode).getNode().getNode() = subject and + nodeTo.(CfgNode).getNode().getNode() = pattern ) } @@ -110,8 +111,8 @@ predicate matchOrFlowStep(Node nodeFrom, Node nodeTo) { */ predicate matchLiteralFlowStep(Node nodeFrom, Node nodeTo) { exists(MatchLiteralPattern pattern, Expr literal | literal = pattern.getLiteral() | - nodeFrom.asExpr() = literal and - nodeTo.asCfgNode().getNode() = pattern + nodeFrom.(CfgNode).getNode().getNode() = literal and + nodeTo.(CfgNode).getNode().getNode() = pattern ) } @@ -121,8 +122,14 @@ predicate matchLiteralFlowStep(Node nodeFrom, Node nodeTo) { */ predicate matchCaptureFlowStep(Node nodeFrom, Node nodeTo) { exists(MatchCapturePattern capture, Name var | capture.getVariable() = var | - nodeFrom.asCfgNode().getNode() = capture and - nodeTo.asVar().getDefinition().(PatternCaptureDefinition).getDefiningNode().getNode() = var + nodeFrom.(CfgNode).getNode().getNode() = capture and + nodeTo + .(EssaNode) + .getVar() + .getDefinition() + .(PatternCaptureDefinition) + .getDefiningNode() + .getNode() = var ) } @@ -132,8 +139,8 @@ predicate matchCaptureFlowStep(Node nodeFrom, Node nodeTo) { */ predicate matchValueFlowStep(Node nodeFrom, Node nodeTo) { exists(MatchValuePattern pattern, Expr value | value = pattern.getValue() | - nodeFrom.asExpr() = value and - nodeTo.asCfgNode().getNode() = pattern + nodeFrom.(CfgNode).getNode().getNode() = value and + nodeTo.(CfgNode).getNode().getNode() = pattern ) } @@ -145,8 +152,8 @@ predicate matchSequenceReadStep(Node nodeFrom, Content c, Node nodeTo) { exists(MatchSequencePattern subject, int index, Pattern element | element = subject.getPattern(index) | - nodeFrom.asCfgNode().getNode() = subject and - nodeTo.asCfgNode().getNode() = element and + nodeFrom.(CfgNode).getNode().getNode() = subject and + nodeTo.(CfgNode).getNode().getNode() = element and ( // tuple content c.(TupleElementContent).getIndex() = index @@ -173,7 +180,7 @@ predicate matchStarReadStep(Node nodeFrom, Content c, Node nodeTo) { exists(MatchSequencePattern subject, int index, MatchStarPattern star | star = subject.getPattern(index) | - nodeFrom.asCfgNode().getNode() = subject and + nodeFrom.(CfgNode).getNode().getNode() = subject and nodeTo = TStarPatternElementNode(star) and ( // tuple content @@ -200,7 +207,7 @@ predicate matchStarReadStep(Node nodeFrom, Content c, Node nodeTo) { predicate matchStarStoreStep(Node nodeFrom, Content c, Node nodeTo) { exists(MatchStarPattern star | nodeFrom = TStarPatternElementNode(star) and - nodeTo.asCfgNode().getNode() = star.getTarget() and + nodeTo.(CfgNode).getNode().getNode() = star.getTarget() and c instanceof ListElementContent ) } @@ -218,8 +225,8 @@ predicate matchMappingReadStep(Node nodeFrom, Content c, Node nodeTo) { key = keyValue.getKey() and value = keyValue.getValue() | - nodeFrom.asCfgNode().getNode() = subject and - nodeTo.asCfgNode().getNode() = value and + nodeFrom.(CfgNode).getNode().getNode() = subject and + nodeTo.(CfgNode).getNode().getNode() = value and c.(DictionaryElementContent).getKey() = key.getLiteral().(StrConst).getText() ) } @@ -233,8 +240,8 @@ predicate matchMappingReadStep(Node nodeFrom, Content c, Node nodeTo) { */ predicate matchMappingFlowStep(Node nodeFrom, Node nodeTo) { exists(MatchMappingPattern subject, MatchDoubleStarPattern dstar | dstar = subject.getAMapping() | - nodeFrom.asCfgNode().getNode() = subject and - nodeTo.asCfgNode().getNode() = dstar.getTarget() + nodeFrom.(CfgNode).getNode().getNode() = subject and + nodeTo.(CfgNode).getNode().getNode() = dstar.getTarget() ) } @@ -251,7 +258,7 @@ predicate matchMappingClearStep(Node n, Content c) { key = keyValue.getKey() and dstar = subject.getAMapping() | - n.asCfgNode().getNode() = dstar.getTarget() and + n.(CfgNode).getNode().getNode() = dstar.getTarget() and c.(DictionaryElementContent).getKey() = key.getLiteral().(StrConst).getText() ) } @@ -266,8 +273,8 @@ predicate matchClassReadStep(Node nodeFrom, Content c, Node nodeTo) { attr = keyword.getAttribute() and value = keyword.getValue() | - nodeFrom.asCfgNode().getNode() = subject and - nodeTo.asCfgNode().getNode() = value and + nodeFrom.(CfgNode).getNode().getNode() = subject and + nodeTo.(CfgNode).getNode().getNode() = value and c.(AttributeContent).getAttribute() = attr.getId() ) } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll index e6db2839263..d46530bddcb 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll @@ -11,7 +11,7 @@ class Node = DataFlowPublic::Node; class TypeTrackingNode = DataFlowPublic::TypeTrackingNode; -predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStep/2; +predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStepForTypetracking/2; predicate jumpStep = DataFlowPrivate::jumpStepSharedWithTypeTracker/2; @@ -35,16 +35,22 @@ string getPossibleContentName() { */ pragma[nomagic] private DataFlowPrivate::DataFlowCallable getCallableForArgument( - DataFlowPublic::ArgumentNode nodeFrom, int i + DataFlowPublic::ExtractedArgumentNode nodeFrom, int i ) { - exists(DataFlowPrivate::DataFlowCall call | - nodeFrom.argumentOf(call, i) and + exists(DataFlowPrivate::ExtractedDataFlowCall call | + nodeFrom.extractedArgumentOf(call, i) and result = call.getCallable() ) } -/** Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. */ -predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPublic::ParameterNode nodeTo) { +/** + * Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. + * + * Flow into summarized library methods is not included, as that will lead to negative + * recursion (or, at best, terrible performance), since identifying calls to library + * methods is done using API graphs (which uses type tracking). + */ +predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPrivate::ParameterNodeImpl nodeTo) { // TODO: Support special methods? exists(DataFlowPrivate::DataFlowCallable callable, int i | callable = getCallableForArgument(nodeFrom, i) and @@ -54,8 +60,9 @@ predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPublic::Parame /** Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. */ predicate returnStep(DataFlowPrivate::ReturnNode nodeFrom, Node nodeTo) { - exists(DataFlowPrivate::DataFlowCall call | - nodeFrom.getEnclosingCallable() = call.getCallable() and nodeTo.asCfgNode() = call.getNode() + exists(DataFlowPrivate::ExtractedDataFlowCall call | + nodeFrom.getEnclosingCallable() = call.getCallable() and + nodeTo.(DataFlowPublic::CfgNode).getNode() = call.getNode() ) } diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index 38f9aca1ff0..dc93677dea0 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.TaintTracking private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.Concepts private import semmle.python.ApiGraphs +private import semmle.python.dataflow.new.FlowSummary private import semmle.python.frameworks.PEP249 private import semmle.python.frameworks.internal.PoorMansFunctionResolution private import semmle.python.frameworks.internal.SelfRefMixin @@ -3670,6 +3671,23 @@ private module StdlibPrivate { override DataFlow::Node getAPathArgument() { result = this.getAnInput() } } + + /** A flow summary for `reversed`. */ + class ReversedSummary extends SummarizedCallable { + ReversedSummary() { this = "builtins.reversed" } + + override DataFlow::CallCfgNode getACall() { result = API::builtin("reversed").getACall() } + + override DataFlow::ArgumentNode getACallback() { + result = API::builtin("reversed").getAValueReachableFromSource() + } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[0].ListElement" and + output = "ReturnValue.ListElement" and + preservesValue = true + } + } } // --------------------------------------------------------------------------- diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll index 0bae1e9b6ca..2f53c4fdeea 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -26,14 +26,14 @@ import semmle.python.ApiGraphs::API as API class Unit = PY::Unit; // Re-export libraries needed by ApiGraphModels.qll -import semmle.python.frameworks.data.internal.AccessPathSyntax as AccessPathSyntax +import semmle.python.dataflow.new.internal.AccessPathSyntax as AccessPathSyntax import semmle.python.dataflow.new.DataFlow::DataFlow as DataFlow private import AccessPathSyntax /** * Holds if models describing `package` may be relevant for the analysis of this database. */ -predicate isPackageUsed(string package) { exists(API::moduleImport(package)) } +predicate isPackageUsed(string package) { API::moduleImportExists(package) } /** Gets a Python-specific interpretation of the `(package, type, path)` tuple after resolving the first `n` access path tokens. */ bindingset[package, type, path] diff --git a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll index 5fa996c97a7..fc2e64fc786 100644 --- a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll +++ b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIs.qll @@ -65,13 +65,17 @@ private class DefaultSafeExternalApi extends SafeExternalApi { /** A node representing data being passed to an external API through a call. */ class ExternalApiDataNode extends DataFlow::Node { - DataFlowPrivate::DataFlowCall call; DataFlowPrivate::DataFlowCallable callable; int i; ExternalApiDataNode() { - exists(call.getLocation().getFile().getRelativePath()) and - callable = call.getCallable() and + exists(DataFlowPrivate::DataFlowCall call | + exists(call.getLocation().getFile().getRelativePath()) + | + callable = call.getCallable() and + // TODO: this ignores some complexity of keyword arguments (especially keyword-only args) + this = call.getArg(i) + ) and not any(SafeExternalApi safe).getSafeCallable() = callable and exists(Value cv | cv = callable.getCallableValue() | cv.isAbsent() @@ -82,8 +86,6 @@ class ExternalApiDataNode extends DataFlow::Node { or not exists(cv.(CallableValue).getScope().getLocation().getFile().getRelativePath()) ) and - // TODO: this ignores some complexity of keyword arguments (especially keyword-only args) - this = call.getArg(i) and // Not already modeled as a taint step not exists(DataFlow::Node next | TaintTrackingPrivate::defaultAdditionalTaintStep(this, next)) and // for `list.append(x)`, we have a additional taint step from x -> [post] list. diff --git a/python/ql/test/experimental/dataflow/TestUtil/NormalTaintTrackingTest.qll b/python/ql/test/experimental/dataflow/TestUtil/NormalTaintTrackingTest.qll new file mode 100644 index 00000000000..9619679da03 --- /dev/null +++ b/python/ql/test/experimental/dataflow/TestUtil/NormalTaintTrackingTest.qll @@ -0,0 +1,33 @@ +import python +import experimental.dataflow.TestUtil.FlowTest +import experimental.dataflow.testTaintConfig +private import semmle.python.dataflow.new.internal.PrintNode + +class DataFlowTest extends FlowTest { + DataFlowTest() { this = "DataFlowTest" } + + override string flowTag() { result = "flow" } + + override predicate relevantFlow(DataFlow::Node source, DataFlow::Node sink) { + exists(TestConfiguration cfg | cfg.hasFlow(source, sink)) + } +} + +query predicate missingAnnotationOnSink(Location location, string error, string element) { + error = "ERROR, you should add `# $ MISSING: flow` annotation" and + exists(DataFlow::Node sink | + exists(DataFlow::CallCfgNode call | + // note: we only care about `SINK` and not `SINK_F`, so we have to reconstruct manually. + call.getFunction().asCfgNode().(NameNode).getId() = "SINK" and + (sink = call.getArg(_) or sink = call.getArgByName(_)) + ) and + location = sink.getLocation() and + element = prettyExpr(sink.asExpr()) and + not any(TestConfiguration config).hasFlow(_, sink) and + not exists(FalseNegativeExpectation missingResult | + missingResult.getTag() = "flow" and + missingResult.getLocation().getFile() = location.getFile() and + missingResult.getLocation().getStartLine() = location.getStartLine() + ) + ) +} diff --git a/python/ql/test/experimental/dataflow/TestUtil/RoutingTest.qll b/python/ql/test/experimental/dataflow/TestUtil/RoutingTest.qll index 311b87a61c2..e96922bc25e 100644 --- a/python/ql/test/experimental/dataflow/TestUtil/RoutingTest.qll +++ b/python/ql/test/experimental/dataflow/TestUtil/RoutingTest.qll @@ -2,6 +2,7 @@ import python import semmle.python.dataflow.new.DataFlow import TestUtilities.InlineExpectationsTest private import semmle.python.dataflow.new.internal.PrintNode +private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate /** * A routing test is designed to test that values are routed to the diff --git a/python/ql/test/experimental/dataflow/TestUtil/UnresolvedCalls.qll b/python/ql/test/experimental/dataflow/TestUtil/UnresolvedCalls.qll index eaf480ec664..ea123e9ca45 100644 --- a/python/ql/test/experimental/dataflow/TestUtil/UnresolvedCalls.qll +++ b/python/ql/test/experimental/dataflow/TestUtil/UnresolvedCalls.qll @@ -12,7 +12,13 @@ class UnresolvedCallExpectations extends InlineExpectationsTest { override predicate hasActualResult(Location location, string element, string tag, string value) { exists(location.getFile().getRelativePath()) and exists(CallNode call | - not exists(DataFlowPrivate::DataFlowCall dfc | dfc.getNode() = call) and + not exists(DataFlowPrivate::DataFlowCall dfc | dfc.getNode() = call | + // For every `CallNode`, there is a `DataFlowCall` in the form of a `NormalCall`. + // It does not really count, as it has some abstract overrides. For instance, it does not + // define `getCallable`, so checking for the existence of this guarantees that we are in a + // properly resolved call. + exists(dfc.getCallable()) + ) and not call = API::builtin(_).getACall().asCfgNode() and location = call.getLocation() and tag = "unresolved_call" and diff --git a/python/ql/test/experimental/dataflow/basic/callGraphSinks.expected b/python/ql/test/experimental/dataflow/basic/callGraphSinks.expected index 17f3028ae23..01ae02a1ad0 100644 --- a/python/ql/test/experimental/dataflow/basic/callGraphSinks.expected +++ b/python/ql/test/experimental/dataflow/basic/callGraphSinks.expected @@ -1,2 +1,3 @@ +| file://:0:0:0:0 | parameter 0 of builtins.reversed | | test.py:1:19:1:19 | ControlFlowNode for x | | test.py:7:5:7:20 | ControlFlowNode for obfuscated_id() | diff --git a/python/ql/test/experimental/dataflow/basic/callGraphSources.expected b/python/ql/test/experimental/dataflow/basic/callGraphSources.expected index 4023ba8f3ea..0b4613c42de 100644 --- a/python/ql/test/experimental/dataflow/basic/callGraphSources.expected +++ b/python/ql/test/experimental/dataflow/basic/callGraphSources.expected @@ -1,2 +1,3 @@ +| file://:0:0:0:0 | [summary] to write: return (return) in builtins.reversed | | test.py:4:10:4:10 | ControlFlowNode for z | | test.py:7:19:7:19 | ControlFlowNode for a | diff --git a/python/ql/test/experimental/dataflow/basic/global.expected b/python/ql/test/experimental/dataflow/basic/global.expected index 8894bcc190a..885d61309d4 100644 --- a/python/ql/test/experimental/dataflow/basic/global.expected +++ b/python/ql/test/experimental/dataflow/basic/global.expected @@ -1,3 +1,4 @@ +| file://:0:0:0:0 | [summary] read: argument 0.List element in builtins.reversed | file://:0:0:0:0 | [summary] to write: return (return).List element in builtins.reversed | | test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | GSSA Variable obfuscated_id | | test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | | test.py:1:5:1:17 | GSSA Variable obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | diff --git a/python/ql/test/experimental/dataflow/basic/globalStep.expected b/python/ql/test/experimental/dataflow/basic/globalStep.expected index 9f228998b9c..38611776824 100644 --- a/python/ql/test/experimental/dataflow/basic/globalStep.expected +++ b/python/ql/test/experimental/dataflow/basic/globalStep.expected @@ -1,3 +1,4 @@ +| file://:0:0:0:0 | [summary] read: argument 0.List element in builtins.reversed | file://:0:0:0:0 | [summary] to write: return (return).List element in builtins.reversed | | test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | GSSA Variable obfuscated_id | | test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | GSSA Variable obfuscated_id | | test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | diff --git a/python/ql/test/experimental/dataflow/basic/local.expected b/python/ql/test/experimental/dataflow/basic/local.expected index c02b341eeae..33636a8e81d 100644 --- a/python/ql/test/experimental/dataflow/basic/local.expected +++ b/python/ql/test/experimental/dataflow/basic/local.expected @@ -1,3 +1,8 @@ +| file://:0:0:0:0 | [summary] read: argument 0.List element in builtins.reversed | file://:0:0:0:0 | [summary] read: argument 0.List element in builtins.reversed | +| file://:0:0:0:0 | [summary] read: argument 0.List element in builtins.reversed | file://:0:0:0:0 | [summary] to write: return (return).List element in builtins.reversed | +| file://:0:0:0:0 | [summary] to write: return (return) in builtins.reversed | file://:0:0:0:0 | [summary] to write: return (return) in builtins.reversed | +| file://:0:0:0:0 | [summary] to write: return (return).List element in builtins.reversed | file://:0:0:0:0 | [summary] to write: return (return).List element in builtins.reversed | +| file://:0:0:0:0 | parameter 0 of builtins.reversed | file://:0:0:0:0 | parameter 0 of builtins.reversed | | test.py:0:0:0:0 | GSSA Variable __name__ | test.py:0:0:0:0 | GSSA Variable __name__ | | test.py:0:0:0:0 | GSSA Variable __package__ | test.py:0:0:0:0 | GSSA Variable __package__ | | test.py:0:0:0:0 | GSSA Variable b | test.py:0:0:0:0 | GSSA Variable b | diff --git a/python/ql/test/experimental/dataflow/basic/localStep.expected b/python/ql/test/experimental/dataflow/basic/localStep.expected index e147bb9f4fc..900e4ac3900 100644 --- a/python/ql/test/experimental/dataflow/basic/localStep.expected +++ b/python/ql/test/experimental/dataflow/basic/localStep.expected @@ -1,3 +1,4 @@ +| file://:0:0:0:0 | [summary] read: argument 0.List element in builtins.reversed | file://:0:0:0:0 | [summary] to write: return (return).List element in builtins.reversed | | test.py:1:1:1:21 | ControlFlowNode for FunctionExpr | test.py:1:5:1:17 | GSSA Variable obfuscated_id | | test.py:1:5:1:17 | GSSA Variable obfuscated_id | test.py:7:5:7:17 | ControlFlowNode for obfuscated_id | | test.py:1:19:1:19 | ControlFlowNode for x | test.py:1:19:1:19 | SSA variable x | diff --git a/python/ql/test/experimental/dataflow/basic/sinks.expected b/python/ql/test/experimental/dataflow/basic/sinks.expected index bfebc2ef31e..3aa3245c465 100644 --- a/python/ql/test/experimental/dataflow/basic/sinks.expected +++ b/python/ql/test/experimental/dataflow/basic/sinks.expected @@ -1,3 +1,7 @@ +| file://:0:0:0:0 | [summary] read: argument 0.List element in builtins.reversed | +| file://:0:0:0:0 | [summary] to write: return (return) in builtins.reversed | +| file://:0:0:0:0 | [summary] to write: return (return).List element in builtins.reversed | +| file://:0:0:0:0 | parameter 0 of builtins.reversed | | test.py:0:0:0:0 | GSSA Variable __name__ | | test.py:0:0:0:0 | GSSA Variable __package__ | | test.py:0:0:0:0 | GSSA Variable b | diff --git a/python/ql/test/experimental/dataflow/basic/sources.expected b/python/ql/test/experimental/dataflow/basic/sources.expected index bfebc2ef31e..3aa3245c465 100644 --- a/python/ql/test/experimental/dataflow/basic/sources.expected +++ b/python/ql/test/experimental/dataflow/basic/sources.expected @@ -1,3 +1,7 @@ +| file://:0:0:0:0 | [summary] read: argument 0.List element in builtins.reversed | +| file://:0:0:0:0 | [summary] to write: return (return) in builtins.reversed | +| file://:0:0:0:0 | [summary] to write: return (return).List element in builtins.reversed | +| file://:0:0:0:0 | parameter 0 of builtins.reversed | | test.py:0:0:0:0 | GSSA Variable __name__ | | test.py:0:0:0:0 | GSSA Variable __package__ | | test.py:0:0:0:0 | GSSA Variable b | diff --git a/python/ql/test/experimental/dataflow/calls/test.py b/python/ql/test/experimental/dataflow/calls/test.py index 8a617ded45b..144a6a79fe1 100644 --- a/python/ql/test/experimental/dataflow/calls/test.py +++ b/python/ql/test/experimental/dataflow/calls/test.py @@ -31,7 +31,7 @@ try: # `mypkg.foo` is a `missing module variable`, but `mypkg.subpkg.bar` is compeltely # ignored. import mypkg - mypkg.foo(42) - mypkg.subpkg.bar(43) + mypkg.foo(42) # $ call=mypkg.foo(..) qlclass=NormalCall + mypkg.subpkg.bar(43) # $ call=mypkg.subpkg.bar(..) qlclass=LibraryCall arg_0=43 except: pass diff --git a/python/ql/test/experimental/dataflow/coverage/classesCallGraph.expected b/python/ql/test/experimental/dataflow/coverage/classesCallGraph.expected index a5477d5667e..f297c4be6e3 100644 --- a/python/ql/test/experimental/dataflow/coverage/classesCallGraph.expected +++ b/python/ql/test/experimental/dataflow/coverage/classesCallGraph.expected @@ -1,8 +1,10 @@ +| classes.py:14:17:14:60 | ControlFlowNode for Attribute() | classes.py:14:17:14:60 | ControlFlowNode for Attribute() | | classes.py:45:16:45:35 | ControlFlowNode for Attribute() | classes.py:45:16:45:35 | ControlFlowNode for Attribute() | | classes.py:60:17:60:27 | [pre objCreate] ControlFlowNode for With_init() | classes.py:54:18:54:21 | ControlFlowNode for self | | classes.py:242:9:242:24 | ControlFlowNode for set() | classes.py:242:9:242:24 | ControlFlowNode for set() | | classes.py:247:9:247:30 | ControlFlowNode for frozenset() | classes.py:247:9:247:30 | ControlFlowNode for frozenset() | | classes.py:252:9:252:28 | ControlFlowNode for dict() | classes.py:252:9:252:28 | ControlFlowNode for dict() | +| classes.py:559:16:559:17 | ControlFlowNode for Str | classes.py:565:5:565:22 | ControlFlowNode for Subscript | | classes.py:565:5:565:16 | ControlFlowNode for with_getitem | classes.py:555:21:555:24 | ControlFlowNode for self | | classes.py:565:18:565:21 | ControlFlowNode for arg2 | classes.py:555:27:555:29 | ControlFlowNode for key | | classes.py:581:5:581:16 | ControlFlowNode for with_setitem | classes.py:570:21:570:24 | ControlFlowNode for self | @@ -11,29 +13,68 @@ | classes.py:595:9:595:20 | ControlFlowNode for with_delitem | classes.py:586:21:586:24 | ControlFlowNode for self | | classes.py:595:22:595:25 | ControlFlowNode for arg2 | classes.py:586:27:586:29 | ControlFlowNode for key | | classes.py:618:16:618:28 | ControlFlowNode for Attribute() | classes.py:618:16:618:28 | ControlFlowNode for Attribute() | +| classes.py:659:15:659:18 | ControlFlowNode for self | classes.py:667:5:667:19 | ControlFlowNode for BinaryExpr | +| classes.py:661:16:661:19 | ControlFlowNode for self | classes.py:667:5:667:19 | ControlFlowNode for BinaryExpr | | classes.py:667:5:667:12 | ControlFlowNode for with_add | classes.py:657:17:657:20 | ControlFlowNode for self | +| classes.py:667:5:667:12 | ControlFlowNode for with_add | classes.py:667:5:667:19 | ControlFlowNode for BinaryExpr | | classes.py:667:16:667:19 | ControlFlowNode for arg2 | classes.py:657:23:657:27 | ControlFlowNode for other | +| classes.py:674:15:674:18 | ControlFlowNode for self | classes.py:682:5:682:19 | ControlFlowNode for BinaryExpr | +| classes.py:676:16:676:19 | ControlFlowNode for self | classes.py:682:5:682:19 | ControlFlowNode for BinaryExpr | | classes.py:682:5:682:12 | ControlFlowNode for with_sub | classes.py:672:17:672:20 | ControlFlowNode for self | +| classes.py:682:5:682:12 | ControlFlowNode for with_sub | classes.py:682:5:682:19 | ControlFlowNode for BinaryExpr | | classes.py:682:16:682:19 | ControlFlowNode for arg2 | classes.py:672:23:672:27 | ControlFlowNode for other | +| classes.py:689:15:689:18 | ControlFlowNode for self | classes.py:697:5:697:19 | ControlFlowNode for BinaryExpr | +| classes.py:691:16:691:19 | ControlFlowNode for self | classes.py:697:5:697:19 | ControlFlowNode for BinaryExpr | | classes.py:697:5:697:12 | ControlFlowNode for with_mul | classes.py:687:17:687:20 | ControlFlowNode for self | +| classes.py:697:5:697:12 | ControlFlowNode for with_mul | classes.py:697:5:697:19 | ControlFlowNode for BinaryExpr | | classes.py:697:16:697:19 | ControlFlowNode for arg2 | classes.py:687:23:687:27 | ControlFlowNode for other | +| classes.py:704:15:704:18 | ControlFlowNode for self | classes.py:712:5:712:22 | ControlFlowNode for BinaryExpr | +| classes.py:706:16:706:19 | ControlFlowNode for self | classes.py:712:5:712:22 | ControlFlowNode for BinaryExpr | | classes.py:712:5:712:15 | ControlFlowNode for with_matmul | classes.py:702:20:702:23 | ControlFlowNode for self | +| classes.py:712:5:712:15 | ControlFlowNode for with_matmul | classes.py:712:5:712:22 | ControlFlowNode for BinaryExpr | | classes.py:712:19:712:22 | ControlFlowNode for arg2 | classes.py:702:26:702:30 | ControlFlowNode for other | +| classes.py:719:15:719:18 | ControlFlowNode for self | classes.py:727:5:727:23 | ControlFlowNode for BinaryExpr | +| classes.py:721:16:721:19 | ControlFlowNode for self | classes.py:727:5:727:23 | ControlFlowNode for BinaryExpr | | classes.py:727:5:727:16 | ControlFlowNode for with_truediv | classes.py:717:21:717:24 | ControlFlowNode for self | +| classes.py:727:5:727:16 | ControlFlowNode for with_truediv | classes.py:727:5:727:23 | ControlFlowNode for BinaryExpr | | classes.py:727:20:727:23 | ControlFlowNode for arg2 | classes.py:717:27:717:31 | ControlFlowNode for other | +| classes.py:734:15:734:18 | ControlFlowNode for self | classes.py:742:5:742:25 | ControlFlowNode for BinaryExpr | +| classes.py:736:16:736:19 | ControlFlowNode for self | classes.py:742:5:742:25 | ControlFlowNode for BinaryExpr | | classes.py:742:5:742:17 | ControlFlowNode for with_floordiv | classes.py:732:22:732:25 | ControlFlowNode for self | +| classes.py:742:5:742:17 | ControlFlowNode for with_floordiv | classes.py:742:5:742:25 | ControlFlowNode for BinaryExpr | | classes.py:742:22:742:25 | ControlFlowNode for arg2 | classes.py:732:28:732:32 | ControlFlowNode for other | +| classes.py:749:15:749:18 | ControlFlowNode for self | classes.py:757:5:757:19 | ControlFlowNode for BinaryExpr | +| classes.py:751:16:751:19 | ControlFlowNode for self | classes.py:757:5:757:19 | ControlFlowNode for BinaryExpr | | classes.py:757:5:757:12 | ControlFlowNode for with_mod | classes.py:747:17:747:20 | ControlFlowNode for self | +| classes.py:757:5:757:12 | ControlFlowNode for with_mod | classes.py:757:5:757:19 | ControlFlowNode for BinaryExpr | | classes.py:757:16:757:19 | ControlFlowNode for arg2 | classes.py:747:23:747:27 | ControlFlowNode for other | +| classes.py:779:15:779:18 | ControlFlowNode for self | classes.py:793:5:793:20 | ControlFlowNode for BinaryExpr | +| classes.py:781:16:781:19 | ControlFlowNode for self | classes.py:793:5:793:20 | ControlFlowNode for BinaryExpr | | classes.py:793:5:793:12 | ControlFlowNode for with_pow | classes.py:777:17:777:20 | ControlFlowNode for self | +| classes.py:793:5:793:12 | ControlFlowNode for with_pow | classes.py:793:5:793:20 | ControlFlowNode for BinaryExpr | | classes.py:793:17:793:20 | ControlFlowNode for arg2 | classes.py:777:23:777:27 | ControlFlowNode for other | +| classes.py:800:15:800:18 | ControlFlowNode for self | classes.py:808:5:808:23 | ControlFlowNode for BinaryExpr | +| classes.py:802:16:802:19 | ControlFlowNode for self | classes.py:808:5:808:23 | ControlFlowNode for BinaryExpr | | classes.py:808:5:808:15 | ControlFlowNode for with_lshift | classes.py:798:20:798:23 | ControlFlowNode for self | +| classes.py:808:5:808:15 | ControlFlowNode for with_lshift | classes.py:808:5:808:23 | ControlFlowNode for BinaryExpr | | classes.py:808:20:808:23 | ControlFlowNode for arg2 | classes.py:798:26:798:30 | ControlFlowNode for other | +| classes.py:815:15:815:18 | ControlFlowNode for self | classes.py:823:5:823:23 | ControlFlowNode for BinaryExpr | +| classes.py:817:16:817:19 | ControlFlowNode for self | classes.py:823:5:823:23 | ControlFlowNode for BinaryExpr | | classes.py:823:5:823:15 | ControlFlowNode for with_rshift | classes.py:813:20:813:23 | ControlFlowNode for self | +| classes.py:823:5:823:15 | ControlFlowNode for with_rshift | classes.py:823:5:823:23 | ControlFlowNode for BinaryExpr | | classes.py:823:20:823:23 | ControlFlowNode for arg2 | classes.py:813:26:813:30 | ControlFlowNode for other | +| classes.py:830:15:830:18 | ControlFlowNode for self | classes.py:838:5:838:19 | ControlFlowNode for BinaryExpr | +| classes.py:832:16:832:19 | ControlFlowNode for self | classes.py:838:5:838:19 | ControlFlowNode for BinaryExpr | | classes.py:838:5:838:12 | ControlFlowNode for with_and | classes.py:828:17:828:20 | ControlFlowNode for self | +| classes.py:838:5:838:12 | ControlFlowNode for with_and | classes.py:838:5:838:19 | ControlFlowNode for BinaryExpr | | classes.py:838:16:838:19 | ControlFlowNode for arg2 | classes.py:828:23:828:27 | ControlFlowNode for other | +| classes.py:845:15:845:18 | ControlFlowNode for self | classes.py:853:5:853:19 | ControlFlowNode for BinaryExpr | +| classes.py:847:16:847:19 | ControlFlowNode for self | classes.py:853:5:853:19 | ControlFlowNode for BinaryExpr | | classes.py:853:5:853:12 | ControlFlowNode for with_xor | classes.py:843:17:843:20 | ControlFlowNode for self | +| classes.py:853:5:853:12 | ControlFlowNode for with_xor | classes.py:853:5:853:19 | ControlFlowNode for BinaryExpr | | classes.py:853:16:853:19 | ControlFlowNode for arg2 | classes.py:843:23:843:27 | ControlFlowNode for other | +| classes.py:860:15:860:18 | ControlFlowNode for self | classes.py:868:5:868:18 | ControlFlowNode for BinaryExpr | +| classes.py:862:16:862:19 | ControlFlowNode for self | classes.py:868:5:868:18 | ControlFlowNode for BinaryExpr | | classes.py:868:5:868:11 | ControlFlowNode for with_or | classes.py:858:16:858:19 | ControlFlowNode for self | +| classes.py:868:5:868:11 | ControlFlowNode for with_or | classes.py:868:5:868:18 | ControlFlowNode for BinaryExpr | | classes.py:868:15:868:18 | ControlFlowNode for arg2 | classes.py:858:22:858:26 | ControlFlowNode for other | diff --git a/python/ql/test/experimental/dataflow/coverage/classesCallGraph.ql b/python/ql/test/experimental/dataflow/coverage/classesCallGraph.ql index 1f8edfc1c79..c1b66d0f323 100644 --- a/python/ql/test/experimental/dataflow/coverage/classesCallGraph.ql +++ b/python/ql/test/experimental/dataflow/coverage/classesCallGraph.ql @@ -20,7 +20,7 @@ class CallGraphConfig extends DataFlow::Configuration { node instanceof DataFlow::ParameterNode and // exclude parameters to the SINK-functions not exists(DataFlowPrivate::DataFlowCallable c | - node.(DataFlow::ParameterNode).isParameterOf(c, _) and + c.getParameter(_) = node.asCfgNode() and c.getName().matches("SINK_") ) } diff --git a/python/ql/test/experimental/dataflow/summaries/NormalTaintTrackingTest.expected b/python/ql/test/experimental/dataflow/summaries/NormalTaintTrackingTest.expected new file mode 100644 index 00000000000..3875da4e143 --- /dev/null +++ b/python/ql/test/experimental/dataflow/summaries/NormalTaintTrackingTest.expected @@ -0,0 +1,2 @@ +missingAnnotationOnSink +failures diff --git a/python/ql/test/experimental/dataflow/summaries/NormalTaintTrackingTest.ql b/python/ql/test/experimental/dataflow/summaries/NormalTaintTrackingTest.ql new file mode 100644 index 00000000000..afb44b6b2ed --- /dev/null +++ b/python/ql/test/experimental/dataflow/summaries/NormalTaintTrackingTest.ql @@ -0,0 +1,3 @@ +import python +private import TestSummaries +import experimental.dataflow.TestUtil.NormalTaintTrackingTest diff --git a/python/ql/test/experimental/dataflow/summaries/TestSummaries.qll b/python/ql/test/experimental/dataflow/summaries/TestSummaries.qll new file mode 100644 index 00000000000..971a653c469 --- /dev/null +++ b/python/ql/test/experimental/dataflow/summaries/TestSummaries.qll @@ -0,0 +1,134 @@ +private import python +private import semmle.python.dataflow.new.FlowSummary +private import semmle.python.ApiGraphs + +/** + * This module ensures that the `callStep` predicate in + * our type tracker implelemtation does not refer to the + * `getACall` predicate on `SummarizedCallable`. + */ +module RecursionGuard { + private import semmle.python.dataflow.new.internal.TypeTrackerSpecific as TT + + private class RecursionGuard extends SummarizedCallable { + RecursionGuard() { this = "RecursionGuard" } + + override DataFlow::CallCfgNode getACall() { + result.getFunction().asCfgNode().(NameNode).getId() = this and + (TT::callStep(_, _) implies any()) + } + + override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } + } +} + +private class SummarizedCallableIdentity extends SummarizedCallable { + SummarizedCallableIdentity() { this = "identity" } + + override DataFlow::CallCfgNode getACall() { + result.getFunction().asCfgNode().(NameNode).getId() = this + } + + override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[0]" and + output = "ReturnValue" and + preservesValue = true + } +} + +// For lambda flow to work, implement lambdaCall and lambdaCreation +private class SummarizedCallableApplyLambda extends SummarizedCallable { + SummarizedCallableApplyLambda() { this = "apply_lambda" } + + override DataFlow::CallCfgNode getACall() { + result.getFunction().asCfgNode().(NameNode).getId() = this + } + + override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[1]" and + output = "Argument[0].Parameter[0]" and + preservesValue = true + or + input = "Argument[0].ReturnValue" and + output = "ReturnValue" and + preservesValue = true + } +} + +private class SummarizedCallableReversed extends SummarizedCallable { + SummarizedCallableReversed() { this = "reversed" } + + override DataFlow::CallCfgNode getACall() { + result.getFunction().asCfgNode().(NameNode).getId() = this + } + + override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[0].ListElement" and + output = "ReturnValue.ListElement" and + preservesValue = true + } +} + +private class SummarizedCallableMap extends SummarizedCallable { + SummarizedCallableMap() { this = "list_map" } + + override DataFlow::CallCfgNode getACall() { + result.getFunction().asCfgNode().(NameNode).getId() = this + } + + override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[1].ListElement" and + output = "Argument[0].Parameter[0]" and + preservesValue = true + or + input = "Argument[0].ReturnValue" and + output = "ReturnValue.ListElement" and + preservesValue = true + } +} + +private class SummarizedCallableAppend extends SummarizedCallable { + SummarizedCallableAppend() { this = "append_to_list" } + + override DataFlow::CallCfgNode getACall() { + result.getFunction().asCfgNode().(NameNode).getId() = this + } + + override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[0]" and + output = "ReturnValue" and + preservesValue = false + or + input = "Argument[1]" and + output = "ReturnValue.ListElement" and + preservesValue = true + } +} + +private class SummarizedCallableJsonLoads extends SummarizedCallable { + SummarizedCallableJsonLoads() { this = "json.loads" } + + override DataFlow::CallCfgNode getACall() { + result = API::moduleImport("json").getMember("loads").getACall() + } + + override DataFlow::ArgumentNode getACallback() { + result = API::moduleImport("json").getMember("loads").getAValueReachableFromSource() + } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + input = "Argument[0]" and + output = "ReturnValue.ListElement" and + preservesValue = true + } +} diff --git a/python/ql/test/experimental/dataflow/summaries/summaries.expected b/python/ql/test/experimental/dataflow/summaries/summaries.expected new file mode 100644 index 00000000000..2d1190eb69c --- /dev/null +++ b/python/ql/test/experimental/dataflow/summaries/summaries.expected @@ -0,0 +1,70 @@ +edges +| summaries.py:32:11:32:26 | ControlFlowNode for identity() | summaries.py:33:6:33:12 | ControlFlowNode for tainted | +| summaries.py:32:20:32:25 | ControlFlowNode for SOURCE | summaries.py:32:11:32:26 | ControlFlowNode for identity() | +| summaries.py:36:18:36:54 | ControlFlowNode for apply_lambda() | summaries.py:37:6:37:19 | ControlFlowNode for tainted_lambda | +| summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | summaries.py:36:18:36:54 | ControlFlowNode for apply_lambda() | +| summaries.py:44:25:44:32 | ControlFlowNode for List | summaries.py:45:6:45:20 | ControlFlowNode for Subscript | +| summaries.py:44:26:44:31 | ControlFlowNode for SOURCE | summaries.py:44:25:44:32 | ControlFlowNode for List | +| summaries.py:51:18:51:46 | ControlFlowNode for list_map() [List element] | summaries.py:52:6:52:19 | ControlFlowNode for tainted_mapped [List element] | +| summaries.py:51:38:51:45 | ControlFlowNode for List [List element] | summaries.py:51:18:51:46 | ControlFlowNode for list_map() [List element] | +| summaries.py:51:39:51:44 | ControlFlowNode for SOURCE | summaries.py:51:38:51:45 | ControlFlowNode for List [List element] | +| summaries.py:52:6:52:19 | ControlFlowNode for tainted_mapped [List element] | summaries.py:52:6:52:22 | ControlFlowNode for Subscript | +| summaries.py:57:27:57:63 | ControlFlowNode for list_map() [List element] | summaries.py:58:6:58:28 | ControlFlowNode for tainted_mapped_explicit [List element] | +| summaries.py:57:55:57:62 | ControlFlowNode for List [List element] | summaries.py:57:27:57:63 | ControlFlowNode for list_map() [List element] | +| summaries.py:57:56:57:61 | ControlFlowNode for SOURCE | summaries.py:57:55:57:62 | ControlFlowNode for List [List element] | +| summaries.py:58:6:58:28 | ControlFlowNode for tainted_mapped_explicit [List element] | summaries.py:58:6:58:31 | ControlFlowNode for Subscript | +| summaries.py:60:26:60:53 | ControlFlowNode for list_map() [List element] | summaries.py:61:6:61:27 | ControlFlowNode for tainted_mapped_summary [List element] | +| summaries.py:60:45:60:52 | ControlFlowNode for List [List element] | summaries.py:60:26:60:53 | ControlFlowNode for list_map() [List element] | +| summaries.py:60:46:60:51 | ControlFlowNode for SOURCE | summaries.py:60:45:60:52 | ControlFlowNode for List [List element] | +| summaries.py:61:6:61:27 | ControlFlowNode for tainted_mapped_summary [List element] | summaries.py:61:6:61:30 | ControlFlowNode for Subscript | +| summaries.py:63:16:63:41 | ControlFlowNode for append_to_list() [List element] | summaries.py:64:6:64:17 | ControlFlowNode for tainted_list [List element] | +| summaries.py:63:35:63:40 | ControlFlowNode for SOURCE | summaries.py:63:16:63:41 | ControlFlowNode for append_to_list() [List element] | +| summaries.py:64:6:64:17 | ControlFlowNode for tainted_list [List element] | summaries.py:64:6:64:20 | ControlFlowNode for Subscript | +| summaries.py:67:22:67:39 | ControlFlowNode for json_loads() [List element] | summaries.py:68:6:68:23 | ControlFlowNode for tainted_resultlist [List element] | +| summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | summaries.py:67:22:67:39 | ControlFlowNode for json_loads() [List element] | +| summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | summaries.py:68:6:68:26 | ControlFlowNode for Subscript | +| summaries.py:68:6:68:23 | ControlFlowNode for tainted_resultlist [List element] | summaries.py:68:6:68:26 | ControlFlowNode for Subscript | +nodes +| summaries.py:32:11:32:26 | ControlFlowNode for identity() | semmle.label | ControlFlowNode for identity() | +| summaries.py:32:20:32:25 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | +| summaries.py:33:6:33:12 | ControlFlowNode for tainted | semmle.label | ControlFlowNode for tainted | +| summaries.py:36:18:36:54 | ControlFlowNode for apply_lambda() | semmle.label | ControlFlowNode for apply_lambda() | +| summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | +| summaries.py:37:6:37:19 | ControlFlowNode for tainted_lambda | semmle.label | ControlFlowNode for tainted_lambda | +| summaries.py:44:25:44:32 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | +| summaries.py:44:26:44:31 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | +| summaries.py:45:6:45:20 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| summaries.py:51:18:51:46 | ControlFlowNode for list_map() [List element] | semmle.label | ControlFlowNode for list_map() [List element] | +| summaries.py:51:38:51:45 | ControlFlowNode for List [List element] | semmle.label | ControlFlowNode for List [List element] | +| summaries.py:51:39:51:44 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | +| summaries.py:52:6:52:19 | ControlFlowNode for tainted_mapped [List element] | semmle.label | ControlFlowNode for tainted_mapped [List element] | +| summaries.py:52:6:52:22 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| summaries.py:57:27:57:63 | ControlFlowNode for list_map() [List element] | semmle.label | ControlFlowNode for list_map() [List element] | +| summaries.py:57:55:57:62 | ControlFlowNode for List [List element] | semmle.label | ControlFlowNode for List [List element] | +| summaries.py:57:56:57:61 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | +| summaries.py:58:6:58:28 | ControlFlowNode for tainted_mapped_explicit [List element] | semmle.label | ControlFlowNode for tainted_mapped_explicit [List element] | +| summaries.py:58:6:58:31 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| summaries.py:60:26:60:53 | ControlFlowNode for list_map() [List element] | semmle.label | ControlFlowNode for list_map() [List element] | +| summaries.py:60:45:60:52 | ControlFlowNode for List [List element] | semmle.label | ControlFlowNode for List [List element] | +| summaries.py:60:46:60:51 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | +| summaries.py:61:6:61:27 | ControlFlowNode for tainted_mapped_summary [List element] | semmle.label | ControlFlowNode for tainted_mapped_summary [List element] | +| summaries.py:61:6:61:30 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| summaries.py:63:16:63:41 | ControlFlowNode for append_to_list() [List element] | semmle.label | ControlFlowNode for append_to_list() [List element] | +| summaries.py:63:35:63:40 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | +| summaries.py:64:6:64:17 | ControlFlowNode for tainted_list [List element] | semmle.label | ControlFlowNode for tainted_list [List element] | +| summaries.py:64:6:64:20 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| summaries.py:67:22:67:39 | ControlFlowNode for json_loads() [List element] | semmle.label | ControlFlowNode for json_loads() [List element] | +| summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE | +| summaries.py:68:6:68:23 | ControlFlowNode for tainted_resultlist [List element] | semmle.label | ControlFlowNode for tainted_resultlist [List element] | +| summaries.py:68:6:68:26 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +subpaths +invalidSpecComponent +#select +| summaries.py:33:6:33:12 | ControlFlowNode for tainted | summaries.py:32:20:32:25 | ControlFlowNode for SOURCE | summaries.py:33:6:33:12 | ControlFlowNode for tainted | $@ | summaries.py:32:20:32:25 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | +| summaries.py:37:6:37:19 | ControlFlowNode for tainted_lambda | summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | summaries.py:37:6:37:19 | ControlFlowNode for tainted_lambda | $@ | summaries.py:36:48:36:53 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | +| summaries.py:45:6:45:20 | ControlFlowNode for Subscript | summaries.py:44:26:44:31 | ControlFlowNode for SOURCE | summaries.py:45:6:45:20 | ControlFlowNode for Subscript | $@ | summaries.py:44:26:44:31 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | +| summaries.py:52:6:52:22 | ControlFlowNode for Subscript | summaries.py:51:39:51:44 | ControlFlowNode for SOURCE | summaries.py:52:6:52:22 | ControlFlowNode for Subscript | $@ | summaries.py:51:39:51:44 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | +| summaries.py:58:6:58:31 | ControlFlowNode for Subscript | summaries.py:57:56:57:61 | ControlFlowNode for SOURCE | summaries.py:58:6:58:31 | ControlFlowNode for Subscript | $@ | summaries.py:57:56:57:61 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | +| summaries.py:61:6:61:30 | ControlFlowNode for Subscript | summaries.py:60:46:60:51 | ControlFlowNode for SOURCE | summaries.py:61:6:61:30 | ControlFlowNode for Subscript | $@ | summaries.py:60:46:60:51 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | +| summaries.py:64:6:64:20 | ControlFlowNode for Subscript | summaries.py:63:35:63:40 | ControlFlowNode for SOURCE | summaries.py:64:6:64:20 | ControlFlowNode for Subscript | $@ | summaries.py:63:35:63:40 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | +| summaries.py:68:6:68:26 | ControlFlowNode for Subscript | summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | summaries.py:68:6:68:26 | ControlFlowNode for Subscript | $@ | summaries.py:67:33:67:38 | ControlFlowNode for SOURCE | ControlFlowNode for SOURCE | diff --git a/python/ql/test/experimental/dataflow/summaries/summaries.py b/python/ql/test/experimental/dataflow/summaries/summaries.py new file mode 100644 index 00000000000..1532a5393d8 --- /dev/null +++ b/python/ql/test/experimental/dataflow/summaries/summaries.py @@ -0,0 +1,68 @@ + +import sys +import os + +sys.path.append(os.path.dirname(os.path.dirname((__file__)))) +from testlib import expects + +# These are defined so that we can evaluate the test code. +NONSOURCE = "not a source" +SOURCE = "source" + + +def is_source(x): + return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j + + +def SINK(x): + if is_source(x): + print("OK") + else: + print("Unexpected flow", x) + + +def SINK_F(x): + if is_source(x): + print("Unexpected flow", x) + else: + print("OK") + + +# Simple summary +tainted = identity(SOURCE) +SINK(tainted) # $ flow="SOURCE, l:-1 -> tainted" + +# Lambda summary +tainted_lambda = apply_lambda(lambda x: x + 1, SOURCE) +SINK(tainted_lambda) # $ flow="SOURCE, l:-1 -> tainted_lambda" + +# A lambda that breaks the flow +untainted_lambda = apply_lambda(lambda x: 1, SOURCE) +SINK_F(untainted_lambda) + +# Collection summaries +tainted_list = reversed([SOURCE]) +SINK(tainted_list[0]) # $ flow="SOURCE, l:-1 -> tainted_list[0]" + +# Complex summaries +def add_colon(x): + return x + ":" + +tainted_mapped = list_map(add_colon, [SOURCE]) +SINK(tainted_mapped[0]) # $ flow="SOURCE, l:-1 -> tainted_mapped[0]" + +def explicit_identity(x): + return x + +tainted_mapped_explicit = list_map(explicit_identity, [SOURCE]) +SINK(tainted_mapped_explicit[0]) # $ flow="SOURCE, l:-1 -> tainted_mapped_explicit[0]" + +tainted_mapped_summary = list_map(identity, [SOURCE]) +SINK(tainted_mapped_summary[0]) # $ flow="SOURCE, l:-1 -> tainted_mapped_summary[0]" + +tainted_list = append_to_list([], SOURCE) +SINK(tainted_list[0]) # $ flow="SOURCE, l:-1 -> tainted_list[0]" + +from json import loads as json_loads +tainted_resultlist = json_loads(SOURCE) +SINK(tainted_resultlist[0]) # $ flow="SOURCE, l:-1 -> tainted_resultlist[0]" diff --git a/python/ql/test/experimental/dataflow/summaries/summaries.ql b/python/ql/test/experimental/dataflow/summaries/summaries.ql new file mode 100644 index 00000000000..f2c0a522279 --- /dev/null +++ b/python/ql/test/experimental/dataflow/summaries/summaries.ql @@ -0,0 +1,21 @@ +/** + * @kind path-problem + */ + +import python +import semmle.python.dataflow.new.FlowSummary +import DataFlow::PathGraph +import semmle.python.dataflow.new.TaintTracking +import semmle.python.dataflow.new.internal.FlowSummaryImpl +import semmle.python.ApiGraphs +import experimental.dataflow.testTaintConfig +private import TestSummaries + +query predicate invalidSpecComponent(SummarizedCallable sc, string s, string c) { + (sc.propagatesFlowExt(s, _, _) or sc.propagatesFlowExt(_, s, _)) and + Private::External::invalidSpecComponent(s, c) +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, TestConfiguration conf +where conf.hasFlowPath(source, sink) +select sink, source, sink, "$@", source, source.toString() diff --git a/python/ql/test/experimental/dataflow/testTaintConfig.qll b/python/ql/test/experimental/dataflow/testTaintConfig.qll new file mode 100644 index 00000000000..13d0620be92 --- /dev/null +++ b/python/ql/test/experimental/dataflow/testTaintConfig.qll @@ -0,0 +1,51 @@ +/** + * Configuration to test selected data flow + * Sources in the source code are denoted by the special name `SOURCE`, + * and sinks are denoted by arguments to the special function `SINK`. + * For example, given the test code + * ```python + * def test(): + * s = SOURCE + * SINK(s) + * ``` + * `SOURCE` will be a source and the second occurrence of `s` will be a sink. + * + * In order to test literals, alternative sources are defined for each type: + * + * for | use + * ---------- + * string | `"source"` + * integer | `42` + * float | `42.0` + * complex | `42j` (not supported yet) + */ + +private import python +import semmle.python.dataflow.new.DataFlow +import semmle.python.dataflow.new.TaintTracking + +class TestConfiguration extends TaintTracking::Configuration { + TestConfiguration() { this = "TestConfiguration" } + + override predicate isSource(DataFlow::Node node) { + node.(DataFlow::CfgNode).getNode().(NameNode).getId() = "SOURCE" + or + node.(DataFlow::CfgNode).getNode().getNode().(StrConst).getS() = "source" + or + node.(DataFlow::CfgNode).getNode().getNode().(IntegerLiteral).getN() = "42" + or + node.(DataFlow::CfgNode).getNode().getNode().(FloatLiteral).getN() = "42.0" + // No support for complex numbers + } + + override predicate isSink(DataFlow::Node node) { + exists(CallNode call | + call.getFunction().(NameNode).getId() in ["SINK", "SINK_F"] and + node.(DataFlow::CfgNode).getNode() = call.getAnArg() + ) + } + + override predicate isSanitizerIn(DataFlow::Node node) { this.isSource(node) } + + override int explorationLimit() { result = 5 } +} diff --git a/python/ql/test/library-tests/frameworks/data/test.ql b/python/ql/test/library-tests/frameworks/data/test.ql index cdd1782052a..00411a5f95f 100644 --- a/python/ql/test/library-tests/frameworks/data/test.ql +++ b/python/ql/test/library-tests/frameworks/data/test.ql @@ -1,5 +1,5 @@ import python -import semmle.python.frameworks.data.internal.AccessPathSyntax as AccessPathSyntax +import semmle.python.dataflow.new.internal.AccessPathSyntax as AccessPathSyntax import semmle.python.frameworks.data.ModelsAsData import semmle.python.dataflow.new.TaintTracking import semmle.python.dataflow.new.DataFlow diff --git a/python/ql/test/library-tests/frameworks/data/warnings.ql b/python/ql/test/library-tests/frameworks/data/warnings.ql index 3443233179e..20b87211765 100644 --- a/python/ql/test/library-tests/frameworks/data/warnings.ql +++ b/python/ql/test/library-tests/frameworks/data/warnings.ql @@ -1,5 +1,5 @@ import python -import semmle.python.frameworks.data.internal.AccessPathSyntax as AccessPathSyntax +import semmle.python.dataflow.new.internal.AccessPathSyntax as AccessPathSyntax import semmle.python.frameworks.data.internal.ApiGraphModels as ApiGraphModels import semmle.python.frameworks.data.ModelsAsData diff --git a/ql/ql/src/queries/style/AlertMessage.ql b/ql/ql/src/queries/style/AlertMessage.ql new file mode 100644 index 00000000000..8be6f0f2d99 --- /dev/null +++ b/ql/ql/src/queries/style/AlertMessage.ql @@ -0,0 +1,198 @@ +/** + * @name Alert message style violation + * @description An alert message that doesn't follow the style guide is harder for end users to digest. + * See the style guide here: https://github.com/github/codeql/blob/main/docs/query-metadata-style-guide.md#alert-messages + * @kind problem + * @problem.severity warning + * @id ql/alert-message-style-violation + * @precision high + */ + +import ql + +/** Gets the `index`th part of the select statement. */ +private AstNode getSelectPart(Select sel, int index) { + result = + rank[index](AstNode n, Location loc | + ( + n.getParent*() = sel.getExpr(_) and loc = n.getLocation() + or + // the strings are behind a predicate call. + exists(Call c, Predicate target | + c.getParent*() = sel.getExpr(_) and loc = c.getLocation() + | + c.getTarget() = target and + ( + target.getBody().(ComparisonFormula).getAnOperand() = n + or + exists(ClassPredicate sub | sub.overrides(target) | + sub.getBody().(ComparisonFormula).getAnOperand() = n + ) + ) + ) + ) + | + n + order by + loc.getStartLine(), loc.getStartColumn(), loc.getEndLine(), loc.getEndColumn(), + loc.getFile().getRelativePath() + ) +} + +/** + * Gets a string element that is the last part of the message, that doesn't end with a period. + * + * For example: + * ```CodeQL + * select foo(), "This is a description" // <- bad + * + * select foo(), "This is a description." // <- good + * ``` + */ +String shouldHaveFullStop(Select sel) { + result = + max(AstNode str, int i | + str.getParent+() = sel.getExpr(1) and str = getSelectPart(sel, i) + | + str order by i + ) and + not result.getValue().matches("%.") and + not result.getValue().matches("%?") +} + +/** + * Gets a string element that is the first part of the message, that starts with a lower case letter. + * + * For example: + * ```CodeQL + * select foo(), "this is a description." // <- bad + * + * select foo(), "This is a description." // <- good + * ``` + */ +String shouldStartCapital(Select sel) { + result = + min(AstNode str, int i | + str.getParent+() = sel.getExpr(1) and str = getSelectPart(sel, i) + | + str order by i + ) and + result.getValue().regexpMatch("^[a-z].*") +} + +/** + * Gets a string element that is used in a message that contains "here" or "this location". + * + * For example: + * ```CodeQL + * select foo(), "XSS happens here from using a unsafe value." // <- bad + * + * select foo(), "XSS from using a unsafe value." // <- good + * ``` + */ +String avoidHere(string part) { + part = ["here", "this location"] and + ( + result.getValue().regexpMatch(".*\\b" + part + "\\b.*") and + result = getSelectPart(_, _) + ) +} + +/** + * Avoid using an indefinite article ("a" or "an") in a link text. + * + * For example: + * ```CodeQL + * select foo(), "XSS from $@", val, "an unsafe value." // <- bad + * + * select foo(), "XSS from a $@", val, "unsafe value." // <- good + * ``` + * + * See https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html for the W3C guideline on link text. a + */ +String avoidArticleInLinkText(Select sel) { + result = sel.getExpr((any(int i | i > 1))) and + result = getSelectPart(sel, _) and + result.getValue().regexpMatch("a|an .*") +} + +/** + * Don't quote substitutions in a message. + * + * For example: + * ```CodeQL + * select foo(), "XSS from '$@'", val, "an unsafe value." // <- bad + * + * select foo(), "XSS from $@", val, "an unsafe value." // <- good + * ``` + */ +String dontQuoteSubstitutions(Select sel) { + result = getSelectPart(sel, _) and + result.getValue().matches(["%'$@'%", "%\"$@\"%"]) +} + +/** + * Gets the kind of the path-query represented by `sel`. + * Either "data" for a dataflow query or "taint" for a taint-tracking query. + */ +private string getQueryKind(Select sel) { + exists(TypeExpr sup | + sup = sel.getVarDecl(_).getType().(ClassType).getDeclaration().getASuperType() and + sup.getResolvedType().(ClassType).getName() = "Configuration" + | + result = "data" and + sup.getModule().getName() = "DataFlow" + or + result = "taint" and + sup.getModule().getName() = "TaintTracking" + ) +} + +/** + * Gets a string element from a message that uses the wrong phrase for a path query. + * A dataflow query should use "flows to" and a taint-tracking query should use "depends on". + */ +String wrongFlowsPhrase(Select sel, string kind) { + result = getSelectPart(sel, _) and + kind = getQueryKind(sel) and + ( + kind = "data" and + result.getValue().matches(["% depends %", "% depend %"]) + or + kind = "taint" and + result.getValue().matches(["% flows to %", "% flow to %"]) + ) +} + +from AstNode node, string msg +where + not node.getLocation().getFile().getAbsolutePath().matches("%/test/%") and + ( + node = shouldHaveFullStop(_) and + msg = "Alert message should end with a full stop." + or + node = shouldStartCapital(_) and + msg = "Alert message should start with a capital letter." + or + exists(string part | node = avoidHere(part) | + part = "here" and + msg = + "Try to use a descriptive phrase instead of \"here\". Use \"this location\" if you can't get around mentioning the current location." + or + part = "this location" and + msg = "Try to more descriptive phrase instead of \"this location\" if possible." + ) + or + node = avoidArticleInLinkText(_) and + msg = "Avoid starting a link text with an indefinite article." + or + node = dontQuoteSubstitutions(_) and + msg = "Don't quote substitutions in alert messages." + or + node = wrongFlowsPhrase(_, "data") and + msg = "Use \"flows to\" instead of \"depends on\" in data flow queries." + or + node = wrongFlowsPhrase(_, "taint") and + msg = "Use \"depends on\" instead of \"flows to\" in taint tracking queries." + ) +select node, msg diff --git a/ruby/ql/lib/codeql/ruby/security/regexp/NfaUtilsSpecific.qll b/ruby/ql/lib/codeql/ruby/security/regexp/NfaUtilsSpecific.qll index a882faa4838..6392d0f9cd2 100644 --- a/ruby/ql/lib/codeql/ruby/security/regexp/NfaUtilsSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/security/regexp/NfaUtilsSpecific.qll @@ -63,14 +63,6 @@ module RegExpFlags { root.getLiteral().isIgnoreCase() } - /** - * Gets the flags for `root`, or the empty string if `root` has no flags. - */ - string getFlags(RegExpTerm root) { - root.isRootTerm() and - result = root.getLiteral().getFlags() - } - /** * Holds if `root` has the `s` flag for multi-line matching. */ diff --git a/swift/extractor/BUILD.bazel b/swift/extractor/BUILD.bazel index bf5c9e65d52..28d89d920de 100644 --- a/swift/extractor/BUILD.bazel +++ b/swift/extractor/BUILD.bazel @@ -10,6 +10,7 @@ swift_cc_binary( deps = [ "//swift/extractor/infra", "//swift/extractor/visitors", + "//swift/extractor/remapping", "//swift/tools/prebuilt:swift-llvm-support", ], ) diff --git a/swift/extractor/SwiftExtractor.cpp b/swift/extractor/SwiftExtractor.cpp index c6473eca415..4d0ec679426 100644 --- a/swift/extractor/SwiftExtractor.cpp +++ b/swift/extractor/SwiftExtractor.cpp @@ -184,7 +184,6 @@ void codeql::extractSwiftFiles(const SwiftExtractorConfiguration& config, while (!todo.empty()) { auto module = todo.back(); todo.pop_back(); - llvm::errs() << "processing module " << module->getName() << '\n'; bool isFromSourceFile = false; std::unordered_set encounteredModules; for (auto file : module->getFiles()) { diff --git a/swift/extractor/SwiftOutputRewrite.cpp b/swift/extractor/SwiftOutputRewrite.cpp index b09a558d187..95315776aa0 100644 --- a/swift/extractor/SwiftOutputRewrite.cpp +++ b/swift/extractor/SwiftOutputRewrite.cpp @@ -1,4 +1,4 @@ -#include "SwiftOutputRewrite.h" +#include "swift/extractor/SwiftOutputRewrite.h" #include "swift/extractor/SwiftExtractorConfiguration.h" #include "swift/extractor/TargetTrapFile.h" diff --git a/swift/extractor/SwiftOutputRewrite.h b/swift/extractor/SwiftOutputRewrite.h index 94f1eeb6aaa..e8a61254a54 100644 --- a/swift/extractor/SwiftOutputRewrite.h +++ b/swift/extractor/SwiftOutputRewrite.h @@ -32,4 +32,5 @@ std::vector collectVFSFiles(const SwiftExtractorConfiguration& conf // Creates empty trap files for output swiftmodule files void lockOutputSwiftModuleTraps(const SwiftExtractorConfiguration& config, const std::unordered_map& remapping); + } // namespace codeql diff --git a/swift/extractor/main.cpp b/swift/extractor/main.cpp index 5f9afef4e81..d65b093a5cf 100644 --- a/swift/extractor/main.cpp +++ b/swift/extractor/main.cpp @@ -9,8 +9,9 @@ #include #include -#include "SwiftExtractor.h" -#include "SwiftOutputRewrite.h" +#include "swift/extractor/SwiftExtractor.h" +#include "swift/extractor/SwiftOutputRewrite.h" +#include "swift/extractor/remapping/SwiftOpenInterception.h" using namespace std::string_literals; @@ -21,14 +22,6 @@ class Observer : public swift::FrontendObserver { public: explicit Observer(const codeql::SwiftExtractorConfiguration& config) : config{config} {} - void parsedArgs(swift::CompilerInvocation& invocation) override { - auto& overlays = invocation.getSearchPathOptions().VFSOverlayFiles; - auto vfsFiles = codeql::collectVFSFiles(config); - for (auto& vfsFile : vfsFiles) { - overlays.push_back(vfsFile); - } - } - void performedSemanticAnalysis(swift::CompilerInstance& compiler) override { codeql::extractSwiftFiles(config, compiler); } @@ -49,6 +42,7 @@ int main(int argc, char** argv) { // TODO: print usage return 1; } + // Required by Swift/LLVM PROGRAM_START(argc, argv); INITIALIZE_LLVM(); @@ -58,6 +52,8 @@ int main(int argc, char** argv) { configuration.sourceArchiveDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SOURCE_ARCHIVE_DIR", "."); configuration.scratchDir = getenv_or("CODEQL_EXTRACTOR_SWIFT_SCRATCH_DIR", "."); + codeql::initRemapping(configuration.getTempArtifactDir()); + configuration.frontendOptions.reserve(argc - 1); for (int i = 1; i < argc; i++) { configuration.frontendOptions.push_back(argv[i]); @@ -67,7 +63,6 @@ int main(int argc, char** argv) { auto remapping = codeql::rewriteOutputsInPlace(configuration, configuration.patchedFrontendOptions); codeql::ensureDirectoriesForNewPathsExist(remapping); - codeql::storeRemappingForVFS(configuration, remapping); codeql::lockOutputSwiftModuleTraps(configuration, remapping); std::vector args; @@ -77,5 +72,8 @@ int main(int argc, char** argv) { Observer observer(configuration); int frontend_rc = swift::performFrontend(args, "swift-extractor", (void*)main, &observer); + + codeql::finalizeRemapping(remapping); + return frontend_rc; } diff --git a/swift/extractor/remapping/BUILD.bazel b/swift/extractor/remapping/BUILD.bazel new file mode 100644 index 00000000000..7a74b4213ec --- /dev/null +++ b/swift/extractor/remapping/BUILD.bazel @@ -0,0 +1,23 @@ +load("//swift:rules.bzl", "swift_cc_library") + +swift_cc_library( + name = "remapping", + srcs = select({ + "@platforms//os:linux": [ + "SwiftOpenInterception.Linux.cpp", + ], + "@platforms//os:macos": [ + "SwiftOpenInterception.macOS.cpp", + ], + }), + hdrs = glob(["*.h"]), + visibility = ["//swift:__subpackages__"], + deps = [ + "//swift/tools/prebuilt:swift-llvm-support", + ] + select({ + "@platforms//os:linux": [], + "@platforms//os:macos": [ + "@fishhook//:fishhook", + ], + }), +) diff --git a/swift/extractor/remapping/SwiftOpenInterception.Linux.cpp b/swift/extractor/remapping/SwiftOpenInterception.Linux.cpp new file mode 100644 index 00000000000..5afe28efe36 --- /dev/null +++ b/swift/extractor/remapping/SwiftOpenInterception.Linux.cpp @@ -0,0 +1,8 @@ +#include "swift/extractor/remapping/SwiftOpenInterception.h" + +namespace codeql { +// TBD +void initRemapping(const std::string& dir) {} +void finalizeRemapping(const std::unordered_map& mapping) {} + +} // namespace codeql diff --git a/swift/extractor/remapping/SwiftOpenInterception.h b/swift/extractor/remapping/SwiftOpenInterception.h new file mode 100644 index 00000000000..1b22d31ca39 --- /dev/null +++ b/swift/extractor/remapping/SwiftOpenInterception.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace codeql { + +void initRemapping(const std::string& dir); +void finalizeRemapping(const std::unordered_map& mapping); + +} // namespace codeql diff --git a/swift/extractor/remapping/SwiftOpenInterception.macOS.cpp b/swift/extractor/remapping/SwiftOpenInterception.macOS.cpp new file mode 100644 index 00000000000..a0f306aa4f3 --- /dev/null +++ b/swift/extractor/remapping/SwiftOpenInterception.macOS.cpp @@ -0,0 +1,82 @@ +#include "swift/extractor/remapping/SwiftOpenInterception.h" +#include +#include +#include +#include +#include +#include + +namespace codeql { + +static std::string scratchDir; +static bool interceptionEnabled = false; + +static int (*original_open)(const char*, int, ...) = nullptr; + +static std::string fileHash(const std::string& filename) { + int fd = original_open(filename.c_str(), O_RDONLY); + if (fd == -1) { + return {}; + } + auto maybeMD5 = llvm::sys::fs::md5_contents(fd); + close(fd); + if (!maybeMD5) { + return {}; + } + return maybeMD5->digest().str().str(); +} + +static int codeql_open(const char* path, int oflag, ...) { + va_list ap = {0}; + mode_t mode = 0; + if ((oflag & O_CREAT) != 0) { + // mode only applies to O_CREAT + va_start(ap, oflag); + mode = va_arg(ap, int); + va_end(ap); + } + + std::string newPath(path); + + if (interceptionEnabled && llvm::sys::fs::exists(newPath)) { + // TODO: check file magic instead + if (llvm::StringRef(newPath).endswith(".swiftmodule")) { + auto hash = fileHash(newPath); + auto hashed = scratchDir + "/" + hash; + if (!hash.empty() && llvm::sys::fs::exists(hashed)) { + newPath = hashed; + } + } + } + + return original_open(newPath.c_str(), oflag, mode); +} + +void finalizeRemapping(const std::unordered_map& mapping) { + for (auto& [original, patched] : mapping) { + // TODO: Check file magic instead + if (!llvm::StringRef(original).endswith(".swiftmodule")) { + continue; + } + auto hash = fileHash(original); + auto hashed = scratchDir + "/" + hash; + if (!hash.empty() && llvm::sys::fs::exists(patched)) { + if (std::error_code ec = llvm::sys::fs::create_link(/* from */ patched, /* to */ hashed)) { + llvm::errs() << "Cannot remap file '" << patched << "' -> '" << hashed + << "': " << ec.message() << "\n"; + } + } + } + interceptionEnabled = false; +} + +void initRemapping(const std::string& dir) { + scratchDir = dir; + + struct rebinding binding[] = { + {"open", reinterpret_cast(codeql_open), reinterpret_cast(&original_open)}}; + rebind_symbols(binding, 1); + interceptionEnabled = true; +} + +} // namespace codeql diff --git a/swift/tools/fishhook/BUILD.bazel b/swift/tools/fishhook/BUILD.bazel new file mode 100644 index 00000000000..e69de29bb2d diff --git a/swift/tools/fishhook/BUILD.fishhook.bazel b/swift/tools/fishhook/BUILD.fishhook.bazel new file mode 100644 index 00000000000..5919f608d65 --- /dev/null +++ b/swift/tools/fishhook/BUILD.fishhook.bazel @@ -0,0 +1,7 @@ +cc_library( + name = "fishhook", + srcs = glob(["*.c"]), + hdrs = glob(["*.h"]), + strip_include_prefix = ".", + visibility = ["//visibility:public"], +)