diff --git a/.github/workflows/check-change-note.yml b/.github/workflows/check-change-note.yml index 0b90ffbc668..8c036c2f1b3 100644 --- a/.github/workflows/check-change-note.yml +++ b/.github/workflows/check-change-note.yml @@ -19,5 +19,5 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh api 'repos/${{github.repository}}/pulls/${{github.event.number}}/files' --paginate | - jq 'any(.[].filename ; test("/change-notes/.*[.]md$"))' --exit-status + gh api 'repos/${{github.repository}}/pulls/${{github.event.number}}/files' --paginate --jq 'any(.[].filename ; test("/change-notes/.*[.]md$"))' | + grep true -c diff --git a/.github/workflows/csv-coverage.yml b/.github/workflows/csv-coverage.yml new file mode 100644 index 00000000000..54c172d66d3 --- /dev/null +++ b/.github/workflows/csv-coverage.yml @@ -0,0 +1,77 @@ +name: Build/check CSV flow coverage report + +on: + workflow_dispatch: + inputs: + qlModelShaOverride: + description: 'github/codeql repo SHA used for looking up the CSV models' + required: false + push: + branches: + - main + - 'rc/**' + pull_request: + paths: + - '.github/workflows/csv-coverage.yml' + - '*/ql/src/**/*.ql' + - '*/ql/src/**/*.qll' + - 'misc/scripts/library-coverage/*.py' + # input data files + - '*/documentation/library-coverage/cwe-sink.csv' + - '*/documentation/library-coverage/frameworks.csv' + # coverage report files + - '*/documentation/library-coverage/flow-model-coverage.csv' + - '*/documentation/library-coverage/flow-model-coverage.rst' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Clone self (github/codeql) + uses: actions/checkout@v2 + with: + path: script + - name: Clone self (github/codeql) at a given SHA for analysis + if: github.event.inputs.qlModelShaOverride != '' + uses: actions/checkout@v2 + with: + path: codeqlModels + ref: github.event.inputs.qlModelShaOverride + - name: Clone self (github/codeql) for analysis + if: github.event.inputs.qlModelShaOverride == '' + uses: actions/checkout@v2 + with: + path: codeqlModels + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Download CodeQL CLI + uses: dsaltares/fetch-gh-release-asset@aa37ae5c44d3c9820bc12fe675e8670ecd93bd1c + with: + repo: "github/codeql-cli-binaries" + version: "latest" + file: "codeql-linux64.zip" + token: ${{ secrets.GITHUB_TOKEN }} + - name: Unzip CodeQL CLI + run: unzip -d codeql-cli codeql-linux64.zip + - name: Build modeled package list + run: | + PATH="$PATH:codeql-cli/codeql" python script/misc/scripts/library-coverage/generate-report.py ci codeqlModels script + - name: Upload CSV package list + uses: actions/upload-artifact@v2 + with: + name: csv-flow-model-coverage + path: flow-model-coverage-*.csv + - name: Upload RST package list + uses: actions/upload-artifact@v2 + with: + name: rst-flow-model-coverage + path: flow-model-coverage-*.rst + # - name: Check coverage files + # if: github.event.pull_request + # run: | + # python script/misc/scripts/library-coverage/compare-files.py codeqlModels + diff --git a/config/identical-files.json b/config/identical-files.json index 8cfca3596c5..582e4c7b6dc 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -250,6 +250,10 @@ "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll", "csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll" ], + "SSA PrintAliasAnalysis": [ + "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/PrintAliasAnalysis.qll", + "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/PrintAliasAnalysis.qll" + ], "C++ SSA AliasAnalysisImports": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll", "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll" @@ -439,6 +443,10 @@ ], "CryptoAlgorithms Python/JS": [ "javascript/ql/src/semmle/javascript/security/CryptoAlgorithms.qll", - "python/ql/src/semmle/crypto/Crypto.qll" + "python/ql/src/semmle/python/concepts/CryptoAlgorithms.qll" + ], + "SensitiveDataHeuristics Python/JS": [ + "javascript/ql/src/semmle/javascript/security/internal/SensitiveDataHeuristics.qll", + "python/ql/src/semmle/python/security/internal/SensitiveDataHeuristics.qll" ] -} \ No newline at end of file +} diff --git a/cpp/change-notes/2021-03-11-overflow-abs.md b/cpp/change-notes/2021-03-11-overflow-abs.md new file mode 100644 index 00000000000..66854412f72 --- /dev/null +++ b/cpp/change-notes/2021-03-11-overflow-abs.md @@ -0,0 +1,2 @@ +lgtm +* The `cpp/tainted-arithmetic`, `cpp/arithmetic-with-extreme-values`, and `cpp/uncontrolled-arithmetic` queries now recognize more functions as returning the absolute value of their input. As a result, they produce fewer false positives. diff --git a/cpp/change-notes/2021-04-09-unsigned-difference-expression-compared-zero.md b/cpp/change-notes/2021-04-09-unsigned-difference-expression-compared-zero.md new file mode 100644 index 00000000000..3297dfce9a9 --- /dev/null +++ b/cpp/change-notes/2021-04-09-unsigned-difference-expression-compared-zero.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The 'Unsigned difference expression compared to zero' (cpp/unsigned-difference-expression-compared-zero) query has been improved to produce fewer false positive results. \ No newline at end of file diff --git a/cpp/change-notes/2021-26-04-more-sound-expr-might-overflow.md b/cpp/change-notes/2021-04-26-more-sound-expr-might-overflow.md similarity index 100% rename from cpp/change-notes/2021-26-04-more-sound-expr-might-overflow.md rename to cpp/change-notes/2021-04-26-more-sound-expr-might-overflow.md diff --git a/cpp/change-notes/2021-05-10-comparison-with-wider-type.md b/cpp/change-notes/2021-05-10-comparison-with-wider-type.md new file mode 100644 index 00000000000..f06895a3c1a --- /dev/null +++ b/cpp/change-notes/2021-05-10-comparison-with-wider-type.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The 'Comparison with wider type' (cpp/comparison-with-wider-type) query has been improved to produce fewer false positives. \ No newline at end of file diff --git a/cpp/change-notes/2021-05-12-uncontrolled-arithmetic.md b/cpp/change-notes/2021-05-12-uncontrolled-arithmetic.md new file mode 100644 index 00000000000..56fbc9a44ce --- /dev/null +++ b/cpp/change-notes/2021-05-12-uncontrolled-arithmetic.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The query "Uncontrolled arithmetic" (`cpp/uncontrolled-arithmetic`) has been improved to produce fewer false positives. diff --git a/cpp/change-notes/2021-05-14-uncontrolled-allocation-size.md b/cpp/change-notes/2021-05-14-uncontrolled-allocation-size.md new file mode 100644 index 00000000000..6f0c9d6fa98 --- /dev/null +++ b/cpp/change-notes/2021-05-14-uncontrolled-allocation-size.md @@ -0,0 +1,2 @@ +lgtm +* The "Tainted allocation size" query (cpp/uncontrolled-allocation-size) has been improved to produce fewer false positives. diff --git a/cpp/change-notes/2021-05-18-static-buffer-overflow.md b/cpp/change-notes/2021-05-18-static-buffer-overflow.md new file mode 100644 index 00000000000..f56040fe8aa --- /dev/null +++ b/cpp/change-notes/2021-05-18-static-buffer-overflow.md @@ -0,0 +1,2 @@ +lgtm +* The "Static buffer overflow" query (cpp/static-buffer-overflow) has been improved to produce fewer false positives. diff --git a/cpp/change-notes/2021-05-19-weak-cryptographic-algorithm.md b/cpp/change-notes/2021-05-19-weak-cryptographic-algorithm.md new file mode 100644 index 00000000000..5e48ba166b7 --- /dev/null +++ b/cpp/change-notes/2021-05-19-weak-cryptographic-algorithm.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The "Use of a broken or risky cryptographic algorithm" (`cpp/weak-cryptographic-algorithm`) query has been enhanced to reduce false positive results, and (rarely) find more true positive results. diff --git a/cpp/change-notes/2021-05-20-incorrect-allocation-error-handling.md b/cpp/change-notes/2021-05-20-incorrect-allocation-error-handling.md new file mode 100644 index 00000000000..f00856ed711 --- /dev/null +++ b/cpp/change-notes/2021-05-20-incorrect-allocation-error-handling.md @@ -0,0 +1,2 @@ +lgtm +* A new query (`cpp/incorrect-allocation-error-handling`) has been added. The query finds incorrect error-handling of calls to `operator new`. This query was originally [submitted as an experimental query by @ihsinme](https://github.com/github/codeql/pull/5010). diff --git a/cpp/change-notes/2021-05-20-ref-qualifiers.md b/cpp/change-notes/2021-05-20-ref-qualifiers.md new file mode 100644 index 00000000000..e4b394a2173 --- /dev/null +++ b/cpp/change-notes/2021-05-20-ref-qualifiers.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* lvalue/rvalue ref qualifiers are now accessible via the new predicates on `MemberFunction`(`.isLValueRefQualified`, `.isRValueRefQualified`, and `isRefQualified`). diff --git a/cpp/change-notes/2021-05-21-unsafe-strncat.md b/cpp/change-notes/2021-05-21-unsafe-strncat.md new file mode 100644 index 00000000000..135b5278df4 --- /dev/null +++ b/cpp/change-notes/2021-05-21-unsafe-strncat.md @@ -0,0 +1,2 @@ +lgtm +* The "Potentially unsafe call to strncat" query (cpp/unsafe-strncat) query has been improved to detect more cases of unsafe calls to `strncat`. diff --git a/cpp/ql/src/Critical/OverflowStatic.ql b/cpp/ql/src/Critical/OverflowStatic.ql index 833ee45499e..011c38ff734 100644 --- a/cpp/ql/src/Critical/OverflowStatic.ql +++ b/cpp/ql/src/Critical/OverflowStatic.ql @@ -14,6 +14,7 @@ import cpp import semmle.code.cpp.commons.Buffer +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis import LoopBounds private predicate staticBufferBase(VariableAccess access, Variable v) { @@ -51,6 +52,8 @@ predicate overflowOffsetInLoop(BufferAccess bufaccess, string msg) { loop.getStmt().getAChild*() = bufaccess.getEnclosingStmt() and loop.limit() >= bufaccess.bufferSize() and loop.counter().getAnAccess() = bufaccess.getArrayOffset() and + // Ensure that we don't have an upper bound on the array index that's less than the buffer size. + not upperBound(bufaccess.getArrayOffset().getFullyConverted()) < bufaccess.bufferSize() and msg = "Potential buffer-overflow: counter '" + loop.counter().toString() + "' <= " + loop.limit().toString() + " but '" + bufaccess.buffer().getName() + "' has " + @@ -94,17 +97,22 @@ class CallWithBufferSize extends FunctionCall { } int statedSizeValue() { - exists(Expr statedSizeSrc | - DataFlow::localExprFlow(statedSizeSrc, statedSizeExpr()) and - result = statedSizeSrc.getValue().toInt() - ) + // `upperBound(e)` defaults to `exprMaxVal(e)` when `e` isn't analyzable. So to get a meaningful + // result in this case we pick the minimum value obtainable from dataflow and range analysis. + result = + upperBound(statedSizeExpr()) + .minimum(min(Expr statedSizeSrc | + DataFlow::localExprFlow(statedSizeSrc, statedSizeExpr()) + | + statedSizeSrc.getValue().toInt() + )) } } predicate wrongBufferSize(Expr error, string msg) { exists(CallWithBufferSize call, int bufsize, Variable buf, int statedSize | staticBuffer(call.buffer(), buf, bufsize) and - statedSize = min(call.statedSizeValue()) and + statedSize = call.statedSizeValue() and statedSize > bufsize and error = call.statedSizeExpr() and msg = diff --git a/cpp/ql/src/Critical/ReturnValueIgnored.qhelp b/cpp/ql/src/Critical/ReturnValueIgnored.qhelp index de0636987a0..d82daf6d149 100644 --- a/cpp/ql/src/Critical/ReturnValueIgnored.qhelp +++ b/cpp/ql/src/Critical/ReturnValueIgnored.qhelp @@ -7,7 +7,7 @@

This rule finds calls to a function that ignore the return value. A function call is only marked -as a violation if at least 80% of the total calls to that function check the return value. Not +as a violation if at least 90% of the total calls to that function check the return value. Not checking a return value is a common source of defects from standard library functions like malloc or fread. These functions return the status information and the return values should always be checked to see if the operation succeeded before operating on any data modified or resources allocated by these functions. diff --git a/cpp/ql/src/Critical/ReturnValueIgnored.ql b/cpp/ql/src/Critical/ReturnValueIgnored.ql index b9143085720..b4a4a044068 100644 --- a/cpp/ql/src/Critical/ReturnValueIgnored.ql +++ b/cpp/ql/src/Critical/ReturnValueIgnored.ql @@ -1,6 +1,6 @@ /** * @name Return value of a function is ignored - * @description A call to a function ignores its return value, but more than 80% of the total number of calls to the function check the return value. Check the return value of functions consistently, especially for functions like 'fread' or the 'scanf' functions that return the status of the operation. + * @description A call to a function ignores its return value, but at least 90% of the total number of calls to the function check the return value. Check the return value of functions consistently, especially for functions like 'fread' or the 'scanf' functions that return the status of the operation. * @kind problem * @id cpp/return-value-ignored * @problem.severity recommendation diff --git a/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.cpp b/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.cpp index c5cbcd2d7f1..d15a123ce66 100644 --- a/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.cpp +++ b/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.cpp @@ -2,3 +2,7 @@ strncat(dest, src, strlen(dest)); //wrong: should use remaining size of dest strncat(dest, src, sizeof(dest)); //wrong: should use remaining size of dest. //Also fails if dest is a pointer and not an array. + +strncat(dest, source, sizeof(dest) - strlen(dest)); // wrong: writes a zero byte past the `dest` buffer. + +strncat(dest, source, sizeof(dest) - strlen(dest) - 1); // correct: reserves space for the zero byte. diff --git a/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.qhelp b/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.qhelp index 5424338e1d1..13c1e6d2710 100644 --- a/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.qhelp +++ b/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.qhelp @@ -4,7 +4,17 @@

The standard library function strncat appends a source string to a target string. -The third argument defines the maximum number of characters to append and should be less than or equal to the remaining space in the destination buffer. Calls of the form strncat(dest, src, strlen(dest)) or strncat(dest, src, sizeof(dest)) set the third argument to the entire size of the destination buffer. Executing a call of this type may cause a buffer overflow unless the buffer is known to be empty. Buffer overflows can lead to anything from a segmentation fault to a security vulnerability.

+The third argument defines the maximum number of characters to append and should be less than or equal +to the remaining space in the destination buffer.

+ +

Calls of the form strncat(dest, src, strlen(dest)) or strncat(dest, src, sizeof(dest)) set +the third argument to the entire size of the destination buffer. +Executing a call of this type may cause a buffer overflow unless the buffer is known to be empty.

+ +

Similarly, calls of the form strncat(dest, src, sizeof (dest) - strlen (dest)) allow one +byte to be written ouside the dest buffer.

+ +

Buffer overflows can lead to anything from a segmentation fault to a security vulnerability.

@@ -25,6 +35,10 @@ The third argument defines the maximum number of characters to append and should
  • M. Donaldson, Inside the Buffer Overflow Attack: Mechanism, Method & Prevention. SANS Institute InfoSec Reading Room, 2002.
  • +
  • + CERT C Coding Standard: +STR31-C. Guarantee that storage for strings has sufficient space for character data and the null terminator. +
  • diff --git a/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.ql b/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.ql index eae20876e35..5e211fc3da6 100644 --- a/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.ql +++ b/cpp/ql/src/Likely Bugs/Memory Management/SuspiciousCallToStrncat.ql @@ -1,7 +1,6 @@ /** * @name Potentially unsafe call to strncat - * @description Calling 'strncat' with the size of the destination buffer - * as the third argument may result in a buffer overflow. + * @description Calling 'strncat' with an incorrect size argument may result in a buffer overflow. * @kind problem * @problem.severity warning * @precision medium @@ -9,6 +8,7 @@ * @tags reliability * correctness * security + * external/cwe/cwe-788 * external/cwe/cwe-676 * external/cwe/cwe-119 * external/cwe/cwe-251 @@ -16,11 +16,53 @@ import cpp import Buffer +import semmle.code.cpp.models.implementations.Strcat +import semmle.code.cpp.valuenumbering.GlobalValueNumbering -from FunctionCall fc, VariableAccess va1, VariableAccess va2 -where - fc.getTarget().(Function).hasName("strncat") and - va1 = fc.getArgument(0) and - va2 = fc.getArgument(2).(BufferSizeExpr).getArg() and - va1.getTarget() = va2.getTarget() +/** + * Holds if `call` is a call to `strncat` such that `sizeArg` and `destArg` are the size and + * destination arguments, respectively. + */ +predicate interestringCallWithArgs(Call call, Expr sizeArg, Expr destArg) { + exists(StrcatFunction strcat | + strcat = call.getTarget() and + sizeArg = call.getArgument(strcat.getParamSize()) and + destArg = call.getArgument(strcat.getParamDest()) + ) +} + +/** + * Holds if `fc` is a call to `strncat` with size argument `sizeArg` and destination + * argument `destArg`, and `destArg` is the size of the buffer pointed to by `destArg`. + */ +predicate case1(FunctionCall fc, Expr sizeArg, VariableAccess destArg) { + interestringCallWithArgs(fc, sizeArg, destArg) and + exists(VariableAccess va | + va = sizeArg.(BufferSizeExpr).getArg() and + destArg.getTarget() = va.getTarget() + ) +} + +/** + * Holds if `fc` is a call to `strncat` with size argument `sizeArg` and destination + * argument `destArg`, and `sizeArg` computes the value `sizeof (dest) - strlen (dest)`. + */ +predicate case2(FunctionCall fc, Expr sizeArg, VariableAccess destArg) { + interestringCallWithArgs(fc, sizeArg, destArg) and + exists(SubExpr sub, int n | + // The destination buffer is an array of size n + destArg.getUnspecifiedType().(ArrayType).getSize() = n and + // The size argument is equivalent to a subtraction + globalValueNumber(sizeArg).getAnExpr() = sub and + // ... where the left side of the subtraction is the constant n + globalValueNumber(sub.getLeftOperand()).getAnExpr().getValue().toInt() = n and + // ... and the right side of the subtraction is a call to `strlen` where the argument is the + // destination buffer. + globalValueNumber(sub.getRightOperand()).getAnExpr().(StrlenCall).getStringExpr() = + globalValueNumber(destArg).getAnExpr() + ) +} + +from FunctionCall fc, Expr sizeArg, Expr destArg +where case1(fc, sizeArg, destArg) or case2(fc, sizeArg, destArg) select fc, "Potentially unsafe call to strncat." diff --git a/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql b/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql index a4b0f131d14..0899c7dff1a 100644 --- a/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql +++ b/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql @@ -15,34 +15,107 @@ import cpp import semmle.code.cpp.security.Overflow import semmle.code.cpp.security.Security import semmle.code.cpp.security.TaintTracking +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis import TaintedWithPath -predicate isRandCall(FunctionCall fc) { fc.getTarget().getName() = "rand" } - -predicate isRandCallOrParent(Expr e) { - isRandCall(e) or - isRandCallOrParent(e.getAChild()) +predicate isUnboundedRandCall(FunctionCall fc) { + exists(Function func | func = fc.getTarget() | + func.hasGlobalOrStdOrBslName("rand") and + not bounded(fc) and + func.getNumberOfParameters() = 0 + ) } -predicate isRandValue(Expr e) { - isRandCall(e) +/** + * An operand `e` of a division expression (i.e., `e` is an operand of either a `DivExpr` or + * a `AssignDivExpr`) is bounded when `e` is the left-hand side of the division. + */ +pragma[inline] +predicate boundedDiv(Expr e, Expr left) { e = left } + +/** + * An operand `e` of a remainder expression `rem` (i.e., `rem` is either a `RemExpr` or + * an `AssignRemExpr`) with left-hand side `left` and right-ahnd side `right` is bounded + * when `e` is `left` and `right` is upper bounded by some number that is less than the maximum integer + * allowed by the result type of `rem`. + */ +pragma[inline] +predicate boundedRem(Expr e, Expr rem, Expr left, Expr right) { + e = left and + upperBound(right.getFullyConverted()) < exprMaxVal(rem.getFullyConverted()) +} + +/** + * An operand `e` of a bitwise and expression `andExpr` (i.e., `andExpr` is either an `BitwiseAndExpr` + * or an `AssignAndExpr`) with operands `operand1` and `operand2` is the operand that is not `e` is upper + * bounded by some number that is less than the maximum integer allowed by the result type of `andExpr`. + */ +pragma[inline] +predicate boundedBitwiseAnd(Expr e, Expr andExpr, Expr operand1, Expr operand2) { + operand1 != operand2 and + e = operand1 and + upperBound(operand2.getFullyConverted()) < exprMaxVal(andExpr.getFullyConverted()) +} + +/** + * Holds if `fc` is a part of the left operand of a binary operation that greatly reduces the range + * of possible values. + */ +predicate bounded(Expr e) { + // For `%` and `&` we require that `e` is bounded by a value that is strictly smaller than the + // maximum possible value of the result type of the operation. + // For example, the function call `rand()` is considered bounded in the following program: + // ``` + // int i = rand() % (UINT8_MAX + 1); + // ``` + // but not in: + // ``` + // unsigned char uc = rand() % (UINT8_MAX + 1); + // ``` + exists(RemExpr rem | boundedRem(e, rem, rem.getLeftOperand(), rem.getRightOperand())) + or + exists(AssignRemExpr rem | boundedRem(e, rem, rem.getLValue(), rem.getRValue())) + or + exists(BitwiseAndExpr andExpr | + boundedBitwiseAnd(e, andExpr, andExpr.getAnOperand(), andExpr.getAnOperand()) + ) + or + exists(AssignAndExpr andExpr | + boundedBitwiseAnd(e, andExpr, andExpr.getAnOperand(), andExpr.getAnOperand()) + ) + or + // Optimitically assume that a division always yields a much smaller value. + boundedDiv(e, any(DivExpr div).getLeftOperand()) + or + boundedDiv(e, any(AssignDivExpr div).getLValue()) + or + boundedDiv(e, any(RShiftExpr shift).getLeftOperand()) + or + boundedDiv(e, any(AssignRShiftExpr div).getLValue()) +} + +predicate isUnboundedRandCallOrParent(Expr e) { + isUnboundedRandCall(e) + or + isUnboundedRandCallOrParent(e.getAChild()) +} + +predicate isUnboundedRandValue(Expr e) { + isUnboundedRandCall(e) or exists(MacroInvocation mi | e = mi.getExpr() and - isRandCallOrParent(e) + isUnboundedRandCallOrParent(e) ) } class SecurityOptionsArith extends SecurityOptions { override predicate isUserInput(Expr expr, string cause) { - isRandValue(expr) and - cause = "rand" and - not expr.getParent*() instanceof DivExpr + isUnboundedRandValue(expr) and + cause = "rand" } } -predicate isDiv(VariableAccess va) { exists(AssignDivExpr div | div.getLValue() = va) } - predicate missingGuard(VariableAccess va, string effect) { exists(Operation op | op.getAnOperand() = va | missingGuardAgainstUnderflow(op, va) and effect = "underflow" @@ -52,29 +125,15 @@ predicate missingGuard(VariableAccess va, string effect) { } class Configuration extends TaintTrackingConfiguration { - override predicate isSink(Element e) { - isDiv(e) - or - missingGuard(e, _) - } -} + override predicate isSink(Element e) { missingGuard(e, _) } -/** - * A value that undergoes division is likely to be bounded within a safe - * range. - */ -predicate guardedByAssignDiv(Expr origin) { - exists(VariableAccess va | - taintedWithPath(origin, va, _, _) and - isDiv(va) - ) + override predicate isBarrier(Expr e) { super.isBarrier(e) or bounded(e) } } from Expr origin, VariableAccess va, string effect, PathNode sourceNode, PathNode sinkNode where taintedWithPath(origin, va, sourceNode, sinkNode) and - missingGuard(va, effect) and - not guardedByAssignDiv(origin) + missingGuard(va, effect) select va, sourceNode, sinkNode, "$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".", origin, "Uncontrolled value" diff --git a/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql b/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql index 3303316cede..2d21ed8a3b2 100644 --- a/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql +++ b/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql @@ -49,7 +49,9 @@ where small = rel.getLesserOperand() and large = rel.getGreaterOperand() and rel = l.getCondition().getAChild*() and - upperBound(large).log2() > getComparisonSize(small) * 8 and + forall(Expr conv | conv = large.getConversion*() | + upperBound(conv).log2() > getComparisonSize(small) * 8 + ) and // Ignore cases where the smaller type is int or larger // These are still bugs, but you should need a very large string or array to // trigger them. We will want to disable this for some applications, but it's diff --git a/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql b/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql index cc2d52385c7..75cac365a1a 100644 --- a/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql +++ b/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql @@ -12,6 +12,7 @@ */ import cpp +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis import semmle.code.cpp.security.TaintTracking import TaintedWithPath @@ -27,6 +28,27 @@ predicate allocSink(Expr alloc, Expr tainted) { class TaintedAllocationSizeConfiguration extends TaintTrackingConfiguration { override predicate isSink(Element tainted) { allocSink(_, tainted) } + + override predicate isBarrier(Expr e) { + super.isBarrier(e) + or + // There can be two separate reasons for `convertedExprMightOverflow` not holding: + // 1. `e` really cannot overflow. + // 2. `e` isn't analyzable. + // If we didn't rule out case 2 we would place barriers on anything that isn't analyzable. + ( + e instanceof UnaryArithmeticOperation or + e instanceof BinaryArithmeticOperation or + e instanceof AssignArithmeticOperation + ) and + not convertedExprMightOverflow(e) + or + // Subtracting two pointers is either well-defined (and the result will likely be small), or + // terribly undefined and dangerous. Here, we assume that the programmer has ensured that the + // result is well-defined (i.e., the two pointers point to the same object), and thus the result + // will likely be small. + e = any(PointerDiffExpr diff).getAnOperand() + } } predicate taintedAllocSize( diff --git a/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql b/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql index 007a4fd746d..ff339af8b67 100644 --- a/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql +++ b/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql @@ -12,29 +12,60 @@ import cpp import semmle.code.cpp.commons.Exclusions -import semmle.code.cpp.valuenumbering.GlobalValueNumbering import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis +import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils import semmle.code.cpp.controlflow.Guards +import semmle.code.cpp.dataflow.DataFlow -/** Holds if `sub` is guarded by a condition which ensures that `left >= right`. */ +/** + * Holds if `sub` is guarded by a condition which ensures that + * `left >= right`. + */ pragma[noinline] predicate isGuarded(SubExpr sub, Expr left, Expr right) { - exists(GuardCondition guard | - guard.controls(sub.getBasicBlock(), true) and - guard.ensuresLt(left, right, 0, sub.getBasicBlock(), false) + exists(GuardCondition guard, int k | + guard.controls(sub.getBasicBlock(), _) and + guard.ensuresLt(left, right, k, sub.getBasicBlock(), false) and + k >= 0 ) } -/** Holds if `sub` will never be negative. */ -predicate nonNegative(SubExpr sub) { - not exprMightOverflowNegatively(sub.getFullyConverted()) +/** + * Holds if `n` is known or suspected to be less than or equal to + * `sub.getLeftOperand()`. + */ +predicate exprIsSubLeftOrLess(SubExpr sub, DataFlow::Node n) { + n.asExpr() = sub.getLeftOperand() or - // The subtraction is guarded by a check of the form `left >= right`. - exists(GVN left, GVN right | - // This is basically a poor man's version of a directional unbind operator. - strictcount([left, globalValueNumber(sub.getLeftOperand())]) = 1 and - strictcount([right, globalValueNumber(sub.getRightOperand())]) = 1 and - isGuarded(sub, left.getAnExpr(), right.getAnExpr()) + exists(DataFlow::Node other | + // dataflow + exprIsSubLeftOrLess(sub, other) and + ( + DataFlow::localFlowStep(n, other) or + DataFlow::localFlowStep(other, n) + ) + ) + or + exists(DataFlow::Node other | + // guard constraining `sub` + exprIsSubLeftOrLess(sub, other) and + isGuarded(sub, other.asExpr(), n.asExpr()) // other >= n + ) + or + exists(DataFlow::Node other, float p, float q | + // linear access of `other` + exprIsSubLeftOrLess(sub, other) and + linearAccess(n.asExpr(), other.asExpr(), p, q) and // n = p * other + q + p <= 1 and + q <= 0 + ) + or + exists(DataFlow::Node other, float p, float q | + // linear access of `n` + exprIsSubLeftOrLess(sub, other) and + linearAccess(other.asExpr(), n.asExpr(), p, q) and // other = p * n + q + p >= 1 and + q >= 0 ) } @@ -45,5 +76,6 @@ where ro.getLesserOperand().getValue().toInt() = 0 and ro.getGreaterOperand() = sub and sub.getFullyConverted().getUnspecifiedType().(IntegralType).isUnsigned() and - not nonNegative(sub) + exprMightOverflowNegatively(sub.getFullyConverted()) and // generally catches false positives involving constants + not exprIsSubLeftOrLess(sub, DataFlow::exprNode(sub.getRightOperand())) // generally catches false positives where there's a relation between the left and right operands select ro, "Unsigned subtraction can never be negative." diff --git a/cpp/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql b/cpp/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql index af64a1789c3..7162b000dfc 100644 --- a/cpp/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql +++ b/cpp/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql @@ -13,39 +13,111 @@ import cpp import semmle.code.cpp.security.Encryption -abstract class InsecureCryptoSpec extends Locatable { - abstract string description(); -} - -Function getAnInsecureFunction() { - result.getName().regexpMatch(getInsecureAlgorithmRegex()) and +/** + * A function which may relate to an insecure encryption algorithm. + */ +Function getAnInsecureEncryptionFunction() { + ( + isInsecureEncryption(result.getName()) or + isInsecureEncryption(result.getAParameter().getName()) or + isInsecureEncryption(result.getDeclaringType().getName()) + ) and exists(result.getACallToThisFunction()) } -class InsecureFunctionCall extends InsecureCryptoSpec, FunctionCall { - InsecureFunctionCall() { this.getTarget() = getAnInsecureFunction() } - - override string description() { result = "function call" } - - override string toString() { result = FunctionCall.super.toString() } - - override Location getLocation() { result = FunctionCall.super.getLocation() } +/** + * A function with additional evidence it is related to encryption. + */ +Function getAdditionalEvidenceFunction() { + ( + isEncryptionAdditionalEvidence(result.getName()) or + isEncryptionAdditionalEvidence(result.getAParameter().getName()) + ) and + exists(result.getACallToThisFunction()) } -Macro getAnInsecureMacro() { - result.getName().regexpMatch(getInsecureAlgorithmRegex()) and +/** + * A macro which may relate to an insecure encryption algorithm. + */ +Macro getAnInsecureEncryptionMacro() { + isInsecureEncryption(result.getName()) and exists(result.getAnInvocation()) } -class InsecureMacroSpec extends InsecureCryptoSpec, MacroInvocation { - InsecureMacroSpec() { this.getMacro() = getAnInsecureMacro() } - - override string description() { result = "macro invocation" } - - override string toString() { result = MacroInvocation.super.toString() } - - override Location getLocation() { result = MacroInvocation.super.getLocation() } +/** + * A macro with additional evidence it is related to encryption. + */ +Macro getAdditionalEvidenceMacro() { + isEncryptionAdditionalEvidence(result.getName()) and + exists(result.getAnInvocation()) } -from InsecureCryptoSpec c -select c, "This " + c.description() + " specifies a broken or weak cryptographic algorithm." +/** + * An enum constant which may relate to an insecure encryption algorithm. + */ +EnumConstant getAnInsecureEncryptionEnumConst() { isInsecureEncryption(result.getName()) } + +/** + * An enum constant with additional evidence it is related to encryption. + */ +EnumConstant getAdditionalEvidenceEnumConst() { isEncryptionAdditionalEvidence(result.getName()) } + +/** + * A function call we have a high confidence is related to use of an insecure + * encryption algorithm. + */ +class InsecureFunctionCall extends FunctionCall { + Element blame; + string explain; + + InsecureFunctionCall() { + // find use of an insecure algorithm name + ( + getTarget() = getAnInsecureEncryptionFunction() and + blame = this and + explain = "function call" + or + exists(MacroInvocation mi | + ( + mi.getAnExpandedElement() = this or + mi.getAnExpandedElement() = this.getAnArgument() + ) and + mi.getMacro() = getAnInsecureEncryptionMacro() and + blame = mi and + explain = "macro invocation" + ) + or + exists(EnumConstantAccess ec | + ec = this.getAnArgument() and + ec.getTarget() = getAnInsecureEncryptionEnumConst() and + blame = ec and + explain = "enum constant access" + ) + ) and + // find additional evidence that this function is related to encryption. + ( + getTarget() = getAdditionalEvidenceFunction() + or + exists(MacroInvocation mi | + ( + mi.getAnExpandedElement() = this or + mi.getAnExpandedElement() = this.getAnArgument() + ) and + mi.getMacro() = getAdditionalEvidenceMacro() + ) + or + exists(EnumConstantAccess ec | + ec = this.getAnArgument() and + ec.getTarget() = getAdditionalEvidenceEnumConst() + ) + ) + } + + Element getBlame() { result = blame } + + string getDescription() { result = explain } +} + +from InsecureFunctionCall c +select c.getBlame(), + "This " + c.getDescription() + " specifies a broken or weak cryptographic algorithm." diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp b/cpp/ql/src/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.cpp similarity index 87% rename from cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp rename to cpp/ql/src/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.cpp index 60f8c7cc9b7..055aadcedb6 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp +++ b/cpp/ql/src/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.cpp @@ -21,7 +21,7 @@ void bad2(std::size_t length) noexcept { } } -// GOOD: the allocation failure is handled appropiately. +// GOOD: the allocation failure is handled appropriately. void good1(std::size_t length) noexcept { try { int* dest = new int[length]; @@ -32,7 +32,7 @@ void good1(std::size_t length) noexcept { } } -// GOOD: the allocation failure is handled appropiately. +// GOOD: the allocation failure is handled appropriately. void good2(std::size_t length) noexcept { int* dest = new int[length]; if(!dest) { diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp b/cpp/ql/src/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.qhelp similarity index 93% rename from cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp rename to cpp/ql/src/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.qhelp index 9365579b44a..9e131e75d4e 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp +++ b/cpp/ql/src/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.qhelp @@ -17,7 +17,7 @@ make sure to handle the possibility of null pointers if new(std::nothrow)
    - + diff --git a/cpp/ql/src/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.ql b/cpp/ql/src/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.ql new file mode 100644 index 00000000000..b06df90c860 --- /dev/null +++ b/cpp/ql/src/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.ql @@ -0,0 +1,251 @@ +/** + * @name Incorrect allocation-error handling + * @description Mixing up the failure conditions of 'operator new' and 'operator new(std::nothrow)' can result in unexpected behavior. + * @kind problem + * @id cpp/incorrect-allocation-error-handling + * @problem.severity warning + * @precision medium + * @tags correctness + * security + * external/cwe/cwe-570 + */ + +import cpp +import semmle.code.cpp.valuenumbering.GlobalValueNumbering +import semmle.code.cpp.controlflow.Guards + +/** + * A C++ `delete` or `delete[]` expression. + */ +class DeleteOrDeleteArrayExpr extends Expr { + DeleteOrDeleteArrayExpr() { this instanceof DeleteExpr or this instanceof DeleteArrayExpr } + + DeallocationFunction getDeallocator() { + result = [this.(DeleteExpr).getDeallocator(), this.(DeleteArrayExpr).getDeallocator()] + } + + Destructor getDestructor() { + result = [this.(DeleteExpr).getDestructor(), this.(DeleteArrayExpr).getDestructor()] + } +} + +/** Gets the `Constructor` invoked when `newExpr` allocates memory. */ +Constructor getConstructorForAllocation(NewOrNewArrayExpr newExpr) { + result.getACallToThisFunction() = newExpr.getInitializer() +} + +/** Gets the `Destructor` invoked when `deleteExpr` deallocates memory. */ +Destructor getDestructorForDeallocation(DeleteOrDeleteArrayExpr deleteExpr) { + result = deleteExpr.getDestructor() +} + +/** Holds if the evaluation of `newExpr` may throw an exception. */ +predicate newMayThrow(NewOrNewArrayExpr newExpr) { + functionMayThrow(newExpr.getAllocator()) or + functionMayThrow(getConstructorForAllocation(newExpr)) +} + +/** Holds if the evaluation of `deleteExpr` may throw an exception. */ +predicate deleteMayThrow(DeleteOrDeleteArrayExpr deleteExpr) { + functionMayThrow(deleteExpr.getDeallocator()) or + functionMayThrow(getDestructorForDeallocation(deleteExpr)) +} + +/** + * Holds if the function may throw an exception when called. That is, if the body of the function looks + * like it might throw an exception, and the function does not have a `noexcept` or `throw()` specifier. + */ +predicate functionMayThrow(Function f) { + (not exists(f.getBlock()) or stmtMayThrow(f.getBlock())) and + not f.isNoExcept() and + not f.isNoThrow() +} + +/** Holds if the evaluation of `stmt` may throw an exception. */ +predicate stmtMayThrow(Stmt stmt) { + stmtMayThrow(stmt.(BlockStmt).getAStmt()) + or + convertedExprMayThrow(stmt.(ExprStmt).getExpr()) + or + convertedExprMayThrow(stmt.(DeclStmt).getADeclaration().(Variable).getInitializer().getExpr()) + or + exists(IfStmt ifStmt | ifStmt = stmt | + convertedExprMayThrow(ifStmt.getCondition()) or + stmtMayThrow([ifStmt.getThen(), ifStmt.getElse()]) + ) + or + exists(ConstexprIfStmt constIfStmt | constIfStmt = stmt | + stmtMayThrow([constIfStmt.getThen(), constIfStmt.getElse()]) + ) + or + exists(Loop loop | loop = stmt | + convertedExprMayThrow(loop.getCondition()) or + stmtMayThrow(loop.getStmt()) + ) + or + // The case for `Loop` already checked the condition and the statement. + convertedExprMayThrow(stmt.(RangeBasedForStmt).getUpdate()) + or + // The case for `Loop` already checked the condition and the statement. + exists(ForStmt forStmt | forStmt = stmt | + stmtMayThrow(forStmt.getInitialization()) + or + convertedExprMayThrow(forStmt.getUpdate()) + ) + or + exists(SwitchStmt switchStmt | switchStmt = stmt | + convertedExprMayThrow(switchStmt.getExpr()) or + stmtMayThrow(switchStmt.getStmt()) + ) + or + // NOTE: We don't include `TryStmt` as those exceptions are not "observable" outside the function. + stmtMayThrow(stmt.(Handler).getBlock()) + or + convertedExprMayThrow(stmt.(CoReturnStmt).getExpr()) + or + convertedExprMayThrow(stmt.(ReturnStmt).getExpr()) +} + +/** Holds if the evaluation of `e` (including conversions) may throw an exception. */ +predicate convertedExprMayThrow(Expr e) { + exprMayThrow(e) + or + convertedExprMayThrow(e.getConversion()) +} + +/** Holds if the evaluation of `e` may throw an exception. */ +predicate exprMayThrow(Expr e) { + e instanceof DynamicCast + or + e instanceof TypeidOperator + or + e instanceof ThrowExpr + or + newMayThrow(e) + or + deleteMayThrow(e) + or + convertedExprMayThrow(e.(UnaryOperation).getOperand()) + or + exists(BinaryOperation binOp | binOp = e | + convertedExprMayThrow([binOp.getLeftOperand(), binOp.getRightOperand()]) + ) + or + exists(Assignment assign | assign = e | + convertedExprMayThrow([assign.getLValue(), assign.getRValue()]) + ) + or + exists(CommaExpr comma | comma = e | + convertedExprMayThrow([comma.getLeftOperand(), comma.getRightOperand()]) + ) + or + exists(StmtExpr stmtExpr | stmtExpr = e | + convertedExprMayThrow(stmtExpr.getResultExpr()) or + stmtMayThrow(stmtExpr.getStmt()) + ) + or + convertedExprMayThrow(e.(Conversion).getExpr()) + or + exists(FunctionCall fc | fc = e | + not exists(fc.getTarget()) or + functionMayThrow(fc.getTarget()) or + convertedExprMayThrow(fc.getAnArgument()) + ) +} + +/** The `std::nothrow_t` class and its `bsl` variant. */ +class NoThrowType extends Struct { + NoThrowType() { this.hasGlobalOrStdOrBslName("nothrow_t") } +} + +/** An allocator that might throw an exception. */ +class ThrowingAllocator extends Function { + ThrowingAllocator() { + exists(NewOrNewArrayExpr newExpr | + newExpr.getAllocator() = this and + // Exclude custom overloads of `operator new`. + // What we really want here is to only include the functions that satisfy `functionMayThrow`, but + // there seems to be examples where `throw()` isn't extracted (which causes false positives). + // + // As noted in the QLDoc for `Function.getAllocatorCall`: + // + // "As a rule of thumb, there will be an allocator call precisely when the type + // being allocated has a custom `operator new`, or when an argument list appears + // after the `new` keyword and before the name of the type being allocated. + // + // In particular note that uses of placement-new and nothrow-new will have an + // allocator call." + // + // So we say an allocator might throw if: + // 1. It doesn't have a body + // 2. there isn't a parameter with type `nothrow_t` + // 3. the allocator isn't marked with `throw()` or `noexcept`. + not exists(this.getBlock()) and + not exists(Parameter p | p = this.getAParameter() | + p.getUnspecifiedType() instanceof NoThrowType + ) and + not this.isNoExcept() and + not this.isNoThrow() + ) + } +} + +/** The `std::bad_alloc` exception and its `bsl` variant. */ +class BadAllocType extends Class { + BadAllocType() { this.hasGlobalOrStdOrBslName("bad_alloc") } +} + +/** + * A catch block that catches a `std::bad_alloc` (or any of its superclasses), or a catch + * block that catches every exception (i.e., `catch(...)`). + */ +class BadAllocCatchBlock extends CatchBlock { + BadAllocCatchBlock() { + this.getParameter().getUnspecifiedType().stripType() = + any(BadAllocType badAlloc).getABaseClass*() + or + not exists(this.getParameter()) + } +} + +/** + * Holds if `newExpr` is embedded in a `try` statement with a catch block `catchBlock` that + * catches a `std::bad_alloc` exception, but nothing in the `try` block (including the `newExpr`) + * will throw that exception. + */ +predicate noThrowInTryBlock(NewOrNewArrayExpr newExpr, BadAllocCatchBlock catchBlock) { + exists(TryStmt try | + not stmtMayThrow(try.getStmt()) and + try.getACatchClause() = catchBlock and + newExpr.getEnclosingBlock().getEnclosingBlock*() = try.getStmt() + ) +} + +/** + * Holds if `newExpr` is handles allocation failures by throwing an exception, yet + * the guard condition `guard` compares the result of `newExpr` to a null value. + */ +predicate nullCheckInThrowingNew(NewOrNewArrayExpr newExpr, GuardCondition guard) { + newExpr.getAllocator() instanceof ThrowingAllocator and + ( + // Handles null comparisons. + guard.ensuresEq(globalValueNumber(newExpr).getAnExpr(), any(NullValue null), _, _, _) + or + // Handles `if(ptr)` and `if(!ptr)` cases. + guard = globalValueNumber(newExpr).getAnExpr() + ) +} + +from NewOrNewArrayExpr newExpr, Element element, string msg, string elementString +where + not newExpr.isFromUninstantiatedTemplate(_) and + ( + noThrowInTryBlock(newExpr, element) and + msg = "This allocation cannot throw. $@ is unnecessary." and + elementString = "This catch block" + or + nullCheckInThrowingNew(newExpr, element) and + msg = "This allocation cannot return null. $@ is unnecessary." and + elementString = "This check" + ) +select newExpr, msg, element, elementString diff --git a/cpp/ql/src/Summary/LinesOfCode.ql b/cpp/ql/src/Summary/LinesOfCode.ql index 3b2aa2ac4c9..2d816b349e8 100644 --- a/cpp/ql/src/Summary/LinesOfCode.ql +++ b/cpp/ql/src/Summary/LinesOfCode.ql @@ -4,6 +4,7 @@ * @description The total number of lines of C/C++ code across all files, including system headers, libraries, and auto-generated files. This is a useful metric of the size of a database. For all files that were seen during the build, this query counts the lines of code, excluding whitespace or comments. * @kind metric * @tags summary + * lines-of-code */ import cpp diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.cpp new file mode 100644 index 00000000000..3b85835fff9 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +int main(int argc, char ** argv) { + + if (argc != 2) { + throw std::runtime_error("Give me a string!"); + } + + pqxx::connection c; + pqxx::work w(c); + + // BAD + char *userName = argv[1]; + char query1[1000] = {0}; + sprintf(query1, "SELECT UID FROM USERS where name = \"%s\"", userName); + pqxx::row r = w.exec1(query1); + w.commit(); + std::cout << r[0].as() << std::endl; + + // GOOD + pqxx::result r2 = w.exec("SELECT " + w.quote(argv[1])); + w.commit(); + std::cout << r2[0][0].c_str() << std::endl; + + return 0; +} diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.qhelp new file mode 100644 index 00000000000..1c01b3e4f3a --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.qhelp @@ -0,0 +1,31 @@ + + + +

    The code passes user input as part of a SQL query without escaping special elements. +It generates a SQL query to Postgres using sprintf, +with the user-supplied data directly passed as an argument +to sprintf. This leaves the code vulnerable to attack by SQL Injection.

    + +
    + + +

    Use a library routine to escape characters in the user-supplied +string before converting it to SQL. Use esc and quote pqxx library functions.

    + +
    + + + + + + +
  • MSDN Library: SQL Injection.
  • + + + + +
    +
    diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.ql b/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.ql new file mode 100644 index 00000000000..8de55953b15 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.ql @@ -0,0 +1,113 @@ +/** + * @name Uncontrolled data in SQL query to Postgres + * @description Including user-supplied data in a SQL query to Postgres + * without neutralizing special elements can make code + * vulnerable to SQL Injection. + * @kind path-problem + * @problem.severity error + * @precision high + * @id cpp/sql-injection-via-pqxx + * @tags security + * external/cwe/cwe-089 + */ + +import cpp +import semmle.code.cpp.security.Security +import semmle.code.cpp.dataflow.TaintTracking +import DataFlow::PathGraph + +predicate pqxxTransationClassNames(string className, string namespace) { + namespace = "pqxx" and + className in [ + "dbtransaction", "nontransaction", "basic_robusttransaction", "robusttransaction", + "subtransaction", "transaction", "basic_transaction", "transaction_base", "work" + ] +} + +predicate pqxxConnectionClassNames(string className, string namespace) { + namespace = "pqxx" and + className in ["connection_base", "basic_connection", "connection"] +} + +predicate pqxxTransactionSqlArgument(string function, int arg) { + function = "exec" and arg = 0 + or + function = "exec0" and arg = 0 + or + function = "exec1" and arg = 0 + or + function = "exec_n" and arg = 1 + or + function = "exec_params" and arg = 0 + or + function = "exec_params0" and arg = 0 + or + function = "exec_params1" and arg = 0 + or + function = "exec_params_n" and arg = 1 + or + function = "query_value" and arg = 0 + or + function = "stream" and arg = 0 +} + +predicate pqxxConnectionSqlArgument(string function, int arg) { function = "prepare" and arg = 1 } + +Expr getPqxxSqlArgument() { + exists(FunctionCall fc, Expr e, int argIndex, UserType t | + // examples: 'work' for 'work.exec(...)'; '->' for 'tx->exec()'. + e = fc.getQualifier() and + // to find ConnectionHandle/TransationHandle and similar classes which override '->' operator behavior + // and return pointer to a connection/transation object + e.getType().refersTo(t) and + // transaction exec and connection prepare variations + ( + pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) and + pqxxTransactionSqlArgument(fc.getTarget().getName(), argIndex) + or + pqxxConnectionClassNames(t.getName(), t.getNamespace().getName()) and + pqxxConnectionSqlArgument(fc.getTarget().getName(), argIndex) + ) and + result = fc.getArgument(argIndex) + ) +} + +predicate pqxxEscapeArgument(string function, int arg) { + arg = 0 and + function in ["esc", "esc_raw", "quote", "quote_raw", "quote_name", "quote_table", "esc_like"] +} + +predicate isEscapedPqxxArgument(Expr argExpr) { + exists(FunctionCall fc, Expr e, int argIndex, UserType t | + // examples: 'work' for 'work.exec(...)'; '->' for 'tx->exec()'. + e = fc.getQualifier() and + // to find ConnectionHandle/TransationHandle and similar classes which override '->' operator behavior + // and return pointer to a connection/transation object + e.getType().refersTo(t) and + // transaction and connection escape functions + ( + pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) or + pqxxConnectionClassNames(t.getName(), t.getNamespace().getName()) + ) and + pqxxEscapeArgument(fc.getTarget().getName(), argIndex) and + // is escaped arg == argExpr + argExpr = fc.getArgument(argIndex) + ) +} + +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "SqlPqxxTainted" } + + override predicate isSource(DataFlow::Node source) { isUserInput(source.asExpr(), _) } + + override predicate isSink(DataFlow::Node sink) { sink.asExpr() = getPqxxSqlArgument() } + + override predicate isSanitizer(DataFlow::Node node) { isEscapedPqxxArgument(node.asExpr()) } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, Configuration config, string taintCause +where + config.hasFlowPath(source, sink) and + isUserInput(source.getNode().asExpr(), taintCause) +select sink, source, sink, "This argument to a SQL query function is derived from $@", source, + "user input (" + taintCause + ")" diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.c b/cpp/ql/src/experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.c new file mode 100644 index 00000000000..53a50841977 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.c @@ -0,0 +1,14 @@ +while(intIndex > 2) +{ + ... + intIndex--; + ... +} // GOOD: correct cycle +... +while(intIndex > 2) +{ + ... + int intIndex; + intIndex--; + ... +} // BAD: the variable used in the condition does not change. diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.qhelp new file mode 100644 index 00000000000..5234212f7ca --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.qhelp @@ -0,0 +1,26 @@ + + + +

    Using variables with the same name is dangerous. However, such a situation inside the while loop can create an infinite loop exhausting resources. Requires the attention of developers.

    + +
    + +

    We recommend not to use local variables inside a loop if their names are the same as the variables in the condition of this loop.

    + +
    + +

    The following example demonstrates an erroneous and corrected use of a local variable within a loop.

    + + +
    + + +
  • + CERT C Coding Standard: + DCL01-C. Do not reuse variable names in subscopes. +
  • + +
    +
    diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.ql b/cpp/ql/src/experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.ql new file mode 100644 index 00000000000..e73f36145c6 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.ql @@ -0,0 +1,60 @@ +/** + * @name Errors When Using Variable Declaration Inside Loop + * @description Using variables with the same name is dangerous. + * However, such a situation inside the while loop can create an infinite loop exhausting resources. + * Requires the attention of developers. + * @kind problem + * @id cpp/errors-when-using-variable-declaration-inside-loop + * @problem.severity warning + * @precision medium + * @tags correctness + * security + * external/cwe/cwe-1126 + */ + +import cpp + +/** + * Errors when using a variable declaration inside a loop. + */ +class DangerousWhileLoop extends WhileStmt { + Expr exp; + Declaration dl; + + DangerousWhileLoop() { + this = dl.getParentScope().(BlockStmt).getParent*() and + exp = this.getCondition().getAChild*() and + not exp instanceof PointerFieldAccess and + not exp instanceof ValueFieldAccess and + exp.(VariableAccess).getTarget().getName() = dl.getName() and + not exp.getParent*() instanceof FunctionCall + } + + Declaration getDeclaration() { result = dl } + + /** Holds when there are changes to the variables involved in the condition. */ + predicate isUseThisVariable() { + exists(Variable v | + this.getCondition().getAChild*().(VariableAccess).getTarget() = v and + ( + exists(Assignment aexp | + this = aexp.getEnclosingStmt().getParentStmt*() and + ( + aexp.getLValue().(ArrayExpr).getArrayBase().(VariableAccess).getTarget() = v + or + aexp.getLValue().(VariableAccess).getTarget() = v + ) + ) + or + exists(CrementOperation crm | + this = crm.getEnclosingStmt().getParentStmt*() and + crm.getOperand().(VariableAccess).getTarget() = v + ) + ) + ) + } +} + +from DangerousWhileLoop lp +where not lp.isUseThisVariable() +select lp.getDeclaration(), "A variable with this name is used in the $@ condition.", lp, "loop" diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.c b/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.c new file mode 100644 index 00000000000..ce7fcc6846e --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.c @@ -0,0 +1,14 @@ +... + buf = malloc(intSize); +... + free(buf); + buf = NULL; + if(buf) free(buf); // GOOD +... + +... + buf = malloc(intSize); +... + free(buf); + if(buf) free(buf); // BAD: the cleanup function does not zero out the pointer +... diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.qhelp new file mode 100644 index 00000000000..69ef348be34 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.qhelp @@ -0,0 +1,26 @@ + + + +

    Freeing a previously allocated resource twice can lead to various vulnerabilities in the program.

    + +
    + +

    We recommend that you exclude situations of possible double release. For example, use the assignment NULL to a freed variable.

    + +
    + +

    The following example demonstrates an erroneous and corrected use of freeing a pointer.

    + + +
    + + +
  • + CERT C Coding Standard: + MEM30-C. Do not access freed memory. +
  • + +
    +
    diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.ql b/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.ql new file mode 100644 index 00000000000..0544c2aefd5 --- /dev/null +++ b/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.ql @@ -0,0 +1,43 @@ +/** + * @name Errors When Double Free + * @description Freeing a previously allocated resource twice can lead to various vulnerabilities in the program. + * @kind problem + * @id cpp/double-free + * @problem.severity warning + * @precision medium + * @tags security + * external/cwe/cwe-415 + */ + +import cpp + +from FunctionCall fc, FunctionCall fc2, LocalScopeVariable v +where + freeCall(fc, v.getAnAccess()) and + freeCall(fc2, v.getAnAccess()) and + fc != fc2 and + fc.getASuccessor*() = fc2 and + not exists(Expr exptmp | + (exptmp = v.getAnAssignedValue() or exptmp.(AddressOfExpr).getOperand() = v.getAnAccess()) and + exptmp = fc.getASuccessor*() and + exptmp = fc2.getAPredecessor*() + ) and + not exists(FunctionCall fctmp | + not fctmp instanceof DeallocationExpr and + fctmp = fc.getASuccessor*() and + fctmp = fc2.getAPredecessor*() and + fctmp.getAnArgument().(VariableAccess).getTarget() = v + ) and + ( + fc.getTarget().hasGlobalOrStdName("realloc") and + ( + not fc.getParent*() instanceof IfStmt and + not exists(IfStmt iftmp | + iftmp.getCondition().getAChild*().(VariableAccess).getTarget().getAnAssignedValue() = fc + ) + ) + or + not fc.getTarget().hasGlobalOrStdName("realloc") + ) +select fc2.getArgument(0), + "This pointer may have already been cleared in the line " + fc.getLocation().getStartLine() + "." diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql deleted file mode 100644 index 8789a468896..00000000000 --- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @name Detect And Handle Memory Allocation Errors - * @description `operator new` throws an exception on allocation failures, while `operator new(std::nothrow)` returns a null pointer. Mixing up these two failure conditions can result in unexpected behavior. - * @kind problem - * @id cpp/detect-and-handle-memory-allocation-errors - * @problem.severity warning - * @precision medium - * @tags correctness - * security - * external/cwe/cwe-570 - */ - -import cpp - -/** - * Lookup if condition compare with 0 - */ -class IfCompareWithZero extends IfStmt { - IfCompareWithZero() { - this.getCondition().(EQExpr).getAChild().getValue() = "0" - or - this.getCondition().(NEExpr).getAChild().getValue() = "0" and - this.hasElse() - or - this.getCondition().(NEExpr).getAChild().getValue() = "0" and - this.getThen().getAChild*() instanceof ReturnStmt - } -} - -/** - * lookup for calls to `operator new`, with incorrect error handling. - */ -class WrongCheckErrorOperatorNew extends FunctionCall { - Expr exp; - - WrongCheckErrorOperatorNew() { - this = exp.(NewOrNewArrayExpr).getAChild().(FunctionCall) and - ( - this.getTarget().hasGlobalOrStdName("operator new") - or - this.getTarget().hasGlobalOrStdName("operator new[]") - ) - } - - /** - * Holds if handler `try ... catch` exists. - */ - predicate isExistsTryCatchBlock() { - exists(TryStmt ts | this.getEnclosingStmt() = ts.getStmt().getAChild*()) - } - - /** - * Holds if results call `operator new` check in `operator if`. - */ - predicate isExistsIfCondition() { - exists(IfCompareWithZero ifc | - // call `operator new` directly from the condition of `operator if`. - this = ifc.getCondition().getAChild*() - or - postDominates(ifc, this) and - exists(Variable v | - v = ifc.getCondition().getAChild().(VariableAccess).getTarget() and - ( - exists(AssignExpr aex | - // check results call `operator new` with variable appropriation - aex.getAChild() = exp and - v = aex.getLValue().(VariableAccess).getTarget() - ) - or - exists(Initializer it | - // check results call `operator new` with declaration variable - exp = it.getExpr() and - it.getDeclaration() = v - ) - ) - ) - ) - } - - /** - * Holds if `(std::nothrow)` or `(std::noexcept)` exists in call `operator new`. - */ - predicate isExistsNothrow() { getTarget().isNoExcept() or getTarget().isNoThrow() } -} - -from WrongCheckErrorOperatorNew op -where - // use call `operator new` with `(std::nothrow)` and checking error using `try ... catch` block and not `operator if` - op.isExistsNothrow() and not op.isExistsIfCondition() and op.isExistsTryCatchBlock() - or - // use call `operator new` without `(std::nothrow)` and checking error using `operator if` and not `try ... catch` block - not op.isExistsNothrow() and not op.isExistsTryCatchBlock() and op.isExistsIfCondition() -select op, "memory allocation error check is incorrect or missing" diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.c b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.c deleted file mode 100644 index 060a22b5c18..00000000000 --- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.c +++ /dev/null @@ -1,4 +0,0 @@ - -strncat(dest, source, sizeof(dest) - strlen(dest)); // BAD: writes a zero byte past the `dest` buffer. - -strncat(dest, source, sizeof(dest) - strlen(dest) -1); // GOOD: Reserves space for the zero byte. diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qhelp deleted file mode 100644 index 5c2154097ec..00000000000 --- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qhelp +++ /dev/null @@ -1,32 +0,0 @@ - - - -

    The standard library function strncat(dest, source, count) appends the source string to the dest string. count specifies the maximum number of characters to append and must be less than the remaining space in the target buffer. Calls of the form strncat (dest, source, sizeof (dest) - strlen (dest)) set the third argument to one more than possible. So when the dest is full, the expression sizeof (dest) - strlen (dest) will be equal to one, and not zero as the programmer might think. Making a call of this type may result in a zero byte being written just outside the dest buffer.

    - - -
    - - -

    We recommend subtracting one from the third argument. For example, replace strncat(dest, source, sizeof(dest)-strlen(dest)) with strncat(dest, source, sizeof(dest)-strlen(dest)-1).

    - -
    - -

    The following example demonstrates an erroneous and corrected use of the strncat function.

    - - -
    - - -
  • - CERT C Coding Standard: -STR31-C. Guarantee that storage for strings has sufficient space for character data and the null terminator. -
  • -
  • - CERT C Coding Standard: - ARR30-C. Do not form or use out-of-bounds pointers or array subscripts. -
  • - -
    -
    diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql deleted file mode 100644 index f68395f29bf..00000000000 --- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @name Access Of Memory Location After The End Of A Buffer Using Strncat - * @description Calls of the form `strncat(dest, source, sizeof (dest) - strlen (dest))` set the third argument to one more than possible. So when `dest` is full, the expression `sizeof(dest) - strlen (dest)` will be equal to one, and not zero as the programmer might think. Making a call of this type may result in a zero byte being written just outside the `dest` buffer. - * @kind problem - * @id cpp/access-memory-location-after-end-buffer-strncat - * @problem.severity warning - * @precision medium - * @tags correctness - * security - * external/cwe/cwe-788 - */ - -import cpp -import semmle.code.cpp.models.implementations.Strcat -import semmle.code.cpp.valuenumbering.GlobalValueNumbering - -/** - * Holds if `call` is a call to `strncat` such that `sizeArg` and `destArg` are the size and - * destination arguments, respectively. - */ -predicate interestringCallWithArgs(Call call, Expr sizeArg, Expr destArg) { - exists(StrcatFunction strcat | - strcat = call.getTarget() and - sizeArg = call.getArgument(strcat.getParamSize()) and - destArg = call.getArgument(strcat.getParamDest()) - ) -} - -from FunctionCall call, Expr sizeArg, Expr destArg, SubExpr sub, int n -where - interestringCallWithArgs(call, sizeArg, destArg) and - // The destination buffer is an array of size n - destArg.getUnspecifiedType().(ArrayType).getSize() = n and - // The size argument is equivalent to a subtraction - globalValueNumber(sizeArg).getAnExpr() = sub and - // ... where the left side of the subtraction is the constant n - globalValueNumber(sub.getLeftOperand()).getAnExpr().getValue().toInt() = n and - // ... and the right side of the subtraction is a call to `strlen` where the argument is the - // destination buffer. - globalValueNumber(sub.getRightOperand()).getAnExpr().(StrlenCall).getStringExpr() = - globalValueNumber(destArg).getAnExpr() -select call, "Possible out-of-bounds write due to incorrect size argument." diff --git a/cpp/ql/src/semmle/code/cpp/MemberFunction.qll b/cpp/ql/src/semmle/code/cpp/MemberFunction.qll index 9dc1bbc7346..63c1406d8a5 100644 --- a/cpp/ql/src/semmle/code/cpp/MemberFunction.qll +++ b/cpp/ql/src/semmle/code/cpp/MemberFunction.qll @@ -48,6 +48,15 @@ class MemberFunction extends Function { /** Holds if this member is public. */ predicate isPublic() { this.hasSpecifier("public") } + /** Holds if this declaration has the lvalue ref-qualifier */ + predicate isLValueRefQualified() { hasSpecifier("&") } + + /** Holds if this declaration has the rvalue ref-qualifier */ + predicate isRValueRefQualified() { hasSpecifier("&&") } + + /** Holds if this declaration has a ref-qualifier */ + predicate isRefQualified() { isLValueRefQualified() or isRValueRefQualified() } + /** Holds if this function overrides that function. */ predicate overrides(MemberFunction that) { overrides(underlyingElement(this), unresolveElement(that)) diff --git a/cpp/ql/src/semmle/code/cpp/Specifier.qll b/cpp/ql/src/semmle/code/cpp/Specifier.qll index 1c1eb0c090a..4a425b690f4 100644 --- a/cpp/ql/src/semmle/code/cpp/Specifier.qll +++ b/cpp/ql/src/semmle/code/cpp/Specifier.qll @@ -37,7 +37,7 @@ class FunctionSpecifier extends Specifier { this.hasName("explicit") } - override string getAPrimaryQlClass() { result = "FunctionSpecifier)" } + override string getAPrimaryQlClass() { result = "FunctionSpecifier" } } /** diff --git a/cpp/ql/src/semmle/code/cpp/controlflow/IRGuards.qll b/cpp/ql/src/semmle/code/cpp/controlflow/IRGuards.qll index 656496325af..d96fc34259c 100644 --- a/cpp/ql/src/semmle/code/cpp/controlflow/IRGuards.qll +++ b/cpp/ql/src/semmle/code/cpp/controlflow/IRGuards.qll @@ -125,17 +125,17 @@ private class GuardConditionFromBinaryLogicalOperator extends GuardCondition { ) } - override predicate comparesEq(Expr left, Expr right, int k, boolean isLessThan, boolean testIsTrue) { + override predicate comparesEq(Expr left, Expr right, int k, boolean areEqual, boolean testIsTrue) { exists(boolean partIsTrue, GuardCondition part | this.(BinaryLogicalOperation).impliesValue(part, partIsTrue, testIsTrue) | - part.comparesEq(left, right, k, isLessThan, partIsTrue) + part.comparesEq(left, right, k, areEqual, partIsTrue) ) } - override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean isLessThan) { + override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean areEqual) { exists(boolean testIsTrue | - comparesEq(left, right, k, isLessThan, testIsTrue) and this.controls(block, testIsTrue) + comparesEq(left, right, k, areEqual, testIsTrue) and this.controls(block, testIsTrue) ) } } @@ -154,20 +154,20 @@ private class GuardConditionFromShortCircuitNot extends GuardCondition, NotExpr getOperand().(GuardCondition).controls(controlled, testIsTrue.booleanNot()) } - override predicate comparesLt(Expr left, Expr right, int k, boolean areEqual, boolean testIsTrue) { - getOperand().(GuardCondition).comparesLt(left, right, k, areEqual, testIsTrue.booleanNot()) + override predicate comparesLt(Expr left, Expr right, int k, boolean isLessThan, boolean testIsTrue) { + getOperand().(GuardCondition).comparesLt(left, right, k, isLessThan, testIsTrue.booleanNot()) } - override predicate ensuresLt(Expr left, Expr right, int k, BasicBlock block, boolean testIsTrue) { - getOperand().(GuardCondition).ensuresLt(left, right, k, block, testIsTrue.booleanNot()) + override predicate ensuresLt(Expr left, Expr right, int k, BasicBlock block, boolean isLessThan) { + getOperand().(GuardCondition).ensuresLt(left, right, k, block, isLessThan.booleanNot()) } override predicate comparesEq(Expr left, Expr right, int k, boolean areEqual, boolean testIsTrue) { getOperand().(GuardCondition).comparesEq(left, right, k, areEqual, testIsTrue.booleanNot()) } - override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean testIsTrue) { - getOperand().(GuardCondition).ensuresEq(left, right, k, block, testIsTrue.booleanNot()) + override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean areEqual) { + getOperand().(GuardCondition).ensuresEq(left, right, k, block, areEqual.booleanNot()) } } diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll index 966c30038cc..462e89ac9ed 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll @@ -35,22 +35,22 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) { * calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly. */ private module LambdaFlow { - private predicate viableParamNonLambda(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallable(call), i) } - private predicate viableParamLambda(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallableLambda(call, _), i) } - private predicate viableParamArgNonLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParamNonLambda(call, i, p) and arg.argumentOf(call, i) ) } - private predicate viableParamArgLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParamLambda(call, i, p) and arg.argumentOf(call, i) @@ -118,8 +118,8 @@ private module LambdaFlow { boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and - if node instanceof CastNode or node instanceof ArgumentNode or node instanceof ReturnNode - then compatibleTypes(t, getNodeType(node)) + if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode + then compatibleTypes(t, getNodeDataFlowType(node)) else any() } @@ -129,7 +129,7 @@ private module LambdaFlow { boolean toJump, DataFlowCallOption lastCall ) { lambdaCall(lambdaCall, kind, node) and - t = getNodeType(node) and + t = getNodeDataFlowType(node) and toReturn = false and toJump = false and lastCall = TDataFlowCallNone() @@ -146,7 +146,7 @@ private module LambdaFlow { getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid) | preservesValue = false and - t = getNodeType(node) + t = getNodeDataFlowType(node) or preservesValue = true and t = t0 @@ -160,7 +160,7 @@ private module LambdaFlow { toJump = true and lastCall = TDataFlowCallNone() | - jumpStep(node, mid) and + jumpStepCached(node, mid) and t = t0 or exists(boolean preservesValue | @@ -168,7 +168,7 @@ private module LambdaFlow { getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid) | preservesValue = false and - t = getNodeType(node) + t = getNodeDataFlowType(node) or preservesValue = true and t = t0 @@ -176,7 +176,7 @@ private module LambdaFlow { ) or // flow into a callable - exists(ParameterNode p, DataFlowCallOption lastCall0, DataFlowCall call | + exists(ParamNode p, DataFlowCallOption lastCall0, DataFlowCall call | revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and ( if lastCall0 = TDataFlowCallNone() and toJump = false @@ -227,7 +227,7 @@ private module LambdaFlow { pragma[nomagic] predicate revLambdaFlowIn( - DataFlowCall lambdaCall, LambdaCallKind kind, ParameterNode p, DataFlowType t, boolean toJump, + DataFlowCall lambdaCall, LambdaCallKind kind, ParamNode p, DataFlowType t, boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall) @@ -242,6 +242,89 @@ private DataFlowCallable viableCallableExt(DataFlowCall call) { cached private module Cached { + /** + * If needed, call this predicate from `DataFlowImplSpecific.qll` in order to + * force a stage-dependency on the `DataFlowImplCommon.qll` stage and therby + * collapsing the two stages. + */ + cached + predicate forceCachingInSameStage() { any() } + + cached + predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() } + + cached + predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) { + c = call.getEnclosingCallable() + } + + cached + predicate nodeDataFlowType(Node n, DataFlowType t) { t = getNodeType(n) } + + cached + predicate jumpStepCached(Node node1, Node node2) { jumpStep(node1, node2) } + + cached + predicate clearsContentCached(Node n, Content c) { clearsContent(n, c) } + + cached + predicate isUnreachableInCallCached(Node n, DataFlowCall call) { isUnreachableInCall(n, call) } + + cached + predicate outNodeExt(Node n) { + n instanceof OutNode + or + n.(PostUpdateNode).getPreUpdateNode() instanceof ArgNode + } + + cached + predicate hiddenNode(Node n) { nodeIsHidden(n) } + + cached + OutNodeExt getAnOutNodeExt(DataFlowCall call, ReturnKindExt k) { + result = getAnOutNode(call, k.(ValueReturnKind).getKind()) + or + exists(ArgNode arg | + result.(PostUpdateNode).getPreUpdateNode() = arg and + arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition()) + ) + } + + cached + predicate returnNodeExt(Node n, ReturnKindExt k) { + k = TValueReturn(n.(ReturnNode).getKind()) + or + exists(ParamNode p, int pos | + parameterValueFlowsToPreUpdate(p, n) and + p.isParameterOf(_, pos) and + k = TParamUpdate(pos) + ) + } + + cached + predicate castNode(Node n) { n instanceof CastNode } + + cached + predicate castingNode(Node n) { + castNode(n) or + n instanceof ParamNode or + n instanceof OutNodeExt or + // For reads, `x.f`, we want to check that the tracked type after the read (which + // is obtained by popping the head of the access path stack) is compatible with + // the type of `x.f`. + read(_, _, n) + } + + cached + predicate parameterNode(Node n, DataFlowCallable c, int i) { + n.(ParameterNode).isParameterOf(c, i) + } + + cached + predicate argumentNode(Node n, DataFlowCall call, int pos) { + n.(ArgumentNode).argumentOf(call, pos) + } + /** * Gets a viable target for the lambda call `call`. * @@ -261,7 +344,7 @@ private module Cached { * The instance parameter is considered to have index `-1`. */ pragma[nomagic] - private predicate viableParam(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParam(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallableExt(call), i) } @@ -270,11 +353,11 @@ private module Cached { * dispatch into account. */ cached - predicate viableParamArg(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParam(call, i, p) and arg.argumentOf(call, i) and - compatibleTypes(getNodeType(arg), getNodeType(p)) + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p)) ) } @@ -312,7 +395,7 @@ private module Cached { * `read` indicates whether it is contents of `p` that can flow to `node`. */ pragma[nomagic] - private predicate parameterValueFlowCand(ParameterNode p, Node node, boolean read) { + private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) { p = node and read = false or @@ -325,30 +408,30 @@ private module Cached { // read exists(Node mid | parameterValueFlowCand(p, mid, false) and - readStep(mid, _, node) and + read(mid, _, node) and read = true ) or // flow through: no prior read - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArgCand(p, arg, false) and argumentValueFlowsThroughCand(arg, node, read) ) or // flow through: no read inside method - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArgCand(p, arg, read) and argumentValueFlowsThroughCand(arg, node, false) ) } pragma[nomagic] - private predicate parameterValueFlowArgCand(ParameterNode p, ArgumentNode arg, boolean read) { + private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) { parameterValueFlowCand(p, arg, read) } pragma[nomagic] - predicate parameterValueFlowsToPreUpdateCand(ParameterNode p, PostUpdateNode n) { + predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) { parameterValueFlowCand(p, n.getPreUpdateNode(), false) } @@ -360,7 +443,7 @@ private module Cached { * `read` indicates whether it is contents of `p` that can flow to the return * node. */ - predicate parameterValueFlowReturnCand(ParameterNode p, ReturnKind kind, boolean read) { + predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) { exists(ReturnNode ret | parameterValueFlowCand(p, ret, read) and kind = ret.getKind() @@ -369,9 +452,9 @@ private module Cached { pragma[nomagic] private predicate argumentValueFlowsThroughCand0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind, boolean read + DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read ) { - exists(ParameterNode param | viableParamArg(call, param, arg) | + exists(ParamNode param | viableParamArg(call, param, arg) | parameterValueFlowReturnCand(param, kind, read) ) } @@ -382,14 +465,14 @@ private module Cached { * * `read` indicates whether it is contents of `arg` that can flow to `out`. */ - predicate argumentValueFlowsThroughCand(ArgumentNode arg, Node out, boolean read) { + predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) { exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThroughCand0(call, arg, kind, read) and out = getAnOutNode(call, kind) ) } - predicate cand(ParameterNode p, Node n) { + predicate cand(ParamNode p, Node n) { parameterValueFlowCand(p, n, _) and ( parameterValueFlowReturnCand(p, _, _) @@ -416,21 +499,21 @@ private module Cached { * If a read step was taken, then `read` captures the `Content`, the * container type, and the content type. */ - predicate parameterValueFlow(ParameterNode p, Node node, ReadStepTypesOption read) { + predicate parameterValueFlow(ParamNode p, Node node, ReadStepTypesOption read) { parameterValueFlow0(p, node, read) and if node instanceof CastingNode then // normal flow through read = TReadStepTypesNone() and - compatibleTypes(getNodeType(p), getNodeType(node)) + compatibleTypes(getNodeDataFlowType(p), getNodeDataFlowType(node)) or // getter - compatibleTypes(read.getContentType(), getNodeType(node)) + compatibleTypes(read.getContentType(), getNodeDataFlowType(node)) else any() } pragma[nomagic] - private predicate parameterValueFlow0(ParameterNode p, Node node, ReadStepTypesOption read) { + private predicate parameterValueFlow0(ParamNode p, Node node, ReadStepTypesOption read) { p = node and Cand::cand(p, _) and read = TReadStepTypesNone() @@ -447,7 +530,7 @@ private module Cached { readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, read.getContentType()) and Cand::parameterValueFlowReturnCand(p, _, true) and - compatibleTypes(getNodeType(p), read.getContainerType()) + compatibleTypes(getNodeDataFlowType(p), read.getContainerType()) ) or parameterValueFlow0_0(TReadStepTypesNone(), p, node, read) @@ -455,34 +538,32 @@ private module Cached { pragma[nomagic] private predicate parameterValueFlow0_0( - ReadStepTypesOption mustBeNone, ParameterNode p, Node node, ReadStepTypesOption read + ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read ) { // flow through: no prior read - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArg(p, arg, mustBeNone) and argumentValueFlowsThrough(arg, read, node) ) or // flow through: no read inside method - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArg(p, arg, read) and argumentValueFlowsThrough(arg, mustBeNone, node) ) } pragma[nomagic] - private predicate parameterValueFlowArg( - ParameterNode p, ArgumentNode arg, ReadStepTypesOption read - ) { + private predicate parameterValueFlowArg(ParamNode p, ArgNode arg, ReadStepTypesOption read) { parameterValueFlow(p, arg, read) and Cand::argumentValueFlowsThroughCand(arg, _, _) } pragma[nomagic] private predicate argumentValueFlowsThrough0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind, ReadStepTypesOption read + DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read ) { - exists(ParameterNode param | viableParamArg(call, param, arg) | + exists(ParamNode param | viableParamArg(call, param, arg) | parameterValueFlowReturn(param, kind, read) ) } @@ -496,18 +577,18 @@ private module Cached { * container type, and the content type. */ pragma[nomagic] - predicate argumentValueFlowsThrough(ArgumentNode arg, ReadStepTypesOption read, Node out) { + predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) { exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThrough0(call, arg, kind, read) and out = getAnOutNode(call, kind) | // normal flow through read = TReadStepTypesNone() and - compatibleTypes(getNodeType(arg), getNodeType(out)) + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(out)) or // getter - compatibleTypes(getNodeType(arg), read.getContainerType()) and - compatibleTypes(read.getContentType(), getNodeType(out)) + compatibleTypes(getNodeDataFlowType(arg), read.getContainerType()) and + compatibleTypes(read.getContentType(), getNodeDataFlowType(out)) ) } @@ -516,7 +597,7 @@ private module Cached { * value-preserving steps and a single read step, not taking call * contexts into account, thus representing a getter-step. */ - predicate getterStep(ArgumentNode arg, Content c, Node out) { + predicate getterStep(ArgNode arg, Content c, Node out) { argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out) } @@ -529,7 +610,7 @@ private module Cached { * container type, and the content type. */ private predicate parameterValueFlowReturn( - ParameterNode p, ReturnKind kind, ReadStepTypesOption read + ParamNode p, ReturnKind kind, ReadStepTypesOption read ) { exists(ReturnNode ret | parameterValueFlow(p, ret, read) and @@ -553,7 +634,7 @@ private module Cached { private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { mayBenefitFromCallContext(call, callable) or - callable = call.getEnclosingCallable() and + callEnclosingCallable(call, callable) and exists(viableCallableLambda(call, TDataFlowCallSome(_))) } @@ -611,7 +692,7 @@ private module Cached { mayBenefitFromCallContextExt(call, _) and c = viableCallableExt(call) and ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and - tgts = strictcount(DataFlowCall ctx | viableCallableExt(ctx) = call.getEnclosingCallable()) and + tgts = strictcount(DataFlowCall ctx | callEnclosingCallable(call, viableCallableExt(ctx))) and ctxtgts < tgts ) } @@ -635,8 +716,7 @@ private module Cached { * Holds if `p` can flow to the pre-update node associated with post-update * node `n`, in the same callable, using only value-preserving steps. */ - cached - predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) { + private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone()) } @@ -644,9 +724,9 @@ private module Cached { Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType ) { storeStep(node1, c, node2) and - readStep(_, c, _) and - contentType = getNodeType(node1) and - containerType = getNodeType(node2) + read(_, c, _) and + contentType = getNodeDataFlowType(node1) and + containerType = getNodeDataFlowType(node2) or exists(Node n1, Node n2 | n1 = node1.(PostUpdateNode).getPreUpdateNode() and @@ -654,12 +734,15 @@ private module Cached { | argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1) or - readStep(n2, c, n1) and - contentType = getNodeType(n1) and - containerType = getNodeType(n2) + read(n2, c, n1) and + contentType = getNodeDataFlowType(n1) and + containerType = getNodeDataFlowType(n2) ) } + cached + predicate read(Node node1, Content c, Node node2) { readStep(node1, c, node2) } + /** * Holds if data can flow from `node1` to `node2` via a direct assignment to * `f`. @@ -678,8 +761,9 @@ private module Cached { * are aliases. A typical example is a function returning `this`, implementing a fluent * interface. */ - cached - predicate reverseStepThroughInputOutputAlias(PostUpdateNode fromNode, PostUpdateNode toNode) { + private predicate reverseStepThroughInputOutputAlias( + PostUpdateNode fromNode, PostUpdateNode toNode + ) { exists(Node fromPre, Node toPre | fromPre = fromNode.getPreUpdateNode() and toPre = toNode.getPreUpdateNode() @@ -688,14 +772,20 @@ private module Cached { // Does the language-specific simpleLocalFlowStep already model flow // from function input to output? fromPre = getAnOutNode(c, _) and - toPre.(ArgumentNode).argumentOf(c, _) and - simpleLocalFlowStep(toPre.(ArgumentNode), fromPre) + toPre.(ArgNode).argumentOf(c, _) and + simpleLocalFlowStep(toPre.(ArgNode), fromPre) ) or argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre) ) } + cached + predicate simpleLocalFlowStepExt(Node node1, Node node2) { + simpleLocalFlowStep(node1, node2) or + reverseStepThroughInputOutputAlias(node1, node2) + } + /** * Holds if the call context `call` either improves virtual dispatch in * `callable` or if it allows us to prune unreachable nodes in `callable`. @@ -704,7 +794,7 @@ private module Cached { predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) { reducedViableImplInCallContext(_, callable, call) or - exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCall(n, call)) + exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call)) } cached @@ -726,12 +816,12 @@ private module Cached { cached newtype TLocalFlowCallContext = TAnyLocalCall() or - TSpecificLocalCall(DataFlowCall call) { isUnreachableInCall(_, call) } + TSpecificLocalCall(DataFlowCall call) { isUnreachableInCallCached(_, call) } cached newtype TReturnKindExt = TValueReturn(ReturnKind kind) or - TParamUpdate(int pos) { exists(ParameterNode p | p.isParameterOf(_, pos)) } + TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) } cached newtype TBooleanOption = @@ -761,23 +851,15 @@ private module Cached { * A `Node` at which a cast can occur such that the type should be checked. */ class CastingNode extends Node { - CastingNode() { - this instanceof ParameterNode or - this instanceof CastNode or - this instanceof OutNodeExt or - // For reads, `x.f`, we want to check that the tracked type after the read (which - // is obtained by popping the head of the access path stack) is compatible with - // the type of `x.f`. - readStep(_, _, this) - } + CastingNode() { castingNode(this) } } private predicate readStepWithTypes( Node n1, DataFlowType container, Content c, Node n2, DataFlowType content ) { - readStep(n1, c, n2) and - container = getNodeType(n1) and - content = getNodeType(n2) + read(n1, c, n2) and + container = getNodeDataFlowType(n1) and + content = getNodeDataFlowType(n2) } private newtype TReadStepTypesOption = @@ -854,7 +936,7 @@ class CallContextSomeCall extends CallContextCall, TSomeCall { override string toString() { result = "CcSomeCall" } override predicate relevantFor(DataFlowCallable callable) { - exists(ParameterNode p | getNodeEnclosingCallable(p) = callable) + exists(ParamNode p | getNodeEnclosingCallable(p) = callable) } override predicate matchesCall(DataFlowCall call) { any() } @@ -866,7 +948,7 @@ class CallContextReturn extends CallContextNoCall, TReturn { } override predicate relevantFor(DataFlowCallable callable) { - exists(DataFlowCall call | this = TReturn(_, call) and call.getEnclosingCallable() = callable) + exists(DataFlowCall call | this = TReturn(_, call) and callEnclosingCallable(call, callable)) } } @@ -899,7 +981,7 @@ class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall } private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) { - exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCall(n, call)) + exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCallCached(n, call)) } /** @@ -913,26 +995,37 @@ LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) else result instanceof LocalCallContextAny } +/** + * The value of a parameter at function entry, viewed as a node in a data + * flow graph. + */ +class ParamNode extends Node { + ParamNode() { parameterNode(this, _, _) } + + /** + * Holds if this node is the parameter of callable `c` at the specified + * (zero-based) position. + */ + predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) } +} + +/** A data-flow node that represents a call argument. */ +class ArgNode extends Node { + ArgNode() { argumentNode(this, _, _) } + + /** Holds if this argument occurs at the given position in the given call. */ + final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) } +} + /** * A node from which flow can return to the caller. This is either a regular * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter. */ class ReturnNodeExt extends Node { - ReturnNodeExt() { - this instanceof ReturnNode or - parameterValueFlowsToPreUpdate(_, this) - } + ReturnNodeExt() { returnNodeExt(this, _) } /** Gets the kind of this returned value. */ - ReturnKindExt getKind() { - result = TValueReturn(this.(ReturnNode).getKind()) - or - exists(ParameterNode p, int pos | - parameterValueFlowsToPreUpdate(p, this) and - p.isParameterOf(_, pos) and - result = TParamUpdate(pos) - ) - } + ReturnKindExt getKind() { returnNodeExt(this, result) } } /** @@ -940,11 +1033,7 @@ class ReturnNodeExt extends Node { * or a post-update node associated with a call argument. */ class OutNodeExt extends Node { - OutNodeExt() { - this instanceof OutNode - or - this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode - } + OutNodeExt() { outNodeExt(this) } } /** @@ -957,7 +1046,7 @@ abstract class ReturnKindExt extends TReturnKindExt { abstract string toString(); /** Gets a node corresponding to data flow out of `call`. */ - abstract OutNodeExt getAnOutNode(DataFlowCall call); + final OutNodeExt getAnOutNode(DataFlowCall call) { result = getAnOutNodeExt(call, this) } } class ValueReturnKind extends ReturnKindExt, TValueReturn { @@ -968,10 +1057,6 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn { ReturnKind getKind() { result = kind } override string toString() { result = kind.toString() } - - override OutNodeExt getAnOutNode(DataFlowCall call) { - result = getAnOutNode(call, this.getKind()) - } } class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { @@ -982,13 +1067,6 @@ class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { int getPosition() { result = pos } override string toString() { result = "param update " + pos } - - override OutNodeExt getAnOutNode(DataFlowCall call) { - exists(ArgumentNode arg | - result.(PostUpdateNode).getPreUpdateNode() = arg and - arg.argumentOf(call, this.getPosition()) - ) - } } /** A callable tagged with a relevant return kind. */ @@ -1015,10 +1093,13 @@ class ReturnPosition extends TReturnPosition0 { */ pragma[inline] DataFlowCallable getNodeEnclosingCallable(Node n) { - exists(Node n0 | - pragma[only_bind_into](n0) = n and - pragma[only_bind_into](result) = n0.getEnclosingCallable() - ) + nodeEnclosingCallable(pragma[only_bind_out](n), pragma[only_bind_into](result)) +} + +/** Gets the type of `n` used for type pruning. */ +pragma[inline] +DataFlowType getNodeDataFlowType(Node n) { + nodeDataFlowType(pragma[only_bind_out](n), pragma[only_bind_into](result)) } pragma[noinline] @@ -1042,7 +1123,7 @@ predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall cc instanceof CallContextAny and callable = viableCallableExt(call) or exists(DataFlowCallable c0, DataFlowCall call0 | - call0.getEnclosingCallable() = callable and + callEnclosingCallable(call0, callable) and cc = TReturn(c0, call0) and c0 = prunedViableImplInCallContextReverse(call0, call) ) @@ -1063,8 +1144,6 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { result = viableCallableExt(call) and cc instanceof CallContextReturn } -predicate read = readStep/3; - /** An optional Boolean value. */ class BooleanOption extends TBooleanOption { string toString() { @@ -1116,7 +1195,7 @@ abstract class AccessPathFront extends TAccessPathFront { TypedContent getHead() { this = TFrontHead(result) } - predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } + predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll index 558b43cb0da..5db9a84c80a 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll @@ -526,7 +526,6 @@ predicate localFlowStep(Node nodeFrom, Node nodeTo) { * This is the local flow predicate that's used as a building block in global * data flow. It may have less flow than the `localFlowStep` predicate. */ -cached predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { // Expr -> Expr exprToExprStep_nocfg(nodeFrom.asExpr(), nodeTo.asExpr()) diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/TaintTrackingUtil.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/TaintTrackingUtil.qll index 591f461c8eb..6216045db32 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/TaintTrackingUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/TaintTrackingUtil.qll @@ -45,6 +45,7 @@ predicate defaultTaintSanitizer(DataFlow::Node node) { none() } * local data flow steps. That is, `nodeFrom` and `nodeTo` are likely to represent * different objects. */ +cached predicate localAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { // Taint can flow through expressions that alter the value but preserve // more than one bit of it _or_ expressions that follow data through diff --git a/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll b/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll index 86f538e5d4e..f77518c2f56 100644 --- a/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll +++ b/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll @@ -850,6 +850,24 @@ class NewOrNewArrayExpr extends Expr, @any_new_expr { this.getAllocatorCall() .getArgument(this.getAllocator().(OperatorNewAllocationFunction).getPlacementArgument()) } + + /** + * For `operator new`, this gets the call or expression that initializes the allocated object, if any. + * + * As examples, for `new int(4)`, this will be `4`, and for `new std::vector(4)`, this will + * be a call to the constructor `std::vector::vector(size_t)` with `4` as an argument. + * + * For `operator new[]`, this gets the call or expression that initializes the first element of the + * array, if any. + * + * This will either be a call to the default constructor for the array's element type (as + * in `new std::string[10]`), or a literal zero for arrays of scalars which are zero-initialized + * due to extra parentheses (as in `new int[10]()`). + * + * At runtime, the constructor will be called once for each element in the array, but the + * constructor call only exists once in the AST. + */ + final Expr getInitializer() { result = this.getChild(1) } } /** @@ -871,14 +889,6 @@ class NewExpr extends NewOrNewArrayExpr, @new_expr { override Type getAllocatedType() { new_allocated_type(underlyingElement(this), unresolveElement(result)) } - - /** - * Gets the call or expression that initializes the allocated object, if any. - * - * As examples, for `new int(4)`, this will be `4`, and for `new std::vector(4)`, this will - * be a call to the constructor `std::vector::vector(size_t)` with `4` as an argument. - */ - Expr getInitializer() { result = this.getChild(1) } } /** @@ -909,18 +919,6 @@ class NewArrayExpr extends NewOrNewArrayExpr, @new_array_expr { result = getType().getUnderlyingType().(PointerType).getBaseType() } - /** - * Gets the call or expression that initializes the first element of the array, if any. - * - * This will either be a call to the default constructor for the array's element type (as - * in `new std::string[10]`), or a literal zero for arrays of scalars which are zero-initialized - * due to extra parentheses (as in `new int[10]()`). - * - * At runtime, the constructor will be called once for each element in the array, but the - * constructor call only exists once in the AST. - */ - Expr getInitializer() { result = this.getChild(1) } - /** * Gets the extent of the non-constant array dimension, if any. * diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index 3092031cbc7..49d11a7e3cc 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -165,105 +165,132 @@ private predicate nodeIsBarrierEqualityCandidate( any(IRGuardCondition guard).ensuresEq(access, _, _, node.asInstruction().getBlock(), true) } -private predicate nodeIsBarrier(DataFlow::Node node) { - exists(Variable checkedVar | - readsVariable(node.asInstruction(), checkedVar) and - hasUpperBoundsCheck(checkedVar) - ) - or - exists(Variable checkedVar, Operand access | - /* - * This node is guarded by a condition that forces the accessed variable - * to equal something else. For example: - * ``` - * x = taintsource() - * if (x == 10) { - * taintsink(x); // not considered tainted - * } - * ``` - */ - - nodeIsBarrierEqualityCandidate(node, access, checkedVar) and - readsVariable(access.getDef(), checkedVar) - ) -} - -private predicate nodeIsBarrierIn(DataFlow::Node node) { - // don't use dataflow into taint sources, as this leads to duplicate results. - exists(Expr source | isUserInput(source, _) | - node = DataFlow::exprNode(source) +cached +private module Cached { + cached + predicate nodeIsBarrier(DataFlow::Node node) { + exists(Variable checkedVar | + readsVariable(node.asInstruction(), checkedVar) and + hasUpperBoundsCheck(checkedVar) + ) or - // This case goes together with the similar (but not identical) rule in - // `getNodeForSource`. - node = DataFlow::definitionByReferenceNodeFromArgument(source) - ) - or - // don't use dataflow into binary instructions if both operands are unpredictable - exists(BinaryInstruction iTo | - iTo = node.asInstruction() and - not predictableInstruction(iTo.getLeft()) and - not predictableInstruction(iTo.getRight()) and - // propagate taint from either the pointer or the offset, regardless of predictability - not iTo instanceof PointerArithmeticInstruction - ) - or - // don't use dataflow through calls to pure functions if two or more operands - // are unpredictable - exists(Instruction iFrom1, Instruction iFrom2, CallInstruction iTo | - iTo = node.asInstruction() and - isPureFunction(iTo.getStaticCallTarget().getName()) and - iFrom1 = iTo.getAnArgument() and - iFrom2 = iTo.getAnArgument() and - not predictableInstruction(iFrom1) and - not predictableInstruction(iFrom2) and - iFrom1 != iFrom2 - ) + exists(Variable checkedVar, Operand access | + /* + * This node is guarded by a condition that forces the accessed variable + * to equal something else. For example: + * ``` + * x = taintsource() + * if (x == 10) { + * taintsink(x); // not considered tainted + * } + * ``` + */ + + nodeIsBarrierEqualityCandidate(node, access, checkedVar) and + readsVariable(access.getDef(), checkedVar) + ) + } + + cached + predicate nodeIsBarrierIn(DataFlow::Node node) { + // don't use dataflow into taint sources, as this leads to duplicate results. + exists(Expr source | isUserInput(source, _) | + node = DataFlow::exprNode(source) + or + // This case goes together with the similar (but not identical) rule in + // `getNodeForSource`. + node = DataFlow::definitionByReferenceNodeFromArgument(source) + ) + or + // don't use dataflow into binary instructions if both operands are unpredictable + exists(BinaryInstruction iTo | + iTo = node.asInstruction() and + not predictableInstruction(iTo.getLeft()) and + not predictableInstruction(iTo.getRight()) and + // propagate taint from either the pointer or the offset, regardless of predictability + not iTo instanceof PointerArithmeticInstruction + ) + or + // don't use dataflow through calls to pure functions if two or more operands + // are unpredictable + exists(Instruction iFrom1, Instruction iFrom2, CallInstruction iTo | + iTo = node.asInstruction() and + isPureFunction(iTo.getStaticCallTarget().getName()) and + iFrom1 = iTo.getAnArgument() and + iFrom2 = iTo.getAnArgument() and + not predictableInstruction(iFrom1) and + not predictableInstruction(iFrom2) and + iFrom1 != iFrom2 + ) + } + + cached + Element adjustedSink(DataFlow::Node sink) { + // TODO: is it more appropriate to use asConvertedExpr here and avoid + // `getConversion*`? Or will that cause us to miss some cases where there's + // flow to a conversion (like a `ReferenceDereferenceExpr`) and we want to + // pretend there was flow to the converted `Expr` for the sake of + // compatibility. + sink.asExpr().getConversion*() = result + or + // For compatibility, send flow from arguments to parameters, even for + // functions with no body. + exists(FunctionCall call, int i | + sink.asExpr() = call.getArgument(i) and + result = resolveCall(call).getParameter(i) + ) + or + // For compatibility, send flow into a `Variable` if there is flow to any + // Load or Store of that variable. + exists(CopyInstruction copy | + copy.getSourceValue() = sink.asInstruction() and + ( + readsVariable(copy, result) or + writesVariable(copy, result) + ) and + not hasUpperBoundsCheck(result) + ) + or + // For compatibility, send flow into a `NotExpr` even if it's part of a + // short-circuiting condition and thus might get skipped. + result.(NotExpr).getOperand() = sink.asExpr() + or + // Taint postfix and prefix crement operations when their operand is tainted. + result.(CrementOperation).getAnOperand() = sink.asExpr() + or + // Taint `e1 += e2`, `e &= e2` and friends when `e1` or `e2` is tainted. + result.(AssignOperation).getAnOperand() = sink.asExpr() + or + result = + sink.asOperand() + .(SideEffectOperand) + .getUse() + .(ReadSideEffectInstruction) + .getArgumentDef() + .getUnconvertedResultExpression() + } + + /** + * Step to return value of a modeled function when an input taints the + * dereference of the return value. + */ + cached + predicate additionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { + exists(CallInstruction call, Function func, FunctionInput modelIn, FunctionOutput modelOut | + n1.asOperand() = callInput(call, modelIn) and + ( + func.(TaintFunction).hasTaintFlow(modelIn, modelOut) + or + func.(DataFlowFunction).hasDataFlow(modelIn, modelOut) + ) and + call.getStaticCallTarget() = func and + modelOut.isReturnValueDeref() and + call = n2.asInstruction() + ) + } } -private Element adjustedSink(DataFlow::Node sink) { - // TODO: is it more appropriate to use asConvertedExpr here and avoid - // `getConversion*`? Or will that cause us to miss some cases where there's - // flow to a conversion (like a `ReferenceDereferenceExpr`) and we want to - // pretend there was flow to the converted `Expr` for the sake of - // compatibility. - sink.asExpr().getConversion*() = result - or - // For compatibility, send flow from arguments to parameters, even for - // functions with no body. - exists(FunctionCall call, int i | - sink.asExpr() = call.getArgument(i) and - result = resolveCall(call).getParameter(i) - ) - or - // For compatibility, send flow into a `Variable` if there is flow to any - // Load or Store of that variable. - exists(CopyInstruction copy | - copy.getSourceValue() = sink.asInstruction() and - ( - readsVariable(copy, result) or - writesVariable(copy, result) - ) and - not hasUpperBoundsCheck(result) - ) - or - // For compatibility, send flow into a `NotExpr` even if it's part of a - // short-circuiting condition and thus might get skipped. - result.(NotExpr).getOperand() = sink.asExpr() - or - // Taint postfix and prefix crement operations when their operand is tainted. - result.(CrementOperation).getAnOperand() = sink.asExpr() - or - // Taint `e1 += e2`, `e &= e2` and friends when `e1` or `e2` is tainted. - result.(AssignOperation).getAnOperand() = sink.asExpr() - or - result = - sink.asOperand() - .(SideEffectOperand) - .getUse() - .(ReadSideEffectInstruction) - .getArgumentDef() - .getUnconvertedResultExpression() -} +private import Cached /** * Holds if `tainted` may contain taint from `source`. @@ -402,19 +429,7 @@ module TaintedWithPath { readsVariable(n2.asInstruction(), n1.asVariable().(GlobalOrNamespaceVariable)) ) or - // Step to return value of a modeled function when an input taints the - // dereference of the return value - exists(CallInstruction call, Function func, FunctionInput modelIn, FunctionOutput modelOut | - n1.asOperand() = callInput(call, modelIn) and - ( - func.(TaintFunction).hasTaintFlow(modelIn, modelOut) - or - func.(DataFlowFunction).hasDataFlow(modelIn, modelOut) - ) and - call.getStaticCallTarget() = func and - modelOut.isReturnValueDeref() and - call = n2.asInstruction() - ) + additionalTaintStep(n1, n2) } override predicate isSanitizer(DataFlow::Node node) { diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll index e927634fec2..99d8555f8ca 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll @@ -2,11 +2,14 @@ private import cpp private import semmle.code.cpp.ir.IR private import semmle.code.cpp.ir.dataflow.DataFlow private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate +private import DataFlowImplCommon as DataFlowImplCommon /** * Gets a function that might be called by `call`. */ +cached Function viableCallable(CallInstruction call) { + DataFlowImplCommon::forceCachingInSameStage() and result = call.getStaticCallTarget() or // If the target of the call does not have a body in the snapshot, it might @@ -43,7 +46,6 @@ private module VirtualDispatch { abstract DataFlow::Node getDispatchValue(); /** Gets a candidate target for this call. */ - cached abstract Function resolve(); /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll index 966c30038cc..462e89ac9ed 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll @@ -35,22 +35,22 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) { * calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly. */ private module LambdaFlow { - private predicate viableParamNonLambda(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallable(call), i) } - private predicate viableParamLambda(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallableLambda(call, _), i) } - private predicate viableParamArgNonLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParamNonLambda(call, i, p) and arg.argumentOf(call, i) ) } - private predicate viableParamArgLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParamLambda(call, i, p) and arg.argumentOf(call, i) @@ -118,8 +118,8 @@ private module LambdaFlow { boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and - if node instanceof CastNode or node instanceof ArgumentNode or node instanceof ReturnNode - then compatibleTypes(t, getNodeType(node)) + if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode + then compatibleTypes(t, getNodeDataFlowType(node)) else any() } @@ -129,7 +129,7 @@ private module LambdaFlow { boolean toJump, DataFlowCallOption lastCall ) { lambdaCall(lambdaCall, kind, node) and - t = getNodeType(node) and + t = getNodeDataFlowType(node) and toReturn = false and toJump = false and lastCall = TDataFlowCallNone() @@ -146,7 +146,7 @@ private module LambdaFlow { getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid) | preservesValue = false and - t = getNodeType(node) + t = getNodeDataFlowType(node) or preservesValue = true and t = t0 @@ -160,7 +160,7 @@ private module LambdaFlow { toJump = true and lastCall = TDataFlowCallNone() | - jumpStep(node, mid) and + jumpStepCached(node, mid) and t = t0 or exists(boolean preservesValue | @@ -168,7 +168,7 @@ private module LambdaFlow { getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid) | preservesValue = false and - t = getNodeType(node) + t = getNodeDataFlowType(node) or preservesValue = true and t = t0 @@ -176,7 +176,7 @@ private module LambdaFlow { ) or // flow into a callable - exists(ParameterNode p, DataFlowCallOption lastCall0, DataFlowCall call | + exists(ParamNode p, DataFlowCallOption lastCall0, DataFlowCall call | revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and ( if lastCall0 = TDataFlowCallNone() and toJump = false @@ -227,7 +227,7 @@ private module LambdaFlow { pragma[nomagic] predicate revLambdaFlowIn( - DataFlowCall lambdaCall, LambdaCallKind kind, ParameterNode p, DataFlowType t, boolean toJump, + DataFlowCall lambdaCall, LambdaCallKind kind, ParamNode p, DataFlowType t, boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall) @@ -242,6 +242,89 @@ private DataFlowCallable viableCallableExt(DataFlowCall call) { cached private module Cached { + /** + * If needed, call this predicate from `DataFlowImplSpecific.qll` in order to + * force a stage-dependency on the `DataFlowImplCommon.qll` stage and therby + * collapsing the two stages. + */ + cached + predicate forceCachingInSameStage() { any() } + + cached + predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() } + + cached + predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) { + c = call.getEnclosingCallable() + } + + cached + predicate nodeDataFlowType(Node n, DataFlowType t) { t = getNodeType(n) } + + cached + predicate jumpStepCached(Node node1, Node node2) { jumpStep(node1, node2) } + + cached + predicate clearsContentCached(Node n, Content c) { clearsContent(n, c) } + + cached + predicate isUnreachableInCallCached(Node n, DataFlowCall call) { isUnreachableInCall(n, call) } + + cached + predicate outNodeExt(Node n) { + n instanceof OutNode + or + n.(PostUpdateNode).getPreUpdateNode() instanceof ArgNode + } + + cached + predicate hiddenNode(Node n) { nodeIsHidden(n) } + + cached + OutNodeExt getAnOutNodeExt(DataFlowCall call, ReturnKindExt k) { + result = getAnOutNode(call, k.(ValueReturnKind).getKind()) + or + exists(ArgNode arg | + result.(PostUpdateNode).getPreUpdateNode() = arg and + arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition()) + ) + } + + cached + predicate returnNodeExt(Node n, ReturnKindExt k) { + k = TValueReturn(n.(ReturnNode).getKind()) + or + exists(ParamNode p, int pos | + parameterValueFlowsToPreUpdate(p, n) and + p.isParameterOf(_, pos) and + k = TParamUpdate(pos) + ) + } + + cached + predicate castNode(Node n) { n instanceof CastNode } + + cached + predicate castingNode(Node n) { + castNode(n) or + n instanceof ParamNode or + n instanceof OutNodeExt or + // For reads, `x.f`, we want to check that the tracked type after the read (which + // is obtained by popping the head of the access path stack) is compatible with + // the type of `x.f`. + read(_, _, n) + } + + cached + predicate parameterNode(Node n, DataFlowCallable c, int i) { + n.(ParameterNode).isParameterOf(c, i) + } + + cached + predicate argumentNode(Node n, DataFlowCall call, int pos) { + n.(ArgumentNode).argumentOf(call, pos) + } + /** * Gets a viable target for the lambda call `call`. * @@ -261,7 +344,7 @@ private module Cached { * The instance parameter is considered to have index `-1`. */ pragma[nomagic] - private predicate viableParam(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParam(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallableExt(call), i) } @@ -270,11 +353,11 @@ private module Cached { * dispatch into account. */ cached - predicate viableParamArg(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParam(call, i, p) and arg.argumentOf(call, i) and - compatibleTypes(getNodeType(arg), getNodeType(p)) + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p)) ) } @@ -312,7 +395,7 @@ private module Cached { * `read` indicates whether it is contents of `p` that can flow to `node`. */ pragma[nomagic] - private predicate parameterValueFlowCand(ParameterNode p, Node node, boolean read) { + private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) { p = node and read = false or @@ -325,30 +408,30 @@ private module Cached { // read exists(Node mid | parameterValueFlowCand(p, mid, false) and - readStep(mid, _, node) and + read(mid, _, node) and read = true ) or // flow through: no prior read - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArgCand(p, arg, false) and argumentValueFlowsThroughCand(arg, node, read) ) or // flow through: no read inside method - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArgCand(p, arg, read) and argumentValueFlowsThroughCand(arg, node, false) ) } pragma[nomagic] - private predicate parameterValueFlowArgCand(ParameterNode p, ArgumentNode arg, boolean read) { + private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) { parameterValueFlowCand(p, arg, read) } pragma[nomagic] - predicate parameterValueFlowsToPreUpdateCand(ParameterNode p, PostUpdateNode n) { + predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) { parameterValueFlowCand(p, n.getPreUpdateNode(), false) } @@ -360,7 +443,7 @@ private module Cached { * `read` indicates whether it is contents of `p` that can flow to the return * node. */ - predicate parameterValueFlowReturnCand(ParameterNode p, ReturnKind kind, boolean read) { + predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) { exists(ReturnNode ret | parameterValueFlowCand(p, ret, read) and kind = ret.getKind() @@ -369,9 +452,9 @@ private module Cached { pragma[nomagic] private predicate argumentValueFlowsThroughCand0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind, boolean read + DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read ) { - exists(ParameterNode param | viableParamArg(call, param, arg) | + exists(ParamNode param | viableParamArg(call, param, arg) | parameterValueFlowReturnCand(param, kind, read) ) } @@ -382,14 +465,14 @@ private module Cached { * * `read` indicates whether it is contents of `arg` that can flow to `out`. */ - predicate argumentValueFlowsThroughCand(ArgumentNode arg, Node out, boolean read) { + predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) { exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThroughCand0(call, arg, kind, read) and out = getAnOutNode(call, kind) ) } - predicate cand(ParameterNode p, Node n) { + predicate cand(ParamNode p, Node n) { parameterValueFlowCand(p, n, _) and ( parameterValueFlowReturnCand(p, _, _) @@ -416,21 +499,21 @@ private module Cached { * If a read step was taken, then `read` captures the `Content`, the * container type, and the content type. */ - predicate parameterValueFlow(ParameterNode p, Node node, ReadStepTypesOption read) { + predicate parameterValueFlow(ParamNode p, Node node, ReadStepTypesOption read) { parameterValueFlow0(p, node, read) and if node instanceof CastingNode then // normal flow through read = TReadStepTypesNone() and - compatibleTypes(getNodeType(p), getNodeType(node)) + compatibleTypes(getNodeDataFlowType(p), getNodeDataFlowType(node)) or // getter - compatibleTypes(read.getContentType(), getNodeType(node)) + compatibleTypes(read.getContentType(), getNodeDataFlowType(node)) else any() } pragma[nomagic] - private predicate parameterValueFlow0(ParameterNode p, Node node, ReadStepTypesOption read) { + private predicate parameterValueFlow0(ParamNode p, Node node, ReadStepTypesOption read) { p = node and Cand::cand(p, _) and read = TReadStepTypesNone() @@ -447,7 +530,7 @@ private module Cached { readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, read.getContentType()) and Cand::parameterValueFlowReturnCand(p, _, true) and - compatibleTypes(getNodeType(p), read.getContainerType()) + compatibleTypes(getNodeDataFlowType(p), read.getContainerType()) ) or parameterValueFlow0_0(TReadStepTypesNone(), p, node, read) @@ -455,34 +538,32 @@ private module Cached { pragma[nomagic] private predicate parameterValueFlow0_0( - ReadStepTypesOption mustBeNone, ParameterNode p, Node node, ReadStepTypesOption read + ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read ) { // flow through: no prior read - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArg(p, arg, mustBeNone) and argumentValueFlowsThrough(arg, read, node) ) or // flow through: no read inside method - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArg(p, arg, read) and argumentValueFlowsThrough(arg, mustBeNone, node) ) } pragma[nomagic] - private predicate parameterValueFlowArg( - ParameterNode p, ArgumentNode arg, ReadStepTypesOption read - ) { + private predicate parameterValueFlowArg(ParamNode p, ArgNode arg, ReadStepTypesOption read) { parameterValueFlow(p, arg, read) and Cand::argumentValueFlowsThroughCand(arg, _, _) } pragma[nomagic] private predicate argumentValueFlowsThrough0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind, ReadStepTypesOption read + DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read ) { - exists(ParameterNode param | viableParamArg(call, param, arg) | + exists(ParamNode param | viableParamArg(call, param, arg) | parameterValueFlowReturn(param, kind, read) ) } @@ -496,18 +577,18 @@ private module Cached { * container type, and the content type. */ pragma[nomagic] - predicate argumentValueFlowsThrough(ArgumentNode arg, ReadStepTypesOption read, Node out) { + predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) { exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThrough0(call, arg, kind, read) and out = getAnOutNode(call, kind) | // normal flow through read = TReadStepTypesNone() and - compatibleTypes(getNodeType(arg), getNodeType(out)) + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(out)) or // getter - compatibleTypes(getNodeType(arg), read.getContainerType()) and - compatibleTypes(read.getContentType(), getNodeType(out)) + compatibleTypes(getNodeDataFlowType(arg), read.getContainerType()) and + compatibleTypes(read.getContentType(), getNodeDataFlowType(out)) ) } @@ -516,7 +597,7 @@ private module Cached { * value-preserving steps and a single read step, not taking call * contexts into account, thus representing a getter-step. */ - predicate getterStep(ArgumentNode arg, Content c, Node out) { + predicate getterStep(ArgNode arg, Content c, Node out) { argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out) } @@ -529,7 +610,7 @@ private module Cached { * container type, and the content type. */ private predicate parameterValueFlowReturn( - ParameterNode p, ReturnKind kind, ReadStepTypesOption read + ParamNode p, ReturnKind kind, ReadStepTypesOption read ) { exists(ReturnNode ret | parameterValueFlow(p, ret, read) and @@ -553,7 +634,7 @@ private module Cached { private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { mayBenefitFromCallContext(call, callable) or - callable = call.getEnclosingCallable() and + callEnclosingCallable(call, callable) and exists(viableCallableLambda(call, TDataFlowCallSome(_))) } @@ -611,7 +692,7 @@ private module Cached { mayBenefitFromCallContextExt(call, _) and c = viableCallableExt(call) and ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and - tgts = strictcount(DataFlowCall ctx | viableCallableExt(ctx) = call.getEnclosingCallable()) and + tgts = strictcount(DataFlowCall ctx | callEnclosingCallable(call, viableCallableExt(ctx))) and ctxtgts < tgts ) } @@ -635,8 +716,7 @@ private module Cached { * Holds if `p` can flow to the pre-update node associated with post-update * node `n`, in the same callable, using only value-preserving steps. */ - cached - predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) { + private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone()) } @@ -644,9 +724,9 @@ private module Cached { Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType ) { storeStep(node1, c, node2) and - readStep(_, c, _) and - contentType = getNodeType(node1) and - containerType = getNodeType(node2) + read(_, c, _) and + contentType = getNodeDataFlowType(node1) and + containerType = getNodeDataFlowType(node2) or exists(Node n1, Node n2 | n1 = node1.(PostUpdateNode).getPreUpdateNode() and @@ -654,12 +734,15 @@ private module Cached { | argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1) or - readStep(n2, c, n1) and - contentType = getNodeType(n1) and - containerType = getNodeType(n2) + read(n2, c, n1) and + contentType = getNodeDataFlowType(n1) and + containerType = getNodeDataFlowType(n2) ) } + cached + predicate read(Node node1, Content c, Node node2) { readStep(node1, c, node2) } + /** * Holds if data can flow from `node1` to `node2` via a direct assignment to * `f`. @@ -678,8 +761,9 @@ private module Cached { * are aliases. A typical example is a function returning `this`, implementing a fluent * interface. */ - cached - predicate reverseStepThroughInputOutputAlias(PostUpdateNode fromNode, PostUpdateNode toNode) { + private predicate reverseStepThroughInputOutputAlias( + PostUpdateNode fromNode, PostUpdateNode toNode + ) { exists(Node fromPre, Node toPre | fromPre = fromNode.getPreUpdateNode() and toPre = toNode.getPreUpdateNode() @@ -688,14 +772,20 @@ private module Cached { // Does the language-specific simpleLocalFlowStep already model flow // from function input to output? fromPre = getAnOutNode(c, _) and - toPre.(ArgumentNode).argumentOf(c, _) and - simpleLocalFlowStep(toPre.(ArgumentNode), fromPre) + toPre.(ArgNode).argumentOf(c, _) and + simpleLocalFlowStep(toPre.(ArgNode), fromPre) ) or argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre) ) } + cached + predicate simpleLocalFlowStepExt(Node node1, Node node2) { + simpleLocalFlowStep(node1, node2) or + reverseStepThroughInputOutputAlias(node1, node2) + } + /** * Holds if the call context `call` either improves virtual dispatch in * `callable` or if it allows us to prune unreachable nodes in `callable`. @@ -704,7 +794,7 @@ private module Cached { predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) { reducedViableImplInCallContext(_, callable, call) or - exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCall(n, call)) + exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call)) } cached @@ -726,12 +816,12 @@ private module Cached { cached newtype TLocalFlowCallContext = TAnyLocalCall() or - TSpecificLocalCall(DataFlowCall call) { isUnreachableInCall(_, call) } + TSpecificLocalCall(DataFlowCall call) { isUnreachableInCallCached(_, call) } cached newtype TReturnKindExt = TValueReturn(ReturnKind kind) or - TParamUpdate(int pos) { exists(ParameterNode p | p.isParameterOf(_, pos)) } + TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) } cached newtype TBooleanOption = @@ -761,23 +851,15 @@ private module Cached { * A `Node` at which a cast can occur such that the type should be checked. */ class CastingNode extends Node { - CastingNode() { - this instanceof ParameterNode or - this instanceof CastNode or - this instanceof OutNodeExt or - // For reads, `x.f`, we want to check that the tracked type after the read (which - // is obtained by popping the head of the access path stack) is compatible with - // the type of `x.f`. - readStep(_, _, this) - } + CastingNode() { castingNode(this) } } private predicate readStepWithTypes( Node n1, DataFlowType container, Content c, Node n2, DataFlowType content ) { - readStep(n1, c, n2) and - container = getNodeType(n1) and - content = getNodeType(n2) + read(n1, c, n2) and + container = getNodeDataFlowType(n1) and + content = getNodeDataFlowType(n2) } private newtype TReadStepTypesOption = @@ -854,7 +936,7 @@ class CallContextSomeCall extends CallContextCall, TSomeCall { override string toString() { result = "CcSomeCall" } override predicate relevantFor(DataFlowCallable callable) { - exists(ParameterNode p | getNodeEnclosingCallable(p) = callable) + exists(ParamNode p | getNodeEnclosingCallable(p) = callable) } override predicate matchesCall(DataFlowCall call) { any() } @@ -866,7 +948,7 @@ class CallContextReturn extends CallContextNoCall, TReturn { } override predicate relevantFor(DataFlowCallable callable) { - exists(DataFlowCall call | this = TReturn(_, call) and call.getEnclosingCallable() = callable) + exists(DataFlowCall call | this = TReturn(_, call) and callEnclosingCallable(call, callable)) } } @@ -899,7 +981,7 @@ class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall } private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) { - exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCall(n, call)) + exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCallCached(n, call)) } /** @@ -913,26 +995,37 @@ LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) else result instanceof LocalCallContextAny } +/** + * The value of a parameter at function entry, viewed as a node in a data + * flow graph. + */ +class ParamNode extends Node { + ParamNode() { parameterNode(this, _, _) } + + /** + * Holds if this node is the parameter of callable `c` at the specified + * (zero-based) position. + */ + predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) } +} + +/** A data-flow node that represents a call argument. */ +class ArgNode extends Node { + ArgNode() { argumentNode(this, _, _) } + + /** Holds if this argument occurs at the given position in the given call. */ + final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) } +} + /** * A node from which flow can return to the caller. This is either a regular * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter. */ class ReturnNodeExt extends Node { - ReturnNodeExt() { - this instanceof ReturnNode or - parameterValueFlowsToPreUpdate(_, this) - } + ReturnNodeExt() { returnNodeExt(this, _) } /** Gets the kind of this returned value. */ - ReturnKindExt getKind() { - result = TValueReturn(this.(ReturnNode).getKind()) - or - exists(ParameterNode p, int pos | - parameterValueFlowsToPreUpdate(p, this) and - p.isParameterOf(_, pos) and - result = TParamUpdate(pos) - ) - } + ReturnKindExt getKind() { returnNodeExt(this, result) } } /** @@ -940,11 +1033,7 @@ class ReturnNodeExt extends Node { * or a post-update node associated with a call argument. */ class OutNodeExt extends Node { - OutNodeExt() { - this instanceof OutNode - or - this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode - } + OutNodeExt() { outNodeExt(this) } } /** @@ -957,7 +1046,7 @@ abstract class ReturnKindExt extends TReturnKindExt { abstract string toString(); /** Gets a node corresponding to data flow out of `call`. */ - abstract OutNodeExt getAnOutNode(DataFlowCall call); + final OutNodeExt getAnOutNode(DataFlowCall call) { result = getAnOutNodeExt(call, this) } } class ValueReturnKind extends ReturnKindExt, TValueReturn { @@ -968,10 +1057,6 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn { ReturnKind getKind() { result = kind } override string toString() { result = kind.toString() } - - override OutNodeExt getAnOutNode(DataFlowCall call) { - result = getAnOutNode(call, this.getKind()) - } } class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { @@ -982,13 +1067,6 @@ class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { int getPosition() { result = pos } override string toString() { result = "param update " + pos } - - override OutNodeExt getAnOutNode(DataFlowCall call) { - exists(ArgumentNode arg | - result.(PostUpdateNode).getPreUpdateNode() = arg and - arg.argumentOf(call, this.getPosition()) - ) - } } /** A callable tagged with a relevant return kind. */ @@ -1015,10 +1093,13 @@ class ReturnPosition extends TReturnPosition0 { */ pragma[inline] DataFlowCallable getNodeEnclosingCallable(Node n) { - exists(Node n0 | - pragma[only_bind_into](n0) = n and - pragma[only_bind_into](result) = n0.getEnclosingCallable() - ) + nodeEnclosingCallable(pragma[only_bind_out](n), pragma[only_bind_into](result)) +} + +/** Gets the type of `n` used for type pruning. */ +pragma[inline] +DataFlowType getNodeDataFlowType(Node n) { + nodeDataFlowType(pragma[only_bind_out](n), pragma[only_bind_into](result)) } pragma[noinline] @@ -1042,7 +1123,7 @@ predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall cc instanceof CallContextAny and callable = viableCallableExt(call) or exists(DataFlowCallable c0, DataFlowCall call0 | - call0.getEnclosingCallable() = callable and + callEnclosingCallable(call0, callable) and cc = TReturn(c0, call0) and c0 = prunedViableImplInCallContextReverse(call0, call) ) @@ -1063,8 +1144,6 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { result = viableCallableExt(call) and cc instanceof CallContextReturn } -predicate read = readStep/3; - /** An optional Boolean value. */ class BooleanOption extends TBooleanOption { string toString() { @@ -1116,7 +1195,7 @@ abstract class AccessPathFront extends TAccessPathFront { TypedContent getHead() { this = TFrontHead(result) } - predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } + predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index f5fb7309cff..915f51b4576 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -12,10 +12,20 @@ private import semmle.code.cpp.controlflow.IRGuards private import semmle.code.cpp.models.interfaces.DataFlow cached -private newtype TIRDataFlowNode = - TInstructionNode(Instruction i) or - TOperandNode(Operand op) or - TVariableNode(Variable var) +private module Cached { + cached + newtype TIRDataFlowNode = + TInstructionNode(Instruction i) or + TOperandNode(Operand op) or + TVariableNode(Variable var) + + cached + predicate localFlowStepCached(Node nodeFrom, Node nodeTo) { + simpleLocalFlowStep(nodeFrom, nodeTo) + } +} + +private import Cached /** * A node in a data flow graph. @@ -590,7 +600,7 @@ Node uninitializedNode(LocalVariable v) { none() } * 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 = localFlowStepCached/2; /** * INTERNAL: do not use. @@ -598,7 +608,6 @@ predicate localFlowStep(Node nodeFrom, Node nodeTo) { simpleLocalFlowStep(nodeFr * This is the local flow predicate that's used as a building block in global * data flow. It may have less flow than the `localFlowStep` predicate. */ -cached predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { // Operand -> Instruction flow simpleInstructionLocalFlowStep(nodeFrom.asOperand(), nodeTo.asInstruction()) @@ -656,7 +665,7 @@ private predicate simpleOperandLocalFlowStep(Instruction iFrom, Operand opTo) { exists(LoadInstruction load | load.getSourceValueOperand() = opTo and opTo.getAnyDef() = iFrom and - isSingleFieldClass(iFrom.getResultType(), opTo) + isSingleFieldClass(pragma[only_bind_out](pragma[only_bind_out](iFrom).getResultType()), opTo) ) } @@ -739,16 +748,10 @@ private predicate modelFlow(Operand opFrom, Instruction iTo) { ) or exists(int index, ReadSideEffectInstruction read | - modelIn.isParameterDeref(index) and + modelIn.isParameterDerefOrQualifierObject(index) and read = getSideEffectFor(call, index) and opFrom = read.getSideEffectOperand() ) - or - exists(ReadSideEffectInstruction read | - modelIn.isQualifierObject() and - read = getSideEffectFor(call, -1) and - opFrom = read.getSideEffectOperand() - ) ) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index e335080ad6b..453838215ff 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -1994,6 +1994,14 @@ class PhiInstruction extends Instruction { */ pragma[noinline] final Instruction getAnInput() { result = this.getAnInputOperand().getDef() } + + /** + * Gets the input operand representing the value that flows from the specified predecessor block. + */ + final PhiInputOperand getInputOperand(IRBlock predecessorBlock) { + result = this.getAnOperand() and + result.getPredecessorBlock() = predecessorBlock + } } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll index a2ce0662dc2..d7cf89ca9aa 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll @@ -28,11 +28,15 @@ class Operand extends TStageOperand { cached Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) or + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or exists(Instruction use, Instruction def, IRBlock predecessorBlock | - this = phiOperand(use, def, predecessorBlock, _) - ) or + this = phiOperand(use, def, predecessorBlock, _) or + this = reusedPhiOperand(use, def, predecessorBlock, _) + ) + or exists(Instruction use | this = chiOperand(use, _)) } @@ -431,7 +435,11 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand { Overlap overlap; cached - PhiInputOperand() { this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) } + PhiInputOperand() { + this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) + or + this = reusedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + } override string toString() { result = "Phi" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll index e26d61b9792..181c6d2c464 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll @@ -90,6 +90,9 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) { or // Converting an address to a `bool` does not escape the address. instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType + or + instr instanceof CallInstruction and + not exists(IREscapeAnalysisConfiguration config | config.useSoundEscapeAnalysis()) ) ) or @@ -284,14 +287,24 @@ private predicate isArgumentForParameter( private predicate isOnlyEscapesViaReturnArgument(Operand operand) { exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and - f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) + ( + f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) + or + f.parameterEscapesOnlyViaReturn(-1) and + operand instanceof ThisArgumentOperand + ) ) } private predicate isNeverEscapesArgument(Operand operand) { exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and - f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) + ( + f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) + or + f.parameterNeverEscapes(-1) and + operand instanceof ThisArgumentOperand + ) ) } @@ -325,6 +338,9 @@ predicate allocationEscapes(Configuration::Allocation allocation) { exists(IREscapeAnalysisConfiguration config | config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction()) ) + or + Configuration::phaseNeedsSoundEscapeAnalysis() and + resultEscapesNonReturn(allocation.getABaseInstruction()) } /** @@ -400,3 +416,46 @@ predicate addressOperandAllocationAndOffset( ) ) } + +/** + * Predicates used only for printing annotated IR dumps. These should not be used in production + * queries. + */ +module Print { + string getOperandProperty(Operand operand, string key) { + key = "alloc" and + result = + strictconcat(Configuration::Allocation allocation, IntValue bitOffset | + addressOperandAllocationAndOffset(operand, allocation, bitOffset) + | + allocation.toString() + Ints::getBitOffsetString(bitOffset), ", " + ) + or + key = "prop" and + result = + strictconcat(Instruction destInstr, IntValue bitOffset, string value | + operandIsPropagatedIncludingByCall(operand, bitOffset, destInstr) and + if destInstr = operand.getUse() + then value = "@" + Ints::getBitOffsetString(bitOffset) + "->result" + else value = "@" + Ints::getBitOffsetString(bitOffset) + "->" + destInstr.getResultId() + | + value, ", " + ) + } + + string getInstructionProperty(Instruction instr, string key) { + key = "prop" and + result = + strictconcat(IntValue bitOffset, Operand sourceOperand, string value | + operandIsPropagatedIncludingByCall(sourceOperand, bitOffset, instr) and + if instr = sourceOperand.getUse() + then value = sourceOperand.getDumpId() + Ints::getBitOffsetString(bitOffset) + "->@" + else + value = + sourceOperand.getUse().getResultId() + "." + sourceOperand.getDumpId() + + Ints::getBitOffsetString(bitOffset) + "->@" + | + value, ", " + ) + } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll index 866afb64b31..8ba91d70087 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll @@ -2,9 +2,13 @@ private import AliasConfigurationInternal private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR private import cpp private import AliasAnalysis +private import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SimpleSSA as UnaliasedSSA private newtype TAllocation = - TVariableAllocation(IRVariable var) or + TVariableAllocation(IRVariable var) { + // Only model variables that were not already handled in unaliased SSA. + not UnaliasedSSA::canReuseSSAForVariable(var) + } or TIndirectParameterAllocation(IRAutomaticVariable var) { exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var) } or @@ -138,3 +142,5 @@ class DynamicAllocation extends Allocation, TDynamicAllocation { final override predicate alwaysEscapes() { none() } } + +predicate phaseNeedsSoundEscapeAnalysis() { none() } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index fdabee2affe..acdae2b758a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -3,6 +3,7 @@ import semmle.code.cpp.ir.internal.Overlap private import semmle.code.cpp.ir.internal.IRCppLanguage as Language private import semmle.code.cpp.Print private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR +private import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SSAConstruction as OldSSA private import semmle.code.cpp.ir.internal.IntegerConstant as Ints private import semmle.code.cpp.ir.internal.IntegerInterval as Interval private import semmle.code.cpp.ir.implementation.internal.OperandTag @@ -131,6 +132,8 @@ abstract class MemoryLocation extends TMemoryLocation { * with automatic storage duration). */ predicate isAlwaysAllocatedOnStack() { none() } + + final predicate canReuseSSA() { none() } } /** @@ -562,10 +565,17 @@ private Overlap getVariableMemoryLocationOverlap( use.getEndBitOffset()) } +/** + * Holds if the def/use information for the result of `instr` can be reused from the previous + * iteration of the IR. + */ +predicate canReuseSSAForOldResult(Instruction instr) { OldSSA::canReuseSSAForMemoryResult(instr) } + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } MemoryLocation getResultMemoryLocation(Instruction instr) { + not canReuseSSAForOldResult(instr) and exists(MemoryAccessKind kind, boolean isMayAccess | kind = instr.getResultMemoryAccess() and (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and @@ -598,6 +608,7 @@ MemoryLocation getResultMemoryLocation(Instruction instr) { } MemoryLocation getOperandMemoryLocation(MemoryOperand operand) { + not canReuseSSAForOldResult(operand.getAnyDef()) and exists(MemoryAccessKind kind, boolean isMayAccess | kind = operand.getMemoryAccess() and (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/PrintAliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/PrintAliasAnalysis.qll new file mode 100644 index 00000000000..262088245e8 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/PrintAliasAnalysis.qll @@ -0,0 +1,19 @@ +/** + * Include this module to annotate IR dumps with information computed by `AliasAnalysis.qll`. + */ + +private import AliasAnalysisInternal +private import InputIR +private import AliasAnalysisImports +private import AliasAnalysis +private import semmle.code.cpp.ir.internal.IntegerConstant + +private class AliasPropertyProvider extends IRPropertyProvider { + override string getOperandProperty(Operand operand, string key) { + result = Print::getOperandProperty(operand, key) + } + + override string getInstructionProperty(Instruction instr, string key) { + result = Print::getInstructionProperty(instr, key) + } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index 1ba19b4a4c3..5092e921cb3 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -43,24 +43,81 @@ private module Cached { class TStageInstruction = TRawInstruction or TPhiInstruction or TChiInstruction or TUnreachedInstruction; + /** + * If `oldInstruction` is a `Phi` instruction that has exactly one reachable predecessor block, + * this predicate returns the `PhiInputOperand` corresponding to that predecessor block. + * Otherwise, this predicate does not hold. + */ + private OldIR::PhiInputOperand getDegeneratePhiOperand(OldInstruction oldInstruction) { + result = + unique(OldIR::PhiInputOperand operand | + operand = oldInstruction.(OldIR::PhiInstruction).getAnInputOperand() and + operand.getPredecessorBlock() instanceof OldBlock + ) + } + cached predicate hasInstruction(TStageInstruction instr) { instr instanceof TRawInstruction and instr instanceof OldInstruction or - instr instanceof TPhiInstruction + instr = phiInstruction(_, _) + or + instr = reusedPhiInstruction(_) and + // Check that the phi instruction is *not* degenerate, but we can't use + // getDegeneratePhiOperand in the first stage with phi instyructions + not exists( + unique(OldIR::PhiInputOperand operand | + operand = instr.(OldIR::PhiInstruction).getAnInputOperand() and + operand.getPredecessorBlock() instanceof OldBlock + ) + ) or instr instanceof TChiInstruction or instr instanceof TUnreachedInstruction } - private IRBlock getNewBlock(OldBlock oldBlock) { - result.getFirstInstruction() = getNewInstruction(oldBlock.getFirstInstruction()) + cached + IRBlock getNewBlock(OldBlock oldBlock) { + exists(Instruction newEnd, OldIR::Instruction oldEnd | + ( + result.getLastInstruction() = newEnd and + not newEnd instanceof ChiInstruction + or + newEnd = result.getLastInstruction().(ChiInstruction).getAPredecessor() // does this work? + ) and + ( + oldBlock.getLastInstruction() = oldEnd and + not oldEnd instanceof OldIR::ChiInstruction + or + oldEnd = oldBlock.getLastInstruction().(OldIR::ChiInstruction).getAPredecessor() // does this work? + ) and + oldEnd = getNewInstruction(newEnd) + ) + } + + /** + * Gets the block from the old IR that corresponds to `newBlock`. + */ + private OldBlock getOldBlock(IRBlock newBlock) { getNewBlock(result) = newBlock } + + /** + * Holds if this iteration of SSA can model the def/use information for the result of + * `oldInstruction`, either because alias analysis has determined a memory location for that + * result, or because a previous iteration of the IR already computed that def/use information + * completely. + */ + private predicate canModelResultForOldInstruction(OldInstruction oldInstruction) { + // We're modeling the result's memory location ourselves. + exists(Alias::getResultMemoryLocation(oldInstruction)) + or + // This result was already modeled by a previous iteration of SSA. + Alias::canReuseSSAForOldResult(oldInstruction) } cached predicate hasModeledMemoryResult(Instruction instruction) { - exists(Alias::getResultMemoryLocation(getOldInstruction(instruction))) or + canModelResultForOldInstruction(getOldInstruction(instruction)) or instruction instanceof PhiInstruction or // Phis always have modeled results instruction instanceof ChiInstruction // Chis always have modeled results } @@ -117,6 +174,32 @@ private module Cached { ) } + /** + * Gets the new definition instruction for `oldOperand` based on `oldOperand`'s definition in the + * old IR. Usually, this will just get the old definition of `oldOperand` and map it to the + * corresponding new instruction. However, if the old definition of `oldOperand` is a `Phi` + * instruction that is now degenerate due all but one of its predecessor branches being + * unreachable, this predicate will recurse through any degenerate `Phi` instructions to find the + * true definition. + */ + private Instruction getNewDefinitionFromOldSSA(OldIR::MemoryOperand oldOperand, Overlap overlap) { + exists(Overlap originalOverlap | + originalOverlap = oldOperand.getDefinitionOverlap() and + ( + result = getNewInstruction(oldOperand.getAnyDef()) and + overlap = originalOverlap + or + exists(OldIR::PhiInputOperand phiOperand, Overlap phiOperandOverlap | + phiOperand = getDegeneratePhiOperand(oldOperand.getAnyDef()) and + result = getNewDefinitionFromOldSSA(phiOperand, phiOperandOverlap) and + overlap = + combineOverlap(pragma[only_bind_out](phiOperandOverlap), + pragma[only_bind_out](originalOverlap)) + ) + ) + ) + } + cached private Instruction getMemoryOperandDefinition0( Instruction instruction, MemoryOperandTag tag, Overlap overlap @@ -148,6 +231,12 @@ private module Cached { overlap instanceof MustExactlyOverlap and exists(MustTotallyOverlap o | exists(getMemoryOperandDefinition0(instruction, tag, o))) ) + or + exists(OldIR::NonPhiMemoryOperand oldOperand | + result = getNewDefinitionFromOldSSA(oldOperand, overlap) and + oldOperand.getUse() = instruction and + tag = oldOperand.getOperandTag() + ) } /** @@ -214,10 +303,24 @@ private module Cached { ) } + /** + * Gets the new definition instruction for the operand of `instr` that flows from the block + * `newPredecessorBlock`, based on that operand's definition in the old IR. + */ + private Instruction getNewPhiOperandDefinitionFromOldSSA( + Instruction instr, IRBlock newPredecessorBlock, Overlap overlap + ) { + exists(OldIR::PhiInstruction oldPhi, OldIR::PhiInputOperand oldOperand | + oldPhi = getOldInstruction(instr) and + oldOperand = oldPhi.getInputOperand(getOldBlock(newPredecessorBlock)) and + result = getNewDefinitionFromOldSSA(oldOperand, overlap) + ) + } + pragma[noopt] cached Instruction getPhiOperandDefinition( - PhiInstruction instr, IRBlock newPredecessorBlock, Overlap overlap + Instruction instr, IRBlock newPredecessorBlock, Overlap overlap ) { exists( Alias::MemoryLocation defLocation, Alias::MemoryLocation useLocation, OldBlock phiBlock, @@ -229,6 +332,8 @@ private module Cached { result = getDefinitionOrChiInstruction(defBlock, defOffset, defLocation, actualDefLocation) and overlap = Alias::getOverlap(actualDefLocation, useLocation) ) + or + result = getNewPhiOperandDefinitionFromOldSSA(instr, newPredecessorBlock, overlap) } cached @@ -249,7 +354,12 @@ private module Cached { cached Instruction getPhiInstructionBlockStart(PhiInstruction instr) { exists(OldBlock oldBlock | - instr = getPhi(oldBlock, _) and + ( + instr = getPhi(oldBlock, _) + or + // Any `Phi` that we propagated from the previous iteration stays in the same block. + getOldInstruction(instr).getBlock() = oldBlock + ) and result = getNewInstruction(oldBlock.getFirstInstruction()) ) } @@ -335,6 +445,9 @@ private module Cached { result = vvar.getType() ) or + instr = reusedPhiInstruction(_) and + result = instr.(OldInstruction).getResultLanguageType() + or instr = unreachedInstruction(_) and result = Language::getVoidType() } @@ -862,6 +975,26 @@ module DefUse { } } +predicate canReuseSSAForMemoryResult(Instruction instruction) { + exists(OldInstruction oldInstruction | + oldInstruction = getOldInstruction(instruction) and + ( + // The previous iteration said it was reusable, so we should mark it as reusable as well. + Alias::canReuseSSAForOldResult(oldInstruction) + or + // The current alias analysis says it is reusable. + Alias::getResultMemoryLocation(oldInstruction).canReuseSSA() + ) + ) + or + exists(Alias::MemoryLocation defLocation | + // This is a `Phi` for a reusable location, so the result of the `Phi` is reusable as well. + instruction = phiInstruction(_, defLocation) and + defLocation.canReuseSSA() + ) + // We don't support reusing SSA for any location that could create a `Chi` instruction. +} + /** * Expose some of the internal predicates to PrintSSA.qll. We do this by publically importing those modules in the * `DebugSSA` module, which is then imported by PrintSSA. @@ -940,7 +1073,10 @@ module SSAConsistency { locationCount > 1 and func = operand.getEnclosingIRFunction() and funcText = Language::getIdentityString(func.getFunction()) and - message = "Operand has " + locationCount.toString() + " memory accesses in function '$@'." + message = + operand.getUse().toString() + " " + "Operand has " + locationCount.toString() + + " memory accesses in function '$@': " + + strictconcat(Alias::getOperandMemoryLocation(operand).toString(), ", ") ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TInstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TInstruction.qll index e16b71733b5..4b3f19cbdde 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TInstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TInstruction.qll @@ -55,6 +55,8 @@ module UnaliasedSSAInstructions { result = TUnaliasedSSAPhiInstruction(blockStartInstr, memoryLocation) } + TRawInstruction reusedPhiInstruction(TRawInstruction blockStartInstr) { none() } + class TChiInstruction = TUnaliasedSSAChiInstruction; TChiInstruction chiInstruction(TRawInstruction primaryInstruction) { @@ -75,7 +77,7 @@ module UnaliasedSSAInstructions { * a class alias. */ module AliasedSSAInstructions { - class TPhiInstruction = TAliasedSSAPhiInstruction; + class TPhiInstruction = TAliasedSSAPhiInstruction or TUnaliasedSSAPhiInstruction; TPhiInstruction phiInstruction( TRawInstruction blockStartInstr, AliasedSSA::SSA::MemoryLocation memoryLocation @@ -83,6 +85,10 @@ module AliasedSSAInstructions { result = TAliasedSSAPhiInstruction(blockStartInstr, memoryLocation) } + TPhiInstruction reusedPhiInstruction(TRawInstruction blockStartInstr) { + result = TUnaliasedSSAPhiInstruction(blockStartInstr, _) + } + class TChiInstruction = TAliasedSSAChiInstruction; TChiInstruction chiInstruction(TRawInstruction primaryInstruction) { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll index 1e132463cdd..e86494af03a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TOperand.qll @@ -36,10 +36,9 @@ private module Internal { useInstr.getOpcode().hasOperand(tag) } or TUnaliasedPhiOperand( - Unaliased::PhiInstruction useInstr, Unaliased::Instruction defInstr, - Unaliased::IRBlock predecessorBlock, Overlap overlap + Unaliased::PhiInstruction useInstr, Unaliased::IRBlock predecessorBlock, Overlap overlap ) { - defInstr = UnaliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) + exists(UnaliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap)) } or //// ALIASED //// @@ -50,10 +49,9 @@ private module Internal { // important that we use the same definition of "is variable aliased" across // the phases. TAliasedPhiOperand( - TAliasedSSAPhiInstruction useInstr, Aliased::Instruction defInstr, - Aliased::IRBlock predecessorBlock, Overlap overlap + TAliasedSSAPhiInstruction useInstr, Aliased::IRBlock predecessorBlock, Overlap overlap ) { - defInstr = AliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) + exists(AliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap)) } or TAliasedChiOperand(TAliasedSSAChiInstruction useInstr, ChiOperandTag tag) { any() } } @@ -109,6 +107,13 @@ module RawOperands { none() } + TPhiOperand reusedPhiOperand( + Raw::PhiInstruction useInstr, Raw::Instruction defInstr, Raw::IRBlock predecessorBlock, + Overlap overlap + ) { + none() + } + /** * Returns the Chi operand with the specified parameters. */ @@ -137,7 +142,15 @@ module UnaliasedSSAOperands { Unaliased::PhiInstruction useInstr, Unaliased::Instruction defInstr, Unaliased::IRBlock predecessorBlock, Overlap overlap ) { - result = Internal::TUnaliasedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + defInstr = UnaliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) and + result = Internal::TUnaliasedPhiOperand(useInstr, predecessorBlock, overlap) + } + + TPhiOperand reusedPhiOperand( + Unaliased::PhiInstruction useInstr, Unaliased::Instruction defInstr, + Unaliased::IRBlock predecessorBlock, Overlap overlap + ) { + none() } /** @@ -155,7 +168,7 @@ module UnaliasedSSAOperands { module AliasedSSAOperands { import Shared - class TPhiOperand = Internal::TAliasedPhiOperand; + class TPhiOperand = Internal::TAliasedPhiOperand or Internal::TUnaliasedPhiOperand; class TChiOperand = Internal::TAliasedChiOperand; @@ -165,10 +178,25 @@ module AliasedSSAOperands { * Returns the Phi operand with the specified parameters. */ TPhiOperand phiOperand( - TAliasedSSAPhiInstruction useInstr, Aliased::Instruction defInstr, + Aliased::PhiInstruction useInstr, Aliased::Instruction defInstr, Aliased::IRBlock predecessorBlock, Overlap overlap ) { - result = Internal::TAliasedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + defInstr = AliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) and + result = Internal::TAliasedPhiOperand(useInstr, predecessorBlock, overlap) + } + + /** + * Returns the Phi operand with the specified parameters. + */ + TPhiOperand reusedPhiOperand( + Aliased::PhiInstruction useInstr, Aliased::Instruction defInstr, + Aliased::IRBlock predecessorBlock, Overlap overlap + ) { + exists(Unaliased::IRBlock oldBlock | + predecessorBlock = AliasedConstruction::getNewBlock(oldBlock) and + result = Internal::TUnaliasedPhiOperand(useInstr, oldBlock, _) and + defInstr = AliasedConstruction::getPhiOperandDefinition(useInstr, predecessorBlock, overlap) + ) } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll index e335080ad6b..453838215ff 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -1994,6 +1994,14 @@ class PhiInstruction extends Instruction { */ pragma[noinline] final Instruction getAnInput() { result = this.getAnInputOperand().getDef() } + + /** + * Gets the input operand representing the value that flows from the specified predecessor block. + */ + final PhiInputOperand getInputOperand(IRBlock predecessorBlock) { + result = this.getAnOperand() and + result.getPredecessorBlock() = predecessorBlock + } } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll index a2ce0662dc2..d7cf89ca9aa 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll @@ -28,11 +28,15 @@ class Operand extends TStageOperand { cached Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) or + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or exists(Instruction use, Instruction def, IRBlock predecessorBlock | - this = phiOperand(use, def, predecessorBlock, _) - ) or + this = phiOperand(use, def, predecessorBlock, _) or + this = reusedPhiOperand(use, def, predecessorBlock, _) + ) + or exists(Instruction use | this = chiOperand(use, _)) } @@ -431,7 +435,11 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand { Overlap overlap; cached - PhiInputOperand() { this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) } + PhiInputOperand() { + this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) + or + this = reusedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + } override string toString() { result = "Phi" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/SideEffects.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/SideEffects.qll index 350127a58d1..e7de9d26326 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/SideEffects.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/SideEffects.qll @@ -7,6 +7,7 @@ private import cpp private import semmle.code.cpp.ir.implementation.Opcode +private import semmle.code.cpp.models.interfaces.PointerWrapper private import semmle.code.cpp.models.interfaces.SideEffect /** @@ -39,7 +40,8 @@ private predicate hasDefaultSideEffect(Call call, ParameterIndex i, boolean buff exists(Type t | t = expr.getUnspecifiedType() | t instanceof ArrayType or t instanceof PointerType or - t instanceof ReferenceType + t instanceof ReferenceType or + t instanceof PointerWrapper ) and ( isWrite = true and diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedFunction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedFunction.qll index f55d661b202..2a0b58ce96a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedFunction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedFunction.qll @@ -725,9 +725,9 @@ abstract class TranslatedReadEffect extends TranslatedElement { override Instruction getChildSuccessor(TranslatedElement child) { none() } - override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind edge) { + override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { tag = OnlyInstructionTag() and - edge = EdgeKind::gotoEdge() and + kind = EdgeKind::gotoEdge() and result = getParent().getChildSuccessor(this) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index e335080ad6b..453838215ff 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1994,6 +1994,14 @@ class PhiInstruction extends Instruction { */ pragma[noinline] final Instruction getAnInput() { result = this.getAnInputOperand().getDef() } + + /** + * Gets the input operand representing the value that flows from the specified predecessor block. + */ + final PhiInputOperand getInputOperand(IRBlock predecessorBlock) { + result = this.getAnOperand() and + result.getPredecessorBlock() = predecessorBlock + } } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll index a2ce0662dc2..d7cf89ca9aa 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll @@ -28,11 +28,15 @@ class Operand extends TStageOperand { cached Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) or + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or exists(Instruction use, Instruction def, IRBlock predecessorBlock | - this = phiOperand(use, def, predecessorBlock, _) - ) or + this = phiOperand(use, def, predecessorBlock, _) or + this = reusedPhiOperand(use, def, predecessorBlock, _) + ) + or exists(Instruction use | this = chiOperand(use, _)) } @@ -431,7 +435,11 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand { Overlap overlap; cached - PhiInputOperand() { this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) } + PhiInputOperand() { + this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) + or + this = reusedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + } override string toString() { result = "Phi" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll index e26d61b9792..181c6d2c464 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll @@ -90,6 +90,9 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) { or // Converting an address to a `bool` does not escape the address. instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType + or + instr instanceof CallInstruction and + not exists(IREscapeAnalysisConfiguration config | config.useSoundEscapeAnalysis()) ) ) or @@ -284,14 +287,24 @@ private predicate isArgumentForParameter( private predicate isOnlyEscapesViaReturnArgument(Operand operand) { exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and - f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) + ( + f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) + or + f.parameterEscapesOnlyViaReturn(-1) and + operand instanceof ThisArgumentOperand + ) ) } private predicate isNeverEscapesArgument(Operand operand) { exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and - f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) + ( + f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) + or + f.parameterNeverEscapes(-1) and + operand instanceof ThisArgumentOperand + ) ) } @@ -325,6 +338,9 @@ predicate allocationEscapes(Configuration::Allocation allocation) { exists(IREscapeAnalysisConfiguration config | config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction()) ) + or + Configuration::phaseNeedsSoundEscapeAnalysis() and + resultEscapesNonReturn(allocation.getABaseInstruction()) } /** @@ -400,3 +416,46 @@ predicate addressOperandAllocationAndOffset( ) ) } + +/** + * Predicates used only for printing annotated IR dumps. These should not be used in production + * queries. + */ +module Print { + string getOperandProperty(Operand operand, string key) { + key = "alloc" and + result = + strictconcat(Configuration::Allocation allocation, IntValue bitOffset | + addressOperandAllocationAndOffset(operand, allocation, bitOffset) + | + allocation.toString() + Ints::getBitOffsetString(bitOffset), ", " + ) + or + key = "prop" and + result = + strictconcat(Instruction destInstr, IntValue bitOffset, string value | + operandIsPropagatedIncludingByCall(operand, bitOffset, destInstr) and + if destInstr = operand.getUse() + then value = "@" + Ints::getBitOffsetString(bitOffset) + "->result" + else value = "@" + Ints::getBitOffsetString(bitOffset) + "->" + destInstr.getResultId() + | + value, ", " + ) + } + + string getInstructionProperty(Instruction instr, string key) { + key = "prop" and + result = + strictconcat(IntValue bitOffset, Operand sourceOperand, string value | + operandIsPropagatedIncludingByCall(sourceOperand, bitOffset, instr) and + if instr = sourceOperand.getUse() + then value = sourceOperand.getDumpId() + Ints::getBitOffsetString(bitOffset) + "->@" + else + value = + sourceOperand.getUse().getResultId() + "." + sourceOperand.getDumpId() + + Ints::getBitOffsetString(bitOffset) + "->@" + | + value, ", " + ) + } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll index 5be476e12ee..dbdd3c14c85 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll @@ -14,3 +14,5 @@ class Allocation extends IRAutomaticVariable { none() } } + +predicate phaseNeedsSoundEscapeAnalysis() { any() } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/PrintAliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/PrintAliasAnalysis.qll new file mode 100644 index 00000000000..262088245e8 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/PrintAliasAnalysis.qll @@ -0,0 +1,19 @@ +/** + * Include this module to annotate IR dumps with information computed by `AliasAnalysis.qll`. + */ + +private import AliasAnalysisInternal +private import InputIR +private import AliasAnalysisImports +private import AliasAnalysis +private import semmle.code.cpp.ir.internal.IntegerConstant + +private class AliasPropertyProvider extends IRPropertyProvider { + override string getOperandProperty(Operand operand, string key) { + result = Print::getOperandProperty(operand, key) + } + + override string getInstructionProperty(Instruction instr, string key) { + result = Print::getInstructionProperty(instr, key) + } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 1ba19b4a4c3..5092e921cb3 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -43,24 +43,81 @@ private module Cached { class TStageInstruction = TRawInstruction or TPhiInstruction or TChiInstruction or TUnreachedInstruction; + /** + * If `oldInstruction` is a `Phi` instruction that has exactly one reachable predecessor block, + * this predicate returns the `PhiInputOperand` corresponding to that predecessor block. + * Otherwise, this predicate does not hold. + */ + private OldIR::PhiInputOperand getDegeneratePhiOperand(OldInstruction oldInstruction) { + result = + unique(OldIR::PhiInputOperand operand | + operand = oldInstruction.(OldIR::PhiInstruction).getAnInputOperand() and + operand.getPredecessorBlock() instanceof OldBlock + ) + } + cached predicate hasInstruction(TStageInstruction instr) { instr instanceof TRawInstruction and instr instanceof OldInstruction or - instr instanceof TPhiInstruction + instr = phiInstruction(_, _) + or + instr = reusedPhiInstruction(_) and + // Check that the phi instruction is *not* degenerate, but we can't use + // getDegeneratePhiOperand in the first stage with phi instyructions + not exists( + unique(OldIR::PhiInputOperand operand | + operand = instr.(OldIR::PhiInstruction).getAnInputOperand() and + operand.getPredecessorBlock() instanceof OldBlock + ) + ) or instr instanceof TChiInstruction or instr instanceof TUnreachedInstruction } - private IRBlock getNewBlock(OldBlock oldBlock) { - result.getFirstInstruction() = getNewInstruction(oldBlock.getFirstInstruction()) + cached + IRBlock getNewBlock(OldBlock oldBlock) { + exists(Instruction newEnd, OldIR::Instruction oldEnd | + ( + result.getLastInstruction() = newEnd and + not newEnd instanceof ChiInstruction + or + newEnd = result.getLastInstruction().(ChiInstruction).getAPredecessor() // does this work? + ) and + ( + oldBlock.getLastInstruction() = oldEnd and + not oldEnd instanceof OldIR::ChiInstruction + or + oldEnd = oldBlock.getLastInstruction().(OldIR::ChiInstruction).getAPredecessor() // does this work? + ) and + oldEnd = getNewInstruction(newEnd) + ) + } + + /** + * Gets the block from the old IR that corresponds to `newBlock`. + */ + private OldBlock getOldBlock(IRBlock newBlock) { getNewBlock(result) = newBlock } + + /** + * Holds if this iteration of SSA can model the def/use information for the result of + * `oldInstruction`, either because alias analysis has determined a memory location for that + * result, or because a previous iteration of the IR already computed that def/use information + * completely. + */ + private predicate canModelResultForOldInstruction(OldInstruction oldInstruction) { + // We're modeling the result's memory location ourselves. + exists(Alias::getResultMemoryLocation(oldInstruction)) + or + // This result was already modeled by a previous iteration of SSA. + Alias::canReuseSSAForOldResult(oldInstruction) } cached predicate hasModeledMemoryResult(Instruction instruction) { - exists(Alias::getResultMemoryLocation(getOldInstruction(instruction))) or + canModelResultForOldInstruction(getOldInstruction(instruction)) or instruction instanceof PhiInstruction or // Phis always have modeled results instruction instanceof ChiInstruction // Chis always have modeled results } @@ -117,6 +174,32 @@ private module Cached { ) } + /** + * Gets the new definition instruction for `oldOperand` based on `oldOperand`'s definition in the + * old IR. Usually, this will just get the old definition of `oldOperand` and map it to the + * corresponding new instruction. However, if the old definition of `oldOperand` is a `Phi` + * instruction that is now degenerate due all but one of its predecessor branches being + * unreachable, this predicate will recurse through any degenerate `Phi` instructions to find the + * true definition. + */ + private Instruction getNewDefinitionFromOldSSA(OldIR::MemoryOperand oldOperand, Overlap overlap) { + exists(Overlap originalOverlap | + originalOverlap = oldOperand.getDefinitionOverlap() and + ( + result = getNewInstruction(oldOperand.getAnyDef()) and + overlap = originalOverlap + or + exists(OldIR::PhiInputOperand phiOperand, Overlap phiOperandOverlap | + phiOperand = getDegeneratePhiOperand(oldOperand.getAnyDef()) and + result = getNewDefinitionFromOldSSA(phiOperand, phiOperandOverlap) and + overlap = + combineOverlap(pragma[only_bind_out](phiOperandOverlap), + pragma[only_bind_out](originalOverlap)) + ) + ) + ) + } + cached private Instruction getMemoryOperandDefinition0( Instruction instruction, MemoryOperandTag tag, Overlap overlap @@ -148,6 +231,12 @@ private module Cached { overlap instanceof MustExactlyOverlap and exists(MustTotallyOverlap o | exists(getMemoryOperandDefinition0(instruction, tag, o))) ) + or + exists(OldIR::NonPhiMemoryOperand oldOperand | + result = getNewDefinitionFromOldSSA(oldOperand, overlap) and + oldOperand.getUse() = instruction and + tag = oldOperand.getOperandTag() + ) } /** @@ -214,10 +303,24 @@ private module Cached { ) } + /** + * Gets the new definition instruction for the operand of `instr` that flows from the block + * `newPredecessorBlock`, based on that operand's definition in the old IR. + */ + private Instruction getNewPhiOperandDefinitionFromOldSSA( + Instruction instr, IRBlock newPredecessorBlock, Overlap overlap + ) { + exists(OldIR::PhiInstruction oldPhi, OldIR::PhiInputOperand oldOperand | + oldPhi = getOldInstruction(instr) and + oldOperand = oldPhi.getInputOperand(getOldBlock(newPredecessorBlock)) and + result = getNewDefinitionFromOldSSA(oldOperand, overlap) + ) + } + pragma[noopt] cached Instruction getPhiOperandDefinition( - PhiInstruction instr, IRBlock newPredecessorBlock, Overlap overlap + Instruction instr, IRBlock newPredecessorBlock, Overlap overlap ) { exists( Alias::MemoryLocation defLocation, Alias::MemoryLocation useLocation, OldBlock phiBlock, @@ -229,6 +332,8 @@ private module Cached { result = getDefinitionOrChiInstruction(defBlock, defOffset, defLocation, actualDefLocation) and overlap = Alias::getOverlap(actualDefLocation, useLocation) ) + or + result = getNewPhiOperandDefinitionFromOldSSA(instr, newPredecessorBlock, overlap) } cached @@ -249,7 +354,12 @@ private module Cached { cached Instruction getPhiInstructionBlockStart(PhiInstruction instr) { exists(OldBlock oldBlock | - instr = getPhi(oldBlock, _) and + ( + instr = getPhi(oldBlock, _) + or + // Any `Phi` that we propagated from the previous iteration stays in the same block. + getOldInstruction(instr).getBlock() = oldBlock + ) and result = getNewInstruction(oldBlock.getFirstInstruction()) ) } @@ -335,6 +445,9 @@ private module Cached { result = vvar.getType() ) or + instr = reusedPhiInstruction(_) and + result = instr.(OldInstruction).getResultLanguageType() + or instr = unreachedInstruction(_) and result = Language::getVoidType() } @@ -862,6 +975,26 @@ module DefUse { } } +predicate canReuseSSAForMemoryResult(Instruction instruction) { + exists(OldInstruction oldInstruction | + oldInstruction = getOldInstruction(instruction) and + ( + // The previous iteration said it was reusable, so we should mark it as reusable as well. + Alias::canReuseSSAForOldResult(oldInstruction) + or + // The current alias analysis says it is reusable. + Alias::getResultMemoryLocation(oldInstruction).canReuseSSA() + ) + ) + or + exists(Alias::MemoryLocation defLocation | + // This is a `Phi` for a reusable location, so the result of the `Phi` is reusable as well. + instruction = phiInstruction(_, defLocation) and + defLocation.canReuseSSA() + ) + // We don't support reusing SSA for any location that could create a `Chi` instruction. +} + /** * Expose some of the internal predicates to PrintSSA.qll. We do this by publically importing those modules in the * `DebugSSA` module, which is then imported by PrintSSA. @@ -940,7 +1073,10 @@ module SSAConsistency { locationCount > 1 and func = operand.getEnclosingIRFunction() and funcText = Language::getIdentityString(func.getFunction()) and - message = "Operand has " + locationCount.toString() + " memory accesses in function '$@'." + message = + operand.getUse().toString() + " " + "Operand has " + locationCount.toString() + + " memory accesses in function '$@': " + + strictconcat(Alias::getOperandMemoryLocation(operand).toString(), ", ") ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll index a7b9160bdc7..f3e02c9f6a8 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll @@ -17,7 +17,7 @@ private predicate isTotalAccess(Allocation var, AddressOperand addrOperand, IRTy * variable if its address never escapes and all reads and writes of that variable access the entire * variable using the original type of the variable. */ -private predicate isVariableModeled(Allocation var) { +predicate isVariableModeled(Allocation var) { not allocationEscapes(var) and forall(Instruction instr, AddressOperand addrOperand, IRType type | addrOperand = instr.getResultAddressOperand() and @@ -35,6 +35,17 @@ private predicate isVariableModeled(Allocation var) { ) } +/** + * Holds if the SSA use/def chain for the specified variable can be safely reused by later + * iterations of SSA construction. This will hold only if we modeled the variable soundly, so that + * subsequent iterations will recompute SSA for any variable that we assumed did not escape, but + * actually would have escaped if we had used a sound escape analysis. + */ +predicate canReuseSSAForVariable(IRAutomaticVariable var) { + isVariableModeled(var) and + not allocationEscapes(var) +} + private newtype TMemoryLocation = MkMemoryLocation(Allocation var) { isVariableModeled(var) } private MemoryLocation getMemoryLocation(Allocation var) { result.getAllocation() = var } @@ -57,8 +68,12 @@ class MemoryLocation extends TMemoryLocation { final Language::LanguageType getType() { result = var.getLanguageType() } final string getUniqueId() { result = var.getUniqueId() } + + final predicate canReuseSSA() { canReuseSSAForVariable(var) } } +predicate canReuseSSAForOldResult(Instruction instr) { none() } + /** * Represents a set of `MemoryLocation`s that cannot overlap with * `MemoryLocation`s outside of the set. The `VirtualVariable` will be diff --git a/cpp/ql/src/semmle/code/cpp/ir/internal/Overlap.qll b/cpp/ql/src/semmle/code/cpp/ir/internal/Overlap.qll index f9a0c574f8c..ca643b56cbb 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/internal/Overlap.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/internal/Overlap.qll @@ -8,6 +8,16 @@ private newtype TOverlap = */ abstract class Overlap extends TOverlap { abstract string toString(); + + /** + * Gets a value representing how precise this overlap is. The higher the value, the more precise + * the overlap. The precision values are ordered as + * follows, from most to least precise: + * `MustExactlyOverlap` + * `MustTotallyOverlap` + * `MayPartiallyOverlap` + */ + abstract int getPrecision(); } /** @@ -16,6 +26,8 @@ abstract class Overlap extends TOverlap { */ class MayPartiallyOverlap extends Overlap, TMayPartiallyOverlap { final override string toString() { result = "MayPartiallyOverlap" } + + final override int getPrecision() { result = 0 } } /** @@ -24,6 +36,8 @@ class MayPartiallyOverlap extends Overlap, TMayPartiallyOverlap { */ class MustTotallyOverlap extends Overlap, TMustTotallyOverlap { final override string toString() { result = "MustTotallyOverlap" } + + final override int getPrecision() { result = 1 } } /** @@ -32,4 +46,25 @@ class MustTotallyOverlap extends Overlap, TMustTotallyOverlap { */ class MustExactlyOverlap extends Overlap, TMustExactlyOverlap { final override string toString() { result = "MustExactlyOverlap" } + + final override int getPrecision() { result = 2 } +} + +/** + * Gets the `Overlap` that best represents the relationship between two memory locations `a` and + * `c`, where `getOverlap(a, b) = previousOverlap` and `getOverlap(b, c) = newOverlap`, for some + * intermediate memory location `b`. + */ +Overlap combineOverlap(Overlap previousOverlap, Overlap newOverlap) { + // Note that it's possible that two less precise overlaps could combine to result in a more + // precise overlap. For example, both `previousOverlap` and `newOverlap` could be + // `MustTotallyOverlap` even though the actual relationship between `a` and `c` is + // `MustExactlyOverlap`. We will still return `MustTotallyOverlap` as the best conservative + // approximation we can make without additional input information. + result = + min(Overlap overlap | + overlap = [previousOverlap, newOverlap] + | + overlap order by overlap.getPrecision() + ) } diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll index 7f9639f6373..e249a164061 100644 --- a/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll +++ b/cpp/ql/src/semmle/code/cpp/models/implementations/SmartPointer.qll @@ -146,8 +146,7 @@ private class SmartPtrSetterFunction extends MemberFunction, AliasFunction, Side } private FunctionInput getPointerInput() { - exists(Parameter param0 | - param0 = this.getParameter(0) and + exists(Parameter param0 | param0 = this.getParameter(0) | ( param0.getUnspecifiedType().(ReferenceType).getBaseType() instanceof SmartPtr and if this.getParameter(1).getUnspecifiedType() instanceof PointerType @@ -158,11 +157,11 @@ private class SmartPtrSetterFunction extends MemberFunction, AliasFunction, Side // parameter. result.isParameter(1) else result.isParameterDeref(0) + or + // One of the functions that takes ownership of a raw pointer. + param0.getUnspecifiedType() instanceof PointerType and + result.isParameter(0) ) - or - // One of the functions that takes ownership of a raw pointer. - param0.getUnspecifiedType() instanceof PointerType and - result.isParameter(0) ) } } diff --git a/cpp/ql/src/semmle/code/cpp/security/Encryption.qll b/cpp/ql/src/semmle/code/cpp/security/Encryption.qll index 606242e833c..55ef606483c 100644 --- a/cpp/ql/src/semmle/code/cpp/security/Encryption.qll +++ b/cpp/ql/src/semmle/code/cpp/security/Encryption.qll @@ -10,10 +10,18 @@ import cpp string getAnInsecureAlgorithmName() { result = [ - "DES", "RC2", "RC4", "RC5", "ARCFOUR" // ARCFOUR is a variant of RC4 + "DES", "RC2", "RC4", "RC5", "ARCFOUR", // ARCFOUR is a variant of RC4 + "3DES", "DES3" // also appears separated, e.g. "TRIPLE-DES", which will be matched as "DES". ] } +/** + * Gets the name of an algorithm that is known to be secure. + */ +string getASecureAlgorithmName() { + result = ["RSA", "SHA256", "CCM", "GCM", "AES", "Blowfish", "ECIES"] +} + /** * Gets the name of a hash algorithm that is insecure if it is being used for * encryption (but it is hard to know when that is happening). @@ -23,25 +31,40 @@ string getAnInsecureHashAlgorithmName() { result = ["SHA1", "MD5"] } /** * Gets the regular expression used for matching strings that look like they * contain an algorithm that is known to be insecure. + * + * Consider using `isInsecureEncryption` rather than accessing this regular + * expression directly. */ string getInsecureAlgorithmRegex() { result = // algorithms usually appear in names surrounded by characters that are not - // alphabetical characters in the same case. This handles the upper and lower - // case cases - "(^|.*[^A-Z])(" + strictconcat(getAnInsecureAlgorithmName(), "|") + ")([^A-Z].*|$)" + "|" + - // for lowercase, we want to be careful to avoid being confused by camelCase - // hence we require two preceding uppercase letters to be sure of a case switch, - // or a preceding non-alphabetic character - "(^|.*[A-Z]{2}|.*[^a-zA-Z])(" + strictconcat(getAnInsecureAlgorithmName().toLowerCase(), "|") + - ")([^a-z].*|$)" + // alphabetical characters in the same case or numerical digits. This + // handles the upper case: + "(^|.*[^A-Z0-9])(" + strictconcat(getAnInsecureAlgorithmName(), "|") + ")([^A-Z0-9].*|$)" + "|" + + // for lowercase, we want to be careful to avoid being confused by + //camelCase, hence we require two preceding uppercase letters to be + // sure of a case switch (or a preceding non-alphabetic, non-numeric + // character). + "(^|.*[A-Z]{2}|.*[^a-zA-Z0-9])(" + + strictconcat(getAnInsecureAlgorithmName().toLowerCase(), "|") + ")([^a-z0-9].*|$)" } /** - * Gets the name of an algorithm that is known to be secure. + * Holds if `name` looks like it might be related to operations with an + * insecure encyption algorithm. */ -string getASecureAlgorithmName() { - result = ["RSA", "SHA256", "CCM", "GCM", "AES", "Blowfish", "ECIES"] +bindingset[name] +predicate isInsecureEncryption(string name) { name.regexpMatch(getInsecureAlgorithmRegex()) } + +/** + * Holds if there is additional evidence that `name` looks like it might be + * related to operations with an encyption algorithm, besides the name of a + * specific algorithm. This can be used in conjuction with + * `isInsecureEncryption` to produce a stronger heuristic. + */ +bindingset[name] +predicate isEncryptionAdditionalEvidence(string name) { + name.toUpperCase().matches("%" + ["CRYPT", "CODE", "CODING", "CBC", "KEY", "CIPHER", "MAC"] + "%") } /** @@ -51,14 +74,15 @@ string getASecureAlgorithmName() { string getSecureAlgorithmRegex() { result = // algorithms usually appear in names surrounded by characters that are not - // alphabetical characters in the same case. This handles the upper and lower - // case cases - "(^|.*[^A-Z])(" + strictconcat(getASecureAlgorithmName(), "|") + ")([^A-Z].*|$)" + "|" + - // for lowercase, we want to be careful to avoid being confused by camelCase - // hence we require two preceding uppercase letters to be sure of a case - // switch, or a preceding non-alphabetic character - "(^|.*[A-Z]{2}|.*[^a-zA-Z])(" + strictconcat(getASecureAlgorithmName().toLowerCase(), "|") + - ")([^a-z].*|$)" + // alphabetical characters in the same case or numerical digits. This + // handles the upper case: + "(^|.*[^A-Z0-9])(" + strictconcat(getASecureAlgorithmName(), "|") + ")([^A-Z0-9].*|$)" + "|" + + // for lowercase, we want to be careful to avoid being confused by + //camelCase, hence we require two preceding uppercase letters to be + // sure of a case switch (or a preceding non-alphabetic, non-numeric + // character). + "(^|.*[A-Z]{2}|.*[^a-zA-Z0-9])(" + strictconcat(getASecureAlgorithmName().toLowerCase(), "|") + + ")([^a-z0-9].*|$)" } /** diff --git a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll index 6cf82791d52..b8ed406cb4a 100644 --- a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll +++ b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll @@ -12,7 +12,7 @@ import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils * Holds if the value of `use` is guarded using `abs`. */ predicate guardedAbs(Operation e, Expr use) { - exists(FunctionCall fc | fc.getTarget().getName() = "abs" | + exists(FunctionCall fc | fc.getTarget().getName() = ["abs", "labs", "llabs", "imaxabs"] | fc.getArgument(0).getAChild*() = use and guardedLesser(e, fc) ) diff --git a/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll b/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll index 0d555a6d340..ed1fb4fbb50 100644 --- a/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll +++ b/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll @@ -1136,6 +1136,11 @@ private predicate inForUpdate(Expr forUpdate, Expr child) { exists(Expr mid | inForUpdate(forUpdate, mid) and child.getParent() = mid) } +/** Gets the `rnk`'th `case` statement in `b`. */ +private int indexOfSwitchCaseRank(BlockStmt b, int rnk) { + result = rank[rnk](int i | b.getStmt(i) instanceof SwitchCase) +} + /** * A C/C++ 'switch case' statement. * @@ -1331,16 +1336,14 @@ class SwitchCase extends Stmt, @stmt_switch_case { * `default:` has results `{ x = 3; }, `x = 4;` and `break;`. */ Stmt getAStmt() { - exists(BlockStmt b, int i, int j | + exists(BlockStmt b, int rnk, int i | b.getStmt(i) = this and - b.getStmt(j) = result and - i < j and - not result instanceof SwitchCase and - not exists(SwitchCase sc, int k | - b.getStmt(k) = sc and - i < k and - j > k - ) + i = indexOfSwitchCaseRank(b, rnk) + | + pragma[only_bind_into](b).getStmt([i + 1 .. indexOfSwitchCaseRank(b, rnk + 1) - 1]) = result + or + not exists(indexOfSwitchCaseRank(b, rnk + 1)) and + b.getStmt([i + 1 .. b.getNumStmt() + 1]) = result ) } diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1126/semmle/tests/DeclarationOfVariableWithUnnecessarilyWideScope.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1126/semmle/tests/DeclarationOfVariableWithUnnecessarilyWideScope.expected new file mode 100644 index 00000000000..244a28cf332 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1126/semmle/tests/DeclarationOfVariableWithUnnecessarilyWideScope.expected @@ -0,0 +1 @@ +| test.c:14:9:14:16 | intIndex | A variable with this name is used in the $@ condition. | test.c:11:3:16:3 | while (...) ... | loop | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1126/semmle/tests/DeclarationOfVariableWithUnnecessarilyWideScope.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1126/semmle/tests/DeclarationOfVariableWithUnnecessarilyWideScope.qlref new file mode 100644 index 00000000000..6da5822f7f0 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1126/semmle/tests/DeclarationOfVariableWithUnnecessarilyWideScope.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-1126/DeclarationOfVariableWithUnnecessarilyWideScope.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1126/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1126/semmle/tests/test.c new file mode 100644 index 00000000000..47d89188e6b --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1126/semmle/tests/test.c @@ -0,0 +1,59 @@ +void workFunction_0(char *s) { + int intIndex = 10; + int intGuard; + char buf[80]; + while(intIndex > 2) // GOOD + { + buf[intIndex] = 1; + intIndex--; + } + intIndex = 10; + while(intIndex > 2) + { + buf[intIndex] = 1; + int intIndex; // BAD + intIndex--; + } + intIndex = 10; + intGuard = 20; + while(intIndex < intGuard--) // GOOD + { + buf[intIndex] = 1; + int intIndex; + intIndex--; + } + intIndex = 10; + intGuard = 20; + while(intIndex < intGuard) // GOOD + { + buf[intIndex] = 1; + int intIndex; + intIndex++; + intGuard--; + } + intIndex = 10; + intGuard = 20; + while(intIndex < intGuard) // GOOD + { + buf[intIndex] = 1; + int intIndex; + intIndex--; + intGuard -= 4; + } + intIndex = 10; + while(intIndex > 2) // GOOD + { + buf[intIndex] = 1; + intIndex -= 2; + int intIndex; + intIndex--; + } + intIndex = 10; + while(intIndex > 2) // GOOD + { + buf[intIndex] = 1; + --intIndex; + int intIndex; + intIndex--; + } +} diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-415/semmle/tests/DoubleFree.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-415/semmle/tests/DoubleFree.expected new file mode 100644 index 00000000000..0ab66e5b26c --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-415/semmle/tests/DoubleFree.expected @@ -0,0 +1,4 @@ +| test.c:11:16:11:18 | buf | This pointer may have already been cleared in the line 10. | +| test.c:18:8:18:10 | buf | This pointer may have already been cleared in the line 17. | +| test.c:57:8:57:10 | buf | This pointer may have already been cleared in the line 55. | +| test.c:78:8:78:10 | buf | This pointer may have already been cleared in the line 77. | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-415/semmle/tests/DoubleFree.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-415/semmle/tests/DoubleFree.qlref new file mode 100644 index 00000000000..242beb593f8 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-415/semmle/tests/DoubleFree.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-415/DoubleFree.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-415/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-415/semmle/tests/test.c new file mode 100644 index 00000000000..1c154c03094 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-415/semmle/tests/test.c @@ -0,0 +1,96 @@ +typedef unsigned long size_t; +void *malloc(size_t size); +void free(void *ptr); +#define NULL 0 + +void workFunction_0(char *s) { + int intSize = 10; + char *buf; + buf = (char *) malloc(intSize); + free(buf); // GOOD + if(buf) free(buf); // BAD +} +void workFunction_1(char *s) { + int intSize = 10; + char *buf; + buf = (char *) malloc(intSize); + free(buf); // GOOD + free(buf); // BAD +} +void workFunction_2(char *s) { + int intSize = 10; + char *buf; + buf = (char *) malloc(intSize); + free(buf); // GOOD + buf = NULL; + free(buf); // GOOD +} +void workFunction_3(char *s) { + int intSize = 10; + char *buf; + int intFlag; + buf = (char *) malloc(intSize); + if(buf[1]%5) { + free(buf); // GOOD + buf = NULL; + } + free(buf); // GOOD +} +void workFunction_4(char *s) { + int intSize = 10; + char *buf; + char *tmpbuf; + tmpbuf = (char *) malloc(intSize); + buf = (char *) malloc(intSize); + free(buf); // GOOD + buf = tmpbuf; + free(buf); // GOOD +} +void workFunction_5(char *s, int intFlag) { + int intSize = 10; + char *buf; + + buf = (char *) malloc(intSize); + if(intFlag) { + free(buf); // GOOD + } + free(buf); // BAD +} +void workFunction_6(char *s, int intFlag) { + int intSize = 10; + char *buf; + char *tmpbuf; + + tmpbuf = (char *) malloc(intSize); + buf = (char *) malloc(intSize); + if(intFlag) { + free(buf); // GOOD + buf = tmpbuf; + } + free(buf); // GOOD +} +void workFunction_7(char *s) { + int intSize = 10; + char *buf; + char *buf1; + buf = (char *) malloc(intSize); + buf1 = (char *) realloc(buf,intSize*4); + free(buf); // BAD +} +void workFunction_8(char *s) { + int intSize = 10; + char *buf; + char *buf1; + buf = (char *) malloc(intSize); + buf1 = (char *) realloc(buf,intSize*4); + if(!buf1) + free(buf); // GOOD +} +void workFunction_9(char *s) { + int intSize = 10; + char *buf; + char *buf1; + buf = (char *) malloc(intSize); + if(!(buf1 = (char *) realloc(buf,intSize*4))) + free(buf); // GOOD +} diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected deleted file mode 100644 index 19886efd28f..00000000000 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected +++ /dev/null @@ -1,9 +0,0 @@ -| test.cpp:29:13:29:24 | call to operator new[] | memory allocation error check is incorrect or missing | -| test.cpp:37:13:37:24 | call to operator new[] | memory allocation error check is incorrect or missing | -| test.cpp:41:13:41:24 | call to operator new[] | memory allocation error check is incorrect or missing | -| test.cpp:49:8:49:19 | call to operator new[] | memory allocation error check is incorrect or missing | -| test.cpp:58:8:58:19 | call to operator new[] | memory allocation error check is incorrect or missing | -| test.cpp:63:8:63:19 | call to operator new[] | memory allocation error check is incorrect or missing | -| test.cpp:92:5:92:31 | call to operator new[] | memory allocation error check is incorrect or missing | -| test.cpp:93:15:93:41 | call to operator new[] | memory allocation error check is incorrect or missing | -| test.cpp:96:10:96:36 | call to operator new[] | memory allocation error check is incorrect or missing | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref deleted file mode 100644 index fc3252ef122..00000000000 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected index cd160a70638..75e40fb96b3 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected @@ -1,9 +1,9 @@ -| test.c:54:3:54:24 | ... = ... | potential unsafe or redundant assignment. | -| test.c:55:3:55:40 | ... = ... | potential unsafe or redundant assignment. | -| test.c:56:3:56:44 | ... = ... | potential unsafe or redundant assignment. | -| test.c:57:3:57:44 | ... = ... | potential unsafe or redundant assignment. | -| test.c:58:3:58:48 | ... = ... | potential unsafe or redundant assignment. | -| test.c:59:3:59:48 | ... = ... | potential unsafe or redundant assignment. | -| test.c:60:3:60:52 | ... = ... | potential unsafe or redundant assignment. | -| test.c:61:3:61:50 | ... = ... | potential unsafe or redundant assignment. | -| test.c:62:3:62:54 | ... = ... | potential unsafe or redundant assignment. | +| test.c:16:3:16:24 | ... = ... | potential unsafe or redundant assignment. | +| test.c:17:3:17:40 | ... = ... | potential unsafe or redundant assignment. | +| test.c:18:3:18:44 | ... = ... | potential unsafe or redundant assignment. | +| test.c:19:3:19:44 | ... = ... | potential unsafe or redundant assignment. | +| test.c:20:3:20:48 | ... = ... | potential unsafe or redundant assignment. | +| test.c:21:3:21:48 | ... = ... | potential unsafe or redundant assignment. | +| test.c:22:3:22:52 | ... = ... | potential unsafe or redundant assignment. | +| test.c:23:3:23:50 | ... = ... | potential unsafe or redundant assignment. | +| test.c:24:3:24:54 | ... = ... | potential unsafe or redundant assignment. | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.expected deleted file mode 100644 index 3e9791e1024..00000000000 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.expected +++ /dev/null @@ -1,5 +0,0 @@ -| test.c:8:3:8:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. | -| test.c:9:3:9:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. | -| test.c:17:3:17:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. | -| test.c:18:3:18:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. | -| test.c:46:3:46:9 | call to strncat | Possible out-of-bounds write due to incorrect size argument. | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qlref deleted file mode 100644 index 8fd8b1b3217..00000000000 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c index 6d310875630..a204aa4db29 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c @@ -2,50 +2,12 @@ char * strncat(char*, const char*, unsigned); unsigned strlen(const char*); void* malloc(unsigned); -void strncat_test1(char *s) { - char buf[80]; - strncat(buf, s, sizeof(buf) - strlen(buf) - 1); // GOOD - strncat(buf, s, sizeof(buf) - strlen(buf)); // BAD - strncat(buf, "fix", sizeof(buf)-strlen(buf)); // BAD -} - -#define MAX_SIZE 80 - -void strncat_test2(char *s) { - char buf[MAX_SIZE]; - strncat(buf, s, MAX_SIZE - strlen(buf) - 1); // GOOD - strncat(buf, s, MAX_SIZE - strlen(buf)); // BAD - strncat(buf, "fix", MAX_SIZE - strlen(buf)); // BAD -} - -void strncat_test3(char *s) { - int len = 80; - char* buf = (char *) malloc(len); - strncat(buf, s, len - strlen(buf) - 1); // GOOD - strncat(buf, s, len - strlen(buf)); // BAD [NOT DETECTED] - strncat(buf, "fix", len - strlen(buf)); // BAD [NOT DETECTED] -} - -void strncat_test4(char *s) { - int len = 80; - char* buf = (char *) malloc(len + 1); - strncat(buf, s, len - strlen(buf) - 1); // GOOD - strncat(buf, s, len - strlen(buf)); // GOOD -} - struct buffers { unsigned char array[50]; unsigned char *pointer; } globalBuff1,*globalBuff2,globalBuff1_c,*globalBuff2_c; -void strncat_test5(char* s, struct buffers* buffers) { - unsigned len_array = strlen(buffers->array); - unsigned max_size = sizeof(buffers->array); - unsigned free_size = max_size - len_array; - strncat(buffers->array, s, free_size); // BAD -} - void strlen_test1(){ unsigned char buff1[12]; struct buffers buffAll; diff --git a/cpp/ql/test/library-tests/clang_ms/element.expected b/cpp/ql/test/library-tests/clang_ms/element.expected index 11e28a50670..8ca381ef406 100644 --- a/cpp/ql/test/library-tests/clang_ms/element.expected +++ b/cpp/ql/test/library-tests/clang_ms/element.expected @@ -26,6 +26,8 @@ | clang_ms.cpp:17:1:17:32 | #pragma | | clang_ms.cpp:18:1:18:31 | #pragma | | file://:0:0:0:0 | | +| file://:0:0:0:0 | & | +| file://:0:0:0:0 | && | | file://:0:0:0:0 | (global namespace) | | file://:0:0:0:0 | (unnamed parameter 0) | | file://:0:0:0:0 | (unnamed parameter 0) | diff --git a/cpp/ql/test/library-tests/conditions/elements.expected b/cpp/ql/test/library-tests/conditions/elements.expected index 483f5ad22ed..cdab5557305 100644 --- a/cpp/ql/test/library-tests/conditions/elements.expected +++ b/cpp/ql/test/library-tests/conditions/elements.expected @@ -1,4 +1,6 @@ | file://:0:0:0:0 | | +| file://:0:0:0:0 | & | +| file://:0:0:0:0 | && | | file://:0:0:0:0 | (global namespace) | | file://:0:0:0:0 | (unnamed parameter 0) | | file://:0:0:0:0 | (unnamed parameter 0) | diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/defaulttainttracking.cpp b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/defaulttainttracking.cpp index d64f1966b49..843587b576a 100644 --- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/defaulttainttracking.cpp +++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/annotate_sinks_only/defaulttainttracking.cpp @@ -187,10 +187,10 @@ void test_pointers1() ptr4 = &ptr3; sink(buffer); // $ ast,ir - sink(ptr1); // $ ast,ir + sink(ptr1); // $ ast MISSING: ir sink(ptr2); // $ SPURIOUS: ast sink(*ptr2); // $ ast MISSING: ir - sink(ptr3); // $ ast,ir + sink(ptr3); // $ ast MISSING: ir sink(ptr4); // $ SPURIOUS: ast sink(*ptr4); // $ ast MISSING: ir } diff --git a/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected b/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected index e6234ca17f7..ee6b6a39bde 100644 --- a/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected +++ b/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected @@ -135,40 +135,24 @@ edges | by_reference.cpp:128:15:128:23 | Chi [a] | by_reference.cpp:136:16:136:16 | a | | by_reference.cpp:128:15:128:23 | taint_a_ref output argument [array content] | by_reference.cpp:128:15:128:23 | Chi | | complex.cpp:40:17:40:17 | *b [a_] | complex.cpp:42:16:42:16 | f indirection [a_] | -| complex.cpp:40:17:40:17 | *b [b_] | complex.cpp:42:16:42:16 | Chi [b_] | | complex.cpp:40:17:40:17 | *b [b_] | complex.cpp:42:16:42:16 | f indirection [b_] | | complex.cpp:40:17:40:17 | *b [b_] | complex.cpp:43:16:43:16 | f indirection [b_] | -| complex.cpp:42:16:42:16 | Chi [b_] | complex.cpp:43:16:43:16 | f indirection [b_] | -| complex.cpp:42:16:42:16 | a output argument [b_] | complex.cpp:42:16:42:16 | Chi [b_] | | complex.cpp:42:16:42:16 | a output argument [b_] | complex.cpp:43:16:43:16 | f indirection [b_] | | complex.cpp:42:16:42:16 | f indirection [a_] | complex.cpp:42:18:42:18 | call to a | | complex.cpp:42:16:42:16 | f indirection [b_] | complex.cpp:42:16:42:16 | a output argument [b_] | | complex.cpp:43:16:43:16 | f indirection [b_] | complex.cpp:43:18:43:18 | call to b | -| complex.cpp:53:12:53:12 | Chi [a_] | complex.cpp:59:7:59:8 | b1 indirection [a_] | -| complex.cpp:53:12:53:12 | setA output argument [a_] | complex.cpp:53:12:53:12 | Chi [a_] | | complex.cpp:53:12:53:12 | setA output argument [a_] | complex.cpp:59:7:59:8 | b1 indirection [a_] | | complex.cpp:53:14:53:17 | call to user_input | complex.cpp:53:12:53:12 | setA output argument [a_] | | complex.cpp:53:19:53:28 | call to user_input | complex.cpp:53:14:53:17 | call to user_input | -| complex.cpp:54:12:54:12 | Chi [b_] | complex.cpp:62:7:62:8 | b2 indirection [b_] | -| complex.cpp:54:12:54:12 | setB output argument [b_] | complex.cpp:54:12:54:12 | Chi [b_] | | complex.cpp:54:12:54:12 | setB output argument [b_] | complex.cpp:62:7:62:8 | b2 indirection [b_] | | complex.cpp:54:14:54:17 | call to user_input | complex.cpp:54:12:54:12 | setB output argument [b_] | | complex.cpp:54:19:54:28 | call to user_input | complex.cpp:54:14:54:17 | call to user_input | -| complex.cpp:55:12:55:12 | Chi [a_] | complex.cpp:56:12:56:12 | Chi [a_] | -| complex.cpp:55:12:55:12 | Chi [a_] | complex.cpp:56:12:56:12 | f indirection [a_] | -| complex.cpp:55:12:55:12 | Chi [a_] | complex.cpp:65:7:65:8 | b3 indirection [a_] | -| complex.cpp:55:12:55:12 | setA output argument [a_] | complex.cpp:55:12:55:12 | Chi [a_] | -| complex.cpp:55:12:55:12 | setA output argument [a_] | complex.cpp:56:12:56:12 | Chi [a_] | | complex.cpp:55:12:55:12 | setA output argument [a_] | complex.cpp:56:12:56:12 | f indirection [a_] | | complex.cpp:55:12:55:12 | setA output argument [a_] | complex.cpp:65:7:65:8 | b3 indirection [a_] | | complex.cpp:55:14:55:17 | call to user_input | complex.cpp:55:12:55:12 | setA output argument [a_] | | complex.cpp:55:19:55:28 | call to user_input | complex.cpp:55:14:55:17 | call to user_input | -| complex.cpp:56:12:56:12 | Chi [a_] | complex.cpp:65:7:65:8 | b3 indirection [a_] | -| complex.cpp:56:12:56:12 | Chi [b_] | complex.cpp:65:7:65:8 | b3 indirection [b_] | | complex.cpp:56:12:56:12 | f indirection [a_] | complex.cpp:56:12:56:12 | setB output argument [a_] | -| complex.cpp:56:12:56:12 | setB output argument [a_] | complex.cpp:56:12:56:12 | Chi [a_] | | complex.cpp:56:12:56:12 | setB output argument [a_] | complex.cpp:65:7:65:8 | b3 indirection [a_] | -| complex.cpp:56:12:56:12 | setB output argument [b_] | complex.cpp:56:12:56:12 | Chi [b_] | | complex.cpp:56:12:56:12 | setB output argument [b_] | complex.cpp:65:7:65:8 | b3 indirection [b_] | | complex.cpp:56:14:56:17 | call to user_input | complex.cpp:56:12:56:12 | setB output argument [b_] | | complex.cpp:56:19:56:28 | call to user_input | complex.cpp:56:14:56:17 | call to user_input | @@ -410,27 +394,21 @@ nodes | by_reference.cpp:136:16:136:16 | a | semmle.label | a | | complex.cpp:40:17:40:17 | *b [a_] | semmle.label | *b [a_] | | complex.cpp:40:17:40:17 | *b [b_] | semmle.label | *b [b_] | -| complex.cpp:42:16:42:16 | Chi [b_] | semmle.label | Chi [b_] | | complex.cpp:42:16:42:16 | a output argument [b_] | semmle.label | a output argument [b_] | | complex.cpp:42:16:42:16 | f indirection [a_] | semmle.label | f indirection [a_] | | complex.cpp:42:16:42:16 | f indirection [b_] | semmle.label | f indirection [b_] | | complex.cpp:42:18:42:18 | call to a | semmle.label | call to a | | complex.cpp:43:16:43:16 | f indirection [b_] | semmle.label | f indirection [b_] | | complex.cpp:43:18:43:18 | call to b | semmle.label | call to b | -| complex.cpp:53:12:53:12 | Chi [a_] | semmle.label | Chi [a_] | | complex.cpp:53:12:53:12 | setA output argument [a_] | semmle.label | setA output argument [a_] | | complex.cpp:53:14:53:17 | call to user_input | semmle.label | call to user_input | | complex.cpp:53:19:53:28 | call to user_input | semmle.label | call to user_input | -| complex.cpp:54:12:54:12 | Chi [b_] | semmle.label | Chi [b_] | | complex.cpp:54:12:54:12 | setB output argument [b_] | semmle.label | setB output argument [b_] | | complex.cpp:54:14:54:17 | call to user_input | semmle.label | call to user_input | | complex.cpp:54:19:54:28 | call to user_input | semmle.label | call to user_input | -| complex.cpp:55:12:55:12 | Chi [a_] | semmle.label | Chi [a_] | | complex.cpp:55:12:55:12 | setA output argument [a_] | semmle.label | setA output argument [a_] | | complex.cpp:55:14:55:17 | call to user_input | semmle.label | call to user_input | | complex.cpp:55:19:55:28 | call to user_input | semmle.label | call to user_input | -| complex.cpp:56:12:56:12 | Chi [a_] | semmle.label | Chi [a_] | -| complex.cpp:56:12:56:12 | Chi [b_] | semmle.label | Chi [b_] | | complex.cpp:56:12:56:12 | f indirection [a_] | semmle.label | f indirection [a_] | | complex.cpp:56:12:56:12 | setB output argument [a_] | semmle.label | setB output argument [a_] | | complex.cpp:56:12:56:12 | setB output argument [b_] | semmle.label | setB output argument [b_] | diff --git a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected index 44d379e46fe..4b997627182 100644 --- a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected +++ b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected @@ -11365,6 +11365,86 @@ perf-regression.cpp: # 12| Type = [IntType] int # 12| Value = [Literal] 0 # 12| ValueCategory = prvalue +smart_ptr.cpp: +# 8| [TopLevelFunction] void unique_ptr_arg(std::unique_ptr>) +# 8| : +# 8| getParameter(0): [Parameter] up +# 8| Type = [ClassTemplateInstantiation] unique_ptr> +# 10| [TopLevelFunction] void call_unique_ptr_arg(int*) +# 10| : +# 10| getParameter(0): [Parameter] p +# 10| Type = [IntPointerType] int * +# 10| getEntryPoint(): [BlockStmt] { ... } +# 11| getStmt(0): [DeclStmt] declaration +# 11| getDeclarationEntry(0): [VariableDeclarationEntry] definition of up +# 11| Type = [ClassTemplateInstantiation] unique_ptr> +# 11| getVariable().getInitializer(): [Initializer] initializer for up +# 11| getExpr(): [ConstructorCall] call to unique_ptr +# 11| Type = [VoidType] void +# 11| ValueCategory = prvalue +# 11| getArgument(0): [VariableAccess] p +# 11| Type = [IntPointerType] int * +# 11| ValueCategory = prvalue(load) +# 12| getStmt(1): [ExprStmt] ExprStmt +# 12| getExpr(): [FunctionCall] call to unique_ptr_arg +# 12| Type = [VoidType] void +# 12| ValueCategory = prvalue +# 12| getArgument(0): [FunctionCall] call to move +# 12| Type = [RValueReferenceType] type && +# 12| ValueCategory = prvalue +# 12| getArgument(0): [VariableAccess] up +# 12| Type = [ClassTemplateInstantiation] unique_ptr> +# 12| ValueCategory = lvalue +# 12| getArgument(0).getFullyConverted(): [ReferenceToExpr] (reference to) +# 12| Type = [LValueReferenceType] unique_ptr> & +# 12| ValueCategory = prvalue +# 12| getArgument(0).getFullyConverted(): [TemporaryObjectExpr] temporary object +# 12| Type = [ClassTemplateInstantiation] unique_ptr> +# 12| ValueCategory = lvalue +# 12| getExpr(): [ReferenceDereferenceExpr] (reference dereference) +# 12| Type = [CTypedefType,NestedTypedefType] type +# 12| ValueCategory = prvalue(load) +# 13| getStmt(2): [ReturnStmt] return ... +# 15| [TopLevelFunction] void shared_ptr_arg(std::shared_ptr) +# 15| : +# 15| getParameter(0): [Parameter] sp +# 15| Type = [ClassTemplateInstantiation] shared_ptr +# 17| [TopLevelFunction] void call_shared_ptr_arg(float*) +# 17| : +# 17| getParameter(0): [Parameter] p +# 17| Type = [PointerType] float * +# 17| getEntryPoint(): [BlockStmt] { ... } +# 18| getStmt(0): [DeclStmt] declaration +# 18| getDeclarationEntry(0): [VariableDeclarationEntry] definition of sp +# 18| Type = [ClassTemplateInstantiation] shared_ptr +# 18| getVariable().getInitializer(): [Initializer] initializer for sp +# 18| getExpr(): [ConstructorCall] call to shared_ptr +# 18| Type = [VoidType] void +# 18| ValueCategory = prvalue +# 18| getArgument(0): [VariableAccess] p +# 18| Type = [PointerType] float * +# 18| ValueCategory = prvalue(load) +# 19| getStmt(1): [ExprStmt] ExprStmt +# 19| getExpr(): [FunctionCall] call to shared_ptr_arg +# 19| Type = [VoidType] void +# 19| ValueCategory = prvalue +# 19| getArgument(0): [ConstructorCall] call to shared_ptr +# 19| Type = [VoidType] void +# 19| ValueCategory = prvalue +# 19| getArgument(0): [VariableAccess] sp +# 19| Type = [ClassTemplateInstantiation] shared_ptr +# 19| ValueCategory = lvalue +# 19| getArgument(0).getFullyConverted(): [ReferenceToExpr] (reference to) +# 19| Type = [LValueReferenceType] const shared_ptr & +# 19| ValueCategory = prvalue +# 19| getExpr(): [CStyleCast] (const shared_ptr)... +# 19| Conversion = [GlvalueConversion] glvalue conversion +# 19| Type = [SpecifiedType] const shared_ptr +# 19| ValueCategory = lvalue +# 19| getArgument(0).getFullyConverted(): [TemporaryObjectExpr] temporary object +# 19| Type = [ClassTemplateInstantiation] shared_ptr +# 19| ValueCategory = lvalue +# 20| getStmt(2): [ReturnStmt] return ... struct_init.cpp: # 1| [TopLevelFunction] int handler1(void*) # 1| : diff --git a/cpp/ql/test/library-tests/ir/ir/PrintAST.ql b/cpp/ql/test/library-tests/ir/ir/PrintAST.ql new file mode 100644 index 00000000000..e107c80de02 --- /dev/null +++ b/cpp/ql/test/library-tests/ir/ir/PrintAST.ql @@ -0,0 +1,11 @@ +/** + * @kind graph + */ + +private import cpp +private import semmle.code.cpp.PrintAST +private import PrintConfig + +private class PrintConfig extends PrintASTConfiguration { + override predicate shouldPrintFunction(Function func) { shouldDumpFunction(func) } +} diff --git a/cpp/ql/test/library-tests/ir/ir/PrintAST.qlref b/cpp/ql/test/library-tests/ir/ir/PrintAST.qlref deleted file mode 100644 index 6fcb30ac7a6..00000000000 --- a/cpp/ql/test/library-tests/ir/ir/PrintAST.qlref +++ /dev/null @@ -1 +0,0 @@ -semmle/code/cpp/PrintAST.ql \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ir/PrintConfig.qll b/cpp/ql/test/library-tests/ir/ir/PrintConfig.qll new file mode 100644 index 00000000000..3253a1196b6 --- /dev/null +++ b/cpp/ql/test/library-tests/ir/ir/PrintConfig.qll @@ -0,0 +1,10 @@ +private import cpp + +/** + * Holds if the AST or IR for the specified function should be printed in the test output. + * + * This predicate excludes functions defined in standard headers. + */ +predicate shouldDumpFunction(Function func) { + not func.getLocation().getFile().getAbsolutePath().regexpMatch(".*/include/[^/]+") +} diff --git a/cpp/ql/test/library-tests/ir/ir/raw_consistency.expected b/cpp/ql/test/library-tests/ir/ir/raw_consistency.expected index 31e5b01229c..57f16b48a1a 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_consistency.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_consistency.expected @@ -6,6 +6,7 @@ missingOperandType duplicateChiOperand sideEffectWithoutPrimary instructionWithoutSuccessor +| ../../../include/memory.h:68:25:68:33 | CopyValue: (reference to) | Instruction 'CopyValue: (reference to)' has no successors in function '$@'. | ../../../include/memory.h:67:5:67:5 | void std::unique_ptr>::~unique_ptr() | void std::unique_ptr>::~unique_ptr() | ambiguousSuccessors unexplainedLoop unnecessaryPhiInstruction diff --git a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected index e008e2de659..6bcfd2c43e4 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected @@ -7903,6 +7903,80 @@ perf-regression.cpp: # 9| v9_6(void) = AliasedUse : ~m? # 9| v9_7(void) = ExitFunction : +smart_ptr.cpp: +# 10| void call_unique_ptr_arg(int*) +# 10| Block 0 +# 10| v10_1(void) = EnterFunction : +# 10| mu10_2(unknown) = AliasedDefinition : +# 10| mu10_3(unknown) = InitializeNonLocal : +# 10| r10_4(glval) = VariableAddress[p] : +# 10| mu10_5(int *) = InitializeParameter[p] : &:r10_4 +# 10| r10_6(int *) = Load[p] : &:r10_4, ~m? +# 10| mu10_7(unknown) = InitializeIndirection[p] : &:r10_6 +# 11| r11_1(glval>>) = VariableAddress[up] : +# 11| mu11_2(unique_ptr>) = Uninitialized[up] : &:r11_1 +# 11| r11_3(glval) = FunctionAddress[unique_ptr] : +# 11| r11_4(glval) = VariableAddress[p] : +# 11| r11_5(int *) = Load[p] : &:r11_4, ~m? +# 11| v11_6(void) = Call[unique_ptr] : func:r11_3, this:r11_1, 0:r11_5 +# 11| mu11_7(unknown) = ^CallSideEffect : ~m? +# 11| mu11_8(unique_ptr>) = ^IndirectMustWriteSideEffect[-1] : &:r11_1 +# 12| r12_1(glval) = FunctionAddress[unique_ptr_arg] : +# 12| r12_2(glval>>) = VariableAddress[#temp12:20] : +# 12| r12_3(glval) = FunctionAddress[move] : +# 12| r12_4(glval>>) = VariableAddress[up] : +# 12| r12_5(unique_ptr> &) = CopyValue : r12_4 +# 12| r12_6(unique_ptr> &&) = Call[move] : func:r12_3, 0:r12_5 +# 12| r12_7(unique_ptr>) = Load[?] : &:r12_6, ~m? +# 12| mu12_8(unique_ptr>) = Store[#temp12:20] : &:r12_2, r12_7 +# 12| r12_9(unique_ptr>) = Load[#temp12:20] : &:r12_2, ~m? +# 12| v12_10(void) = Call[unique_ptr_arg] : func:r12_1, 0:r12_9 +# 12| mu12_11(unknown) = ^CallSideEffect : ~m? +# 12| v12_12(void) = ^BufferReadSideEffect[0] : &:r12_9, ~m? +# 13| v13_1(void) = NoOp : +# 10| v10_8(void) = ReturnIndirection[p] : &:r10_6, ~m? +# 10| v10_9(void) = ReturnVoid : +# 10| v10_10(void) = AliasedUse : ~m? +# 10| v10_11(void) = ExitFunction : + +# 17| void call_shared_ptr_arg(float*) +# 17| Block 0 +# 17| v17_1(void) = EnterFunction : +# 17| mu17_2(unknown) = AliasedDefinition : +# 17| mu17_3(unknown) = InitializeNonLocal : +# 17| r17_4(glval) = VariableAddress[p] : +# 17| mu17_5(float *) = InitializeParameter[p] : &:r17_4 +# 17| r17_6(float *) = Load[p] : &:r17_4, ~m? +# 17| mu17_7(unknown) = InitializeIndirection[p] : &:r17_6 +# 18| r18_1(glval>) = VariableAddress[sp] : +# 18| mu18_2(shared_ptr) = Uninitialized[sp] : &:r18_1 +# 18| r18_3(glval) = FunctionAddress[shared_ptr] : +# 18| r18_4(glval) = VariableAddress[p] : +# 18| r18_5(float *) = Load[p] : &:r18_4, ~m? +# 18| v18_6(void) = Call[shared_ptr] : func:r18_3, this:r18_1, 0:r18_5 +# 18| mu18_7(unknown) = ^CallSideEffect : ~m? +# 18| mu18_8(shared_ptr) = ^IndirectMustWriteSideEffect[-1] : &:r18_1 +# 19| r19_1(glval) = FunctionAddress[shared_ptr_arg] : +# 19| r19_2(glval>) = VariableAddress[#temp19:20] : +# 19| mu19_3(shared_ptr) = Uninitialized[#temp19:20] : &:r19_2 +# 19| r19_4(glval) = FunctionAddress[shared_ptr] : +# 19| r19_5(glval>) = VariableAddress[sp] : +# 19| r19_6(glval>) = Convert : r19_5 +# 19| r19_7(shared_ptr &) = CopyValue : r19_6 +# 19| v19_8(void) = Call[shared_ptr] : func:r19_4, this:r19_2, 0:r19_7 +# 19| mu19_9(unknown) = ^CallSideEffect : ~m? +# 19| mu19_10(shared_ptr) = ^IndirectMustWriteSideEffect[-1] : &:r19_2 +# 19| v19_11(void) = ^IndirectReadSideEffect[0] : &:r19_7, ~m? +# 19| r19_12(shared_ptr) = Load[#temp19:20] : &:r19_2, ~m? +# 19| v19_13(void) = Call[shared_ptr_arg] : func:r19_1, 0:r19_12 +# 19| mu19_14(unknown) = ^CallSideEffect : ~m? +# 19| v19_15(void) = ^BufferReadSideEffect[0] : &:r19_12, ~m? +# 20| v20_1(void) = NoOp : +# 17| v17_8(void) = ReturnIndirection[p] : &:r17_6, ~m? +# 17| v17_9(void) = ReturnVoid : +# 17| v17_10(void) = AliasedUse : ~m? +# 17| v17_11(void) = ExitFunction : + struct_init.cpp: # 16| void let_info_escape(Info*) # 16| Block 0 diff --git a/cpp/ql/test/library-tests/ir/ir/raw_ir.ql b/cpp/ql/test/library-tests/ir/ir/raw_ir.ql new file mode 100644 index 00000000000..a0ebe4d2bdd --- /dev/null +++ b/cpp/ql/test/library-tests/ir/ir/raw_ir.ql @@ -0,0 +1,11 @@ +/** + * @kind graph + */ + +private import cpp +private import semmle.code.cpp.ir.implementation.raw.PrintIR +private import PrintConfig + +private class PrintConfig extends PrintIRConfiguration { + override predicate shouldPrintFunction(Function func) { shouldDumpFunction(func) } +} diff --git a/cpp/ql/test/library-tests/ir/ir/raw_ir.qlref b/cpp/ql/test/library-tests/ir/ir/raw_ir.qlref deleted file mode 100644 index fa4c4bda903..00000000000 --- a/cpp/ql/test/library-tests/ir/ir/raw_ir.qlref +++ /dev/null @@ -1 +0,0 @@ -semmle/code/cpp/ir/implementation/raw/PrintIR.ql \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ir/smart_ptr.cpp b/cpp/ql/test/library-tests/ir/ir/smart_ptr.cpp new file mode 100644 index 00000000000..980c7767009 --- /dev/null +++ b/cpp/ql/test/library-tests/ir/ir/smart_ptr.cpp @@ -0,0 +1,20 @@ +#include "../../../include/memory.h" +#include "../../../include/utility.h" + +using std::move; +using std::shared_ptr; +using std::unique_ptr; + +void unique_ptr_arg(unique_ptr up); + +void call_unique_ptr_arg(int* p) { + unique_ptr up(p); + unique_ptr_arg(move(up)); +} + +void shared_ptr_arg(shared_ptr sp); + +void call_shared_ptr_arg(float* p) { + shared_ptr sp(p); + shared_ptr_arg(sp); +} diff --git a/cpp/ql/test/library-tests/ir/points_to/points_to.cpp b/cpp/ql/test/library-tests/ir/points_to/points_to.cpp index 024a3190706..fa4d062c5f8 100644 --- a/cpp/ql/test/library-tests/ir/points_to/points_to.cpp +++ b/cpp/ql/test/library-tests/ir/points_to/points_to.cpp @@ -37,51 +37,51 @@ void Locals() { } void PointsTo( - int a, //$raw,ussa=a - Point& b, //$raw,ussa=b ussa=*b - Point* c, //$raw,ussa=c ussa=*c - int* d, //$raw,ussa=d ussa=*d - DerivedSI* e, //$raw,ussa=e ussa=*e - DerivedMI* f, //$raw,ussa=f ussa=*f - DerivedVI* g //$raw,ussa=g ussa=*g + int a, //$raw=a + Point& b, //$raw=b ussa=*b + Point* c, //$raw=c ussa=*c + int* d, //$raw=d ussa=*d + DerivedSI* e, //$raw=e ussa=*e + DerivedMI* f, //$raw=f ussa=*f + DerivedVI* g //$raw=g ussa=*g ) { - int i = a; //$raw,ussa=a - i = *&a; //$raw,ussa=a - i = *(&a + 0); //$raw,ussa=a - i = b.x; //$raw,ussa=b ussa=*b[0..4) - i = b.y; //$raw,ussa=b ussa=*b[4..8) - i = c->x; //$raw,ussa=c ussa=*c[0..4) - i = c->y; //$raw,ussa=c ussa=*c[4..8) - i = *d; //$raw,ussa=d ussa=*d[0..4) - i = *(d + 0); //$raw,ussa=d ussa=*d[0..4) - i = d[5]; //$raw,ussa=d ussa=*d[20..24) - i = 5[d]; //$raw,ussa=d ussa=*d[20..24) - i = d[a]; //$raw,ussa=d raw,ussa=a ussa=*d[?..?) - i = a[d]; //$raw,ussa=d raw,ussa=a ussa=*d[?..?) + int i = a; //$raw=a + i = *&a; //$raw=a + i = *(&a + 0); //$raw=a + i = b.x; //$raw=b ussa=*b[0..4) + i = b.y; //$raw=b ussa=*b[4..8) + i = c->x; //$raw=c ussa=*c[0..4) + i = c->y; //$raw=c ussa=*c[4..8) + i = *d; //$raw=d ussa=*d[0..4) + i = *(d + 0); //$raw=d ussa=*d[0..4) + i = d[5]; //$raw=d ussa=*d[20..24) + i = 5[d]; //$raw=d ussa=*d[20..24) + i = d[a]; //$raw=d raw=a ussa=*d[?..?) + i = a[d]; //$raw=d raw=a ussa=*d[?..?) - int* p = &b.x; //$raw,ussa=b + int* p = &b.x; //$raw=b i = *p; //$ussa=*b[0..4) - p = &b.y; //$raw,ussa=b + p = &b.y; //$raw=b i = *p; //$ussa=*b[4..8) - p = &c->x; //$raw,ussa=c + p = &c->x; //$raw=c i = *p; //$ussa=*c[0..4) - p = &c->y; //$raw,ussa=c + p = &c->y; //$raw=c i = *p; //$ussa=*c[4..8) - p = &d[5]; //$raw,ussa=d + p = &d[5]; //$raw=d i = *p; //$ussa=*d[20..24) - p = &d[a]; //$raw,ussa=d raw,ussa=a + p = &d[a]; //$raw=d raw=a i = *p; //$ussa=*d[?..?) - Point* q = &c[a]; //$raw,ussa=c raw,ussa=a + Point* q = &c[a]; //$raw=c raw=a i = q->x; //$ussa=*c[?..?) i = q->y; //$ussa=*c[?..?) - i = e->b1; //$raw,ussa=e ussa=*e[0..4) - i = e->dsi; //$raw,ussa=e ussa=*e[4..8) - i = f->b1; //$raw,ussa=f ussa=*f[0..4) - i = f->b2; //$raw,ussa=f ussa=*f[4..8) - i = f->dmi; //$raw,ussa=f ussa=*f[8..12) - i = g->b1; //$raw,ussa=g ussa=*g[?..?) - i = g->dvi; //$raw,ussa=g ussa=*g[8..12) + i = e->b1; //$raw=e ussa=*e[0..4) + i = e->dsi; //$raw=e ussa=*e[4..8) + i = f->b1; //$raw=f ussa=*f[0..4) + i = f->b2; //$raw=f ussa=*f[4..8) + i = f->dmi; //$raw=f ussa=*f[8..12) + i = g->b1; //$raw=g ussa=*g[?..?) + i = g->dvi; //$raw=g ussa=*g[8..12) } \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected index 76de50dd792..59505ce6f39 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected @@ -219,11 +219,11 @@ ssa.cpp: #-----| Goto -> Block 1 # 69| Block 1 -# 69| m69_1(char *) = Phi : from 0:m68_8, from 2:m70_6 -# 69| m69_2(int) = Phi : from 0:m68_6, from 2:m69_8 -# 69| m69_3(unknown) = Phi : from 0:~m68_4, from 2:~m70_10 +# 69| m69_1(unknown) = Phi : from 0:~m68_4, from 2:~m70_10 +# 69| m69_2(char *) = Phi : from 0:m68_8, from 2:m70_6 +# 69| m69_3(int) = Phi : from 0:m68_6, from 2:m69_8 # 69| r69_4(glval) = VariableAddress[n] : -# 69| r69_5(int) = Load[n] : &:r69_4, m69_2 +# 69| r69_5(int) = Load[n] : &:r69_4, m69_3 # 69| r69_6(int) = Constant[1] : # 69| r69_7(int) = Sub : r69_5, r69_6 # 69| m69_8(int) = Store[n] : &:r69_4, r69_7 @@ -237,21 +237,21 @@ ssa.cpp: # 70| Block 2 # 70| r70_1(char) = Constant[0] : # 70| r70_2(glval) = VariableAddress[p] : -# 70| r70_3(char *) = Load[p] : &:r70_2, m69_1 +# 70| r70_3(char *) = Load[p] : &:r70_2, m69_2 # 70| r70_4(int) = Constant[1] : # 70| r70_5(char *) = PointerAdd[1] : r70_3, r70_4 # 70| m70_6(char *) = Store[p] : &:r70_2, r70_5 # 70| r70_7(char *) = CopyValue : r70_3 # 70| r70_8(glval) = CopyValue : r70_7 # 70| m70_9(char) = Store[?] : &:r70_8, r70_1 -# 70| m70_10(unknown) = Chi : total:m69_3, partial:m70_9 +# 70| m70_10(unknown) = Chi : total:m69_1, partial:m70_9 #-----| Goto (back edge) -> Block 1 # 71| Block 3 # 71| v71_1(void) = NoOp : # 68| v68_11(void) = ReturnIndirection[p] : &:r68_9, m68_10 # 68| v68_12(void) = ReturnVoid : -# 68| v68_13(void) = AliasedUse : ~m69_3 +# 68| v68_13(void) = AliasedUse : ~m69_1 # 68| v68_14(void) = ExitFunction : # 75| void ScalarPhi(bool) @@ -1502,3 +1502,315 @@ ssa.cpp: # 310| v310_12(void) = ReturnVoid : # 310| v310_13(void) = AliasedUse : m310_3 # 310| v310_14(void) = ExitFunction : + +# 319| void DoubleIndirectionEscapes(char*) +# 319| Block 0 +# 319| v319_1(void) = EnterFunction : +# 319| m319_2(unknown) = AliasedDefinition : +# 319| m319_3(unknown) = InitializeNonLocal : +# 319| m319_4(unknown) = Chi : total:m319_2, partial:m319_3 +# 319| r319_5(glval) = VariableAddress[s] : +# 319| m319_6(char *) = InitializeParameter[s] : &:r319_5 +# 319| r319_7(char *) = Load[s] : &:r319_5, m319_6 +# 319| m319_8(unknown) = InitializeIndirection[s] : &:r319_7 +# 321| r321_1(glval) = VariableAddress[buffer] : +# 321| m321_2(char[1024]) = Uninitialized[buffer] : &:r321_1 +# 321| m321_3(unknown) = Chi : total:m319_4, partial:m321_2 +# 322| r322_1(glval) = VariableAddress[ptr1] : +# 322| m322_2(char *) = Uninitialized[ptr1] : &:r322_1 +# 322| m322_3(unknown) = Chi : total:m321_3, partial:m322_2 +# 322| r322_4(glval) = VariableAddress[ptr2] : +# 322| m322_5(char **) = Uninitialized[ptr2] : &:r322_4 +# 323| r323_1(glval) = VariableAddress[ptr3] : +# 323| m323_2(char *) = Uninitialized[ptr3] : &:r323_1 +# 323| r323_3(glval) = VariableAddress[ptr4] : +# 323| m323_4(char **) = Uninitialized[ptr4] : &:r323_3 +# 325| r325_1(glval) = VariableAddress[buffer] : +# 325| r325_2(char *) = Convert : r325_1 +# 325| r325_3(glval) = VariableAddress[ptr1] : +# 325| m325_4(char *) = Store[ptr1] : &:r325_3, r325_2 +# 325| m325_5(unknown) = Chi : total:m322_3, partial:m325_4 +# 326| r326_1(glval) = VariableAddress[ptr1] : +# 326| r326_2(char **) = CopyValue : r326_1 +# 326| r326_3(glval) = VariableAddress[ptr2] : +# 326| m326_4(char **) = Store[ptr2] : &:r326_3, r326_2 +# 327| r327_1(glval) = FunctionAddress[memcpy] : +# 327| r327_2(glval) = VariableAddress[ptr2] : +# 327| r327_3(char **) = Load[ptr2] : &:r327_2, m326_4 +# 327| r327_4(char *) = Load[?] : &:r327_3, m325_4 +# 327| r327_5(void *) = Convert : r327_4 +# 327| r327_6(glval) = VariableAddress[s] : +# 327| r327_7(char *) = Load[s] : &:r327_6, m319_6 +# 327| r327_8(void *) = Convert : r327_7 +# 327| r327_9(int) = Constant[1024] : +# 327| r327_10(void *) = Call[memcpy] : func:r327_1, 0:r327_5, 1:r327_8, 2:r327_9 +# 327| v327_11(void) = ^SizedBufferReadSideEffect[1] : &:r327_8, r327_9, ~m319_8 +# 327| m327_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r327_5, r327_9 +# 327| m327_13(unknown) = Chi : total:m325_5, partial:m327_12 +# 329| r329_1(glval) = FunctionAddress[sink] : +# 329| r329_2(glval) = VariableAddress[buffer] : +# 329| r329_3(char *) = Convert : r329_2 +# 329| v329_4(void) = Call[sink] : func:r329_1, 0:r329_3 +# 329| m329_5(unknown) = ^CallSideEffect : ~m327_13 +# 329| m329_6(unknown) = Chi : total:m327_13, partial:m329_5 +# 329| v329_7(void) = ^BufferReadSideEffect[0] : &:r329_3, ~m329_6 +# 329| m329_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r329_3 +# 329| m329_9(unknown) = Chi : total:m329_6, partial:m329_8 +# 330| r330_1(glval) = FunctionAddress[sink] : +# 330| r330_2(glval) = VariableAddress[ptr1] : +# 330| r330_3(char *) = Load[ptr1] : &:r330_2, ~m329_6 +# 330| v330_4(void) = Call[sink] : func:r330_1, 0:r330_3 +# 330| m330_5(unknown) = ^CallSideEffect : ~m329_9 +# 330| m330_6(unknown) = Chi : total:m329_9, partial:m330_5 +# 330| v330_7(void) = ^BufferReadSideEffect[0] : &:r330_3, ~m330_6 +# 330| m330_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r330_3 +# 330| m330_9(unknown) = Chi : total:m330_6, partial:m330_8 +# 331| r331_1(glval) = FunctionAddress[sink] : +# 331| r331_2(glval) = VariableAddress[ptr2] : +# 331| r331_3(char **) = Load[ptr2] : &:r331_2, m326_4 +# 331| v331_4(void) = Call[sink] : func:r331_1, 0:r331_3 +# 331| m331_5(unknown) = ^CallSideEffect : ~m330_9 +# 331| m331_6(unknown) = Chi : total:m330_9, partial:m331_5 +# 331| v331_7(void) = ^BufferReadSideEffect[0] : &:r331_3, ~m331_6 +# 331| m331_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r331_3 +# 331| m331_9(unknown) = Chi : total:m331_6, partial:m331_8 +# 332| r332_1(glval) = FunctionAddress[sink] : +# 332| r332_2(glval) = VariableAddress[ptr2] : +# 332| r332_3(char **) = Load[ptr2] : &:r332_2, m326_4 +# 332| r332_4(char *) = Load[?] : &:r332_3, ~m331_9 +# 332| v332_5(void) = Call[sink] : func:r332_1, 0:r332_4 +# 332| m332_6(unknown) = ^CallSideEffect : ~m331_9 +# 332| m332_7(unknown) = Chi : total:m331_9, partial:m332_6 +# 332| v332_8(void) = ^BufferReadSideEffect[0] : &:r332_4, ~m332_7 +# 332| m332_9(unknown) = ^BufferMayWriteSideEffect[0] : &:r332_4 +# 332| m332_10(unknown) = Chi : total:m332_7, partial:m332_9 +# 333| v333_1(void) = NoOp : +# 319| v319_9(void) = ReturnIndirection[s] : &:r319_7, m319_8 +# 319| v319_10(void) = ReturnVoid : +# 319| v319_11(void) = AliasedUse : ~m332_10 +# 319| v319_12(void) = ExitFunction : + +# 335| int UnreachablePhiOperand(int, int) +# 335| Block 0 +# 335| v335_1(void) = EnterFunction : +# 335| m335_2(unknown) = AliasedDefinition : +# 335| m335_3(unknown) = InitializeNonLocal : +# 335| m335_4(unknown) = Chi : total:m335_2, partial:m335_3 +# 335| r335_5(glval) = VariableAddress[x] : +# 335| m335_6(int) = InitializeParameter[x] : &:r335_5 +# 335| r335_7(glval) = VariableAddress[y] : +# 335| m335_8(int) = InitializeParameter[y] : &:r335_7 +# 336| r336_1(glval) = VariableAddress[b] : +# 336| r336_2(bool) = Constant[1] : +# 336| m336_3(bool) = Store[b] : &:r336_1, r336_2 +# 337| r337_1(glval) = VariableAddress[ret] : +# 337| m337_2(int) = Uninitialized[ret] : &:r337_1 +# 339| r339_1(glval) = VariableAddress[b] : +# 339| r339_2(bool) = Load[b] : &:r339_1, m336_3 +# 339| v339_3(void) = ConditionalBranch : r339_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 340| Block 1 +# 340| r340_1(glval) = VariableAddress[x] : +# 340| r340_2(int) = Load[x] : &:r340_1, m335_6 +# 340| r340_3(glval) = VariableAddress[ret] : +# 340| m340_4(int) = Store[ret] : &:r340_3, r340_2 +# 345| r345_1(glval) = VariableAddress[#return] : +# 345| r345_2(glval) = VariableAddress[ret] : +# 345| r345_3(int) = Load[ret] : &:r345_2, m340_4 +# 345| m345_4(int) = Store[#return] : &:r345_1, r345_3 +# 335| r335_9(glval) = VariableAddress[#return] : +# 335| v335_10(void) = ReturnValue : &:r335_9, m345_4 +# 335| v335_11(void) = AliasedUse : m335_3 +# 335| v335_12(void) = ExitFunction : + +# 335| Block 2 +# 335| v335_13(void) = Unreached : + +# 348| int UnreachablePhiOperand2(int, int, int, bool) +# 348| Block 0 +# 348| v348_1(void) = EnterFunction : +# 348| m348_2(unknown) = AliasedDefinition : +# 348| m348_3(unknown) = InitializeNonLocal : +# 348| m348_4(unknown) = Chi : total:m348_2, partial:m348_3 +# 348| r348_5(glval) = VariableAddress[x] : +# 348| m348_6(int) = InitializeParameter[x] : &:r348_5 +# 348| r348_7(glval) = VariableAddress[y] : +# 348| m348_8(int) = InitializeParameter[y] : &:r348_7 +# 348| r348_9(glval) = VariableAddress[z] : +# 348| m348_10(int) = InitializeParameter[z] : &:r348_9 +# 348| r348_11(glval) = VariableAddress[b1] : +# 348| m348_12(bool) = InitializeParameter[b1] : &:r348_11 +# 349| r349_1(glval) = VariableAddress[b2] : +# 349| r349_2(bool) = Constant[1] : +# 349| m349_3(bool) = Store[b2] : &:r349_1, r349_2 +# 350| r350_1(glval) = VariableAddress[ret] : +# 350| m350_2(int) = Uninitialized[ret] : &:r350_1 +# 352| r352_1(glval) = VariableAddress[b1] : +# 352| r352_2(bool) = Load[b1] : &:r352_1, m348_12 +# 352| v352_3(void) = ConditionalBranch : r352_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 353| Block 1 +# 353| r353_1(glval) = VariableAddress[x] : +# 353| r353_2(int) = Load[x] : &:r353_1, m348_6 +# 353| r353_3(glval) = VariableAddress[ret] : +# 353| m353_4(int) = Store[ret] : &:r353_3, r353_2 +#-----| Goto -> Block 4 + +# 355| Block 2 +# 355| r355_1(glval) = VariableAddress[b2] : +# 355| r355_2(bool) = Load[b2] : &:r355_1, m349_3 +# 355| v355_3(void) = ConditionalBranch : r355_2 +#-----| False -> Block 5 +#-----| True -> Block 3 + +# 356| Block 3 +# 356| r356_1(glval) = VariableAddress[y] : +# 356| r356_2(int) = Load[y] : &:r356_1, m348_8 +# 356| r356_3(glval) = VariableAddress[ret] : +# 356| m356_4(int) = Store[ret] : &:r356_3, r356_2 +#-----| Goto -> Block 4 + +# 362| Block 4 +# 362| m362_1(int) = Phi : from 1:m353_4, from 3:m356_4 +# 362| r362_2(glval) = VariableAddress[#return] : +# 362| r362_3(glval) = VariableAddress[ret] : +# 362| r362_4(int) = Load[ret] : &:r362_3, m362_1 +# 362| m362_5(int) = Store[#return] : &:r362_2, r362_4 +# 348| r348_13(glval) = VariableAddress[#return] : +# 348| v348_14(void) = ReturnValue : &:r348_13, m362_5 +# 348| v348_15(void) = AliasedUse : m348_3 +# 348| v348_16(void) = ExitFunction : + +# 348| Block 5 +# 348| v348_17(void) = Unreached : + +# 365| int DegeneratePhi(int, int, bool) +# 365| Block 0 +# 365| v365_1(void) = EnterFunction : +# 365| m365_2(unknown) = AliasedDefinition : +# 365| m365_3(unknown) = InitializeNonLocal : +# 365| m365_4(unknown) = Chi : total:m365_2, partial:m365_3 +# 365| r365_5(glval) = VariableAddress[x] : +# 365| m365_6(int) = InitializeParameter[x] : &:r365_5 +# 365| r365_7(glval) = VariableAddress[y] : +# 365| m365_8(int) = InitializeParameter[y] : &:r365_7 +# 365| r365_9(glval) = VariableAddress[b1] : +# 365| m365_10(bool) = InitializeParameter[b1] : &:r365_9 +# 366| r366_1(glval) = VariableAddress[b2] : +# 366| r366_2(bool) = Constant[1] : +# 366| m366_3(bool) = Store[b2] : &:r366_1, r366_2 +# 367| r367_1(glval) = VariableAddress[ret1] : +# 367| m367_2(int) = Uninitialized[ret1] : &:r367_1 +# 368| r368_1(glval) = VariableAddress[ret2] : +# 368| r368_2(glval) = VariableAddress[x] : +# 368| r368_3(int) = Load[x] : &:r368_2, m365_6 +# 368| m368_4(int) = Store[ret2] : &:r368_1, r368_3 +# 370| r370_1(glval) = VariableAddress[b1] : +# 370| r370_2(bool) = Load[b1] : &:r370_1, m365_10 +# 370| v370_3(void) = ConditionalBranch : r370_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 371| Block 1 +# 371| r371_1(glval) = VariableAddress[x] : +# 371| r371_2(int) = Load[x] : &:r371_1, m365_6 +# 371| r371_3(glval) = VariableAddress[ret1] : +# 371| m371_4(int) = Store[ret1] : &:r371_3, r371_2 +#-----| Goto -> Block 4 + +# 373| Block 2 +# 373| r373_1(glval) = VariableAddress[b2] : +# 373| r373_2(bool) = Load[b2] : &:r373_1, m366_3 +# 373| v373_3(void) = ConditionalBranch : r373_2 +#-----| False -> Block 5 +#-----| True -> Block 3 + +# 374| Block 3 +# 374| r374_1(glval) = VariableAddress[x] : +# 374| r374_2(int) = Load[x] : &:r374_1, m365_6 +# 374| r374_3(glval) = VariableAddress[ret1] : +# 374| m374_4(int) = Store[ret1] : &:r374_3, r374_2 +#-----| Goto -> Block 4 + +# 380| Block 4 +# 380| m380_1(int) = Phi : from 1:m368_4, from 3:m368_4 +# 380| m380_2(int) = Phi : from 1:m371_4, from 3:m374_4 +# 380| r380_3(glval) = VariableAddress[#return] : +# 380| r380_4(glval) = VariableAddress[ret1] : +# 380| r380_5(int) = Load[ret1] : &:r380_4, m380_2 +# 380| r380_6(glval) = VariableAddress[ret2] : +# 380| r380_7(int) = Load[ret2] : &:r380_6, m380_1 +# 380| r380_8(int) = Add : r380_5, r380_7 +# 380| m380_9(int) = Store[#return] : &:r380_3, r380_8 +# 365| r365_11(glval) = VariableAddress[#return] : +# 365| v365_12(void) = ReturnValue : &:r365_11, m380_9 +# 365| v365_13(void) = AliasedUse : m365_3 +# 365| v365_14(void) = ExitFunction : + +# 365| Block 5 +# 365| v365_15(void) = Unreached : + +# 383| int FusedBlockPhiOperand(int, int, int, bool) +# 383| Block 0 +# 383| v383_1(void) = EnterFunction : +# 383| m383_2(unknown) = AliasedDefinition : +# 383| m383_3(unknown) = InitializeNonLocal : +# 383| m383_4(unknown) = Chi : total:m383_2, partial:m383_3 +# 383| r383_5(glval) = VariableAddress[x] : +# 383| m383_6(int) = InitializeParameter[x] : &:r383_5 +# 383| r383_7(glval) = VariableAddress[y] : +# 383| m383_8(int) = InitializeParameter[y] : &:r383_7 +# 383| r383_9(glval) = VariableAddress[z] : +# 383| m383_10(int) = InitializeParameter[z] : &:r383_9 +# 383| r383_11(glval) = VariableAddress[b1] : +# 383| m383_12(bool) = InitializeParameter[b1] : &:r383_11 +# 384| r384_1(glval) = VariableAddress[b2] : +# 384| r384_2(bool) = Constant[1] : +# 384| m384_3(bool) = Store[b2] : &:r384_1, r384_2 +# 385| r385_1(glval) = VariableAddress[ret] : +# 385| m385_2(int) = Uninitialized[ret] : &:r385_1 +# 387| r387_1(glval) = VariableAddress[b1] : +# 387| r387_2(bool) = Load[b1] : &:r387_1, m383_12 +# 387| v387_3(void) = ConditionalBranch : r387_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 388| Block 1 +# 388| r388_1(glval) = VariableAddress[x] : +# 388| r388_2(int) = Load[x] : &:r388_1, m383_6 +# 388| r388_3(glval) = VariableAddress[ret] : +# 388| m388_4(int) = Store[ret] : &:r388_3, r388_2 +#-----| Goto -> Block 4 + +# 390| Block 2 +# 390| r390_1(glval) = VariableAddress[b2] : +# 390| r390_2(bool) = Load[b2] : &:r390_1, m384_3 +# 390| v390_3(void) = ConditionalBranch : r390_2 +#-----| False -> Block 5 +#-----| True -> Block 3 + +# 391| Block 3 +# 391| r391_1(glval) = VariableAddress[y] : +# 391| r391_2(int) = Load[y] : &:r391_1, m383_8 +# 391| r391_3(glval) = VariableAddress[ret] : +# 391| m391_4(int) = Store[ret] : &:r391_3, r391_2 +# 395| v395_1(void) = NoOp : +#-----| Goto -> Block 4 + +# 398| Block 4 +# 398| m398_1(int) = Phi : from 1:m388_4, from 3:m391_4 +# 398| r398_2(glval) = VariableAddress[#return] : +# 398| r398_3(glval) = VariableAddress[ret] : +# 398| r398_4(int) = Load[ret] : &:r398_3, m398_1 +# 398| m398_5(int) = Store[#return] : &:r398_2, r398_4 +# 383| r383_13(glval) = VariableAddress[#return] : +# 383| v383_14(void) = ReturnValue : &:r383_13, m398_5 +# 383| v383_15(void) = AliasedUse : m383_3 +# 383| v383_16(void) = ExitFunction : + +# 383| Block 5 +# 383| v383_17(void) = Unreached : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected index ab79d20214a..b87b65363dc 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected @@ -219,11 +219,11 @@ ssa.cpp: #-----| Goto -> Block 1 # 69| Block 1 -# 69| m69_1(char *) = Phi : from 0:m68_8, from 2:m70_6 -# 69| m69_2(int) = Phi : from 0:m68_6, from 2:m69_8 -# 69| m69_3(unknown) = Phi : from 0:~m68_4, from 2:~m70_10 +# 69| m69_1(unknown) = Phi : from 0:~m68_4, from 2:~m70_10 +# 69| m69_2(char *) = Phi : from 0:m68_8, from 2:m70_6 +# 69| m69_3(int) = Phi : from 0:m68_6, from 2:m69_8 # 69| r69_4(glval) = VariableAddress[n] : -# 69| r69_5(int) = Load[n] : &:r69_4, m69_2 +# 69| r69_5(int) = Load[n] : &:r69_4, m69_3 # 69| r69_6(int) = Constant[1] : # 69| r69_7(int) = Sub : r69_5, r69_6 # 69| m69_8(int) = Store[n] : &:r69_4, r69_7 @@ -237,21 +237,21 @@ ssa.cpp: # 70| Block 2 # 70| r70_1(char) = Constant[0] : # 70| r70_2(glval) = VariableAddress[p] : -# 70| r70_3(char *) = Load[p] : &:r70_2, m69_1 +# 70| r70_3(char *) = Load[p] : &:r70_2, m69_2 # 70| r70_4(int) = Constant[1] : # 70| r70_5(char *) = PointerAdd[1] : r70_3, r70_4 # 70| m70_6(char *) = Store[p] : &:r70_2, r70_5 # 70| r70_7(char *) = CopyValue : r70_3 # 70| r70_8(glval) = CopyValue : r70_7 # 70| m70_9(char) = Store[?] : &:r70_8, r70_1 -# 70| m70_10(unknown) = Chi : total:m69_3, partial:m70_9 +# 70| m70_10(unknown) = Chi : total:m69_1, partial:m70_9 #-----| Goto (back edge) -> Block 1 # 71| Block 3 # 71| v71_1(void) = NoOp : # 68| v68_11(void) = ReturnIndirection[p] : &:r68_9, m68_10 # 68| v68_12(void) = ReturnVoid : -# 68| v68_13(void) = AliasedUse : ~m69_3 +# 68| v68_13(void) = AliasedUse : ~m69_1 # 68| v68_14(void) = ExitFunction : # 75| void ScalarPhi(bool) @@ -1495,3 +1495,312 @@ ssa.cpp: # 310| v310_12(void) = ReturnVoid : # 310| v310_13(void) = AliasedUse : m310_3 # 310| v310_14(void) = ExitFunction : + +# 319| void DoubleIndirectionEscapes(char*) +# 319| Block 0 +# 319| v319_1(void) = EnterFunction : +# 319| m319_2(unknown) = AliasedDefinition : +# 319| m319_3(unknown) = InitializeNonLocal : +# 319| m319_4(unknown) = Chi : total:m319_2, partial:m319_3 +# 319| r319_5(glval) = VariableAddress[s] : +# 319| m319_6(char *) = InitializeParameter[s] : &:r319_5 +# 319| r319_7(char *) = Load[s] : &:r319_5, m319_6 +# 319| m319_8(unknown) = InitializeIndirection[s] : &:r319_7 +# 321| r321_1(glval) = VariableAddress[buffer] : +# 321| m321_2(char[1024]) = Uninitialized[buffer] : &:r321_1 +# 322| r322_1(glval) = VariableAddress[ptr1] : +# 322| m322_2(char *) = Uninitialized[ptr1] : &:r322_1 +# 322| r322_3(glval) = VariableAddress[ptr2] : +# 322| m322_4(char **) = Uninitialized[ptr2] : &:r322_3 +# 323| r323_1(glval) = VariableAddress[ptr3] : +# 323| m323_2(char *) = Uninitialized[ptr3] : &:r323_1 +# 323| r323_3(glval) = VariableAddress[ptr4] : +# 323| m323_4(char **) = Uninitialized[ptr4] : &:r323_3 +# 325| r325_1(glval) = VariableAddress[buffer] : +# 325| r325_2(char *) = Convert : r325_1 +# 325| r325_3(glval) = VariableAddress[ptr1] : +# 325| m325_4(char *) = Store[ptr1] : &:r325_3, r325_2 +# 326| r326_1(glval) = VariableAddress[ptr1] : +# 326| r326_2(char **) = CopyValue : r326_1 +# 326| r326_3(glval) = VariableAddress[ptr2] : +# 326| m326_4(char **) = Store[ptr2] : &:r326_3, r326_2 +# 327| r327_1(glval) = FunctionAddress[memcpy] : +# 327| r327_2(glval) = VariableAddress[ptr2] : +# 327| r327_3(char **) = Load[ptr2] : &:r327_2, m326_4 +# 327| r327_4(char *) = Load[?] : &:r327_3, m325_4 +# 327| r327_5(void *) = Convert : r327_4 +# 327| r327_6(glval) = VariableAddress[s] : +# 327| r327_7(char *) = Load[s] : &:r327_6, m319_6 +# 327| r327_8(void *) = Convert : r327_7 +# 327| r327_9(int) = Constant[1024] : +# 327| r327_10(void *) = Call[memcpy] : func:r327_1, 0:r327_5, 1:r327_8, 2:r327_9 +# 327| v327_11(void) = ^SizedBufferReadSideEffect[1] : &:r327_8, r327_9, ~m319_8 +# 327| m327_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r327_5, r327_9 +# 327| m327_13(unknown) = Chi : total:m319_4, partial:m327_12 +# 329| r329_1(glval) = FunctionAddress[sink] : +# 329| r329_2(glval) = VariableAddress[buffer] : +# 329| r329_3(char *) = Convert : r329_2 +# 329| v329_4(void) = Call[sink] : func:r329_1, 0:r329_3 +# 329| m329_5(unknown) = ^CallSideEffect : ~m327_13 +# 329| m329_6(unknown) = Chi : total:m327_13, partial:m329_5 +# 329| v329_7(void) = ^BufferReadSideEffect[0] : &:r329_3, ~m321_2 +# 329| m329_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r329_3 +# 329| m329_9(char[1024]) = Chi : total:m321_2, partial:m329_8 +# 330| r330_1(glval) = FunctionAddress[sink] : +# 330| r330_2(glval) = VariableAddress[ptr1] : +# 330| r330_3(char *) = Load[ptr1] : &:r330_2, m325_4 +# 330| v330_4(void) = Call[sink] : func:r330_1, 0:r330_3 +# 330| m330_5(unknown) = ^CallSideEffect : ~m329_6 +# 330| m330_6(unknown) = Chi : total:m329_6, partial:m330_5 +# 330| v330_7(void) = ^BufferReadSideEffect[0] : &:r330_3, ~m330_6 +# 330| m330_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r330_3 +# 330| m330_9(unknown) = Chi : total:m330_6, partial:m330_8 +# 331| r331_1(glval) = FunctionAddress[sink] : +# 331| r331_2(glval) = VariableAddress[ptr2] : +# 331| r331_3(char **) = Load[ptr2] : &:r331_2, m326_4 +# 331| v331_4(void) = Call[sink] : func:r331_1, 0:r331_3 +# 331| m331_5(unknown) = ^CallSideEffect : ~m330_9 +# 331| m331_6(unknown) = Chi : total:m330_9, partial:m331_5 +# 331| v331_7(void) = ^BufferReadSideEffect[0] : &:r331_3, ~m325_4 +# 331| m331_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r331_3 +# 331| m331_9(char *) = Chi : total:m325_4, partial:m331_8 +# 332| r332_1(glval) = FunctionAddress[sink] : +# 332| r332_2(glval) = VariableAddress[ptr2] : +# 332| r332_3(char **) = Load[ptr2] : &:r332_2, m326_4 +# 332| r332_4(char *) = Load[?] : &:r332_3, m331_9 +# 332| v332_5(void) = Call[sink] : func:r332_1, 0:r332_4 +# 332| m332_6(unknown) = ^CallSideEffect : ~m331_6 +# 332| m332_7(unknown) = Chi : total:m331_6, partial:m332_6 +# 332| v332_8(void) = ^BufferReadSideEffect[0] : &:r332_4, ~m332_7 +# 332| m332_9(unknown) = ^BufferMayWriteSideEffect[0] : &:r332_4 +# 332| m332_10(unknown) = Chi : total:m332_7, partial:m332_9 +# 333| v333_1(void) = NoOp : +# 319| v319_9(void) = ReturnIndirection[s] : &:r319_7, m319_8 +# 319| v319_10(void) = ReturnVoid : +# 319| v319_11(void) = AliasedUse : ~m332_10 +# 319| v319_12(void) = ExitFunction : + +# 335| int UnreachablePhiOperand(int, int) +# 335| Block 0 +# 335| v335_1(void) = EnterFunction : +# 335| m335_2(unknown) = AliasedDefinition : +# 335| m335_3(unknown) = InitializeNonLocal : +# 335| m335_4(unknown) = Chi : total:m335_2, partial:m335_3 +# 335| r335_5(glval) = VariableAddress[x] : +# 335| m335_6(int) = InitializeParameter[x] : &:r335_5 +# 335| r335_7(glval) = VariableAddress[y] : +# 335| m335_8(int) = InitializeParameter[y] : &:r335_7 +# 336| r336_1(glval) = VariableAddress[b] : +# 336| r336_2(bool) = Constant[1] : +# 336| m336_3(bool) = Store[b] : &:r336_1, r336_2 +# 337| r337_1(glval) = VariableAddress[ret] : +# 337| m337_2(int) = Uninitialized[ret] : &:r337_1 +# 339| r339_1(glval) = VariableAddress[b] : +# 339| r339_2(bool) = Load[b] : &:r339_1, m336_3 +# 339| v339_3(void) = ConditionalBranch : r339_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 340| Block 1 +# 340| r340_1(glval) = VariableAddress[x] : +# 340| r340_2(int) = Load[x] : &:r340_1, m335_6 +# 340| r340_3(glval) = VariableAddress[ret] : +# 340| m340_4(int) = Store[ret] : &:r340_3, r340_2 +# 345| r345_1(glval) = VariableAddress[#return] : +# 345| r345_2(glval) = VariableAddress[ret] : +# 345| r345_3(int) = Load[ret] : &:r345_2, m340_4 +# 345| m345_4(int) = Store[#return] : &:r345_1, r345_3 +# 335| r335_9(glval) = VariableAddress[#return] : +# 335| v335_10(void) = ReturnValue : &:r335_9, m345_4 +# 335| v335_11(void) = AliasedUse : m335_3 +# 335| v335_12(void) = ExitFunction : + +# 335| Block 2 +# 335| v335_13(void) = Unreached : + +# 348| int UnreachablePhiOperand2(int, int, int, bool) +# 348| Block 0 +# 348| v348_1(void) = EnterFunction : +# 348| m348_2(unknown) = AliasedDefinition : +# 348| m348_3(unknown) = InitializeNonLocal : +# 348| m348_4(unknown) = Chi : total:m348_2, partial:m348_3 +# 348| r348_5(glval) = VariableAddress[x] : +# 348| m348_6(int) = InitializeParameter[x] : &:r348_5 +# 348| r348_7(glval) = VariableAddress[y] : +# 348| m348_8(int) = InitializeParameter[y] : &:r348_7 +# 348| r348_9(glval) = VariableAddress[z] : +# 348| m348_10(int) = InitializeParameter[z] : &:r348_9 +# 348| r348_11(glval) = VariableAddress[b1] : +# 348| m348_12(bool) = InitializeParameter[b1] : &:r348_11 +# 349| r349_1(glval) = VariableAddress[b2] : +# 349| r349_2(bool) = Constant[1] : +# 349| m349_3(bool) = Store[b2] : &:r349_1, r349_2 +# 350| r350_1(glval) = VariableAddress[ret] : +# 350| m350_2(int) = Uninitialized[ret] : &:r350_1 +# 352| r352_1(glval) = VariableAddress[b1] : +# 352| r352_2(bool) = Load[b1] : &:r352_1, m348_12 +# 352| v352_3(void) = ConditionalBranch : r352_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 353| Block 1 +# 353| r353_1(glval) = VariableAddress[x] : +# 353| r353_2(int) = Load[x] : &:r353_1, m348_6 +# 353| r353_3(glval) = VariableAddress[ret] : +# 353| m353_4(int) = Store[ret] : &:r353_3, r353_2 +#-----| Goto -> Block 4 + +# 355| Block 2 +# 355| r355_1(glval) = VariableAddress[b2] : +# 355| r355_2(bool) = Load[b2] : &:r355_1, m349_3 +# 355| v355_3(void) = ConditionalBranch : r355_2 +#-----| False -> Block 5 +#-----| True -> Block 3 + +# 356| Block 3 +# 356| r356_1(glval) = VariableAddress[y] : +# 356| r356_2(int) = Load[y] : &:r356_1, m348_8 +# 356| r356_3(glval) = VariableAddress[ret] : +# 356| m356_4(int) = Store[ret] : &:r356_3, r356_2 +#-----| Goto -> Block 4 + +# 362| Block 4 +# 362| m362_1(int) = Phi : from 1:m353_4, from 3:m356_4 +# 362| r362_2(glval) = VariableAddress[#return] : +# 362| r362_3(glval) = VariableAddress[ret] : +# 362| r362_4(int) = Load[ret] : &:r362_3, m362_1 +# 362| m362_5(int) = Store[#return] : &:r362_2, r362_4 +# 348| r348_13(glval) = VariableAddress[#return] : +# 348| v348_14(void) = ReturnValue : &:r348_13, m362_5 +# 348| v348_15(void) = AliasedUse : m348_3 +# 348| v348_16(void) = ExitFunction : + +# 348| Block 5 +# 348| v348_17(void) = Unreached : + +# 365| int DegeneratePhi(int, int, bool) +# 365| Block 0 +# 365| v365_1(void) = EnterFunction : +# 365| m365_2(unknown) = AliasedDefinition : +# 365| m365_3(unknown) = InitializeNonLocal : +# 365| m365_4(unknown) = Chi : total:m365_2, partial:m365_3 +# 365| r365_5(glval) = VariableAddress[x] : +# 365| m365_6(int) = InitializeParameter[x] : &:r365_5 +# 365| r365_7(glval) = VariableAddress[y] : +# 365| m365_8(int) = InitializeParameter[y] : &:r365_7 +# 365| r365_9(glval) = VariableAddress[b1] : +# 365| m365_10(bool) = InitializeParameter[b1] : &:r365_9 +# 366| r366_1(glval) = VariableAddress[b2] : +# 366| r366_2(bool) = Constant[1] : +# 366| m366_3(bool) = Store[b2] : &:r366_1, r366_2 +# 367| r367_1(glval) = VariableAddress[ret1] : +# 367| m367_2(int) = Uninitialized[ret1] : &:r367_1 +# 368| r368_1(glval) = VariableAddress[ret2] : +# 368| r368_2(glval) = VariableAddress[x] : +# 368| r368_3(int) = Load[x] : &:r368_2, m365_6 +# 368| m368_4(int) = Store[ret2] : &:r368_1, r368_3 +# 370| r370_1(glval) = VariableAddress[b1] : +# 370| r370_2(bool) = Load[b1] : &:r370_1, m365_10 +# 370| v370_3(void) = ConditionalBranch : r370_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 371| Block 1 +# 371| r371_1(glval) = VariableAddress[x] : +# 371| r371_2(int) = Load[x] : &:r371_1, m365_6 +# 371| r371_3(glval) = VariableAddress[ret1] : +# 371| m371_4(int) = Store[ret1] : &:r371_3, r371_2 +#-----| Goto -> Block 4 + +# 373| Block 2 +# 373| r373_1(glval) = VariableAddress[b2] : +# 373| r373_2(bool) = Load[b2] : &:r373_1, m366_3 +# 373| v373_3(void) = ConditionalBranch : r373_2 +#-----| False -> Block 5 +#-----| True -> Block 3 + +# 374| Block 3 +# 374| r374_1(glval) = VariableAddress[x] : +# 374| r374_2(int) = Load[x] : &:r374_1, m365_6 +# 374| r374_3(glval) = VariableAddress[ret1] : +# 374| m374_4(int) = Store[ret1] : &:r374_3, r374_2 +#-----| Goto -> Block 4 + +# 380| Block 4 +# 380| m380_1(int) = Phi : from 1:m368_4, from 3:m368_4 +# 380| m380_2(int) = Phi : from 1:m371_4, from 3:m374_4 +# 380| r380_3(glval) = VariableAddress[#return] : +# 380| r380_4(glval) = VariableAddress[ret1] : +# 380| r380_5(int) = Load[ret1] : &:r380_4, m380_2 +# 380| r380_6(glval) = VariableAddress[ret2] : +# 380| r380_7(int) = Load[ret2] : &:r380_6, m380_1 +# 380| r380_8(int) = Add : r380_5, r380_7 +# 380| m380_9(int) = Store[#return] : &:r380_3, r380_8 +# 365| r365_11(glval) = VariableAddress[#return] : +# 365| v365_12(void) = ReturnValue : &:r365_11, m380_9 +# 365| v365_13(void) = AliasedUse : m365_3 +# 365| v365_14(void) = ExitFunction : + +# 365| Block 5 +# 365| v365_15(void) = Unreached : + +# 383| int FusedBlockPhiOperand(int, int, int, bool) +# 383| Block 0 +# 383| v383_1(void) = EnterFunction : +# 383| m383_2(unknown) = AliasedDefinition : +# 383| m383_3(unknown) = InitializeNonLocal : +# 383| m383_4(unknown) = Chi : total:m383_2, partial:m383_3 +# 383| r383_5(glval) = VariableAddress[x] : +# 383| m383_6(int) = InitializeParameter[x] : &:r383_5 +# 383| r383_7(glval) = VariableAddress[y] : +# 383| m383_8(int) = InitializeParameter[y] : &:r383_7 +# 383| r383_9(glval) = VariableAddress[z] : +# 383| m383_10(int) = InitializeParameter[z] : &:r383_9 +# 383| r383_11(glval) = VariableAddress[b1] : +# 383| m383_12(bool) = InitializeParameter[b1] : &:r383_11 +# 384| r384_1(glval) = VariableAddress[b2] : +# 384| r384_2(bool) = Constant[1] : +# 384| m384_3(bool) = Store[b2] : &:r384_1, r384_2 +# 385| r385_1(glval) = VariableAddress[ret] : +# 385| m385_2(int) = Uninitialized[ret] : &:r385_1 +# 387| r387_1(glval) = VariableAddress[b1] : +# 387| r387_2(bool) = Load[b1] : &:r387_1, m383_12 +# 387| v387_3(void) = ConditionalBranch : r387_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 388| Block 1 +# 388| r388_1(glval) = VariableAddress[x] : +# 388| r388_2(int) = Load[x] : &:r388_1, m383_6 +# 388| r388_3(glval) = VariableAddress[ret] : +# 388| m388_4(int) = Store[ret] : &:r388_3, r388_2 +#-----| Goto -> Block 4 + +# 390| Block 2 +# 390| r390_1(glval) = VariableAddress[b2] : +# 390| r390_2(bool) = Load[b2] : &:r390_1, m384_3 +# 390| v390_3(void) = ConditionalBranch : r390_2 +#-----| False -> Block 5 +#-----| True -> Block 3 + +# 391| Block 3 +# 391| r391_1(glval) = VariableAddress[y] : +# 391| r391_2(int) = Load[y] : &:r391_1, m383_8 +# 391| r391_3(glval) = VariableAddress[ret] : +# 391| m391_4(int) = Store[ret] : &:r391_3, r391_2 +# 395| v395_1(void) = NoOp : +#-----| Goto -> Block 4 + +# 398| Block 4 +# 398| m398_1(int) = Phi : from 1:m388_4, from 3:m391_4 +# 398| r398_2(glval) = VariableAddress[#return] : +# 398| r398_3(glval) = VariableAddress[ret] : +# 398| r398_4(int) = Load[ret] : &:r398_3, m398_1 +# 398| m398_5(int) = Store[#return] : &:r398_2, r398_4 +# 383| r383_13(glval) = VariableAddress[#return] : +# 383| v383_14(void) = ReturnValue : &:r383_13, m398_5 +# 383| v383_15(void) = AliasedUse : m383_3 +# 383| v383_16(void) = ExitFunction : + +# 383| Block 5 +# 383| v383_17(void) = Unreached : diff --git a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp index 34886b1f343..ec8ea81e9e4 100644 --- a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp +++ b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp @@ -311,3 +311,89 @@ class ThisAliasTest { this->x = arg; } }; + +void sink(char **); +void sink(char *); + +// This test case comes from DefaultTaintTracking. +void DoubleIndirectionEscapes(char *s) +{ + char buffer[1024]; + char *ptr1, **ptr2; + char *ptr3, **ptr4; + + ptr1 = buffer; + ptr2 = &ptr1; + memcpy(*ptr2, s, 1024); + + sink(buffer); // $ MISSING: ast,ir + sink(ptr1); // $ ast MISSING: ir + sink(ptr2); // $ SPURIOUS: ast + sink(*ptr2); // $ ast MISSING: ir +} + +int UnreachablePhiOperand(int x, int y) { + bool b = true; + int ret; + + if(b) { + ret = x; + } else { + ret = y; + } + + return ret; +} + +int UnreachablePhiOperand2(int x, int y, int z, bool b1) { + bool b2 = true; + int ret; + + if(b1) { + ret = x; + } else { + if(b2) { + ret = y; + } else { + ret = z; + } + } + + return ret; +} + +int DegeneratePhi(int x, int y, bool b1) { + bool b2 = true; + int ret1; + int ret2 = x; + + if(b1) { + ret1 = x; + } else { + if(b2) { + ret1 = x; + } else { + ret2 = y; + } + } + + return ret1 + ret2; +} + +int FusedBlockPhiOperand(int x, int y, int z, bool b1) { + bool b2 = true; + int ret; + + if(b1) { + ret = x; + } else { + if(b2) { + ret = y; + } else { + ret = z; + } + ; // creates a NoOp instruction with its own basic block + } + + return ret; +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected index 0b257db98a4..9b1ebd7fe6e 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected @@ -1376,3 +1376,322 @@ ssa.cpp: # 310| v310_11(void) = ReturnVoid : # 310| v310_12(void) = AliasedUse : ~m? # 310| v310_13(void) = ExitFunction : + +# 319| void DoubleIndirectionEscapes(char*) +# 319| Block 0 +# 319| v319_1(void) = EnterFunction : +# 319| mu319_2(unknown) = AliasedDefinition : +# 319| mu319_3(unknown) = InitializeNonLocal : +# 319| r319_4(glval) = VariableAddress[s] : +# 319| m319_5(char *) = InitializeParameter[s] : &:r319_4 +# 319| r319_6(char *) = Load[s] : &:r319_4, m319_5 +# 319| mu319_7(unknown) = InitializeIndirection[s] : &:r319_6 +# 321| r321_1(glval) = VariableAddress[buffer] : +# 321| mu321_2(char[1024]) = Uninitialized[buffer] : &:r321_1 +# 322| r322_1(glval) = VariableAddress[ptr1] : +# 322| mu322_2(char *) = Uninitialized[ptr1] : &:r322_1 +# 322| r322_3(glval) = VariableAddress[ptr2] : +# 322| m322_4(char **) = Uninitialized[ptr2] : &:r322_3 +# 323| r323_1(glval) = VariableAddress[ptr3] : +# 323| m323_2(char *) = Uninitialized[ptr3] : &:r323_1 +# 323| r323_3(glval) = VariableAddress[ptr4] : +# 323| m323_4(char **) = Uninitialized[ptr4] : &:r323_3 +# 325| r325_1(glval) = VariableAddress[buffer] : +# 325| r325_2(char *) = Convert : r325_1 +# 325| r325_3(glval) = VariableAddress[ptr1] : +# 325| mu325_4(char *) = Store[ptr1] : &:r325_3, r325_2 +# 326| r326_1(glval) = VariableAddress[ptr1] : +# 326| r326_2(char **) = CopyValue : r326_1 +# 326| r326_3(glval) = VariableAddress[ptr2] : +# 326| m326_4(char **) = Store[ptr2] : &:r326_3, r326_2 +# 327| r327_1(glval) = FunctionAddress[memcpy] : +# 327| r327_2(glval) = VariableAddress[ptr2] : +# 327| r327_3(char **) = Load[ptr2] : &:r327_2, m326_4 +# 327| r327_4(char *) = Load[?] : &:r327_3, ~m? +# 327| r327_5(void *) = Convert : r327_4 +# 327| r327_6(glval) = VariableAddress[s] : +# 327| r327_7(char *) = Load[s] : &:r327_6, m319_5 +# 327| r327_8(void *) = Convert : r327_7 +# 327| r327_9(int) = Constant[1024] : +# 327| r327_10(void *) = Call[memcpy] : func:r327_1, 0:r327_5, 1:r327_8, 2:r327_9 +# 327| v327_11(void) = ^SizedBufferReadSideEffect[1] : &:r327_8, r327_9, ~m? +# 327| mu327_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r327_5, r327_9 +# 329| r329_1(glval) = FunctionAddress[sink] : +# 329| r329_2(glval) = VariableAddress[buffer] : +# 329| r329_3(char *) = Convert : r329_2 +# 329| v329_4(void) = Call[sink] : func:r329_1, 0:r329_3 +# 329| mu329_5(unknown) = ^CallSideEffect : ~m? +# 329| v329_6(void) = ^BufferReadSideEffect[0] : &:r329_3, ~m? +# 329| mu329_7(unknown) = ^BufferMayWriteSideEffect[0] : &:r329_3 +# 330| r330_1(glval) = FunctionAddress[sink] : +# 330| r330_2(glval) = VariableAddress[ptr1] : +# 330| r330_3(char *) = Load[ptr1] : &:r330_2, ~m? +# 330| v330_4(void) = Call[sink] : func:r330_1, 0:r330_3 +# 330| mu330_5(unknown) = ^CallSideEffect : ~m? +# 330| v330_6(void) = ^BufferReadSideEffect[0] : &:r330_3, ~m? +# 330| mu330_7(unknown) = ^BufferMayWriteSideEffect[0] : &:r330_3 +# 331| r331_1(glval) = FunctionAddress[sink] : +# 331| r331_2(glval) = VariableAddress[ptr2] : +# 331| r331_3(char **) = Load[ptr2] : &:r331_2, m326_4 +# 331| v331_4(void) = Call[sink] : func:r331_1, 0:r331_3 +# 331| mu331_5(unknown) = ^CallSideEffect : ~m? +# 331| v331_6(void) = ^BufferReadSideEffect[0] : &:r331_3, ~m? +# 331| mu331_7(unknown) = ^BufferMayWriteSideEffect[0] : &:r331_3 +# 332| r332_1(glval) = FunctionAddress[sink] : +# 332| r332_2(glval) = VariableAddress[ptr2] : +# 332| r332_3(char **) = Load[ptr2] : &:r332_2, m326_4 +# 332| r332_4(char *) = Load[?] : &:r332_3, ~m? +# 332| v332_5(void) = Call[sink] : func:r332_1, 0:r332_4 +# 332| mu332_6(unknown) = ^CallSideEffect : ~m? +# 332| v332_7(void) = ^BufferReadSideEffect[0] : &:r332_4, ~m? +# 332| mu332_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r332_4 +# 333| v333_1(void) = NoOp : +# 319| v319_8(void) = ReturnIndirection[s] : &:r319_6, ~m? +# 319| v319_9(void) = ReturnVoid : +# 319| v319_10(void) = AliasedUse : ~m? +# 319| v319_11(void) = ExitFunction : + +# 335| int UnreachablePhiOperand(int, int) +# 335| Block 0 +# 335| v335_1(void) = EnterFunction : +# 335| mu335_2(unknown) = AliasedDefinition : +# 335| mu335_3(unknown) = InitializeNonLocal : +# 335| r335_4(glval) = VariableAddress[x] : +# 335| m335_5(int) = InitializeParameter[x] : &:r335_4 +# 335| r335_6(glval) = VariableAddress[y] : +# 335| m335_7(int) = InitializeParameter[y] : &:r335_6 +# 336| r336_1(glval) = VariableAddress[b] : +# 336| r336_2(bool) = Constant[1] : +# 336| m336_3(bool) = Store[b] : &:r336_1, r336_2 +# 337| r337_1(glval) = VariableAddress[ret] : +# 337| m337_2(int) = Uninitialized[ret] : &:r337_1 +# 339| r339_1(glval) = VariableAddress[b] : +# 339| r339_2(bool) = Load[b] : &:r339_1, m336_3 +# 339| v339_3(void) = ConditionalBranch : r339_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 340| Block 1 +# 340| r340_1(glval) = VariableAddress[x] : +# 340| r340_2(int) = Load[x] : &:r340_1, m335_5 +# 340| r340_3(glval) = VariableAddress[ret] : +# 340| m340_4(int) = Store[ret] : &:r340_3, r340_2 +#-----| Goto -> Block 3 + +# 342| Block 2 +# 342| r342_1(glval) = VariableAddress[y] : +# 342| r342_2(int) = Load[y] : &:r342_1, m335_7 +# 342| r342_3(glval) = VariableAddress[ret] : +# 342| m342_4(int) = Store[ret] : &:r342_3, r342_2 +#-----| Goto -> Block 3 + +# 345| Block 3 +# 345| m345_1(int) = Phi : from 1:m340_4, from 2:m342_4 +# 345| r345_2(glval) = VariableAddress[#return] : +# 345| r345_3(glval) = VariableAddress[ret] : +# 345| r345_4(int) = Load[ret] : &:r345_3, m345_1 +# 345| m345_5(int) = Store[#return] : &:r345_2, r345_4 +# 335| r335_8(glval) = VariableAddress[#return] : +# 335| v335_9(void) = ReturnValue : &:r335_8, m345_5 +# 335| v335_10(void) = AliasedUse : ~m? +# 335| v335_11(void) = ExitFunction : + +# 348| int UnreachablePhiOperand2(int, int, int, bool) +# 348| Block 0 +# 348| v348_1(void) = EnterFunction : +# 348| mu348_2(unknown) = AliasedDefinition : +# 348| mu348_3(unknown) = InitializeNonLocal : +# 348| r348_4(glval) = VariableAddress[x] : +# 348| m348_5(int) = InitializeParameter[x] : &:r348_4 +# 348| r348_6(glval) = VariableAddress[y] : +# 348| m348_7(int) = InitializeParameter[y] : &:r348_6 +# 348| r348_8(glval) = VariableAddress[z] : +# 348| m348_9(int) = InitializeParameter[z] : &:r348_8 +# 348| r348_10(glval) = VariableAddress[b1] : +# 348| m348_11(bool) = InitializeParameter[b1] : &:r348_10 +# 349| r349_1(glval) = VariableAddress[b2] : +# 349| r349_2(bool) = Constant[1] : +# 349| m349_3(bool) = Store[b2] : &:r349_1, r349_2 +# 350| r350_1(glval) = VariableAddress[ret] : +# 350| m350_2(int) = Uninitialized[ret] : &:r350_1 +# 352| r352_1(glval) = VariableAddress[b1] : +# 352| r352_2(bool) = Load[b1] : &:r352_1, m348_11 +# 352| v352_3(void) = ConditionalBranch : r352_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 353| Block 1 +# 353| r353_1(glval) = VariableAddress[x] : +# 353| r353_2(int) = Load[x] : &:r353_1, m348_5 +# 353| r353_3(glval) = VariableAddress[ret] : +# 353| m353_4(int) = Store[ret] : &:r353_3, r353_2 +#-----| Goto -> Block 5 + +# 355| Block 2 +# 355| r355_1(glval) = VariableAddress[b2] : +# 355| r355_2(bool) = Load[b2] : &:r355_1, m349_3 +# 355| v355_3(void) = ConditionalBranch : r355_2 +#-----| False -> Block 4 +#-----| True -> Block 3 + +# 356| Block 3 +# 356| r356_1(glval) = VariableAddress[y] : +# 356| r356_2(int) = Load[y] : &:r356_1, m348_7 +# 356| r356_3(glval) = VariableAddress[ret] : +# 356| m356_4(int) = Store[ret] : &:r356_3, r356_2 +#-----| Goto -> Block 5 + +# 358| Block 4 +# 358| r358_1(glval) = VariableAddress[z] : +# 358| r358_2(int) = Load[z] : &:r358_1, m348_9 +# 358| r358_3(glval) = VariableAddress[ret] : +# 358| m358_4(int) = Store[ret] : &:r358_3, r358_2 +#-----| Goto -> Block 5 + +# 362| Block 5 +# 362| m362_1(int) = Phi : from 1:m353_4, from 3:m356_4, from 4:m358_4 +# 362| r362_2(glval) = VariableAddress[#return] : +# 362| r362_3(glval) = VariableAddress[ret] : +# 362| r362_4(int) = Load[ret] : &:r362_3, m362_1 +# 362| m362_5(int) = Store[#return] : &:r362_2, r362_4 +# 348| r348_12(glval) = VariableAddress[#return] : +# 348| v348_13(void) = ReturnValue : &:r348_12, m362_5 +# 348| v348_14(void) = AliasedUse : ~m? +# 348| v348_15(void) = ExitFunction : + +# 365| int DegeneratePhi(int, int, bool) +# 365| Block 0 +# 365| v365_1(void) = EnterFunction : +# 365| mu365_2(unknown) = AliasedDefinition : +# 365| mu365_3(unknown) = InitializeNonLocal : +# 365| r365_4(glval) = VariableAddress[x] : +# 365| m365_5(int) = InitializeParameter[x] : &:r365_4 +# 365| r365_6(glval) = VariableAddress[y] : +# 365| m365_7(int) = InitializeParameter[y] : &:r365_6 +# 365| r365_8(glval) = VariableAddress[b1] : +# 365| m365_9(bool) = InitializeParameter[b1] : &:r365_8 +# 366| r366_1(glval) = VariableAddress[b2] : +# 366| r366_2(bool) = Constant[1] : +# 366| m366_3(bool) = Store[b2] : &:r366_1, r366_2 +# 367| r367_1(glval) = VariableAddress[ret1] : +# 367| m367_2(int) = Uninitialized[ret1] : &:r367_1 +# 368| r368_1(glval) = VariableAddress[ret2] : +# 368| r368_2(glval) = VariableAddress[x] : +# 368| r368_3(int) = Load[x] : &:r368_2, m365_5 +# 368| m368_4(int) = Store[ret2] : &:r368_1, r368_3 +# 370| r370_1(glval) = VariableAddress[b1] : +# 370| r370_2(bool) = Load[b1] : &:r370_1, m365_9 +# 370| v370_3(void) = ConditionalBranch : r370_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 371| Block 1 +# 371| r371_1(glval) = VariableAddress[x] : +# 371| r371_2(int) = Load[x] : &:r371_1, m365_5 +# 371| r371_3(glval) = VariableAddress[ret1] : +# 371| m371_4(int) = Store[ret1] : &:r371_3, r371_2 +#-----| Goto -> Block 5 + +# 373| Block 2 +# 373| r373_1(glval) = VariableAddress[b2] : +# 373| r373_2(bool) = Load[b2] : &:r373_1, m366_3 +# 373| v373_3(void) = ConditionalBranch : r373_2 +#-----| False -> Block 4 +#-----| True -> Block 3 + +# 374| Block 3 +# 374| r374_1(glval) = VariableAddress[x] : +# 374| r374_2(int) = Load[x] : &:r374_1, m365_5 +# 374| r374_3(glval) = VariableAddress[ret1] : +# 374| m374_4(int) = Store[ret1] : &:r374_3, r374_2 +#-----| Goto -> Block 5 + +# 376| Block 4 +# 376| r376_1(glval) = VariableAddress[y] : +# 376| r376_2(int) = Load[y] : &:r376_1, m365_7 +# 376| r376_3(glval) = VariableAddress[ret2] : +# 376| m376_4(int) = Store[ret2] : &:r376_3, r376_2 +#-----| Goto -> Block 5 + +# 380| Block 5 +# 380| m380_1(int) = Phi : from 1:m368_4, from 3:m368_4, from 4:m376_4 +# 380| m380_2(int) = Phi : from 1:m371_4, from 3:m374_4, from 4:m367_2 +# 380| r380_3(glval) = VariableAddress[#return] : +# 380| r380_4(glval) = VariableAddress[ret1] : +# 380| r380_5(int) = Load[ret1] : &:r380_4, m380_2 +# 380| r380_6(glval) = VariableAddress[ret2] : +# 380| r380_7(int) = Load[ret2] : &:r380_6, m380_1 +# 380| r380_8(int) = Add : r380_5, r380_7 +# 380| m380_9(int) = Store[#return] : &:r380_3, r380_8 +# 365| r365_10(glval) = VariableAddress[#return] : +# 365| v365_11(void) = ReturnValue : &:r365_10, m380_9 +# 365| v365_12(void) = AliasedUse : ~m? +# 365| v365_13(void) = ExitFunction : + +# 383| int FusedBlockPhiOperand(int, int, int, bool) +# 383| Block 0 +# 383| v383_1(void) = EnterFunction : +# 383| mu383_2(unknown) = AliasedDefinition : +# 383| mu383_3(unknown) = InitializeNonLocal : +# 383| r383_4(glval) = VariableAddress[x] : +# 383| m383_5(int) = InitializeParameter[x] : &:r383_4 +# 383| r383_6(glval) = VariableAddress[y] : +# 383| m383_7(int) = InitializeParameter[y] : &:r383_6 +# 383| r383_8(glval) = VariableAddress[z] : +# 383| m383_9(int) = InitializeParameter[z] : &:r383_8 +# 383| r383_10(glval) = VariableAddress[b1] : +# 383| m383_11(bool) = InitializeParameter[b1] : &:r383_10 +# 384| r384_1(glval) = VariableAddress[b2] : +# 384| r384_2(bool) = Constant[1] : +# 384| m384_3(bool) = Store[b2] : &:r384_1, r384_2 +# 385| r385_1(glval) = VariableAddress[ret] : +# 385| m385_2(int) = Uninitialized[ret] : &:r385_1 +# 387| r387_1(glval) = VariableAddress[b1] : +# 387| r387_2(bool) = Load[b1] : &:r387_1, m383_11 +# 387| v387_3(void) = ConditionalBranch : r387_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 388| Block 1 +# 388| r388_1(glval) = VariableAddress[x] : +# 388| r388_2(int) = Load[x] : &:r388_1, m383_5 +# 388| r388_3(glval) = VariableAddress[ret] : +# 388| m388_4(int) = Store[ret] : &:r388_3, r388_2 +#-----| Goto -> Block 6 + +# 390| Block 2 +# 390| r390_1(glval) = VariableAddress[b2] : +# 390| r390_2(bool) = Load[b2] : &:r390_1, m384_3 +# 390| v390_3(void) = ConditionalBranch : r390_2 +#-----| False -> Block 4 +#-----| True -> Block 3 + +# 391| Block 3 +# 391| r391_1(glval) = VariableAddress[y] : +# 391| r391_2(int) = Load[y] : &:r391_1, m383_7 +# 391| r391_3(glval) = VariableAddress[ret] : +# 391| m391_4(int) = Store[ret] : &:r391_3, r391_2 +#-----| Goto -> Block 5 + +# 393| Block 4 +# 393| r393_1(glval) = VariableAddress[z] : +# 393| r393_2(int) = Load[z] : &:r393_1, m383_9 +# 393| r393_3(glval) = VariableAddress[ret] : +# 393| m393_4(int) = Store[ret] : &:r393_3, r393_2 +#-----| Goto -> Block 5 + +# 395| Block 5 +# 395| m395_1(int) = Phi : from 3:m391_4, from 4:m393_4 +# 395| v395_2(void) = NoOp : +#-----| Goto -> Block 6 + +# 398| Block 6 +# 398| m398_1(int) = Phi : from 1:m388_4, from 5:m395_1 +# 398| r398_2(glval) = VariableAddress[#return] : +# 398| r398_3(glval) = VariableAddress[ret] : +# 398| r398_4(int) = Load[ret] : &:r398_3, m398_1 +# 398| m398_5(int) = Store[#return] : &:r398_2, r398_4 +# 383| r383_12(glval) = VariableAddress[#return] : +# 383| v383_13(void) = ReturnValue : &:r383_12, m398_5 +# 383| v383_14(void) = AliasedUse : ~m? +# 383| v383_15(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected index 0b257db98a4..9b1ebd7fe6e 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected @@ -1376,3 +1376,322 @@ ssa.cpp: # 310| v310_11(void) = ReturnVoid : # 310| v310_12(void) = AliasedUse : ~m? # 310| v310_13(void) = ExitFunction : + +# 319| void DoubleIndirectionEscapes(char*) +# 319| Block 0 +# 319| v319_1(void) = EnterFunction : +# 319| mu319_2(unknown) = AliasedDefinition : +# 319| mu319_3(unknown) = InitializeNonLocal : +# 319| r319_4(glval) = VariableAddress[s] : +# 319| m319_5(char *) = InitializeParameter[s] : &:r319_4 +# 319| r319_6(char *) = Load[s] : &:r319_4, m319_5 +# 319| mu319_7(unknown) = InitializeIndirection[s] : &:r319_6 +# 321| r321_1(glval) = VariableAddress[buffer] : +# 321| mu321_2(char[1024]) = Uninitialized[buffer] : &:r321_1 +# 322| r322_1(glval) = VariableAddress[ptr1] : +# 322| mu322_2(char *) = Uninitialized[ptr1] : &:r322_1 +# 322| r322_3(glval) = VariableAddress[ptr2] : +# 322| m322_4(char **) = Uninitialized[ptr2] : &:r322_3 +# 323| r323_1(glval) = VariableAddress[ptr3] : +# 323| m323_2(char *) = Uninitialized[ptr3] : &:r323_1 +# 323| r323_3(glval) = VariableAddress[ptr4] : +# 323| m323_4(char **) = Uninitialized[ptr4] : &:r323_3 +# 325| r325_1(glval) = VariableAddress[buffer] : +# 325| r325_2(char *) = Convert : r325_1 +# 325| r325_3(glval) = VariableAddress[ptr1] : +# 325| mu325_4(char *) = Store[ptr1] : &:r325_3, r325_2 +# 326| r326_1(glval) = VariableAddress[ptr1] : +# 326| r326_2(char **) = CopyValue : r326_1 +# 326| r326_3(glval) = VariableAddress[ptr2] : +# 326| m326_4(char **) = Store[ptr2] : &:r326_3, r326_2 +# 327| r327_1(glval) = FunctionAddress[memcpy] : +# 327| r327_2(glval) = VariableAddress[ptr2] : +# 327| r327_3(char **) = Load[ptr2] : &:r327_2, m326_4 +# 327| r327_4(char *) = Load[?] : &:r327_3, ~m? +# 327| r327_5(void *) = Convert : r327_4 +# 327| r327_6(glval) = VariableAddress[s] : +# 327| r327_7(char *) = Load[s] : &:r327_6, m319_5 +# 327| r327_8(void *) = Convert : r327_7 +# 327| r327_9(int) = Constant[1024] : +# 327| r327_10(void *) = Call[memcpy] : func:r327_1, 0:r327_5, 1:r327_8, 2:r327_9 +# 327| v327_11(void) = ^SizedBufferReadSideEffect[1] : &:r327_8, r327_9, ~m? +# 327| mu327_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r327_5, r327_9 +# 329| r329_1(glval) = FunctionAddress[sink] : +# 329| r329_2(glval) = VariableAddress[buffer] : +# 329| r329_3(char *) = Convert : r329_2 +# 329| v329_4(void) = Call[sink] : func:r329_1, 0:r329_3 +# 329| mu329_5(unknown) = ^CallSideEffect : ~m? +# 329| v329_6(void) = ^BufferReadSideEffect[0] : &:r329_3, ~m? +# 329| mu329_7(unknown) = ^BufferMayWriteSideEffect[0] : &:r329_3 +# 330| r330_1(glval) = FunctionAddress[sink] : +# 330| r330_2(glval) = VariableAddress[ptr1] : +# 330| r330_3(char *) = Load[ptr1] : &:r330_2, ~m? +# 330| v330_4(void) = Call[sink] : func:r330_1, 0:r330_3 +# 330| mu330_5(unknown) = ^CallSideEffect : ~m? +# 330| v330_6(void) = ^BufferReadSideEffect[0] : &:r330_3, ~m? +# 330| mu330_7(unknown) = ^BufferMayWriteSideEffect[0] : &:r330_3 +# 331| r331_1(glval) = FunctionAddress[sink] : +# 331| r331_2(glval) = VariableAddress[ptr2] : +# 331| r331_3(char **) = Load[ptr2] : &:r331_2, m326_4 +# 331| v331_4(void) = Call[sink] : func:r331_1, 0:r331_3 +# 331| mu331_5(unknown) = ^CallSideEffect : ~m? +# 331| v331_6(void) = ^BufferReadSideEffect[0] : &:r331_3, ~m? +# 331| mu331_7(unknown) = ^BufferMayWriteSideEffect[0] : &:r331_3 +# 332| r332_1(glval) = FunctionAddress[sink] : +# 332| r332_2(glval) = VariableAddress[ptr2] : +# 332| r332_3(char **) = Load[ptr2] : &:r332_2, m326_4 +# 332| r332_4(char *) = Load[?] : &:r332_3, ~m? +# 332| v332_5(void) = Call[sink] : func:r332_1, 0:r332_4 +# 332| mu332_6(unknown) = ^CallSideEffect : ~m? +# 332| v332_7(void) = ^BufferReadSideEffect[0] : &:r332_4, ~m? +# 332| mu332_8(unknown) = ^BufferMayWriteSideEffect[0] : &:r332_4 +# 333| v333_1(void) = NoOp : +# 319| v319_8(void) = ReturnIndirection[s] : &:r319_6, ~m? +# 319| v319_9(void) = ReturnVoid : +# 319| v319_10(void) = AliasedUse : ~m? +# 319| v319_11(void) = ExitFunction : + +# 335| int UnreachablePhiOperand(int, int) +# 335| Block 0 +# 335| v335_1(void) = EnterFunction : +# 335| mu335_2(unknown) = AliasedDefinition : +# 335| mu335_3(unknown) = InitializeNonLocal : +# 335| r335_4(glval) = VariableAddress[x] : +# 335| m335_5(int) = InitializeParameter[x] : &:r335_4 +# 335| r335_6(glval) = VariableAddress[y] : +# 335| m335_7(int) = InitializeParameter[y] : &:r335_6 +# 336| r336_1(glval) = VariableAddress[b] : +# 336| r336_2(bool) = Constant[1] : +# 336| m336_3(bool) = Store[b] : &:r336_1, r336_2 +# 337| r337_1(glval) = VariableAddress[ret] : +# 337| m337_2(int) = Uninitialized[ret] : &:r337_1 +# 339| r339_1(glval) = VariableAddress[b] : +# 339| r339_2(bool) = Load[b] : &:r339_1, m336_3 +# 339| v339_3(void) = ConditionalBranch : r339_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 340| Block 1 +# 340| r340_1(glval) = VariableAddress[x] : +# 340| r340_2(int) = Load[x] : &:r340_1, m335_5 +# 340| r340_3(glval) = VariableAddress[ret] : +# 340| m340_4(int) = Store[ret] : &:r340_3, r340_2 +#-----| Goto -> Block 3 + +# 342| Block 2 +# 342| r342_1(glval) = VariableAddress[y] : +# 342| r342_2(int) = Load[y] : &:r342_1, m335_7 +# 342| r342_3(glval) = VariableAddress[ret] : +# 342| m342_4(int) = Store[ret] : &:r342_3, r342_2 +#-----| Goto -> Block 3 + +# 345| Block 3 +# 345| m345_1(int) = Phi : from 1:m340_4, from 2:m342_4 +# 345| r345_2(glval) = VariableAddress[#return] : +# 345| r345_3(glval) = VariableAddress[ret] : +# 345| r345_4(int) = Load[ret] : &:r345_3, m345_1 +# 345| m345_5(int) = Store[#return] : &:r345_2, r345_4 +# 335| r335_8(glval) = VariableAddress[#return] : +# 335| v335_9(void) = ReturnValue : &:r335_8, m345_5 +# 335| v335_10(void) = AliasedUse : ~m? +# 335| v335_11(void) = ExitFunction : + +# 348| int UnreachablePhiOperand2(int, int, int, bool) +# 348| Block 0 +# 348| v348_1(void) = EnterFunction : +# 348| mu348_2(unknown) = AliasedDefinition : +# 348| mu348_3(unknown) = InitializeNonLocal : +# 348| r348_4(glval) = VariableAddress[x] : +# 348| m348_5(int) = InitializeParameter[x] : &:r348_4 +# 348| r348_6(glval) = VariableAddress[y] : +# 348| m348_7(int) = InitializeParameter[y] : &:r348_6 +# 348| r348_8(glval) = VariableAddress[z] : +# 348| m348_9(int) = InitializeParameter[z] : &:r348_8 +# 348| r348_10(glval) = VariableAddress[b1] : +# 348| m348_11(bool) = InitializeParameter[b1] : &:r348_10 +# 349| r349_1(glval) = VariableAddress[b2] : +# 349| r349_2(bool) = Constant[1] : +# 349| m349_3(bool) = Store[b2] : &:r349_1, r349_2 +# 350| r350_1(glval) = VariableAddress[ret] : +# 350| m350_2(int) = Uninitialized[ret] : &:r350_1 +# 352| r352_1(glval) = VariableAddress[b1] : +# 352| r352_2(bool) = Load[b1] : &:r352_1, m348_11 +# 352| v352_3(void) = ConditionalBranch : r352_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 353| Block 1 +# 353| r353_1(glval) = VariableAddress[x] : +# 353| r353_2(int) = Load[x] : &:r353_1, m348_5 +# 353| r353_3(glval) = VariableAddress[ret] : +# 353| m353_4(int) = Store[ret] : &:r353_3, r353_2 +#-----| Goto -> Block 5 + +# 355| Block 2 +# 355| r355_1(glval) = VariableAddress[b2] : +# 355| r355_2(bool) = Load[b2] : &:r355_1, m349_3 +# 355| v355_3(void) = ConditionalBranch : r355_2 +#-----| False -> Block 4 +#-----| True -> Block 3 + +# 356| Block 3 +# 356| r356_1(glval) = VariableAddress[y] : +# 356| r356_2(int) = Load[y] : &:r356_1, m348_7 +# 356| r356_3(glval) = VariableAddress[ret] : +# 356| m356_4(int) = Store[ret] : &:r356_3, r356_2 +#-----| Goto -> Block 5 + +# 358| Block 4 +# 358| r358_1(glval) = VariableAddress[z] : +# 358| r358_2(int) = Load[z] : &:r358_1, m348_9 +# 358| r358_3(glval) = VariableAddress[ret] : +# 358| m358_4(int) = Store[ret] : &:r358_3, r358_2 +#-----| Goto -> Block 5 + +# 362| Block 5 +# 362| m362_1(int) = Phi : from 1:m353_4, from 3:m356_4, from 4:m358_4 +# 362| r362_2(glval) = VariableAddress[#return] : +# 362| r362_3(glval) = VariableAddress[ret] : +# 362| r362_4(int) = Load[ret] : &:r362_3, m362_1 +# 362| m362_5(int) = Store[#return] : &:r362_2, r362_4 +# 348| r348_12(glval) = VariableAddress[#return] : +# 348| v348_13(void) = ReturnValue : &:r348_12, m362_5 +# 348| v348_14(void) = AliasedUse : ~m? +# 348| v348_15(void) = ExitFunction : + +# 365| int DegeneratePhi(int, int, bool) +# 365| Block 0 +# 365| v365_1(void) = EnterFunction : +# 365| mu365_2(unknown) = AliasedDefinition : +# 365| mu365_3(unknown) = InitializeNonLocal : +# 365| r365_4(glval) = VariableAddress[x] : +# 365| m365_5(int) = InitializeParameter[x] : &:r365_4 +# 365| r365_6(glval) = VariableAddress[y] : +# 365| m365_7(int) = InitializeParameter[y] : &:r365_6 +# 365| r365_8(glval) = VariableAddress[b1] : +# 365| m365_9(bool) = InitializeParameter[b1] : &:r365_8 +# 366| r366_1(glval) = VariableAddress[b2] : +# 366| r366_2(bool) = Constant[1] : +# 366| m366_3(bool) = Store[b2] : &:r366_1, r366_2 +# 367| r367_1(glval) = VariableAddress[ret1] : +# 367| m367_2(int) = Uninitialized[ret1] : &:r367_1 +# 368| r368_1(glval) = VariableAddress[ret2] : +# 368| r368_2(glval) = VariableAddress[x] : +# 368| r368_3(int) = Load[x] : &:r368_2, m365_5 +# 368| m368_4(int) = Store[ret2] : &:r368_1, r368_3 +# 370| r370_1(glval) = VariableAddress[b1] : +# 370| r370_2(bool) = Load[b1] : &:r370_1, m365_9 +# 370| v370_3(void) = ConditionalBranch : r370_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 371| Block 1 +# 371| r371_1(glval) = VariableAddress[x] : +# 371| r371_2(int) = Load[x] : &:r371_1, m365_5 +# 371| r371_3(glval) = VariableAddress[ret1] : +# 371| m371_4(int) = Store[ret1] : &:r371_3, r371_2 +#-----| Goto -> Block 5 + +# 373| Block 2 +# 373| r373_1(glval) = VariableAddress[b2] : +# 373| r373_2(bool) = Load[b2] : &:r373_1, m366_3 +# 373| v373_3(void) = ConditionalBranch : r373_2 +#-----| False -> Block 4 +#-----| True -> Block 3 + +# 374| Block 3 +# 374| r374_1(glval) = VariableAddress[x] : +# 374| r374_2(int) = Load[x] : &:r374_1, m365_5 +# 374| r374_3(glval) = VariableAddress[ret1] : +# 374| m374_4(int) = Store[ret1] : &:r374_3, r374_2 +#-----| Goto -> Block 5 + +# 376| Block 4 +# 376| r376_1(glval) = VariableAddress[y] : +# 376| r376_2(int) = Load[y] : &:r376_1, m365_7 +# 376| r376_3(glval) = VariableAddress[ret2] : +# 376| m376_4(int) = Store[ret2] : &:r376_3, r376_2 +#-----| Goto -> Block 5 + +# 380| Block 5 +# 380| m380_1(int) = Phi : from 1:m368_4, from 3:m368_4, from 4:m376_4 +# 380| m380_2(int) = Phi : from 1:m371_4, from 3:m374_4, from 4:m367_2 +# 380| r380_3(glval) = VariableAddress[#return] : +# 380| r380_4(glval) = VariableAddress[ret1] : +# 380| r380_5(int) = Load[ret1] : &:r380_4, m380_2 +# 380| r380_6(glval) = VariableAddress[ret2] : +# 380| r380_7(int) = Load[ret2] : &:r380_6, m380_1 +# 380| r380_8(int) = Add : r380_5, r380_7 +# 380| m380_9(int) = Store[#return] : &:r380_3, r380_8 +# 365| r365_10(glval) = VariableAddress[#return] : +# 365| v365_11(void) = ReturnValue : &:r365_10, m380_9 +# 365| v365_12(void) = AliasedUse : ~m? +# 365| v365_13(void) = ExitFunction : + +# 383| int FusedBlockPhiOperand(int, int, int, bool) +# 383| Block 0 +# 383| v383_1(void) = EnterFunction : +# 383| mu383_2(unknown) = AliasedDefinition : +# 383| mu383_3(unknown) = InitializeNonLocal : +# 383| r383_4(glval) = VariableAddress[x] : +# 383| m383_5(int) = InitializeParameter[x] : &:r383_4 +# 383| r383_6(glval) = VariableAddress[y] : +# 383| m383_7(int) = InitializeParameter[y] : &:r383_6 +# 383| r383_8(glval) = VariableAddress[z] : +# 383| m383_9(int) = InitializeParameter[z] : &:r383_8 +# 383| r383_10(glval) = VariableAddress[b1] : +# 383| m383_11(bool) = InitializeParameter[b1] : &:r383_10 +# 384| r384_1(glval) = VariableAddress[b2] : +# 384| r384_2(bool) = Constant[1] : +# 384| m384_3(bool) = Store[b2] : &:r384_1, r384_2 +# 385| r385_1(glval) = VariableAddress[ret] : +# 385| m385_2(int) = Uninitialized[ret] : &:r385_1 +# 387| r387_1(glval) = VariableAddress[b1] : +# 387| r387_2(bool) = Load[b1] : &:r387_1, m383_11 +# 387| v387_3(void) = ConditionalBranch : r387_2 +#-----| False -> Block 2 +#-----| True -> Block 1 + +# 388| Block 1 +# 388| r388_1(glval) = VariableAddress[x] : +# 388| r388_2(int) = Load[x] : &:r388_1, m383_5 +# 388| r388_3(glval) = VariableAddress[ret] : +# 388| m388_4(int) = Store[ret] : &:r388_3, r388_2 +#-----| Goto -> Block 6 + +# 390| Block 2 +# 390| r390_1(glval) = VariableAddress[b2] : +# 390| r390_2(bool) = Load[b2] : &:r390_1, m384_3 +# 390| v390_3(void) = ConditionalBranch : r390_2 +#-----| False -> Block 4 +#-----| True -> Block 3 + +# 391| Block 3 +# 391| r391_1(glval) = VariableAddress[y] : +# 391| r391_2(int) = Load[y] : &:r391_1, m383_7 +# 391| r391_3(glval) = VariableAddress[ret] : +# 391| m391_4(int) = Store[ret] : &:r391_3, r391_2 +#-----| Goto -> Block 5 + +# 393| Block 4 +# 393| r393_1(glval) = VariableAddress[z] : +# 393| r393_2(int) = Load[z] : &:r393_1, m383_9 +# 393| r393_3(glval) = VariableAddress[ret] : +# 393| m393_4(int) = Store[ret] : &:r393_3, r393_2 +#-----| Goto -> Block 5 + +# 395| Block 5 +# 395| m395_1(int) = Phi : from 3:m391_4, from 4:m393_4 +# 395| v395_2(void) = NoOp : +#-----| Goto -> Block 6 + +# 398| Block 6 +# 398| m398_1(int) = Phi : from 1:m388_4, from 5:m395_1 +# 398| r398_2(glval) = VariableAddress[#return] : +# 398| r398_3(glval) = VariableAddress[ret] : +# 398| r398_4(int) = Load[ret] : &:r398_3, m398_1 +# 398| m398_5(int) = Store[#return] : &:r398_2, r398_4 +# 383| r383_12(glval) = VariableAddress[#return] : +# 383| v383_13(void) = ReturnValue : &:r383_12, m398_5 +# 383| v383_14(void) = AliasedUse : ~m? +# 383| v383_15(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected b/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected index 2a7165a8419..44d8ff0cb83 100644 --- a/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected +++ b/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected @@ -1,4 +1,6 @@ | file://:0:0:0:0 | | +| file://:0:0:0:0 | & | +| file://:0:0:0:0 | && | | file://:0:0:0:0 | (composite *)... | | file://:0:0:0:0 | (composite *)... | | file://:0:0:0:0 | (global namespace) | diff --git a/cpp/ql/test/library-tests/udl/udl.expected b/cpp/ql/test/library-tests/udl/udl.expected index fce69664a06..52c57beffc5 100644 --- a/cpp/ql/test/library-tests/udl/udl.expected +++ b/cpp/ql/test/library-tests/udl/udl.expected @@ -1,4 +1,4 @@ | udl.cpp:5:25:5:28 | Xy | udl.cpp:5:25:5:28 | initializer for xy | | udl.cpp:6:27:6:34 | XyXy | udl.cpp:6:27:6:34 | initializer for xyxy | -| udl.cpp:7:26:7:30 | X | udl.cpp:7:26:7:26 | call to operator "_Y | -| udl.cpp:8:29:8:38 | XX | udl.cpp:8:29:8:29 | call to operator "_Y | +| udl.cpp:7:26:7:30 | X | udl.cpp:7:26:7:26 | call to operator ""_Y | +| udl.cpp:8:29:8:38 | XX | udl.cpp:8:29:8:29 | call to operator ""_Y | diff --git a/cpp/ql/test/library-tests/unnamed/elements.expected b/cpp/ql/test/library-tests/unnamed/elements.expected index 37695f5e837..2b32306fbac 100644 --- a/cpp/ql/test/library-tests/unnamed/elements.expected +++ b/cpp/ql/test/library-tests/unnamed/elements.expected @@ -1,4 +1,6 @@ | file://:0:0:0:0 | | Other | +| file://:0:0:0:0 | & | Other | +| file://:0:0:0:0 | && | Other | | file://:0:0:0:0 | (global namespace) | Other | | file://:0:0:0:0 | (unnamed global/namespace variable) | Other | | file://:0:0:0:0 | _Complex __float128 | Other | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/SuspiciousCallToStrncat/SuspiciousCallToStrncat.expected b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/SuspiciousCallToStrncat/SuspiciousCallToStrncat.expected index b4f8836dbe3..114cddb11a5 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/SuspiciousCallToStrncat/SuspiciousCallToStrncat.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/SuspiciousCallToStrncat/SuspiciousCallToStrncat.expected @@ -1 +1,5 @@ | test.c:24:2:24:8 | call to strncat | Potentially unsafe call to strncat. | +| test.c:45:3:45:9 | call to strncat | Potentially unsafe call to strncat. | +| test.c:67:3:67:9 | call to strncat | Potentially unsafe call to strncat. | +| test.c:75:3:75:9 | call to strncat | Potentially unsafe call to strncat. | +| test.c:76:3:76:9 | call to strncat | Potentially unsafe call to strncat. | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/SuspiciousCallToStrncat/test.c b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/SuspiciousCallToStrncat/test.c index 3c41316aaf2..f166e5c5ab9 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/SuspiciousCallToStrncat/test.c +++ b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/SuspiciousCallToStrncat/test.c @@ -39,3 +39,46 @@ void bad1(char *s) { strncat(buf, ".", 1); // BAD [NOT DETECTED] -- Need to check if any space is left } +void strncat_test1(char *s) { + char buf[80]; + strncat(buf, s, sizeof(buf) - strlen(buf) - 1); // GOOD + strncat(buf, s, sizeof(buf) - strlen(buf)); // BAD +} + +void* malloc(size_t); + +void strncat_test2(char *s) { + int len = 80; + char* buf = (char *)malloc(len); + strncat(buf, s, len - strlen(buf) - 1); // GOOD + strncat(buf, s, len - strlen(buf)); // BAD [NOT DETECTED] +} + +struct buffers +{ + char array[50]; + char* pointer; +}; + +void strncat_test3(char* s, struct buffers* buffers) { + unsigned len_array = strlen(buffers->array); + unsigned max_size = sizeof(buffers->array); + unsigned free_size = max_size - len_array; + strncat(buffers->array, s, free_size); // BAD +} + +#define MAX_SIZE 80 + +void strncat_test4(char *s) { + char buf[MAX_SIZE]; + strncat(buf, s, MAX_SIZE - strlen(buf) - 1); // GOOD + strncat(buf, s, MAX_SIZE - strlen(buf)); // BAD + strncat(buf, "...", MAX_SIZE - strlen(buf)); // BAD +} + +void strncat_test5(char *s) { + int len = 80; + char* buf = (char *) malloc(len + 1); + strncat(buf, s, len - strlen(buf) - 1); // GOOD + strncat(buf, s, len - strlen(buf)); // GOOD +} diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-114/semmle/UncontrolledProcessOperation/UncontrolledProcessOperation.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-114/semmle/UncontrolledProcessOperation/UncontrolledProcessOperation.expected index 5b45af80d5b..c610e346bb1 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-114/semmle/UncontrolledProcessOperation/UncontrolledProcessOperation.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-114/semmle/UncontrolledProcessOperation/UncontrolledProcessOperation.expected @@ -26,27 +26,15 @@ edges | test.cpp:56:12:56:17 | buffer | test.cpp:62:10:62:15 | (const char *)... | | test.cpp:56:12:56:17 | buffer | test.cpp:62:10:62:15 | buffer | | test.cpp:56:12:56:17 | buffer | test.cpp:62:10:62:15 | buffer indirection | -| test.cpp:56:12:56:17 | buffer | test.cpp:63:10:63:13 | (const char *)... | -| test.cpp:56:12:56:17 | buffer | test.cpp:63:10:63:13 | data | -| test.cpp:56:12:56:17 | buffer | test.cpp:63:10:63:13 | data indirection | | test.cpp:56:12:56:17 | fgets output argument | test.cpp:62:10:62:15 | (const char *)... | | test.cpp:56:12:56:17 | fgets output argument | test.cpp:62:10:62:15 | buffer | | test.cpp:56:12:56:17 | fgets output argument | test.cpp:62:10:62:15 | buffer indirection | -| test.cpp:56:12:56:17 | fgets output argument | test.cpp:63:10:63:13 | (const char *)... | -| test.cpp:56:12:56:17 | fgets output argument | test.cpp:63:10:63:13 | data | -| test.cpp:56:12:56:17 | fgets output argument | test.cpp:63:10:63:13 | data indirection | | test.cpp:76:12:76:17 | buffer | test.cpp:78:10:78:15 | (const char *)... | | test.cpp:76:12:76:17 | buffer | test.cpp:78:10:78:15 | buffer | | test.cpp:76:12:76:17 | buffer | test.cpp:78:10:78:15 | buffer indirection | -| test.cpp:76:12:76:17 | buffer | test.cpp:79:10:79:13 | (const char *)... | -| test.cpp:76:12:76:17 | buffer | test.cpp:79:10:79:13 | data | -| test.cpp:76:12:76:17 | buffer | test.cpp:79:10:79:13 | data indirection | | test.cpp:76:12:76:17 | fgets output argument | test.cpp:78:10:78:15 | (const char *)... | | test.cpp:76:12:76:17 | fgets output argument | test.cpp:78:10:78:15 | buffer | | test.cpp:76:12:76:17 | fgets output argument | test.cpp:78:10:78:15 | buffer indirection | -| test.cpp:76:12:76:17 | fgets output argument | test.cpp:79:10:79:13 | (const char *)... | -| test.cpp:76:12:76:17 | fgets output argument | test.cpp:79:10:79:13 | data | -| test.cpp:76:12:76:17 | fgets output argument | test.cpp:79:10:79:13 | data indirection | | test.cpp:98:17:98:22 | buffer | test.cpp:99:15:99:20 | (const char *)... | | test.cpp:98:17:98:22 | buffer | test.cpp:99:15:99:20 | buffer | | test.cpp:98:17:98:22 | buffer | test.cpp:99:15:99:20 | buffer indirection | @@ -89,11 +77,6 @@ nodes | test.cpp:62:10:62:15 | buffer | semmle.label | buffer | | test.cpp:62:10:62:15 | buffer indirection | semmle.label | buffer indirection | | test.cpp:62:10:62:15 | buffer indirection | semmle.label | buffer indirection | -| test.cpp:63:10:63:13 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:63:10:63:13 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:63:10:63:13 | data | semmle.label | data | -| test.cpp:63:10:63:13 | data indirection | semmle.label | data indirection | -| test.cpp:63:10:63:13 | data indirection | semmle.label | data indirection | | test.cpp:76:12:76:17 | buffer | semmle.label | buffer | | test.cpp:76:12:76:17 | fgets output argument | semmle.label | fgets output argument | | test.cpp:78:10:78:15 | (const char *)... | semmle.label | (const char *)... | @@ -101,11 +84,6 @@ nodes | test.cpp:78:10:78:15 | buffer | semmle.label | buffer | | test.cpp:78:10:78:15 | buffer indirection | semmle.label | buffer indirection | | test.cpp:78:10:78:15 | buffer indirection | semmle.label | buffer indirection | -| test.cpp:79:10:79:13 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:79:10:79:13 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:79:10:79:13 | data | semmle.label | data | -| test.cpp:79:10:79:13 | data indirection | semmle.label | data indirection | -| test.cpp:79:10:79:13 | data indirection | semmle.label | data indirection | | test.cpp:98:17:98:22 | buffer | semmle.label | buffer | | test.cpp:98:17:98:22 | recv output argument | semmle.label | recv output argument | | test.cpp:99:15:99:20 | (const char *)... | semmle.label | (const char *)... | @@ -124,8 +102,6 @@ nodes | test.cpp:26:10:26:16 | command | test.cpp:42:18:42:23 | call to getenv | test.cpp:26:10:26:16 | command | The value of this argument may come from $@ and is being passed to system | test.cpp:42:18:42:23 | call to getenv | call to getenv | | test.cpp:31:10:31:16 | command | test.cpp:43:18:43:23 | call to getenv | test.cpp:31:10:31:16 | command | The value of this argument may come from $@ and is being passed to system | test.cpp:43:18:43:23 | call to getenv | call to getenv | | test.cpp:62:10:62:15 | buffer | test.cpp:56:12:56:17 | buffer | test.cpp:62:10:62:15 | buffer | The value of this argument may come from $@ and is being passed to system | test.cpp:56:12:56:17 | buffer | buffer | -| test.cpp:63:10:63:13 | data | test.cpp:56:12:56:17 | buffer | test.cpp:63:10:63:13 | data | The value of this argument may come from $@ and is being passed to system | test.cpp:56:12:56:17 | buffer | buffer | | test.cpp:78:10:78:15 | buffer | test.cpp:76:12:76:17 | buffer | test.cpp:78:10:78:15 | buffer | The value of this argument may come from $@ and is being passed to system | test.cpp:76:12:76:17 | buffer | buffer | -| test.cpp:79:10:79:13 | data | test.cpp:76:12:76:17 | buffer | test.cpp:79:10:79:13 | data | The value of this argument may come from $@ and is being passed to system | test.cpp:76:12:76:17 | buffer | buffer | | test.cpp:99:15:99:20 | buffer | test.cpp:98:17:98:22 | buffer | test.cpp:99:15:99:20 | buffer | The value of this argument may come from $@ and is being passed to LoadLibrary | test.cpp:98:17:98:22 | buffer | buffer | | test.cpp:107:15:107:20 | buffer | test.cpp:106:17:106:22 | buffer | test.cpp:107:15:107:20 | buffer | The value of this argument may come from $@ and is being passed to LoadLibrary | test.cpp:106:17:106:22 | buffer | buffer | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/tests.cpp index c4ccdfad8b3..4f8bcc0fa59 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/tests.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/tests.cpp @@ -586,6 +586,23 @@ void test21(bool cond) if (ptr[-1] == 0) { return; } // GOOD: accesses buffer[1] } +void test22(bool b, const char* source) { + char buffer[16]; + int k; + for (k = 0; k <= 100; k++) { + if(k < 16) { + buffer[k] = 'x'; // GOOD + } + } + + char dest[128]; + int n = b ? 1024 : 132; + if (n >= 128) { + return; + } + memcpy(dest, source, n); // GOOD +} + int main(int argc, char *argv[]) { long long arr17[19]; @@ -609,6 +626,7 @@ int main(int argc, char *argv[]) test19(argc == 0); test20(); test21(argc == 0); + test22(argc == 0, argv[0]); return 0; } diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/ifs/ifs.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/ifs/ifs.c index 3d15905d82d..69a46dd3879 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/ifs/ifs.c +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/ifs/ifs.c @@ -86,13 +86,13 @@ int main(int argc, char **argv) { i3 = argv[1]; printf(i3); - // BAD: varOne is 1 so condition is true and it always goes inside the if + // BAD [FALSE NEGATIVE]: varOne is 1 so condition is true and it always goes inside the if char *i4; if (varOne) i4 = argv[1]; printf(i4); - // BAD: varZero is 0 so condition is true and it always goes inside the if + // BAD [FALSE NEGATIVE]: varZero is 0 so condition is true and it always goes inside the if char *i5; if (!varZero) i5 = argv[1]; diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/ifs/ifs.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/ifs/ifs.expected index 50ae940400a..8c2c89035e2 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/ifs/ifs.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/ifs/ifs.expected @@ -39,22 +39,6 @@ edges | ifs.c:86:8:86:11 | argv | ifs.c:87:9:87:10 | i3 | | ifs.c:86:8:86:11 | argv | ifs.c:87:9:87:10 | i3 indirection | | ifs.c:86:8:86:11 | argv | ifs.c:87:9:87:10 | i3 indirection | -| ifs.c:92:8:92:11 | argv | ifs.c:93:9:93:10 | (const char *)... | -| ifs.c:92:8:92:11 | argv | ifs.c:93:9:93:10 | (const char *)... | -| ifs.c:92:8:92:11 | argv | ifs.c:93:9:93:10 | i4 | -| ifs.c:92:8:92:11 | argv | ifs.c:93:9:93:10 | i4 | -| ifs.c:92:8:92:11 | argv | ifs.c:93:9:93:10 | i4 | -| ifs.c:92:8:92:11 | argv | ifs.c:93:9:93:10 | i4 | -| ifs.c:92:8:92:11 | argv | ifs.c:93:9:93:10 | i4 indirection | -| ifs.c:92:8:92:11 | argv | ifs.c:93:9:93:10 | i4 indirection | -| ifs.c:98:8:98:11 | argv | ifs.c:99:9:99:10 | (const char *)... | -| ifs.c:98:8:98:11 | argv | ifs.c:99:9:99:10 | (const char *)... | -| ifs.c:98:8:98:11 | argv | ifs.c:99:9:99:10 | i5 | -| ifs.c:98:8:98:11 | argv | ifs.c:99:9:99:10 | i5 | -| ifs.c:98:8:98:11 | argv | ifs.c:99:9:99:10 | i5 | -| ifs.c:98:8:98:11 | argv | ifs.c:99:9:99:10 | i5 | -| ifs.c:98:8:98:11 | argv | ifs.c:99:9:99:10 | i5 indirection | -| ifs.c:98:8:98:11 | argv | ifs.c:99:9:99:10 | i5 indirection | | ifs.c:105:8:105:11 | argv | ifs.c:106:9:106:10 | (const char *)... | | ifs.c:105:8:105:11 | argv | ifs.c:106:9:106:10 | (const char *)... | | ifs.c:105:8:105:11 | argv | ifs.c:106:9:106:10 | i6 | @@ -133,24 +117,6 @@ nodes | ifs.c:87:9:87:10 | i3 | semmle.label | i3 | | ifs.c:87:9:87:10 | i3 indirection | semmle.label | i3 indirection | | ifs.c:87:9:87:10 | i3 indirection | semmle.label | i3 indirection | -| ifs.c:92:8:92:11 | argv | semmle.label | argv | -| ifs.c:92:8:92:11 | argv | semmle.label | argv | -| ifs.c:93:9:93:10 | (const char *)... | semmle.label | (const char *)... | -| ifs.c:93:9:93:10 | (const char *)... | semmle.label | (const char *)... | -| ifs.c:93:9:93:10 | i4 | semmle.label | i4 | -| ifs.c:93:9:93:10 | i4 | semmle.label | i4 | -| ifs.c:93:9:93:10 | i4 | semmle.label | i4 | -| ifs.c:93:9:93:10 | i4 indirection | semmle.label | i4 indirection | -| ifs.c:93:9:93:10 | i4 indirection | semmle.label | i4 indirection | -| ifs.c:98:8:98:11 | argv | semmle.label | argv | -| ifs.c:98:8:98:11 | argv | semmle.label | argv | -| ifs.c:99:9:99:10 | (const char *)... | semmle.label | (const char *)... | -| ifs.c:99:9:99:10 | (const char *)... | semmle.label | (const char *)... | -| ifs.c:99:9:99:10 | i5 | semmle.label | i5 | -| ifs.c:99:9:99:10 | i5 | semmle.label | i5 | -| ifs.c:99:9:99:10 | i5 | semmle.label | i5 | -| ifs.c:99:9:99:10 | i5 indirection | semmle.label | i5 indirection | -| ifs.c:99:9:99:10 | i5 indirection | semmle.label | i5 indirection | | ifs.c:105:8:105:11 | argv | semmle.label | argv | | ifs.c:105:8:105:11 | argv | semmle.label | argv | | ifs.c:106:9:106:10 | (const char *)... | semmle.label | (const char *)... | @@ -193,8 +159,6 @@ nodes | ifs.c:75:9:75:10 | i1 | ifs.c:74:8:74:11 | argv | ifs.c:75:9:75:10 | i1 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | ifs.c:74:8:74:11 | argv | argv | | ifs.c:81:9:81:10 | i2 | ifs.c:80:8:80:11 | argv | ifs.c:81:9:81:10 | i2 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | ifs.c:80:8:80:11 | argv | argv | | ifs.c:87:9:87:10 | i3 | ifs.c:86:8:86:11 | argv | ifs.c:87:9:87:10 | i3 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | ifs.c:86:8:86:11 | argv | argv | -| ifs.c:93:9:93:10 | i4 | ifs.c:92:8:92:11 | argv | ifs.c:93:9:93:10 | i4 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | ifs.c:92:8:92:11 | argv | argv | -| ifs.c:99:9:99:10 | i5 | ifs.c:98:8:98:11 | argv | ifs.c:99:9:99:10 | i5 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | ifs.c:98:8:98:11 | argv | argv | | ifs.c:106:9:106:10 | i6 | ifs.c:105:8:105:11 | argv | ifs.c:106:9:106:10 | i6 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | ifs.c:105:8:105:11 | argv | argv | | ifs.c:112:9:112:10 | i7 | ifs.c:111:8:111:11 | argv | ifs.c:112:9:112:10 | i7 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | ifs.c:111:8:111:11 | argv | argv | | ifs.c:118:9:118:10 | i8 | ifs.c:117:8:117:11 | argv | ifs.c:118:9:118:10 | i8 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | ifs.c:117:8:117:11 | argv | argv | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/ComparisonWithWiderType/test3.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/ComparisonWithWiderType/test3.cpp new file mode 100644 index 00000000000..2cde5e689c7 --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/ComparisonWithWiderType/test3.cpp @@ -0,0 +1,7 @@ +void test_issue_5850(unsigned char small, unsigned int large1) { + for(; small < static_cast(large1 - 1); small++) { } // GOOD +} + +void test_widening(unsigned char small, char large) { + for(; small < static_cast(static_cast(large) - 1); small++) { } // GOOD +} diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected index 25ff3162973..752e9165c07 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected @@ -1,228 +1,174 @@ edges -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | (size_t)... | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | (size_t)... | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | -| test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | -| test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | -| test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | -| test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | (size_t)... | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | (size_t)... | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | -| test.cpp:75:25:75:29 | start | test.cpp:79:18:79:28 | ... - ... | -| test.cpp:75:25:75:29 | start | test.cpp:79:18:79:28 | ... - ... | -| test.cpp:75:38:75:40 | end | test.cpp:79:18:79:28 | ... - ... | -| test.cpp:75:38:75:40 | end | test.cpp:79:18:79:28 | ... - ... | -| test.cpp:97:18:97:23 | buffer | test.cpp:100:4:100:15 | buffer | -| test.cpp:97:18:97:23 | buffer | test.cpp:100:17:100:22 | buffer indirection | -| test.cpp:97:18:97:23 | buffer | test.cpp:101:4:101:15 | ... + ... | -| test.cpp:97:18:97:23 | buffer | test.cpp:101:4:101:15 | buffer | -| test.cpp:97:18:97:23 | fread output argument | test.cpp:100:4:100:15 | buffer | -| test.cpp:97:18:97:23 | fread output argument | test.cpp:100:17:100:22 | buffer indirection | -| test.cpp:97:18:97:23 | fread output argument | test.cpp:101:4:101:15 | ... + ... | -| test.cpp:97:18:97:23 | fread output argument | test.cpp:101:4:101:15 | buffer | -| test.cpp:100:4:100:15 | buffer | test.cpp:100:17:100:22 | processData1 output argument | -| test.cpp:100:17:100:22 | buffer indirection | test.cpp:100:17:100:22 | processData1 output argument | -| test.cpp:100:17:100:22 | processData1 output argument | test.cpp:101:4:101:15 | ... + ... | -| test.cpp:100:17:100:22 | processData1 output argument | test.cpp:101:4:101:15 | buffer | -| test.cpp:101:4:101:15 | ... + ... | test.cpp:75:38:75:40 | end | -| test.cpp:101:4:101:15 | buffer | test.cpp:75:25:75:29 | start | -| test.cpp:123:18:123:23 | call to getenv | test.cpp:127:24:127:41 | ... * ... | -| test.cpp:123:18:123:23 | call to getenv | test.cpp:127:24:127:41 | ... * ... | -| test.cpp:123:18:123:31 | (const char *)... | test.cpp:127:24:127:41 | ... * ... | -| test.cpp:123:18:123:31 | (const char *)... | test.cpp:127:24:127:41 | ... * ... | -| test.cpp:132:19:132:24 | call to getenv | test.cpp:134:10:134:27 | ... * ... | -| test.cpp:132:19:132:24 | call to getenv | test.cpp:134:10:134:27 | ... * ... | -| test.cpp:132:19:132:32 | (const char *)... | test.cpp:134:10:134:27 | ... * ... | -| test.cpp:132:19:132:32 | (const char *)... | test.cpp:134:10:134:27 | ... * ... | -| test.cpp:138:19:138:24 | call to getenv | test.cpp:142:11:142:28 | ... * ... | -| test.cpp:138:19:138:24 | call to getenv | test.cpp:142:11:142:28 | ... * ... | -| test.cpp:138:19:138:32 | (const char *)... | test.cpp:142:11:142:28 | ... * ... | -| test.cpp:138:19:138:32 | (const char *)... | test.cpp:142:11:142:28 | ... * ... | -| test.cpp:201:9:201:42 | Store | test.cpp:231:9:231:24 | call to get_tainted_size | -| test.cpp:201:9:201:42 | Store | test.cpp:231:9:231:24 | call to get_tainted_size | -| test.cpp:201:14:201:19 | call to getenv | test.cpp:201:9:201:42 | Store | -| test.cpp:201:14:201:27 | (const char *)... | test.cpp:201:9:201:42 | Store | -| test.cpp:214:23:214:23 | s | test.cpp:215:21:215:21 | s | -| test.cpp:214:23:214:23 | s | test.cpp:215:21:215:21 | s | -| test.cpp:220:21:220:21 | s | test.cpp:221:21:221:21 | s | -| test.cpp:220:21:220:21 | s | test.cpp:221:21:221:21 | s | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:229:9:229:18 | (size_t)... | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:229:9:229:18 | local_size | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:229:9:229:18 | local_size | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:235:2:235:9 | local_size | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:237:2:237:8 | local_size | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:229:9:229:18 | (size_t)... | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:229:9:229:18 | local_size | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:229:9:229:18 | local_size | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:235:2:235:9 | local_size | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:237:2:237:8 | local_size | -| test.cpp:235:2:235:9 | local_size | test.cpp:214:23:214:23 | s | -| test.cpp:237:2:237:8 | local_size | test.cpp:220:21:220:21 | s | -| test.cpp:241:2:241:32 | Chi [array content] | test.cpp:279:17:279:20 | get_size output argument [array content] | -| test.cpp:241:2:241:32 | Chi [array content] | test.cpp:295:18:295:21 | get_size output argument [array content] | -| test.cpp:241:18:241:23 | call to getenv | test.cpp:241:2:241:32 | Chi [array content] | -| test.cpp:241:18:241:31 | (const char *)... | test.cpp:241:2:241:32 | Chi [array content] | -| test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... | -| test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... | -| test.cpp:249:20:249:33 | (const char *)... | test.cpp:253:11:253:29 | ... * ... | -| test.cpp:249:20:249:33 | (const char *)... | test.cpp:253:11:253:29 | ... * ... | -| test.cpp:279:17:279:20 | Chi | test.cpp:281:11:281:28 | ... * ... | -| test.cpp:279:17:279:20 | Chi | test.cpp:281:11:281:28 | ... * ... | -| test.cpp:279:17:279:20 | get_size output argument [array content] | test.cpp:279:17:279:20 | Chi | -| test.cpp:295:18:295:21 | Chi | test.cpp:298:10:298:27 | ... * ... | -| test.cpp:295:18:295:21 | Chi | test.cpp:298:10:298:27 | ... * ... | -| test.cpp:295:18:295:21 | get_size output argument [array content] | test.cpp:295:18:295:21 | Chi | -| test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... | -| test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... | -| test.cpp:301:19:301:32 | (const char *)... | test.cpp:305:11:305:28 | ... * ... | -| test.cpp:301:19:301:32 | (const char *)... | test.cpp:305:11:305:28 | ... * ... | -| test.cpp:309:19:309:24 | call to getenv | test.cpp:314:10:314:27 | ... * ... | -| test.cpp:309:19:309:24 | call to getenv | test.cpp:314:10:314:27 | ... * ... | -| test.cpp:309:19:309:32 | (const char *)... | test.cpp:314:10:314:27 | ... * ... | -| test.cpp:309:19:309:32 | (const char *)... | test.cpp:314:10:314:27 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | (size_t)... | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | (size_t)... | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | +| test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | +| test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | +| test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | +| test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | (size_t)... | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | (size_t)... | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | +| test.cpp:124:18:124:23 | call to getenv | test.cpp:128:24:128:41 | ... * ... | +| test.cpp:124:18:124:23 | call to getenv | test.cpp:128:24:128:41 | ... * ... | +| test.cpp:124:18:124:31 | (const char *)... | test.cpp:128:24:128:41 | ... * ... | +| test.cpp:124:18:124:31 | (const char *)... | test.cpp:128:24:128:41 | ... * ... | +| test.cpp:133:19:133:24 | call to getenv | test.cpp:135:10:135:27 | ... * ... | +| test.cpp:133:19:133:24 | call to getenv | test.cpp:135:10:135:27 | ... * ... | +| test.cpp:133:19:133:32 | (const char *)... | test.cpp:135:10:135:27 | ... * ... | +| test.cpp:133:19:133:32 | (const char *)... | test.cpp:135:10:135:27 | ... * ... | +| test.cpp:148:20:148:25 | call to getenv | test.cpp:152:11:152:28 | ... * ... | +| test.cpp:148:20:148:25 | call to getenv | test.cpp:152:11:152:28 | ... * ... | +| test.cpp:148:20:148:33 | (const char *)... | test.cpp:152:11:152:28 | ... * ... | +| test.cpp:148:20:148:33 | (const char *)... | test.cpp:152:11:152:28 | ... * ... | +| test.cpp:211:9:211:42 | Store | test.cpp:241:9:241:24 | call to get_tainted_size | +| test.cpp:211:9:211:42 | Store | test.cpp:241:9:241:24 | call to get_tainted_size | +| test.cpp:211:14:211:19 | call to getenv | test.cpp:211:9:211:42 | Store | +| test.cpp:211:14:211:27 | (const char *)... | test.cpp:211:9:211:42 | Store | +| test.cpp:224:23:224:23 | s | test.cpp:225:21:225:21 | s | +| test.cpp:224:23:224:23 | s | test.cpp:225:21:225:21 | s | +| test.cpp:230:21:230:21 | s | test.cpp:231:21:231:21 | s | +| test.cpp:230:21:230:21 | s | test.cpp:231:21:231:21 | s | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:239:9:239:18 | (size_t)... | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:239:9:239:18 | local_size | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:239:9:239:18 | local_size | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:245:2:245:9 | local_size | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:247:2:247:8 | local_size | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:239:9:239:18 | (size_t)... | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:239:9:239:18 | local_size | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:239:9:239:18 | local_size | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:245:2:245:9 | local_size | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:247:2:247:8 | local_size | +| test.cpp:245:2:245:9 | local_size | test.cpp:224:23:224:23 | s | +| test.cpp:247:2:247:8 | local_size | test.cpp:230:21:230:21 | s | +| test.cpp:251:2:251:32 | Chi [array content] | test.cpp:289:17:289:20 | get_size output argument [array content] | +| test.cpp:251:2:251:32 | Chi [array content] | test.cpp:305:18:305:21 | get_size output argument [array content] | +| test.cpp:251:18:251:23 | call to getenv | test.cpp:251:2:251:32 | Chi [array content] | +| test.cpp:251:18:251:31 | (const char *)... | test.cpp:251:2:251:32 | Chi [array content] | +| test.cpp:259:20:259:25 | call to getenv | test.cpp:263:11:263:29 | ... * ... | +| test.cpp:259:20:259:25 | call to getenv | test.cpp:263:11:263:29 | ... * ... | +| test.cpp:259:20:259:33 | (const char *)... | test.cpp:263:11:263:29 | ... * ... | +| test.cpp:259:20:259:33 | (const char *)... | test.cpp:263:11:263:29 | ... * ... | +| test.cpp:289:17:289:20 | Chi | test.cpp:291:11:291:28 | ... * ... | +| test.cpp:289:17:289:20 | Chi | test.cpp:291:11:291:28 | ... * ... | +| test.cpp:289:17:289:20 | get_size output argument [array content] | test.cpp:289:17:289:20 | Chi | +| test.cpp:305:18:305:21 | Chi | test.cpp:308:10:308:27 | ... * ... | +| test.cpp:305:18:305:21 | Chi | test.cpp:308:10:308:27 | ... * ... | +| test.cpp:305:18:305:21 | get_size output argument [array content] | test.cpp:305:18:305:21 | Chi | nodes -| test.cpp:39:21:39:24 | argv | semmle.label | argv | -| test.cpp:39:21:39:24 | argv | semmle.label | argv | -| test.cpp:42:38:42:44 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:42:38:42:44 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:42:38:42:44 | tainted | semmle.label | tainted | -| test.cpp:42:38:42:44 | tainted | semmle.label | tainted | -| test.cpp:42:38:42:44 | tainted | semmle.label | tainted | -| test.cpp:43:38:43:63 | ... * ... | semmle.label | ... * ... | -| test.cpp:43:38:43:63 | ... * ... | semmle.label | ... * ... | -| test.cpp:43:38:43:63 | ... * ... | semmle.label | ... * ... | -| test.cpp:45:38:45:63 | ... + ... | semmle.label | ... + ... | -| test.cpp:45:38:45:63 | ... + ... | semmle.label | ... + ... | -| test.cpp:45:38:45:63 | ... + ... | semmle.label | ... + ... | -| test.cpp:48:32:48:35 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:48:32:48:35 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:48:32:48:35 | size | semmle.label | size | -| test.cpp:48:32:48:35 | size | semmle.label | size | -| test.cpp:48:32:48:35 | size | semmle.label | size | -| test.cpp:49:26:49:29 | size | semmle.label | size | -| test.cpp:49:26:49:29 | size | semmle.label | size | -| test.cpp:49:26:49:29 | size | semmle.label | size | -| test.cpp:52:35:52:60 | ... * ... | semmle.label | ... * ... | -| test.cpp:52:35:52:60 | ... * ... | semmle.label | ... * ... | -| test.cpp:52:35:52:60 | ... * ... | semmle.label | ... * ... | -| test.cpp:64:25:64:30 | *buffer | semmle.label | *buffer | -| test.cpp:64:25:64:30 | *buffer | semmle.label | *buffer | -| test.cpp:64:25:64:30 | buffer | semmle.label | buffer | -| test.cpp:75:25:75:29 | start | semmle.label | start | -| test.cpp:75:38:75:40 | end | semmle.label | end | -| test.cpp:79:18:79:28 | ... - ... | semmle.label | ... - ... | -| test.cpp:79:18:79:28 | ... - ... | semmle.label | ... - ... | -| test.cpp:79:18:79:28 | ... - ... | semmle.label | ... - ... | -| test.cpp:97:18:97:23 | buffer | semmle.label | buffer | -| test.cpp:97:18:97:23 | fread output argument | semmle.label | fread output argument | -| test.cpp:100:4:100:15 | buffer | semmle.label | buffer | -| test.cpp:100:17:100:22 | buffer indirection | semmle.label | buffer indirection | -| test.cpp:100:17:100:22 | processData1 output argument | semmle.label | processData1 output argument | -| test.cpp:101:4:101:15 | ... + ... | semmle.label | ... + ... | -| test.cpp:101:4:101:15 | buffer | semmle.label | buffer | -| test.cpp:123:18:123:23 | call to getenv | semmle.label | call to getenv | -| test.cpp:123:18:123:31 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:127:24:127:41 | ... * ... | semmle.label | ... * ... | -| test.cpp:127:24:127:41 | ... * ... | semmle.label | ... * ... | -| test.cpp:127:24:127:41 | ... * ... | semmle.label | ... * ... | -| test.cpp:132:19:132:24 | call to getenv | semmle.label | call to getenv | -| test.cpp:132:19:132:32 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:134:10:134:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:134:10:134:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:134:10:134:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:138:19:138:24 | call to getenv | semmle.label | call to getenv | -| test.cpp:138:19:138:32 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:142:11:142:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:142:11:142:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:142:11:142:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:201:9:201:42 | Store | semmle.label | Store | -| test.cpp:201:14:201:19 | call to getenv | semmle.label | call to getenv | -| test.cpp:201:14:201:27 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:214:23:214:23 | s | semmle.label | s | -| test.cpp:215:21:215:21 | s | semmle.label | s | -| test.cpp:215:21:215:21 | s | semmle.label | s | -| test.cpp:215:21:215:21 | s | semmle.label | s | -| test.cpp:220:21:220:21 | s | semmle.label | s | -| test.cpp:221:21:221:21 | s | semmle.label | s | -| test.cpp:221:21:221:21 | s | semmle.label | s | -| test.cpp:221:21:221:21 | s | semmle.label | s | -| test.cpp:227:24:227:29 | call to getenv | semmle.label | call to getenv | -| test.cpp:227:24:227:37 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:229:9:229:18 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:229:9:229:18 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:229:9:229:18 | local_size | semmle.label | local_size | -| test.cpp:229:9:229:18 | local_size | semmle.label | local_size | -| test.cpp:229:9:229:18 | local_size | semmle.label | local_size | -| test.cpp:231:9:231:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | -| test.cpp:231:9:231:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | -| test.cpp:231:9:231:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | -| test.cpp:235:2:235:9 | local_size | semmle.label | local_size | -| test.cpp:237:2:237:8 | local_size | semmle.label | local_size | -| test.cpp:241:2:241:32 | Chi [array content] | semmle.label | Chi [array content] | -| test.cpp:241:2:241:32 | ChiPartial | semmle.label | ChiPartial | -| test.cpp:241:18:241:23 | call to getenv | semmle.label | call to getenv | -| test.cpp:241:18:241:31 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:249:20:249:25 | call to getenv | semmle.label | call to getenv | -| test.cpp:249:20:249:33 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... | -| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... | -| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... | -| test.cpp:279:17:279:20 | Chi | semmle.label | Chi | -| test.cpp:279:17:279:20 | get_size output argument [array content] | semmle.label | get_size output argument [array content] | -| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:295:18:295:21 | Chi | semmle.label | Chi | -| test.cpp:295:18:295:21 | get_size output argument [array content] | semmle.label | get_size output argument [array content] | -| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:301:19:301:24 | call to getenv | semmle.label | call to getenv | -| test.cpp:301:19:301:32 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:305:11:305:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:305:11:305:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:305:11:305:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:309:19:309:24 | call to getenv | semmle.label | call to getenv | -| test.cpp:309:19:309:32 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:314:10:314:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:314:10:314:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:314:10:314:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:40:21:40:24 | argv | semmle.label | argv | +| test.cpp:40:21:40:24 | argv | semmle.label | argv | +| test.cpp:43:38:43:44 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:43:38:43:44 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:43:38:43:44 | tainted | semmle.label | tainted | +| test.cpp:43:38:43:44 | tainted | semmle.label | tainted | +| test.cpp:43:38:43:44 | tainted | semmle.label | tainted | +| test.cpp:44:38:44:63 | ... * ... | semmle.label | ... * ... | +| test.cpp:44:38:44:63 | ... * ... | semmle.label | ... * ... | +| test.cpp:44:38:44:63 | ... * ... | semmle.label | ... * ... | +| test.cpp:46:38:46:63 | ... + ... | semmle.label | ... + ... | +| test.cpp:46:38:46:63 | ... + ... | semmle.label | ... + ... | +| test.cpp:46:38:46:63 | ... + ... | semmle.label | ... + ... | +| test.cpp:49:32:49:35 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:49:32:49:35 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:49:32:49:35 | size | semmle.label | size | +| test.cpp:49:32:49:35 | size | semmle.label | size | +| test.cpp:49:32:49:35 | size | semmle.label | size | +| test.cpp:50:26:50:29 | size | semmle.label | size | +| test.cpp:50:26:50:29 | size | semmle.label | size | +| test.cpp:50:26:50:29 | size | semmle.label | size | +| test.cpp:53:35:53:60 | ... * ... | semmle.label | ... * ... | +| test.cpp:53:35:53:60 | ... * ... | semmle.label | ... * ... | +| test.cpp:53:35:53:60 | ... * ... | semmle.label | ... * ... | +| test.cpp:124:18:124:23 | call to getenv | semmle.label | call to getenv | +| test.cpp:124:18:124:31 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:128:24:128:41 | ... * ... | semmle.label | ... * ... | +| test.cpp:128:24:128:41 | ... * ... | semmle.label | ... * ... | +| test.cpp:128:24:128:41 | ... * ... | semmle.label | ... * ... | +| test.cpp:133:19:133:24 | call to getenv | semmle.label | call to getenv | +| test.cpp:133:19:133:32 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:135:10:135:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:135:10:135:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:135:10:135:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:148:20:148:25 | call to getenv | semmle.label | call to getenv | +| test.cpp:148:20:148:33 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:152:11:152:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:152:11:152:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:152:11:152:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:211:9:211:42 | Store | semmle.label | Store | +| test.cpp:211:14:211:19 | call to getenv | semmle.label | call to getenv | +| test.cpp:211:14:211:27 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:224:23:224:23 | s | semmle.label | s | +| test.cpp:225:21:225:21 | s | semmle.label | s | +| test.cpp:225:21:225:21 | s | semmle.label | s | +| test.cpp:225:21:225:21 | s | semmle.label | s | +| test.cpp:230:21:230:21 | s | semmle.label | s | +| test.cpp:231:21:231:21 | s | semmle.label | s | +| test.cpp:231:21:231:21 | s | semmle.label | s | +| test.cpp:231:21:231:21 | s | semmle.label | s | +| test.cpp:237:24:237:29 | call to getenv | semmle.label | call to getenv | +| test.cpp:237:24:237:37 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:239:9:239:18 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:239:9:239:18 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:239:9:239:18 | local_size | semmle.label | local_size | +| test.cpp:239:9:239:18 | local_size | semmle.label | local_size | +| test.cpp:239:9:239:18 | local_size | semmle.label | local_size | +| test.cpp:241:9:241:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | +| test.cpp:241:9:241:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | +| test.cpp:241:9:241:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | +| test.cpp:245:2:245:9 | local_size | semmle.label | local_size | +| test.cpp:247:2:247:8 | local_size | semmle.label | local_size | +| test.cpp:251:2:251:32 | Chi [array content] | semmle.label | Chi [array content] | +| test.cpp:251:2:251:32 | ChiPartial | semmle.label | ChiPartial | +| test.cpp:251:18:251:23 | call to getenv | semmle.label | call to getenv | +| test.cpp:251:18:251:31 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:259:20:259:25 | call to getenv | semmle.label | call to getenv | +| test.cpp:259:20:259:33 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:263:11:263:29 | ... * ... | semmle.label | ... * ... | +| test.cpp:263:11:263:29 | ... * ... | semmle.label | ... * ... | +| test.cpp:263:11:263:29 | ... * ... | semmle.label | ... * ... | +| test.cpp:289:17:289:20 | Chi | semmle.label | Chi | +| test.cpp:289:17:289:20 | get_size output argument [array content] | semmle.label | get_size output argument [array content] | +| test.cpp:291:11:291:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:291:11:291:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:291:11:291:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:305:18:305:21 | Chi | semmle.label | Chi | +| test.cpp:305:18:305:21 | get_size output argument [array content] | semmle.label | get_size output argument [array content] | +| test.cpp:308:10:308:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:308:10:308:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:308:10:308:27 | ... * ... | semmle.label | ... * ... | #select -| test.cpp:42:31:42:36 | call to malloc | test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:43:31:43:36 | call to malloc | test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:45:31:45:36 | call to malloc | test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:48:25:48:30 | call to malloc | test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:49:17:49:30 | new[] | test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:52:21:52:27 | call to realloc | test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:79:9:79:29 | new[] | test.cpp:97:18:97:23 | buffer | test.cpp:79:18:79:28 | ... - ... | This allocation size is derived from $@ and might overflow | test.cpp:97:18:97:23 | buffer | user input (fread) | -| test.cpp:127:17:127:22 | call to malloc | test.cpp:123:18:123:23 | call to getenv | test.cpp:127:24:127:41 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:123:18:123:23 | call to getenv | user input (getenv) | -| test.cpp:134:3:134:8 | call to malloc | test.cpp:132:19:132:24 | call to getenv | test.cpp:134:10:134:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:132:19:132:24 | call to getenv | user input (getenv) | -| test.cpp:142:4:142:9 | call to malloc | test.cpp:138:19:138:24 | call to getenv | test.cpp:142:11:142:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:138:19:138:24 | call to getenv | user input (getenv) | -| test.cpp:215:14:215:19 | call to malloc | test.cpp:227:24:227:29 | call to getenv | test.cpp:215:21:215:21 | s | This allocation size is derived from $@ and might overflow | test.cpp:227:24:227:29 | call to getenv | user input (getenv) | -| test.cpp:221:14:221:19 | call to malloc | test.cpp:227:24:227:29 | call to getenv | test.cpp:221:21:221:21 | s | This allocation size is derived from $@ and might overflow | test.cpp:227:24:227:29 | call to getenv | user input (getenv) | -| test.cpp:229:2:229:7 | call to malloc | test.cpp:227:24:227:29 | call to getenv | test.cpp:229:9:229:18 | local_size | This allocation size is derived from $@ and might overflow | test.cpp:227:24:227:29 | call to getenv | user input (getenv) | -| test.cpp:231:2:231:7 | call to malloc | test.cpp:201:14:201:19 | call to getenv | test.cpp:231:9:231:24 | call to get_tainted_size | This allocation size is derived from $@ and might overflow | test.cpp:201:14:201:19 | call to getenv | user input (getenv) | -| test.cpp:253:4:253:9 | call to malloc | test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:249:20:249:25 | call to getenv | user input (getenv) | -| test.cpp:281:4:281:9 | call to malloc | test.cpp:241:18:241:23 | call to getenv | test.cpp:281:11:281:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:241:18:241:23 | call to getenv | user input (getenv) | -| test.cpp:298:3:298:8 | call to malloc | test.cpp:241:18:241:23 | call to getenv | test.cpp:298:10:298:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:241:18:241:23 | call to getenv | user input (getenv) | -| test.cpp:305:4:305:9 | call to malloc | test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:301:19:301:24 | call to getenv | user input (getenv) | -| test.cpp:314:3:314:8 | call to malloc | test.cpp:309:19:309:24 | call to getenv | test.cpp:314:10:314:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:309:19:309:24 | call to getenv | user input (getenv) | +| test.cpp:43:31:43:36 | call to malloc | test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:44:31:44:36 | call to malloc | test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:46:31:46:36 | call to malloc | test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:49:25:49:30 | call to malloc | test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:50:17:50:30 | new[] | test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:53:21:53:27 | call to realloc | test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:128:17:128:22 | call to malloc | test.cpp:124:18:124:23 | call to getenv | test.cpp:128:24:128:41 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:124:18:124:23 | call to getenv | user input (getenv) | +| test.cpp:135:3:135:8 | call to malloc | test.cpp:133:19:133:24 | call to getenv | test.cpp:135:10:135:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:133:19:133:24 | call to getenv | user input (getenv) | +| test.cpp:152:4:152:9 | call to malloc | test.cpp:148:20:148:25 | call to getenv | test.cpp:152:11:152:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:148:20:148:25 | call to getenv | user input (getenv) | +| test.cpp:225:14:225:19 | call to malloc | test.cpp:237:24:237:29 | call to getenv | test.cpp:225:21:225:21 | s | This allocation size is derived from $@ and might overflow | test.cpp:237:24:237:29 | call to getenv | user input (getenv) | +| test.cpp:231:14:231:19 | call to malloc | test.cpp:237:24:237:29 | call to getenv | test.cpp:231:21:231:21 | s | This allocation size is derived from $@ and might overflow | test.cpp:237:24:237:29 | call to getenv | user input (getenv) | +| test.cpp:239:2:239:7 | call to malloc | test.cpp:237:24:237:29 | call to getenv | test.cpp:239:9:239:18 | local_size | This allocation size is derived from $@ and might overflow | test.cpp:237:24:237:29 | call to getenv | user input (getenv) | +| test.cpp:241:2:241:7 | call to malloc | test.cpp:211:14:211:19 | call to getenv | test.cpp:241:9:241:24 | call to get_tainted_size | This allocation size is derived from $@ and might overflow | test.cpp:211:14:211:19 | call to getenv | user input (getenv) | +| test.cpp:263:4:263:9 | call to malloc | test.cpp:259:20:259:25 | call to getenv | test.cpp:263:11:263:29 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:259:20:259:25 | call to getenv | user input (getenv) | +| test.cpp:291:4:291:9 | call to malloc | test.cpp:251:18:251:23 | call to getenv | test.cpp:291:11:291:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:251:18:251:23 | call to getenv | user input (getenv) | +| test.cpp:308:3:308:8 | call to malloc | test.cpp:251:18:251:23 | call to getenv | test.cpp:308:10:308:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:251:18:251:23 | call to getenv | user input (getenv) | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp index 943bc3b1214..b11a136ed24 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp @@ -7,6 +7,7 @@ void *malloc(size_t size); void *realloc(void *ptr, size_t size); void free(void *ptr); int atoi(const char *nptr); +long atol(const char *nptr); struct MyStruct { char data[256]; @@ -76,7 +77,7 @@ void processData2(char *start, char *end) { char *copy; - copy = new char[end - start]; // GOOD [FALSE POSITIVE] + copy = new char[end - start]; // GOOD // ... @@ -137,6 +138,15 @@ void more_bounded_tests() { { int size = atoi(getenv("USER")); + if (size > 0) + { + malloc(size * sizeof(int)); // GOOD (overflow not possible) + } + } + + { + long size = atol(getenv("USER")); + if (size > 0) { malloc(size * sizeof(int)); // BAD @@ -302,7 +312,7 @@ void equality_cases() { if ((size == 50) || (size == 100)) { - malloc(size * sizeof(int)); // GOOD [FALSE POSITIVE] + malloc(size * sizeof(int)); // GOOD } } { @@ -311,6 +321,15 @@ void equality_cases() { if (size != 50 && size != 100) return; - malloc(size * sizeof(int)); // GOOD [FALSE POSITIVE] + malloc(size * sizeof(int)); // GOOD } } + +char * strstr(char *, const char *); + +void ptr_diff_case() { + char* user = getenv("USER"); + char* admin_begin_pos = strstr(user, "ADMIN"); + int offset = admin_begin_pos ? user - admin_begin_pos : 0; + malloc(offset); // GOOD +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/test5.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/test5.cpp index 527c603d1b8..2ee675be6b5 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/test5.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/test5.cpp @@ -18,3 +18,25 @@ void useTaintedInt() y = getTaintedInt(); y = y * 1024; // BAD: arithmetic on a tainted value } + +typedef long long int intmax_t; + +intmax_t imaxabs(intmax_t j); + +void useTaintedIntWithGuard() { + int tainted = getTaintedInt(); + + if(imaxabs(tainted) <= 100) { + int product = tainted * tainted; // GOOD: can't underflow/overflow + } +} + +#define INTMAX_MIN (-0x7fffffffffffffff - 1) + +void useTaintedIntWithGuardIntMaxMin() { + intmax_t tainted = getTaintedInt(); + + if(imaxabs(tainted) <= INTMAX_MIN) { + int product = tainted * tainted; // BAD: imaxabs(INTMAX_MIN) == INTMAX_MIN [NOT DETECTED] + } +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected index ca8dd38fc3b..097efb73b9f 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected @@ -7,30 +7,10 @@ edges | test.c:34:13:34:18 | call to rand | test.c:35:5:35:5 | r | | test.c:34:13:34:18 | call to rand | test.c:35:5:35:5 | r | | test.c:34:13:34:18 | call to rand | test.c:35:5:35:5 | r | -| test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | -| test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | -| test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | -| test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | -| test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | -| test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | -| test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | -| test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:61:5:61:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:61:5:61:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:61:5:61:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:61:5:61:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:62:5:62:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:62:5:62:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:62:5:62:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:62:5:62:5 | r | -| test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | -| test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | -| test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | -| test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | | test.c:75:13:75:19 | ... ^ ... | test.c:77:9:77:9 | r | | test.c:75:13:75:19 | ... ^ ... | test.c:77:9:77:9 | r | | test.c:75:13:75:19 | ... ^ ... | test.c:77:9:77:9 | r | @@ -67,34 +47,11 @@ nodes | test.c:35:5:35:5 | r | semmle.label | r | | test.c:35:5:35:5 | r | semmle.label | r | | test.c:35:5:35:5 | r | semmle.label | r | -| test.c:39:13:39:21 | ... % ... | semmle.label | ... % ... | -| test.c:39:13:39:21 | ... % ... | semmle.label | ... % ... | -| test.c:40:5:40:5 | r | semmle.label | r | -| test.c:40:5:40:5 | r | semmle.label | r | -| test.c:40:5:40:5 | r | semmle.label | r | | test.c:44:13:44:16 | call to rand | semmle.label | call to rand | | test.c:44:13:44:16 | call to rand | semmle.label | call to rand | | test.c:45:5:45:5 | r | semmle.label | r | | test.c:45:5:45:5 | r | semmle.label | r | | test.c:45:5:45:5 | r | semmle.label | r | -| test.c:54:13:54:16 | call to rand | semmle.label | call to rand | -| test.c:54:13:54:16 | call to rand | semmle.label | call to rand | -| test.c:56:5:56:5 | r | semmle.label | r | -| test.c:56:5:56:5 | r | semmle.label | r | -| test.c:56:5:56:5 | r | semmle.label | r | -| test.c:60:13:60:16 | call to rand | semmle.label | call to rand | -| test.c:60:13:60:16 | call to rand | semmle.label | call to rand | -| test.c:61:5:61:5 | r | semmle.label | r | -| test.c:61:5:61:5 | r | semmle.label | r | -| test.c:61:5:61:5 | r | semmle.label | r | -| test.c:62:5:62:5 | r | semmle.label | r | -| test.c:62:5:62:5 | r | semmle.label | r | -| test.c:62:5:62:5 | r | semmle.label | r | -| test.c:66:13:66:16 | call to rand | semmle.label | call to rand | -| test.c:66:13:66:16 | call to rand | semmle.label | call to rand | -| test.c:67:5:67:5 | r | semmle.label | r | -| test.c:67:5:67:5 | r | semmle.label | r | -| test.c:67:5:67:5 | r | semmle.label | r | | test.c:75:13:75:19 | ... ^ ... | semmle.label | ... ^ ... | | test.c:75:13:75:19 | ... ^ ... | semmle.label | ... ^ ... | | test.c:77:9:77:9 | r | semmle.label | r | @@ -133,10 +90,7 @@ nodes #select | test.c:21:17:21:17 | r | test.c:18:13:18:16 | call to rand | test.c:21:17:21:17 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:18:13:18:16 | call to rand | Uncontrolled value | | test.c:35:5:35:5 | r | test.c:34:13:34:18 | call to rand | test.c:35:5:35:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:34:13:34:18 | call to rand | Uncontrolled value | -| test.c:40:5:40:5 | r | test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:39:13:39:21 | ... % ... | Uncontrolled value | | test.c:45:5:45:5 | r | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:44:13:44:16 | call to rand | Uncontrolled value | -| test.c:56:5:56:5 | r | test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:54:13:54:16 | call to rand | Uncontrolled value | -| test.c:67:5:67:5 | r | test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:66:13:66:16 | call to rand | Uncontrolled value | | test.c:77:9:77:9 | r | test.c:75:13:75:19 | ... ^ ... | test.c:77:9:77:9 | r | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test.c:75:13:75:19 | ... ^ ... | Uncontrolled value | | test.c:100:5:100:5 | r | test.c:99:14:99:19 | call to rand | test.c:100:5:100:5 | r | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test.c:99:14:99:19 | call to rand | Uncontrolled value | | test.cpp:25:7:25:7 | r | test.cpp:8:9:8:12 | call to rand | test.cpp:25:7:25:7 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.cpp:8:9:8:12 | call to rand | Uncontrolled value | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.c index 2b67b499a3c..61f39a8e851 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.c +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.c @@ -37,7 +37,7 @@ void randomTester() { { int r = RANDN(100); - r += 100; // GOOD: The return from RANDN is bounded [FALSE POSITIVE] + r += 100; // GOOD: The return from RANDN is bounded } { @@ -53,7 +53,7 @@ void randomTester() { { int r = rand(); r = r / 10; - r += 100; // GOOD [FALSE POSITIVE] + r += 100; // GOOD } { @@ -64,7 +64,7 @@ void randomTester() { { int r = rand() & 0xFF; - r += 100; // GOOD [FALSE POSITIVE] + r += 100; // GOOD } { diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.cpp index d3dc3352a29..78a285fffaf 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.cpp @@ -37,3 +37,14 @@ void randomTester2() r = r + 100; // BAD } } + +int rand(int min, int max); +unsigned rand(int max); + +void test_with_bounded_randomness() { + int r = rand(0, 10); + r++; // GOOD + + unsigned unsigned_r = rand(10); + unsigned_r++; // GOOD +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected index 2d28795d126..85938f15499 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected @@ -1,10 +1,15 @@ | test.cpp:6:5:6:13 | ... > ... | Unsigned subtraction can never be negative. | | test.cpp:10:8:10:24 | ... > ... | Unsigned subtraction can never be negative. | -| test.cpp:15:9:15:25 | ... > ... | Unsigned subtraction can never be negative. | -| test.cpp:32:12:32:20 | ... > ... | Unsigned subtraction can never be negative. | -| test.cpp:39:12:39:20 | ... > ... | Unsigned subtraction can never be negative. | -| test.cpp:47:5:47:13 | ... > ... | Unsigned subtraction can never be negative. | -| test.cpp:55:5:55:13 | ... > ... | Unsigned subtraction can never be negative. | | test.cpp:62:5:62:13 | ... > ... | Unsigned subtraction can never be negative. | -| test.cpp:69:5:69:13 | ... > ... | Unsigned subtraction can never be negative. | | test.cpp:75:8:75:16 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:101:6:101:14 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:128:6:128:14 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:137:6:137:14 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:146:7:146:15 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:152:7:152:15 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:182:6:182:14 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:208:6:208:14 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:252:10:252:18 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:266:10:266:24 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:276:11:276:19 | ... > ... | Unsigned subtraction can never be negative. | +| test.cpp:288:10:288:18 | ... > ... | Unsigned subtraction can never be negative. | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp index 11de69e8c62..1a6aaa618e5 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp @@ -12,7 +12,7 @@ void test(unsigned x, unsigned y, bool unknown) { } if(total <= limit) { - while(limit - total > 0) { // GOOD [FALSE POSITIVE] + while(limit - total > 0) { // GOOD total += getAnInt(); if(total > limit) break; } @@ -29,14 +29,14 @@ void test(unsigned x, unsigned y, bool unknown) { } else { y = x; } - bool b1 = x - y > 0; // GOOD [FALSE POSITIVE] + bool b1 = x - y > 0; // GOOD x = getAnInt(); y = getAnInt(); if(y > x) { y = x - 1; } - bool b2 = x - y > 0; // GOOD [FALSE POSITIVE] + bool b2 = x - y > 0; // GOOD int N = getAnInt(); y = x; @@ -44,7 +44,7 @@ void test(unsigned x, unsigned y, bool unknown) { if(unknown) { y--; } } - if(x - y > 0) { } // GOOD [FALSE POSITIVE] + if(x - y > 0) { } // GOOD x = y; while(cond()) { @@ -52,7 +52,7 @@ void test(unsigned x, unsigned y, bool unknown) { y--; } - if(x - y > 0) { } // GOOD [FALSE POSITIVE] + if(x - y > 0) { } // GOOD y = 0; for(int i = 0; i < x; ++i) { @@ -66,7 +66,7 @@ void test(unsigned x, unsigned y, bool unknown) { if(unknown) { x++; } } - if(x - y > 0) { } // GOOD [FALSE POSITIVE] + if(x - y > 0) { } // GOOD int n = getAnInt(); if (n > x - y) { n = x - y; } @@ -74,4 +74,227 @@ void test(unsigned x, unsigned y, bool unknown) { y += n; // NOTE: `n` is at most `x - y` at this point. if (x - y > 0) {} // GOOD [FALSE POSITIVE] } -} \ No newline at end of file +} + +void test2() { + unsigned int a = getAnInt(); + unsigned int b = a; + + if (a - b > 0) { // GOOD (as a = b) + // ... + } +} + +void test3() { + unsigned int a = getAnInt(); + unsigned int b = a - 1; + + if (a - b > 0) { // GOOD (as a >= b) + // ... + } +} + +void test4() { + unsigned int a = getAnInt(); + unsigned int b = a + 1; + + if (a - b > 0) { // BAD + // ... + } +} + +void test5() { + unsigned int b = getAnInt(); + unsigned int a = b; + + if (a - b > 0) { // GOOD (as a = b) + // ... + } +} + +void test6() { + unsigned int b = getAnInt(); + unsigned int a = b + 1; + + if (a - b > 0) { // GOOD (as a >= b) + // ... + } +} + +void test7() { + unsigned int b = getAnInt(); + unsigned int a = b - 1; + + if (a - b > 0) { // BAD + // ... + } +} + +void test8() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + + if (a - b > 0) { // BAD + // ... + } + + if (a >= b) { // GOOD + if (a - b > 0) { // GOOD (as a >= b) + // ... + } + } else { + if (a - b > 0) { // BAD + // ... + } + } + + if (b >= a) { // GOOD + if (a - b > 0) { // BAD + // ... + } + } else { + if (a - b > 0) { // GOOD (as a > b) + // ... + } + } + + while (a >= b) { // GOOD + if (a - b > 0) { // GOOD (as a >= b) + // ... + } + } + + if (a < b) return; + + if (a - b > 0) { // GOOD (as a >= b) + // ... + } +} + +void test9() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + + if (a < b) { + b = 0; + } + + if (a - b > 0) { // GOOD (as a >= b) [FALSE POSITIVE] + // ... + } +} + +void test10() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + + if (a < b) { + a = b; + } + + if (a - b > 0) { // GOOD (as a >= b) + // ... + } +} + +void test11() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + + if (a < b) return; + + b = getAnInt(); + + if (a - b > 0) { // BAD + // ... + } +} + +void test12() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + unsigned int c; + + if ((b <= c) && (c <= a)) { + if (a - b > 0) { // GOOD (as b <= a) + // ... + } + } + + if (b <= c) { + if (c <= a) { + if (a - b > 0) { // GOOD (as b <= a) + // ... + } + } + } +} + +int test13() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + + if (b != 0) { + return 0; + } // b = 0 + + return (a - b > 0); // GOOD (as b = 0) +} + +int test14() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + + if (!b) { + return 0; + } // b != 0 + + return (a - b > 0); // BAD +} + +struct Numbers +{ + unsigned int a, b; +}; + +int test15(Numbers *n) { + + if (!n) { + return 0; + } + + return (n->a - n->b > 0); // BAD +} + +int test16() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + + if (!b) { + return 0; + } else { + return (a - b > 0); // BAD + } +} + +int test17() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + + if (b == 0) { + return 0; + } // b != 0 + + return (a - b > 0); // BAD +} + +int test18() { + unsigned int a = getAnInt(); + unsigned int b = getAnInt(); + + if (b) { + return 0; + } // b == 0 + + return (a - b > 0); // GOOD (as b = 0) +} diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-327/BrokenCryptoAlgorithm.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-327/BrokenCryptoAlgorithm.expected new file mode 100644 index 00000000000..19cdfa5e9c7 --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-327/BrokenCryptoAlgorithm.expected @@ -0,0 +1,25 @@ +| test2.cpp:49:4:49:24 | call to my_des_implementation | This function call specifies a broken or weak cryptographic algorithm. | +| test2.cpp:62:33:62:40 | ALGO_DES | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test2.cpp:124:4:124:24 | call to my_des_implementation | This function call specifies a broken or weak cryptographic algorithm. | +| test2.cpp:144:27:144:29 | DES | This enum constant access specifies a broken or weak cryptographic algorithm. | +| test2.cpp:172:28:172:35 | ALGO_DES | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test2.cpp:175:28:175:34 | USE_DES | This enum constant access specifies a broken or weak cryptographic algorithm. | +| test2.cpp:182:38:182:45 | ALGO_DES | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test2.cpp:185:38:185:44 | USE_DES | This enum constant access specifies a broken or weak cryptographic algorithm. | +| test2.cpp:238:2:238:20 | call to encrypt | This function call specifies a broken or weak cryptographic algorithm. | +| test2.cpp:245:5:245:11 | call to encrypt | This function call specifies a broken or weak cryptographic algorithm. | +| test.cpp:38:2:38:31 | ENCRYPT_WITH_DES(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test.cpp:39:2:39:31 | ENCRYPT_WITH_RC2(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test.cpp:41:2:41:32 | ENCRYPT_WITH_3DES(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test.cpp:42:2:42:38 | ENCRYPT_WITH_TRIPLE_DES(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test.cpp:51:2:51:32 | DES_DO_ENCRYPTION(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test.cpp:52:2:52:31 | RUN_DES_ENCODING(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test.cpp:53:2:53:25 | DES_ENCODE(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test.cpp:54:2:54:26 | DES_SET_KEY(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. | +| test.cpp:88:2:88:11 | call to encryptDES | This function call specifies a broken or weak cryptographic algorithm. | +| test.cpp:89:2:89:11 | call to encryptRC2 | This function call specifies a broken or weak cryptographic algorithm. | +| test.cpp:91:2:91:12 | call to encrypt3DES | This function call specifies a broken or weak cryptographic algorithm. | +| test.cpp:92:2:92:17 | call to encryptTripleDES | This function call specifies a broken or weak cryptographic algorithm. | +| test.cpp:101:2:101:15 | call to do_des_encrypt | This function call specifies a broken or weak cryptographic algorithm. | +| test.cpp:102:2:102:12 | call to DES_Set_Key | This function call specifies a broken or weak cryptographic algorithm. | +| test.cpp:121:2:121:24 | INIT_ENCRYPT_WITH_DES() | This macro invocation specifies a broken or weak cryptographic algorithm. | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-327/BrokenCryptoAlgorithm.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-327/BrokenCryptoAlgorithm.qlref new file mode 100644 index 00000000000..8424dee1a9b --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-327/BrokenCryptoAlgorithm.qlref @@ -0,0 +1 @@ +Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-327/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-327/test.cpp new file mode 100644 index 00000000000..8af9868f1ee --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-327/test.cpp @@ -0,0 +1,125 @@ + +typedef unsigned long size_t; + +// --- simple encryption macro invocations --- + +void my_implementation1(void *data, size_t amount); +void my_implementation2(void *data, size_t amount); +void my_implementation3(void *data, size_t amount); +void my_implementation4(void *data, size_t amount); +void my_implementation5(void *data, size_t amount); +void my_implementation6(const char *str); + +#define ENCRYPT_WITH_DES(data, amount) my_implementation1(data, amount) +#define ENCRYPT_WITH_RC2(data, amount) my_implementation2(data, amount) +#define ENCRYPT_WITH_AES(data, amount) my_implementation3(data, amount) +#define ENCRYPT_WITH_3DES(data, amount) my_implementation4(data, amount) +#define ENCRYPT_WITH_TRIPLE_DES(data, amount) my_implementation4(data, amount) +#define ENCRYPT_WITH_RC20(data, amount) my_implementation5(data, amount) +#define ENCRYPT_WITH_DES_REMOVED(data, amount) + +#define DESENCRYPT(data, amount) my_implementation1(data, amount) +#define RC2ENCRYPT(data, amount) my_implementation2(data, amount) +#define AESENCRYPT(data, amount) my_implementation3(data, amount) +#define DES3ENCRYPT(data, amount) my_implementation4(data, amount) + +#define DES_DO_ENCRYPTION(data, amount) my_implementation1(data, amount) +#define RUN_DES_ENCODING(data, amount) my_implementation1(data, amount) +#define DES_ENCODE(data, amount) my_implementation1(data, amount) +#define DES_SET_KEY(data, amount) my_implementation1(data, amount) + +#define DES(str) my_implementation6(str) +#define DESMOND(str) my_implementation6(str) +#define ANODES(str) my_implementation6(str) +#define SORT_ORDER_DES (1) + +void test_macros(void *data, size_t amount, const char *str) +{ + ENCRYPT_WITH_DES(data, amount); // BAD + ENCRYPT_WITH_RC2(data, amount); // BAD + ENCRYPT_WITH_AES(data, amount); // GOOD (good algorithm) + ENCRYPT_WITH_3DES(data, amount); // BAD + ENCRYPT_WITH_TRIPLE_DES(data, amount); // BAD + ENCRYPT_WITH_RC20(data, amount); // GOOD (if there ever is an RC20 algorithm, we have no reason to believe it's weak) + ENCRYPT_WITH_DES_REMOVED(data, amount); // GOOD (implementation has been deleted) + + DESENCRYPT(data, amount); // BAD [NOT DETECTED] + RC2ENCRYPT(data, amount); // BAD [NOT DETECTED] + AESENCRYPT(data, amount); // GOOD (good algorithm) + DES3ENCRYPT(data, amount); // BAD [NOT DETECTED] + + DES_DO_ENCRYPTION(data, amount); // BAD + RUN_DES_ENCODING(data, amount); // BAD + DES_ENCODE(data, amount); // BAD + DES_SET_KEY(data, amount); // BAD + + DES(str); // GOOD (probably nothing to do with encryption) + DESMOND(str); // GOOD (probably nothing to do with encryption) + ANODES(str); // GOOD (probably nothing to do with encryption) + int ord = SORT_ORDER_DES; // GOOD (probably nothing to do with encryption) +} + +// --- simple encryption function calls --- + +void encryptDES(void *data, size_t amount); +void encryptRC2(void *data, size_t amount); +void encryptAES(void *data, size_t amount); +void encrypt3DES(void *data, size_t amount); +void encryptTripleDES(void *data, size_t amount); + +void DESEncrypt(void *data, size_t amount); +void RC2Encrypt(void *data, size_t amount); +void AESEncrypt(void *data, size_t amount); +void DES3Encrypt(void *data, size_t amount); + +void DoDESEncryption(void *data, size_t amount); +void encryptDes(void *data, size_t amount); +void do_des_encrypt(void *data, size_t amount); +void DES_Set_Key(const char *key); +void DESSetKey(const char *key); + +int Des(); +void Desmond(const char *str); +void Anodes(int i); +void ConDes(); + +void test_functions(void *data, size_t amount, const char *str) +{ + encryptDES(data, amount); // BAD + encryptRC2(data, amount); // BAD + encryptAES(data, amount); // GOOD (good algorithm) + encrypt3DES(data, amount); // BAD + encryptTripleDES(data, amount); // BAD + + DESEncrypt(data, amount); // BAD [NOT DETECTED] + RC2Encrypt(data, amount); // BAD [NOT DETECTED] + AESEncrypt(data, amount); // GOOD (good algorithm) + DES3Encrypt(data, amount); // BAD [NOT DETECTED] + + DoDESEncryption(data, amount); // BAD [NOT DETECTED] + encryptDes(data, amount); // BAD [NOT DETECTED] + do_des_encrypt(data, amount); // BAD + DES_Set_Key(str); // BAD + DESSetKey(str); // BAD [NOT DETECTED] + + Des(); // GOOD (probably nothing to do with encryption) + Desmond(str); // GOOD (probably nothing to do with encryption) + Anodes(1); // GOOD (probably nothing to do with encryption) + ConDes(); // GOOD (probably nothing to do with encryption) +} + +// --- macros for functions with no arguments --- + +void my_implementation7(); +void my_implementation8(); + +#define INIT_ENCRYPT_WITH_DES() my_implementation7() +#define INIT_ENCRYPT_WITH_AES() my_implementation8() + +void test_macros2() +{ + INIT_ENCRYPT_WITH_DES(); // BAD + INIT_ENCRYPT_WITH_AES(); // GOOD (good algorithm) + + // ... +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-327/test2.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-327/test2.cpp new file mode 100644 index 00000000000..66d6f283ba3 --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-327/test2.cpp @@ -0,0 +1,262 @@ + +typedef unsigned long size_t; + +int strcmp(const char *s1, const char *s2); +void abort(void); + +struct keytype +{ + char data[16]; +}; + +void my_des_implementation(char *data, size_t amount, keytype key); +void my_rc2_implementation(char *data, size_t amount, keytype key); +void my_aes_implementation(char *data, size_t amount, keytype key); +void my_3des_implementation(char *data, size_t amount, keytype key); + +typedef void (*implementation_fn_ptr)(char *data, size_t amount, keytype key); + +// --- more involved C-style example --- + +#define ALGO_DES (1) +#define ALGO_AES (2) + +int all_algos[] = { + ALGO_DES, + ALGO_AES +}; + +void encrypt_good(char *data, size_t amount, keytype key, int algo) +{ + switch (algo) + { + case ALGO_DES: + abort(); + + case ALGO_AES: + { + my_aes_implementation(data, amount, key); // GOOD + } break; + } +}; + +void encrypt_bad(char *data, size_t amount, keytype key, int algo) +{ + switch (algo) + { + case ALGO_DES: + { + my_des_implementation(data, amount, key); // BAD + } break; + + case ALGO_AES: + { + my_aes_implementation(data, amount, key); // GOOD + } break; + } +}; + +void do_encrypts(char *data, size_t amount, keytype key) +{ + encrypt_good(data, amount, key, ALGO_AES); // GOOD + encrypt_bad(data, amount, key, ALGO_DES); // BAD +} + +// --- more involved CPP-style example --- + +enum algorithm +{ + DES, + AES +}; + +algorithm all_algorithms[] = { + DES, + AES +}; + +class MyGoodEncryptor +{ +public: + MyGoodEncryptor(keytype _key, algorithm _algo) : key(_key), algo(_algo) {}; + + void encrypt(char *data, size_t amount); + +private: + keytype key; + algorithm algo; +}; + +void MyGoodEncryptor :: encrypt(char *data, size_t amount) +{ + switch (algo) + { + case DES: + { + throw "DES is not a good choice of encryption algorithm!"; + } break; + + case AES: + { + my_aes_implementation(data, amount, key); // GOOD + } break; + } +} + +class MyBadEncryptor +{ +public: + MyBadEncryptor(keytype _key, algorithm _algo) : key(_key), algo(_algo) {}; + + void encrypt(char *data, size_t amount); + +private: + keytype key; + algorithm algo; +}; + +void MyBadEncryptor :: encrypt(char *data, size_t amount) +{ + switch (algo) + { + case DES: + { + my_des_implementation(data, amount, key); // BAD + } break; + + case AES: + { + my_aes_implementation(data, amount, key); // GOOD + } break; + } +} + +void do_class_encrypts(char *data, size_t amount, keytype key) +{ + { + MyGoodEncryptor mge(key, AES); // GOOD + + mge.encrypt(data, amount); + + } + + { + MyBadEncryptor mbe(key, DES); // BAD + + mbe.encrypt(data, amount); + } +} + +// --- unseen implementation --- + +enum use_algorithm +{ + USE_DES, + USE_AES +}; + +void set_encryption_algorithm1(int algorithm); +void set_encryption_algorithm2(use_algorithm algorithm); +void set_encryption_algorithm3(const char *algorithm_str); + +void encryption_with1(char *data, size_t amount, keytype key, int algorithm); +void encryption_with2(char *data, size_t amount, keytype key, use_algorithm algorithm); +void encryption_with3(char *data, size_t amount, keytype key, const char *algorithm_str); + +int get_algorithm1(); +use_algorithm get_algorithm2(); +const char *get_algorithm3(); + +void do_unseen_encrypts(char *data, size_t amount, keytype key) +{ + set_encryption_algorithm1(ALGO_DES); // BAD + set_encryption_algorithm1(ALGO_AES); // GOOD + + set_encryption_algorithm2(USE_DES); // BAD + set_encryption_algorithm2(USE_AES); // GOOD + + set_encryption_algorithm3("DES"); // BAD [NOT DETECTED] + set_encryption_algorithm3("AES"); // GOOD + set_encryption_algorithm3("AES-256"); // GOOD + + encryption_with1(data, amount, key, ALGO_DES); // BAD + encryption_with1(data, amount, key, ALGO_AES); // GOOD + + encryption_with2(data, amount, key, USE_DES); // BAD + encryption_with2(data, amount, key, USE_AES); // GOOD + + encryption_with3(data, amount, key, "DES"); // BAD [NOT DETECTED] + encryption_with3(data, amount, key, "AES"); // GOOD + encryption_with3(data, amount, key, "AES-256"); // GOOD + + if (get_algorithm1() == ALGO_DES) // GOOD + { + throw "DES is not a good choice of encryption algorithm!"; + } + if (get_algorithm2() == USE_DES) // GOOD + { + throw "DES is not a good choice of encryption algorithm!"; + } + if (strcmp(get_algorithm3(), "DES") == 0) // GOOD + { + throw "DES is not a good choice of encryption algorithm!"; + } +} + +// --- classes --- + +class desEncrypt +{ +public: + static void encrypt(const char *data); + static void doSomethingElse(); +}; + +class aes256Encrypt +{ +public: + static void encrypt(const char *data); + static void doSomethingElse(); +}; + +class desCipher +{ +public: + void encrypt(const char *data); + void doSomethingElse(); +}; + +class aesCipher +{ +public: + void encrypt(const char *data); + void doSomethingElse(); +}; + +void do_classes(const char *data) +{ + desEncrypt::encrypt(data); // BAD + aes256Encrypt::encrypt(data); // GOOD + desEncrypt::doSomethingElse(); // GOOD + aes256Encrypt::doSomethingElse(); // GOOD + + desCipher dc; + aesCipher ac; + dc.encrypt(data); // BAD + ac.encrypt(data); // GOOD + dc.doSomethingElse(); // GOOD + ac.doSomethingElse(); // GOOD +} + +// --- function pointer --- + +void do_fn_ptr(char *data, size_t amount, keytype key) +{ + implementation_fn_ptr impl; + + impl = &my_des_implementation; // BAD [NOT DETECTED] + impl(data, amount, key); + + impl = &my_aes_implementation; // GOOD + impl(data, amount, key); +} diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.expected new file mode 100644 index 00000000000..4720e02b381 --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.expected @@ -0,0 +1,18 @@ +| test.cpp:21:9:21:15 | new | This allocation cannot return null. $@ is unnecessary. | test.cpp:21:9:21:15 | new | This check | +| test.cpp:29:13:29:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:30:7:30:13 | ... == ... | This check | +| test.cpp:33:13:33:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:34:8:34:9 | p2 | This check | +| test.cpp:37:13:37:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:38:7:38:16 | ... == ... | This check | +| test.cpp:41:13:41:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:42:7:42:19 | ... == ... | This check | +| test.cpp:45:13:45:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:46:7:46:8 | p5 | This check | +| test.cpp:49:8:49:19 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:50:7:50:13 | ... == ... | This check | +| test.cpp:53:8:53:19 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:54:8:54:9 | p7 | This check | +| test.cpp:58:8:58:19 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:59:7:59:16 | ... == ... | This check | +| test.cpp:63:8:63:19 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:64:7:64:19 | ... != ... | This check | +| test.cpp:69:9:69:20 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:70:7:70:14 | ... != ... | This check | +| test.cpp:75:11:75:22 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:76:13:76:15 | p11 | This check | +| test.cpp:92:5:92:31 | new[] | This allocation cannot throw. $@ is unnecessary. | test.cpp:97:36:98:3 | { ... } | This catch block | +| test.cpp:93:15:93:41 | new[] | This allocation cannot throw. $@ is unnecessary. | test.cpp:97:36:98:3 | { ... } | This catch block | +| test.cpp:96:10:96:36 | new[] | This allocation cannot throw. $@ is unnecessary. | test.cpp:97:36:98:3 | { ... } | This catch block | +| test.cpp:151:9:151:24 | new | This allocation cannot throw. $@ is unnecessary. | test.cpp:152:15:152:18 | { ... } | This catch block | +| test.cpp:199:15:199:35 | new | This allocation cannot throw. $@ is unnecessary. | test.cpp:201:16:201:19 | { ... } | This catch block | +| test.cpp:212:14:212:34 | new | This allocation cannot throw. $@ is unnecessary. | test.cpp:213:34:213:36 | { ... } | This catch block | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.qlref new file mode 100644 index 00000000000..fe4bb214bb4 --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.qlref @@ -0,0 +1 @@ +Security/CWE/CWE-570/IncorrectAllocationErrorHandling.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-570/test.cpp similarity index 52% rename from cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp rename to cpp/ql/test/query-tests/Security/CWE/CWE-570/test.cpp index 06c22778812..5b3425029e9 100644 --- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-570/test.cpp @@ -18,7 +18,7 @@ void *operator new(std::size_t, const std::nothrow_t &) noexcept; void *operator new[](std::size_t, const std::nothrow_t &) noexcept; void bad_new_in_condition() { - if (!(new int)) { // BAD [NOT DETECTED] + if (!(new int)) { // BAD return; } } @@ -30,7 +30,7 @@ void bad_new_missing_exception_handling() { if (p1 == 0) return; - int *p2 = new int[100]; // BAD [NOT DETECTED] + int *p2 = new int[100]; // BAD if (!p2) return; @@ -42,7 +42,7 @@ void bad_new_missing_exception_handling() { if (p4 == nullptr) return; - int *p5 = new int[100]; // BAD [NOT DETECTED] + int *p5 = new int[100]; // BAD if (p5) {} else return; int *p6; @@ -50,7 +50,7 @@ void bad_new_missing_exception_handling() { if (p6 == 0) return; int *p7; - p7 = new int[100]; // BAD [NOT DETECTED] + p7 = new int[100]; // BAD if (!p7) return; @@ -66,13 +66,13 @@ void bad_new_missing_exception_handling() { return; int *p10; - p10 = new int[100]; // BAD [NOT DETECTED] + p10 = new int[100]; // BAD if (p10 != 0) { } int *p11; do { - p11 = new int[100]; // BAD [NOT DETECTED] + p11 = new int[100]; // BAD } while (!p11); int* p12 = new int[100]; @@ -135,3 +135,93 @@ void good_new_handles_nullptr() { if (new (std::nothrow) int[100] == nullptr) return; // GOOD } + +void* operator new(std::size_t count, void*) noexcept; +void* operator new[](std::size_t count, void*) noexcept; + +struct Foo { + Foo() noexcept; + Foo(int); + + operator bool(); +}; + +void bad_placement_new_with_exception_handling() { + char buffer[1024]; + try { new (buffer) Foo; } // BAD + catch (...) { } +} + +void good_placement_new_with_exception_handling() { + char buffer[1024]; + try { new (buffer) Foo(42); } // GOOD: Foo constructor might throw + catch (...) { } +} + +int unknown_value_without_exceptions() noexcept; + +void may_throw() { + if(unknown_value_without_exceptions()) { + throw "bad luck exception!"; + } +} + +void unknown_code_that_may_throw(int*); +void unknown_code_that_will_not_throw(int*) noexcept; + +void calls_throwing_code(int* p) { + if(unknown_value_without_exceptions()) unknown_code_that_may_throw(p); +} + +void calls_non_throwing(int* p) { + if (unknown_value_without_exceptions()) unknown_code_that_will_not_throw(p); +} + +void good_new_with_throwing_call() { + try { + int* p1 = new(std::nothrow) int; // GOOD + may_throw(); + } catch(...) { } + + try { + int* p2 = new(std::nothrow) int; // GOOD + Foo f(10); + } catch(...) { } + + try { + int* p3 = new(std::nothrow) int; // GOOD + calls_throwing_code(p3); + } catch(...) { } +} + +void bad_new_with_nonthrowing_call() { + try { + int* p1 = new(std::nothrow) int; // BAD + calls_non_throwing(p1); + } catch(...) { } + + try { + int* p2 = new(std::nothrow) int; // GOOD: boolean conversion constructor might throw + Foo f; + if(f) { } + } catch(...) { } +} + +void bad_new_catch_baseclass_of_bad_alloc() { + try { + int* p = new(std::nothrow) int; // BAD + } catch(const std::exception&) { } +} + +void good_new_catch_exception_in_assignment() { + int* p; + try { + p = new int; // GOOD + } catch(const std::bad_alloc&) { } +} + +void good_new_catch_exception_in_conversion() { + try { + long* p = (long*) new int; // GOOD + } catch(const std::bad_alloc&) { } +} \ No newline at end of file diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs index 99ad4c8f963..197edc2c162 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs @@ -415,7 +415,7 @@ namespace Semmle.Autobuild.CSharp.Tests actions.RunProcess["cmd.exe /C dotnet --info"] = 0; actions.RunProcess[@"cmd.exe /C dotnet clean C:\Project\test.csproj"] = 0; actions.RunProcess[@"cmd.exe /C dotnet restore C:\Project\test.csproj"] = 0; - actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto dotnet build --no-incremental C:\Project\test.csproj"] = 0; + actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false C:\Project\test.csproj"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists[@"C:\Project\test.csproj"] = true; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; @@ -439,9 +439,6 @@ namespace Semmle.Autobuild.CSharp.Tests [Fact] public void TestLinuxCSharpAutoBuilder() { - actions.RunProcess["dotnet --list-runtimes"] = 0; - actions.RunProcessOut["dotnet --list-runtimes"] = @"Microsoft.AspNetCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]"; actions.RunProcess["dotnet --info"] = 0; actions.RunProcess[@"dotnet clean C:\Project/test.csproj"] = 0; actions.RunProcess[@"dotnet restore C:\Project/test.csproj"] = 0; @@ -463,7 +460,7 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.LoadXml[@"C:\Project/test.csproj"] = xml; var autobuilder = CreateAutoBuilder(false); - TestAutobuilderScript(autobuilder, 0, 5); + TestAutobuilderScript(autobuilder, 0, 4); } [Fact] @@ -603,8 +600,6 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap [Fact] public void TestLinuxBuildCommand() { - actions.RunProcess["dotnet --list-runtimes"] = 1; - actions.RunProcessOut["dotnet --list-runtimes"] = ""; actions.RunProcess[@"C:\odasa/tools/odasa index --auto ""./build.sh --skip-tests"""] = 0; actions.FileExists["csharp.log"] = true; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; @@ -615,7 +610,7 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap SkipVsWhere(); var autobuilder = CreateAutoBuilder(false, buildCommand: "./build.sh --skip-tests"); - TestAutobuilderScript(autobuilder, 0, 2); + TestAutobuilderScript(autobuilder, 0, 1); } [Fact] @@ -626,14 +621,12 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = ""; actions.RunProcess[@"/bin/chmod u+x C:\Project/build/build.sh"] = 0; - actions.RunProcess["dotnet --list-runtimes"] = 1; - actions.RunProcessOut["dotnet --list-runtimes"] = ""; actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/build/build.sh"] = 0; actions.RunProcessWorkingDirectory[@"C:\odasa/tools/odasa index --auto C:\Project/build/build.sh"] = @"C:\Project/build"; actions.FileExists["csharp.log"] = true; var autobuilder = CreateAutoBuilder(false); - TestAutobuilderScript(autobuilder, 0, 3); + TestAutobuilderScript(autobuilder, 0, 2); } [Fact] @@ -645,14 +638,12 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = ""; actions.RunProcess[@"/bin/chmod u+x C:\Project/build.sh"] = 0; - actions.RunProcess["dotnet --list-runtimes"] = 1; - actions.RunProcessOut["dotnet --list-runtimes"] = ""; actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/build.sh"] = 0; actions.RunProcessWorkingDirectory[@"C:\odasa/tools/odasa index --auto C:\Project/build.sh"] = @"C:\Project"; actions.FileExists["csharp.log"] = false; var autobuilder = CreateAutoBuilder(false); - TestAutobuilderScript(autobuilder, 1, 3); + TestAutobuilderScript(autobuilder, 1, 2); } [Fact] @@ -664,14 +655,12 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = ""; actions.RunProcess[@"/bin/chmod u+x C:\Project/build.sh"] = 0; - actions.RunProcess["dotnet --list-runtimes"] = 1; - actions.RunProcessOut["dotnet --list-runtimes"] = ""; actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/build.sh"] = 5; actions.RunProcessWorkingDirectory[@"C:\odasa/tools/odasa index --auto C:\Project/build.sh"] = @"C:\Project"; actions.FileExists["csharp.log"] = true; var autobuilder = CreateAutoBuilder(false); - TestAutobuilderScript(autobuilder, 1, 3); + TestAutobuilderScript(autobuilder, 1, 2); } [Fact] @@ -872,9 +861,6 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap [Fact] public void TestSkipNugetDotnet() { - actions.RunProcess["dotnet --list-runtimes"] = 0; - actions.RunProcessOut["dotnet --list-runtimes"] = @"Microsoft.AspNetCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.NETCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]"; actions.RunProcess["dotnet --info"] = 0; actions.RunProcess[@"dotnet clean C:\Project/test.csproj"] = 0; actions.RunProcess[@"dotnet restore C:\Project/test.csproj"] = 0; @@ -896,7 +882,7 @@ Microsoft.NETCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.LoadXml[@"C:\Project/test.csproj"] = xml; var autobuilder = CreateAutoBuilder(false, dotnetArguments: "--no-restore"); // nugetRestore=false does not work for now. - TestAutobuilderScript(autobuilder, 0, 5); + TestAutobuilderScript(autobuilder, 0, 4); } [Fact] @@ -907,13 +893,10 @@ Microsoft.NETCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.RunProcess[@"chmod u+x dotnet-install.sh"] = 0; actions.RunProcess[@"./dotnet-install.sh --channel release --version 2.1.3 --install-dir C:\Project/.dotnet"] = 0; actions.RunProcess[@"rm dotnet-install.sh"] = 0; - actions.RunProcess[@"C:\Project/.dotnet/dotnet --list-runtimes"] = 0; - actions.RunProcessOut[@"C:\Project/.dotnet/dotnet --list-runtimes"] = @"Microsoft.AspNetCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.NETCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]"; actions.RunProcess[@"C:\Project/.dotnet/dotnet --info"] = 0; actions.RunProcess[@"C:\Project/.dotnet/dotnet clean C:\Project/test.csproj"] = 0; actions.RunProcess[@"C:\Project/.dotnet/dotnet restore C:\Project/test.csproj"] = 0; - actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/.dotnet/dotnet build --no-incremental C:\Project/test.csproj"] = 0; + actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/.dotnet/dotnet build --no-incremental /p:UseSharedCompilation=false C:\Project/test.csproj"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists["test.csproj"] = true; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; @@ -933,7 +916,7 @@ Microsoft.NETCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.DownloadFiles.Add(("https://dot.net/v1/dotnet-install.sh", "dotnet-install.sh")); var autobuilder = CreateAutoBuilder(false, dotnetVersion: "2.1.3"); - TestAutobuilderScript(autobuilder, 0, 9); + TestAutobuilderScript(autobuilder, 0, 8); } [Fact] @@ -945,11 +928,6 @@ Microsoft.NETCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.RunProcess[@"chmod u+x dotnet-install.sh"] = 0; actions.RunProcess[@"./dotnet-install.sh --channel release --version 2.1.3 --install-dir C:\Project/.dotnet"] = 0; actions.RunProcess[@"rm dotnet-install.sh"] = 0; - actions.RunProcess[@"C:\Project/.dotnet/dotnet --list-runtimes"] = 0; - actions.RunProcessOut[@"C:\Project/.dotnet/dotnet --list-runtimes"] = @"Microsoft.AspNetCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.AspNetCore.App 2.1.4 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.NETCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] -Microsoft.NETCore.App 2.1.4 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]"; actions.RunProcess[@"C:\Project/.dotnet/dotnet --info"] = 0; actions.RunProcess[@"C:\Project/.dotnet/dotnet clean C:\Project/test.csproj"] = 0; actions.RunProcess[@"C:\Project/.dotnet/dotnet restore C:\Project/test.csproj"] = 0; @@ -973,7 +951,7 @@ Microsoft.NETCore.App 2.1.4 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.DownloadFiles.Add(("https://dot.net/v1/dotnet-install.sh", "dotnet-install.sh")); var autobuilder = CreateAutoBuilder(false, dotnetVersion: "2.1.3"); - TestAutobuilderScript(autobuilder, 0, 9); + TestAutobuilderScript(autobuilder, 0, 8); } private void TestDotnetVersionWindows(Action action, int commandsRun) @@ -984,7 +962,7 @@ Microsoft.NETCore.App 2.1.4 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean C:\Project\test.csproj"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore C:\Project\test.csproj"] = 0; - actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental C:\Project\test.csproj"] = 0; + actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false C:\Project\test.csproj"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists[@"C:\Project\test.csproj"] = true; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs index a456c9407db..3d7a1168e30 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs @@ -36,7 +36,7 @@ namespace Semmle.Autobuild.CSharp builder.Log(Severity.Info, "Attempting to build using .NET Core"); } - return WithDotNet(builder, (dotNetPath, environment, compatibleClr) => + return WithDotNet(builder, (dotNetPath, environment) => { var ret = GetInfoCommand(builder.Actions, dotNetPath, environment); foreach (var projectOrSolution in builder.ProjectsOrSolutionsToBuild) @@ -49,7 +49,7 @@ namespace Semmle.Autobuild.CSharp restoreCommand.QuoteArgument(projectOrSolution.FullPath); var restore = restoreCommand.Script; - var build = GetBuildScript(builder, dotNetPath, environment, compatibleClr, projectOrSolution.FullPath); + var build = GetBuildScript(builder, dotNetPath, environment, projectOrSolution.FullPath); ret &= BuildScript.Try(clean) & BuildScript.Try(restore) & build; } @@ -57,7 +57,7 @@ namespace Semmle.Autobuild.CSharp }); } - private static BuildScript WithDotNet(Autobuilder builder, Func?, bool, BuildScript> f) + private static BuildScript WithDotNet(Autobuilder builder, Func?, BuildScript> f) { var installDir = builder.Actions.PathCombine(builder.Options.RootDirectory, ".dotnet"); var installScript = DownloadDotNet(builder, installDir); @@ -81,35 +81,10 @@ namespace Semmle.Autobuild.CSharp env = null; } - // The CLR tracer is always compatible on Windows - if (builder.Actions.IsWindows()) - return f(installDir, env, true); - - // The CLR tracer is only compatible on .NET Core >= 3 on Linux and macOS (see - // https://github.com/dotnet/coreclr/issues/19622) - return BuildScript.Bind(GetInstalledRuntimesScript(builder.Actions, installDir, env), (runtimes, runtimesRet) => - { - var compatibleClr = false; - if (runtimesRet == 0) - { - var minimumVersion = new Version(3, 0); - var regex = new Regex(@"Microsoft\.NETCore\.App (\d\.\d\.\d)"); - compatibleClr = runtimes - .Select(runtime => regex.Match(runtime)) - .Where(m => m.Success) - .Select(m => m.Groups[1].Value) - .Any(m => Version.TryParse(m, out var v) && v >= minimumVersion); - } - - if (!compatibleClr) - { - if (env is null) - env = new Dictionary(); - env.Add("UseSharedCompilation", "false"); - } - - return f(installDir, env, compatibleClr); - }); + if (env is null) + env = new Dictionary(); + env.Add("UseSharedCompilation", "false"); + return f(installDir, env); }); } @@ -122,7 +97,7 @@ namespace Semmle.Autobuild.CSharp /// are needed). /// public static BuildScript WithDotNet(Autobuilder builder, Func?, BuildScript> f) - => WithDotNet(builder, (_1, env, _2) => f(env)); + => WithDotNet(builder, (_1, env) => f(env)); /// /// Returns a script for downloading relevant versions of the @@ -259,34 +234,17 @@ namespace Semmle.Autobuild.CSharp return restore; } - private static BuildScript GetInstalledRuntimesScript(IBuildActions actions, string? dotNetPath, IDictionary? environment) - { - var listSdks = new CommandBuilder(actions, environment: environment, silent: true). - RunCommand(DotNetCommand(actions, dotNetPath)). - Argument("--list-runtimes"); - return listSdks.Script; - } - /// /// Gets the `dotnet build` script. - /// - /// The CLR tracer only works on .NET Core >= 3 on Linux and macOS (see - /// https://github.com/dotnet/coreclr/issues/19622), so in case we are - /// running on an older .NET Core, we disable shared compilation (and - /// hence the need for CLR tracing), by adding a - /// `/p:UseSharedCompilation=false` argument. /// - private static BuildScript GetBuildScript(Autobuilder builder, string? dotNetPath, IDictionary? environment, bool compatibleClr, string projOrSln) + private static BuildScript GetBuildScript(Autobuilder builder, string? dotNetPath, IDictionary? environment, string projOrSln) { var build = new CommandBuilder(builder.Actions, null, environment); var script = builder.MaybeIndex(build, DotNetCommand(builder.Actions, dotNetPath)). Argument("build"). Argument("--no-incremental"); - return compatibleClr ? - script.Argument(builder.Options.DotNetArguments). - QuoteArgument(projOrSln). - Script : + return script.Argument("/p:UseSharedCompilation=false"). Argument(builder.Options.DotNetArguments). QuoteArgument(projOrSln). diff --git a/csharp/extractor/Semmle.Extraction.CIL/Context.Factories.cs b/csharp/extractor/Semmle.Extraction.CIL/Context.Factories.cs index 0cdc5bf8dac..5ce1a58491f 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Context.Factories.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Context.Factories.cs @@ -36,7 +36,7 @@ namespace Semmle.Extraction.CIL c.Extract(this); }); #if DEBUG_LABELS - using var writer = new StringWriter(); + using var writer = new EscapingTextWriter(); e.WriteId(writer); var id = writer.ToString(); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ArrayType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ArrayType.cs index b838a9070ff..0f4138e20c5 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/ArrayType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ArrayType.cs @@ -29,7 +29,7 @@ namespace Semmle.Extraction.CIL.Entities public override int GetHashCode() => HashCode.Combine(elementType, rank); - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { elementType.WriteId(trapFile, inContext); trapFile.Write('['); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs index d28fa0035c3..c94038902f7 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs @@ -32,7 +32,7 @@ namespace Semmle.Extraction.CIL.Entities file = new File(cx, cx.AssemblyPath); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write(FullName); trapFile.Write("#file:///"); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Base/LabelledEntity.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Base/LabelledEntity.cs index 150074f22ee..780ced94916 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Base/LabelledEntity.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Base/LabelledEntity.cs @@ -25,7 +25,7 @@ namespace Semmle.Extraction.CIL public override string ToString() { - using var writer = new StringWriter(); + using var writer = new EscapingTextWriter(); WriteQuotedId(writer); return writer.ToString(); } diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ByRefType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ByRefType.cs index 2a527419249..5b9ae9fd1aa 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/ByRefType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ByRefType.cs @@ -33,7 +33,7 @@ namespace Semmle.Extraction.CIL.Entities public override void WriteAssemblyPrefix(TextWriter trapFile) => throw new NotImplementedException(); - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { ElementType.WriteId(trapFile, inContext); trapFile.Write('&'); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ConstructedType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ConstructedType.cs index 6e2fb90beae..c97ef697700 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/ConstructedType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ConstructedType.cs @@ -100,7 +100,7 @@ namespace Semmle.Extraction.CIL.Entities throw new NotImplementedException(); } - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { idWriter.WriteId(trapFile, inContext); } diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ErrorType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ErrorType.cs index e08cea2854c..41b5810ba27 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/ErrorType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ErrorType.cs @@ -10,7 +10,7 @@ namespace Semmle.Extraction.CIL.Entities { } - public override void WriteId(TextWriter trapFile, bool inContext) => trapFile.Write(""); + public override void WriteId(EscapingTextWriter trapFile, bool inContext) => trapFile.Write(""); public override CilTypeKind Kind => CilTypeKind.ValueOrRefType; diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Event.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Event.cs index a54d0a8065d..0ed8e871d55 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Event.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Event.cs @@ -20,7 +20,7 @@ namespace Semmle.Extraction.CIL.Entities ed = cx.MdReader.GetEventDefinition(handle); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { parent.WriteId(trapFile); trapFile.Write('.'); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs index 8decef24128..96ad1715c27 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Field.cs @@ -14,7 +14,7 @@ namespace Semmle.Extraction.CIL.Entities { } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(DeclaringType); trapFile.Write('.'); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/File.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/File.cs index a07ffc61de8..c2fd6837e3e 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/File.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/File.cs @@ -14,7 +14,7 @@ namespace Semmle.Extraction.CIL.Entities TransformedPath = Context.Extractor.PathTransformer.Transform(OriginalPath); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write(TransformedPath.DatabaseId); trapFile.Write(";sourcefile"); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Folder.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Folder.cs index b49abce64c9..9c3fbadcf20 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Folder.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Folder.cs @@ -12,7 +12,7 @@ namespace Semmle.Extraction.CIL.Entities this.transformedPath = path; } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write(transformedPath.DatabaseId); trapFile.Write(";folder"); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/FunctionPointerType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/FunctionPointerType.cs index 3b6bbba00cc..511826e003c 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/FunctionPointerType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/FunctionPointerType.cs @@ -42,7 +42,7 @@ namespace Semmle.Extraction.CIL.Entities public override void WriteAssemblyPrefix(TextWriter trapFile) { } - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { WriteName( trapFile.Write, diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ITypeSignature.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ITypeSignature.cs index beaecdfab6f..004d2707738 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/ITypeSignature.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ITypeSignature.cs @@ -4,6 +4,6 @@ namespace Semmle.Extraction.CIL.Entities { internal interface ITypeSignature { - void WriteId(TextWriter trapFile, IGenericContext gc); + void WriteId(EscapingTextWriter trapFile, IGenericContext gc); } } diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/LocalVariable.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/LocalVariable.cs index 5d7a00c64c9..b7690d2498e 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/LocalVariable.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/LocalVariable.cs @@ -16,7 +16,7 @@ namespace Semmle.Extraction.CIL.Entities type = t; } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(method); trapFile.Write('_'); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs index 9df82edaa68..199eb691689 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Method.cs @@ -37,17 +37,15 @@ namespace Semmle.Extraction.CIL.Entities public virtual IList? LocalVariables => throw new NotImplementedException(); public IList? Parameters { get; protected set; } - public override void WriteId(TextWriter trapFile) => WriteMethodId(trapFile, DeclaringType, NameLabel); - public abstract string NameLabel { get; } - protected internal void WriteMethodId(TextWriter trapFile, Type parent, string methodName) + public override void WriteId(EscapingTextWriter trapFile) { signature.ReturnType.WriteId(trapFile, this); trapFile.Write(' '); - parent.WriteId(trapFile); + DeclaringType.WriteId(trapFile); trapFile.Write('.'); - trapFile.Write(methodName); + trapFile.Write(NameLabel); if (signature.GenericParameterCount > 0) { @@ -61,11 +59,9 @@ namespace Semmle.Extraction.CIL.Entities trapFile.WriteSeparator(",", ref index); param.WriteId(trapFile, this); } - trapFile.Write(')'); + trapFile.Write(");cil-method"); } - public override string IdSuffix => ";cil-method"; - protected IEnumerable PopulateFlags { get diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodSpecificationMethod.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodSpecificationMethod.cs index 840d106b536..2cea67ec16c 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodSpecificationMethod.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodSpecificationMethod.cs @@ -26,7 +26,7 @@ namespace Semmle.Extraction.CIL.Entities unboundMethod = (Method)Context.CreateGeneric(gc, ms.Method); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { unboundMethod.WriteId(trapFile); trapFile.Write('<'); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodTypeParameter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodTypeParameter.cs index 1795eb29269..a36cf372ea0 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodTypeParameter.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/MethodTypeParameter.cs @@ -9,7 +9,7 @@ namespace Semmle.Extraction.CIL.Entities private readonly Method method; private readonly int index; - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { if (!(inContext && method == gc)) { diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/ModifiedType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/ModifiedType.cs index f160c6869de..36e08a2e594 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/ModifiedType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/ModifiedType.cs @@ -37,7 +37,7 @@ namespace Semmle.Extraction.CIL.Entities public override void WriteAssemblyPrefix(TextWriter trapFile) => throw new NotImplementedException(); - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { Unmodified.WriteId(trapFile, inContext); trapFile.Write(IsRequired ? " modreq" : " modopt"); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/NamedTypeIdWriter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/NamedTypeIdWriter.cs index 59c7d172d01..3a71b76b0c3 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/NamedTypeIdWriter.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/NamedTypeIdWriter.cs @@ -12,7 +12,7 @@ namespace Semmle.Extraction.CIL.Entities this.type = type; } - public void WriteId(TextWriter trapFile, bool inContext) + public void WriteId(EscapingTextWriter trapFile, bool inContext) { if (type.IsPrimitiveType) { diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Namespace.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Namespace.cs index 1d0b373952b..3b7354c791d 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Namespace.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Namespace.cs @@ -14,7 +14,7 @@ namespace Semmle.Extraction.CIL.Entities public bool IsGlobalNamespace => ParentNamespace is null; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { if (ParentNamespace is not null && !ParentNamespace.IsGlobalNamespace) { @@ -22,10 +22,9 @@ namespace Semmle.Extraction.CIL.Entities trapFile.Write('.'); } trapFile.Write(Name); + trapFile.Write(";namespace"); } - public override string IdSuffix => ";namespacee"; - public override bool Equals(object? obj) { if (obj is Namespace ns && Name == ns.Name) diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.cs index c2a340c4de1..41a28e32bf1 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/NoMetadataHandleType.cs @@ -137,7 +137,7 @@ namespace Semmle.Extraction.CIL.Entities } } - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { idWriter.WriteId(trapFile, inContext); } diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs index 9cf96412309..b8906f6b845 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Parameter.cs @@ -19,7 +19,7 @@ namespace Semmle.Extraction.CIL.Entities type = t; } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(parameterizable); trapFile.Write('_'); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/PointerType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/PointerType.cs index 8b1209925b6..1a6bd474bd0 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/PointerType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/PointerType.cs @@ -20,7 +20,7 @@ namespace Semmle.Extraction.CIL.Entities public override int GetHashCode() => HashCode.Combine(pointee, nameof(PointerType)); - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { pointee.WriteId(trapFile, inContext); trapFile.Write('*'); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/PrimitiveType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/PrimitiveType.cs index 1e331397237..0e776c1aaad 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/PrimitiveType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/PrimitiveType.cs @@ -20,7 +20,7 @@ namespace Semmle.Extraction.CIL.Entities public override int GetHashCode() => typeCode.GetHashCode(); - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { Type.WritePrimitiveTypeId(trapFile, Name); } diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs index 9e56c22a099..99a462069e1 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Property.cs @@ -23,7 +23,7 @@ namespace Semmle.Extraction.CIL.Entities this.type = type; } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(type); trapFile.Write('.'); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/SignatureDecoder.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/SignatureDecoder.cs index ea646449421..0f012c0c18d 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/SignatureDecoder.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/SignatureDecoder.cs @@ -17,7 +17,7 @@ namespace Semmle.Extraction.CIL.Entities this.shape = shape; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { elementType.WriteId(trapFile, gc); trapFile.Write('['); @@ -38,7 +38,7 @@ namespace Semmle.Extraction.CIL.Entities this.elementType = elementType; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { elementType.WriteId(trapFile, gc); trapFile.Write('&'); @@ -54,7 +54,7 @@ namespace Semmle.Extraction.CIL.Entities this.signature = signature; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { FunctionPointerType.WriteName( trapFile.Write, @@ -84,7 +84,7 @@ namespace Semmle.Extraction.CIL.Entities this.typeArguments = typeArguments; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { genericType.WriteId(trapFile, gc); trapFile.Write('<'); @@ -112,7 +112,7 @@ namespace Semmle.Extraction.CIL.Entities this.index = index; } - public void WriteId(TextWriter trapFile, IGenericContext outerGc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext outerGc) { if (!ReferenceEquals(innerGc, outerGc) && innerGc is Method method) { @@ -132,7 +132,7 @@ namespace Semmle.Extraction.CIL.Entities this.index = index; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { trapFile.Write("T!"); trapFile.Write(index); @@ -158,7 +158,7 @@ namespace Semmle.Extraction.CIL.Entities this.isRequired = isRequired; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { unmodifiedType.WriteId(trapFile, gc); trapFile.Write(isRequired ? " modreq(" : " modopt("); @@ -186,7 +186,7 @@ namespace Semmle.Extraction.CIL.Entities this.elementType = elementType; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { elementType.WriteId(trapFile, gc); trapFile.Write('*'); @@ -207,7 +207,7 @@ namespace Semmle.Extraction.CIL.Entities this.typeCode = typeCode; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { trapFile.Write(typeCode.Id()); } @@ -227,7 +227,7 @@ namespace Semmle.Extraction.CIL.Entities this.elementType = elementType; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { elementType.WriteId(trapFile, gc); trapFile.Write("[]"); @@ -248,7 +248,7 @@ namespace Semmle.Extraction.CIL.Entities this.handle = handle; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { var type = (Type)gc.Context.Create(handle); type.WriteId(trapFile); @@ -269,7 +269,7 @@ namespace Semmle.Extraction.CIL.Entities this.handle = handle; } - public void WriteId(TextWriter trapFile, IGenericContext gc) + public void WriteId(EscapingTextWriter trapFile, IGenericContext gc) { var type = (Type)gc.Context.Create(handle); type.WriteId(trapFile); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/SourceLocation.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/SourceLocation.cs index ff67844121a..5420ac53645 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/SourceLocation.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/SourceLocation.cs @@ -15,7 +15,7 @@ namespace Semmle.Extraction.CIL.Entities file = cx.CreateSourceFile(location.File); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { file.WriteId(trapFile); trapFile.Write(','); diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs index db4876f8a9a..5d27c5400d5 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/Type.cs @@ -43,11 +43,13 @@ namespace Semmle.Extraction.CIL.Entities /// (This is to avoid infinite recursion generating a method ID that returns a /// type parameter.) /// - public abstract void WriteId(TextWriter trapFile, bool inContext); + public abstract void WriteId(EscapingTextWriter trapFile, bool inContext); - public sealed override void WriteId(TextWriter trapFile) => WriteId(trapFile, false); - - public override string IdSuffix => ";cil-type"; + public sealed override void WriteId(EscapingTextWriter trapFile) + { + WriteId(trapFile, false); + trapFile.Write(";cil-type"); + } /// /// Returns the friendly qualified name of types, such as @@ -58,10 +60,12 @@ namespace Semmle.Extraction.CIL.Entities /// public string GetQualifiedName() { - using var writer = new StringWriter(); + using var writer = new EscapingTextWriter(); WriteId(writer, false); var name = writer.ToString(); - return name.Substring(name.IndexOf(AssemblyTypeNameSeparator) + 2); + return name.Substring(name.IndexOf(AssemblyTypeNameSeparator) + 2). + Replace(";namespace", ""). + Replace(";cil-type", ""); } public abstract CilTypeKind Kind { get; } diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeContainer.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeContainer.cs index e201e065255..aae0b4b0b48 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeContainer.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeContainer.cs @@ -12,16 +12,6 @@ namespace Semmle.Extraction.CIL.Entities { } - public abstract string IdSuffix { get; } - - public override void WriteQuotedId(TextWriter trapFile) - { - trapFile.Write("@\""); - WriteId(trapFile); - trapFile.Write(IdSuffix); - trapFile.Write('\"'); - } - public abstract IEnumerable MethodParameters { get; } public abstract IEnumerable TypeParameters { get; } } diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeDefinitionType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeDefinitionType.cs index 51b02f1482e..a5d377846d7 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeDefinitionType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeDefinitionType.cs @@ -40,7 +40,7 @@ namespace Semmle.Extraction.CIL.Entities public override int GetHashCode() => handle.GetHashCode(); - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { idWriter.WriteId(trapFile, inContext); } diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeReferenceType.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeReferenceType.cs index c94a6978f5e..1af91d32586 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeReferenceType.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeReferenceType.cs @@ -88,7 +88,7 @@ namespace Semmle.Extraction.CIL.Entities public override IEnumerable ThisGenericArguments => typeParams.Value; - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { idWriter.WriteId(trapFile, inContext); } diff --git a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeTypeParameter.cs b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeTypeParameter.cs index 15db1536a3f..580c8573acf 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeTypeParameter.cs +++ b/csharp/extractor/Semmle.Extraction.CIL/Entities/TypeTypeParameter.cs @@ -25,7 +25,7 @@ namespace Semmle.Extraction.CIL.Entities return type.GetHashCode() * 13 + index; } - public override void WriteId(TextWriter trapFile, bool inContext) + public override void WriteId(EscapingTextWriter trapFile, bool inContext) { type.WriteId(trapFile, inContext); trapFile.Write('!'); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Assembly.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Assembly.cs index c0ffce70dbe..9555fabec4f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Assembly.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Assembly.cs @@ -69,7 +69,7 @@ namespace Semmle.Extraction.CSharp.Entities return AssemblyConstructorFactory.Instance.CreateEntity(cx, outputAssemblyCacheKey, null); } - public override void WriteId(System.IO.TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write(assembly.ToString()); if (!(assemblyPath is null)) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs index b1bfb508d75..7e7fc39aab8 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs @@ -20,7 +20,7 @@ namespace Semmle.Extraction.CSharp.Entities this.entity = entity; } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { if (ReportingLocation?.IsInSource == true) { @@ -33,7 +33,7 @@ namespace Semmle.Extraction.CSharp.Entities } } - public override void WriteQuotedId(TextWriter trapFile) + public sealed override void WriteQuotedId(EscapingTextWriter trapFile) { if (ReportingLocation?.IsInSource == true) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs index a3394885d1c..6d4a2cf07f9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs @@ -21,7 +21,7 @@ namespace Semmle.Extraction.CSharp.Entities public override bool NeedsPopulation => true; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(Context.CreateLocation(Symbol.Location)); trapFile.Write(";commentblock"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs index a1c9fb5c643..b9fb6cdfee2 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs @@ -32,7 +32,7 @@ namespace Semmle.Extraction.CSharp.Entities public override bool NeedsPopulation => true; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(Context.CreateLocation(Location)); trapFile.Write(";commentline"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs index 1c929c1ad5a..f3672e4c6e4 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs @@ -81,7 +81,7 @@ namespace Semmle.Extraction.CSharp.Entities trapFile.compilation_finished(this, (float)p.Total.Cpu.TotalSeconds, (float)p.Total.Elapsed.TotalSeconds); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write(hashCode); trapFile.Write(";compilation"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs index 39c202929d2..3f31108b57e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs @@ -144,7 +144,7 @@ namespace Semmle.Extraction.CSharp.Entities } } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { if (Symbol.IsStatic) trapFile.Write("static"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs index e92a42d8ab1..ca9753d229b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs @@ -10,7 +10,7 @@ namespace Semmle.Extraction.CSharp.Entities private Event(Context cx, IEventSymbol init) : base(cx, init) { } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(ContainingType!); trapFile.Write('.'); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs index 103ea8cacda..a478047ac7b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs @@ -170,7 +170,8 @@ namespace Semmle.Extraction.CSharp.Entities public static Expression? CreateGenerated(Context cx, IParameterSymbol parameter, IExpressionParentEntity parent, int childIndex, Extraction.Entities.Location location) { - if (!parameter.HasExplicitDefaultValue) + if (!parameter.HasExplicitDefaultValue || + parameter.Type is IErrorTypeSymbol) { return null; } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs index 2fe1cf224ab..851d1103e1d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs @@ -118,7 +118,7 @@ namespace Semmle.Extraction.CSharp.Entities private readonly Lazy type; public Type Type => type.Value; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(Type); trapFile.Write(" "); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs index 3da002f8b2c..8c00bb94335 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs @@ -73,13 +73,13 @@ namespace Semmle.Extraction.CSharp.Entities public static new Indexer Create(Context cx, IPropertySymbol prop) => IndexerFactory.Instance.CreateEntityFromSymbol(cx, prop); - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(ContainingType!); trapFile.Write('.'); trapFile.Write(Symbol.MetadataName); trapFile.Write('('); - trapFile.BuildList(",", Symbol.Parameters, (p, tb0) => tb0.WriteSubId(Type.Create(Context, p.Type))); + trapFile.BuildList(",", Symbol.Parameters, p => trapFile.WriteSubId(Type.Create(Context, p.Type))); trapFile.Write(");indexer"); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs index a94401f2a93..a94c170f408 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs @@ -10,12 +10,12 @@ namespace Semmle.Extraction.CSharp.Entities { } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { throw new InvalidOperationException(); } - public override void WriteQuotedId(TextWriter trapFile) + public sealed override void WriteQuotedId(EscapingTextWriter trapFile) { trapFile.Write('*'); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs index ee685731c60..81a47710ba1 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs @@ -8,12 +8,12 @@ namespace Semmle.Extraction.CSharp.Entities { private LocalVariable(Context cx, ISymbol init) : base(cx, init) { } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { throw new InvalidOperationException(); } - public override void WriteQuotedId(TextWriter trapFile) + public sealed override void WriteQuotedId(EscapingTextWriter trapFile) { trapFile.Write('*'); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs index 61c8d768521..12d0ce4cc10 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs @@ -127,7 +127,7 @@ namespace Semmle.Extraction.CSharp.Entities /// /// Factored out to share logic between `Method` and `UserOperator`. /// - private static void BuildMethodId(Method m, TextWriter trapFile) + private static void BuildMethodId(Method m, EscapingTextWriter trapFile) { m.Symbol.ReturnType.BuildOrWriteId(m.Context, trapFile, m.Symbol); trapFile.Write(" "); @@ -153,7 +153,7 @@ namespace Semmle.Extraction.CSharp.Entities // Type arguments with different nullability can result in // a constructed method with different nullability of its parameters and return type, // so we need to create a distinct database entity for it. - trapFile.BuildList(",", m.Symbol.GetAnnotatedTypeArguments(), (ta, tb0) => { ta.Symbol.BuildOrWriteId(m.Context, tb0, m.Symbol); trapFile.Write((int)ta.Nullability); }); + trapFile.BuildList(",", m.Symbol.GetAnnotatedTypeArguments(), ta => { ta.Symbol.BuildOrWriteId(m.Context, trapFile, m.Symbol); trapFile.Write((int)ta.Nullability); }); trapFile.Write('>'); } } @@ -182,12 +182,12 @@ namespace Semmle.Extraction.CSharp.Entities } } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { BuildMethodId(this, trapFile); } - protected static void AddParametersToId(Context cx, TextWriter trapFile, IMethodSymbol method) + protected static void AddParametersToId(Context cx, EscapingTextWriter trapFile, IMethodSymbol method) { trapFile.Write('('); var index = 0; @@ -222,7 +222,7 @@ namespace Semmle.Extraction.CSharp.Entities trapFile.Write(')'); } - public static void AddExplicitInterfaceQualifierToId(Context cx, System.IO.TextWriter trapFile, IEnumerable explicitInterfaceImplementations) + public static void AddExplicitInterfaceQualifierToId(Context cx, EscapingTextWriter trapFile, IEnumerable explicitInterfaceImplementations) { if (explicitInterfaceImplementations.Any()) trapFile.AppendList(",", explicitInterfaceImplementations.Select(impl => cx.CreateEntity(impl.ContainingType))); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs index 1d56da2623f..806e73e3590 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Modifier.cs @@ -10,7 +10,7 @@ namespace Semmle.Extraction.CSharp.Entities public override Location? ReportingLocation => null; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write(Symbol); trapFile.Write(";modifier"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Namespace.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Namespace.cs index 7d77ef276cb..68b108743f3 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Namespace.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Namespace.cs @@ -23,7 +23,7 @@ namespace Semmle.Extraction.CSharp.Entities public override bool NeedsPopulation => true; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { if (!Symbol.IsGlobalNamespace) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs index 749c7ae22c8..b9fd57b2cea 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs @@ -20,7 +20,7 @@ namespace Semmle.Extraction.CSharp.Entities this.parent = parent; } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(Context.CreateLocation(ReportingLocation)); trapFile.Write(";namespacedeclaration"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NonGeneratedSourceLocation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NonGeneratedSourceLocation.cs index 141c428dd55..7642c30d1a7 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NonGeneratedSourceLocation.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NonGeneratedSourceLocation.cs @@ -41,7 +41,7 @@ namespace Semmle.Extraction.CSharp.Entities get; } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write("loc,"); trapFile.WriteSubId(FileEntity); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs index 462c8bbb297..9d69372174e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs @@ -74,7 +74,7 @@ namespace Semmle.Extraction.CSharp.Entities public static Parameter Create(Context cx, IParameterSymbol param) => ParameterFactory.Instance.CreateEntity(cx, param, (param, null, null)); - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { if (Parent is null) Parent = Method.Create(Context, Symbol.ContainingSymbol as IMethodSymbol); @@ -209,7 +209,7 @@ namespace Semmle.Extraction.CSharp.Entities public override bool NeedsPopulation => true; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write("__arglist;type"); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs index 5fb77cf4dc2..9e4a1c79ad2 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs @@ -21,7 +21,7 @@ namespace Semmle.Extraction.CSharp.Entities private Type Type => type.Value; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(Type); trapFile.Write(" "); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs index 48381430428..18a98883c8b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/ArrayType.cs @@ -30,7 +30,7 @@ namespace Semmle.Extraction.CSharp.Entities PopulateType(trapFile); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(ElementType); Symbol.BuildArraySuffix(trapFile); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs index b43c4c741f4..8a28beaff7b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs @@ -22,7 +22,7 @@ namespace Semmle.Extraction.CSharp.Entities trapFile.parent_namespace(this, Namespace.Create(Context, Context.Compilation.GlobalNamespace)); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write("dynamic;type"); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/FunctionPointerType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/FunctionPointerType.cs index 2bb9cd3a1b6..4c3ab516172 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/FunctionPointerType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/FunctionPointerType.cs @@ -11,9 +11,9 @@ namespace Semmle.Extraction.CSharp.Entities { } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { - Symbol.BuildTypeId(Context, trapFile, Symbol); + Symbol.BuildTypeId(Context, trapFile, Symbol, constructUnderlyingTupleType: false); trapFile.Write(";functionpointertype"); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs index b607da5d998..01ca82b71ba 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs @@ -128,7 +128,7 @@ namespace Semmle.Extraction.CSharp.Entities private bool IsAnonymousType() => Symbol.IsAnonymousType || Symbol.Name.Contains("__AnonymousType"); - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { if (IsAnonymousType()) { @@ -141,7 +141,7 @@ namespace Semmle.Extraction.CSharp.Entities } } - public override void WriteQuotedId(TextWriter trapFile) + public sealed override void WriteQuotedId(EscapingTextWriter trapFile) { if (IsAnonymousType()) trapFile.Write('*'); @@ -195,7 +195,7 @@ namespace Semmle.Extraction.CSharp.Entities public override bool NeedsPopulation => true; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(referencedType); trapFile.Write(";typeRef"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NullType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NullType.cs index f65d7b17db6..6a049bc3939 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NullType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NullType.cs @@ -13,7 +13,7 @@ namespace Semmle.Extraction.CSharp.Entities trapFile.types(this, Kinds.TypeKind.NULL, "null"); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write(";type"); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Nullability.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Nullability.cs index 3cd85bdbdde..c0ef8299eed 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Nullability.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Nullability.cs @@ -79,7 +79,7 @@ namespace Semmle.Extraction.CSharp.Entities return h; } - public void WriteId(TextWriter trapFile) + public void WriteId(EscapingTextWriter trapFile) { trapFile.Write(Annotation); trapFile.Write('('); @@ -90,7 +90,7 @@ namespace Semmle.Extraction.CSharp.Entities public override string ToString() { - using var w = new StringWriter(); + using var w = new EscapingTextWriter(); WriteId(w); return w.ToString(); } @@ -120,7 +120,7 @@ namespace Semmle.Extraction.CSharp.Entities } } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { Symbol.WriteId(trapFile); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/PointerType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/PointerType.cs index 635172a0e62..c4f8aa74245 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/PointerType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/PointerType.cs @@ -11,7 +11,7 @@ namespace Semmle.Extraction.CSharp.Entities PointedAtType = Create(cx, Symbol.PointedAtType); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.WriteSubId(PointedAtType); trapFile.Write("*;type"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs index ca3e182a99d..56db07671d7 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs @@ -30,9 +30,9 @@ namespace Semmle.Extraction.CSharp.Entities // All tuple types are "local types" public override bool NeedsPopulation => true; - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { - Symbol.BuildTypeId(Context, trapFile, Symbol); + Symbol.BuildTypeId(Context, trapFile, Symbol, constructUnderlyingTupleType: false); trapFile.Write(";tuple"); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs index e2522648678..67936f9a913 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/Type.cs @@ -81,22 +81,45 @@ namespace Semmle.Extraction.CSharp.Entities Symbol.BuildDisplayName(Context, trapFile, constructUnderlyingTupleType); trapFile.WriteLine("\")"); + var baseTypes = GetBaseTypeDeclarations(); + // Visit base types - var baseTypes = new List(); if (Symbol.GetNonObjectBaseType(Context) is INamedTypeSymbol @base) { - var baseKey = Create(Context, @base); - trapFile.extend(this, baseKey.TypeRef); - if (Symbol.TypeKind != TypeKind.Struct) - baseTypes.Add(baseKey); + var bts = GetBaseTypeDeclarations(baseTypes, @base); + + Context.PopulateLater(() => + { + var baseKey = Create(Context, @base); + trapFile.extend(this, baseKey.TypeRef); + + if (Symbol.TypeKind != TypeKind.Struct) + { + foreach (var bt in bts) + { + TypeMention.Create(Context, bt.Type, this, baseKey); + } + } + }); } + // Visit implemented interfaces if (!(base.Symbol is IArrayTypeSymbol)) { - foreach (var t in base.Symbol.Interfaces.Select(i => Create(Context, i))) + foreach (var i in base.Symbol.Interfaces) { - trapFile.implement(this, t.TypeRef); - baseTypes.Add(t); + var bts = GetBaseTypeDeclarations(baseTypes, i); + + Context.PopulateLater(() => + { + var interfaceKey = Create(Context, i); + trapFile.implement(this, interfaceKey.TypeRef); + + foreach (var bt in bts) + { + TypeMention.Create(Context, bt.Type, this, interfaceKey); + } + }); } } @@ -145,23 +168,30 @@ namespace Semmle.Extraction.CSharp.Entities } Modifier.ExtractModifiers(Context, trapFile, this, Symbol); + } - if (IsSourceDeclaration && Symbol.FromSource()) + private IEnumerable GetBaseTypeDeclarations() + { + if (!IsSourceDeclaration || !Symbol.FromSource()) { - var declSyntaxReferences = Symbol.DeclaringSyntaxReferences.Select(d => d.GetSyntax()).ToArray(); - - var baseLists = declSyntaxReferences.OfType().Select(c => c.BaseList); - baseLists = baseLists.Concat(declSyntaxReferences.OfType().Select(c => c.BaseList)); - baseLists = baseLists.Concat(declSyntaxReferences.OfType().Select(c => c.BaseList)); - - baseLists - .Where(bl => bl is not null) - .SelectMany(bl => bl!.Types) - .Zip( - baseTypes.Where(bt => bt.Symbol.SpecialType != SpecialType.System_Object), - (s, t) => TypeMention.Create(Context, s.Type, this, t)) - .Enumerate(); + return Enumerable.Empty(); } + + var declSyntaxReferences = Symbol.DeclaringSyntaxReferences.Select(d => d.GetSyntax()).ToArray(); + + var baseLists = declSyntaxReferences.OfType().Select(c => c.BaseList); + baseLists = baseLists.Concat(declSyntaxReferences.OfType().Select(c => c.BaseList)); + baseLists = baseLists.Concat(declSyntaxReferences.OfType().Select(c => c.BaseList)); + + return baseLists + .Where(bl => bl is not null) + .SelectMany(bl => bl!.Types) + .ToList(); + } + + private IEnumerable GetBaseTypeDeclarations(IEnumerable baseTypes, INamedTypeSymbol type) + { + return baseTypes.Where(bt => SymbolEqualityComparer.Default.Equals(Context.GetModel(bt).GetTypeInfo(bt.Type).Type, type)); } private void ExtractParametersForDelegateLikeType(TextWriter trapFile, IMethodSymbol invokeMethod, Action storeReturnType) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs index dcddf5d5cf2..146b1905018 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs @@ -103,7 +103,7 @@ namespace Semmle.Extraction.CSharp.Entities } } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { string kind; IEntity containingEntity; diff --git a/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs b/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs index f03f1674969..c3bc02bf480 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/SymbolExtensions.cs @@ -121,8 +121,6 @@ namespace Semmle.Extraction.CSharp named = named.TupleUnderlyingType; if (IdDependsOnImpl(named.ContainingType)) return true; - if (IdDependsOnImpl(named.GetNonObjectBaseType(cx))) - return true; if (IdDependsOnImpl(named.ConstructedFrom)) return true; return named.TypeArguments.Any(IdDependsOnImpl); @@ -160,10 +158,7 @@ namespace Semmle.Extraction.CSharp /// The trap builder used to store the result. /// The outer symbol being defined (to avoid recursive ids). /// Whether to build a type ID for the underlying `System.ValueTuple` struct in the case of tuple types. - public static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool constructUnderlyingTupleType = false) => - type.BuildTypeId(cx, trapFile, symbolBeingDefined, true, constructUnderlyingTupleType); - - private static void BuildTypeId(this ITypeSymbol type, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass, bool constructUnderlyingTupleType) + public static void BuildTypeId(this ITypeSymbol type, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined, bool constructUnderlyingTupleType) { using (cx.StackGuard) { @@ -171,7 +166,7 @@ namespace Semmle.Extraction.CSharp { case TypeKind.Array: var array = (IArrayTypeSymbol)type; - array.ElementType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, addBaseClass); + array.ElementType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); array.BuildArraySuffix(trapFile); return; case TypeKind.Class: @@ -181,16 +176,16 @@ namespace Semmle.Extraction.CSharp case TypeKind.Delegate: case TypeKind.Error: var named = (INamedTypeSymbol)type; - named.BuildNamedTypeId(cx, trapFile, symbolBeingDefined, addBaseClass, constructUnderlyingTupleType); + named.BuildNamedTypeId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType); return; case TypeKind.Pointer: var ptr = (IPointerTypeSymbol)type; - ptr.PointedAtType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, addBaseClass); + ptr.PointedAtType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); trapFile.Write('*'); return; case TypeKind.TypeParameter: var tp = (ITypeParameterSymbol)type; - tp.ContainingSymbol.BuildOrWriteId(cx, trapFile, symbolBeingDefined, addBaseClass); + tp.ContainingSymbol.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); trapFile.Write('_'); trapFile.Write(tp.Name); return; @@ -207,7 +202,7 @@ namespace Semmle.Extraction.CSharp } } - private static void BuildOrWriteId(this ISymbol? symbol, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass, bool constructUnderlyingTupleType = false) + private static void BuildOrWriteId(this ISymbol? symbol, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined, bool constructUnderlyingTupleType) { if (symbol is null) { @@ -232,7 +227,7 @@ namespace Semmle.Extraction.CSharp if (SymbolEqualityComparer.Default.Equals(symbol, symbolBeingDefined)) trapFile.Write("__self__"); else if (symbol is ITypeSymbol type && type.IdDependsOn(cx, symbolBeingDefined)) - type.BuildTypeId(cx, trapFile, symbolBeingDefined, addBaseClass, constructUnderlyingTupleType); + type.BuildTypeId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType); else if (symbol is INamedTypeSymbol namedType && namedType.IsTupleType && constructUnderlyingTupleType) trapFile.WriteSubId(NamedType.CreateNamedTypeFromTupleType(cx, namedType)); else @@ -249,8 +244,8 @@ namespace Semmle.Extraction.CSharp /// it will generate an appropriate ID that encodes the signature of /// . /// - public static void BuildOrWriteId(this ISymbol? symbol, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined) => - symbol.BuildOrWriteId(cx, trapFile, symbolBeingDefined, true); + public static void BuildOrWriteId(this ISymbol? symbol, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined) => + symbol.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); /// /// Constructs an array suffix string for this array type symbol. @@ -264,7 +259,7 @@ namespace Semmle.Extraction.CSharp trapFile.Write(']'); } - private static void BuildAssembly(IAssemblySymbol asm, TextWriter trapFile, bool extraPrecise = false) + private static void BuildAssembly(IAssemblySymbol asm, EscapingTextWriter trapFile, bool extraPrecise = false) { var assembly = asm.Identity; trapFile.Write(assembly.Name); @@ -282,22 +277,22 @@ namespace Semmle.Extraction.CSharp trapFile.Write("::"); } - private static void BuildFunctionPointerTypeId(this IFunctionPointerTypeSymbol funptr, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined) + private static void BuildFunctionPointerTypeId(this IFunctionPointerTypeSymbol funptr, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined) { - BuildFunctionPointerSignature(funptr, trapFile, (s, tw) => s.BuildOrWriteId(cx, tw, symbolBeingDefined)); + BuildFunctionPointerSignature(funptr, trapFile, s => s.BuildOrWriteId(cx, trapFile, symbolBeingDefined)); } - private static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, TextWriter trapFile, ISymbol symbolBeingDefined, bool addBaseClass, bool constructUnderlyingTupleType) + private static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined, bool constructUnderlyingTupleType) { if (!constructUnderlyingTupleType && named.IsTupleType) { trapFile.Write('('); trapFile.BuildList(",", named.TupleElements, - (f, tb0) => + f => { trapFile.Write((f.CorrespondingTupleField ?? f).Name); trapFile.Write(":"); - f.Type.BuildOrWriteId(cx, tb0, symbolBeingDefined, addBaseClass); + f.Type.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); } ); trapFile.Write(")"); @@ -308,7 +303,7 @@ namespace Semmle.Extraction.CSharp { if (named.ContainingType is not null) { - named.ContainingType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, addBaseClass); + named.ContainingType.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false); trapFile.Write('.'); } else if (named.ContainingNamespace is not null) @@ -333,38 +328,20 @@ namespace Semmle.Extraction.CSharp } else { - named.ConstructedFrom.BuildOrWriteId(cx, trapFile, symbolBeingDefined, addBaseClass, constructUnderlyingTupleType); + named.ConstructedFrom.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType); trapFile.Write('<'); // Encode the nullability of the type arguments in the label. // Type arguments with different nullability can result in // a constructed type with different nullability of its members and methods, // so we need to create a distinct database entity for it. trapFile.BuildList(",", named.GetAnnotatedTypeArguments(), - (ta, tb0) => ta.Symbol.BuildOrWriteId(cx, tb0, symbolBeingDefined, addBaseClass) + ta => ta.Symbol.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false) ); trapFile.Write('>'); } - - if (addBaseClass && named.GetNonObjectBaseType(cx) is INamedTypeSymbol @base) - { - // We need to limit unfolding of base classes. For example, in - // - // ```csharp - // class C1 { } - // class C2 : C1> { } - // class C3 : C1> { } - // class C4 : C3 { } - // ``` - // - // when we generate the label for `C4`, the base class `C3` has itself `C1>` as - // a base class, which in turn has `C1>` as a base class. The latter has the original - // base class `C3` as a type argument, which would lead to infinite unfolding. - trapFile.Write(" : "); - @base.BuildOrWriteId(cx, trapFile, symbolBeingDefined, addBaseClass: false); - } } - private static void BuildNamespace(this INamespaceSymbol ns, Context cx, TextWriter trapFile) + private static void BuildNamespace(this INamespaceSymbol ns, Context cx, EscapingTextWriter trapFile) { trapFile.WriteSubId(Namespace.Create(cx, ns)); trapFile.Write('.'); @@ -377,7 +354,7 @@ namespace Semmle.Extraction.CSharp trapFile.Write("<>__AnonType"); trapFile.Write(hackTypeNumber); trapFile.Write('<'); - trapFile.BuildList(",", type.GetMembers().OfType(), (prop, tb0) => BuildDisplayName(prop.Type, cx, tb0)); + trapFile.BuildList(",", type.GetMembers().OfType(), prop => BuildDisplayName(prop.Type, cx, trapFile)); trapFile.Write('>'); } @@ -396,7 +373,7 @@ namespace Semmle.Extraction.CSharp var elementType = array.ElementType; if (elementType.MetadataName.Contains("`")) { - trapFile.Write(elementType.Name); + trapFile.Write(TrapExtensions.EncodeString(elementType.Name)); return; } elementType.BuildDisplayName(cx, trapFile); @@ -433,7 +410,7 @@ namespace Semmle.Extraction.CSharp } public static void BuildFunctionPointerSignature(IFunctionPointerTypeSymbol funptr, TextWriter trapFile, - Action buildNested) + Action buildNested) { trapFile.Write("delegate* "); trapFile.Write(funptr.Signature.CallingConvention.ToString().ToLowerInvariant()); @@ -447,19 +424,19 @@ namespace Semmle.Extraction.CSharp trapFile.Write('<'); trapFile.BuildList(",", funptr.Signature.Parameters, - (p, trap) => + p => { - buildNested(p.Type, trap); + buildNested(p.Type); switch (p.RefKind) { case RefKind.Out: - trap.Write(" out"); + trapFile.Write(" out"); break; case RefKind.In: - trap.Write(" in"); + trapFile.Write(" in"); break; case RefKind.Ref: - trap.Write(" ref"); + trapFile.Write(" ref"); break; } }); @@ -469,7 +446,7 @@ namespace Semmle.Extraction.CSharp trapFile.Write(","); } - buildNested(funptr.Signature.ReturnType, trapFile); + buildNested(funptr.Signature.ReturnType); if (funptr.Signature.ReturnsByRef) trapFile.Write(" ref"); @@ -481,7 +458,7 @@ namespace Semmle.Extraction.CSharp private static void BuildFunctionPointerTypeDisplayName(this IFunctionPointerTypeSymbol funptr, Context cx, TextWriter trapFile) { - BuildFunctionPointerSignature(funptr, trapFile, (s, tw) => s.BuildDisplayName(cx, tw)); + BuildFunctionPointerSignature(funptr, trapFile, s => s.BuildDisplayName(cx, trapFile)); } private static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, Context cx, TextWriter trapFile, bool constructUnderlyingTupleType) @@ -492,7 +469,7 @@ namespace Semmle.Extraction.CSharp trapFile.BuildList( ",", namedType.TupleElements.Select(f => f.Type), - (t, tb0) => t.BuildDisplayName(cx, tb0)); + t => t.BuildDisplayName(cx, trapFile)); trapFile.Write(")"); return; } @@ -503,7 +480,7 @@ namespace Semmle.Extraction.CSharp } else { - trapFile.Write(namedType.Name); + trapFile.Write(TrapExtensions.EncodeString(namedType.Name)); } if (namedType.IsGenericType && namedType.TypeKind != TypeKind.Error && namedType.TypeArguments.Any()) @@ -512,11 +489,11 @@ namespace Semmle.Extraction.CSharp trapFile.BuildList( ",", namedType.TypeArguments, - (p, tb0) => + p => { if (IsReallyBound(namedType)) { - p.BuildDisplayName(cx, tb0); + p.BuildDisplayName(cx, trapFile); } }); trapFile.Write('>'); diff --git a/csharp/extractor/Semmle.Extraction/Context.cs b/csharp/extractor/Semmle.Extraction/Context.cs index a60d82c6952..b08fb98fc55 100644 --- a/csharp/extractor/Semmle.Extraction/Context.cs +++ b/csharp/extractor/Semmle.Extraction/Context.cs @@ -108,7 +108,7 @@ namespace Semmle.Extraction Populate(init as ISymbol, entity); #if DEBUG_LABELS - using var id = new StringWriter(); + using var id = new EscapingTextWriter(); entity.WriteQuotedId(id); CheckEntityHasUniqueLabel(id.ToString(), entity); #endif diff --git a/csharp/extractor/Semmle.Extraction/Entities/Base/Entity.cs b/csharp/extractor/Semmle.Extraction/Entities/Base/Entity.cs index 1d5081df2e5..c5d630bc7b1 100644 --- a/csharp/extractor/Semmle.Extraction/Entities/Base/Entity.cs +++ b/csharp/extractor/Semmle.Extraction/Entities/Base/Entity.cs @@ -15,9 +15,14 @@ namespace Semmle.Extraction public Label Label { get; set; } - public abstract void WriteId(TextWriter trapFile); + public abstract void WriteId(EscapingTextWriter trapFile); - public abstract void WriteQuotedId(TextWriter trapFile); + public virtual void WriteQuotedId(EscapingTextWriter trapFile) + { + trapFile.WriteUnescaped("@\""); + WriteId(trapFile); + trapFile.WriteUnescaped('\"'); + } public abstract Location? ReportingLocation { get; } @@ -27,9 +32,10 @@ namespace Semmle.Extraction { trapFile.WriteLabel(this); trapFile.Write("="); + using var escaping = new EscapingTextWriter(trapFile); try { - WriteQuotedId(trapFile); + WriteQuotedId(escaping); } catch (Exception ex) // lgtm[cs/catch-of-all-exceptions] { @@ -51,7 +57,7 @@ namespace Semmle.Extraction /// public string GetDebugLabel() { - using var writer = new StringWriter(); + using var writer = new EscapingTextWriter(); writer.WriteLabel(Label.Value); writer.Write('='); WriteQuotedId(writer); diff --git a/csharp/extractor/Semmle.Extraction/Entities/Base/IEntity.cs b/csharp/extractor/Semmle.Extraction/Entities/Base/IEntity.cs index 5aadbeb9e4d..dcf8dcbc373 100644 --- a/csharp/extractor/Semmle.Extraction/Entities/Base/IEntity.cs +++ b/csharp/extractor/Semmle.Extraction/Entities/Base/IEntity.cs @@ -29,14 +29,14 @@ namespace Semmle.Extraction /// Writes the unique identifier of this entitiy to a trap file. /// /// The trapfile to write to. - void WriteId(TextWriter trapFile); + void WriteId(EscapingTextWriter trapFile); /// /// Writes the quoted identifier of this entity, /// which could be @"..." or * /// /// The trapfile to write to. - void WriteQuotedId(TextWriter trapFile); + void WriteQuotedId(EscapingTextWriter trapFile); /// /// The location for reporting purposes. diff --git a/csharp/extractor/Semmle.Extraction/Entities/Base/LabelledEntity.cs b/csharp/extractor/Semmle.Extraction/Entities/Base/LabelledEntity.cs index 12a98ce2303..62d9cbd64be 100644 --- a/csharp/extractor/Semmle.Extraction/Entities/Base/LabelledEntity.cs +++ b/csharp/extractor/Semmle.Extraction/Entities/Base/LabelledEntity.cs @@ -7,12 +7,5 @@ namespace Semmle.Extraction protected LabelledEntity(Context cx) : base(cx) { } - - public override void WriteQuotedId(TextWriter trapFile) - { - trapFile.Write("@\""); - WriteId(trapFile); - trapFile.Write('\"'); - } } } diff --git a/csharp/extractor/Semmle.Extraction/Entities/Base/UnlabelledEntity.cs b/csharp/extractor/Semmle.Extraction/Entities/Base/UnlabelledEntity.cs index 6b6a22eb4b5..506a84bf7ad 100644 --- a/csharp/extractor/Semmle.Extraction/Entities/Base/UnlabelledEntity.cs +++ b/csharp/extractor/Semmle.Extraction/Entities/Base/UnlabelledEntity.cs @@ -9,14 +9,14 @@ namespace Semmle.Extraction cx.AddFreshLabel(this); } - public sealed override void WriteId(TextWriter writer) + public sealed override void WriteId(EscapingTextWriter writer) { writer.Write('*'); } - public sealed override void WriteQuotedId(TextWriter writer) + public sealed override void WriteQuotedId(EscapingTextWriter writer) { - WriteId(writer); + writer.Write('*'); } } } diff --git a/csharp/extractor/Semmle.Extraction/Entities/File.cs b/csharp/extractor/Semmle.Extraction/Entities/File.cs index ed8881f0d2f..11d37c99636 100644 --- a/csharp/extractor/Semmle.Extraction/Entities/File.cs +++ b/csharp/extractor/Semmle.Extraction/Entities/File.cs @@ -17,7 +17,7 @@ namespace Semmle.Extraction.Entities public override bool NeedsPopulation => true; - public override void WriteId(System.IO.TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write(TransformedPath.DatabaseId); trapFile.Write(";sourcefile"); diff --git a/csharp/extractor/Semmle.Extraction/Entities/Folder.cs b/csharp/extractor/Semmle.Extraction/Entities/Folder.cs index 82364c987e7..07e8c805e7f 100644 --- a/csharp/extractor/Semmle.Extraction/Entities/Folder.cs +++ b/csharp/extractor/Semmle.Extraction/Entities/Folder.cs @@ -15,7 +15,7 @@ namespace Semmle.Extraction.Entities public override bool NeedsPopulation => true; - public override void WriteId(System.IO.TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write(Symbol.DatabaseId); trapFile.Write(";folder"); diff --git a/csharp/extractor/Semmle.Extraction/Entities/GeneratedFile.cs b/csharp/extractor/Semmle.Extraction/Entities/GeneratedFile.cs index db458e574a5..7c0e5df7be9 100644 --- a/csharp/extractor/Semmle.Extraction/Entities/GeneratedFile.cs +++ b/csharp/extractor/Semmle.Extraction/Entities/GeneratedFile.cs @@ -13,7 +13,7 @@ namespace Semmle.Extraction.Entities trapFile.files(this, "", "", ""); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write("GENERATED;sourcefile"); } diff --git a/csharp/extractor/Semmle.Extraction/Entities/GeneratedLocation.cs b/csharp/extractor/Semmle.Extraction/Entities/GeneratedLocation.cs index 331e70262dc..db552f7e452 100644 --- a/csharp/extractor/Semmle.Extraction/Entities/GeneratedLocation.cs +++ b/csharp/extractor/Semmle.Extraction/Entities/GeneratedLocation.cs @@ -17,7 +17,7 @@ namespace Semmle.Extraction.Entities trapFile.locations_default(this, generatedFile, 0, 0, 0, 0); } - public override void WriteId(TextWriter trapFile) + public override void WriteId(EscapingTextWriter trapFile) { trapFile.Write("loc,"); trapFile.WriteSubId(generatedFile); diff --git a/csharp/extractor/Semmle.Extraction/EscapingTextWriter.cs b/csharp/extractor/Semmle.Extraction/EscapingTextWriter.cs new file mode 100644 index 00000000000..6294ec3ffd3 --- /dev/null +++ b/csharp/extractor/Semmle.Extraction/EscapingTextWriter.cs @@ -0,0 +1,340 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Semmle.Extraction +{ + /// + /// A `TextWriter` object that wraps another `TextWriter` object, and which + /// HTML escapes the characters `&`, `{`, `}`, `"`, `@`, and `#`, before + /// writing to the underlying object. + /// + public sealed class EscapingTextWriter : TextWriter + { + private readonly TextWriter wrapped; + private readonly bool disposeUnderlying; + + public EscapingTextWriter(TextWriter wrapped, bool disposeUnderlying = false) + { + this.wrapped = wrapped; + this.disposeUnderlying = disposeUnderlying; + } + + /// + /// Creates a new instance with a new underlying `StringWriter` object. The + /// underlying object is disposed of when this object is. + /// + public EscapingTextWriter() : this(new StringWriter(), true) { } + + public EscapingTextWriter(IFormatProvider? formatProvider) : base(formatProvider) + => throw new NotImplementedException(); + + private void WriteEscaped(char c) + { + switch (c) + { + case '&': + wrapped.Write("&"); + break; + case '{': + wrapped.Write("{"); + break; + case '}': + wrapped.Write("}"); + break; + case '"': + wrapped.Write("""); + break; + case '@': + wrapped.Write("@"); + break; + case '#': + wrapped.Write("#"); + break; + default: + wrapped.Write(c); + break; + }; + } + + public void WriteSubId(IEntity entity) + { + if (entity is null) + { + wrapped.Write(""); + return; + } + + WriteUnescaped('{'); + wrapped.WriteLabel(entity); + WriteUnescaped('}'); + } + + public void WriteUnescaped(char c) + => wrapped.Write(c); + + public void WriteUnescaped(string s) + => wrapped.Write(s); + + #region overrides + + public override Encoding Encoding => wrapped.Encoding; + + public override IFormatProvider FormatProvider => wrapped.FormatProvider; + + public override string NewLine { get => wrapped.NewLine; } + + public override void Close() + => throw new NotImplementedException(); + + public override ValueTask DisposeAsync() + => throw new NotImplementedException(); + + public override bool Equals(object? obj) + => wrapped.Equals(obj) && obj is EscapingTextWriter other && disposeUnderlying == other.disposeUnderlying; + + public override void Flush() + => wrapped.Flush(); + + public override Task FlushAsync() + => wrapped.FlushAsync(); + + public override int GetHashCode() + => HashCode.Combine(wrapped, disposeUnderlying); + + public override string ToString() + => wrapped.ToString() ?? ""; + + public override void Write(bool value) + => wrapped.Write(value); + + public override void Write(char value) + => WriteEscaped(value); + + public override void Write(char[]? buffer) + { + if (buffer is null) + return; + Write(buffer, 0, buffer.Length); + } + + public override void Write(char[] buffer, int index, int count) + { + for (var i = index; i < buffer.Length && i < index + count; i++) + { + WriteEscaped(buffer[i]); + } + } + + + public override void Write(decimal value) + => wrapped.Write(value); + + public override void Write(double value) + => wrapped.Write(value); + + public override void Write(int value) + => wrapped.Write(value); + + public override void Write(long value) + => wrapped.Write(value); + + public override void Write(object? value) + => Write(value?.ToString()); + + public override void Write(ReadOnlySpan buffer) + { + foreach (var c in buffer) + { + WriteEscaped(c); + } + } + + public override void Write(float value) + => wrapped.Write(value); + + public override void Write(string? value) + { + if (value is null) + { + wrapped.Write(value); + } + else + { + foreach (var c in value) + { + WriteEscaped(c); + } + } + } + + public override void Write(string format, object? arg0) + => Write(string.Format(format, arg0)); + + public override void Write(string format, object? arg0, object? arg1) + => Write(string.Format(format, arg0, arg1)); + + public override void Write(string format, object? arg0, object? arg1, object? arg2) + => Write(string.Format(format, arg0, arg1, arg2)); + + public override void Write(string format, params object?[] arg) + => Write(string.Format(format, arg)); + + public override void Write(StringBuilder? value) + { + if (value is null) + { + wrapped.Write(value); + } + else + { + for (var i = 0; i < value.Length; i++) + { + WriteEscaped(value[i]); + } + } + } + + public override void Write(uint value) + => wrapped.Write(value); + + public override void Write(ulong value) + => wrapped.Write(value); + + public override Task WriteAsync(char value) + => throw new NotImplementedException(); + + public override Task WriteAsync(char[] buffer, int index, int count) + => throw new NotImplementedException(); + + public override Task WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + public override Task WriteAsync(string? value) + => throw new NotImplementedException(); + + public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + public override void WriteLine() + => wrapped.WriteLine(); + + public override void WriteLine(bool value) + => wrapped.WriteLine(value); + + public override void WriteLine(char value) + { + Write(value); + WriteLine(); + } + + public override void WriteLine(char[]? buffer) + { + Write(buffer); + WriteLine(); + } + + public override void WriteLine(char[] buffer, int index, int count) + { + Write(buffer, index, count); + WriteLine(); + } + + public override void WriteLine(decimal value) + => wrapped.WriteLine(value); + + public override void WriteLine(double value) + => wrapped.WriteLine(value); + + public override void WriteLine(int value) + => wrapped.WriteLine(value); + + public override void WriteLine(long value) + => wrapped.WriteLine(value); + + public override void WriteLine(object? value) + { + Write(value); + WriteLine(); + } + + public override void WriteLine(ReadOnlySpan buffer) + { + Write(buffer); + WriteLine(); + } + + public override void WriteLine(float value) + => wrapped.WriteLine(value); + + public override void WriteLine(string? value) + { + Write(value); + WriteLine(); + } + + public override void WriteLine(string format, object? arg0) + { + Write(format, arg0); + WriteLine(); + } + + public override void WriteLine(string format, object? arg0, object? arg1) + { + Write(format, arg0, arg1); + WriteLine(); + } + + public override void WriteLine(string format, object? arg0, object? arg1, object? arg2) + { + Write(format, arg0, arg1, arg2); + WriteLine(); + } + + public override void WriteLine(string format, params object?[] arg) + { + Write(format, arg); + WriteLine(); + } + + public override void WriteLine(StringBuilder? value) + { + Write(value); + WriteLine(); + } + + public override void WriteLine(uint value) + => wrapped.WriteLine(value); + + public override void WriteLine(ulong value) + => wrapped.WriteLine(value); + + public override Task WriteLineAsync() + => throw new NotImplementedException(); + + public override Task WriteLineAsync(char value) + => throw new NotImplementedException(); + + public override Task WriteLineAsync(char[] buffer, int index, int count) + => throw new NotImplementedException(); + + public override Task WriteLineAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + public override Task WriteLineAsync(string? value) + => throw new NotImplementedException(); + + public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + protected override void Dispose(bool disposing) + { + if (disposing && disposeUnderlying) + wrapped.Dispose(); + } + + #endregion overrides + } +} diff --git a/csharp/extractor/Semmle.Extraction/TrapExtensions.cs b/csharp/extractor/Semmle.Extraction/TrapExtensions.cs index 11111467147..e1343019335 100644 --- a/csharp/extractor/Semmle.Extraction/TrapExtensions.cs +++ b/csharp/extractor/Semmle.Extraction/TrapExtensions.cs @@ -17,19 +17,6 @@ namespace Semmle.Extraction trapFile.WriteLabel(entity.Label.Value); } - public static void WriteSubId(this TextWriter trapFile, IEntity entity) - { - if (entity is null) - { - trapFile.Write(""); - return; - } - - trapFile.Write('{'); - trapFile.WriteLabel(entity); - trapFile.Write('}'); - } - public static void WriteSeparator(this TextWriter trapFile, string separator, ref int index) { if (index++ > 0) @@ -130,7 +117,7 @@ namespace Semmle.Extraction return s; } - private static string EncodeString(string s) => s.Replace("\"", "\"\""); + public static string EncodeString(string s) => s.Replace("\"", "\"\""); /// /// Output a string to the trap file, such that the encoded output does not exceed @@ -231,9 +218,9 @@ namespace Semmle.Extraction /// The separator string (e.g. ",") /// The list of items. /// The original trap builder (fluent interface). - public static TextWriter AppendList(this TextWriter trapFile, string separator, IEnumerable items) where T : IEntity + public static TextWriter AppendList(this EscapingTextWriter trapFile, string separator, IEnumerable items) where T : IEntity { - return trapFile.BuildList(separator, items, (x, tb0) => { tb0.WriteSubId(x); }); + return trapFile.BuildList(separator, items, x => trapFile.WriteSubId(x)); } /// @@ -245,7 +232,8 @@ namespace Semmle.Extraction /// The list of items. /// The action on each item. /// The original trap builder (fluent interface). - public static TextWriter BuildList(this TextWriter trapFile, string separator, IEnumerable items, Action action) + public static T1 BuildList(this T1 trapFile, string separator, IEnumerable items, Action action) + where T1 : TextWriter { var first = true; foreach (var item in items) @@ -254,7 +242,7 @@ namespace Semmle.Extraction first = false; else trapFile.Write(separator); - action(item, trapFile); + action(item); } return trapFile; } diff --git a/csharp/ql/src/Metrics/Summaries/LinesOfCode.ql b/csharp/ql/src/Metrics/Summaries/LinesOfCode.ql index 9156a5e4a7f..e93e3c7416f 100644 --- a/csharp/ql/src/Metrics/Summaries/LinesOfCode.ql +++ b/csharp/ql/src/Metrics/Summaries/LinesOfCode.ql @@ -4,6 +4,7 @@ * @description The total number of lines of code across all files. This is a useful metric of the size of a database. For all files that were seen during the build, this query counts the lines of code, excluding whitespace or comments. * @kind metric * @tags summary + * lines-of-code */ import csharp diff --git a/csharp/ql/src/experimental/ir/implementation/internal/TInstruction.qll b/csharp/ql/src/experimental/ir/implementation/internal/TInstruction.qll index e16b71733b5..4b3f19cbdde 100644 --- a/csharp/ql/src/experimental/ir/implementation/internal/TInstruction.qll +++ b/csharp/ql/src/experimental/ir/implementation/internal/TInstruction.qll @@ -55,6 +55,8 @@ module UnaliasedSSAInstructions { result = TUnaliasedSSAPhiInstruction(blockStartInstr, memoryLocation) } + TRawInstruction reusedPhiInstruction(TRawInstruction blockStartInstr) { none() } + class TChiInstruction = TUnaliasedSSAChiInstruction; TChiInstruction chiInstruction(TRawInstruction primaryInstruction) { @@ -75,7 +77,7 @@ module UnaliasedSSAInstructions { * a class alias. */ module AliasedSSAInstructions { - class TPhiInstruction = TAliasedSSAPhiInstruction; + class TPhiInstruction = TAliasedSSAPhiInstruction or TUnaliasedSSAPhiInstruction; TPhiInstruction phiInstruction( TRawInstruction blockStartInstr, AliasedSSA::SSA::MemoryLocation memoryLocation @@ -83,6 +85,10 @@ module AliasedSSAInstructions { result = TAliasedSSAPhiInstruction(blockStartInstr, memoryLocation) } + TPhiInstruction reusedPhiInstruction(TRawInstruction blockStartInstr) { + result = TUnaliasedSSAPhiInstruction(blockStartInstr, _) + } + class TChiInstruction = TAliasedSSAChiInstruction; TChiInstruction chiInstruction(TRawInstruction primaryInstruction) { diff --git a/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll index 9c3e7620186..20fe77f8223 100644 --- a/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll +++ b/csharp/ql/src/experimental/ir/implementation/internal/TOperand.qll @@ -92,6 +92,16 @@ module RawOperands { none() } + /** + * Returns the Phi operand with the specified parameters. + */ + TPhiOperand reusedPhiOperand( + Raw::PhiInstruction useInstr, Raw::Instruction defInstr, Raw::IRBlock predecessorBlock, + Overlap overlap + ) { + none() + } + /** * Returns the Chi operand with the specified parameters. */ @@ -123,6 +133,16 @@ module UnaliasedSSAOperands { result = Internal::TUnaliasedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) } + /** + * Returns the Phi operand with the specified parameters. + */ + TPhiOperand reusedPhiOperand( + Unaliased::PhiInstruction useInstr, Unaliased::Instruction defInstr, + Unaliased::IRBlock predecessorBlock, Overlap overlap + ) { + none() + } + /** * Returns the Chi operand with the specified parameters. */ diff --git a/csharp/ql/src/experimental/ir/implementation/raw/Instruction.qll b/csharp/ql/src/experimental/ir/implementation/raw/Instruction.qll index e335080ad6b..453838215ff 100644 --- a/csharp/ql/src/experimental/ir/implementation/raw/Instruction.qll +++ b/csharp/ql/src/experimental/ir/implementation/raw/Instruction.qll @@ -1994,6 +1994,14 @@ class PhiInstruction extends Instruction { */ pragma[noinline] final Instruction getAnInput() { result = this.getAnInputOperand().getDef() } + + /** + * Gets the input operand representing the value that flows from the specified predecessor block. + */ + final PhiInputOperand getInputOperand(IRBlock predecessorBlock) { + result = this.getAnOperand() and + result.getPredecessorBlock() = predecessorBlock + } } /** diff --git a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll index a2ce0662dc2..d7cf89ca9aa 100644 --- a/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/raw/Operand.qll @@ -28,11 +28,15 @@ class Operand extends TStageOperand { cached Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) or + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or exists(Instruction use, Instruction def, IRBlock predecessorBlock | - this = phiOperand(use, def, predecessorBlock, _) - ) or + this = phiOperand(use, def, predecessorBlock, _) or + this = reusedPhiOperand(use, def, predecessorBlock, _) + ) + or exists(Instruction use | this = chiOperand(use, _)) } @@ -431,7 +435,11 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand { Overlap overlap; cached - PhiInputOperand() { this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) } + PhiInputOperand() { + this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) + or + this = reusedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + } override string toString() { result = "Phi" } diff --git a/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Common.qll b/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Common.qll index 267cf903b00..ef40d716fea 100644 --- a/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Common.qll +++ b/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Common.qll @@ -182,15 +182,21 @@ abstract class TranslatedCompilerGeneratedVariableAccess extends TranslatedCompi override Instruction getChildSuccessor(TranslatedElement child) { none() } + /** + * Returns the type of the accessed variable. Can be overriden when the return + * type is different than the type of the underlying variable. + */ + Type getVariableType() { result = getResultType() } + override predicate hasInstruction(Opcode opcode, InstructionTag tag, CSharpType resultType) { tag = AddressTag() and opcode instanceof Opcode::VariableAddress and - resultType = getTypeForGLValue(getResultType()) + resultType = getTypeForGLValue(getVariableType()) or needsLoad() and tag = LoadTag() and opcode instanceof Opcode::Load and - resultType = getTypeForPRValue(getResultType()) + resultType = getTypeForPRValue(getVariableType()) } override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { diff --git a/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Foreach.qll b/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Foreach.qll index a526ec5bdcd..195072ed124 100644 --- a/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Foreach.qll +++ b/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Foreach.qll @@ -359,9 +359,16 @@ private class TranslatedMoveNextEnumAcc extends TTranslatedCompilerGeneratedElem override Type getResultType() { result instanceof BoolType } + override Type getVariableType() { + exists(TranslatedForeachGetEnumerator ge | + ge.getAST() = generatedBy and + result = ge.getCallResultType() + ) + } + override predicate hasTempVariable(TempVariableTag tag, CSharpType type) { tag = ForeachEnumTempVar() and - type = getTypeForPRValue(getResultType()) + type = getTypeForPRValue(getVariableType()) } override IRVariable getInstructionVariable(InstructionTag tag) { @@ -384,9 +391,16 @@ private class TranslatedForeachCurrentEnumAcc extends TTranslatedCompilerGenerat override Type getResultType() { result instanceof BoolType } + override Type getVariableType() { + exists(TranslatedForeachGetEnumerator ge | + ge.getAST() = generatedBy and + result = ge.getCallResultType() + ) + } + override predicate hasTempVariable(TempVariableTag tag, CSharpType type) { tag = ForeachEnumTempVar() and - type = getTypeForPRValue(getResultType()) + type = getTypeForPRValue(getVariableType()) } override IRVariable getInstructionVariable(InstructionTag tag) { @@ -409,9 +423,16 @@ private class TranslatedForeachDisposeEnumAcc extends TTranslatedCompilerGenerat override Type getResultType() { result instanceof BoolType } + override Type getVariableType() { + exists(TranslatedForeachGetEnumerator ge | + ge.getAST() = generatedBy and + result = ge.getCallResultType() + ) + } + override predicate hasTempVariable(TempVariableTag tag, CSharpType type) { tag = ForeachEnumTempVar() and - type = getTypeForPRValue(getResultType()) + type = getTypeForPRValue(getVariableType()) } override IRVariable getInstructionVariable(InstructionTag tag) { diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Instruction.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Instruction.qll index e335080ad6b..453838215ff 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Instruction.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Instruction.qll @@ -1994,6 +1994,14 @@ class PhiInstruction extends Instruction { */ pragma[noinline] final Instruction getAnInput() { result = this.getAnInputOperand().getDef() } + + /** + * Gets the input operand representing the value that flows from the specified predecessor block. + */ + final PhiInputOperand getInputOperand(IRBlock predecessorBlock) { + result = this.getAnOperand() and + result.getPredecessorBlock() = predecessorBlock + } } /** diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll index a2ce0662dc2..d7cf89ca9aa 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll @@ -28,11 +28,15 @@ class Operand extends TStageOperand { cached Operand() { // Ensure that the operand does not refer to instructions from earlier stages that are unreachable here - exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) or - exists(Instruction use | this = nonSSAMemoryOperand(use, _)) or + exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) + or + exists(Instruction use | this = nonSSAMemoryOperand(use, _)) + or exists(Instruction use, Instruction def, IRBlock predecessorBlock | - this = phiOperand(use, def, predecessorBlock, _) - ) or + this = phiOperand(use, def, predecessorBlock, _) or + this = reusedPhiOperand(use, def, predecessorBlock, _) + ) + or exists(Instruction use | this = chiOperand(use, _)) } @@ -431,7 +435,11 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand { Overlap overlap; cached - PhiInputOperand() { this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) } + PhiInputOperand() { + this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) + or + this = reusedPhiOperand(useInstr, defInstr, predecessorBlock, overlap) + } override string toString() { result = "Phi" } diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll index e26d61b9792..181c6d2c464 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll @@ -90,6 +90,9 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) { or // Converting an address to a `bool` does not escape the address. instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType + or + instr instanceof CallInstruction and + not exists(IREscapeAnalysisConfiguration config | config.useSoundEscapeAnalysis()) ) ) or @@ -284,14 +287,24 @@ private predicate isArgumentForParameter( private predicate isOnlyEscapesViaReturnArgument(Operand operand) { exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and - f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) + ( + f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) + or + f.parameterEscapesOnlyViaReturn(-1) and + operand instanceof ThisArgumentOperand + ) ) } private predicate isNeverEscapesArgument(Operand operand) { exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and - f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) + ( + f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) + or + f.parameterNeverEscapes(-1) and + operand instanceof ThisArgumentOperand + ) ) } @@ -325,6 +338,9 @@ predicate allocationEscapes(Configuration::Allocation allocation) { exists(IREscapeAnalysisConfiguration config | config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction()) ) + or + Configuration::phaseNeedsSoundEscapeAnalysis() and + resultEscapesNonReturn(allocation.getABaseInstruction()) } /** @@ -400,3 +416,46 @@ predicate addressOperandAllocationAndOffset( ) ) } + +/** + * Predicates used only for printing annotated IR dumps. These should not be used in production + * queries. + */ +module Print { + string getOperandProperty(Operand operand, string key) { + key = "alloc" and + result = + strictconcat(Configuration::Allocation allocation, IntValue bitOffset | + addressOperandAllocationAndOffset(operand, allocation, bitOffset) + | + allocation.toString() + Ints::getBitOffsetString(bitOffset), ", " + ) + or + key = "prop" and + result = + strictconcat(Instruction destInstr, IntValue bitOffset, string value | + operandIsPropagatedIncludingByCall(operand, bitOffset, destInstr) and + if destInstr = operand.getUse() + then value = "@" + Ints::getBitOffsetString(bitOffset) + "->result" + else value = "@" + Ints::getBitOffsetString(bitOffset) + "->" + destInstr.getResultId() + | + value, ", " + ) + } + + string getInstructionProperty(Instruction instr, string key) { + key = "prop" and + result = + strictconcat(IntValue bitOffset, Operand sourceOperand, string value | + operandIsPropagatedIncludingByCall(sourceOperand, bitOffset, instr) and + if instr = sourceOperand.getUse() + then value = sourceOperand.getDumpId() + Ints::getBitOffsetString(bitOffset) + "->@" + else + value = + sourceOperand.getUse().getResultId() + "." + sourceOperand.getDumpId() + + Ints::getBitOffsetString(bitOffset) + "->@" + | + value, ", " + ) + } +} diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll index 5be476e12ee..dbdd3c14c85 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll @@ -14,3 +14,5 @@ class Allocation extends IRAutomaticVariable { none() } } + +predicate phaseNeedsSoundEscapeAnalysis() { any() } diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 1ba19b4a4c3..5092e921cb3 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -43,24 +43,81 @@ private module Cached { class TStageInstruction = TRawInstruction or TPhiInstruction or TChiInstruction or TUnreachedInstruction; + /** + * If `oldInstruction` is a `Phi` instruction that has exactly one reachable predecessor block, + * this predicate returns the `PhiInputOperand` corresponding to that predecessor block. + * Otherwise, this predicate does not hold. + */ + private OldIR::PhiInputOperand getDegeneratePhiOperand(OldInstruction oldInstruction) { + result = + unique(OldIR::PhiInputOperand operand | + operand = oldInstruction.(OldIR::PhiInstruction).getAnInputOperand() and + operand.getPredecessorBlock() instanceof OldBlock + ) + } + cached predicate hasInstruction(TStageInstruction instr) { instr instanceof TRawInstruction and instr instanceof OldInstruction or - instr instanceof TPhiInstruction + instr = phiInstruction(_, _) + or + instr = reusedPhiInstruction(_) and + // Check that the phi instruction is *not* degenerate, but we can't use + // getDegeneratePhiOperand in the first stage with phi instyructions + not exists( + unique(OldIR::PhiInputOperand operand | + operand = instr.(OldIR::PhiInstruction).getAnInputOperand() and + operand.getPredecessorBlock() instanceof OldBlock + ) + ) or instr instanceof TChiInstruction or instr instanceof TUnreachedInstruction } - private IRBlock getNewBlock(OldBlock oldBlock) { - result.getFirstInstruction() = getNewInstruction(oldBlock.getFirstInstruction()) + cached + IRBlock getNewBlock(OldBlock oldBlock) { + exists(Instruction newEnd, OldIR::Instruction oldEnd | + ( + result.getLastInstruction() = newEnd and + not newEnd instanceof ChiInstruction + or + newEnd = result.getLastInstruction().(ChiInstruction).getAPredecessor() // does this work? + ) and + ( + oldBlock.getLastInstruction() = oldEnd and + not oldEnd instanceof OldIR::ChiInstruction + or + oldEnd = oldBlock.getLastInstruction().(OldIR::ChiInstruction).getAPredecessor() // does this work? + ) and + oldEnd = getNewInstruction(newEnd) + ) + } + + /** + * Gets the block from the old IR that corresponds to `newBlock`. + */ + private OldBlock getOldBlock(IRBlock newBlock) { getNewBlock(result) = newBlock } + + /** + * Holds if this iteration of SSA can model the def/use information for the result of + * `oldInstruction`, either because alias analysis has determined a memory location for that + * result, or because a previous iteration of the IR already computed that def/use information + * completely. + */ + private predicate canModelResultForOldInstruction(OldInstruction oldInstruction) { + // We're modeling the result's memory location ourselves. + exists(Alias::getResultMemoryLocation(oldInstruction)) + or + // This result was already modeled by a previous iteration of SSA. + Alias::canReuseSSAForOldResult(oldInstruction) } cached predicate hasModeledMemoryResult(Instruction instruction) { - exists(Alias::getResultMemoryLocation(getOldInstruction(instruction))) or + canModelResultForOldInstruction(getOldInstruction(instruction)) or instruction instanceof PhiInstruction or // Phis always have modeled results instruction instanceof ChiInstruction // Chis always have modeled results } @@ -117,6 +174,32 @@ private module Cached { ) } + /** + * Gets the new definition instruction for `oldOperand` based on `oldOperand`'s definition in the + * old IR. Usually, this will just get the old definition of `oldOperand` and map it to the + * corresponding new instruction. However, if the old definition of `oldOperand` is a `Phi` + * instruction that is now degenerate due all but one of its predecessor branches being + * unreachable, this predicate will recurse through any degenerate `Phi` instructions to find the + * true definition. + */ + private Instruction getNewDefinitionFromOldSSA(OldIR::MemoryOperand oldOperand, Overlap overlap) { + exists(Overlap originalOverlap | + originalOverlap = oldOperand.getDefinitionOverlap() and + ( + result = getNewInstruction(oldOperand.getAnyDef()) and + overlap = originalOverlap + or + exists(OldIR::PhiInputOperand phiOperand, Overlap phiOperandOverlap | + phiOperand = getDegeneratePhiOperand(oldOperand.getAnyDef()) and + result = getNewDefinitionFromOldSSA(phiOperand, phiOperandOverlap) and + overlap = + combineOverlap(pragma[only_bind_out](phiOperandOverlap), + pragma[only_bind_out](originalOverlap)) + ) + ) + ) + } + cached private Instruction getMemoryOperandDefinition0( Instruction instruction, MemoryOperandTag tag, Overlap overlap @@ -148,6 +231,12 @@ private module Cached { overlap instanceof MustExactlyOverlap and exists(MustTotallyOverlap o | exists(getMemoryOperandDefinition0(instruction, tag, o))) ) + or + exists(OldIR::NonPhiMemoryOperand oldOperand | + result = getNewDefinitionFromOldSSA(oldOperand, overlap) and + oldOperand.getUse() = instruction and + tag = oldOperand.getOperandTag() + ) } /** @@ -214,10 +303,24 @@ private module Cached { ) } + /** + * Gets the new definition instruction for the operand of `instr` that flows from the block + * `newPredecessorBlock`, based on that operand's definition in the old IR. + */ + private Instruction getNewPhiOperandDefinitionFromOldSSA( + Instruction instr, IRBlock newPredecessorBlock, Overlap overlap + ) { + exists(OldIR::PhiInstruction oldPhi, OldIR::PhiInputOperand oldOperand | + oldPhi = getOldInstruction(instr) and + oldOperand = oldPhi.getInputOperand(getOldBlock(newPredecessorBlock)) and + result = getNewDefinitionFromOldSSA(oldOperand, overlap) + ) + } + pragma[noopt] cached Instruction getPhiOperandDefinition( - PhiInstruction instr, IRBlock newPredecessorBlock, Overlap overlap + Instruction instr, IRBlock newPredecessorBlock, Overlap overlap ) { exists( Alias::MemoryLocation defLocation, Alias::MemoryLocation useLocation, OldBlock phiBlock, @@ -229,6 +332,8 @@ private module Cached { result = getDefinitionOrChiInstruction(defBlock, defOffset, defLocation, actualDefLocation) and overlap = Alias::getOverlap(actualDefLocation, useLocation) ) + or + result = getNewPhiOperandDefinitionFromOldSSA(instr, newPredecessorBlock, overlap) } cached @@ -249,7 +354,12 @@ private module Cached { cached Instruction getPhiInstructionBlockStart(PhiInstruction instr) { exists(OldBlock oldBlock | - instr = getPhi(oldBlock, _) and + ( + instr = getPhi(oldBlock, _) + or + // Any `Phi` that we propagated from the previous iteration stays in the same block. + getOldInstruction(instr).getBlock() = oldBlock + ) and result = getNewInstruction(oldBlock.getFirstInstruction()) ) } @@ -335,6 +445,9 @@ private module Cached { result = vvar.getType() ) or + instr = reusedPhiInstruction(_) and + result = instr.(OldInstruction).getResultLanguageType() + or instr = unreachedInstruction(_) and result = Language::getVoidType() } @@ -862,6 +975,26 @@ module DefUse { } } +predicate canReuseSSAForMemoryResult(Instruction instruction) { + exists(OldInstruction oldInstruction | + oldInstruction = getOldInstruction(instruction) and + ( + // The previous iteration said it was reusable, so we should mark it as reusable as well. + Alias::canReuseSSAForOldResult(oldInstruction) + or + // The current alias analysis says it is reusable. + Alias::getResultMemoryLocation(oldInstruction).canReuseSSA() + ) + ) + or + exists(Alias::MemoryLocation defLocation | + // This is a `Phi` for a reusable location, so the result of the `Phi` is reusable as well. + instruction = phiInstruction(_, defLocation) and + defLocation.canReuseSSA() + ) + // We don't support reusing SSA for any location that could create a `Chi` instruction. +} + /** * Expose some of the internal predicates to PrintSSA.qll. We do this by publically importing those modules in the * `DebugSSA` module, which is then imported by PrintSSA. @@ -940,7 +1073,10 @@ module SSAConsistency { locationCount > 1 and func = operand.getEnclosingIRFunction() and funcText = Language::getIdentityString(func.getFunction()) and - message = "Operand has " + locationCount.toString() + " memory accesses in function '$@'." + message = + operand.getUse().toString() + " " + "Operand has " + locationCount.toString() + + " memory accesses in function '$@': " + + strictconcat(Alias::getOperandMemoryLocation(operand).toString(), ", ") ) } diff --git a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll index a7b9160bdc7..f3e02c9f6a8 100644 --- a/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll +++ b/csharp/ql/src/experimental/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll @@ -17,7 +17,7 @@ private predicate isTotalAccess(Allocation var, AddressOperand addrOperand, IRTy * variable if its address never escapes and all reads and writes of that variable access the entire * variable using the original type of the variable. */ -private predicate isVariableModeled(Allocation var) { +predicate isVariableModeled(Allocation var) { not allocationEscapes(var) and forall(Instruction instr, AddressOperand addrOperand, IRType type | addrOperand = instr.getResultAddressOperand() and @@ -35,6 +35,17 @@ private predicate isVariableModeled(Allocation var) { ) } +/** + * Holds if the SSA use/def chain for the specified variable can be safely reused by later + * iterations of SSA construction. This will hold only if we modeled the variable soundly, so that + * subsequent iterations will recompute SSA for any variable that we assumed did not escape, but + * actually would have escaped if we had used a sound escape analysis. + */ +predicate canReuseSSAForVariable(IRAutomaticVariable var) { + isVariableModeled(var) and + not allocationEscapes(var) +} + private newtype TMemoryLocation = MkMemoryLocation(Allocation var) { isVariableModeled(var) } private MemoryLocation getMemoryLocation(Allocation var) { result.getAllocation() = var } @@ -57,8 +68,12 @@ class MemoryLocation extends TMemoryLocation { final Language::LanguageType getType() { result = var.getLanguageType() } final string getUniqueId() { result = var.getUniqueId() } + + final predicate canReuseSSA() { canReuseSSAForVariable(var) } } +predicate canReuseSSAForOldResult(Instruction instr) { none() } + /** * Represents a set of `MemoryLocation`s that cannot overlap with * `MemoryLocation`s outside of the set. The `VirtualVariable` will be diff --git a/csharp/ql/src/experimental/ir/internal/IRGuards.qll b/csharp/ql/src/experimental/ir/internal/IRGuards.qll index a505e54c37e..d01dcbed1e1 100644 --- a/csharp/ql/src/experimental/ir/internal/IRGuards.qll +++ b/csharp/ql/src/experimental/ir/internal/IRGuards.qll @@ -151,17 +151,17 @@ private class GuardConditionFromBinaryLogicalOperator extends GuardCondition { ) } - override predicate comparesEq(Expr left, Expr right, int k, boolean isLessThan, boolean testIsTrue) { + override predicate comparesEq(Expr left, Expr right, int k, boolean areEqual, boolean testIsTrue) { exists(boolean partIsTrue, GuardCondition part | impliesValue(this.(BinaryLogicalOperation), part, partIsTrue, testIsTrue) | - part.comparesEq(left, right, k, isLessThan, partIsTrue) + part.comparesEq(left, right, k, areEqual, partIsTrue) ) } - override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean isLessThan) { + override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean areEqual) { exists(boolean testIsTrue | - comparesEq(left, right, k, isLessThan, testIsTrue) and this.controls(block, testIsTrue) + comparesEq(left, right, k, areEqual, testIsTrue) and this.controls(block, testIsTrue) ) } } @@ -180,20 +180,20 @@ private class GuardConditionFromShortCircuitNot extends GuardCondition, LogicalN getOperand().(GuardCondition).controls(controlled, testIsTrue.booleanNot()) } - override predicate comparesLt(Expr left, Expr right, int k, boolean areEqual, boolean testIsTrue) { - getOperand().(GuardCondition).comparesLt(left, right, k, areEqual, testIsTrue.booleanNot()) + override predicate comparesLt(Expr left, Expr right, int k, boolean isLessThan, boolean testIsTrue) { + getOperand().(GuardCondition).comparesLt(left, right, k, isLessThan, testIsTrue.booleanNot()) } - override predicate ensuresLt(Expr left, Expr right, int k, BasicBlock block, boolean testIsTrue) { - getOperand().(GuardCondition).ensuresLt(left, right, k, block, testIsTrue.booleanNot()) + override predicate ensuresLt(Expr left, Expr right, int k, BasicBlock block, boolean isLessThan) { + getOperand().(GuardCondition).ensuresLt(left, right, k, block, isLessThan.booleanNot()) } override predicate comparesEq(Expr left, Expr right, int k, boolean areEqual, boolean testIsTrue) { getOperand().(GuardCondition).comparesEq(left, right, k, areEqual, testIsTrue.booleanNot()) } - override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean testIsTrue) { - getOperand().(GuardCondition).ensuresEq(left, right, k, block, testIsTrue.booleanNot()) + override predicate ensuresEq(Expr left, Expr right, int k, BasicBlock block, boolean areEqual) { + getOperand().(GuardCondition).ensuresEq(left, right, k, block, areEqual.booleanNot()) } } diff --git a/csharp/ql/src/experimental/ir/internal/Overlap.qll b/csharp/ql/src/experimental/ir/internal/Overlap.qll index f9a0c574f8c..ca643b56cbb 100644 --- a/csharp/ql/src/experimental/ir/internal/Overlap.qll +++ b/csharp/ql/src/experimental/ir/internal/Overlap.qll @@ -8,6 +8,16 @@ private newtype TOverlap = */ abstract class Overlap extends TOverlap { abstract string toString(); + + /** + * Gets a value representing how precise this overlap is. The higher the value, the more precise + * the overlap. The precision values are ordered as + * follows, from most to least precise: + * `MustExactlyOverlap` + * `MustTotallyOverlap` + * `MayPartiallyOverlap` + */ + abstract int getPrecision(); } /** @@ -16,6 +26,8 @@ abstract class Overlap extends TOverlap { */ class MayPartiallyOverlap extends Overlap, TMayPartiallyOverlap { final override string toString() { result = "MayPartiallyOverlap" } + + final override int getPrecision() { result = 0 } } /** @@ -24,6 +36,8 @@ class MayPartiallyOverlap extends Overlap, TMayPartiallyOverlap { */ class MustTotallyOverlap extends Overlap, TMustTotallyOverlap { final override string toString() { result = "MustTotallyOverlap" } + + final override int getPrecision() { result = 1 } } /** @@ -32,4 +46,25 @@ class MustTotallyOverlap extends Overlap, TMustTotallyOverlap { */ class MustExactlyOverlap extends Overlap, TMustExactlyOverlap { final override string toString() { result = "MustExactlyOverlap" } + + final override int getPrecision() { result = 2 } +} + +/** + * Gets the `Overlap` that best represents the relationship between two memory locations `a` and + * `c`, where `getOverlap(a, b) = previousOverlap` and `getOverlap(b, c) = newOverlap`, for some + * intermediate memory location `b`. + */ +Overlap combineOverlap(Overlap previousOverlap, Overlap newOverlap) { + // Note that it's possible that two less precise overlaps could combine to result in a more + // precise overlap. For example, both `previousOverlap` and `newOverlap` could be + // `MustTotallyOverlap` even though the actual relationship between `a` and `c` is + // `MustExactlyOverlap`. We will still return `MustTotallyOverlap` as the best conservative + // approximation we can make without additional input information. + result = + min(Overlap overlap | + overlap = [previousOverlap, newOverlap] + | + overlap order by overlap.getPrecision() + ) } diff --git a/csharp/ql/src/semmle/code/csharp/Caching.qll b/csharp/ql/src/semmle/code/csharp/Caching.qll index 374ecaaa183..92417e34586 100644 --- a/csharp/ql/src/semmle/code/csharp/Caching.qll +++ b/csharp/ql/src/semmle/code/csharp/Caching.qll @@ -47,44 +47,6 @@ module Stages { } } - cached - module DataFlowStage { - private import semmle.code.csharp.dataflow.internal.DataFlowDispatch - private import semmle.code.csharp.dataflow.internal.DataFlowPrivate - private import semmle.code.csharp.dataflow.internal.DataFlowImplCommon - private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate - - cached - predicate forceCachingInSameStage() { any() } - - cached - private predicate forceCachingInSameStageRev() { - defaultAdditionalTaintStep(_, _) - or - any(ArgumentNode n).argumentOf(_, _) - or - exists(any(DataFlow::Node n).getEnclosingCallable()) - or - exists(any(DataFlow::Node n).getControlFlowNode()) - or - exists(any(DataFlow::Node n).getType()) - or - exists(any(NodeImpl n).getDataFlowType()) - or - exists(any(DataFlow::Node n).getLocation()) - or - exists(any(DataFlow::Node n).toString()) - or - exists(any(OutNode n).getCall(_)) - or - exists(CallContext cc) - or - exists(any(DataFlowCall c).getEnclosingCallable()) - or - forceCachingInSameStageRev() - } - } - cached module UnificationStage { private import semmle.code.csharp.Unification diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll b/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll index 8b20ccb3c22..b4448a71380 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll @@ -403,7 +403,7 @@ private module JoinBlockPredecessors { private import semmle.code.csharp.controlflow.internal.ControlFlowGraphImpl int getId(JoinBlockPredecessor jbp) { - exists(ControlFlowTree::Range t | ControlFlowTree::idOf(t, result) | + exists(ControlFlowTree::Range_ t | ControlFlowTree::idOf(t, result) | t = jbp.getFirstNode().getElement() or t = jbp.(EntryBasicBlock).getCallable() diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll index dcae798813a..7601b83f6b8 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll @@ -2,6 +2,7 @@ import csharp private import semmle.code.csharp.ExprOrStmtParent +private import semmle.code.csharp.commons.Compilation private import ControlFlow private import ControlFlow::BasicBlocks private import SuccessorTypes @@ -22,7 +23,12 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element { Callable getEnclosingCallable() { none() } /** Gets the assembly that this element was compiled into. */ - Assembly getAssembly() { result = this.getEnclosingCallable().getDeclaringType().getALocation() } + Assembly getAssembly() { + exists(Compilation c | + c.getAFileCompiled() = this.getFile() and + result = c.getOutputAssembly() + ) + } /** * Gets a control flow node for this element. That is, a node in the diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll index 8a721da5293..5a402717401 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll @@ -1466,8 +1466,10 @@ module Internal { PreSsa::Definition def, AssignableRead read ) { read = def.getAFirstRead() and - not exists(AssignableRead other | PreSsa::adjacentReadPairSameVar(other, read) | - other != read + ( + not PreSsa::adjacentReadPairSameVar(_, read) + or + read = unique(AssignableRead read0 | PreSsa::adjacentReadPairSameVar(read0, read)) ) } @@ -1651,10 +1653,14 @@ module Internal { AssignableRead read1, AssignableRead read2 ) { PreSsa::adjacentReadPairSameVar(read1, read2) and - not exists(AssignableRead other | - PreSsa::adjacentReadPairSameVar(other, read2) and - other != read1 and - other != read2 + ( + read1 = read2 and + read1 = unique(AssignableRead other | PreSsa::adjacentReadPairSameVar(other, read2)) + or + read1 = + unique(AssignableRead other | + PreSsa::adjacentReadPairSameVar(other, read2) and other != read2 + ) ) } @@ -1887,10 +1893,14 @@ module Internal { Ssa::Definition def, ControlFlow::Node cfn1, ControlFlow::Node cfn2 ) { SsaImpl::adjacentReadPairSameVar(def, cfn1, cfn2) and - not exists(ControlFlow::Node other | - SsaImpl::adjacentReadPairSameVar(def, other, cfn2) and - other != cfn1 and - other != cfn2 + ( + cfn1 = cfn2 and + cfn1 = unique(ControlFlow::Node other | SsaImpl::adjacentReadPairSameVar(def, other, cfn2)) + or + cfn1 = + unique(ControlFlow::Node other | + SsaImpl::adjacentReadPairSameVar(def, other, cfn2) and other != cfn2 + ) ) } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll index 089339ba1b8..bda14e0b4ae 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll @@ -293,18 +293,6 @@ private class Overflowable extends UnaryOperation { } } -private class CoreLib extends Assembly { - CoreLib() { this = any(SystemExceptionClass c).getALocation() } -} - -/** - * Holds if assembly `a` was definitely compiled with core library `core`. - */ -pragma[noinline] -private predicate assemblyCompiledWithCoreLib(Assembly a, CoreLib core) { - a.getAnAttribute().getType().getBaseClass*().(SystemAttributeClass).getALocation() = core -} - /** A control flow element that is inside a `try` block. */ private class TriedControlFlowElement extends ControlFlowElement { TryStmt try; @@ -317,7 +305,7 @@ private class TriedControlFlowElement extends ControlFlowElement { /** * Gets an exception class that is potentially thrown by this element, if any. */ - private Class getAThrownException0() { + Class getAThrownException() { this instanceof Overflowable and result instanceof SystemOverflowExceptionClass or @@ -376,41 +364,6 @@ private class TriedControlFlowElement extends ControlFlowElement { this instanceof DynamicExpr and result instanceof SystemExceptionClass } - - private CoreLib getCoreLibFromACatchClause() { - exists(SpecificCatchClause scc | scc = try.getACatchClause() | - result = scc.getCaughtExceptionType().getBaseClass*().(SystemExceptionClass).getALocation() - ) - } - - private CoreLib getCoreLib() { - result = this.getCoreLibFromACatchClause() - or - not exists(this.getCoreLibFromACatchClause()) and - assemblyCompiledWithCoreLib(this.getAssembly(), result) - } - - pragma[noinline] - private Class getAThrownExceptionFromPlausibleCoreLib(string name) { - result = this.getAThrownException0() and - name = result.getQualifiedName() and - ( - not exists(this.getCoreLib()) - or - this.getCoreLib() = result.getALocation() - ) - } - - Class getAThrownException() { - exists(string name | result = this.getAThrownExceptionFromPlausibleCoreLib(name) | - result = - min(Class c | - c = this.getAThrownExceptionFromPlausibleCoreLib(name) - | - c order by c.getLocation().(Assembly).getFullName() - ) - ) - } } pragma[nomagic] diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll index a0c0784c010..63f6943a29c 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll @@ -49,6 +49,27 @@ private import SuccessorType private import SuccessorTypes private import Splitting private import semmle.code.csharp.ExprOrStmtParent +private import semmle.code.csharp.commons.Compilation + +/** + * A compilation. + * + * Unlike the standard `Compilation` class, this class also supports buildless + * extraction. + */ +newtype CompilationExt = + TCompilation(Compilation c) { not extractionIsStandalone() } or + TBuildless() { extractionIsStandalone() } + +/** Gets the compilation that source file `f` belongs to. */ +CompilationExt getCompilation(SourceFile f) { + exists(Compilation c | + f = c.getAFileCompiled() and + result = TCompilation(c) + ) + or + result = TBuildless() +} /** An element that defines a new CFG scope. */ class CfgScope extends Element, @top_level_exprorstmt_parent { @@ -56,7 +77,7 @@ class CfgScope extends Element, @top_level_exprorstmt_parent { } module ControlFlowTree { - private class Range_ = @callable or @control_flow_element; + class Range_ = @callable or @control_flow_element; class Range extends Element, Range_ { Range() { this = getAChild*(any(CfgScope scope)) } @@ -67,9 +88,9 @@ module ControlFlowTree { result = p.(AssignOperation).getExpandedAssignment() } - private predicate id(Range x, Range y) { x = y } + private predicate id(Range_ x, Range_ y) { x = y } - predicate idOf(Range x, int y) = equivalenceRelation(id/2)(x, y) + predicate idOf(Range_ x, int y) = equivalenceRelation(id/2)(x, y) } abstract private class ControlFlowTree extends ControlFlowTree::Range { @@ -135,10 +156,7 @@ predicate scopeFirst(CfgScope scope, ControlFlowElement first) { then first(c.(Constructor).getInitializer(), first) else if InitializerSplitting::constructorInitializes(c, _) - then - first(any(InitializerSplitting::InitializedInstanceMember m | - InitializerSplitting::constructorInitializeOrder(c, m, 0) - ).getInitializer(), first) + then first(InitializerSplitting::constructorInitializeOrder(c, _, 0), first) else first(c.getBody(), first) ) or @@ -152,36 +170,36 @@ predicate scopeLast(Callable scope, ControlFlowElement last, Completion c) { last(scope.getBody(), last, c) and not c instanceof GotoCompletion or - exists(InitializerSplitting::InitializedInstanceMember m | - m = InitializerSplitting::lastConstructorInitializer(scope) and - last(m.getInitializer(), last, c) and - not scope.hasBody() - ) + last(InitializerSplitting::lastConstructorInitializer(scope, _), last, c) and + not scope.hasBody() } -private class CallableTree extends ControlFlowTree, Callable { +private class ConstructorTree extends ControlFlowTree, Constructor { final override predicate propagatesAbnormal(ControlFlowElement child) { none() } final override predicate first(ControlFlowElement first) { none() } final override predicate last(ControlFlowElement last, Completion c) { none() } + /** Gets the body of this constructor belonging to compilation `comp`. */ + pragma[noinline] + ControlFlowElement getBody(CompilationExt comp) { + result = this.getBody() and + comp = getCompilation(result.getFile()) + } + final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - exists(Constructor con, InitializerSplitting::InitializedInstanceMember m, int i | - this = con and - last(m.getInitializer(), pred, c) and - c instanceof NormalCompletion and - InitializerSplitting::constructorInitializeOrder(con, m, i) + exists(CompilationExt comp, int i, AssignExpr ae | + ae = InitializerSplitting::constructorInitializeOrder(this, comp, i) and + last(ae, pred, c) and + c instanceof NormalCompletion | // Flow from one member initializer to the next - exists(InitializerSplitting::InitializedInstanceMember next | - InitializerSplitting::constructorInitializeOrder(con, next, i + 1) and - first(next.getInitializer(), succ) - ) + first(InitializerSplitting::constructorInitializeOrder(this, comp, i + 1), succ) or // Flow from last member initializer to constructor body - m = InitializerSplitting::lastConstructorInitializer(con) and - first(con.getBody(), succ) + ae = InitializerSplitting::lastConstructorInitializer(this, comp) and + first(this.getBody(comp), succ) ) } } @@ -884,21 +902,18 @@ module Expressions { first(this.getChildElement(i + 1), succ) ) or - exists(Constructor con | + exists(ConstructorTree con, CompilationExt comp | last(this, pred, c) and con = this.getConstructor() and + comp = getCompilation(this.getFile()) and c instanceof NormalCompletion | // Flow from constructor initializer to first member initializer - exists(InitializerSplitting::InitializedInstanceMember m | - InitializerSplitting::constructorInitializeOrder(con, m, 0) - | - first(m.getInitializer(), succ) - ) + first(InitializerSplitting::constructorInitializeOrder(con, comp, 0), succ) or // Flow from constructor initializer to first element of constructor body - not InitializerSplitting::constructorInitializeOrder(con, _, _) and - first(con.getBody(), succ) + not exists(InitializerSplitting::constructorInitializeOrder(con, comp, _)) and + first(con.getBody(comp), succ) ) } } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/NonReturning.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/NonReturning.qll index 6d075d031f5..fe9f1148a6d 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/NonReturning.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/NonReturning.qll @@ -39,8 +39,10 @@ private class ThrowingCall extends NonReturningCall { or this.(FailingAssertion).getAssertionFailure().isException(c.getExceptionClass()) or - exists(CIL::Method m, CIL::Type ex | - this.getTarget().matchesHandle(m) and + exists(Callable target, CIL::Method m, CIL::Type ex | + target = this.getTarget() and + not target.hasBody() and + target.matchesHandle(m) and alwaysThrowsException(m, ex) and c.getExceptionClass().matchesHandle(ex) and not m.isVirtual() diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll index 498e6c461ae..8ca77a4775e 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll @@ -209,6 +209,13 @@ abstract class SplitImpl extends Split { or exists(ControlFlowElement pred | this.appliesTo(pred) | this.hasSuccessor(pred, cfe, _)) } + + /** The `succ` relation restricted to predecessors `pred` that this split applies to. */ + pragma[noinline] + final predicate appliesSucc(ControlFlowElement pred, ControlFlowElement succ, Completion c) { + this.appliesTo(pred) and + succ(pred, succ, c) + } } module InitializerSplitting { @@ -218,23 +225,23 @@ module InitializerSplitting { * A non-static member with an initializer, for example a field `int Field = 0`. */ class InitializedInstanceMember extends Member { - private AssignExpr ae; - InitializedInstanceMember() { - not this.isStatic() and - expr_parent_top_level_adjusted(ae, _, this) and - not ae = any(Callable c).getExpressionBody() + exists(AssignExpr ae | + not this.isStatic() and + expr_parent_top_level(ae, _, this) and + not ae = any(Callable c).getExpressionBody() + ) } /** Gets the initializer expression. */ - AssignExpr getInitializer() { result = ae } + AssignExpr getInitializer() { expr_parent_top_level(result, _, this) } /** * Gets a control flow element that is a syntactic descendant of the * initializer expression. */ ControlFlowElement getAnInitializerDescendant() { - result = ae + result = this.getInitializer() or result = this.getAnInitializerDescendant().getAChild() } @@ -242,35 +249,41 @@ module InitializerSplitting { /** * Holds if `c` is a non-static constructor that performs the initialization - * of member `m`. + * of a member via assignment `init`. */ - predicate constructorInitializes(InstanceConstructor c, InitializedInstanceMember m) { - c.isUnboundDeclaration() and - c.getDeclaringType().getAMember() = m and - not c.getInitializer().isThis() + predicate constructorInitializes(InstanceConstructor c, AssignExpr init) { + exists(InitializedInstanceMember m | + c.isUnboundDeclaration() and + c.getDeclaringType().getAMember() = m and + not c.getInitializer().isThis() and + init = m.getInitializer() + ) } /** - * Holds if `m` is the `i`th member initialized by non-static constructor `c`. + * Gets the `i`th member initializer expression for non-static constructor `c` + * in compilation `comp`. */ - predicate constructorInitializeOrder(Constructor c, InitializedInstanceMember m, int i) { - constructorInitializes(c, m) and - m = - rank[i + 1](InitializedInstanceMember m0 | - constructorInitializes(c, m0) + AssignExpr constructorInitializeOrder(Constructor c, CompilationExt comp, int i) { + constructorInitializes(c, result) and + result = + rank[i + 1](AssignExpr ae0, Location l | + constructorInitializes(c, ae0) and + l = ae0.getLocation() and + getCompilation(l.getFile()) = comp | - m0 - order by - m0.getLocation().getStartLine(), m0.getLocation().getStartColumn(), - m0.getLocation().getFile().getAbsolutePath() + ae0 order by l.getStartLine(), l.getStartColumn(), l.getFile().getAbsolutePath() ) } - /** Gets the last member initialized by non-static constructor `c`. */ - InitializedInstanceMember lastConstructorInitializer(Constructor c) { + /** + * Gets the last member initializer expression for non-static constructor `c` + * in compilation `comp`. + */ + AssignExpr lastConstructorInitializer(Constructor c, CompilationExt comp) { exists(int i | - constructorInitializeOrder(c, result, i) and - not constructorInitializeOrder(c, _, i + 1) + result = constructorInitializeOrder(c, comp, i) and + not exists(constructorInitializeOrder(c, comp, i + 1)) ) } @@ -376,11 +389,11 @@ module InitializerSplitting { } override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - this.appliesTo(pred) and - succ(pred, succ, c) and + this.appliesSucc(pred, succ, c) and succ = - any(InitializedInstanceMember m | constructorInitializes(this.getConstructor(), m)) - .getAnInitializerDescendant() + any(InitializedInstanceMember m | + constructorInitializes(this.getConstructor(), m.getInitializer()) + ).getAnInitializerDescendant() } } } @@ -613,8 +626,7 @@ module AssertionSplitting { } override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - this.appliesTo(pred) and - succ(pred, succ, c) and + this.appliesSucc(pred, succ, c) and succ = getAnAssertionDescendant(a) } } @@ -851,8 +863,7 @@ module FinallySplitting { } override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { - this.appliesToPredecessor(pred) and - succ(pred, succ, c) and + this.appliesSucc(pred, succ, c) and succ = any(FinallyControlFlowElement fcfe | if fcfe.isEntryNode() @@ -1030,7 +1041,7 @@ module ExceptionHandlerSplitting { override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesToPredecessor(pred, c) and - succ(pred, succ, c) and + this.appliesSucc(pred, succ, c) and not first(any(SpecificCatchClause scc).getBlock(), succ) and not succ instanceof GeneralCatchClause and not exists(TryStmt ts, SpecificCatchClause scc, int last | @@ -1204,14 +1215,12 @@ module BooleanSplitting { exists(Callable c, int r | c = kind.getEnclosingCallable() | result = r + ExceptionHandlerSplitting::getNextListOrder() - 1 and kind = - rank[r](BooleanSplitSubKind kind0 | + rank[r](BooleanSplitSubKind kind0, Location l | kind0.getEnclosingCallable() = c and - kind0.startsSplit(_) + kind0.startsSplit(_) and + l = kind0.getLocation() | - kind0 - order by - kind0.getLocation().getStartLine(), kind0.getLocation().getStartColumn(), - kind0.toString() + kind0 order by l.getStartLine(), l.getStartColumn(), kind0.toString() ) ) } @@ -1293,7 +1302,7 @@ module BooleanSplitting { override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { exists(PreBasicBlock bb, Completion c0 | this.appliesToBlock(bb, c0) | pred = bb.getAnElement() and - succ(pred, succ, c) and + this.appliesSucc(pred, succ, c) and ( pred = bb.getLastElement() implies @@ -1430,10 +1439,11 @@ module LoopSplitting { exists(Callable c, int r | c = enclosingCallable(loop) | result = r + BooleanSplitting::getNextListOrder() - 1 and loop = - rank[r](AnalyzableLoopStmt loop0 | - enclosingCallable(loop0) = c + rank[r](AnalyzableLoopStmt loop0, Location l | + enclosingCallable(loop0) = c and + l = loop0.getLocation() | - loop0 order by loop0.getLocation().getStartLine(), loop0.getLocation().getStartColumn() + loop0 order by l.getStartLine(), l.getStartColumn() ) ) } @@ -1483,7 +1493,7 @@ module LoopSplitting { override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) { this.appliesToPredecessor(pred, c) and - succ(pred, succ, c) and + this.appliesSucc(pred, succ, c) and not loop.stop(pred, succ, c) } } diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/internal/pressa/SsaImplSpecific.qll b/csharp/ql/src/semmle/code/csharp/controlflow/internal/pressa/SsaImplSpecific.qll index 610e808a640..ad64c38973a 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/internal/pressa/SsaImplSpecific.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/internal/pressa/SsaImplSpecific.qll @@ -15,13 +15,37 @@ class ExitBasicBlock extends BasicBlock { ExitBasicBlock() { scopeLast(_, this.getLastElement(), _) } } -pragma[noinline] +/** Holds if `a` is assigned in non-constructor callable `c`. */ +pragma[nomagic] +private predicate assignableDefinition(Assignable a, Callable c) { + exists(AssignableDefinition def | def.getTarget() = a | + c = def.getEnclosingCallable() and + not c instanceof Constructor + ) +} + +/** Holds if `a` is accessed in callable `c`. */ +pragma[nomagic] +private predicate assignableAccess(Assignable a, Callable c) { + exists(AssignableAccess aa | aa.getTarget() = a | c = aa.getEnclosingCallable()) +} + +pragma[nomagic] private predicate assignableNoCapturing(Assignable a, Callable c) { - exists(AssignableAccess aa | aa.getTarget() = a | c = aa.getEnclosingCallable()) and - forall(AssignableDefinition def | def.getTarget() = a | - c = def.getEnclosingCallable() + assignableAccess(a, c) and + /* + * The code below is equivalent to + * ```ql + * not exists(Callable other | assignableDefinition(a, other) | other != c) + * ``` + * but it avoids a Cartesian product in the compiler generated antijoin + * predicate. + */ + + ( + not assignableDefinition(a, _) or - def.getEnclosingCallable() instanceof Constructor + c = unique(Callable c0 | assignableDefinition(a, c0) | c0) ) } @@ -41,7 +65,7 @@ class SourceVariable extends Assignable { ( this instanceof LocalScopeVariable or - this instanceof Field + this = any(Field f | not f.isVolatile()) or this = any(TrivialProperty tp | not tp.isOverridableOrImplementable()) ) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll index 0a7cf4acc41..04465f5ae9e 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll @@ -1,10 +1,10 @@ private import csharp private import cil private import dotnet +private import DataFlowImplCommon as DataFlowImplCommon private import DataFlowPublic private import DataFlowPrivate private import FlowSummaryImpl as FlowSummaryImpl -private import semmle.code.csharp.Caching private import semmle.code.csharp.dataflow.FlowSummary private import semmle.code.csharp.dispatch.Dispatch private import semmle.code.csharp.frameworks.system.Collections @@ -68,31 +68,30 @@ private predicate transitiveCapturedCallTarget(ControlFlow::Nodes::ElementNode c ) } -cached -private module Cached { - cached - newtype TReturnKind = - TNormalReturnKind() { Stages::DataFlowStage::forceCachingInSameStage() } or - TOutReturnKind(int i) { i = any(Parameter p | p.isOut()).getPosition() } or - TRefReturnKind(int i) { i = any(Parameter p | p.isRef()).getPosition() } or - TImplicitCapturedReturnKind(LocalScopeVariable v) { - exists(Ssa::ExplicitDefinition def | def.isCapturedVariableDefinitionFlowOut(_, _) | - v = def.getSourceVariable().getAssignable() - ) - } or - TJumpReturnKind(DataFlowCallable target, ReturnKind rk) { - rk instanceof NormalReturnKind and - ( - target instanceof Constructor or - not target.getReturnType() instanceof VoidType - ) - or - exists(target.getParameter(rk.(OutRefReturnKind).getPosition())) - } +newtype TReturnKind = + TNormalReturnKind() or + TOutReturnKind(int i) { i = any(Parameter p | p.isOut()).getPosition() } or + TRefReturnKind(int i) { i = any(Parameter p | p.isRef()).getPosition() } or + TImplicitCapturedReturnKind(LocalScopeVariable v) { + exists(Ssa::ExplicitDefinition def | def.isCapturedVariableDefinitionFlowOut(_, _) | + v = def.getSourceVariable().getAssignable() + ) + } or + TJumpReturnKind(DataFlowCallable target, ReturnKind rk) { + rk instanceof NormalReturnKind and + ( + target instanceof Constructor or + not target.getReturnType() instanceof VoidType + ) + or + exists(target.getParameter(rk.(OutRefReturnKind).getPosition())) + } +private module Cached { cached newtype TDataFlowCall = TNonDelegateCall(ControlFlow::Nodes::ElementNode cfn, DispatchCall dc) { + DataFlowImplCommon::forceCachingInSameStage() and cfn.getElement() = dc.getCall() } or TExplicitDelegateLikeCall(ControlFlow::Nodes::ElementNode cfn, DelegateLikeCall dc) { @@ -246,7 +245,6 @@ abstract class DataFlowCall extends TDataFlowCall { abstract DataFlow::Node getNode(); /** Gets the enclosing callable of this call. */ - cached abstract DataFlowCallable getEnclosingCallable(); /** Gets the underlying expression, if any. */ @@ -280,10 +278,7 @@ class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall { override DataFlow::ExprNode getNode() { result.getControlFlowNode() = cfn } - override DataFlowCallable getEnclosingCallable() { - Stages::DataFlowStage::forceCachingInSameStage() and - result = cfn.getEnclosingCallable() - } + override DataFlowCallable getEnclosingCallable() { result = cfn.getEnclosingCallable() } override string toString() { result = cfn.toString() } diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll index 966c30038cc..462e89ac9ed 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll @@ -35,22 +35,22 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) { * calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly. */ private module LambdaFlow { - private predicate viableParamNonLambda(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallable(call), i) } - private predicate viableParamLambda(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallableLambda(call, _), i) } - private predicate viableParamArgNonLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParamNonLambda(call, i, p) and arg.argumentOf(call, i) ) } - private predicate viableParamArgLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParamLambda(call, i, p) and arg.argumentOf(call, i) @@ -118,8 +118,8 @@ private module LambdaFlow { boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and - if node instanceof CastNode or node instanceof ArgumentNode or node instanceof ReturnNode - then compatibleTypes(t, getNodeType(node)) + if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode + then compatibleTypes(t, getNodeDataFlowType(node)) else any() } @@ -129,7 +129,7 @@ private module LambdaFlow { boolean toJump, DataFlowCallOption lastCall ) { lambdaCall(lambdaCall, kind, node) and - t = getNodeType(node) and + t = getNodeDataFlowType(node) and toReturn = false and toJump = false and lastCall = TDataFlowCallNone() @@ -146,7 +146,7 @@ private module LambdaFlow { getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid) | preservesValue = false and - t = getNodeType(node) + t = getNodeDataFlowType(node) or preservesValue = true and t = t0 @@ -160,7 +160,7 @@ private module LambdaFlow { toJump = true and lastCall = TDataFlowCallNone() | - jumpStep(node, mid) and + jumpStepCached(node, mid) and t = t0 or exists(boolean preservesValue | @@ -168,7 +168,7 @@ private module LambdaFlow { getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid) | preservesValue = false and - t = getNodeType(node) + t = getNodeDataFlowType(node) or preservesValue = true and t = t0 @@ -176,7 +176,7 @@ private module LambdaFlow { ) or // flow into a callable - exists(ParameterNode p, DataFlowCallOption lastCall0, DataFlowCall call | + exists(ParamNode p, DataFlowCallOption lastCall0, DataFlowCall call | revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and ( if lastCall0 = TDataFlowCallNone() and toJump = false @@ -227,7 +227,7 @@ private module LambdaFlow { pragma[nomagic] predicate revLambdaFlowIn( - DataFlowCall lambdaCall, LambdaCallKind kind, ParameterNode p, DataFlowType t, boolean toJump, + DataFlowCall lambdaCall, LambdaCallKind kind, ParamNode p, DataFlowType t, boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall) @@ -242,6 +242,89 @@ private DataFlowCallable viableCallableExt(DataFlowCall call) { cached private module Cached { + /** + * If needed, call this predicate from `DataFlowImplSpecific.qll` in order to + * force a stage-dependency on the `DataFlowImplCommon.qll` stage and therby + * collapsing the two stages. + */ + cached + predicate forceCachingInSameStage() { any() } + + cached + predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() } + + cached + predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) { + c = call.getEnclosingCallable() + } + + cached + predicate nodeDataFlowType(Node n, DataFlowType t) { t = getNodeType(n) } + + cached + predicate jumpStepCached(Node node1, Node node2) { jumpStep(node1, node2) } + + cached + predicate clearsContentCached(Node n, Content c) { clearsContent(n, c) } + + cached + predicate isUnreachableInCallCached(Node n, DataFlowCall call) { isUnreachableInCall(n, call) } + + cached + predicate outNodeExt(Node n) { + n instanceof OutNode + or + n.(PostUpdateNode).getPreUpdateNode() instanceof ArgNode + } + + cached + predicate hiddenNode(Node n) { nodeIsHidden(n) } + + cached + OutNodeExt getAnOutNodeExt(DataFlowCall call, ReturnKindExt k) { + result = getAnOutNode(call, k.(ValueReturnKind).getKind()) + or + exists(ArgNode arg | + result.(PostUpdateNode).getPreUpdateNode() = arg and + arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition()) + ) + } + + cached + predicate returnNodeExt(Node n, ReturnKindExt k) { + k = TValueReturn(n.(ReturnNode).getKind()) + or + exists(ParamNode p, int pos | + parameterValueFlowsToPreUpdate(p, n) and + p.isParameterOf(_, pos) and + k = TParamUpdate(pos) + ) + } + + cached + predicate castNode(Node n) { n instanceof CastNode } + + cached + predicate castingNode(Node n) { + castNode(n) or + n instanceof ParamNode or + n instanceof OutNodeExt or + // For reads, `x.f`, we want to check that the tracked type after the read (which + // is obtained by popping the head of the access path stack) is compatible with + // the type of `x.f`. + read(_, _, n) + } + + cached + predicate parameterNode(Node n, DataFlowCallable c, int i) { + n.(ParameterNode).isParameterOf(c, i) + } + + cached + predicate argumentNode(Node n, DataFlowCall call, int pos) { + n.(ArgumentNode).argumentOf(call, pos) + } + /** * Gets a viable target for the lambda call `call`. * @@ -261,7 +344,7 @@ private module Cached { * The instance parameter is considered to have index `-1`. */ pragma[nomagic] - private predicate viableParam(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParam(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallableExt(call), i) } @@ -270,11 +353,11 @@ private module Cached { * dispatch into account. */ cached - predicate viableParamArg(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParam(call, i, p) and arg.argumentOf(call, i) and - compatibleTypes(getNodeType(arg), getNodeType(p)) + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p)) ) } @@ -312,7 +395,7 @@ private module Cached { * `read` indicates whether it is contents of `p` that can flow to `node`. */ pragma[nomagic] - private predicate parameterValueFlowCand(ParameterNode p, Node node, boolean read) { + private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) { p = node and read = false or @@ -325,30 +408,30 @@ private module Cached { // read exists(Node mid | parameterValueFlowCand(p, mid, false) and - readStep(mid, _, node) and + read(mid, _, node) and read = true ) or // flow through: no prior read - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArgCand(p, arg, false) and argumentValueFlowsThroughCand(arg, node, read) ) or // flow through: no read inside method - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArgCand(p, arg, read) and argumentValueFlowsThroughCand(arg, node, false) ) } pragma[nomagic] - private predicate parameterValueFlowArgCand(ParameterNode p, ArgumentNode arg, boolean read) { + private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) { parameterValueFlowCand(p, arg, read) } pragma[nomagic] - predicate parameterValueFlowsToPreUpdateCand(ParameterNode p, PostUpdateNode n) { + predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) { parameterValueFlowCand(p, n.getPreUpdateNode(), false) } @@ -360,7 +443,7 @@ private module Cached { * `read` indicates whether it is contents of `p` that can flow to the return * node. */ - predicate parameterValueFlowReturnCand(ParameterNode p, ReturnKind kind, boolean read) { + predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) { exists(ReturnNode ret | parameterValueFlowCand(p, ret, read) and kind = ret.getKind() @@ -369,9 +452,9 @@ private module Cached { pragma[nomagic] private predicate argumentValueFlowsThroughCand0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind, boolean read + DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read ) { - exists(ParameterNode param | viableParamArg(call, param, arg) | + exists(ParamNode param | viableParamArg(call, param, arg) | parameterValueFlowReturnCand(param, kind, read) ) } @@ -382,14 +465,14 @@ private module Cached { * * `read` indicates whether it is contents of `arg` that can flow to `out`. */ - predicate argumentValueFlowsThroughCand(ArgumentNode arg, Node out, boolean read) { + predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) { exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThroughCand0(call, arg, kind, read) and out = getAnOutNode(call, kind) ) } - predicate cand(ParameterNode p, Node n) { + predicate cand(ParamNode p, Node n) { parameterValueFlowCand(p, n, _) and ( parameterValueFlowReturnCand(p, _, _) @@ -416,21 +499,21 @@ private module Cached { * If a read step was taken, then `read` captures the `Content`, the * container type, and the content type. */ - predicate parameterValueFlow(ParameterNode p, Node node, ReadStepTypesOption read) { + predicate parameterValueFlow(ParamNode p, Node node, ReadStepTypesOption read) { parameterValueFlow0(p, node, read) and if node instanceof CastingNode then // normal flow through read = TReadStepTypesNone() and - compatibleTypes(getNodeType(p), getNodeType(node)) + compatibleTypes(getNodeDataFlowType(p), getNodeDataFlowType(node)) or // getter - compatibleTypes(read.getContentType(), getNodeType(node)) + compatibleTypes(read.getContentType(), getNodeDataFlowType(node)) else any() } pragma[nomagic] - private predicate parameterValueFlow0(ParameterNode p, Node node, ReadStepTypesOption read) { + private predicate parameterValueFlow0(ParamNode p, Node node, ReadStepTypesOption read) { p = node and Cand::cand(p, _) and read = TReadStepTypesNone() @@ -447,7 +530,7 @@ private module Cached { readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, read.getContentType()) and Cand::parameterValueFlowReturnCand(p, _, true) and - compatibleTypes(getNodeType(p), read.getContainerType()) + compatibleTypes(getNodeDataFlowType(p), read.getContainerType()) ) or parameterValueFlow0_0(TReadStepTypesNone(), p, node, read) @@ -455,34 +538,32 @@ private module Cached { pragma[nomagic] private predicate parameterValueFlow0_0( - ReadStepTypesOption mustBeNone, ParameterNode p, Node node, ReadStepTypesOption read + ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read ) { // flow through: no prior read - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArg(p, arg, mustBeNone) and argumentValueFlowsThrough(arg, read, node) ) or // flow through: no read inside method - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArg(p, arg, read) and argumentValueFlowsThrough(arg, mustBeNone, node) ) } pragma[nomagic] - private predicate parameterValueFlowArg( - ParameterNode p, ArgumentNode arg, ReadStepTypesOption read - ) { + private predicate parameterValueFlowArg(ParamNode p, ArgNode arg, ReadStepTypesOption read) { parameterValueFlow(p, arg, read) and Cand::argumentValueFlowsThroughCand(arg, _, _) } pragma[nomagic] private predicate argumentValueFlowsThrough0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind, ReadStepTypesOption read + DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read ) { - exists(ParameterNode param | viableParamArg(call, param, arg) | + exists(ParamNode param | viableParamArg(call, param, arg) | parameterValueFlowReturn(param, kind, read) ) } @@ -496,18 +577,18 @@ private module Cached { * container type, and the content type. */ pragma[nomagic] - predicate argumentValueFlowsThrough(ArgumentNode arg, ReadStepTypesOption read, Node out) { + predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) { exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThrough0(call, arg, kind, read) and out = getAnOutNode(call, kind) | // normal flow through read = TReadStepTypesNone() and - compatibleTypes(getNodeType(arg), getNodeType(out)) + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(out)) or // getter - compatibleTypes(getNodeType(arg), read.getContainerType()) and - compatibleTypes(read.getContentType(), getNodeType(out)) + compatibleTypes(getNodeDataFlowType(arg), read.getContainerType()) and + compatibleTypes(read.getContentType(), getNodeDataFlowType(out)) ) } @@ -516,7 +597,7 @@ private module Cached { * value-preserving steps and a single read step, not taking call * contexts into account, thus representing a getter-step. */ - predicate getterStep(ArgumentNode arg, Content c, Node out) { + predicate getterStep(ArgNode arg, Content c, Node out) { argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out) } @@ -529,7 +610,7 @@ private module Cached { * container type, and the content type. */ private predicate parameterValueFlowReturn( - ParameterNode p, ReturnKind kind, ReadStepTypesOption read + ParamNode p, ReturnKind kind, ReadStepTypesOption read ) { exists(ReturnNode ret | parameterValueFlow(p, ret, read) and @@ -553,7 +634,7 @@ private module Cached { private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { mayBenefitFromCallContext(call, callable) or - callable = call.getEnclosingCallable() and + callEnclosingCallable(call, callable) and exists(viableCallableLambda(call, TDataFlowCallSome(_))) } @@ -611,7 +692,7 @@ private module Cached { mayBenefitFromCallContextExt(call, _) and c = viableCallableExt(call) and ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and - tgts = strictcount(DataFlowCall ctx | viableCallableExt(ctx) = call.getEnclosingCallable()) and + tgts = strictcount(DataFlowCall ctx | callEnclosingCallable(call, viableCallableExt(ctx))) and ctxtgts < tgts ) } @@ -635,8 +716,7 @@ private module Cached { * Holds if `p` can flow to the pre-update node associated with post-update * node `n`, in the same callable, using only value-preserving steps. */ - cached - predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) { + private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone()) } @@ -644,9 +724,9 @@ private module Cached { Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType ) { storeStep(node1, c, node2) and - readStep(_, c, _) and - contentType = getNodeType(node1) and - containerType = getNodeType(node2) + read(_, c, _) and + contentType = getNodeDataFlowType(node1) and + containerType = getNodeDataFlowType(node2) or exists(Node n1, Node n2 | n1 = node1.(PostUpdateNode).getPreUpdateNode() and @@ -654,12 +734,15 @@ private module Cached { | argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1) or - readStep(n2, c, n1) and - contentType = getNodeType(n1) and - containerType = getNodeType(n2) + read(n2, c, n1) and + contentType = getNodeDataFlowType(n1) and + containerType = getNodeDataFlowType(n2) ) } + cached + predicate read(Node node1, Content c, Node node2) { readStep(node1, c, node2) } + /** * Holds if data can flow from `node1` to `node2` via a direct assignment to * `f`. @@ -678,8 +761,9 @@ private module Cached { * are aliases. A typical example is a function returning `this`, implementing a fluent * interface. */ - cached - predicate reverseStepThroughInputOutputAlias(PostUpdateNode fromNode, PostUpdateNode toNode) { + private predicate reverseStepThroughInputOutputAlias( + PostUpdateNode fromNode, PostUpdateNode toNode + ) { exists(Node fromPre, Node toPre | fromPre = fromNode.getPreUpdateNode() and toPre = toNode.getPreUpdateNode() @@ -688,14 +772,20 @@ private module Cached { // Does the language-specific simpleLocalFlowStep already model flow // from function input to output? fromPre = getAnOutNode(c, _) and - toPre.(ArgumentNode).argumentOf(c, _) and - simpleLocalFlowStep(toPre.(ArgumentNode), fromPre) + toPre.(ArgNode).argumentOf(c, _) and + simpleLocalFlowStep(toPre.(ArgNode), fromPre) ) or argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre) ) } + cached + predicate simpleLocalFlowStepExt(Node node1, Node node2) { + simpleLocalFlowStep(node1, node2) or + reverseStepThroughInputOutputAlias(node1, node2) + } + /** * Holds if the call context `call` either improves virtual dispatch in * `callable` or if it allows us to prune unreachable nodes in `callable`. @@ -704,7 +794,7 @@ private module Cached { predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) { reducedViableImplInCallContext(_, callable, call) or - exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCall(n, call)) + exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call)) } cached @@ -726,12 +816,12 @@ private module Cached { cached newtype TLocalFlowCallContext = TAnyLocalCall() or - TSpecificLocalCall(DataFlowCall call) { isUnreachableInCall(_, call) } + TSpecificLocalCall(DataFlowCall call) { isUnreachableInCallCached(_, call) } cached newtype TReturnKindExt = TValueReturn(ReturnKind kind) or - TParamUpdate(int pos) { exists(ParameterNode p | p.isParameterOf(_, pos)) } + TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) } cached newtype TBooleanOption = @@ -761,23 +851,15 @@ private module Cached { * A `Node` at which a cast can occur such that the type should be checked. */ class CastingNode extends Node { - CastingNode() { - this instanceof ParameterNode or - this instanceof CastNode or - this instanceof OutNodeExt or - // For reads, `x.f`, we want to check that the tracked type after the read (which - // is obtained by popping the head of the access path stack) is compatible with - // the type of `x.f`. - readStep(_, _, this) - } + CastingNode() { castingNode(this) } } private predicate readStepWithTypes( Node n1, DataFlowType container, Content c, Node n2, DataFlowType content ) { - readStep(n1, c, n2) and - container = getNodeType(n1) and - content = getNodeType(n2) + read(n1, c, n2) and + container = getNodeDataFlowType(n1) and + content = getNodeDataFlowType(n2) } private newtype TReadStepTypesOption = @@ -854,7 +936,7 @@ class CallContextSomeCall extends CallContextCall, TSomeCall { override string toString() { result = "CcSomeCall" } override predicate relevantFor(DataFlowCallable callable) { - exists(ParameterNode p | getNodeEnclosingCallable(p) = callable) + exists(ParamNode p | getNodeEnclosingCallable(p) = callable) } override predicate matchesCall(DataFlowCall call) { any() } @@ -866,7 +948,7 @@ class CallContextReturn extends CallContextNoCall, TReturn { } override predicate relevantFor(DataFlowCallable callable) { - exists(DataFlowCall call | this = TReturn(_, call) and call.getEnclosingCallable() = callable) + exists(DataFlowCall call | this = TReturn(_, call) and callEnclosingCallable(call, callable)) } } @@ -899,7 +981,7 @@ class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall } private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) { - exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCall(n, call)) + exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCallCached(n, call)) } /** @@ -913,26 +995,37 @@ LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) else result instanceof LocalCallContextAny } +/** + * The value of a parameter at function entry, viewed as a node in a data + * flow graph. + */ +class ParamNode extends Node { + ParamNode() { parameterNode(this, _, _) } + + /** + * Holds if this node is the parameter of callable `c` at the specified + * (zero-based) position. + */ + predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) } +} + +/** A data-flow node that represents a call argument. */ +class ArgNode extends Node { + ArgNode() { argumentNode(this, _, _) } + + /** Holds if this argument occurs at the given position in the given call. */ + final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) } +} + /** * A node from which flow can return to the caller. This is either a regular * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter. */ class ReturnNodeExt extends Node { - ReturnNodeExt() { - this instanceof ReturnNode or - parameterValueFlowsToPreUpdate(_, this) - } + ReturnNodeExt() { returnNodeExt(this, _) } /** Gets the kind of this returned value. */ - ReturnKindExt getKind() { - result = TValueReturn(this.(ReturnNode).getKind()) - or - exists(ParameterNode p, int pos | - parameterValueFlowsToPreUpdate(p, this) and - p.isParameterOf(_, pos) and - result = TParamUpdate(pos) - ) - } + ReturnKindExt getKind() { returnNodeExt(this, result) } } /** @@ -940,11 +1033,7 @@ class ReturnNodeExt extends Node { * or a post-update node associated with a call argument. */ class OutNodeExt extends Node { - OutNodeExt() { - this instanceof OutNode - or - this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode - } + OutNodeExt() { outNodeExt(this) } } /** @@ -957,7 +1046,7 @@ abstract class ReturnKindExt extends TReturnKindExt { abstract string toString(); /** Gets a node corresponding to data flow out of `call`. */ - abstract OutNodeExt getAnOutNode(DataFlowCall call); + final OutNodeExt getAnOutNode(DataFlowCall call) { result = getAnOutNodeExt(call, this) } } class ValueReturnKind extends ReturnKindExt, TValueReturn { @@ -968,10 +1057,6 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn { ReturnKind getKind() { result = kind } override string toString() { result = kind.toString() } - - override OutNodeExt getAnOutNode(DataFlowCall call) { - result = getAnOutNode(call, this.getKind()) - } } class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { @@ -982,13 +1067,6 @@ class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { int getPosition() { result = pos } override string toString() { result = "param update " + pos } - - override OutNodeExt getAnOutNode(DataFlowCall call) { - exists(ArgumentNode arg | - result.(PostUpdateNode).getPreUpdateNode() = arg and - arg.argumentOf(call, this.getPosition()) - ) - } } /** A callable tagged with a relevant return kind. */ @@ -1015,10 +1093,13 @@ class ReturnPosition extends TReturnPosition0 { */ pragma[inline] DataFlowCallable getNodeEnclosingCallable(Node n) { - exists(Node n0 | - pragma[only_bind_into](n0) = n and - pragma[only_bind_into](result) = n0.getEnclosingCallable() - ) + nodeEnclosingCallable(pragma[only_bind_out](n), pragma[only_bind_into](result)) +} + +/** Gets the type of `n` used for type pruning. */ +pragma[inline] +DataFlowType getNodeDataFlowType(Node n) { + nodeDataFlowType(pragma[only_bind_out](n), pragma[only_bind_into](result)) } pragma[noinline] @@ -1042,7 +1123,7 @@ predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall cc instanceof CallContextAny and callable = viableCallableExt(call) or exists(DataFlowCallable c0, DataFlowCall call0 | - call0.getEnclosingCallable() = callable and + callEnclosingCallable(call0, callable) and cc = TReturn(c0, call0) and c0 = prunedViableImplInCallContextReverse(call0, call) ) @@ -1063,8 +1144,6 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { result = viableCallableExt(call) and cc instanceof CallContextReturn } -predicate read = readStep/3; - /** An optional Boolean value. */ class BooleanOption extends TBooleanOption { string toString() { @@ -1116,7 +1195,7 @@ abstract class AccessPathFront extends TAccessPathFront { TypedContent getHead() { this = TFrontHead(result) } - predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } + predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll index 97a8406a21e..6373382a204 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll @@ -7,7 +7,6 @@ private import DataFlowImplCommon private import ControlFlowReachability private import FlowSummaryImpl as FlowSummaryImpl private import semmle.code.csharp.dataflow.FlowSummary -private import semmle.code.csharp.Caching private import semmle.code.csharp.Conversion private import semmle.code.csharp.dataflow.internal.SsaImpl as SsaImpl private import semmle.code.csharp.ExprOrStmtParent @@ -21,7 +20,6 @@ private import semmle.code.csharp.frameworks.system.threading.Tasks abstract class NodeImpl extends Node { /** Do not call: use `getEnclosingCallable()` instead. */ - cached abstract DataFlowCallable getEnclosingCallableImpl(); /** Do not call: use `getType()` instead. */ @@ -29,9 +27,8 @@ abstract class NodeImpl extends Node { abstract DotNet::Type getTypeImpl(); /** Gets the type of this node used for type pruning. */ - cached Gvn::GvnType getDataFlowType() { - Stages::DataFlowStage::forceCachingInSameStage() and + forceCachingInSameStage() and exists(Type t0 | result = Gvn::getGlobalValueNumber(t0) | t0 = getCSharpType(this.getType()) or @@ -55,26 +52,25 @@ abstract class NodeImpl extends Node { private class ExprNodeImpl extends ExprNode, NodeImpl { override DataFlowCallable getEnclosingCallableImpl() { - Stages::DataFlowStage::forceCachingInSameStage() and result = this.getExpr().getEnclosingCallable() } override DotNet::Type getTypeImpl() { - Stages::DataFlowStage::forceCachingInSameStage() and + forceCachingInSameStage() and result = this.getExpr().getType() } override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { - Stages::DataFlowStage::forceCachingInSameStage() and this = TExprNode(result) + forceCachingInSameStage() and this = TExprNode(result) } override Location getLocationImpl() { - Stages::DataFlowStage::forceCachingInSameStage() and result = this.getExpr().getLocation() + forceCachingInSameStage() and result = this.getExpr().getLocation() } override string toStringImpl() { - Stages::DataFlowStage::forceCachingInSameStage() and - result = this.getControlFlowNode().toString() + forceCachingInSameStage() and + result = this.getControlFlowNodeImpl().toString() or exists(CIL::Expr e | this = TCilExprNode(e) and @@ -394,6 +390,22 @@ module LocalFlow { } } +/** + * This is the local flow predicate that is used as a building block in global + * data flow. It excludes SSA flow through instance fields, as flow through fields + * is handled by the global data-flow library, but includes various other steps + * that are only relevant for global flow. + */ +predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { + LocalFlow::localFlowStepCommon(nodeFrom, nodeTo) + or + LocalFlow::localFlowCapturedVarStep(nodeFrom, nodeTo) + or + FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true) + or + nodeTo.(ObjectCreationNode).getPreUpdateNode() = nodeFrom.(ObjectInitializerNode) +} + pragma[noinline] private Expr getImplicitArgument(Call c, int pos) { result = c.getArgument(pos) and @@ -582,12 +594,16 @@ private Type getCSharpType(DotNet::Type t) { result.matchesHandle(t) } +private class RelevantDataFlowType extends DataFlowType { + RelevantDataFlowType() { this = any(NodeImpl n).getDataFlowType() } +} + /** A GVN type that is either a `DataFlowType` or unifiable with a `DataFlowType`. */ private class DataFlowTypeOrUnifiable extends Gvn::GvnType { pragma[nomagic] DataFlowTypeOrUnifiable() { - this instanceof DataFlowType or - Gvn::unifiable(any(DataFlowType t), this) + this instanceof RelevantDataFlowType or + Gvn::unifiable(any(RelevantDataFlowType t), this) } } @@ -598,7 +614,7 @@ private TypeParameter getATypeParameterSubType(DataFlowTypeOrUnifiable t) { } pragma[noinline] -private TypeParameter getATypeParameterSubTypeRestricted(DataFlowType t) { +private TypeParameter getATypeParameterSubTypeRestricted(RelevantDataFlowType t) { result = getATypeParameterSubType(t) } @@ -614,17 +630,30 @@ private Gvn::GvnType getANonTypeParameterSubType(DataFlowTypeOrUnifiable t) { } pragma[noinline] -private Gvn::GvnType getANonTypeParameterSubTypeRestricted(DataFlowType t) { +private Gvn::GvnType getANonTypeParameterSubTypeRestricted(RelevantDataFlowType t) { result = getANonTypeParameterSubType(t) } /** A collection of cached types and predicates to be evaluated in the same stage. */ cached private module Cached { + private import TaintTrackingPrivate as TaintTrackingPrivate + + // Add artificial dependencies to enforce all cached predicates are evaluated + // in the "DataFlowImplCommon stage" + private predicate forceCaching() { + TaintTrackingPrivate::forceCachingInSameStage() or + exists(any(NodeImpl n).getTypeImpl()) or + exists(any(NodeImpl n).getControlFlowNodeImpl()) or + exists(any(NodeImpl n).getLocationImpl()) or + exists(any(NodeImpl n).toStringImpl()) + } + cached newtype TNode = TExprNode(ControlFlow::Nodes::ElementNode cfn) { - Stages::DataFlowStage::forceCachingInSameStage() and cfn.getElement() instanceof Expr + forceCaching() and + cfn.getElement() instanceof Expr } or TCilExprNode(CIL::Expr e) { e.getImplementation() instanceof CIL::BestImplementation } or TSsaDefinitionNode(Ssa::Definition def) { @@ -679,23 +708,6 @@ private module Cached { callCfn = any(Call c | isParamsArg(c, _, _)).getAControlFlowNode() } - /** - * This is the local flow predicate that is used as a building block in global - * data flow. It excludes SSA flow through instance fields, as flow through fields - * is handled by the global data-flow library, but includes various other steps - * that are only relevant for global flow. - */ - cached - predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { - LocalFlow::localFlowStepCommon(nodeFrom, nodeTo) - or - LocalFlow::localFlowCapturedVarStep(nodeFrom, nodeTo) - or - FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true) - or - nodeTo.(ObjectCreationNode).getPreUpdateNode() = nodeFrom.(ObjectInitializerNode) - } - /** * Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local * (intra-procedural) step. @@ -714,178 +726,14 @@ private module Cached { FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true) } - /** - * Holds if `pred` can flow to `succ`, by jumping from one callable to - * another. Additional steps specified by the configuration are *not* - * taken into account. - */ - cached - predicate jumpStepImpl(Node pred, Node succ) { - pred.(NonLocalJumpNode).getAJumpSuccessor(true) = succ - or - exists(FieldOrProperty fl, FieldOrPropertyRead flr | - fl.isStatic() and - fl.isFieldLike() and - fl.getAnAssignedValue() = pred.asExpr() and - fl.getAnAccess() = flr and - flr = succ.asExpr() and - flr.hasNonlocalValue() - ) - or - exists(JumpReturnKind jrk, DataFlowCall call | - FlowSummaryImpl::Private::summaryReturnNode(pred, jrk) and - viableCallable(call) = jrk.getTarget() and - succ = getAnOutNode(call, jrk.getTargetReturnKind()) - ) - } - cached newtype TContent = TFieldContent(Field f) { f.isUnboundDeclaration() } or TPropertyContent(Property p) { p.isUnboundDeclaration() } or TElementContent() - /** - * Holds if data can flow from `node1` to `node2` via an assignment to - * content `c`. - */ - cached - predicate storeStepImpl(Node node1, Content c, Node node2) { - exists(StoreStepConfiguration x, ExprNode node, boolean postUpdate | - hasNodePath(x, node1, node) and - if postUpdate = true then node = node2.(PostUpdateNode).getPreUpdateNode() else node = node2 - | - fieldOrPropertyStore(_, c, node1.asExpr(), node.getExpr(), postUpdate) - or - arrayStore(_, node1.asExpr(), node.getExpr(), postUpdate) and c instanceof ElementContent - ) - or - exists(StoreStepConfiguration x, Expr arg, ControlFlow::Node callCfn | - x.hasExprPath(arg, node1.(ExprNode).getControlFlowNode(), _, callCfn) and - node2 = TParamsArgumentNode(callCfn) and - isParamsArg(_, arg, _) and - c instanceof ElementContent - ) - or - exists(Expr e | - e = node1.asExpr() and - node2.(YieldReturnNode).getYieldReturnStmt().getExpr() = e and - c instanceof ElementContent - ) - or - exists(Expr e | - e = node1.asExpr() and - node2.(AsyncReturnNode).getExpr() = e and - c = getResultContent() - ) - or - FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2) - } - pragma[nomagic] - private PropertyContent getResultContent() { - result.getProperty() = any(SystemThreadingTasksTaskTClass c_).getResultProperty() - } - - /** - * Holds if data can flow from `node1` to `node2` via a read of content `c`. - */ - cached - predicate readStepImpl(Node node1, Content c, Node node2) { - exists(ReadStepConfiguration x | - hasNodePath(x, node1, node2) and - fieldOrPropertyRead(node1.asExpr(), c, node2.asExpr()) - or - hasNodePath(x, node1, node2) and - arrayRead(node1.asExpr(), node2.asExpr()) and - c instanceof ElementContent - or - exists(ForeachStmt fs, Ssa::ExplicitDefinition def | - x.hasDefPath(fs.getIterableExpr(), node1.getControlFlowNode(), def.getADefinition(), - def.getControlFlowNode()) and - node2.(SsaDefinitionNode).getDefinition() = def and - c instanceof ElementContent - ) - or - hasNodePath(x, node1, node2) and - node2.asExpr().(AwaitExpr).getExpr() = node1.asExpr() and - c = getResultContent() - or - // node1 = (..., node2, ...) - // node1.ItemX flows to node2 - exists(TupleExpr te, int i, Expr item | - te = node1.asExpr() and - not te.isConstruction() and - c.(FieldContent).getField() = te.getType().(TupleType).getElement(i).getUnboundDeclaration() and - // node1 = (..., item, ...) - te.getArgument(i) = item - | - // item = (..., ..., ...) in node1 = (..., (..., ..., ...), ...) - node2.asExpr().(TupleExpr) = item and - hasNodePath(x, node1, node2) - or - // item = variable in node1 = (..., variable, ...) - exists(AssignableDefinitions::TupleAssignmentDefinition tad, Ssa::ExplicitDefinition def | - node2.(SsaDefinitionNode).getDefinition() = def and - def.getADefinition() = tad and - tad.getLeaf() = item and - hasNodePath(x, node1, node2) - ) - or - // item = variable in node1 = (..., variable, ...) in a case/is var (..., ...) - te = any(PatternExpr pe).getAChildExpr*() and - exists(AssignableDefinitions::LocalVariableDefinition lvd, Ssa::ExplicitDefinition def | - node2.(SsaDefinitionNode).getDefinition() = def and - def.getADefinition() = lvd and - lvd.getDeclaration() = item and - hasNodePath(x, node1, node2) - ) - ) - ) - or - FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2) - } - - /** - * Holds if values stored inside content `c` are cleared at node `n`. For example, - * any value stored inside `f` is cleared at the pre-update node associated with `x` - * in `x.f = newValue`. - */ - cached - predicate clearsContent(Node n, Content c) { - fieldOrPropertyStore(_, c, _, n.asExpr(), true) - or - fieldOrPropertyStore(_, c, _, n.(ObjectInitializerNode).getInitializer(), false) - or - FlowSummaryImpl::Private::Steps::summaryStoresIntoArg(c, n) and - not c instanceof ElementContent - or - FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c) - or - exists(WithExpr we, ObjectInitializer oi, FieldOrProperty f | - oi = we.getInitializer() and - n.asExpr() = oi and - f = oi.getAMemberInitializer().getInitializedMember() and - c = f.getContent() - ) - } - - /** - * Holds if the node `n` is unreachable when the call context is `call`. - */ - cached - predicate isUnreachableInCall(Node n, DataFlowCall call) { - exists( - ExplicitParameterNode paramNode, Guard guard, ControlFlow::SuccessorTypes::BooleanSuccessor bs - | - viableConstantBooleanParamArg(paramNode, bs.getValue().booleanNot(), call) and - paramNode.getSsaDefinition().getARead() = guard and - guard.controlsBlock(n.getControlFlowNode().getBasicBlock(), bs, _) - ) - } - - pragma[nomagic] - private predicate commonSubTypeGeneral(DataFlowTypeOrUnifiable t1, DataFlowType t2) { + private predicate commonSubTypeGeneral(DataFlowTypeOrUnifiable t1, RelevantDataFlowType t2) { not t1 instanceof Gvn::TypeParameterGvnType and t1 = t2 or @@ -899,102 +747,53 @@ private module Cached { * `t2` are allowed to be type parameters. */ cached - predicate commonSubType(DataFlowType t1, DataFlowType t2) { commonSubTypeGeneral(t1, t2) } + predicate commonSubType(RelevantDataFlowType t1, RelevantDataFlowType t2) { + commonSubTypeGeneral(t1, t2) + } cached - predicate commonSubTypeUnifiableLeft(DataFlowType t1, DataFlowType t2) { + predicate commonSubTypeUnifiableLeft(RelevantDataFlowType t1, RelevantDataFlowType t2) { exists(Gvn::GvnType t | Gvn::unifiable(t1, t) and commonSubTypeGeneral(t, t2) ) } - - cached - predicate outRefReturnNode(Ssa::ExplicitDefinition def, OutRefReturnKind kind) { - exists(Parameter p | - def.isLiveOutRefParameterDefinition(p) and - kind.getPosition() = p.getPosition() - | - p.isOut() and kind instanceof OutReturnKind - or - p.isRef() and kind instanceof RefReturnKind - ) - } - - cached - predicate summaryOutNodeCached(DataFlowCall c, Node out, ReturnKind rk) { - FlowSummaryImpl::Private::summaryOutNode(c, out, rk) - } - - cached - predicate summaryArgumentNodeCached(DataFlowCall c, Node arg, int i) { - FlowSummaryImpl::Private::summaryArgumentNode(c, arg, i) - } - - cached - predicate summaryPostUpdateNodeCached(Node post, ParameterNode pre) { - FlowSummaryImpl::Private::summaryPostUpdateNode(post, pre) - } - - cached - predicate summaryReturnNodeCached(Node ret, ReturnKind rk) { - FlowSummaryImpl::Private::summaryReturnNode(ret, rk) and - not rk instanceof JumpReturnKind - } - - cached - predicate castNode(Node n) { - n.asExpr() instanceof Cast - or - n.(AssignableDefinitionNode).getDefinition() instanceof AssignableDefinitions::PatternDefinition - } - - /** Holds if `n` should be hidden from path explanations. */ - cached - predicate nodeIsHidden(Node n) { - exists(Ssa::Definition def | def = n.(SsaDefinitionNode).getDefinition() | - def instanceof Ssa::PhiNode - or - def instanceof Ssa::ImplicitEntryDefinition - or - def instanceof Ssa::ImplicitCallDefinition - ) - or - exists(Parameter p | - p = n.(ParameterNode).getParameter() and - not p.fromSource() - ) - or - n = TInstanceParameterNode(any(Callable c | not c.fromSource())) - or - n instanceof YieldReturnNode - or - n instanceof AsyncReturnNode - or - n instanceof ImplicitCapturedArgumentNode - or - n instanceof MallocNode - or - n instanceof SummaryNode - or - n instanceof ParamsArgumentNode - or - n.asExpr() = any(WithExpr we).getInitializer() - } - - cached - predicate parameterNode(Node n, DataFlowCallable c, int i) { - n.(ParameterNodeImpl).isParameterOf(c, i) - } - - cached - predicate argumentNode(Node n, DataFlowCall call, int pos) { - n.(ArgumentNodeImpl).argumentOf(call, pos) - } } import Cached +/** Holds if `n` should be hidden from path explanations. */ +predicate nodeIsHidden(Node n) { + exists(Ssa::Definition def | def = n.(SsaDefinitionNode).getDefinition() | + def instanceof Ssa::PhiNode + or + def instanceof Ssa::ImplicitEntryDefinition + or + def instanceof Ssa::ImplicitCallDefinition + ) + or + exists(Parameter p | + p = n.(ParameterNode).getParameter() and + not p.fromSource() + ) + or + n = TInstanceParameterNode(any(Callable c | not c.fromSource())) + or + n instanceof YieldReturnNode + or + n instanceof AsyncReturnNode + or + n instanceof ImplicitCapturedArgumentNode + or + n instanceof MallocNode + or + n instanceof SummaryNode + or + n instanceof ParamsArgumentNode + or + n.asExpr() = any(WithExpr we).getInitializer() +} + /** An SSA definition, viewed as a node in a data flow graph. */ class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode { Ssa::Definition def; @@ -1142,10 +941,12 @@ import ParameterNodes /** A data-flow node that represents a call argument. */ class ArgumentNode extends Node { - ArgumentNode() { argumentNode(this, _, _) } + ArgumentNode() { this instanceof ArgumentNodeImpl } /** Holds if this argument occurs at the given position in the given call. */ - final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) } + final predicate argumentOf(DataFlowCall call, int pos) { + this.(ArgumentNodeImpl).argumentOf(call, pos) + } } abstract private class ArgumentNodeImpl extends Node { @@ -1310,14 +1111,10 @@ private module ArgumentNodes { } private class SummaryArgumentNode extends SummaryNode, ArgumentNodeImpl { - private DataFlowCall c; - private int i; - - SummaryArgumentNode() { summaryArgumentNodeCached(c, this, i) } + SummaryArgumentNode() { FlowSummaryImpl::Private::summaryArgumentNode(_, this, _) } override predicate argumentOf(DataFlowCall call, int pos) { - call = c and - i = pos + FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos) } } } @@ -1352,7 +1149,16 @@ private module ReturnNodes { class OutRefReturnNode extends ReturnNode, SsaDefinitionNode { OutRefReturnKind kind; - OutRefReturnNode() { outRefReturnNode(this.getDefinition(), kind) } + OutRefReturnNode() { + exists(Parameter p | + this.getDefinition().isLiveOutRefParameterDefinition(p) and + kind.getPosition() = p.getPosition() + | + p.isOut() and kind instanceof OutReturnKind + or + p.isRef() and kind instanceof RefReturnKind + ) + } override ReturnKind getKind() { result = kind } } @@ -1449,7 +1255,10 @@ private module ReturnNodes { private class SummaryReturnNode extends SummaryNode, ReturnNode { private ReturnKind rk; - SummaryReturnNode() { summaryReturnNodeCached(this, rk) } + SummaryReturnNode() { + FlowSummaryImpl::Private::summaryReturnNode(this, rk) and + not rk instanceof JumpReturnKind + } override ReturnKind getKind() { result = rk } } @@ -1460,7 +1269,6 @@ import ReturnNodes /** 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`. */ - cached abstract DataFlowCall getCall(ReturnKind kind); } @@ -1488,7 +1296,6 @@ private module OutNodes { } override DataFlowCall getCall(ReturnKind kind) { - Stages::DataFlowStage::forceCachingInSameStage() and result = call and ( kind instanceof NormalReturnKind and @@ -1564,14 +1371,10 @@ private module OutNodes { } private class SummaryOutNode extends SummaryNode, OutNode { - private DataFlowCall c; - private ReturnKind rk; - - SummaryOutNode() { summaryOutNodeCached(c, this, rk) } + SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) } override DataFlowCall getCall(ReturnKind kind) { - result = c and - kind = rk + FlowSummaryImpl::Private::summaryOutNode(result, this, kind) } } } @@ -1654,7 +1457,29 @@ private class FieldOrPropertyRead extends FieldOrPropertyAccess, AssignableRead } } -predicate jumpStep = jumpStepImpl/2; +/** + * Holds if `pred` can flow to `succ`, by jumping from one callable to + * another. Additional steps specified by the configuration are *not* + * taken into account. + */ +predicate jumpStep(Node pred, Node succ) { + pred.(NonLocalJumpNode).getAJumpSuccessor(true) = succ + or + exists(FieldOrProperty fl, FieldOrPropertyRead flr | + fl.isStatic() and + fl.isFieldLike() and + fl.getAnAssignedValue() = pred.asExpr() and + fl.getAnAccess() = flr and + flr = succ.asExpr() and + flr.hasNonlocalValue() + ) + or + exists(JumpReturnKind jrk, DataFlowCall call | + FlowSummaryImpl::Private::summaryReturnNode(pred, jrk) and + viableCallable(call) = jrk.getTarget() and + succ = getAnOutNode(call, jrk.getTargetReturnKind()) + ) +} private class StoreStepConfiguration extends ControlFlowReachabilityConfiguration { StoreStepConfiguration() { this = "StoreStepConfiguration" } @@ -1675,7 +1500,46 @@ private class StoreStepConfiguration extends ControlFlowReachabilityConfiguratio } } -predicate storeStep = storeStepImpl/3; +pragma[nomagic] +private PropertyContent getResultContent() { + result.getProperty() = any(SystemThreadingTasksTaskTClass c_).getResultProperty() +} + +/** + * Holds if data can flow from `node1` to `node2` via an assignment to + * content `c`. + */ +predicate storeStep(Node node1, Content c, Node node2) { + exists(StoreStepConfiguration x, ExprNode node, boolean postUpdate | + hasNodePath(x, node1, node) and + if postUpdate = true then node = node2.(PostUpdateNode).getPreUpdateNode() else node = node2 + | + fieldOrPropertyStore(_, c, node1.asExpr(), node.getExpr(), postUpdate) + or + arrayStore(_, node1.asExpr(), node.getExpr(), postUpdate) and c instanceof ElementContent + ) + or + exists(StoreStepConfiguration x, Expr arg, ControlFlow::Node callCfn | + x.hasExprPath(arg, node1.(ExprNode).getControlFlowNode(), _, callCfn) and + node2 = TParamsArgumentNode(callCfn) and + isParamsArg(_, arg, _) and + c instanceof ElementContent + ) + or + exists(Expr e | + e = node1.asExpr() and + node2.(YieldReturnNode).getYieldReturnStmt().getExpr() = e and + c instanceof ElementContent + ) + or + exists(Expr e | + e = node1.asExpr() and + node2.(AsyncReturnNode).getExpr() = e and + c = getResultContent() + ) + or + FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2) +} private class ReadStepConfiguration extends ControlFlowReachabilityConfiguration { ReadStepConfiguration() { this = "ReadStepConfiguration" } @@ -1742,7 +1606,99 @@ private class ReadStepConfiguration extends ControlFlowReachabilityConfiguration } } -predicate readStep = readStepImpl/3; +/** + * Holds if data can flow from `node1` to `node2` via a read of content `c`. + */ +predicate readStep(Node node1, Content c, Node node2) { + exists(ReadStepConfiguration x | + hasNodePath(x, node1, node2) and + fieldOrPropertyRead(node1.asExpr(), c, node2.asExpr()) + or + hasNodePath(x, node1, node2) and + arrayRead(node1.asExpr(), node2.asExpr()) and + c instanceof ElementContent + or + exists(ForeachStmt fs, Ssa::ExplicitDefinition def | + x.hasDefPath(fs.getIterableExpr(), node1.getControlFlowNode(), def.getADefinition(), + def.getControlFlowNode()) and + node2.(SsaDefinitionNode).getDefinition() = def and + c instanceof ElementContent + ) + or + hasNodePath(x, node1, node2) and + node2.asExpr().(AwaitExpr).getExpr() = node1.asExpr() and + c = getResultContent() + or + // node1 = (..., node2, ...) + // node1.ItemX flows to node2 + exists(TupleExpr te, int i, Expr item | + te = node1.asExpr() and + not te.isConstruction() and + c.(FieldContent).getField() = te.getType().(TupleType).getElement(i).getUnboundDeclaration() and + // node1 = (..., item, ...) + te.getArgument(i) = item + | + // item = (..., ..., ...) in node1 = (..., (..., ..., ...), ...) + node2.asExpr().(TupleExpr) = item and + hasNodePath(x, node1, node2) + or + // item = variable in node1 = (..., variable, ...) + exists(AssignableDefinitions::TupleAssignmentDefinition tad, Ssa::ExplicitDefinition def | + node2.(SsaDefinitionNode).getDefinition() = def and + def.getADefinition() = tad and + tad.getLeaf() = item and + hasNodePath(x, node1, node2) + ) + or + // item = variable in node1 = (..., variable, ...) in a case/is var (..., ...) + te = any(PatternExpr pe).getAChildExpr*() and + exists(AssignableDefinitions::LocalVariableDefinition lvd, Ssa::ExplicitDefinition def | + node2.(SsaDefinitionNode).getDefinition() = def and + def.getADefinition() = lvd and + lvd.getDeclaration() = item and + hasNodePath(x, node1, node2) + ) + ) + ) + or + FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2) +} + +/** + * Holds if values stored inside content `c` are cleared at node `n`. For example, + * any value stored inside `f` is cleared at the pre-update node associated with `x` + * in `x.f = newValue`. + */ +predicate clearsContent(Node n, Content c) { + fieldOrPropertyStore(_, c, _, n.asExpr(), true) + or + fieldOrPropertyStore(_, c, _, n.(ObjectInitializerNode).getInitializer(), false) + or + FlowSummaryImpl::Private::Steps::summaryStoresIntoArg(c, n) and + not c instanceof ElementContent + or + FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c) + or + exists(WithExpr we, ObjectInitializer oi, FieldOrProperty f | + oi = we.getInitializer() and + n.asExpr() = oi and + f = oi.getAMemberInitializer().getInitializedMember() and + c = f.getContent() + ) +} + +/** + * Holds if the node `n` is unreachable when the call context is `call`. + */ +predicate isUnreachableInCall(Node n, DataFlowCall call) { + exists( + ExplicitParameterNode paramNode, Guard guard, ControlFlow::SuccessorTypes::BooleanSuccessor bs + | + viableConstantBooleanParamArg(paramNode, bs.getValue().booleanNot(), call) and + paramNode.getSsaDefinition().getARead() = guard and + guard.controlsBlock(n.getControlFlowNode().getBasicBlock(), bs, _) + ) +} /** * An entity used to represent the type of data-flow node. Two nodes will have @@ -1753,10 +1709,7 @@ predicate readStep = readStepImpl/3; * `DataFlowType`, while `Func` and `Func` are not, because * `string` is not a type parameter. */ -class DataFlowType extends Gvn::GvnType { - pragma[nomagic] - DataFlowType() { this = any(NodeImpl n).getDataFlowType() } -} +class DataFlowType = Gvn::GvnType; /** Gets the type of `n` used for type pruning. */ pragma[inline] @@ -1888,11 +1841,11 @@ private module PostUpdateNodes { } private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode { - private Node pre; + SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, _) } - SummaryPostUpdateNode() { summaryPostUpdateNodeCached(this, pre) } - - override Node getPreUpdateNode() { result = pre } + override Node getPreUpdateNode() { + FlowSummaryImpl::Private::summaryPostUpdateNode(this, result) + } } } @@ -1900,7 +1853,12 @@ private import PostUpdateNodes /** A node that performs a type cast. */ class CastNode extends Node { - CastNode() { castNode(this) } + CastNode() { + this.asExpr() instanceof Cast + or + this.(AssignableDefinitionNode).getDefinition() instanceof + AssignableDefinitions::PatternDefinition + } } class DataFlowExpr = DotNet::Expr; diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPublic.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPublic.qll index 12a62faf387..bfc2f5469d0 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPublic.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPublic.qll @@ -67,6 +67,8 @@ class Node extends TNode { } } +private class TExprNode_ = TExprNode or TCilExprNode; + /** * An expression, viewed as a node in a data flow graph. * @@ -74,9 +76,7 @@ class Node extends TNode { * to multiple `ExprNode`s, just like it may correspond to multiple * `ControlFlow::Node`s. */ -class ExprNode extends Node { - ExprNode() { this = TExprNode(_) or this = TCilExprNode(_) } - +class ExprNode extends Node, TExprNode_ { /** Gets the expression corresponding to this node. */ DotNet::Expr getExpr() { result = this.getExprAtNode(_) @@ -99,7 +99,7 @@ class ExprNode extends Node { * flow graph. */ class ParameterNode extends Node { - ParameterNode() { parameterNode(this, _, _) } + ParameterNode() { this instanceof ParameterNodeImpl } /** Gets the parameter corresponding to this node, if any. */ DotNet::Parameter getParameter() { @@ -110,7 +110,9 @@ class ParameterNode extends Node { * Holds if this node is the parameter of callable `c` at the specified * (zero-based) position. */ - predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) } + predicate isParameterOf(DataFlowCallable c, int i) { + this.(ParameterNodeImpl).isParameterOf(c, i) + } } /** A definition, viewed as a node in a data flow graph. */ diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll index bdf5498d8c6..da6f89071ef 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll @@ -580,88 +580,112 @@ module Private { * summaries into a `SummarizedCallable`s. */ module External { - /** - * Provides a means of translating an externally (e.g., CSV) defined flow - * summary into a `SummarizedCallable`. - */ - abstract class ExternalSummaryCompilation extends string { - bindingset[this] - ExternalSummaryCompilation() { any() } + /** Holds if the `n`th component of specification `s` is `c`. */ + predicate specSplit(string s, string c, int n) { relevantSpec(s) and s.splitAt(" of ", n) = c } - /** Holds if this flow summary is for callable `c`. */ - abstract predicate callable(DataFlowCallable c, boolean preservesValue); + /** Holds if specification `s` has length `len`. */ + predicate specLength(string s, int len) { len = 1 + max(int n | specSplit(s, _, n)) } - /** Holds if the `i`th input component is `c`. */ - abstract predicate input(int i, SummaryComponent c); - - /** Holds if the `i`th output component is `c`. */ - abstract predicate output(int i, SummaryComponent c); - - /** - * Holds if the input components starting from index `i` translate into `suffix`. - */ - final predicate translateInput(int i, SummaryComponentStack suffix) { - exists(SummaryComponent comp | this.input(i, comp) | - i = max(int j | this.input(j, _)) and - suffix = TSingletonSummaryComponentStack(comp) - or - exists(TSummaryComponent head, SummaryComponentStack tail | - this.translateInputCons(i, head, tail) and - suffix = TConsSummaryComponentStack(head, tail) - ) - ) - } - - final predicate translateInputCons(int i, SummaryComponent head, SummaryComponentStack tail) { - this.input(i, head) and - this.translateInput(i + 1, tail) - } - - /** - * Holds if the output components starting from index `i` translate into `suffix`. - */ - predicate translateOutput(int i, SummaryComponentStack suffix) { - exists(SummaryComponent comp | this.output(i, comp) | - i = max(int j | this.output(j, _)) and - suffix = TSingletonSummaryComponentStack(comp) - or - exists(TSummaryComponent head, SummaryComponentStack tail | - this.translateOutputCons(i, head, tail) and - suffix = TConsSummaryComponentStack(head, tail) - ) - ) - } - - predicate translateOutputCons(int i, SummaryComponent head, SummaryComponentStack tail) { - this.output(i, head) and - this.translateOutput(i + 1, tail) - } + /** Gets the last component of specification `s`. */ + string specLast(string s) { + exists(int len | + specLength(s, len) and + specSplit(s, result, len - 1) + ) } - private class ExternalRequiredSummaryComponentStack extends RequiredSummaryComponentStack { - private SummaryComponent head; - - ExternalRequiredSummaryComponentStack() { - any(ExternalSummaryCompilation s).translateInputCons(_, head, this) or - any(ExternalSummaryCompilation s).translateOutputCons(_, head, this) - } - - override predicate required(SummaryComponent c) { c = head } + /** Holds if specification component `c` parses as parameter `n`. */ + predicate parseParam(string c, int n) { + specSplit(_, c, _) and + ( + c.regexpCapture("Parameter\\[([-0-9]+)\\]", 1).toInt() = n + or + exists(int n1, int n2 | + c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and + c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and + n = [n1 .. n2] + ) + ) } - class ExternalSummarizedCallableAdaptor extends SummarizedCallable { - ExternalSummarizedCallableAdaptor() { any(ExternalSummaryCompilation s).callable(this, _) } + /** Holds if specification component `c` parses as argument `n`. */ + predicate parseArg(string c, int n) { + specSplit(_, c, _) and + ( + c.regexpCapture("Argument\\[([-0-9]+)\\]", 1).toInt() = n + or + exists(int n1, int n2 | + c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and + c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and + n = [n1 .. n2] + ) + ) + } + + private SummaryComponent interpretComponent(string c) { + specSplit(_, c, _) and + ( + exists(int pos | parseArg(c, pos) and result = SummaryComponent::argument(pos)) + or + exists(int pos | parseParam(c, pos) and result = SummaryComponent::parameter(pos)) + or + result = interpretComponentSpecific(c) + ) + } + + private predicate interpretSpec(string spec, int idx, SummaryComponentStack stack) { + exists(string c | + relevantSpec(spec) and + specLength(spec, idx + 1) and + specSplit(spec, c, idx) and + stack = SummaryComponentStack::singleton(interpretComponent(c)) + ) + or + exists(SummaryComponent head, SummaryComponentStack tail | + interpretSpec(spec, idx, head, tail) and + stack = SummaryComponentStack::push(head, tail) + ) + } + + private predicate interpretSpec( + string output, int idx, SummaryComponent head, SummaryComponentStack tail + ) { + exists(string c | + interpretSpec(output, idx + 1, tail) and + specSplit(output, c, idx) and + head = interpretComponent(c) + ) + } + + private class MkStack extends RequiredSummaryComponentStack { + MkStack() { interpretSpec(_, _, _, this) } + + override predicate required(SummaryComponent c) { interpretSpec(_, _, c, this) } + } + + private class SummarizedCallableExternal extends SummarizedCallable { + SummarizedCallableExternal() { externalSummary(this, _, _, _) } override predicate propagatesFlow( SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue ) { - exists(ExternalSummaryCompilation s | - s.callable(this, preservesValue) and - s.translateInput(0, input) and - s.translateOutput(0, output) + exists(string inSpec, string outSpec, string kind | + externalSummary(this, inSpec, outSpec, kind) and + interpretSpec(inSpec, 0, input) and + interpretSpec(outSpec, 0, output) + | + kind = "value" and preservesValue = true + or + kind = "taint" and preservesValue = false ) } } + + /** Holds if component `c` of specification `spec` cannot be parsed. */ + predicate invalidSpecComponent(string spec, string c) { + specSplit(spec, c, _) and + not exists(interpretComponent(c)) + } } /** Provides a query predicate for outputting a set of relevant flow summaries. */ diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImplSpecific.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImplSpecific.qll index 01e3a2e1633..5568b8d3368 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImplSpecific.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/FlowSummaryImplSpecific.qll @@ -77,3 +77,30 @@ DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { result = Gvn::getGlobalValueNumber(dt.getDelegateType().getReturnType()) ) } + +/** Holds if `spec` is a relevant external specification. */ +predicate relevantSpec(string spec) { none() } + +/** + * Holds if an external flow summary exists for `c` with input specification + * `input`, output specification `output`, and kind `kind`. + */ +predicate externalSummary(DataFlowCallable c, string input, string output, string kind) { none() } + +/** Gets the summary component for specification component `c`, if any. */ +bindingset[c] +SummaryComponent interpretComponentSpecific(string c) { + c = "ReturnValue" and result = SummaryComponent::return(any(NormalReturnKind nrk)) + or + c = "Element" and result = SummaryComponent::content(any(ElementContent ec)) + or + exists(Field f | + c.regexpCapture("Field\\[(.+)\\]", 1) = f.getQualifiedName() and + result = SummaryComponent::content(any(FieldContent fc | fc.getField() = f)) + ) + or + exists(Property p | + c.regexpCapture("Property\\[(.+)\\]", 1) = p.getQualifiedName() and + result = SummaryComponent::content(any(PropertyContent pc | pc.getProperty() = p)) + ) +} diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll index 72525d3234a..462838abcd1 100755 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll @@ -1,6 +1,5 @@ private import csharp private import TaintTrackingPublic -private import DataFlowImplCommon private import FlowSummaryImpl as FlowSummaryImpl private import semmle.code.csharp.Caching private import semmle.code.csharp.dataflow.internal.DataFlowPrivate @@ -79,7 +78,6 @@ private class LocalTaintExprStepConfiguration extends ControlFlowReachabilityCon } private predicate localTaintStepCommon(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { - Stages::DataFlowStage::forceCachingInSameStage() and hasNodePath(any(LocalTaintExprStepConfiguration x), nodeFrom, nodeTo) or localTaintStepCil(nodeFrom, nodeTo) @@ -87,6 +85,11 @@ private predicate localTaintStepCommon(DataFlow::Node nodeFrom, DataFlow::Node n cached private module Cached { + private import DataFlowImplCommon as DataFlowImplCommon + + cached + predicate forceCachingInSameStage() { DataFlowImplCommon::forceCachingInSameStage() } + /** * Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local * (intra-procedural) step. diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/ModulusAnalysisSpecific.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/ModulusAnalysisSpecific.qll index f71b0d0ffcd..030020ede63 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/ModulusAnalysisSpecific.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/ModulusAnalysisSpecific.qll @@ -49,7 +49,7 @@ module Private { } int getId(PhiInputEdgeBlock bb) { - exists(CfgImpl::ControlFlowTree::Range t | CfgImpl::ControlFlowTree::idOf(t, result) | + exists(CfgImpl::ControlFlowTree::Range_ t | CfgImpl::ControlFlowTree::idOf(t, result) | t = bb.getFirstNode().getElement() or t = bb.(CS::ControlFlow::BasicBlocks::EntryBlock).getCallable() diff --git a/csharp/ql/src/semmle/code/csharp/dispatch/Dispatch.qll b/csharp/ql/src/semmle/code/csharp/dispatch/Dispatch.qll index cca858d69ba..f06fbca375d 100644 --- a/csharp/ql/src/semmle/code/csharp/dispatch/Dispatch.qll +++ b/csharp/ql/src/semmle/code/csharp/dispatch/Dispatch.qll @@ -387,7 +387,8 @@ private module Internal { result = this.getAStaticTarget() or result.getUnboundDeclaration() = - this.getASubsumedStaticTarget0(Gvn::getGlobalValueNumber(result.getDeclaringType())) + this.getASubsumedStaticTarget0(pragma[only_bind_out](Gvn::getGlobalValueNumber(result + .getDeclaringType()))) } /** diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme index 9258e9b38d8..770f844243d 100644 --- a/csharp/ql/src/semmlecode.csharp.dbscheme +++ b/csharp/ql/src/semmlecode.csharp.dbscheme @@ -529,7 +529,7 @@ function_pointer_return_type( int return_type_id: @type_or_ref ref); extend( - unique int sub: @type ref, + int sub: @type ref, int super: @type_or_ref ref); anonymous_types( diff --git a/csharp/ql/test/experimental/ir/ir/raw_ir.expected b/csharp/ql/test/experimental/ir/ir/raw_ir.expected index 646cd0f26f9..5d3a1dfe6bf 100644 --- a/csharp/ql/test/experimental/ir/ir/raw_ir.expected +++ b/csharp/ql/test/experimental/ir/ir/raw_ir.expected @@ -623,38 +623,38 @@ foreach.cs: #-----| Goto -> Block 1 # 7| Block 1 -# 7| r7_8(glval) = VariableAddress[#temp7:9] : -# 7| r7_9(Boolean) = Load[#temp7:9] : &:r7_8, ~m? -# 7| r7_10() = FunctionAddress[MoveNext] : -# 7| r7_11(Boolean) = Call[MoveNext] : func:r7_10, this:r7_9 -# 7| mu7_12() = ^CallSideEffect : ~m? -# 7| v7_13(Void) = ConditionalBranch : r7_11 +# 7| r7_8(glval) = VariableAddress[#temp7:9] : +# 7| r7_9(IEnumerator) = Load[#temp7:9] : &:r7_8, ~m? +# 7| r7_10() = FunctionAddress[MoveNext] : +# 7| r7_11(Boolean) = Call[MoveNext] : func:r7_10, this:r7_9 +# 7| mu7_12() = ^CallSideEffect : ~m? +# 7| v7_13(Void) = ConditionalBranch : r7_11 #-----| False -> Block 3 #-----| True -> Block 2 # 7| Block 2 -# 7| r7_14(glval) = VariableAddress[items] : -# 7| r7_15(glval) = VariableAddress[#temp7:9] : -# 7| r7_16(Boolean) = Load[#temp7:9] : &:r7_15, ~m? -# 7| r7_17() = FunctionAddress[get_Current] : -# 7| r7_18(Int32) = Call[get_Current] : func:r7_17, this:r7_16 -# 7| mu7_19() = ^CallSideEffect : ~m? -# 7| mu7_20(Int32) = Store[items] : &:r7_14, r7_18 -# 9| r9_1(glval) = VariableAddress[x] : -# 9| r9_2(glval) = VariableAddress[items] : -# 9| r9_3(Int32) = Load[items] : &:r9_2, ~m? -# 9| mu9_4(Int32) = Store[x] : &:r9_1, r9_3 +# 7| r7_14(glval) = VariableAddress[items] : +# 7| r7_15(glval) = VariableAddress[#temp7:9] : +# 7| r7_16(IEnumerator) = Load[#temp7:9] : &:r7_15, ~m? +# 7| r7_17() = FunctionAddress[get_Current] : +# 7| r7_18(Int32) = Call[get_Current] : func:r7_17, this:r7_16 +# 7| mu7_19() = ^CallSideEffect : ~m? +# 7| mu7_20(Int32) = Store[items] : &:r7_14, r7_18 +# 9| r9_1(glval) = VariableAddress[x] : +# 9| r9_2(glval) = VariableAddress[items] : +# 9| r9_3(Int32) = Load[items] : &:r9_2, ~m? +# 9| mu9_4(Int32) = Store[x] : &:r9_1, r9_3 #-----| Goto (back edge) -> Block 1 # 7| Block 3 -# 7| r7_21(glval) = VariableAddress[#temp7:9] : -# 7| r7_22(Boolean) = Load[#temp7:9] : &:r7_21, ~m? -# 7| r7_23() = FunctionAddress[Dispose] : -# 7| v7_24(Void) = Call[Dispose] : func:r7_23, this:r7_22 -# 7| mu7_25() = ^CallSideEffect : ~m? -# 4| v4_3(Void) = ReturnVoid : -# 4| v4_4(Void) = AliasedUse : ~m? -# 4| v4_5(Void) = ExitFunction : +# 7| r7_21(glval) = VariableAddress[#temp7:9] : +# 7| r7_22(IEnumerator) = Load[#temp7:9] : &:r7_21, ~m? +# 7| r7_23() = FunctionAddress[Dispose] : +# 7| v7_24(Void) = Call[Dispose] : func:r7_23, this:r7_22 +# 7| mu7_25() = ^CallSideEffect : ~m? +# 4| v4_3(Void) = ReturnVoid : +# 4| v4_4(Void) = AliasedUse : ~m? +# 4| v4_5(Void) = ExitFunction : func_with_param_call.cs: # 5| System.Int32 test_call_with_param.f(System.Int32,System.Int32) diff --git a/csharp/ql/test/experimental/ir/ir/raw_ir_consistency.expected b/csharp/ql/test/experimental/ir/ir/raw_ir_consistency.expected index b2f9c0da160..7231134d5e2 100644 --- a/csharp/ql/test/experimental/ir/ir/raw_ir_consistency.expected +++ b/csharp/ql/test/experimental/ir/ir/raw_ir_consistency.expected @@ -26,9 +26,6 @@ fieldAddressOnNonPointer | inoutref.cs:19:13:19:17 | FieldAddress: access to field fld | FieldAddress instruction 'FieldAddress: access to field fld' has an object address operand that is not an address, in function '$@'. | inoutref.cs:16:17:16:17 | System.Void InOutRef.F(System.Int32,MyStruct,MyStruct,MyClass,MyClass) | System.Void InOutRef.F(System.Int32,MyStruct,MyStruct,MyClass,MyClass) | | pointers.cs:35:17:35:24 | FieldAddress: access to field fld | FieldAddress instruction 'FieldAddress: access to field fld' has an object address operand that is not an address, in function '$@'. | pointers.cs:25:17:25:20 | System.Void Pointers.Main() | System.Void Pointers.Main() | thisArgumentIsNonPointer -| foreach.cs:7:9:10:9 | Call: foreach (... ... in ...) ... | Call instruction 'Call: foreach (... ... in ...) ...' has a `this` argument operand that is not an address, in function '$@'. | foreach.cs:4:24:4:27 | System.Void ForEach.Main() | System.Void ForEach.Main() | -| foreach.cs:7:9:10:9 | Call: foreach (... ... in ...) ... | Call instruction 'Call: foreach (... ... in ...) ...' has a `this` argument operand that is not an address, in function '$@'. | foreach.cs:4:24:4:27 | System.Void ForEach.Main() | System.Void ForEach.Main() | -| foreach.cs:7:9:10:9 | Call: foreach (... ... in ...) ... | Call instruction 'Call: foreach (... ... in ...) ...' has a `this` argument operand that is not an address, in function '$@'. | foreach.cs:4:24:4:27 | System.Void ForEach.Main() | System.Void ForEach.Main() | | inoutref.cs:32:22:32:35 | Call: object creation of type MyStruct | Call instruction 'Call: object creation of type MyStruct' has a `this` argument operand that is not an address, in function '$@'. | inoutref.cs:29:17:29:20 | System.Void InOutRef.Main() | System.Void InOutRef.Main() | | pointers.cs:27:22:27:35 | Call: object creation of type MyStruct | Call instruction 'Call: object creation of type MyStruct' has a `this` argument operand that is not an address, in function '$@'. | pointers.cs:25:17:25:20 | System.Void Pointers.Main() | System.Void Pointers.Main() | missingCanonicalLanguageType diff --git a/csharp/ql/test/experimental/ir/ir/unaliased_ssa_consistency.expected b/csharp/ql/test/experimental/ir/ir/unaliased_ssa_consistency.expected index b2f9c0da160..7231134d5e2 100644 --- a/csharp/ql/test/experimental/ir/ir/unaliased_ssa_consistency.expected +++ b/csharp/ql/test/experimental/ir/ir/unaliased_ssa_consistency.expected @@ -26,9 +26,6 @@ fieldAddressOnNonPointer | inoutref.cs:19:13:19:17 | FieldAddress: access to field fld | FieldAddress instruction 'FieldAddress: access to field fld' has an object address operand that is not an address, in function '$@'. | inoutref.cs:16:17:16:17 | System.Void InOutRef.F(System.Int32,MyStruct,MyStruct,MyClass,MyClass) | System.Void InOutRef.F(System.Int32,MyStruct,MyStruct,MyClass,MyClass) | | pointers.cs:35:17:35:24 | FieldAddress: access to field fld | FieldAddress instruction 'FieldAddress: access to field fld' has an object address operand that is not an address, in function '$@'. | pointers.cs:25:17:25:20 | System.Void Pointers.Main() | System.Void Pointers.Main() | thisArgumentIsNonPointer -| foreach.cs:7:9:10:9 | Call: foreach (... ... in ...) ... | Call instruction 'Call: foreach (... ... in ...) ...' has a `this` argument operand that is not an address, in function '$@'. | foreach.cs:4:24:4:27 | System.Void ForEach.Main() | System.Void ForEach.Main() | -| foreach.cs:7:9:10:9 | Call: foreach (... ... in ...) ... | Call instruction 'Call: foreach (... ... in ...) ...' has a `this` argument operand that is not an address, in function '$@'. | foreach.cs:4:24:4:27 | System.Void ForEach.Main() | System.Void ForEach.Main() | -| foreach.cs:7:9:10:9 | Call: foreach (... ... in ...) ... | Call instruction 'Call: foreach (... ... in ...) ...' has a `this` argument operand that is not an address, in function '$@'. | foreach.cs:4:24:4:27 | System.Void ForEach.Main() | System.Void ForEach.Main() | | inoutref.cs:32:22:32:35 | Call: object creation of type MyStruct | Call instruction 'Call: object creation of type MyStruct' has a `this` argument operand that is not an address, in function '$@'. | inoutref.cs:29:17:29:20 | System.Void InOutRef.Main() | System.Void InOutRef.Main() | | pointers.cs:27:22:27:35 | Call: object creation of type MyStruct | Call instruction 'Call: object creation of type MyStruct' has a `this` argument operand that is not an address, in function '$@'. | pointers.cs:25:17:25:20 | System.Void Pointers.Main() | System.Void Pointers.Main() | missingCanonicalLanguageType diff --git a/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected b/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected index 2ec16a930fb..45b1b481612 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected @@ -752,7 +752,6 @@ | MultiImplementationA.cs:8:16:8:16 | exit M | MultiImplementationB.cs:5:16:5:16 | exit M | 1 | | MultiImplementationA.cs:8:29:8:32 | null | MultiImplementationA.cs:8:16:8:16 | exit M (abnormal) | 3 | | MultiImplementationA.cs:8:29:8:32 | null | MultiImplementationB.cs:5:16:5:16 | exit M (abnormal) | 3 | -| MultiImplementationA.cs:13:16:13:16 | this access | MultiImplementationA.cs:13:16:13:20 | ... = ... | 3 | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationA.cs:14:31:14:31 | exit get_Item (normal) | 2 | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationB.cs:12:31:12:40 | exit get_Item (normal) | 2 | | MultiImplementationA.cs:14:31:14:31 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | enter get_Item | 1 | @@ -776,19 +775,17 @@ | MultiImplementationA.cs:16:17:16:18 | exit M1 (normal) | MultiImplementationB.cs:14:17:14:18 | exit M1 | 2 | | MultiImplementationA.cs:17:5:19:5 | {...} | MultiImplementationA.cs:18:9:18:22 | M2(...) | 2 | | MultiImplementationA.cs:18:9:18:22 | enter M2 | MultiImplementationA.cs:18:9:18:22 | exit M2 | 4 | -| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | 1 | +| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationA.cs:20:12:20:13 | exit C2 (normal) | 14 | +| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationB.cs:18:12:18:13 | exit C2 (normal) | 14 | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | enter C2 | 1 | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | enter C2 | 1 | | MultiImplementationA.cs:20:12:20:13 | exit C2 | MultiImplementationA.cs:20:12:20:13 | exit C2 | 1 | | MultiImplementationA.cs:20:12:20:13 | exit C2 | MultiImplementationB.cs:18:12:18:13 | exit C2 | 1 | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:20:12:20:13 | exit C2 (normal) | 6 | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationB.cs:18:12:18:13 | exit C2 (normal) | 6 | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | enter C2 | 1 | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | enter C2 | 1 | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 | 2 | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 | 2 | -| MultiImplementationA.cs:21:24:21:24 | 0 | MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | 2 | -| MultiImplementationA.cs:21:27:21:29 | {...} | MultiImplementationA.cs:21:27:21:29 | {...} | 1 | +| MultiImplementationA.cs:21:24:21:24 | 0 | MultiImplementationA.cs:21:27:21:29 | {...} | 3 | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | 1 | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | 1 | | MultiImplementationA.cs:22:6:22:7 | exit ~C2 | MultiImplementationA.cs:22:6:22:7 | exit ~C2 | 1 | @@ -801,7 +798,6 @@ | MultiImplementationA.cs:23:28:23:35 | exit implicit conversion | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | 1 | | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationA.cs:23:28:23:35 | exit implicit conversion (normal) | 2 | | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion (normal) | 2 | -| MultiImplementationA.cs:24:16:24:16 | this access | MultiImplementationA.cs:24:32:24:34 | ... = ... | 4 | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationA.cs:30:21:30:23 | exit get_P3 | 5 | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationB.cs:27:21:27:23 | exit get_P3 | 5 | | MultiImplementationA.cs:36:9:36:10 | enter M1 | MultiImplementationA.cs:36:9:36:10 | enter M1 | 1 | @@ -835,7 +831,6 @@ | MultiImplementationB.cs:5:16:5:16 | exit M | MultiImplementationB.cs:5:16:5:16 | exit M | 1 | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationA.cs:8:16:8:16 | exit M (normal) | 2 | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationB.cs:5:16:5:16 | exit M (normal) | 2 | -| MultiImplementationB.cs:11:16:11:16 | this access | MultiImplementationB.cs:11:16:11:20 | ... = ... | 3 | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | enter get_Item | 1 | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationB.cs:12:31:12:40 | enter get_Item | 1 | | MultiImplementationB.cs:12:31:12:40 | exit get_Item | MultiImplementationA.cs:14:31:14:31 | exit get_Item | 1 | @@ -859,19 +854,17 @@ | MultiImplementationB.cs:14:17:14:18 | exit M1 (normal) | MultiImplementationB.cs:14:17:14:18 | exit M1 | 2 | | MultiImplementationB.cs:15:5:17:5 | {...} | MultiImplementationB.cs:16:9:16:31 | M2(...) | 2 | | MultiImplementationB.cs:16:9:16:31 | enter M2 | MultiImplementationB.cs:16:9:16:31 | exit M2 | 5 | -| MultiImplementationB.cs:18:12:18:13 | call to constructor Object | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | 1 | +| MultiImplementationB.cs:18:12:18:13 | call to constructor Object | MultiImplementationA.cs:20:12:20:13 | exit C2 (abnormal) | 12 | +| MultiImplementationB.cs:18:12:18:13 | call to constructor Object | MultiImplementationB.cs:18:12:18:13 | exit C2 (abnormal) | 12 | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | enter C2 | 1 | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | enter C2 | 1 | | MultiImplementationB.cs:18:12:18:13 | exit C2 | MultiImplementationA.cs:20:12:20:13 | exit C2 | 1 | | MultiImplementationB.cs:18:12:18:13 | exit C2 | MultiImplementationB.cs:18:12:18:13 | exit C2 | 1 | -| MultiImplementationB.cs:18:22:18:36 | {...} | MultiImplementationA.cs:20:12:20:13 | exit C2 (abnormal) | 4 | -| MultiImplementationB.cs:18:22:18:36 | {...} | MultiImplementationB.cs:18:12:18:13 | exit C2 (abnormal) | 4 | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | enter C2 | 1 | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | enter C2 | 1 | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 | 2 | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 | 2 | -| MultiImplementationB.cs:19:24:19:24 | 1 | MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | 2 | -| MultiImplementationB.cs:19:27:19:29 | {...} | MultiImplementationB.cs:19:27:19:29 | {...} | 1 | +| MultiImplementationB.cs:19:24:19:24 | 1 | MultiImplementationB.cs:19:27:19:29 | {...} | 3 | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | 1 | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | 1 | | MultiImplementationB.cs:20:6:20:7 | exit ~C2 | MultiImplementationA.cs:22:6:22:7 | exit ~C2 | 1 | @@ -884,7 +877,6 @@ | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | 1 | | MultiImplementationB.cs:21:56:21:59 | null | MultiImplementationA.cs:23:28:23:35 | exit implicit conversion (abnormal) | 3 | | MultiImplementationB.cs:21:56:21:59 | null | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion (abnormal) | 3 | -| MultiImplementationB.cs:22:16:22:16 | this access | MultiImplementationB.cs:22:32:22:34 | ... = ... | 4 | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationA.cs:30:21:30:23 | exit get_P3 | 5 | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationB.cs:27:21:27:23 | exit get_P3 | 5 | | MultiImplementationB.cs:32:9:32:10 | enter M1 | MultiImplementationA.cs:36:9:36:10 | enter M1 | 1 | @@ -930,6 +922,8 @@ | NullCoalescing.cs:15:31:15:31 | 0 | NullCoalescing.cs:16:17:16:18 | "" | 5 | | NullCoalescing.cs:16:17:16:25 | ... ?? ... | NullCoalescing.cs:17:13:17:19 | (...) ... | 5 | | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:13:10:13:11 | exit M6 | 4 | +| PartialImplementationA.cs:3:12:3:18 | enter Partial | PartialImplementationA.cs:3:12:3:18 | exit Partial | 12 | +| PartialImplementationB.cs:4:12:4:18 | enter Partial | PartialImplementationB.cs:4:12:4:18 | exit Partial | 12 | | Patterns.cs:5:10:5:11 | enter M1 | Patterns.cs:8:18:8:23 | Int32 i1 | 8 | | Patterns.cs:8:13:8:23 | [false] ... is ... | Patterns.cs:8:13:8:23 | [false] ... is ... | 1 | | Patterns.cs:8:13:8:23 | [true] ... is ... | Patterns.cs:8:13:8:23 | [true] ... is ... | 1 | diff --git a/csharp/ql/test/library-tests/controlflow/graph/Consistency.expected b/csharp/ql/test/library-tests/controlflow/graph/Consistency.expected index 0df56b8f207..bccab8e85a4 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Consistency.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/Consistency.expected @@ -5,27 +5,3 @@ breakInvariant3 breakInvariant4 breakInvariant5 multipleSuccessors -| MultiImplementationA.cs:13:16:13:20 | ... = ... | successor | MultiImplementationA.cs:13:16:13:16 | this access | -| MultiImplementationA.cs:13:16:13:20 | ... = ... | successor | MultiImplementationA.cs:24:16:24:16 | this access | -| MultiImplementationA.cs:13:16:13:20 | ... = ... | successor | MultiImplementationB.cs:11:16:11:16 | this access | -| MultiImplementationA.cs:13:16:13:20 | ... = ... | successor | MultiImplementationB.cs:22:16:22:16 | this access | -| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | successor | MultiImplementationA.cs:13:16:13:16 | this access | -| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | successor | MultiImplementationB.cs:11:16:11:16 | this access | -| MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | successor | MultiImplementationA.cs:21:27:21:29 | {...} | -| MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | successor | MultiImplementationB.cs:19:27:19:29 | {...} | -| MultiImplementationA.cs:24:32:24:34 | ... = ... | successor | MultiImplementationA.cs:20:22:20:31 | {...} | -| MultiImplementationA.cs:24:32:24:34 | ... = ... | successor | MultiImplementationA.cs:24:16:24:16 | this access | -| MultiImplementationA.cs:24:32:24:34 | ... = ... | successor | MultiImplementationB.cs:18:22:18:36 | {...} | -| MultiImplementationA.cs:24:32:24:34 | ... = ... | successor | MultiImplementationB.cs:22:16:22:16 | this access | -| MultiImplementationB.cs:11:16:11:20 | ... = ... | successor | MultiImplementationA.cs:13:16:13:16 | this access | -| MultiImplementationB.cs:11:16:11:20 | ... = ... | successor | MultiImplementationA.cs:24:16:24:16 | this access | -| MultiImplementationB.cs:11:16:11:20 | ... = ... | successor | MultiImplementationB.cs:11:16:11:16 | this access | -| MultiImplementationB.cs:11:16:11:20 | ... = ... | successor | MultiImplementationB.cs:22:16:22:16 | this access | -| MultiImplementationB.cs:18:12:18:13 | call to constructor Object | successor | MultiImplementationA.cs:13:16:13:16 | this access | -| MultiImplementationB.cs:18:12:18:13 | call to constructor Object | successor | MultiImplementationB.cs:11:16:11:16 | this access | -| MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | successor | MultiImplementationA.cs:21:27:21:29 | {...} | -| MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | successor | MultiImplementationB.cs:19:27:19:29 | {...} | -| MultiImplementationB.cs:22:32:22:34 | ... = ... | successor | MultiImplementationA.cs:20:22:20:31 | {...} | -| MultiImplementationB.cs:22:32:22:34 | ... = ... | successor | MultiImplementationA.cs:24:16:24:16 | this access | -| MultiImplementationB.cs:22:32:22:34 | ... = ... | successor | MultiImplementationB.cs:18:22:18:36 | {...} | -| MultiImplementationB.cs:22:32:22:34 | ... = ... | successor | MultiImplementationB.cs:22:16:22:16 | this access | diff --git a/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected b/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected index 5a316e90ec0..de10c0acf6f 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/Dominance.expected @@ -2862,6 +2862,7 @@ dominance | MultiImplementationA.cs:8:23:8:32 | throw ... | MultiImplementationB.cs:5:16:5:16 | exit M (abnormal) | | MultiImplementationA.cs:8:29:8:32 | null | MultiImplementationA.cs:8:23:8:32 | throw ... | | MultiImplementationA.cs:13:16:13:16 | this access | MultiImplementationA.cs:13:20:13:20 | 0 | +| MultiImplementationA.cs:13:16:13:20 | ... = ... | MultiImplementationA.cs:24:16:24:16 | this access | | MultiImplementationA.cs:13:20:13:20 | 0 | MultiImplementationA.cs:13:16:13:20 | ... = ... | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationA.cs:14:31:14:31 | exit get_Item (normal) | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationB.cs:12:31:12:40 | exit get_Item (normal) | @@ -2885,6 +2886,7 @@ dominance | MultiImplementationA.cs:18:9:18:22 | enter M2 | MultiImplementationA.cs:18:21:18:21 | 0 | | MultiImplementationA.cs:18:9:18:22 | exit M2 (normal) | MultiImplementationA.cs:18:9:18:22 | exit M2 | | MultiImplementationA.cs:18:21:18:21 | 0 | MultiImplementationA.cs:18:9:18:22 | exit M2 (normal) | +| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationA.cs:13:16:13:16 | this access | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | | MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:20:24:20:29 | ...; | @@ -2897,6 +2899,7 @@ dominance | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationB.cs:19:24:19:24 | 1 | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 | +| MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | MultiImplementationA.cs:21:27:21:29 | {...} | | MultiImplementationA.cs:21:24:21:24 | 0 | MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationA.cs:22:11:22:13 | {...} | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationB.cs:20:11:20:25 | {...} | @@ -2908,6 +2911,7 @@ dominance | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion (normal) | | MultiImplementationA.cs:24:16:24:16 | access to property P | MultiImplementationA.cs:24:32:24:34 | ... = ... | | MultiImplementationA.cs:24:16:24:16 | this access | MultiImplementationA.cs:24:34:24:34 | 0 | +| MultiImplementationA.cs:24:32:24:34 | ... = ... | MultiImplementationA.cs:20:22:20:31 | {...} | | MultiImplementationA.cs:24:34:24:34 | 0 | MultiImplementationA.cs:24:16:24:16 | access to property P | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationA.cs:30:34:30:37 | null | | MultiImplementationA.cs:30:21:30:23 | exit get_P3 (abnormal) | MultiImplementationA.cs:30:21:30:23 | exit get_P3 | @@ -2945,6 +2949,7 @@ dominance | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationA.cs:8:16:8:16 | exit M (normal) | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationB.cs:5:16:5:16 | exit M (normal) | | MultiImplementationB.cs:11:16:11:16 | this access | MultiImplementationB.cs:11:20:11:20 | 1 | +| MultiImplementationB.cs:11:16:11:20 | ... = ... | MultiImplementationB.cs:22:16:22:16 | this access | | MultiImplementationB.cs:11:20:11:20 | 1 | MultiImplementationB.cs:11:16:11:20 | ... = ... | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | access to parameter i | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationB.cs:12:37:12:40 | null | @@ -2970,6 +2975,7 @@ dominance | MultiImplementationB.cs:16:9:16:31 | exit M2 (abnormal) | MultiImplementationB.cs:16:9:16:31 | exit M2 | | MultiImplementationB.cs:16:21:16:30 | throw ... | MultiImplementationB.cs:16:9:16:31 | exit M2 (abnormal) | | MultiImplementationB.cs:16:27:16:30 | null | MultiImplementationB.cs:16:21:16:30 | throw ... | +| MultiImplementationB.cs:18:12:18:13 | call to constructor Object | MultiImplementationB.cs:11:16:11:16 | this access | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | | MultiImplementationB.cs:18:22:18:36 | {...} | MultiImplementationB.cs:18:30:18:33 | null | @@ -2980,6 +2986,7 @@ dominance | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationB.cs:19:24:19:24 | 1 | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 | +| MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | MultiImplementationB.cs:19:27:19:29 | {...} | | MultiImplementationB.cs:19:24:19:24 | 1 | MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationA.cs:22:11:22:13 | {...} | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationB.cs:20:11:20:25 | {...} | @@ -2994,6 +3001,7 @@ dominance | MultiImplementationB.cs:21:56:21:59 | null | MultiImplementationB.cs:21:50:21:59 | throw ... | | MultiImplementationB.cs:22:16:22:16 | access to property P | MultiImplementationB.cs:22:32:22:34 | ... = ... | | MultiImplementationB.cs:22:16:22:16 | this access | MultiImplementationB.cs:22:34:22:34 | 1 | +| MultiImplementationB.cs:22:32:22:34 | ... = ... | MultiImplementationB.cs:18:22:18:36 | {...} | | MultiImplementationB.cs:22:34:22:34 | 1 | MultiImplementationB.cs:22:16:22:16 | access to property P | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationA.cs:30:34:30:37 | null | | MultiImplementationB.cs:27:21:27:23 | exit get_P3 (abnormal) | MultiImplementationA.cs:30:21:30:23 | exit get_P3 | @@ -3058,6 +3066,28 @@ dominance | NullCoalescing.cs:17:13:17:19 | (...) ... | NullCoalescing.cs:17:13:17:24 | ... ?? ... | | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:17:9:17:24 | ... = ... | | NullCoalescing.cs:17:19:17:19 | access to parameter i | NullCoalescing.cs:17:13:17:19 | (...) ... | +| PartialImplementationA.cs:3:12:3:18 | call to constructor Object | PartialImplementationB.cs:3:16:3:16 | this access | +| PartialImplementationA.cs:3:12:3:18 | enter Partial | PartialImplementationA.cs:3:12:3:18 | call to constructor Object | +| PartialImplementationA.cs:3:12:3:18 | exit Partial (normal) | PartialImplementationA.cs:3:12:3:18 | exit Partial | +| PartialImplementationA.cs:3:27:3:29 | {...} | PartialImplementationA.cs:3:12:3:18 | exit Partial (normal) | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationB.cs:3:20:3:20 | 0 | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationB.cs:3:20:3:20 | 0 | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationB.cs:5:16:5:16 | this access | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationB.cs:5:16:5:16 | this access | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationB.cs:3:16:3:20 | ... = ... | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationB.cs:3:16:3:20 | ... = ... | +| PartialImplementationB.cs:4:12:4:18 | call to constructor Object | PartialImplementationB.cs:3:16:3:16 | this access | +| PartialImplementationB.cs:4:12:4:18 | enter Partial | PartialImplementationB.cs:4:12:4:18 | call to constructor Object | +| PartialImplementationB.cs:4:12:4:18 | exit Partial (normal) | PartialImplementationB.cs:4:12:4:18 | exit Partial | +| PartialImplementationB.cs:4:22:4:24 | {...} | PartialImplementationB.cs:4:12:4:18 | exit Partial (normal) | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationB.cs:5:32:5:34 | ... = ... | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationB.cs:5:32:5:34 | ... = ... | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationB.cs:5:34:5:34 | 0 | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationB.cs:5:34:5:34 | 0 | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationA.cs:3:27:3:29 | {...} | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationB.cs:4:22:4:24 | {...} | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationB.cs:5:16:5:16 | access to property P | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationB.cs:5:16:5:16 | access to property P | | Patterns.cs:5:10:5:11 | enter M1 | Patterns.cs:6:5:43:5 | {...} | | Patterns.cs:5:10:5:11 | exit M1 (normal) | Patterns.cs:5:10:5:11 | exit M1 | | Patterns.cs:6:5:43:5 | {...} | Patterns.cs:7:9:7:24 | ... ...; | @@ -6947,6 +6977,7 @@ postDominance | MultiImplementationA.cs:8:16:8:16 | exit M (abnormal) | MultiImplementationA.cs:8:23:8:32 | throw ... | | MultiImplementationA.cs:8:16:8:16 | exit M (normal) | MultiImplementationB.cs:5:23:5:23 | 2 | | MultiImplementationA.cs:8:23:8:32 | throw ... | MultiImplementationA.cs:8:29:8:32 | null | +| MultiImplementationA.cs:13:16:13:16 | this access | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | | MultiImplementationA.cs:13:16:13:20 | ... = ... | MultiImplementationA.cs:13:20:13:20 | 0 | | MultiImplementationA.cs:13:20:13:20 | 0 | MultiImplementationA.cs:13:16:13:16 | this access | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationA.cs:14:31:14:31 | enter get_Item | @@ -6971,10 +7002,11 @@ postDominance | MultiImplementationA.cs:18:9:18:22 | exit M2 | MultiImplementationA.cs:18:9:18:22 | exit M2 (normal) | | MultiImplementationA.cs:18:9:18:22 | exit M2 (normal) | MultiImplementationA.cs:18:21:18:21 | 0 | | MultiImplementationA.cs:18:21:18:21 | 0 | MultiImplementationA.cs:18:9:18:22 | enter M2 | +| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationA.cs:20:12:20:13 | enter C2 | +| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationB.cs:18:12:18:13 | enter C2 | | MultiImplementationA.cs:20:12:20:13 | exit C2 (abnormal) | MultiImplementationB.cs:18:24:18:34 | throw ...; | | MultiImplementationA.cs:20:12:20:13 | exit C2 (normal) | MultiImplementationA.cs:20:24:20:28 | ... = ... | | MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:24:32:24:34 | ... = ... | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationB.cs:22:32:22:34 | ... = ... | | MultiImplementationA.cs:20:24:20:24 | this access | MultiImplementationA.cs:20:24:20:29 | ...; | | MultiImplementationA.cs:20:24:20:28 | ... = ... | MultiImplementationA.cs:20:28:20:28 | access to parameter i | | MultiImplementationA.cs:20:24:20:29 | ...; | MultiImplementationA.cs:20:22:20:31 | {...} | @@ -6984,6 +7016,7 @@ postDominance | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:27:21:29 | {...} | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:27:19:29 | {...} | | MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | MultiImplementationA.cs:21:24:21:24 | 0 | +| MultiImplementationA.cs:21:27:21:29 | {...} | MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | | MultiImplementationA.cs:22:6:22:7 | exit ~C2 (abnormal) | MultiImplementationB.cs:20:13:20:23 | throw ...; | | MultiImplementationA.cs:22:6:22:7 | exit ~C2 (normal) | MultiImplementationA.cs:22:11:22:13 | {...} | | MultiImplementationA.cs:22:11:22:13 | {...} | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | @@ -6993,6 +7026,7 @@ postDominance | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationA.cs:23:28:23:35 | enter implicit conversion | | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationB.cs:21:28:21:35 | enter implicit conversion | | MultiImplementationA.cs:24:16:24:16 | access to property P | MultiImplementationA.cs:24:34:24:34 | 0 | +| MultiImplementationA.cs:24:16:24:16 | this access | MultiImplementationA.cs:13:16:13:20 | ... = ... | | MultiImplementationA.cs:24:32:24:34 | ... = ... | MultiImplementationA.cs:24:16:24:16 | access to property P | | MultiImplementationA.cs:24:34:24:34 | 0 | MultiImplementationA.cs:24:16:24:16 | this access | | MultiImplementationA.cs:30:21:30:23 | exit get_P3 | MultiImplementationA.cs:30:21:30:23 | exit get_P3 (abnormal) | @@ -7028,6 +7062,7 @@ postDominance | MultiImplementationB.cs:5:16:5:16 | exit M (normal) | MultiImplementationB.cs:5:23:5:23 | 2 | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationA.cs:8:16:8:16 | enter M | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationB.cs:5:16:5:16 | enter M | +| MultiImplementationB.cs:11:16:11:16 | this access | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | | MultiImplementationB.cs:11:16:11:20 | ... = ... | MultiImplementationB.cs:11:20:11:20 | 1 | | MultiImplementationB.cs:11:20:11:20 | 1 | MultiImplementationB.cs:11:16:11:16 | this access | | MultiImplementationB.cs:12:31:12:40 | exit get_Item (abnormal) | MultiImplementationB.cs:12:31:12:40 | throw ... | @@ -7052,6 +7087,7 @@ postDominance | MultiImplementationB.cs:16:27:16:30 | null | MultiImplementationB.cs:16:9:16:31 | enter M2 | | MultiImplementationB.cs:18:12:18:13 | exit C2 (abnormal) | MultiImplementationB.cs:18:24:18:34 | throw ...; | | MultiImplementationB.cs:18:12:18:13 | exit C2 (normal) | MultiImplementationA.cs:20:24:20:28 | ... = ... | +| MultiImplementationB.cs:18:22:18:36 | {...} | MultiImplementationB.cs:22:32:22:34 | ... = ... | | MultiImplementationB.cs:18:24:18:34 | throw ...; | MultiImplementationB.cs:18:30:18:33 | null | | MultiImplementationB.cs:18:30:18:33 | null | MultiImplementationB.cs:18:22:18:36 | {...} | | MultiImplementationB.cs:19:12:19:13 | exit C2 | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | @@ -7059,6 +7095,7 @@ postDominance | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:27:21:29 | {...} | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:27:19:29 | {...} | | MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | MultiImplementationB.cs:19:24:19:24 | 1 | +| MultiImplementationB.cs:19:27:19:29 | {...} | MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | | MultiImplementationB.cs:20:6:20:7 | exit ~C2 (abnormal) | MultiImplementationB.cs:20:13:20:23 | throw ...; | | MultiImplementationB.cs:20:6:20:7 | exit ~C2 (normal) | MultiImplementationA.cs:22:11:22:13 | {...} | | MultiImplementationB.cs:20:13:20:23 | throw ...; | MultiImplementationB.cs:20:19:20:22 | null | @@ -7067,6 +7104,7 @@ postDominance | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion (normal) | MultiImplementationA.cs:23:50:23:53 | null | | MultiImplementationB.cs:21:50:21:59 | throw ... | MultiImplementationB.cs:21:56:21:59 | null | | MultiImplementationB.cs:22:16:22:16 | access to property P | MultiImplementationB.cs:22:34:22:34 | 1 | +| MultiImplementationB.cs:22:16:22:16 | this access | MultiImplementationB.cs:11:16:11:20 | ... = ... | | MultiImplementationB.cs:22:32:22:34 | ... = ... | MultiImplementationB.cs:22:16:22:16 | access to property P | | MultiImplementationB.cs:22:34:22:34 | 1 | MultiImplementationB.cs:22:16:22:16 | this access | | MultiImplementationB.cs:27:21:27:23 | exit get_P3 | MultiImplementationA.cs:30:21:30:23 | exit get_P3 (abnormal) | @@ -7130,6 +7168,28 @@ postDominance | NullCoalescing.cs:17:13:17:19 | (...) ... | NullCoalescing.cs:17:19:17:19 | access to parameter i | | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:17:13:17:19 | (...) ... | | NullCoalescing.cs:17:19:17:19 | access to parameter i | NullCoalescing.cs:17:9:17:25 | ...; | +| PartialImplementationA.cs:3:12:3:18 | call to constructor Object | PartialImplementationA.cs:3:12:3:18 | enter Partial | +| PartialImplementationA.cs:3:12:3:18 | exit Partial | PartialImplementationA.cs:3:12:3:18 | exit Partial (normal) | +| PartialImplementationA.cs:3:12:3:18 | exit Partial (normal) | PartialImplementationA.cs:3:27:3:29 | {...} | +| PartialImplementationA.cs:3:27:3:29 | {...} | PartialImplementationB.cs:5:32:5:34 | ... = ... | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationA.cs:3:12:3:18 | call to constructor Object | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationB.cs:4:12:4:18 | call to constructor Object | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationB.cs:3:20:3:20 | 0 | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationB.cs:3:20:3:20 | 0 | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationB.cs:3:16:3:16 | this access | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationB.cs:3:16:3:16 | this access | +| PartialImplementationB.cs:4:12:4:18 | call to constructor Object | PartialImplementationB.cs:4:12:4:18 | enter Partial | +| PartialImplementationB.cs:4:12:4:18 | exit Partial | PartialImplementationB.cs:4:12:4:18 | exit Partial (normal) | +| PartialImplementationB.cs:4:12:4:18 | exit Partial (normal) | PartialImplementationB.cs:4:22:4:24 | {...} | +| PartialImplementationB.cs:4:22:4:24 | {...} | PartialImplementationB.cs:5:32:5:34 | ... = ... | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationB.cs:5:34:5:34 | 0 | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationB.cs:5:34:5:34 | 0 | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationB.cs:3:16:3:20 | ... = ... | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationB.cs:3:16:3:20 | ... = ... | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationB.cs:5:16:5:16 | access to property P | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationB.cs:5:16:5:16 | access to property P | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationB.cs:5:16:5:16 | this access | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationB.cs:5:16:5:16 | this access | | Patterns.cs:5:10:5:11 | exit M1 | Patterns.cs:5:10:5:11 | exit M1 (normal) | | Patterns.cs:5:10:5:11 | exit M1 (normal) | Patterns.cs:40:17:40:17 | access to local variable o | | Patterns.cs:6:5:43:5 | {...} | Patterns.cs:5:10:5:11 | enter M1 | @@ -11364,7 +11424,6 @@ blockDominance | MultiImplementationA.cs:8:16:8:16 | exit M | MultiImplementationA.cs:8:16:8:16 | exit M | | MultiImplementationA.cs:8:16:8:16 | exit M | MultiImplementationB.cs:5:16:5:16 | exit M | | MultiImplementationA.cs:8:29:8:32 | null | MultiImplementationA.cs:8:29:8:32 | null | -| MultiImplementationA.cs:13:16:13:16 | this access | MultiImplementationA.cs:13:16:13:16 | this access | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationA.cs:14:31:14:31 | access to parameter i | | MultiImplementationA.cs:14:31:14:31 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | access to parameter i | | MultiImplementationA.cs:14:31:14:31 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | enter get_Item | @@ -11403,33 +11462,23 @@ blockDominance | MultiImplementationA.cs:17:5:19:5 | {...} | MultiImplementationA.cs:17:5:19:5 | {...} | | MultiImplementationA.cs:18:9:18:22 | enter M2 | MultiImplementationA.cs:18:9:18:22 | enter M2 | | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | -| MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:13:16:13:16 | this access | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | enter C2 | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | exit C2 | -| MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:20:22:20:31 | {...} | -| MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:24:16:24:16 | this access | -| MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:11:16:11:16 | this access | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | enter C2 | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | exit C2 | -| MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:18:22:18:36 | {...} | -| MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:22:16:22:16 | this access | | MultiImplementationA.cs:20:12:20:13 | exit C2 | MultiImplementationA.cs:20:12:20:13 | exit C2 | | MultiImplementationA.cs:20:12:20:13 | exit C2 | MultiImplementationB.cs:18:12:18:13 | exit C2 | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:20:22:20:31 | {...} | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | enter C2 | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationA.cs:21:24:21:24 | 0 | -| MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationA.cs:21:27:21:29 | {...} | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | enter C2 | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationB.cs:19:24:19:24 | 1 | -| MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationB.cs:19:27:19:29 | {...} | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | | MultiImplementationA.cs:21:24:21:24 | 0 | MultiImplementationA.cs:21:24:21:24 | 0 | -| MultiImplementationA.cs:21:27:21:29 | {...} | MultiImplementationA.cs:21:27:21:29 | {...} | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | exit ~C2 | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationA.cs:22:11:22:13 | {...} | @@ -11448,7 +11497,6 @@ blockDominance | MultiImplementationA.cs:23:28:23:35 | exit implicit conversion | MultiImplementationA.cs:23:28:23:35 | exit implicit conversion | | MultiImplementationA.cs:23:28:23:35 | exit implicit conversion | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationA.cs:23:50:23:53 | null | -| MultiImplementationA.cs:24:16:24:16 | this access | MultiImplementationA.cs:24:16:24:16 | this access | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | | MultiImplementationA.cs:36:9:36:10 | enter M1 | MultiImplementationA.cs:36:9:36:10 | enter M1 | @@ -11497,7 +11545,6 @@ blockDominance | MultiImplementationB.cs:5:16:5:16 | exit M | MultiImplementationA.cs:8:16:8:16 | exit M | | MultiImplementationB.cs:5:16:5:16 | exit M | MultiImplementationB.cs:5:16:5:16 | exit M | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationB.cs:5:23:5:23 | 2 | -| MultiImplementationB.cs:11:16:11:16 | this access | MultiImplementationB.cs:11:16:11:16 | this access | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | access to parameter i | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | enter get_Item | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | exit get_Item | @@ -11536,33 +11583,23 @@ blockDominance | MultiImplementationB.cs:15:5:17:5 | {...} | MultiImplementationB.cs:15:5:17:5 | {...} | | MultiImplementationB.cs:16:9:16:31 | enter M2 | MultiImplementationB.cs:16:9:16:31 | enter M2 | | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | -| MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationA.cs:13:16:13:16 | this access | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | enter C2 | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | exit C2 | -| MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationA.cs:20:22:20:31 | {...} | -| MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationA.cs:24:16:24:16 | this access | -| MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:11:16:11:16 | this access | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | enter C2 | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | exit C2 | -| MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:18:22:18:36 | {...} | -| MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:22:16:22:16 | this access | | MultiImplementationB.cs:18:12:18:13 | exit C2 | MultiImplementationA.cs:20:12:20:13 | exit C2 | | MultiImplementationB.cs:18:12:18:13 | exit C2 | MultiImplementationB.cs:18:12:18:13 | exit C2 | -| MultiImplementationB.cs:18:22:18:36 | {...} | MultiImplementationB.cs:18:22:18:36 | {...} | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | enter C2 | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationA.cs:21:24:21:24 | 0 | -| MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationA.cs:21:27:21:29 | {...} | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | enter C2 | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationB.cs:19:24:19:24 | 1 | -| MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationB.cs:19:27:19:29 | {...} | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | | MultiImplementationB.cs:19:24:19:24 | 1 | MultiImplementationB.cs:19:24:19:24 | 1 | -| MultiImplementationB.cs:19:27:19:29 | {...} | MultiImplementationB.cs:19:27:19:29 | {...} | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | exit ~C2 | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationA.cs:22:11:22:13 | {...} | @@ -11581,7 +11618,6 @@ blockDominance | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | MultiImplementationA.cs:23:28:23:35 | exit implicit conversion | | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | | MultiImplementationB.cs:21:56:21:59 | null | MultiImplementationB.cs:21:56:21:59 | null | -| MultiImplementationB.cs:22:16:22:16 | this access | MultiImplementationB.cs:22:16:22:16 | this access | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | | MultiImplementationB.cs:32:9:32:10 | enter M1 | MultiImplementationA.cs:36:9:36:10 | enter M1 | @@ -11677,6 +11713,8 @@ blockDominance | NullCoalescing.cs:16:17:16:25 | ... ?? ... | NullCoalescing.cs:16:17:16:25 | ... ?? ... | | NullCoalescing.cs:16:17:16:25 | ... ?? ... | NullCoalescing.cs:17:13:17:24 | ... ?? ... | | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:17:13:17:24 | ... ?? ... | +| PartialImplementationA.cs:3:12:3:18 | enter Partial | PartialImplementationA.cs:3:12:3:18 | enter Partial | +| PartialImplementationB.cs:4:12:4:18 | enter Partial | PartialImplementationB.cs:4:12:4:18 | enter Partial | | Patterns.cs:5:10:5:11 | enter M1 | Patterns.cs:5:10:5:11 | enter M1 | | Patterns.cs:5:10:5:11 | enter M1 | Patterns.cs:8:13:8:23 | [false] ... is ... | | Patterns.cs:5:10:5:11 | enter M1 | Patterns.cs:8:13:8:23 | [true] ... is ... | @@ -14953,7 +14991,6 @@ postBlockDominance | MultiImplementationA.cs:8:16:8:16 | exit M | MultiImplementationA.cs:8:16:8:16 | exit M | | MultiImplementationA.cs:8:16:8:16 | exit M | MultiImplementationB.cs:5:16:5:16 | exit M | | MultiImplementationA.cs:8:29:8:32 | null | MultiImplementationA.cs:8:29:8:32 | null | -| MultiImplementationA.cs:13:16:13:16 | this access | MultiImplementationA.cs:13:16:13:16 | this access | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationA.cs:14:31:14:31 | access to parameter i | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationA.cs:14:31:14:31 | enter get_Item | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationB.cs:12:31:12:40 | enter get_Item | @@ -14988,31 +15025,21 @@ postBlockDominance | MultiImplementationA.cs:17:5:19:5 | {...} | MultiImplementationA.cs:17:5:19:5 | {...} | | MultiImplementationA.cs:18:9:18:22 | enter M2 | MultiImplementationA.cs:18:9:18:22 | enter M2 | | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | +| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationA.cs:20:12:20:13 | enter C2 | +| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationB.cs:18:12:18:13 | enter C2 | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | enter C2 | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | enter C2 | | MultiImplementationA.cs:20:12:20:13 | exit C2 | MultiImplementationA.cs:20:12:20:13 | exit C2 | | MultiImplementationA.cs:20:12:20:13 | exit C2 | MultiImplementationB.cs:18:12:18:13 | exit C2 | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:13:16:13:16 | this access | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:20:12:20:13 | enter C2 | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:20:22:20:31 | {...} | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:24:16:24:16 | this access | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationB.cs:11:16:11:16 | this access | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationB.cs:18:12:18:13 | enter C2 | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationB.cs:22:16:22:16 | this access | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | enter C2 | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | enter C2 | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | enter C2 | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:24:21:24 | 0 | -| MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:27:21:29 | {...} | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | enter C2 | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:24:19:24 | 1 | -| MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:27:19:29 | {...} | | MultiImplementationA.cs:21:24:21:24 | 0 | MultiImplementationA.cs:21:24:21:24 | 0 | -| MultiImplementationA.cs:21:27:21:29 | {...} | MultiImplementationA.cs:21:27:21:29 | {...} | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | | MultiImplementationA.cs:22:6:22:7 | exit ~C2 | MultiImplementationA.cs:22:6:22:7 | exit ~C2 | @@ -15027,7 +15054,6 @@ postBlockDominance | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationA.cs:23:28:23:35 | enter implicit conversion | | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationA.cs:23:50:23:53 | null | | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationB.cs:21:28:21:35 | enter implicit conversion | -| MultiImplementationA.cs:24:16:24:16 | this access | MultiImplementationA.cs:24:16:24:16 | this access | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | | MultiImplementationA.cs:36:9:36:10 | enter M1 | MultiImplementationA.cs:36:9:36:10 | enter M1 | @@ -15064,7 +15090,6 @@ postBlockDominance | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationA.cs:8:16:8:16 | enter M | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationB.cs:5:16:5:16 | enter M | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationB.cs:5:23:5:23 | 2 | -| MultiImplementationB.cs:11:16:11:16 | this access | MultiImplementationB.cs:11:16:11:16 | this access | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | enter get_Item | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationB.cs:12:31:12:40 | enter get_Item | | MultiImplementationB.cs:12:31:12:40 | exit get_Item | MultiImplementationA.cs:14:31:14:31 | exit get_Item | @@ -15099,19 +15124,15 @@ postBlockDominance | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | enter C2 | | MultiImplementationB.cs:18:12:18:13 | exit C2 | MultiImplementationA.cs:20:12:20:13 | exit C2 | | MultiImplementationB.cs:18:12:18:13 | exit C2 | MultiImplementationB.cs:18:12:18:13 | exit C2 | -| MultiImplementationB.cs:18:22:18:36 | {...} | MultiImplementationB.cs:18:22:18:36 | {...} | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | enter C2 | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | enter C2 | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | enter C2 | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:24:21:24 | 0 | -| MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:27:21:29 | {...} | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | enter C2 | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:24:19:24 | 1 | -| MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:27:19:29 | {...} | | MultiImplementationB.cs:19:24:19:24 | 1 | MultiImplementationB.cs:19:24:19:24 | 1 | -| MultiImplementationB.cs:19:27:19:29 | {...} | MultiImplementationB.cs:19:27:19:29 | {...} | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | | MultiImplementationB.cs:20:6:20:7 | exit ~C2 | MultiImplementationA.cs:22:6:22:7 | exit ~C2 | @@ -15122,7 +15143,6 @@ postBlockDominance | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | MultiImplementationA.cs:23:28:23:35 | exit implicit conversion | | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | | MultiImplementationB.cs:21:56:21:59 | null | MultiImplementationB.cs:21:56:21:59 | null | -| MultiImplementationB.cs:22:16:22:16 | this access | MultiImplementationB.cs:22:16:22:16 | this access | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | | MultiImplementationB.cs:32:9:32:10 | enter M1 | MultiImplementationA.cs:36:9:36:10 | enter M1 | @@ -15218,6 +15238,8 @@ postBlockDominance | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:15:31:15:31 | 0 | | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:16:17:16:25 | ... ?? ... | | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:17:13:17:24 | ... ?? ... | +| PartialImplementationA.cs:3:12:3:18 | enter Partial | PartialImplementationA.cs:3:12:3:18 | enter Partial | +| PartialImplementationB.cs:4:12:4:18 | enter Partial | PartialImplementationB.cs:4:12:4:18 | enter Partial | | Patterns.cs:5:10:5:11 | enter M1 | Patterns.cs:5:10:5:11 | enter M1 | | Patterns.cs:8:13:8:23 | [false] ... is ... | Patterns.cs:8:13:8:23 | [false] ... is ... | | Patterns.cs:8:13:8:23 | [true] ... is ... | Patterns.cs:8:13:8:23 | [true] ... is ... | diff --git a/csharp/ql/test/library-tests/controlflow/graph/EnclosingCallable.expected b/csharp/ql/test/library-tests/controlflow/graph/EnclosingCallable.expected index c9a2116762d..ab190057a83 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/EnclosingCallable.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/EnclosingCallable.expected @@ -3498,6 +3498,30 @@ nodeEnclosing | NullCoalescing.cs:17:13:17:19 | (...) ... | NullCoalescing.cs:13:10:13:11 | M6 | | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:13:10:13:11 | M6 | | NullCoalescing.cs:17:19:17:19 | access to parameter i | NullCoalescing.cs:13:10:13:11 | M6 | +| PartialImplementationA.cs:3:12:3:18 | call to constructor Object | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationA.cs:3:12:3:18 | enter Partial | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationA.cs:3:12:3:18 | exit Partial | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationA.cs:3:12:3:18 | exit Partial (normal) | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationA.cs:3:27:3:29 | {...} | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:4:12:4:18 | call to constructor Object | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:4:12:4:18 | enter Partial | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:4:12:4:18 | exit Partial | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:4:12:4:18 | exit Partial (normal) | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:4:22:4:24 | {...} | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationB.cs:4:12:4:18 | Partial | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationB.cs:4:12:4:18 | Partial | | Patterns.cs:5:10:5:11 | enter M1 | Patterns.cs:5:10:5:11 | M1 | | Patterns.cs:5:10:5:11 | exit M1 | Patterns.cs:5:10:5:11 | M1 | | Patterns.cs:5:10:5:11 | exit M1 (normal) | Patterns.cs:5:10:5:11 | M1 | @@ -5604,8 +5628,6 @@ blockEnclosing | MultiImplementationA.cs:8:16:8:16 | exit M | MultiImplementationB.cs:5:16:5:16 | M | | MultiImplementationA.cs:8:29:8:32 | null | MultiImplementationA.cs:8:16:8:16 | M | | MultiImplementationA.cs:8:29:8:32 | null | MultiImplementationB.cs:5:16:5:16 | M | -| MultiImplementationA.cs:13:16:13:16 | this access | MultiImplementationA.cs:20:12:20:13 | C2 | -| MultiImplementationA.cs:13:16:13:16 | this access | MultiImplementationB.cs:18:12:18:13 | C2 | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationA.cs:14:31:14:31 | get_Item | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationB.cs:12:31:12:40 | get_Item | | MultiImplementationA.cs:14:31:14:31 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | get_Item | @@ -5637,16 +5659,12 @@ blockEnclosing | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | C2 | | MultiImplementationA.cs:20:12:20:13 | exit C2 | MultiImplementationA.cs:20:12:20:13 | C2 | | MultiImplementationA.cs:20:12:20:13 | exit C2 | MultiImplementationB.cs:18:12:18:13 | C2 | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationA.cs:20:12:20:13 | C2 | -| MultiImplementationA.cs:20:22:20:31 | {...} | MultiImplementationB.cs:18:12:18:13 | C2 | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | C2 | | MultiImplementationA.cs:21:12:21:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | C2 | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | C2 | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | C2 | | MultiImplementationA.cs:21:24:21:24 | 0 | MultiImplementationA.cs:21:12:21:13 | C2 | | MultiImplementationA.cs:21:24:21:24 | 0 | MultiImplementationB.cs:19:12:19:13 | C2 | -| MultiImplementationA.cs:21:27:21:29 | {...} | MultiImplementationA.cs:21:12:21:13 | C2 | -| MultiImplementationA.cs:21:27:21:29 | {...} | MultiImplementationB.cs:19:12:19:13 | C2 | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | ~C2 | | MultiImplementationA.cs:22:6:22:7 | enter ~C2 | MultiImplementationB.cs:20:6:20:7 | ~C2 | | MultiImplementationA.cs:22:6:22:7 | exit ~C2 | MultiImplementationA.cs:22:6:22:7 | ~C2 | @@ -5659,8 +5677,6 @@ blockEnclosing | MultiImplementationA.cs:23:28:23:35 | exit implicit conversion | MultiImplementationB.cs:21:28:21:35 | implicit conversion | | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationA.cs:23:28:23:35 | implicit conversion | | MultiImplementationA.cs:23:50:23:53 | null | MultiImplementationB.cs:21:28:21:35 | implicit conversion | -| MultiImplementationA.cs:24:16:24:16 | this access | MultiImplementationA.cs:20:12:20:13 | C2 | -| MultiImplementationA.cs:24:16:24:16 | this access | MultiImplementationB.cs:18:12:18:13 | C2 | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationA.cs:30:21:30:23 | get_P3 | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationB.cs:27:21:27:23 | get_P3 | | MultiImplementationA.cs:36:9:36:10 | enter M1 | MultiImplementationA.cs:36:9:36:10 | M1 | @@ -5694,8 +5710,6 @@ blockEnclosing | MultiImplementationB.cs:5:16:5:16 | exit M | MultiImplementationB.cs:5:16:5:16 | M | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationA.cs:8:16:8:16 | M | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationB.cs:5:16:5:16 | M | -| MultiImplementationB.cs:11:16:11:16 | this access | MultiImplementationA.cs:20:12:20:13 | C2 | -| MultiImplementationB.cs:11:16:11:16 | this access | MultiImplementationB.cs:18:12:18:13 | C2 | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | get_Item | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationB.cs:12:31:12:40 | get_Item | | MultiImplementationB.cs:12:31:12:40 | exit get_Item | MultiImplementationA.cs:14:31:14:31 | get_Item | @@ -5727,16 +5741,12 @@ blockEnclosing | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | C2 | | MultiImplementationB.cs:18:12:18:13 | exit C2 | MultiImplementationA.cs:20:12:20:13 | C2 | | MultiImplementationB.cs:18:12:18:13 | exit C2 | MultiImplementationB.cs:18:12:18:13 | C2 | -| MultiImplementationB.cs:18:22:18:36 | {...} | MultiImplementationA.cs:20:12:20:13 | C2 | -| MultiImplementationB.cs:18:22:18:36 | {...} | MultiImplementationB.cs:18:12:18:13 | C2 | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationA.cs:21:12:21:13 | C2 | | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationB.cs:19:12:19:13 | C2 | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | C2 | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | C2 | | MultiImplementationB.cs:19:24:19:24 | 1 | MultiImplementationA.cs:21:12:21:13 | C2 | | MultiImplementationB.cs:19:24:19:24 | 1 | MultiImplementationB.cs:19:12:19:13 | C2 | -| MultiImplementationB.cs:19:27:19:29 | {...} | MultiImplementationA.cs:21:12:21:13 | C2 | -| MultiImplementationB.cs:19:27:19:29 | {...} | MultiImplementationB.cs:19:12:19:13 | C2 | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationA.cs:22:6:22:7 | ~C2 | | MultiImplementationB.cs:20:6:20:7 | enter ~C2 | MultiImplementationB.cs:20:6:20:7 | ~C2 | | MultiImplementationB.cs:20:6:20:7 | exit ~C2 | MultiImplementationA.cs:22:6:22:7 | ~C2 | @@ -5749,8 +5759,6 @@ blockEnclosing | MultiImplementationB.cs:21:28:21:35 | exit implicit conversion | MultiImplementationB.cs:21:28:21:35 | implicit conversion | | MultiImplementationB.cs:21:56:21:59 | null | MultiImplementationA.cs:23:28:23:35 | implicit conversion | | MultiImplementationB.cs:21:56:21:59 | null | MultiImplementationB.cs:21:28:21:35 | implicit conversion | -| MultiImplementationB.cs:22:16:22:16 | this access | MultiImplementationA.cs:20:12:20:13 | C2 | -| MultiImplementationB.cs:22:16:22:16 | this access | MultiImplementationB.cs:18:12:18:13 | C2 | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationA.cs:30:21:30:23 | get_P3 | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationB.cs:27:21:27:23 | get_P3 | | MultiImplementationB.cs:32:9:32:10 | enter M1 | MultiImplementationA.cs:36:9:36:10 | M1 | @@ -5796,6 +5804,8 @@ blockEnclosing | NullCoalescing.cs:15:31:15:31 | 0 | NullCoalescing.cs:13:10:13:11 | M6 | | NullCoalescing.cs:16:17:16:25 | ... ?? ... | NullCoalescing.cs:13:10:13:11 | M6 | | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:13:10:13:11 | M6 | +| PartialImplementationA.cs:3:12:3:18 | enter Partial | PartialImplementationA.cs:3:12:3:18 | Partial | +| PartialImplementationB.cs:4:12:4:18 | enter Partial | PartialImplementationB.cs:4:12:4:18 | Partial | | Patterns.cs:5:10:5:11 | enter M1 | Patterns.cs:5:10:5:11 | M1 | | Patterns.cs:8:13:8:23 | [false] ... is ... | Patterns.cs:5:10:5:11 | M1 | | Patterns.cs:8:13:8:23 | [true] ... is ... | Patterns.cs:5:10:5:11 | M1 | diff --git a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.expected b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.expected index d48906f29c5..4a8935d09b5 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/EntryElement.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/EntryElement.expected @@ -2158,6 +2158,18 @@ | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:17:19:17:19 | access to parameter i | | NullCoalescing.cs:17:19:17:19 | access to parameter i | NullCoalescing.cs:17:19:17:19 | access to parameter i | | NullCoalescing.cs:17:24:17:24 | 1 | NullCoalescing.cs:17:24:17:24 | 1 | +| PartialImplementationA.cs:3:12:3:18 | call to constructor Object | PartialImplementationA.cs:3:12:3:18 | call to constructor Object | +| PartialImplementationA.cs:3:27:3:29 | {...} | PartialImplementationA.cs:3:27:3:29 | {...} | +| PartialImplementationB.cs:3:16:3:16 | access to field F | PartialImplementationB.cs:3:16:3:16 | this access | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationB.cs:3:16:3:16 | this access | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationB.cs:3:16:3:16 | this access | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationB.cs:3:20:3:20 | 0 | +| PartialImplementationB.cs:4:12:4:18 | call to constructor Object | PartialImplementationB.cs:4:12:4:18 | call to constructor Object | +| PartialImplementationB.cs:4:22:4:24 | {...} | PartialImplementationB.cs:4:22:4:24 | {...} | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationB.cs:5:16:5:16 | this access | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationB.cs:5:16:5:16 | this access | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationB.cs:5:16:5:16 | this access | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationB.cs:5:34:5:34 | 0 | | Patterns.cs:6:5:43:5 | {...} | Patterns.cs:6:5:43:5 | {...} | | Patterns.cs:7:9:7:24 | ... ...; | Patterns.cs:7:9:7:24 | ... ...; | | Patterns.cs:7:16:7:23 | Object o = ... | Patterns.cs:7:20:7:23 | null | diff --git a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected index c96b19b371b..0207253e3f9 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/ExitElement.expected @@ -2826,6 +2826,18 @@ | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:17:13:17:24 | ... ?? ... | normal | | NullCoalescing.cs:17:19:17:19 | access to parameter i | NullCoalescing.cs:17:19:17:19 | access to parameter i | normal | | NullCoalescing.cs:17:24:17:24 | 1 | NullCoalescing.cs:17:24:17:24 | 1 | normal | +| PartialImplementationA.cs:3:12:3:18 | call to constructor Object | PartialImplementationA.cs:3:12:3:18 | call to constructor Object | normal | +| PartialImplementationA.cs:3:27:3:29 | {...} | PartialImplementationA.cs:3:27:3:29 | {...} | normal | +| PartialImplementationB.cs:3:16:3:16 | access to field F | PartialImplementationB.cs:3:16:3:16 | this access | normal | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationB.cs:3:16:3:16 | this access | normal | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationB.cs:3:16:3:20 | ... = ... | normal | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationB.cs:3:20:3:20 | 0 | normal | +| PartialImplementationB.cs:4:12:4:18 | call to constructor Object | PartialImplementationB.cs:4:12:4:18 | call to constructor Object | normal | +| PartialImplementationB.cs:4:22:4:24 | {...} | PartialImplementationB.cs:4:22:4:24 | {...} | normal | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationB.cs:5:16:5:16 | this access | normal | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationB.cs:5:16:5:16 | this access | normal | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationB.cs:5:32:5:34 | ... = ... | normal | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationB.cs:5:34:5:34 | 0 | normal | | Patterns.cs:6:5:43:5 | {...} | Patterns.cs:40:17:40:17 | access to local variable o | normal | | Patterns.cs:7:9:7:24 | ... ...; | Patterns.cs:7:16:7:23 | Object o = ... | normal | | Patterns.cs:7:16:7:23 | Object o = ... | Patterns.cs:7:16:7:23 | Object o = ... | normal | diff --git a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected index c39b233b314..2441e36f62d 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/NodeGraph.expected @@ -3211,10 +3211,7 @@ | MultiImplementationA.cs:8:23:8:32 | throw ... | MultiImplementationB.cs:5:16:5:16 | exit M (abnormal) | semmle.label | exception(NullReferenceException) | | MultiImplementationA.cs:8:29:8:32 | null | MultiImplementationA.cs:8:23:8:32 | throw ... | semmle.label | successor | | MultiImplementationA.cs:13:16:13:16 | this access | MultiImplementationA.cs:13:20:13:20 | 0 | semmle.label | successor | -| MultiImplementationA.cs:13:16:13:20 | ... = ... | MultiImplementationA.cs:13:16:13:16 | this access | semmle.label | successor | | MultiImplementationA.cs:13:16:13:20 | ... = ... | MultiImplementationA.cs:24:16:24:16 | this access | semmle.label | successor | -| MultiImplementationA.cs:13:16:13:20 | ... = ... | MultiImplementationB.cs:11:16:11:16 | this access | semmle.label | successor | -| MultiImplementationA.cs:13:16:13:20 | ... = ... | MultiImplementationB.cs:22:16:22:16 | this access | semmle.label | successor | | MultiImplementationA.cs:13:20:13:20 | 0 | MultiImplementationA.cs:13:16:13:20 | ... = ... | semmle.label | successor | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationA.cs:14:31:14:31 | exit get_Item (normal) | semmle.label | successor | | MultiImplementationA.cs:14:31:14:31 | access to parameter i | MultiImplementationB.cs:12:31:12:40 | exit get_Item (normal) | semmle.label | successor | @@ -3251,7 +3248,6 @@ | MultiImplementationA.cs:18:9:18:22 | exit M2 (normal) | MultiImplementationA.cs:18:9:18:22 | exit M2 | semmle.label | successor | | MultiImplementationA.cs:18:21:18:21 | 0 | MultiImplementationA.cs:18:9:18:22 | exit M2 (normal) | semmle.label | successor | | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationA.cs:13:16:13:16 | this access | semmle.label | successor | -| MultiImplementationA.cs:20:12:20:13 | call to constructor Object | MultiImplementationB.cs:11:16:11:16 | this access | semmle.label | successor | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | semmle.label | successor | | MultiImplementationA.cs:20:12:20:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | semmle.label | successor | | MultiImplementationA.cs:20:12:20:13 | exit C2 (abnormal) | MultiImplementationA.cs:20:12:20:13 | exit C2 | semmle.label | successor | @@ -3269,7 +3265,6 @@ | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 | semmle.label | successor | | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 | semmle.label | successor | | MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | MultiImplementationA.cs:21:27:21:29 | {...} | semmle.label | successor | -| MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | MultiImplementationB.cs:19:27:19:29 | {...} | semmle.label | successor | | MultiImplementationA.cs:21:24:21:24 | 0 | MultiImplementationA.cs:21:19:21:22 | call to constructor C2 | semmle.label | successor | | MultiImplementationA.cs:21:27:21:29 | {...} | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | semmle.label | successor | | MultiImplementationA.cs:21:27:21:29 | {...} | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | semmle.label | successor | @@ -3292,9 +3287,6 @@ | MultiImplementationA.cs:24:16:24:16 | access to property P | MultiImplementationA.cs:24:32:24:34 | ... = ... | semmle.label | successor | | MultiImplementationA.cs:24:16:24:16 | this access | MultiImplementationA.cs:24:34:24:34 | 0 | semmle.label | successor | | MultiImplementationA.cs:24:32:24:34 | ... = ... | MultiImplementationA.cs:20:22:20:31 | {...} | semmle.label | successor | -| MultiImplementationA.cs:24:32:24:34 | ... = ... | MultiImplementationA.cs:24:16:24:16 | this access | semmle.label | successor | -| MultiImplementationA.cs:24:32:24:34 | ... = ... | MultiImplementationB.cs:18:22:18:36 | {...} | semmle.label | successor | -| MultiImplementationA.cs:24:32:24:34 | ... = ... | MultiImplementationB.cs:22:16:22:16 | this access | semmle.label | successor | | MultiImplementationA.cs:24:34:24:34 | 0 | MultiImplementationA.cs:24:16:24:16 | access to property P | semmle.label | successor | | MultiImplementationA.cs:30:21:30:23 | enter get_P3 | MultiImplementationA.cs:30:34:30:37 | null | semmle.label | successor | | MultiImplementationA.cs:30:21:30:23 | exit get_P3 (abnormal) | MultiImplementationA.cs:30:21:30:23 | exit get_P3 | semmle.label | successor | @@ -3352,9 +3344,6 @@ | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationA.cs:8:16:8:16 | exit M (normal) | semmle.label | successor | | MultiImplementationB.cs:5:23:5:23 | 2 | MultiImplementationB.cs:5:16:5:16 | exit M (normal) | semmle.label | successor | | MultiImplementationB.cs:11:16:11:16 | this access | MultiImplementationB.cs:11:20:11:20 | 1 | semmle.label | successor | -| MultiImplementationB.cs:11:16:11:20 | ... = ... | MultiImplementationA.cs:13:16:13:16 | this access | semmle.label | successor | -| MultiImplementationB.cs:11:16:11:20 | ... = ... | MultiImplementationA.cs:24:16:24:16 | this access | semmle.label | successor | -| MultiImplementationB.cs:11:16:11:20 | ... = ... | MultiImplementationB.cs:11:16:11:16 | this access | semmle.label | successor | | MultiImplementationB.cs:11:16:11:20 | ... = ... | MultiImplementationB.cs:22:16:22:16 | this access | semmle.label | successor | | MultiImplementationB.cs:11:20:11:20 | 1 | MultiImplementationB.cs:11:16:11:20 | ... = ... | semmle.label | successor | | MultiImplementationB.cs:12:31:12:40 | enter get_Item | MultiImplementationA.cs:14:31:14:31 | access to parameter i | semmle.label | successor | @@ -3393,7 +3382,6 @@ | MultiImplementationB.cs:16:9:16:31 | exit M2 (abnormal) | MultiImplementationB.cs:16:9:16:31 | exit M2 | semmle.label | successor | | MultiImplementationB.cs:16:21:16:30 | throw ... | MultiImplementationB.cs:16:9:16:31 | exit M2 (abnormal) | semmle.label | exception(NullReferenceException) | | MultiImplementationB.cs:16:27:16:30 | null | MultiImplementationB.cs:16:21:16:30 | throw ... | semmle.label | successor | -| MultiImplementationB.cs:18:12:18:13 | call to constructor Object | MultiImplementationA.cs:13:16:13:16 | this access | semmle.label | successor | | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | MultiImplementationB.cs:11:16:11:16 | this access | semmle.label | successor | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationA.cs:20:12:20:13 | call to constructor Object | semmle.label | successor | | MultiImplementationB.cs:18:12:18:13 | enter C2 | MultiImplementationB.cs:18:12:18:13 | call to constructor Object | semmle.label | successor | @@ -3409,7 +3397,6 @@ | MultiImplementationB.cs:19:12:19:13 | enter C2 | MultiImplementationB.cs:19:24:19:24 | 1 | semmle.label | successor | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationA.cs:21:12:21:13 | exit C2 | semmle.label | successor | | MultiImplementationB.cs:19:12:19:13 | exit C2 (normal) | MultiImplementationB.cs:19:12:19:13 | exit C2 | semmle.label | successor | -| MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | MultiImplementationA.cs:21:27:21:29 | {...} | semmle.label | successor | | MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | MultiImplementationB.cs:19:27:19:29 | {...} | semmle.label | successor | | MultiImplementationB.cs:19:24:19:24 | 1 | MultiImplementationB.cs:19:19:19:22 | call to constructor C2 | semmle.label | successor | | MultiImplementationB.cs:19:27:19:29 | {...} | MultiImplementationA.cs:21:12:21:13 | exit C2 (normal) | semmle.label | successor | @@ -3435,10 +3422,7 @@ | MultiImplementationB.cs:21:56:21:59 | null | MultiImplementationB.cs:21:50:21:59 | throw ... | semmle.label | successor | | MultiImplementationB.cs:22:16:22:16 | access to property P | MultiImplementationB.cs:22:32:22:34 | ... = ... | semmle.label | successor | | MultiImplementationB.cs:22:16:22:16 | this access | MultiImplementationB.cs:22:34:22:34 | 1 | semmle.label | successor | -| MultiImplementationB.cs:22:32:22:34 | ... = ... | MultiImplementationA.cs:20:22:20:31 | {...} | semmle.label | successor | -| MultiImplementationB.cs:22:32:22:34 | ... = ... | MultiImplementationA.cs:24:16:24:16 | this access | semmle.label | successor | | MultiImplementationB.cs:22:32:22:34 | ... = ... | MultiImplementationB.cs:18:22:18:36 | {...} | semmle.label | successor | -| MultiImplementationB.cs:22:32:22:34 | ... = ... | MultiImplementationB.cs:22:16:22:16 | this access | semmle.label | successor | | MultiImplementationB.cs:22:34:22:34 | 1 | MultiImplementationB.cs:22:16:22:16 | access to property P | semmle.label | successor | | MultiImplementationB.cs:27:21:27:23 | enter get_P3 | MultiImplementationA.cs:30:34:30:37 | null | semmle.label | successor | | MultiImplementationB.cs:27:21:27:23 | exit get_P3 (abnormal) | MultiImplementationA.cs:30:21:30:23 | exit get_P3 | semmle.label | successor | @@ -3524,6 +3508,28 @@ | NullCoalescing.cs:17:13:17:19 | (...) ... | NullCoalescing.cs:17:13:17:24 | ... ?? ... | semmle.label | non-null | | NullCoalescing.cs:17:13:17:24 | ... ?? ... | NullCoalescing.cs:17:9:17:24 | ... = ... | semmle.label | successor | | NullCoalescing.cs:17:19:17:19 | access to parameter i | NullCoalescing.cs:17:13:17:19 | (...) ... | semmle.label | successor | +| PartialImplementationA.cs:3:12:3:18 | call to constructor Object | PartialImplementationB.cs:3:16:3:16 | this access | semmle.label | successor | +| PartialImplementationA.cs:3:12:3:18 | enter Partial | PartialImplementationA.cs:3:12:3:18 | call to constructor Object | semmle.label | successor | +| PartialImplementationA.cs:3:12:3:18 | exit Partial (normal) | PartialImplementationA.cs:3:12:3:18 | exit Partial | semmle.label | successor | +| PartialImplementationA.cs:3:27:3:29 | {...} | PartialImplementationA.cs:3:12:3:18 | exit Partial (normal) | semmle.label | successor | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationB.cs:3:20:3:20 | 0 | semmle.label | successor | +| PartialImplementationB.cs:3:16:3:16 | this access | PartialImplementationB.cs:3:20:3:20 | 0 | semmle.label | successor | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationB.cs:5:16:5:16 | this access | semmle.label | successor | +| PartialImplementationB.cs:3:16:3:20 | ... = ... | PartialImplementationB.cs:5:16:5:16 | this access | semmle.label | successor | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationB.cs:3:16:3:20 | ... = ... | semmle.label | successor | +| PartialImplementationB.cs:3:20:3:20 | 0 | PartialImplementationB.cs:3:16:3:20 | ... = ... | semmle.label | successor | +| PartialImplementationB.cs:4:12:4:18 | call to constructor Object | PartialImplementationB.cs:3:16:3:16 | this access | semmle.label | successor | +| PartialImplementationB.cs:4:12:4:18 | enter Partial | PartialImplementationB.cs:4:12:4:18 | call to constructor Object | semmle.label | successor | +| PartialImplementationB.cs:4:12:4:18 | exit Partial (normal) | PartialImplementationB.cs:4:12:4:18 | exit Partial | semmle.label | successor | +| PartialImplementationB.cs:4:22:4:24 | {...} | PartialImplementationB.cs:4:12:4:18 | exit Partial (normal) | semmle.label | successor | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationB.cs:5:32:5:34 | ... = ... | semmle.label | successor | +| PartialImplementationB.cs:5:16:5:16 | access to property P | PartialImplementationB.cs:5:32:5:34 | ... = ... | semmle.label | successor | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationB.cs:5:34:5:34 | 0 | semmle.label | successor | +| PartialImplementationB.cs:5:16:5:16 | this access | PartialImplementationB.cs:5:34:5:34 | 0 | semmle.label | successor | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationA.cs:3:27:3:29 | {...} | semmle.label | successor | +| PartialImplementationB.cs:5:32:5:34 | ... = ... | PartialImplementationB.cs:4:22:4:24 | {...} | semmle.label | successor | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationB.cs:5:16:5:16 | access to property P | semmle.label | successor | +| PartialImplementationB.cs:5:34:5:34 | 0 | PartialImplementationB.cs:5:16:5:16 | access to property P | semmle.label | successor | | Patterns.cs:5:10:5:11 | enter M1 | Patterns.cs:6:5:43:5 | {...} | semmle.label | successor | | Patterns.cs:5:10:5:11 | exit M1 (normal) | Patterns.cs:5:10:5:11 | exit M1 | semmle.label | successor | | Patterns.cs:6:5:43:5 | {...} | Patterns.cs:7:9:7:24 | ... ...; | semmle.label | successor | diff --git a/csharp/ql/test/library-tests/controlflow/graph/Nodes.expected b/csharp/ql/test/library-tests/controlflow/graph/Nodes.expected index b58f1a57cc7..8b158822831 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/Nodes.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/Nodes.expected @@ -1219,6 +1219,8 @@ entryPoint | NullCoalescing.cs:9:12:9:13 | M4 | NullCoalescing.cs:9:37:9:37 | access to parameter b | | NullCoalescing.cs:11:9:11:10 | M5 | NullCoalescing.cs:11:44:11:45 | access to parameter b1 | | NullCoalescing.cs:13:10:13:11 | M6 | NullCoalescing.cs:14:5:18:5 | {...} | +| PartialImplementationA.cs:3:12:3:18 | Partial | PartialImplementationA.cs:3:12:3:18 | call to constructor Object | +| PartialImplementationB.cs:4:12:4:18 | Partial | PartialImplementationB.cs:4:12:4:18 | call to constructor Object | | Patterns.cs:5:10:5:11 | M1 | Patterns.cs:6:5:43:5 | {...} | | Patterns.cs:47:24:47:25 | M2 | Patterns.cs:48:9:48:9 | access to parameter c | | Patterns.cs:50:24:50:25 | M3 | Patterns.cs:51:9:51:9 | access to parameter c | diff --git a/csharp/ql/test/library-tests/controlflow/graph/PartialImplementationA.cs b/csharp/ql/test/library-tests/controlflow/graph/PartialImplementationA.cs new file mode 100644 index 00000000000..e3061f38a6e --- /dev/null +++ b/csharp/ql/test/library-tests/controlflow/graph/PartialImplementationA.cs @@ -0,0 +1,4 @@ +partial class Partial +{ + public Partial(int i) { } +} diff --git a/csharp/ql/test/library-tests/controlflow/graph/PartialImplementationB.cs b/csharp/ql/test/library-tests/controlflow/graph/PartialImplementationB.cs new file mode 100644 index 00000000000..c5120b44cdd --- /dev/null +++ b/csharp/ql/test/library-tests/controlflow/graph/PartialImplementationB.cs @@ -0,0 +1,6 @@ +partial class Partial +{ + public int F = 0; + public Partial() { } + public int P { get; set; } = 0; +} diff --git a/csharp/ql/test/library-tests/regressions/{}.cs b/csharp/ql/test/library-tests/regressions/{}.cs new file mode 100644 index 00000000000..266f8ccf4b1 --- /dev/null +++ b/csharp/ql/test/library-tests/regressions/{}.cs @@ -0,0 +1 @@ +class CheckFileNameEscaping { } \ No newline at end of file diff --git a/csharp/upgrades/9258e9b38d85f92cee9559f2ed21e241f0c7a29e/old.dbscheme b/csharp/upgrades/9258e9b38d85f92cee9559f2ed21e241f0c7a29e/old.dbscheme new file mode 100644 index 00000000000..9258e9b38d8 --- /dev/null +++ b/csharp/upgrades/9258e9b38d85f92cee9559f2ed21e241f0c7a29e/old.dbscheme @@ -0,0 +1,2083 @@ + +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * csc f1.cs f2.cs f3.cs + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | --compiler + * 1 | *path to compiler* + * 2 | --cil + * 3 | f1.cs + * 4 | f2.cs + * 5 | f3.cs + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | f1.cs + * 1 | f2.cs + * 2 | f3.cs + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The references used by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | ref1.dll + * 1 | ref2.dll + * 2 | ref3.dll + */ +#keyset[id, num] +compilation_referencing_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +extractor_messages( + unique int id: @extractor_message, + int severity: int ref, + string origin : string ref, + string text : string ref, + string entity : string ref, + int location: @location_default ref, + string stack_trace : string ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +compilation_assembly( + unique int id : @compilation ref, + int assembly: @assembly ref +) + +/* + * External artifacts + */ + +externalDefects( + unique int id: @externalDefect, + string queryPath: string ref, + int location: @location ref, + string message: string ref, + float severity: float ref); + +externalMetrics( + unique int id: @externalMetric, + string queryPath: string ref, + int location: @location ref, + float value: float ref); + +externalData( + int id: @externalDataElement, + string path: string ref, + int column: int ref, + string value: string ref); + +snapshotDate( + unique date snapshotDate: date ref); + +sourceLocationPrefix( + string prefix: string ref); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id: @duplication, + string relativePath: string ref, + int equivClass: int ref); + +similarCode( + unique int id: @similarity, + string relativePath: string ref, + int equivClass: int ref); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id: @duplication_or_similarity ref, + int offset: int ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +/* + * C# dbscheme + */ + +/** ELEMENTS **/ + +@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration + | @using_directive | @type_parameter_constraints | @external_element + | @xmllocatable | @asp_element | @namespace | @preprocessor_directive; + +@declaration = @callable | @generic | @assignable | @namespace; + +@named_element = @namespace | @declaration; + +@declaration_with_accessors = @property | @indexer | @event; + +@assignable = @variable | @assignable_with_accessors | @event; + +@assignable_with_accessors = @property | @indexer; + +@external_element = @externalMetric | @externalDefect | @externalDataElement; + +@attributable = @assembly | @field | @parameter | @operator | @method | @constructor + | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors + | @local_function; + +/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ + +@location = @location_default | @assembly; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +locations_mapped( + unique int id: @location_default ref, + int mapped_to: @location_default ref); + +@sourceline = @file | @callable | @xmllocatable; + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref); + +assemblies( + unique int id: @assembly, + int file: @file ref, + string fullname: string ref, + string name: string ref, + string version: string ref); + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref); + +@container = @folder | @file ; + +containerparent( + int parent: @container ref, + unique int child: @container ref); + +file_extraction_mode( + unique int file: @file ref, + int mode: int ref + /* 0 = normal, 1 = standalone extractor */ + ); + +/** NAMESPACES **/ + +@type_container = @namespace | @type; + +namespaces( + unique int id: @namespace, + string name: string ref); + +namespace_declarations( + unique int id: @namespace_declaration, + int namespace_id: @namespace ref); + +namespace_declaration_location( + unique int id: @namespace_declaration ref, + int loc: @location ref); + +parent_namespace( + unique int child_id: @type_container ref, + int namespace_id: @namespace ref); + +@declaration_or_directive = @namespace_declaration | @type | @using_directive; + +parent_namespace_declaration( + int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes + int namespace_id: @namespace_declaration ref); + +@using_directive = @using_namespace_directive | @using_static_directive; + +using_namespace_directives( + unique int id: @using_namespace_directive, + int namespace_id: @namespace ref); + +using_static_directives( + unique int id: @using_static_directive, + int type_id: @type_or_ref ref); + +using_directive_location( + unique int id: @using_directive ref, + int loc: @location ref); + +@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define | @directive_undefine | @directive_warning + | @directive_error | @directive_nullable | @directive_line | @directive_region | @directive_endregion | @directive_if + | @directive_elif | @directive_else | @directive_endif; + +@conditional_directive = @directive_if | @directive_elif; +@branch_directive = @directive_if | @directive_elif | @directive_else; + +directive_ifs( + unique int id: @directive_if, + int branchTaken: int ref, /* 0: false, 1: true */ + int conditionValue: int ref); /* 0: false, 1: true */ + +directive_elifs( + unique int id: @directive_elif, + int branchTaken: int ref, /* 0: false, 1: true */ + int conditionValue: int ref, /* 0: false, 1: true */ + int parent: @directive_if ref, + int index: int ref); + +directive_elses( + unique int id: @directive_else, + int branchTaken: int ref, /* 0: false, 1: true */ + int parent: @directive_if ref, + int index: int ref); + +#keyset[id, start] +directive_endifs( + unique int id: @directive_endif, + unique int start: @directive_if ref); + +directive_define_symbols( + unique int id: @define_symbol_expr ref, + string name: string ref); + +directive_regions( + unique int id: @directive_region, + string name: string ref); + +#keyset[id, start] +directive_endregions( + unique int id: @directive_endregion, + unique int start: @directive_region ref); + +directive_lines( + unique int id: @directive_line, + int kind: int ref); /* 0: default, 1: hidden, 2: numeric */ + +directive_line_value( + unique int id: @directive_line ref, + int line: int ref); + +directive_line_file( + unique int id: @directive_line ref, + int file: @file ref +) + +directive_nullables( + unique int id: @directive_nullable, + int setting: int ref, /* 0: disable, 1: enable, 2: restore */ + int target: int ref); /* 0: none, 1: annotations, 2: warnings */ + +directive_warnings( + unique int id: @directive_warning, + string message: string ref); + +directive_errors( + unique int id: @directive_error, + string message: string ref); + +directive_undefines( + unique int id: @directive_undefine, + string name: string ref); + +directive_defines( + unique int id: @directive_define, + string name: string ref); + +pragma_checksums( + unique int id: @pragma_checksum, + int file: @file ref, + string guid: string ref, + string bytes: string ref); + +pragma_warnings( + unique int id: @pragma_warning, + int kind: int ref /* 0 = disable, 1 = restore */); + +#keyset[id, index] +pragma_warning_error_codes( + int id: @pragma_warning ref, + string errorCode: string ref, + int index: int ref); + +preprocessor_directive_location( + unique int id: @preprocessor_directive ref, + int loc: @location ref); + +preprocessor_directive_compilation( + unique int id: @preprocessor_directive ref, + int compilation: @compilation ref); + +preprocessor_directive_active( + unique int id: @preprocessor_directive ref, + int active: int ref); /* 0: false, 1: true */ + +/** TYPES **/ + +types( + unique int id: @type, + int kind: int ref, + string name: string ref); + +case @type.kind of + 1 = @bool_type +| 2 = @char_type +| 3 = @decimal_type +| 4 = @sbyte_type +| 5 = @short_type +| 6 = @int_type +| 7 = @long_type +| 8 = @byte_type +| 9 = @ushort_type +| 10 = @uint_type +| 11 = @ulong_type +| 12 = @float_type +| 13 = @double_type +| 14 = @enum_type +| 15 = @struct_type +| 17 = @class_type +| 19 = @interface_type +| 20 = @delegate_type +| 21 = @null_type +| 22 = @type_parameter +| 23 = @pointer_type +| 24 = @nullable_type +| 25 = @array_type +| 26 = @void_type +| 27 = @int_ptr_type +| 28 = @uint_ptr_type +| 29 = @dynamic_type +| 30 = @arglist_type +| 31 = @unknown_type +| 32 = @tuple_type +| 33 = @function_pointer_type + ; + +@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; +@integral_type = @signed_integral_type | @unsigned_integral_type; +@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; +@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; +@floating_point_type = @float_type | @double_type; +@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type + | @uint_ptr_type | @tuple_type; +@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type + | @dynamic_type; +@value_or_ref_type = @value_type | @ref_type; + +typerefs( + unique int id: @typeref, + string name: string ref); + +typeref_type( + int id: @typeref ref, + unique int typeId: @type ref); + +@type_or_ref = @type | @typeref; + +array_element_type( + unique int array: @array_type ref, + int dimension: int ref, + int rank: int ref, + int element: @type_or_ref ref); + +nullable_underlying_type( + unique int nullable: @nullable_type ref, + int underlying: @type_or_ref ref); + +pointer_referent_type( + unique int pointer: @pointer_type ref, + int referent: @type_or_ref ref); + +enum_underlying_type( + unique int enum_id: @enum_type ref, + int underlying_type_id: @type_or_ref ref); + +delegate_return_type( + unique int delegate_id: @delegate_type ref, + int return_type_id: @type_or_ref ref); + +function_pointer_return_type( + unique int function_pointer_id: @function_pointer_type ref, + int return_type_id: @type_or_ref ref); + +extend( + unique int sub: @type ref, + int super: @type_or_ref ref); + +anonymous_types( + unique int id: @type ref); + +@interface_or_ref = @interface_type | @typeref; + +implement( + int sub: @type ref, + int super: @type_or_ref ref); + +type_location( + int id: @type ref, + int loc: @location ref); + +tuple_underlying_type( + unique int tuple: @tuple_type ref, + int struct: @type_or_ref ref); + +#keyset[tuple, index] +tuple_element( + int tuple: @tuple_type ref, + int index: int ref, + unique int field: @field ref); + +attributes( + unique int id: @attribute, + int type_id: @type_or_ref ref, + int target: @attributable ref); + +attribute_location( + int id: @attribute ref, + int loc: @location ref); + +@type_mention_parent = @element | @type_mention; + +type_mention( + unique int id: @type_mention, + int type_id: @type_or_ref ref, + int parent: @type_mention_parent ref); + +type_mention_location( + unique int id: @type_mention ref, + int loc: @location ref); + +@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic | @function_pointer_type; + +/** + * A direct annotation on an entity, for example `string? x;`. + * + * Annotations: + * 2 = reftype is not annotated "!" + * 3 = reftype is annotated "?" + * 4 = readonly ref type / in parameter + * 5 = ref type parameter, return or local variable + * 6 = out parameter + * + * Note that the annotation depends on the element it annotates. + * @assignable: The annotation is on the type of the assignable, for example the variable type. + * @type_parameter: The annotation is on the reftype constraint + * @callable: The annotation is on the return type + * @array_type: The annotation is on the element type + */ +type_annotation(int id: @has_type_annotation ref, int annotation: int ref); + +nullability(unique int nullability: @nullability, int kind: int ref); + +case @nullability.kind of + 0 = @oblivious +| 1 = @not_annotated +| 2 = @annotated +; + +#keyset[parent, index] +nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) + +type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); + +/** + * The nullable flow state of an expression, as determined by Roslyn. + * 0 = none (default, not populated) + * 1 = not null + * 2 = maybe null + */ +expr_flowstate(unique int id: @expr ref, int state: int ref); + +/** GENERICS **/ + +@generic = @type | @method | @local_function; + +type_parameters( + unique int id: @type_parameter ref, + int index: int ref, + int generic_id: @generic ref, + int variance: int ref /* none = 0, out = 1, in = 2 */); + +#keyset[constructed_id, index] +type_arguments( + int id: @type_or_ref ref, + int index: int ref, + int constructed_id: @generic_or_ref ref); + +@generic_or_ref = @generic | @typeref; + +constructed_generic( + unique int constructed: @generic ref, + int generic: @generic_or_ref ref); + +type_parameter_constraints( + unique int id: @type_parameter_constraints, + int param_id: @type_parameter ref); + +type_parameter_constraints_location( + int id: @type_parameter_constraints ref, + int loc: @location ref); + +general_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int kind: int ref /* class = 1, struct = 2, new = 3 */); + +specific_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref); + +specific_type_parameter_nullability( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref, + int nullability: @nullability ref); + +/** FUNCTION POINTERS */ + +function_pointer_calling_conventions( + int id: @function_pointer_type ref, + int kind: int ref); + +#keyset[id, index] +has_unmanaged_calling_conventions( + int id: @function_pointer_type ref, + int index: int ref, + int conv_id: @type_or_ref ref); + +/** MODIFIERS */ + +@modifiable = @modifiable_direct | @event_accessor; + +@modifiable_direct = @member | @accessor | @local_function | @anonymous_function_expr; + +modifiers( + unique int id: @modifier, + string name: string ref); + +has_modifiers( + int id: @modifiable_direct ref, + int mod_id: @modifier ref); + +compiler_generated(unique int id: @modifiable_direct ref); + +/** MEMBERS **/ + +@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; + +@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; + +@virtualizable = @method | @property | @indexer | @event; + +exprorstmt_name( + unique int parent_id: @named_exprorstmt ref, + string name: string ref); + +nested_types( + unique int id: @type ref, + int declaring_type_id: @type ref, + int unbound_id: @type ref); + +properties( + unique int id: @property, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @property ref); + +property_location( + int id: @property ref, + int loc: @location ref); + +indexers( + unique int id: @indexer, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @indexer ref); + +indexer_location( + int id: @indexer ref, + int loc: @location ref); + +accessors( + unique int id: @accessor, + int kind: int ref, + string name: string ref, + int declaring_member_id: @member ref, + int unbound_id: @accessor ref); + +case @accessor.kind of + 1 = @getter +| 2 = @setter + ; + +init_only_accessors( + unique int id: @accessor ref); + +accessor_location( + int id: @accessor ref, + int loc: @location ref); + +events( + unique int id: @event, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @event ref); + +event_location( + int id: @event ref, + int loc: @location ref); + +event_accessors( + unique int id: @event_accessor, + int kind: int ref, + string name: string ref, + int declaring_event_id: @event ref, + int unbound_id: @event_accessor ref); + +case @event_accessor.kind of + 1 = @add_event_accessor +| 2 = @remove_event_accessor + ; + +event_accessor_location( + int id: @event_accessor ref, + int loc: @location ref); + +operators( + unique int id: @operator, + string name: string ref, + string symbol: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @operator ref); + +operator_location( + int id: @operator ref, + int loc: @location ref); + +constant_value( + int id: @variable ref, + string value: string ref); + +/** CALLABLES **/ + +@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; + +@callable_accessor = @accessor | @event_accessor; + +methods( + unique int id: @method, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @method ref); + +method_location( + int id: @method ref, + int loc: @location ref); + +constructors( + unique int id: @constructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @constructor ref); + +constructor_location( + int id: @constructor ref, + int loc: @location ref); + +destructors( + unique int id: @destructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @destructor ref); + +destructor_location( + int id: @destructor ref, + int loc: @location ref); + +overrides( + int id: @callable ref, + int base_id: @callable ref); + +explicitly_implements( + int id: @member ref, + int interface_id: @interface_or_ref ref); + +local_functions( + unique int id: @local_function, + string name: string ref, + int return_type: @type ref, + int unbound_id: @local_function ref); + +local_function_stmts( + unique int fn: @local_function_stmt ref, + int stmt: @local_function ref); + +/** VARIABLES **/ + +@variable = @local_scope_variable | @field; + +@local_scope_variable = @local_variable | @parameter; + +fields( + unique int id: @field, + int kind: int ref, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @field ref); + +case @field.kind of + 1 = @addressable_field +| 2 = @constant + ; + +field_location( + int id: @field ref, + int loc: @location ref); + +localvars( + unique int id: @local_variable, + int kind: int ref, + string name: string ref, + int implicitly_typed: int ref /* 0 = no, 1 = yes */, + int type_id: @type_or_ref ref, + int parent_id: @local_var_decl_expr ref); + +case @local_variable.kind of + 1 = @addressable_local_variable +| 2 = @local_constant +| 3 = @local_variable_ref + ; + +localvar_location( + unique int id: @local_variable ref, + int loc: @location ref); + +@parameterizable = @callable | @delegate_type | @indexer | @function_pointer_type; + +#keyset[name, parent_id] +#keyset[index, parent_id] +params( + unique int id: @parameter, + string name: string ref, + int type_id: @type_or_ref ref, + int index: int ref, + int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ + int parent_id: @parameterizable ref, + int unbound_id: @parameter ref); + +param_location( + int id: @parameter ref, + int loc: @location ref); + +/** STATEMENTS **/ + +@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; + +statements( + unique int id: @stmt, + int kind: int ref); + +#keyset[index, parent] +stmt_parent( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_stmt_parent = @callable; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +stmt_parent_top_level( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @top_level_stmt_parent ref); + +case @stmt.kind of + 1 = @block_stmt +| 2 = @expr_stmt +| 3 = @if_stmt +| 4 = @switch_stmt +| 5 = @while_stmt +| 6 = @do_stmt +| 7 = @for_stmt +| 8 = @foreach_stmt +| 9 = @break_stmt +| 10 = @continue_stmt +| 11 = @goto_stmt +| 12 = @goto_case_stmt +| 13 = @goto_default_stmt +| 14 = @throw_stmt +| 15 = @return_stmt +| 16 = @yield_stmt +| 17 = @try_stmt +| 18 = @checked_stmt +| 19 = @unchecked_stmt +| 20 = @lock_stmt +| 21 = @using_block_stmt +| 22 = @var_decl_stmt +| 23 = @const_decl_stmt +| 24 = @empty_stmt +| 25 = @unsafe_stmt +| 26 = @fixed_stmt +| 27 = @label_stmt +| 28 = @catch +| 29 = @case_stmt +| 30 = @local_function_stmt +| 31 = @using_decl_stmt + ; + +@using_stmt = @using_block_stmt | @using_decl_stmt; + +@labeled_stmt = @label_stmt | @case; + +@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; + +@cond_stmt = @if_stmt | @switch_stmt; + +@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; + +@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt + | @yield_stmt; + +@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; + + +stmt_location( + unique int id: @stmt ref, + int loc: @location ref); + +catch_type( + unique int catch_id: @catch ref, + int type_id: @type_or_ref ref, + int kind: int ref /* explicit = 1, implicit = 2 */); + +foreach_stmt_info( + unique int id: @foreach_stmt ref, + int kind: int ref /* non-async = 1, async = 2 */); + +@foreach_symbol = @method | @property | @type_or_ref; + +#keyset[id, kind] +foreach_stmt_desugar( + int id: @foreach_stmt ref, + int symbol: @foreach_symbol ref, + int kind: int ref /* GetEnumeratorMethod = 1, CurrentProperty = 2, MoveNextMethod = 3, DisposeMethod = 4, ElementType = 5 */); + +/** EXPRESSIONS **/ + +expressions( + unique int id: @expr, + int kind: int ref, + int type_id: @type_or_ref ref); + +#keyset[index, parent] +expr_parent( + unique int expr: @expr ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter | @directive_if | @directive_elif; + +@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +expr_parent_top_level( + unique int expr: @expr ref, + int index: int ref, + int parent: @top_level_exprorstmt_parent ref); + +case @expr.kind of +/* literal */ + 1 = @bool_literal_expr +| 2 = @char_literal_expr +| 3 = @decimal_literal_expr +| 4 = @int_literal_expr +| 5 = @long_literal_expr +| 6 = @uint_literal_expr +| 7 = @ulong_literal_expr +| 8 = @float_literal_expr +| 9 = @double_literal_expr +| 10 = @string_literal_expr +| 11 = @null_literal_expr +/* primary & unary */ +| 12 = @this_access_expr +| 13 = @base_access_expr +| 14 = @local_variable_access_expr +| 15 = @parameter_access_expr +| 16 = @field_access_expr +| 17 = @property_access_expr +| 18 = @method_access_expr +| 19 = @event_access_expr +| 20 = @indexer_access_expr +| 21 = @array_access_expr +| 22 = @type_access_expr +| 23 = @typeof_expr +| 24 = @method_invocation_expr +| 25 = @delegate_invocation_expr +| 26 = @operator_invocation_expr +| 27 = @cast_expr +| 28 = @object_creation_expr +| 29 = @explicit_delegate_creation_expr +| 30 = @implicit_delegate_creation_expr +| 31 = @array_creation_expr +| 32 = @default_expr +| 33 = @plus_expr +| 34 = @minus_expr +| 35 = @bit_not_expr +| 36 = @log_not_expr +| 37 = @post_incr_expr +| 38 = @post_decr_expr +| 39 = @pre_incr_expr +| 40 = @pre_decr_expr +/* multiplicative */ +| 41 = @mul_expr +| 42 = @div_expr +| 43 = @rem_expr +/* additive */ +| 44 = @add_expr +| 45 = @sub_expr +/* shift */ +| 46 = @lshift_expr +| 47 = @rshift_expr +/* relational */ +| 48 = @lt_expr +| 49 = @gt_expr +| 50 = @le_expr +| 51 = @ge_expr +/* equality */ +| 52 = @eq_expr +| 53 = @ne_expr +/* logical */ +| 54 = @bit_and_expr +| 55 = @bit_xor_expr +| 56 = @bit_or_expr +| 57 = @log_and_expr +| 58 = @log_or_expr +/* type testing */ +| 59 = @is_expr +| 60 = @as_expr +/* null coalescing */ +| 61 = @null_coalescing_expr +/* conditional */ +| 62 = @conditional_expr +/* assignment */ +| 63 = @simple_assign_expr +| 64 = @assign_add_expr +| 65 = @assign_sub_expr +| 66 = @assign_mul_expr +| 67 = @assign_div_expr +| 68 = @assign_rem_expr +| 69 = @assign_and_expr +| 70 = @assign_xor_expr +| 71 = @assign_or_expr +| 72 = @assign_lshift_expr +| 73 = @assign_rshift_expr +/* more */ +| 74 = @object_init_expr +| 75 = @collection_init_expr +| 76 = @array_init_expr +| 77 = @checked_expr +| 78 = @unchecked_expr +| 79 = @constructor_init_expr +| 80 = @add_event_expr +| 81 = @remove_event_expr +| 82 = @par_expr +| 83 = @local_var_decl_expr +| 84 = @lambda_expr +| 85 = @anonymous_method_expr +| 86 = @namespace_expr +/* dynamic */ +| 92 = @dynamic_element_access_expr +| 93 = @dynamic_member_access_expr +/* unsafe */ +| 100 = @pointer_indirection_expr +| 101 = @address_of_expr +| 102 = @sizeof_expr +/* async */ +| 103 = @await_expr +/* C# 6.0 */ +| 104 = @nameof_expr +| 105 = @interpolated_string_expr +| 106 = @unknown_expr +/* C# 7.0 */ +| 107 = @throw_expr +| 108 = @tuple_expr +| 109 = @local_function_invocation_expr +| 110 = @ref_expr +| 111 = @discard_expr +/* C# 8.0 */ +| 112 = @range_expr +| 113 = @index_expr +| 114 = @switch_expr +| 115 = @recursive_pattern_expr +| 116 = @property_pattern_expr +| 117 = @positional_pattern_expr +| 118 = @switch_case_expr +| 119 = @assign_coalesce_expr +| 120 = @suppress_nullable_warning_expr +| 121 = @namespace_access_expr +/* C# 9.0 */ +| 122 = @lt_pattern_expr +| 123 = @gt_pattern_expr +| 124 = @le_pattern_expr +| 125 = @ge_pattern_expr +| 126 = @not_pattern_expr +| 127 = @and_pattern_expr +| 128 = @or_pattern_expr +| 129 = @function_pointer_invocation_expr +| 130 = @with_expr +/* Preprocessor */ +| 999 = @define_symbol_expr +; + +@switch = @switch_stmt | @switch_expr; +@case = @case_stmt | @switch_case_expr; +@pattern_match = @case | @is_expr; +@unary_pattern_expr = @not_pattern_expr; +@relational_pattern_expr = @gt_pattern_expr | @lt_pattern_expr | @ge_pattern_expr | @le_pattern_expr; +@binary_pattern_expr = @and_pattern_expr | @or_pattern_expr; + +@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; +@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; +@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr + | @string_literal_expr | @null_literal_expr; + +@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; +@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; +@assign_event_expr = @add_event_expr | @remove_event_expr; + +@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr + | @assign_rem_expr +@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr + | @assign_lshift_expr | @assign_rshift_expr; + +@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr + | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; +@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; +@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; + +@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; +@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; +@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; + +@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr + | @event_access_expr | @dynamic_member_access_expr; + +@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; + +@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; + +@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; +@incr_op_expr = @pre_incr_expr | @post_incr_expr; +@decr_op_expr = @pre_decr_expr | @post_decr_expr; +@mut_op_expr = @incr_op_expr | @decr_op_expr; +@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; +@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; + +@ternary_log_op_expr = @conditional_expr; +@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; +@un_log_op_expr = @log_not_expr; +@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; + +@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr + | @rshift_expr; +@un_bit_op_expr = @bit_not_expr; +@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; + +@equality_op_expr = @eq_expr | @ne_expr; +@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; +@comp_expr = @equality_op_expr | @rel_op_expr; + +@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; + +@ternary_op = @ternary_log_op_expr; +@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; +@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr + | @pointer_indirection_expr | @address_of_expr; + +@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; + +@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr + | @delegate_invocation_expr | @object_creation_expr | @call_access_expr + | @local_function_invocation_expr | @function_pointer_invocation_expr; + +@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; + +@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr + | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; + +@throw_element = @throw_expr | @throw_stmt; + +@implicitly_typeable_object_creation_expr = @object_creation_expr | @explicit_delegate_creation_expr; + +implicitly_typed_array_creation( + unique int id: @array_creation_expr ref); + +explicitly_sized_array_creation( + unique int id: @array_creation_expr ref); + +stackalloc_array_creation( + unique int id: @array_creation_expr ref); + +implicitly_typed_object_creation( + unique int id: @implicitly_typeable_object_creation_expr ref); + +mutator_invocation_mode( + unique int id: @operator_invocation_expr ref, + int mode: int ref /* prefix = 1, postfix = 2*/); + +expr_compiler_generated( + unique int id: @expr ref); + +expr_value( + unique int id: @expr ref, + string value: string ref); + +expr_call( + unique int caller_id: @expr ref, + int target_id: @callable ref); + +expr_access( + unique int accesser_id: @access_expr ref, + int target_id: @accessible ref); + +@accessible = @method | @assignable | @local_function | @namespace; + +expr_location( + unique int id: @expr ref, + int loc: @location ref); + +dynamic_member_name( + unique int id: @late_bindable_expr ref, + string name: string ref); + +@qualifiable_expr = @member_access_expr + | @method_invocation_expr + | @element_access_expr; + +conditional_access( + unique int id: @qualifiable_expr ref); + +expr_argument( + unique int id: @expr ref, + int mode: int ref); + /* mode is the same as params: value = 0, ref = 1, out = 2 */ + +expr_argument_name( + unique int id: @expr ref, + string name: string ref); + +/** CONTROL/DATA FLOW **/ + +@control_flow_element = @stmt | @expr; + +/* XML Files */ + +xmlEncoding ( + unique int id: @file ref, + string encoding: string ref); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* Comments */ + +commentline( + unique int id: @commentline, + int kind: int ref, + string text: string ref, + string rawtext: string ref); + +case @commentline.kind of + 0 = @singlelinecomment +| 1 = @xmldoccomment +| 2 = @multilinecomment; + +commentline_location( + unique int id: @commentline ref, + int loc: @location ref); + +commentblock( + unique int id : @commentblock); + +commentblock_location( + unique int id: @commentblock ref, + int loc: @location ref); + +commentblock_binding( + int id: @commentblock ref, + int entity: @element ref, + int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ + +commentblock_child( + int id: @commentblock ref, + int commentline: @commentline ref, + int index: int ref); + +/* ASP.NET */ + +case @asp_element.kind of + 0=@asp_close_tag +| 1=@asp_code +| 2=@asp_comment +| 3=@asp_data_binding +| 4=@asp_directive +| 5=@asp_open_tag +| 6=@asp_quoted_string +| 7=@asp_text +| 8=@asp_xml_directive; + +@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; + +asp_elements( + unique int id: @asp_element, + int kind: int ref, + int loc: @location ref); + +asp_comment_server(unique int comment: @asp_comment ref); +asp_code_inline(unique int code: @asp_code ref); +asp_directive_attribute( + int directive: @asp_directive ref, + int index: int ref, + string name: string ref, + int value: @asp_quoted_string ref); +asp_directive_name( + unique int directive: @asp_directive ref, + string name: string ref); +asp_element_body( + unique int element: @asp_element ref, + string body: string ref); +asp_tag_attribute( + int tag: @asp_open_tag ref, + int index: int ref, + string name: string ref, + int attribute: @asp_attribute ref); +asp_tag_name( + unique int tag: @asp_open_tag ref, + string name: string ref); +asp_tag_isempty(int tag: @asp_open_tag ref); + +/* Common Intermediate Language - CIL */ + +case @cil_instruction.opcode of + 0 = @cil_nop +| 1 = @cil_break +| 2 = @cil_ldarg_0 +| 3 = @cil_ldarg_1 +| 4 = @cil_ldarg_2 +| 5 = @cil_ldarg_3 +| 6 = @cil_ldloc_0 +| 7 = @cil_ldloc_1 +| 8 = @cil_ldloc_2 +| 9 = @cil_ldloc_3 +| 10 = @cil_stloc_0 +| 11 = @cil_stloc_1 +| 12 = @cil_stloc_2 +| 13 = @cil_stloc_3 +| 14 = @cil_ldarg_s +| 15 = @cil_ldarga_s +| 16 = @cil_starg_s +| 17 = @cil_ldloc_s +| 18 = @cil_ldloca_s +| 19 = @cil_stloc_s +| 20 = @cil_ldnull +| 21 = @cil_ldc_i4_m1 +| 22 = @cil_ldc_i4_0 +| 23 = @cil_ldc_i4_1 +| 24 = @cil_ldc_i4_2 +| 25 = @cil_ldc_i4_3 +| 26 = @cil_ldc_i4_4 +| 27 = @cil_ldc_i4_5 +| 28 = @cil_ldc_i4_6 +| 29 = @cil_ldc_i4_7 +| 30 = @cil_ldc_i4_8 +| 31 = @cil_ldc_i4_s +| 32 = @cil_ldc_i4 +| 33 = @cil_ldc_i8 +| 34 = @cil_ldc_r4 +| 35 = @cil_ldc_r8 +| 37 = @cil_dup +| 38 = @cil_pop +| 39 = @cil_jmp +| 40 = @cil_call +| 41 = @cil_calli +| 42 = @cil_ret +| 43 = @cil_br_s +| 44 = @cil_brfalse_s +| 45 = @cil_brtrue_s +| 46 = @cil_beq_s +| 47 = @cil_bge_s +| 48 = @cil_bgt_s +| 49 = @cil_ble_s +| 50 = @cil_blt_s +| 51 = @cil_bne_un_s +| 52 = @cil_bge_un_s +| 53 = @cil_bgt_un_s +| 54 = @cil_ble_un_s +| 55 = @cil_blt_un_s +| 56 = @cil_br +| 57 = @cil_brfalse +| 58 = @cil_brtrue +| 59 = @cil_beq +| 60 = @cil_bge +| 61 = @cil_bgt +| 62 = @cil_ble +| 63 = @cil_blt +| 64 = @cil_bne_un +| 65 = @cil_bge_un +| 66 = @cil_bgt_un +| 67 = @cil_ble_un +| 68 = @cil_blt_un +| 69 = @cil_switch +| 70 = @cil_ldind_i1 +| 71 = @cil_ldind_u1 +| 72 = @cil_ldind_i2 +| 73 = @cil_ldind_u2 +| 74 = @cil_ldind_i4 +| 75 = @cil_ldind_u4 +| 76 = @cil_ldind_i8 +| 77 = @cil_ldind_i +| 78 = @cil_ldind_r4 +| 79 = @cil_ldind_r8 +| 80 = @cil_ldind_ref +| 81 = @cil_stind_ref +| 82 = @cil_stind_i1 +| 83 = @cil_stind_i2 +| 84 = @cil_stind_i4 +| 85 = @cil_stind_i8 +| 86 = @cil_stind_r4 +| 87 = @cil_stind_r8 +| 88 = @cil_add +| 89 = @cil_sub +| 90 = @cil_mul +| 91 = @cil_div +| 92 = @cil_div_un +| 93 = @cil_rem +| 94 = @cil_rem_un +| 95 = @cil_and +| 96 = @cil_or +| 97 = @cil_xor +| 98 = @cil_shl +| 99 = @cil_shr +| 100 = @cil_shr_un +| 101 = @cil_neg +| 102 = @cil_not +| 103 = @cil_conv_i1 +| 104 = @cil_conv_i2 +| 105 = @cil_conv_i4 +| 106 = @cil_conv_i8 +| 107 = @cil_conv_r4 +| 108 = @cil_conv_r8 +| 109 = @cil_conv_u4 +| 110 = @cil_conv_u8 +| 111 = @cil_callvirt +| 112 = @cil_cpobj +| 113 = @cil_ldobj +| 114 = @cil_ldstr +| 115 = @cil_newobj +| 116 = @cil_castclass +| 117 = @cil_isinst +| 118 = @cil_conv_r_un +| 121 = @cil_unbox +| 122 = @cil_throw +| 123 = @cil_ldfld +| 124 = @cil_ldflda +| 125 = @cil_stfld +| 126 = @cil_ldsfld +| 127 = @cil_ldsflda +| 128 = @cil_stsfld +| 129 = @cil_stobj +| 130 = @cil_conv_ovf_i1_un +| 131 = @cil_conv_ovf_i2_un +| 132 = @cil_conv_ovf_i4_un +| 133 = @cil_conv_ovf_i8_un +| 134 = @cil_conv_ovf_u1_un +| 135 = @cil_conv_ovf_u2_un +| 136 = @cil_conv_ovf_u4_un +| 137 = @cil_conv_ovf_u8_un +| 138 = @cil_conv_ovf_i_un +| 139 = @cil_conv_ovf_u_un +| 140 = @cil_box +| 141 = @cil_newarr +| 142 = @cil_ldlen +| 143 = @cil_ldelema +| 144 = @cil_ldelem_i1 +| 145 = @cil_ldelem_u1 +| 146 = @cil_ldelem_i2 +| 147 = @cil_ldelem_u2 +| 148 = @cil_ldelem_i4 +| 149 = @cil_ldelem_u4 +| 150 = @cil_ldelem_i8 +| 151 = @cil_ldelem_i +| 152 = @cil_ldelem_r4 +| 153 = @cil_ldelem_r8 +| 154 = @cil_ldelem_ref +| 155 = @cil_stelem_i +| 156 = @cil_stelem_i1 +| 157 = @cil_stelem_i2 +| 158 = @cil_stelem_i4 +| 159 = @cil_stelem_i8 +| 160 = @cil_stelem_r4 +| 161 = @cil_stelem_r8 +| 162 = @cil_stelem_ref +| 163 = @cil_ldelem +| 164 = @cil_stelem +| 165 = @cil_unbox_any +| 179 = @cil_conv_ovf_i1 +| 180 = @cil_conv_ovf_u1 +| 181 = @cil_conv_ovf_i2 +| 182 = @cil_conv_ovf_u2 +| 183 = @cil_conv_ovf_i4 +| 184 = @cil_conv_ovf_u4 +| 185 = @cil_conv_ovf_i8 +| 186 = @cil_conv_ovf_u8 +| 194 = @cil_refanyval +| 195 = @cil_ckinfinite +| 198 = @cil_mkrefany +| 208 = @cil_ldtoken +| 209 = @cil_conv_u2 +| 210 = @cil_conv_u1 +| 211 = @cil_conv_i +| 212 = @cil_conv_ovf_i +| 213 = @cil_conv_ovf_u +| 214 = @cil_add_ovf +| 215 = @cil_add_ovf_un +| 216 = @cil_mul_ovf +| 217 = @cil_mul_ovf_un +| 218 = @cil_sub_ovf +| 219 = @cil_sub_ovf_un +| 220 = @cil_endfinally +| 221 = @cil_leave +| 222 = @cil_leave_s +| 223 = @cil_stind_i +| 224 = @cil_conv_u +| 65024 = @cil_arglist +| 65025 = @cil_ceq +| 65026 = @cil_cgt +| 65027 = @cil_cgt_un +| 65028 = @cil_clt +| 65029 = @cil_clt_un +| 65030 = @cil_ldftn +| 65031 = @cil_ldvirtftn +| 65033 = @cil_ldarg +| 65034 = @cil_ldarga +| 65035 = @cil_starg +| 65036 = @cil_ldloc +| 65037 = @cil_ldloca +| 65038 = @cil_stloc +| 65039 = @cil_localloc +| 65041 = @cil_endfilter +| 65042 = @cil_unaligned +| 65043 = @cil_volatile +| 65044 = @cil_tail +| 65045 = @cil_initobj +| 65046 = @cil_constrained +| 65047 = @cil_cpblk +| 65048 = @cil_initblk +| 65050 = @cil_rethrow +| 65052 = @cil_sizeof +| 65053 = @cil_refanytype +| 65054 = @cil_readonly +; + +// CIL ignored instructions + +@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; + +// CIL local/parameter/field access + +@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; +@cil_starg_any = @cil_starg | @cil_starg_s; + +@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; +@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; + +@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; +@cil_stfld_any = @cil_stfld | @cil_stsfld; + +@cil_local_access = @cil_stloc_any | @cil_ldloc_any; +@cil_arg_access = @cil_starg_any | @cil_ldarg_any; +@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; +@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; + +@cil_stack_access = @cil_local_access | @cil_arg_access; +@cil_field_access = @cil_ldfld_any | @cil_stfld_any; + +@cil_access = @cil_read_access | @cil_write_access; + +// CIL constant/literal instructions + +@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; + +@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | + @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; + +@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; + +@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; + +// Control flow + +@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; +@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | + @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | + @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | + @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; +@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; +@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; +@cil_leave_any = @cil_leave | @cil_leave_s; +@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; + +// CIL call instructions + +@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; + +// CIL expression instructions + +@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | + @cil_newarr | @cil_ldtoken | @cil_sizeof | + @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; + +@cil_unary_expr = + @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| + @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | + @cil_ldind | @cil_unbox; + +@cil_conversion_operation = + @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | + @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | + @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | + @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | + @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | + @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | + @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_i | @cil_conv_u | @cil_conv_r_un; + +@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | + @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; + +@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | + @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; + +@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; + +@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; + +@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | + @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | + @cil_sub_ovf | @cil_sub_ovf_un; + +@cil_unary_bitwise_operation = @cil_not; + +@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; + +@cil_unary_arithmetic_operation = @cil_neg; + +@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; + +// Elements that retrieve an address of something +@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; + +// CIL array instructions + +@cil_read_array = + @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | + @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | + @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; + +@cil_write_array = @cil_stelem | @cil_stelem_ref | + @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | + @cil_stelem_r4 | @cil_stelem_r8; + +@cil_throw_any = @cil_throw | @cil_rethrow; + +#keyset[impl, index] +cil_instruction( + unique int id: @cil_instruction, + int opcode: int ref, + int index: int ref, + int impl: @cil_method_implementation ref); + +cil_jump( + unique int instruction: @cil_jump ref, + int target: @cil_instruction ref); + +cil_access( + unique int instruction: @cil_instruction ref, + int target: @cil_accessible ref); + +cil_value( + unique int instruction: @cil_literal ref, + string value: string ref); + +#keyset[instruction, index] +cil_switch( + int instruction: @cil_switch ref, + int index: int ref, + int target: @cil_instruction ref); + +cil_instruction_location( + unique int id: @cil_instruction ref, + int loc: @location ref); + +cil_type_location( + int id: @cil_type ref, + int loc: @location ref); + +cil_method_location( + int id: @cil_method ref, + int loc: @location ref); + +@cil_namespace = @namespace; + +@cil_type_container = @cil_type | @cil_namespace | @cil_method; + +case @cil_type.kind of + 0 = @cil_valueorreftype +| 1 = @cil_typeparameter +| 2 = @cil_array_type +| 3 = @cil_pointer_type +| 4 = @cil_function_pointer_type +; + +cil_type( + unique int id: @cil_type, + string name: string ref, + int kind: int ref, + int parent: @cil_type_container ref, + int sourceDecl: @cil_type ref); + +cil_pointer_type( + unique int id: @cil_pointer_type ref, + int pointee: @cil_type ref); + +cil_array_type( + unique int id: @cil_array_type ref, + int element_type: @cil_type ref, + int rank: int ref); + +cil_function_pointer_return_type( + unique int id: @cil_function_pointer_type ref, + int return_type: @cil_type ref); + +cil_method( + unique int id: @cil_method, + string name: string ref, + int parent: @cil_type ref, + int return_type: @cil_type ref); + +cil_method_source_declaration( + unique int method: @cil_method ref, + int source: @cil_method ref); + +cil_method_implementation( + unique int id: @cil_method_implementation, + int method: @cil_method ref, + int location: @assembly ref); + +cil_implements( + int id: @cil_method ref, + int decl: @cil_method ref); + +#keyset[parent, name] +cil_field( + unique int id: @cil_field, + int parent: @cil_type ref, + string name: string ref, + int field_type: @cil_type ref); + +@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; +@cil_named_element = @cil_declaration | @cil_namespace; +@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; +@cil_accessible = @cil_declaration; +@cil_variable = @cil_field | @cil_stack_variable; +@cil_stack_variable = @cil_local_variable | @cil_parameter; +@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; +@cil_custom_modifier_receiver = @cil_method | @cil_property | @cil_parameter | @cil_field | @cil_function_pointer_type; +@cil_parameterizable = @cil_method | @cil_function_pointer_type; +@cil_has_type_annotation = @cil_stack_variable | @cil_property | @cil_method | @cil_function_pointer_type; + +#keyset[parameterizable, index] +cil_parameter( + unique int id: @cil_parameter, + int parameterizable: @cil_parameterizable ref, + int index: int ref, + int param_type: @cil_type ref); + +cil_parameter_in(unique int id: @cil_parameter ref); +cil_parameter_out(unique int id: @cil_parameter ref); + +cil_setter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +#keyset[id, modifier] +cil_custom_modifiers( + int id: @cil_custom_modifier_receiver ref, + int modifier: @cil_type ref, + int kind: int ref); // modreq: 1, modopt: 0 + +cil_type_annotation( + int id: @cil_has_type_annotation ref, + int annotation: int ref); + +cil_getter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_adder(unique int event: @cil_event ref, + int method: @cil_method ref); + +cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_property( + unique int id: @cil_property, + int parent: @cil_type ref, + string name: string ref, + int property_type: @cil_type ref); + +#keyset[parent, name] +cil_event(unique int id: @cil_event, + int parent: @cil_type ref, + string name: string ref, + int event_type: @cil_type ref); + +#keyset[impl, index] +cil_local_variable( + unique int id: @cil_local_variable, + int impl: @cil_method_implementation ref, + int index: int ref, + int var_type: @cil_type ref); + +cil_function_pointer_calling_conventions( + int id: @cil_function_pointer_type ref, + int kind: int ref); + +// CIL handlers (exception handlers etc). + +case @cil_handler.kind of + 0 = @cil_catch_handler +| 1 = @cil_filter_handler +| 2 = @cil_finally_handler +| 4 = @cil_fault_handler +; + +#keyset[impl, index] +cil_handler( + unique int id: @cil_handler, + int impl: @cil_method_implementation ref, + int index: int ref, + int kind: int ref, + int try_start: @cil_instruction ref, + int try_end: @cil_instruction ref, + int handler_start: @cil_instruction ref); + +cil_handler_filter( + unique int id: @cil_handler ref, + int filter_start: @cil_instruction ref); + +cil_handler_type( + unique int id: @cil_handler ref, + int catch_type: @cil_type ref); + +@cil_controlflow_node = @cil_entry_point | @cil_instruction; + +@cil_entry_point = @cil_method_implementation | @cil_handler; + +@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; + +cil_method_stack_size( + unique int method: @cil_method_implementation ref, + int size: int ref); + +// CIL modifiers + +cil_public(int id: @cil_member ref); +cil_private(int id: @cil_member ref); +cil_protected(int id: @cil_member ref); +cil_internal(int id: @cil_member ref); +cil_static(int id: @cil_member ref); +cil_sealed(int id: @cil_member ref); +cil_virtual(int id: @cil_method ref); +cil_abstract(int id: @cil_member ref); +cil_class(int id: @cil_type ref); +cil_interface(int id: @cil_type ref); +cil_security(int id: @cil_member ref); +cil_requiresecobject(int id: @cil_method ref); +cil_specialname(int id: @cil_method ref); +cil_newslot(int id: @cil_method ref); + +cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); +cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); +cil_enum_underlying_type(unique int id: @cil_type ref, int underlying: @cil_type ref); + +#keyset[unbound, index] +cil_type_parameter( + int unbound: @cil_member ref, + int index: int ref, + int param: @cil_typeparameter ref); + +#keyset[bound, index] +cil_type_argument( + int bound: @cil_member ref, + int index: int ref, + int t: @cil_type ref); + +// CIL type parameter constraints + +cil_typeparam_covariant(int tp: @cil_typeparameter ref); +cil_typeparam_contravariant(int tp: @cil_typeparameter ref); +cil_typeparam_class(int tp: @cil_typeparameter ref); +cil_typeparam_struct(int tp: @cil_typeparameter ref); +cil_typeparam_new(int tp: @cil_typeparameter ref); +cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); + +// CIL attributes + +cil_attribute( + unique int attributeid: @cil_attribute, + int element: @cil_declaration ref, + int constructor: @cil_method ref); + +#keyset[attribute_id, param] +cil_attribute_named_argument( + int attribute_id: @cil_attribute ref, + string param: string ref, + string value: string ref); + +#keyset[attribute_id, index] +cil_attribute_positional_argument( + int attribute_id: @cil_attribute ref, + int index: int ref, + string value: string ref); + + +// Common .Net data model covering both C# and CIL + +// Common elements +@dotnet_element = @element | @cil_element; +@dotnet_named_element = @named_element | @cil_named_element; +@dotnet_callable = @callable | @cil_method; +@dotnet_variable = @variable | @cil_variable; +@dotnet_field = @field | @cil_field; +@dotnet_parameter = @parameter | @cil_parameter; +@dotnet_declaration = @declaration | @cil_declaration; +@dotnet_member = @member | @cil_member; +@dotnet_event = @event | @cil_event; +@dotnet_property = @property | @cil_property | @indexer; +@dotnet_parameterizable = @parameterizable | @cil_parameterizable; + +// Common types +@dotnet_type = @type | @cil_type; +@dotnet_call = @call | @cil_call_any; +@dotnet_throw = @throw_element | @cil_throw_any; +@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; +@dotnet_typeparameter = @type_parameter | @cil_typeparameter; +@dotnet_array_type = @array_type | @cil_array_type; +@dotnet_pointer_type = @pointer_type | @cil_pointer_type; +@dotnet_type_parameter = @type_parameter | @cil_typeparameter; +@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; + +// Attributes +@dotnet_attribute = @attribute | @cil_attribute; + +// Expressions +@dotnet_expr = @expr | @cil_expr; + +// Literals +@dotnet_literal = @literal_expr | @cil_literal; +@dotnet_string_literal = @string_literal_expr | @cil_ldstr; +@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; +@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; +@dotnet_null_literal = @null_literal_expr | @cil_ldnull; + +@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | + @callable | @value_or_ref_type | @void_type; + +#keyset[entity, location] +metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/9258e9b38d85f92cee9559f2ed21e241f0c7a29e/semmlecode.csharp.dbscheme b/csharp/upgrades/9258e9b38d85f92cee9559f2ed21e241f0c7a29e/semmlecode.csharp.dbscheme new file mode 100644 index 00000000000..770f844243d --- /dev/null +++ b/csharp/upgrades/9258e9b38d85f92cee9559f2ed21e241f0c7a29e/semmlecode.csharp.dbscheme @@ -0,0 +1,2083 @@ + +/** + * An invocation of the compiler. Note that more than one file may be + * compiled per invocation. For example, this command compiles three + * source files: + * + * csc f1.cs f2.cs f3.cs + * + * The `id` simply identifies the invocation, while `cwd` is the working + * directory from which the compiler was invoked. + */ +compilations( + unique int id : @compilation, + string cwd : string ref +); + +/** + * The arguments that were passed to the extractor for a compiler + * invocation. If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then typically there will be rows for + * + * num | arg + * --- | --- + * 0 | --compiler + * 1 | *path to compiler* + * 2 | --cil + * 3 | f1.cs + * 4 | f2.cs + * 5 | f3.cs + */ +#keyset[id, num] +compilation_args( + int id : @compilation ref, + int num : int ref, + string arg : string ref +); + +/** + * The source files that are compiled by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | f1.cs + * 1 | f2.cs + * 2 | f3.cs + */ +#keyset[id, num] +compilation_compiling_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The references used by a compiler invocation. + * If `id` is for the compiler invocation + * + * csc f1.cs f2.cs f3.cs /r:ref1.dll /r:ref2.dll /r:ref3.dll + * + * then there will be rows for + * + * num | arg + * --- | --- + * 0 | ref1.dll + * 1 | ref2.dll + * 2 | ref3.dll + */ +#keyset[id, num] +compilation_referencing_files( + int id : @compilation ref, + int num : int ref, + int file : @file ref +); + +/** + * The time taken by the extractor for a compiler invocation. + * + * For each file `num`, there will be rows for + * + * kind | seconds + * ---- | --- + * 1 | CPU seconds used by the extractor frontend + * 2 | Elapsed seconds during the extractor frontend + * 3 | CPU seconds used by the extractor backend + * 4 | Elapsed seconds during the extractor backend + */ +#keyset[id, num, kind] +compilation_time( + int id : @compilation ref, + int num : int ref, + /* kind: + 1 = frontend_cpu_seconds + 2 = frontend_elapsed_seconds + 3 = extractor_cpu_seconds + 4 = extractor_elapsed_seconds + */ + int kind : int ref, + float seconds : float ref +); + +/** + * An error or warning generated by the extractor. + * The diagnostic message `diagnostic` was generated during compiler + * invocation `compilation`, and is the `file_number_diagnostic_number`th + * message generated while extracting the `file_number`th file of that + * invocation. + */ +#keyset[compilation, file_number, file_number_diagnostic_number] +diagnostic_for( + unique int diagnostic : @diagnostic ref, + int compilation : @compilation ref, + int file_number : int ref, + int file_number_diagnostic_number : int ref +); + +diagnostics( + unique int id: @diagnostic, + int severity: int ref, + string error_tag: string ref, + string error_message: string ref, + string full_error_message: string ref, + int location: @location_default ref +); + +extractor_messages( + unique int id: @extractor_message, + int severity: int ref, + string origin : string ref, + string text : string ref, + string entity : string ref, + int location: @location_default ref, + string stack_trace : string ref +); + +/** + * If extraction was successful, then `cpu_seconds` and + * `elapsed_seconds` are the CPU time and elapsed time (respectively) + * that extraction took for compiler invocation `id`. + */ +compilation_finished( + unique int id : @compilation ref, + float cpu_seconds : float ref, + float elapsed_seconds : float ref +); + +compilation_assembly( + unique int id : @compilation ref, + int assembly: @assembly ref +) + +/* + * External artifacts + */ + +externalDefects( + unique int id: @externalDefect, + string queryPath: string ref, + int location: @location ref, + string message: string ref, + float severity: float ref); + +externalMetrics( + unique int id: @externalMetric, + string queryPath: string ref, + int location: @location ref, + float value: float ref); + +externalData( + int id: @externalDataElement, + string path: string ref, + int column: int ref, + string value: string ref); + +snapshotDate( + unique date snapshotDate: date ref); + +sourceLocationPrefix( + string prefix: string ref); + +/* + * Duplicate code + */ + +duplicateCode( + unique int id: @duplication, + string relativePath: string ref, + int equivClass: int ref); + +similarCode( + unique int id: @similarity, + string relativePath: string ref, + int equivClass: int ref); + +@duplication_or_similarity = @duplication | @similarity + +tokens( + int id: @duplication_or_similarity ref, + int offset: int ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +/* + * C# dbscheme + */ + +/** ELEMENTS **/ + +@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration + | @using_directive | @type_parameter_constraints | @external_element + | @xmllocatable | @asp_element | @namespace | @preprocessor_directive; + +@declaration = @callable | @generic | @assignable | @namespace; + +@named_element = @namespace | @declaration; + +@declaration_with_accessors = @property | @indexer | @event; + +@assignable = @variable | @assignable_with_accessors | @event; + +@assignable_with_accessors = @property | @indexer; + +@external_element = @externalMetric | @externalDefect | @externalDataElement; + +@attributable = @assembly | @field | @parameter | @operator | @method | @constructor + | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors + | @local_function; + +/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/ + +@location = @location_default | @assembly; + +locations_default( + unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref); + +locations_mapped( + unique int id: @location_default ref, + int mapped_to: @location_default ref); + +@sourceline = @file | @callable | @xmllocatable; + +numlines( + int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref); + +assemblies( + unique int id: @assembly, + int file: @file ref, + string fullname: string ref, + string name: string ref, + string version: string ref); + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files( + unique int id: @file, + string name: string ref, + string simple: string ref, + string ext: string ref, + int fromSource: int ref); + +folders( + unique int id: @folder, + string name: string ref, + string simple: string ref); + +@container = @folder | @file ; + +containerparent( + int parent: @container ref, + unique int child: @container ref); + +file_extraction_mode( + unique int file: @file ref, + int mode: int ref + /* 0 = normal, 1 = standalone extractor */ + ); + +/** NAMESPACES **/ + +@type_container = @namespace | @type; + +namespaces( + unique int id: @namespace, + string name: string ref); + +namespace_declarations( + unique int id: @namespace_declaration, + int namespace_id: @namespace ref); + +namespace_declaration_location( + unique int id: @namespace_declaration ref, + int loc: @location ref); + +parent_namespace( + unique int child_id: @type_container ref, + int namespace_id: @namespace ref); + +@declaration_or_directive = @namespace_declaration | @type | @using_directive; + +parent_namespace_declaration( + int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes + int namespace_id: @namespace_declaration ref); + +@using_directive = @using_namespace_directive | @using_static_directive; + +using_namespace_directives( + unique int id: @using_namespace_directive, + int namespace_id: @namespace ref); + +using_static_directives( + unique int id: @using_static_directive, + int type_id: @type_or_ref ref); + +using_directive_location( + unique int id: @using_directive ref, + int loc: @location ref); + +@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define | @directive_undefine | @directive_warning + | @directive_error | @directive_nullable | @directive_line | @directive_region | @directive_endregion | @directive_if + | @directive_elif | @directive_else | @directive_endif; + +@conditional_directive = @directive_if | @directive_elif; +@branch_directive = @directive_if | @directive_elif | @directive_else; + +directive_ifs( + unique int id: @directive_if, + int branchTaken: int ref, /* 0: false, 1: true */ + int conditionValue: int ref); /* 0: false, 1: true */ + +directive_elifs( + unique int id: @directive_elif, + int branchTaken: int ref, /* 0: false, 1: true */ + int conditionValue: int ref, /* 0: false, 1: true */ + int parent: @directive_if ref, + int index: int ref); + +directive_elses( + unique int id: @directive_else, + int branchTaken: int ref, /* 0: false, 1: true */ + int parent: @directive_if ref, + int index: int ref); + +#keyset[id, start] +directive_endifs( + unique int id: @directive_endif, + unique int start: @directive_if ref); + +directive_define_symbols( + unique int id: @define_symbol_expr ref, + string name: string ref); + +directive_regions( + unique int id: @directive_region, + string name: string ref); + +#keyset[id, start] +directive_endregions( + unique int id: @directive_endregion, + unique int start: @directive_region ref); + +directive_lines( + unique int id: @directive_line, + int kind: int ref); /* 0: default, 1: hidden, 2: numeric */ + +directive_line_value( + unique int id: @directive_line ref, + int line: int ref); + +directive_line_file( + unique int id: @directive_line ref, + int file: @file ref +) + +directive_nullables( + unique int id: @directive_nullable, + int setting: int ref, /* 0: disable, 1: enable, 2: restore */ + int target: int ref); /* 0: none, 1: annotations, 2: warnings */ + +directive_warnings( + unique int id: @directive_warning, + string message: string ref); + +directive_errors( + unique int id: @directive_error, + string message: string ref); + +directive_undefines( + unique int id: @directive_undefine, + string name: string ref); + +directive_defines( + unique int id: @directive_define, + string name: string ref); + +pragma_checksums( + unique int id: @pragma_checksum, + int file: @file ref, + string guid: string ref, + string bytes: string ref); + +pragma_warnings( + unique int id: @pragma_warning, + int kind: int ref /* 0 = disable, 1 = restore */); + +#keyset[id, index] +pragma_warning_error_codes( + int id: @pragma_warning ref, + string errorCode: string ref, + int index: int ref); + +preprocessor_directive_location( + unique int id: @preprocessor_directive ref, + int loc: @location ref); + +preprocessor_directive_compilation( + unique int id: @preprocessor_directive ref, + int compilation: @compilation ref); + +preprocessor_directive_active( + unique int id: @preprocessor_directive ref, + int active: int ref); /* 0: false, 1: true */ + +/** TYPES **/ + +types( + unique int id: @type, + int kind: int ref, + string name: string ref); + +case @type.kind of + 1 = @bool_type +| 2 = @char_type +| 3 = @decimal_type +| 4 = @sbyte_type +| 5 = @short_type +| 6 = @int_type +| 7 = @long_type +| 8 = @byte_type +| 9 = @ushort_type +| 10 = @uint_type +| 11 = @ulong_type +| 12 = @float_type +| 13 = @double_type +| 14 = @enum_type +| 15 = @struct_type +| 17 = @class_type +| 19 = @interface_type +| 20 = @delegate_type +| 21 = @null_type +| 22 = @type_parameter +| 23 = @pointer_type +| 24 = @nullable_type +| 25 = @array_type +| 26 = @void_type +| 27 = @int_ptr_type +| 28 = @uint_ptr_type +| 29 = @dynamic_type +| 30 = @arglist_type +| 31 = @unknown_type +| 32 = @tuple_type +| 33 = @function_pointer_type + ; + +@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type; +@integral_type = @signed_integral_type | @unsigned_integral_type; +@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type; +@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type; +@floating_point_type = @float_type | @double_type; +@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type + | @uint_ptr_type | @tuple_type; +@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type + | @dynamic_type; +@value_or_ref_type = @value_type | @ref_type; + +typerefs( + unique int id: @typeref, + string name: string ref); + +typeref_type( + int id: @typeref ref, + unique int typeId: @type ref); + +@type_or_ref = @type | @typeref; + +array_element_type( + unique int array: @array_type ref, + int dimension: int ref, + int rank: int ref, + int element: @type_or_ref ref); + +nullable_underlying_type( + unique int nullable: @nullable_type ref, + int underlying: @type_or_ref ref); + +pointer_referent_type( + unique int pointer: @pointer_type ref, + int referent: @type_or_ref ref); + +enum_underlying_type( + unique int enum_id: @enum_type ref, + int underlying_type_id: @type_or_ref ref); + +delegate_return_type( + unique int delegate_id: @delegate_type ref, + int return_type_id: @type_or_ref ref); + +function_pointer_return_type( + unique int function_pointer_id: @function_pointer_type ref, + int return_type_id: @type_or_ref ref); + +extend( + int sub: @type ref, + int super: @type_or_ref ref); + +anonymous_types( + unique int id: @type ref); + +@interface_or_ref = @interface_type | @typeref; + +implement( + int sub: @type ref, + int super: @type_or_ref ref); + +type_location( + int id: @type ref, + int loc: @location ref); + +tuple_underlying_type( + unique int tuple: @tuple_type ref, + int struct: @type_or_ref ref); + +#keyset[tuple, index] +tuple_element( + int tuple: @tuple_type ref, + int index: int ref, + unique int field: @field ref); + +attributes( + unique int id: @attribute, + int type_id: @type_or_ref ref, + int target: @attributable ref); + +attribute_location( + int id: @attribute ref, + int loc: @location ref); + +@type_mention_parent = @element | @type_mention; + +type_mention( + unique int id: @type_mention, + int type_id: @type_or_ref ref, + int parent: @type_mention_parent ref); + +type_mention_location( + unique int id: @type_mention ref, + int loc: @location ref); + +@has_type_annotation = @assignable | @type_parameter | @callable | @expr | @delegate_type | @generic | @function_pointer_type; + +/** + * A direct annotation on an entity, for example `string? x;`. + * + * Annotations: + * 2 = reftype is not annotated "!" + * 3 = reftype is annotated "?" + * 4 = readonly ref type / in parameter + * 5 = ref type parameter, return or local variable + * 6 = out parameter + * + * Note that the annotation depends on the element it annotates. + * @assignable: The annotation is on the type of the assignable, for example the variable type. + * @type_parameter: The annotation is on the reftype constraint + * @callable: The annotation is on the return type + * @array_type: The annotation is on the element type + */ +type_annotation(int id: @has_type_annotation ref, int annotation: int ref); + +nullability(unique int nullability: @nullability, int kind: int ref); + +case @nullability.kind of + 0 = @oblivious +| 1 = @not_annotated +| 2 = @annotated +; + +#keyset[parent, index] +nullability_parent(int nullability: @nullability ref, int index: int ref, int parent: @nullability ref) + +type_nullability(int id: @has_type_annotation ref, int nullability: @nullability ref); + +/** + * The nullable flow state of an expression, as determined by Roslyn. + * 0 = none (default, not populated) + * 1 = not null + * 2 = maybe null + */ +expr_flowstate(unique int id: @expr ref, int state: int ref); + +/** GENERICS **/ + +@generic = @type | @method | @local_function; + +type_parameters( + unique int id: @type_parameter ref, + int index: int ref, + int generic_id: @generic ref, + int variance: int ref /* none = 0, out = 1, in = 2 */); + +#keyset[constructed_id, index] +type_arguments( + int id: @type_or_ref ref, + int index: int ref, + int constructed_id: @generic_or_ref ref); + +@generic_or_ref = @generic | @typeref; + +constructed_generic( + unique int constructed: @generic ref, + int generic: @generic_or_ref ref); + +type_parameter_constraints( + unique int id: @type_parameter_constraints, + int param_id: @type_parameter ref); + +type_parameter_constraints_location( + int id: @type_parameter_constraints ref, + int loc: @location ref); + +general_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int kind: int ref /* class = 1, struct = 2, new = 3 */); + +specific_type_parameter_constraints( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref); + +specific_type_parameter_nullability( + int id: @type_parameter_constraints ref, + int base_id: @type_or_ref ref, + int nullability: @nullability ref); + +/** FUNCTION POINTERS */ + +function_pointer_calling_conventions( + int id: @function_pointer_type ref, + int kind: int ref); + +#keyset[id, index] +has_unmanaged_calling_conventions( + int id: @function_pointer_type ref, + int index: int ref, + int conv_id: @type_or_ref ref); + +/** MODIFIERS */ + +@modifiable = @modifiable_direct | @event_accessor; + +@modifiable_direct = @member | @accessor | @local_function | @anonymous_function_expr; + +modifiers( + unique int id: @modifier, + string name: string ref); + +has_modifiers( + int id: @modifiable_direct ref, + int mod_id: @modifier ref); + +compiler_generated(unique int id: @modifiable_direct ref); + +/** MEMBERS **/ + +@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type; + +@named_exprorstmt = @goto_stmt | @labeled_stmt | @expr; + +@virtualizable = @method | @property | @indexer | @event; + +exprorstmt_name( + unique int parent_id: @named_exprorstmt ref, + string name: string ref); + +nested_types( + unique int id: @type ref, + int declaring_type_id: @type ref, + int unbound_id: @type ref); + +properties( + unique int id: @property, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @property ref); + +property_location( + int id: @property ref, + int loc: @location ref); + +indexers( + unique int id: @indexer, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @indexer ref); + +indexer_location( + int id: @indexer ref, + int loc: @location ref); + +accessors( + unique int id: @accessor, + int kind: int ref, + string name: string ref, + int declaring_member_id: @member ref, + int unbound_id: @accessor ref); + +case @accessor.kind of + 1 = @getter +| 2 = @setter + ; + +init_only_accessors( + unique int id: @accessor ref); + +accessor_location( + int id: @accessor ref, + int loc: @location ref); + +events( + unique int id: @event, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @event ref); + +event_location( + int id: @event ref, + int loc: @location ref); + +event_accessors( + unique int id: @event_accessor, + int kind: int ref, + string name: string ref, + int declaring_event_id: @event ref, + int unbound_id: @event_accessor ref); + +case @event_accessor.kind of + 1 = @add_event_accessor +| 2 = @remove_event_accessor + ; + +event_accessor_location( + int id: @event_accessor ref, + int loc: @location ref); + +operators( + unique int id: @operator, + string name: string ref, + string symbol: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @operator ref); + +operator_location( + int id: @operator ref, + int loc: @location ref); + +constant_value( + int id: @variable ref, + string value: string ref); + +/** CALLABLES **/ + +@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function; + +@callable_accessor = @accessor | @event_accessor; + +methods( + unique int id: @method, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @method ref); + +method_location( + int id: @method ref, + int loc: @location ref); + +constructors( + unique int id: @constructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @constructor ref); + +constructor_location( + int id: @constructor ref, + int loc: @location ref); + +destructors( + unique int id: @destructor, + string name: string ref, + int declaring_type_id: @type ref, + int unbound_id: @destructor ref); + +destructor_location( + int id: @destructor ref, + int loc: @location ref); + +overrides( + int id: @callable ref, + int base_id: @callable ref); + +explicitly_implements( + int id: @member ref, + int interface_id: @interface_or_ref ref); + +local_functions( + unique int id: @local_function, + string name: string ref, + int return_type: @type ref, + int unbound_id: @local_function ref); + +local_function_stmts( + unique int fn: @local_function_stmt ref, + int stmt: @local_function ref); + +/** VARIABLES **/ + +@variable = @local_scope_variable | @field; + +@local_scope_variable = @local_variable | @parameter; + +fields( + unique int id: @field, + int kind: int ref, + string name: string ref, + int declaring_type_id: @type ref, + int type_id: @type_or_ref ref, + int unbound_id: @field ref); + +case @field.kind of + 1 = @addressable_field +| 2 = @constant + ; + +field_location( + int id: @field ref, + int loc: @location ref); + +localvars( + unique int id: @local_variable, + int kind: int ref, + string name: string ref, + int implicitly_typed: int ref /* 0 = no, 1 = yes */, + int type_id: @type_or_ref ref, + int parent_id: @local_var_decl_expr ref); + +case @local_variable.kind of + 1 = @addressable_local_variable +| 2 = @local_constant +| 3 = @local_variable_ref + ; + +localvar_location( + unique int id: @local_variable ref, + int loc: @location ref); + +@parameterizable = @callable | @delegate_type | @indexer | @function_pointer_type; + +#keyset[name, parent_id] +#keyset[index, parent_id] +params( + unique int id: @parameter, + string name: string ref, + int type_id: @type_or_ref ref, + int index: int ref, + int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */ + int parent_id: @parameterizable ref, + int unbound_id: @parameter ref); + +param_location( + int id: @parameter ref, + int loc: @location ref); + +/** STATEMENTS **/ + +@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent; + +statements( + unique int id: @stmt, + int kind: int ref); + +#keyset[index, parent] +stmt_parent( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_stmt_parent = @callable; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +stmt_parent_top_level( + unique int stmt: @stmt ref, + int index: int ref, + int parent: @top_level_stmt_parent ref); + +case @stmt.kind of + 1 = @block_stmt +| 2 = @expr_stmt +| 3 = @if_stmt +| 4 = @switch_stmt +| 5 = @while_stmt +| 6 = @do_stmt +| 7 = @for_stmt +| 8 = @foreach_stmt +| 9 = @break_stmt +| 10 = @continue_stmt +| 11 = @goto_stmt +| 12 = @goto_case_stmt +| 13 = @goto_default_stmt +| 14 = @throw_stmt +| 15 = @return_stmt +| 16 = @yield_stmt +| 17 = @try_stmt +| 18 = @checked_stmt +| 19 = @unchecked_stmt +| 20 = @lock_stmt +| 21 = @using_block_stmt +| 22 = @var_decl_stmt +| 23 = @const_decl_stmt +| 24 = @empty_stmt +| 25 = @unsafe_stmt +| 26 = @fixed_stmt +| 27 = @label_stmt +| 28 = @catch +| 29 = @case_stmt +| 30 = @local_function_stmt +| 31 = @using_decl_stmt + ; + +@using_stmt = @using_block_stmt | @using_decl_stmt; + +@labeled_stmt = @label_stmt | @case; + +@decl_stmt = @var_decl_stmt | @const_decl_stmt | @using_decl_stmt; + +@cond_stmt = @if_stmt | @switch_stmt; + +@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt; + +@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt + | @yield_stmt; + +@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt; + + +stmt_location( + unique int id: @stmt ref, + int loc: @location ref); + +catch_type( + unique int catch_id: @catch ref, + int type_id: @type_or_ref ref, + int kind: int ref /* explicit = 1, implicit = 2 */); + +foreach_stmt_info( + unique int id: @foreach_stmt ref, + int kind: int ref /* non-async = 1, async = 2 */); + +@foreach_symbol = @method | @property | @type_or_ref; + +#keyset[id, kind] +foreach_stmt_desugar( + int id: @foreach_stmt ref, + int symbol: @foreach_symbol ref, + int kind: int ref /* GetEnumeratorMethod = 1, CurrentProperty = 2, MoveNextMethod = 3, DisposeMethod = 4, ElementType = 5 */); + +/** EXPRESSIONS **/ + +expressions( + unique int id: @expr, + int kind: int ref, + int type_id: @type_or_ref ref); + +#keyset[index, parent] +expr_parent( + unique int expr: @expr ref, + int index: int ref, + int parent: @control_flow_element ref); + +@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter | @directive_if | @directive_elif; + +@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent; + +// [index, parent] is not a keyset because the same parent may be compiled multiple times +expr_parent_top_level( + unique int expr: @expr ref, + int index: int ref, + int parent: @top_level_exprorstmt_parent ref); + +case @expr.kind of +/* literal */ + 1 = @bool_literal_expr +| 2 = @char_literal_expr +| 3 = @decimal_literal_expr +| 4 = @int_literal_expr +| 5 = @long_literal_expr +| 6 = @uint_literal_expr +| 7 = @ulong_literal_expr +| 8 = @float_literal_expr +| 9 = @double_literal_expr +| 10 = @string_literal_expr +| 11 = @null_literal_expr +/* primary & unary */ +| 12 = @this_access_expr +| 13 = @base_access_expr +| 14 = @local_variable_access_expr +| 15 = @parameter_access_expr +| 16 = @field_access_expr +| 17 = @property_access_expr +| 18 = @method_access_expr +| 19 = @event_access_expr +| 20 = @indexer_access_expr +| 21 = @array_access_expr +| 22 = @type_access_expr +| 23 = @typeof_expr +| 24 = @method_invocation_expr +| 25 = @delegate_invocation_expr +| 26 = @operator_invocation_expr +| 27 = @cast_expr +| 28 = @object_creation_expr +| 29 = @explicit_delegate_creation_expr +| 30 = @implicit_delegate_creation_expr +| 31 = @array_creation_expr +| 32 = @default_expr +| 33 = @plus_expr +| 34 = @minus_expr +| 35 = @bit_not_expr +| 36 = @log_not_expr +| 37 = @post_incr_expr +| 38 = @post_decr_expr +| 39 = @pre_incr_expr +| 40 = @pre_decr_expr +/* multiplicative */ +| 41 = @mul_expr +| 42 = @div_expr +| 43 = @rem_expr +/* additive */ +| 44 = @add_expr +| 45 = @sub_expr +/* shift */ +| 46 = @lshift_expr +| 47 = @rshift_expr +/* relational */ +| 48 = @lt_expr +| 49 = @gt_expr +| 50 = @le_expr +| 51 = @ge_expr +/* equality */ +| 52 = @eq_expr +| 53 = @ne_expr +/* logical */ +| 54 = @bit_and_expr +| 55 = @bit_xor_expr +| 56 = @bit_or_expr +| 57 = @log_and_expr +| 58 = @log_or_expr +/* type testing */ +| 59 = @is_expr +| 60 = @as_expr +/* null coalescing */ +| 61 = @null_coalescing_expr +/* conditional */ +| 62 = @conditional_expr +/* assignment */ +| 63 = @simple_assign_expr +| 64 = @assign_add_expr +| 65 = @assign_sub_expr +| 66 = @assign_mul_expr +| 67 = @assign_div_expr +| 68 = @assign_rem_expr +| 69 = @assign_and_expr +| 70 = @assign_xor_expr +| 71 = @assign_or_expr +| 72 = @assign_lshift_expr +| 73 = @assign_rshift_expr +/* more */ +| 74 = @object_init_expr +| 75 = @collection_init_expr +| 76 = @array_init_expr +| 77 = @checked_expr +| 78 = @unchecked_expr +| 79 = @constructor_init_expr +| 80 = @add_event_expr +| 81 = @remove_event_expr +| 82 = @par_expr +| 83 = @local_var_decl_expr +| 84 = @lambda_expr +| 85 = @anonymous_method_expr +| 86 = @namespace_expr +/* dynamic */ +| 92 = @dynamic_element_access_expr +| 93 = @dynamic_member_access_expr +/* unsafe */ +| 100 = @pointer_indirection_expr +| 101 = @address_of_expr +| 102 = @sizeof_expr +/* async */ +| 103 = @await_expr +/* C# 6.0 */ +| 104 = @nameof_expr +| 105 = @interpolated_string_expr +| 106 = @unknown_expr +/* C# 7.0 */ +| 107 = @throw_expr +| 108 = @tuple_expr +| 109 = @local_function_invocation_expr +| 110 = @ref_expr +| 111 = @discard_expr +/* C# 8.0 */ +| 112 = @range_expr +| 113 = @index_expr +| 114 = @switch_expr +| 115 = @recursive_pattern_expr +| 116 = @property_pattern_expr +| 117 = @positional_pattern_expr +| 118 = @switch_case_expr +| 119 = @assign_coalesce_expr +| 120 = @suppress_nullable_warning_expr +| 121 = @namespace_access_expr +/* C# 9.0 */ +| 122 = @lt_pattern_expr +| 123 = @gt_pattern_expr +| 124 = @le_pattern_expr +| 125 = @ge_pattern_expr +| 126 = @not_pattern_expr +| 127 = @and_pattern_expr +| 128 = @or_pattern_expr +| 129 = @function_pointer_invocation_expr +| 130 = @with_expr +/* Preprocessor */ +| 999 = @define_symbol_expr +; + +@switch = @switch_stmt | @switch_expr; +@case = @case_stmt | @switch_case_expr; +@pattern_match = @case | @is_expr; +@unary_pattern_expr = @not_pattern_expr; +@relational_pattern_expr = @gt_pattern_expr | @lt_pattern_expr | @ge_pattern_expr | @le_pattern_expr; +@binary_pattern_expr = @and_pattern_expr | @or_pattern_expr; + +@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr; +@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr; +@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr + | @string_literal_expr | @null_literal_expr; + +@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr; +@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr | @assign_coalesce_expr; +@assign_event_expr = @add_event_expr | @remove_event_expr; + +@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr + | @assign_rem_expr +@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr + | @assign_lshift_expr | @assign_rshift_expr; + +@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr + | @method_access_expr | @type_access_expr | @dynamic_member_access_expr; +@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr | @namespace_access_expr; +@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr; + +@local_variable_access = @local_variable_access_expr | @local_var_decl_expr; +@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access; +@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr; + +@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr + | @event_access_expr | @dynamic_member_access_expr; + +@objectorcollection_init_expr = @object_init_expr | @collection_init_expr; + +@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr; + +@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr; +@incr_op_expr = @pre_incr_expr | @post_incr_expr; +@decr_op_expr = @pre_decr_expr | @post_decr_expr; +@mut_op_expr = @incr_op_expr | @decr_op_expr; +@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr; +@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr; + +@ternary_log_op_expr = @conditional_expr; +@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr; +@un_log_op_expr = @log_not_expr; +@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr; + +@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr + | @rshift_expr; +@un_bit_op_expr = @bit_not_expr; +@bit_expr = @un_bit_op_expr | @bin_bit_op_expr; + +@equality_op_expr = @eq_expr | @ne_expr; +@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr; +@comp_expr = @equality_op_expr | @rel_op_expr; + +@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op; + +@ternary_op = @ternary_log_op_expr; +@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr; +@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr + | @pointer_indirection_expr | @address_of_expr; + +@anonymous_function_expr = @lambda_expr | @anonymous_method_expr; + +@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr + | @delegate_invocation_expr | @object_creation_expr | @call_access_expr + | @local_function_invocation_expr | @function_pointer_invocation_expr; + +@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr; + +@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr + | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr; + +@throw_element = @throw_expr | @throw_stmt; + +@implicitly_typeable_object_creation_expr = @object_creation_expr | @explicit_delegate_creation_expr; + +implicitly_typed_array_creation( + unique int id: @array_creation_expr ref); + +explicitly_sized_array_creation( + unique int id: @array_creation_expr ref); + +stackalloc_array_creation( + unique int id: @array_creation_expr ref); + +implicitly_typed_object_creation( + unique int id: @implicitly_typeable_object_creation_expr ref); + +mutator_invocation_mode( + unique int id: @operator_invocation_expr ref, + int mode: int ref /* prefix = 1, postfix = 2*/); + +expr_compiler_generated( + unique int id: @expr ref); + +expr_value( + unique int id: @expr ref, + string value: string ref); + +expr_call( + unique int caller_id: @expr ref, + int target_id: @callable ref); + +expr_access( + unique int accesser_id: @access_expr ref, + int target_id: @accessible ref); + +@accessible = @method | @assignable | @local_function | @namespace; + +expr_location( + unique int id: @expr ref, + int loc: @location ref); + +dynamic_member_name( + unique int id: @late_bindable_expr ref, + string name: string ref); + +@qualifiable_expr = @member_access_expr + | @method_invocation_expr + | @element_access_expr; + +conditional_access( + unique int id: @qualifiable_expr ref); + +expr_argument( + unique int id: @expr ref, + int mode: int ref); + /* mode is the same as params: value = 0, ref = 1, out = 2 */ + +expr_argument_name( + unique int id: @expr ref, + string name: string ref); + +/** CONTROL/DATA FLOW **/ + +@control_flow_element = @stmt | @expr; + +/* XML Files */ + +xmlEncoding ( + unique int id: @file ref, + string encoding: string ref); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +/* Comments */ + +commentline( + unique int id: @commentline, + int kind: int ref, + string text: string ref, + string rawtext: string ref); + +case @commentline.kind of + 0 = @singlelinecomment +| 1 = @xmldoccomment +| 2 = @multilinecomment; + +commentline_location( + unique int id: @commentline ref, + int loc: @location ref); + +commentblock( + unique int id : @commentblock); + +commentblock_location( + unique int id: @commentblock ref, + int loc: @location ref); + +commentblock_binding( + int id: @commentblock ref, + int entity: @element ref, + int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */ + +commentblock_child( + int id: @commentblock ref, + int commentline: @commentline ref, + int index: int ref); + +/* ASP.NET */ + +case @asp_element.kind of + 0=@asp_close_tag +| 1=@asp_code +| 2=@asp_comment +| 3=@asp_data_binding +| 4=@asp_directive +| 5=@asp_open_tag +| 6=@asp_quoted_string +| 7=@asp_text +| 8=@asp_xml_directive; + +@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string; + +asp_elements( + unique int id: @asp_element, + int kind: int ref, + int loc: @location ref); + +asp_comment_server(unique int comment: @asp_comment ref); +asp_code_inline(unique int code: @asp_code ref); +asp_directive_attribute( + int directive: @asp_directive ref, + int index: int ref, + string name: string ref, + int value: @asp_quoted_string ref); +asp_directive_name( + unique int directive: @asp_directive ref, + string name: string ref); +asp_element_body( + unique int element: @asp_element ref, + string body: string ref); +asp_tag_attribute( + int tag: @asp_open_tag ref, + int index: int ref, + string name: string ref, + int attribute: @asp_attribute ref); +asp_tag_name( + unique int tag: @asp_open_tag ref, + string name: string ref); +asp_tag_isempty(int tag: @asp_open_tag ref); + +/* Common Intermediate Language - CIL */ + +case @cil_instruction.opcode of + 0 = @cil_nop +| 1 = @cil_break +| 2 = @cil_ldarg_0 +| 3 = @cil_ldarg_1 +| 4 = @cil_ldarg_2 +| 5 = @cil_ldarg_3 +| 6 = @cil_ldloc_0 +| 7 = @cil_ldloc_1 +| 8 = @cil_ldloc_2 +| 9 = @cil_ldloc_3 +| 10 = @cil_stloc_0 +| 11 = @cil_stloc_1 +| 12 = @cil_stloc_2 +| 13 = @cil_stloc_3 +| 14 = @cil_ldarg_s +| 15 = @cil_ldarga_s +| 16 = @cil_starg_s +| 17 = @cil_ldloc_s +| 18 = @cil_ldloca_s +| 19 = @cil_stloc_s +| 20 = @cil_ldnull +| 21 = @cil_ldc_i4_m1 +| 22 = @cil_ldc_i4_0 +| 23 = @cil_ldc_i4_1 +| 24 = @cil_ldc_i4_2 +| 25 = @cil_ldc_i4_3 +| 26 = @cil_ldc_i4_4 +| 27 = @cil_ldc_i4_5 +| 28 = @cil_ldc_i4_6 +| 29 = @cil_ldc_i4_7 +| 30 = @cil_ldc_i4_8 +| 31 = @cil_ldc_i4_s +| 32 = @cil_ldc_i4 +| 33 = @cil_ldc_i8 +| 34 = @cil_ldc_r4 +| 35 = @cil_ldc_r8 +| 37 = @cil_dup +| 38 = @cil_pop +| 39 = @cil_jmp +| 40 = @cil_call +| 41 = @cil_calli +| 42 = @cil_ret +| 43 = @cil_br_s +| 44 = @cil_brfalse_s +| 45 = @cil_brtrue_s +| 46 = @cil_beq_s +| 47 = @cil_bge_s +| 48 = @cil_bgt_s +| 49 = @cil_ble_s +| 50 = @cil_blt_s +| 51 = @cil_bne_un_s +| 52 = @cil_bge_un_s +| 53 = @cil_bgt_un_s +| 54 = @cil_ble_un_s +| 55 = @cil_blt_un_s +| 56 = @cil_br +| 57 = @cil_brfalse +| 58 = @cil_brtrue +| 59 = @cil_beq +| 60 = @cil_bge +| 61 = @cil_bgt +| 62 = @cil_ble +| 63 = @cil_blt +| 64 = @cil_bne_un +| 65 = @cil_bge_un +| 66 = @cil_bgt_un +| 67 = @cil_ble_un +| 68 = @cil_blt_un +| 69 = @cil_switch +| 70 = @cil_ldind_i1 +| 71 = @cil_ldind_u1 +| 72 = @cil_ldind_i2 +| 73 = @cil_ldind_u2 +| 74 = @cil_ldind_i4 +| 75 = @cil_ldind_u4 +| 76 = @cil_ldind_i8 +| 77 = @cil_ldind_i +| 78 = @cil_ldind_r4 +| 79 = @cil_ldind_r8 +| 80 = @cil_ldind_ref +| 81 = @cil_stind_ref +| 82 = @cil_stind_i1 +| 83 = @cil_stind_i2 +| 84 = @cil_stind_i4 +| 85 = @cil_stind_i8 +| 86 = @cil_stind_r4 +| 87 = @cil_stind_r8 +| 88 = @cil_add +| 89 = @cil_sub +| 90 = @cil_mul +| 91 = @cil_div +| 92 = @cil_div_un +| 93 = @cil_rem +| 94 = @cil_rem_un +| 95 = @cil_and +| 96 = @cil_or +| 97 = @cil_xor +| 98 = @cil_shl +| 99 = @cil_shr +| 100 = @cil_shr_un +| 101 = @cil_neg +| 102 = @cil_not +| 103 = @cil_conv_i1 +| 104 = @cil_conv_i2 +| 105 = @cil_conv_i4 +| 106 = @cil_conv_i8 +| 107 = @cil_conv_r4 +| 108 = @cil_conv_r8 +| 109 = @cil_conv_u4 +| 110 = @cil_conv_u8 +| 111 = @cil_callvirt +| 112 = @cil_cpobj +| 113 = @cil_ldobj +| 114 = @cil_ldstr +| 115 = @cil_newobj +| 116 = @cil_castclass +| 117 = @cil_isinst +| 118 = @cil_conv_r_un +| 121 = @cil_unbox +| 122 = @cil_throw +| 123 = @cil_ldfld +| 124 = @cil_ldflda +| 125 = @cil_stfld +| 126 = @cil_ldsfld +| 127 = @cil_ldsflda +| 128 = @cil_stsfld +| 129 = @cil_stobj +| 130 = @cil_conv_ovf_i1_un +| 131 = @cil_conv_ovf_i2_un +| 132 = @cil_conv_ovf_i4_un +| 133 = @cil_conv_ovf_i8_un +| 134 = @cil_conv_ovf_u1_un +| 135 = @cil_conv_ovf_u2_un +| 136 = @cil_conv_ovf_u4_un +| 137 = @cil_conv_ovf_u8_un +| 138 = @cil_conv_ovf_i_un +| 139 = @cil_conv_ovf_u_un +| 140 = @cil_box +| 141 = @cil_newarr +| 142 = @cil_ldlen +| 143 = @cil_ldelema +| 144 = @cil_ldelem_i1 +| 145 = @cil_ldelem_u1 +| 146 = @cil_ldelem_i2 +| 147 = @cil_ldelem_u2 +| 148 = @cil_ldelem_i4 +| 149 = @cil_ldelem_u4 +| 150 = @cil_ldelem_i8 +| 151 = @cil_ldelem_i +| 152 = @cil_ldelem_r4 +| 153 = @cil_ldelem_r8 +| 154 = @cil_ldelem_ref +| 155 = @cil_stelem_i +| 156 = @cil_stelem_i1 +| 157 = @cil_stelem_i2 +| 158 = @cil_stelem_i4 +| 159 = @cil_stelem_i8 +| 160 = @cil_stelem_r4 +| 161 = @cil_stelem_r8 +| 162 = @cil_stelem_ref +| 163 = @cil_ldelem +| 164 = @cil_stelem +| 165 = @cil_unbox_any +| 179 = @cil_conv_ovf_i1 +| 180 = @cil_conv_ovf_u1 +| 181 = @cil_conv_ovf_i2 +| 182 = @cil_conv_ovf_u2 +| 183 = @cil_conv_ovf_i4 +| 184 = @cil_conv_ovf_u4 +| 185 = @cil_conv_ovf_i8 +| 186 = @cil_conv_ovf_u8 +| 194 = @cil_refanyval +| 195 = @cil_ckinfinite +| 198 = @cil_mkrefany +| 208 = @cil_ldtoken +| 209 = @cil_conv_u2 +| 210 = @cil_conv_u1 +| 211 = @cil_conv_i +| 212 = @cil_conv_ovf_i +| 213 = @cil_conv_ovf_u +| 214 = @cil_add_ovf +| 215 = @cil_add_ovf_un +| 216 = @cil_mul_ovf +| 217 = @cil_mul_ovf_un +| 218 = @cil_sub_ovf +| 219 = @cil_sub_ovf_un +| 220 = @cil_endfinally +| 221 = @cil_leave +| 222 = @cil_leave_s +| 223 = @cil_stind_i +| 224 = @cil_conv_u +| 65024 = @cil_arglist +| 65025 = @cil_ceq +| 65026 = @cil_cgt +| 65027 = @cil_cgt_un +| 65028 = @cil_clt +| 65029 = @cil_clt_un +| 65030 = @cil_ldftn +| 65031 = @cil_ldvirtftn +| 65033 = @cil_ldarg +| 65034 = @cil_ldarga +| 65035 = @cil_starg +| 65036 = @cil_ldloc +| 65037 = @cil_ldloca +| 65038 = @cil_stloc +| 65039 = @cil_localloc +| 65041 = @cil_endfilter +| 65042 = @cil_unaligned +| 65043 = @cil_volatile +| 65044 = @cil_tail +| 65045 = @cil_initobj +| 65046 = @cil_constrained +| 65047 = @cil_cpblk +| 65048 = @cil_initblk +| 65050 = @cil_rethrow +| 65052 = @cil_sizeof +| 65053 = @cil_refanytype +| 65054 = @cil_readonly +; + +// CIL ignored instructions + +@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned; + +// CIL local/parameter/field access + +@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga; +@cil_starg_any = @cil_starg | @cil_starg_s; + +@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca; +@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc; + +@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda; +@cil_stfld_any = @cil_stfld | @cil_stsfld; + +@cil_local_access = @cil_stloc_any | @cil_ldloc_any; +@cil_arg_access = @cil_starg_any | @cil_ldarg_any; +@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any; +@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any; + +@cil_stack_access = @cil_local_access | @cil_arg_access; +@cil_field_access = @cil_ldfld_any | @cil_stfld_any; + +@cil_access = @cil_read_access | @cil_write_access; + +// CIL constant/literal instructions + +@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8; + +@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 | + @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4; + +@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8; + +@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr; + +// Control flow + +@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump; +@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s | + @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s | + @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt | + @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un; +@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch; +@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any; +@cil_leave_any = @cil_leave | @cil_leave_s; +@cil_jump = @cil_unconditional_jump | @cil_conditional_jump; + +// CIL call instructions + +@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj; + +// CIL expression instructions + +@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access | + @cil_newarr | @cil_ldtoken | @cil_sizeof | + @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup; + +@cil_unary_expr = + @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation| + @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any | + @cil_ldind | @cil_unbox; + +@cil_conversion_operation = + @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 | + @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 | + @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un | + @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un | + @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un | + @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un | + @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un | + @cil_conv_i | @cil_conv_u | @cil_conv_r_un; + +@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 | + @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4; + +@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 | + @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref; + +@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation; + +@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl; + +@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un | + @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un | + @cil_sub_ovf | @cil_sub_ovf_un; + +@cil_unary_bitwise_operation = @cil_not; + +@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation; + +@cil_unary_arithmetic_operation = @cil_neg; + +@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un; + +// Elements that retrieve an address of something +@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema; + +// CIL array instructions + +@cil_read_array = + @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i | + @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 | + @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4; + +@cil_write_array = @cil_stelem | @cil_stelem_ref | + @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 | + @cil_stelem_r4 | @cil_stelem_r8; + +@cil_throw_any = @cil_throw | @cil_rethrow; + +#keyset[impl, index] +cil_instruction( + unique int id: @cil_instruction, + int opcode: int ref, + int index: int ref, + int impl: @cil_method_implementation ref); + +cil_jump( + unique int instruction: @cil_jump ref, + int target: @cil_instruction ref); + +cil_access( + unique int instruction: @cil_instruction ref, + int target: @cil_accessible ref); + +cil_value( + unique int instruction: @cil_literal ref, + string value: string ref); + +#keyset[instruction, index] +cil_switch( + int instruction: @cil_switch ref, + int index: int ref, + int target: @cil_instruction ref); + +cil_instruction_location( + unique int id: @cil_instruction ref, + int loc: @location ref); + +cil_type_location( + int id: @cil_type ref, + int loc: @location ref); + +cil_method_location( + int id: @cil_method ref, + int loc: @location ref); + +@cil_namespace = @namespace; + +@cil_type_container = @cil_type | @cil_namespace | @cil_method; + +case @cil_type.kind of + 0 = @cil_valueorreftype +| 1 = @cil_typeparameter +| 2 = @cil_array_type +| 3 = @cil_pointer_type +| 4 = @cil_function_pointer_type +; + +cil_type( + unique int id: @cil_type, + string name: string ref, + int kind: int ref, + int parent: @cil_type_container ref, + int sourceDecl: @cil_type ref); + +cil_pointer_type( + unique int id: @cil_pointer_type ref, + int pointee: @cil_type ref); + +cil_array_type( + unique int id: @cil_array_type ref, + int element_type: @cil_type ref, + int rank: int ref); + +cil_function_pointer_return_type( + unique int id: @cil_function_pointer_type ref, + int return_type: @cil_type ref); + +cil_method( + unique int id: @cil_method, + string name: string ref, + int parent: @cil_type ref, + int return_type: @cil_type ref); + +cil_method_source_declaration( + unique int method: @cil_method ref, + int source: @cil_method ref); + +cil_method_implementation( + unique int id: @cil_method_implementation, + int method: @cil_method ref, + int location: @assembly ref); + +cil_implements( + int id: @cil_method ref, + int decl: @cil_method ref); + +#keyset[parent, name] +cil_field( + unique int id: @cil_field, + int parent: @cil_type ref, + string name: string ref, + int field_type: @cil_type ref); + +@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace; +@cil_named_element = @cil_declaration | @cil_namespace; +@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member; +@cil_accessible = @cil_declaration; +@cil_variable = @cil_field | @cil_stack_variable; +@cil_stack_variable = @cil_local_variable | @cil_parameter; +@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event; +@cil_custom_modifier_receiver = @cil_method | @cil_property | @cil_parameter | @cil_field | @cil_function_pointer_type; +@cil_parameterizable = @cil_method | @cil_function_pointer_type; +@cil_has_type_annotation = @cil_stack_variable | @cil_property | @cil_method | @cil_function_pointer_type; + +#keyset[parameterizable, index] +cil_parameter( + unique int id: @cil_parameter, + int parameterizable: @cil_parameterizable ref, + int index: int ref, + int param_type: @cil_type ref); + +cil_parameter_in(unique int id: @cil_parameter ref); +cil_parameter_out(unique int id: @cil_parameter ref); + +cil_setter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +#keyset[id, modifier] +cil_custom_modifiers( + int id: @cil_custom_modifier_receiver ref, + int modifier: @cil_type ref, + int kind: int ref); // modreq: 1, modopt: 0 + +cil_type_annotation( + int id: @cil_has_type_annotation ref, + int annotation: int ref); + +cil_getter(unique int prop: @cil_property ref, + int method: @cil_method ref); + +cil_adder(unique int event: @cil_event ref, + int method: @cil_method ref); + +cil_remover(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref); + +cil_property( + unique int id: @cil_property, + int parent: @cil_type ref, + string name: string ref, + int property_type: @cil_type ref); + +#keyset[parent, name] +cil_event(unique int id: @cil_event, + int parent: @cil_type ref, + string name: string ref, + int event_type: @cil_type ref); + +#keyset[impl, index] +cil_local_variable( + unique int id: @cil_local_variable, + int impl: @cil_method_implementation ref, + int index: int ref, + int var_type: @cil_type ref); + +cil_function_pointer_calling_conventions( + int id: @cil_function_pointer_type ref, + int kind: int ref); + +// CIL handlers (exception handlers etc). + +case @cil_handler.kind of + 0 = @cil_catch_handler +| 1 = @cil_filter_handler +| 2 = @cil_finally_handler +| 4 = @cil_fault_handler +; + +#keyset[impl, index] +cil_handler( + unique int id: @cil_handler, + int impl: @cil_method_implementation ref, + int index: int ref, + int kind: int ref, + int try_start: @cil_instruction ref, + int try_end: @cil_instruction ref, + int handler_start: @cil_instruction ref); + +cil_handler_filter( + unique int id: @cil_handler ref, + int filter_start: @cil_instruction ref); + +cil_handler_type( + unique int id: @cil_handler ref, + int catch_type: @cil_type ref); + +@cil_controlflow_node = @cil_entry_point | @cil_instruction; + +@cil_entry_point = @cil_method_implementation | @cil_handler; + +@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method; + +cil_method_stack_size( + unique int method: @cil_method_implementation ref, + int size: int ref); + +// CIL modifiers + +cil_public(int id: @cil_member ref); +cil_private(int id: @cil_member ref); +cil_protected(int id: @cil_member ref); +cil_internal(int id: @cil_member ref); +cil_static(int id: @cil_member ref); +cil_sealed(int id: @cil_member ref); +cil_virtual(int id: @cil_method ref); +cil_abstract(int id: @cil_member ref); +cil_class(int id: @cil_type ref); +cil_interface(int id: @cil_type ref); +cil_security(int id: @cil_member ref); +cil_requiresecobject(int id: @cil_method ref); +cil_specialname(int id: @cil_method ref); +cil_newslot(int id: @cil_method ref); + +cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref); +cil_base_interface(int id: @cil_type ref, int base: @cil_type ref); +cil_enum_underlying_type(unique int id: @cil_type ref, int underlying: @cil_type ref); + +#keyset[unbound, index] +cil_type_parameter( + int unbound: @cil_member ref, + int index: int ref, + int param: @cil_typeparameter ref); + +#keyset[bound, index] +cil_type_argument( + int bound: @cil_member ref, + int index: int ref, + int t: @cil_type ref); + +// CIL type parameter constraints + +cil_typeparam_covariant(int tp: @cil_typeparameter ref); +cil_typeparam_contravariant(int tp: @cil_typeparameter ref); +cil_typeparam_class(int tp: @cil_typeparameter ref); +cil_typeparam_struct(int tp: @cil_typeparameter ref); +cil_typeparam_new(int tp: @cil_typeparameter ref); +cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref); + +// CIL attributes + +cil_attribute( + unique int attributeid: @cil_attribute, + int element: @cil_declaration ref, + int constructor: @cil_method ref); + +#keyset[attribute_id, param] +cil_attribute_named_argument( + int attribute_id: @cil_attribute ref, + string param: string ref, + string value: string ref); + +#keyset[attribute_id, index] +cil_attribute_positional_argument( + int attribute_id: @cil_attribute ref, + int index: int ref, + string value: string ref); + + +// Common .Net data model covering both C# and CIL + +// Common elements +@dotnet_element = @element | @cil_element; +@dotnet_named_element = @named_element | @cil_named_element; +@dotnet_callable = @callable | @cil_method; +@dotnet_variable = @variable | @cil_variable; +@dotnet_field = @field | @cil_field; +@dotnet_parameter = @parameter | @cil_parameter; +@dotnet_declaration = @declaration | @cil_declaration; +@dotnet_member = @member | @cil_member; +@dotnet_event = @event | @cil_event; +@dotnet_property = @property | @cil_property | @indexer; +@dotnet_parameterizable = @parameterizable | @cil_parameterizable; + +// Common types +@dotnet_type = @type | @cil_type; +@dotnet_call = @call | @cil_call_any; +@dotnet_throw = @throw_element | @cil_throw_any; +@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type; +@dotnet_typeparameter = @type_parameter | @cil_typeparameter; +@dotnet_array_type = @array_type | @cil_array_type; +@dotnet_pointer_type = @pointer_type | @cil_pointer_type; +@dotnet_type_parameter = @type_parameter | @cil_typeparameter; +@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable; + +// Attributes +@dotnet_attribute = @attribute | @cil_attribute; + +// Expressions +@dotnet_expr = @expr | @cil_expr; + +// Literals +@dotnet_literal = @literal_expr | @cil_literal; +@dotnet_string_literal = @string_literal_expr | @cil_ldstr; +@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i; +@dotnet_float_literal = @float_literal_expr | @cil_ldc_r; +@dotnet_null_literal = @null_literal_expr | @cil_ldnull; + +@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property | + @callable | @value_or_ref_type | @void_type; + +#keyset[entity, location] +metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref) diff --git a/csharp/upgrades/9258e9b38d85f92cee9559f2ed21e241f0c7a29e/upgrade.properties b/csharp/upgrades/9258e9b38d85f92cee9559f2ed21e241f0c7a29e/upgrade.properties new file mode 100644 index 00000000000..64b44aecc3c --- /dev/null +++ b/csharp/upgrades/9258e9b38d85f92cee9559f2ed21e241f0c7a29e/upgrade.properties @@ -0,0 +1,2 @@ +description: Removed unique base class constraint +compatibility: backwards diff --git a/docs/codeql/codeql-cli/analyzing-databases-with-the-codeql-cli.rst b/docs/codeql/codeql-cli/analyzing-databases-with-the-codeql-cli.rst index 60126d12d0a..6d30fee7f65 100644 --- a/docs/codeql/codeql-cli/analyzing-databases-with-the-codeql-cli.rst +++ b/docs/codeql/codeql-cli/analyzing-databases-with-the-codeql-cli.rst @@ -101,39 +101,44 @@ You can also run your own custom queries with the ``database analyze`` command. For more information about preparing your queries to use with the CodeQL CLI, see ":doc:`Using custom queries with the CodeQL CLI `." - -Running LGTM.com query suites -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Running GitHub code scanning suites +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The CodeQL repository also includes query suites, which can be run over your code as part of a broader code review. CodeQL query suites are ``.qls`` files that use directives to select queries to run based on certain metadata properties. -The query suites included in the CodeQL repository select the same set of -queries that are run by default on `LGTM.com `__. The queries -are selected to highlight the most relevant and useful results for each -language. - -The language-specific LGTM query suites are located at the following paths in +The CodeQL repository includes query suites that are used by the CodeQL action on +`GitHub.com `__. The query suites are located at the following paths in the CodeQL repository:: - ql//ql/src/codeql-suites/-lgtm.qls + ql//ql/src/codeql-suites/-code-scanning.qls and at the following path in the CodeQL for Go repository:: - ql/src/codeql-suites/go-lgtm.qls + ql/src/codeql-suites/go-code-scanning.qls These locations are specified in the metadata included in the standard QL packs. -This means that CodeQL knows where to find the suite files automatically, and +This means that the CodeQL CLI knows where to find the suite files automatically, and you don't have to specify the full path on the command line when running an analysis. For more information, see ":ref:`About QL packs `." -For example, to run the LGTM.com query suite on a C++ codebase (generating -results in the latest SARIF format), you would run:: +.. pull-quote:: + + Important + + If you plan to upload the results to GitHub, you must generate SARIF results. + For more information, see `Analyzing a CodeQL database `__ in the GitHub documentation. + +For example, to run the code scanning query suite on a C++ codebase and generate +results in the v2.1 SARIF format supported by all versions of GitHub, you would run:: + + codeql database analyze cpp-code-scanning.qls --format=sarifv2.1.0 --output=cpp-analysis/cpp-results.sarif + +The repository also includes the query suites used by `LGTM.com `__. +These are stored alongside the code scanning suites with names of the form: ``-lgtm.qls``. - codeql database analyze cpp-lgtm.qls --format=sarif-latest --output=cpp-analysis/cpp-results.sarif - For information about creating custom query suites, see ":doc:`Creating CodeQL query suites `." diff --git a/docs/codeql/codeql-cli/creating-codeql-databases.rst b/docs/codeql/codeql-cli/creating-codeql-databases.rst index 4f7212050df..637df58555b 100644 --- a/docs/codeql/codeql-cli/creating-codeql-databases.rst +++ b/docs/codeql/codeql-cli/creating-codeql-databases.rst @@ -165,13 +165,15 @@ build steps, you may need to explicitly define each step in the command line. .. pull-quote:: Creating databases for Go - For Go, you should always use the CodeQL autobuilder. Install the Go - toolchain (version 1.11 or later) and, if there are dependencies, the - appropriate dependency manager (such as `dep + For Go, install the Go toolchain (version 1.11 or later) and, if there + are dependencies, the appropriate dependency manager (such as `dep `__). - Do not specify any build commands, as you will override the autobuilder - invocation, which will create an empty database. + The Go autobuilder attempts to automatically detect code written in Go in a repository, + and only runs build scripts in an attempt to fetch dependencies. To force + CodeQL to limit extraction to the files compiled by your build script, set the environment variable + `CODEQL_EXTRACTOR_GO_BUILD_TRACING=on` or use the ``--command`` option to specify a + build command. Specifying build commands ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -200,6 +202,14 @@ commands that you can specify for compiled languages. codeql database create csharp-database --language=csharp --command='dotnet build /p:UseSharedCompilation=false /t:rebuild' +- Go project built using the ``COEQL_EXTRACTOR_GO_BUILD_TRACING=on`` environment variable:: + + CODEQL_EXTRACTOR_GO_BUILD_TRACING=on codeql database create go-database --language=go + +- Go project built using a custom build script:: + + codeql database create go-database --language=go --command='./scripts/build.sh' + - Java project built using Gradle:: codeql database create java-database --language=java --command='gradle clean test' diff --git a/docs/codeql/codeql-language-guides/data-flow-cheat-sheet-for-javascript.rst b/docs/codeql/codeql-language-guides/data-flow-cheat-sheet-for-javascript.rst index 1b2e5ca450b..7d9ac42c3d8 100644 --- a/docs/codeql/codeql-language-guides/data-flow-cheat-sheet-for-javascript.rst +++ b/docs/codeql/codeql-language-guides/data-flow-cheat-sheet-for-javascript.rst @@ -59,6 +59,7 @@ Classes and member predicates in the ``DataFlow::`` module: - `getStringValue `__ -- value of this node if it's is a string constant - `mayHaveBooleanValue `__ -- check if the value is ``true`` or ``false`` - `SourceNode `__ extends `Node `__ -- function call, parameter, object creation, or reference to a property or global variable + - `getALocalUse `__ -- find nodes whose value came from this node - `getACall `__ -- find calls with this as the callee - `getAnInstantiation `__ -- find ``new``-calls with this as the callee - `getAnInvocation `__ -- find calls or ``new``-calls with this as the callee @@ -130,9 +131,25 @@ System and Network - `FileSystemWriteAccess `__ -- writing to the contents of a file - `PersistentReadAccess `__ -- reading from persistent storage, like cookies - `PersistentWriteAccess `__ -- writing to persistent storage -- `RemoteFlowSource `__ -- source of untrusted user input - `SystemCommandExecution `__ -- execution of a system command +.. _data-flow-cheat-sheet-for-javascript--untrusted-data: + +Untrusted data +-------------- + +- `RemoteFlowSource `__ -- source of untrusted user input + - `isUserControlledObject `__ -- is the input deserialized to a JSON-like object? (as opposed to just being a string) +- `ClientSideRemoteFlowSource `__ extends `RemoteFlowSource `__ -- input specific to the browser environment + - `getKind `__ -- is this derived from the ``path``, ``fragment``, ``query``, ``url``, or ``name``? +- HTTP::`RequestInputAccess `__ extends `RemoteFlowSource `__ -- input from an incoming HTTP request + - `getKind `__ -- is this derived from a ``parameter``, ``header``, ``body``, ``url``, or ``cookie``? +- HTTP::`RequestHeaderAccess `__ extends `RequestInputAccess `__ -- access to a specific header + - `getAHeaderName `__ -- the name of a header being accessed + +Note: some `RemoteFlowSource `__ instances, such as input from a web socket, +belong to none of the specific subcategories above. + Files ----- @@ -164,6 +181,19 @@ String matching - x.\ `regexpMatch `__\ ("(?i).*escape.*") -- holds if x contains "escape" (case insensitive) +Access paths +------------ + +When multiple property accesses are chained together they form what's called an "access path". + +To identify nodes based on access paths, use the following predicates in `AccessPath `__ module: + +- AccessPath::`getAReferenceTo `__ -- find nodes that refer to the given access path +- AccessPath::`getAnAssignmentTo `__ -- finds nodes that are assigned to the given access path +- AccessPath::`getAnAliasedSourceNode `__ -- finds nodes that refer to the same access path + +``getAReferenceTo`` and ``getAnAssignmentTo`` have a 1-argument version for global access paths, and a 2-argument version for access paths starting at a given node. + Type tracking ------------- diff --git a/docs/codeql/codeql-language-guides/specifying-additional-remote-flow-sources-for-javascript.rst b/docs/codeql/codeql-language-guides/specifying-additional-remote-flow-sources-for-javascript.rst index 082a7e3fcb7..cc3fe5e9469 100644 --- a/docs/codeql/codeql-language-guides/specifying-additional-remote-flow-sources-for-javascript.rst +++ b/docs/codeql/codeql-language-guides/specifying-additional-remote-flow-sources-for-javascript.rst @@ -11,7 +11,7 @@ You can model potential sources of untrusted user input in your code without mak Specifying remote flow sources in external files is currently in beta and subject to change. -As mentioned in the :doc:`Data flow cheat sheet for JavaScript `, the CodeQL libraries for JavaScript +As mentioned in the :ref:`Data flow cheat sheet for JavaScript `, the CodeQL libraries for JavaScript provide a class `RemoteFlowSource `__ to represent sources of untrusted user input, sometimes also referred to as remote flow sources. diff --git a/docs/codeql/ql-language-reference/expressions.rst b/docs/codeql/ql-language-reference/expressions.rst index a9facb0dec1..f8869e427a0 100644 --- a/docs/codeql/ql-language-reference/expressions.rst +++ b/docs/codeql/ql-language-reference/expressions.rst @@ -488,6 +488,101 @@ value for each value generated by the ````: value generated by the ````. Here, the aggregation function is applied to each of the resulting combinations. +Example of monotonic aggregates +------------------------------- + +Consider this query: + +.. code-block:: ql + + string getPerson() { result = "Alice" or + result = "Bob" or + result = "Charles" or + result = "Diane" + } + string getFruit(string p) { p = "Alice" and result = "Orange" or + p = "Alice" and result = "Apple" or + p = "Bob" and result = "Apple" or + p = "Charles" and result = "Apple" or + p = "Charles" and result = "Banana" + } + int getPrice(string f) { f = "Apple" and result = 100 or + f = "Orange" and result = 100 or + f = "Orange" and result = 1 + } + + predicate nonmono(string p, int cost) { + p = getPerson() and cost = sum(string f | f = getFruit(p) | getPrice(f)) + } + + language[monotonicAggregates] + predicate mono(string p, int cost) { + p = getPerson() and cost = sum(string f | f = getFruit(p) | getPrice(f)) + } + + from string variant, string person, int cost + where variant = "default" and nonmono(person, cost) or + variant = "monotonic" and mono(person, cost) + select variant, person, cost + order by variant, person + +The query produces these results: + ++-----------+---------+------+ +| variant | person | cost | ++-----------+---------+------+ +| default | Alice | 201 | +| default | Bob | 100 | +| default | Charles | 100 | +| default | Diane | 0 | +| monotonic | Alice | 101 | +| monotonic | Alice | 200 | +| monotonic | Bob | 100 | +| monotonic | Diane | 0 | ++-----------+---------+------+ + +The two variants of the aggregate semantics differ in what happens +when ``getPrice(f)`` has either multiple results or no results +for a given ``f``. + +In this query, oranges are available at two different prices, and the +default ``sum`` aggregate returns a single line where Alice buys an +orange at a price of 100, another orange at a price of 1, and an apple +at a price of 100, totalling 201. On the other hand, in the the +*monotonic* semantics for ``sum``, Alice always buys one orange and +one apple, and a line of output is produced for each *way* she can +complete her shopping list. + +If there had been two different prices for apples too, the monotonic +``sum`` would have produced *four* output lines for Alice. + +Charles wants to buy a banana, which is not for sale at all. In the +default case, the sum produced for Charles includes the cost of the +apple he *can* buy, but there's no line for Charles in the monontonic +``sum`` output, because there *is no way* for Charles to buy one apple +plus one banana. + +(Diane buys no fruit at all, and in both variants her total cost +is 0. The ``strictsum`` aggregate would have excluded her from the +results in both cases). + +In actual QL practice, it is quite rare to use monotonic aggregates +with the *goal* of having multiple output lines, as in the "Alice" +case of this example. The more significant point is the "Charles" +case: As long as there's no price for bananas, no output is produced +for him. This means that if we later do learn of a banana price, we +don't need to *remove* any output tuple already produced. The +importance of this is that the monotonic aggregate behavior works well +with a fixpoint-based semantics for recursion, so it will be meaningul +to let the ``getPrice`` predicate be mutually recursive with the count +aggregate itself. (On the other hand, ``getFruit`` still cannot be +allowed to be recursive, because adding another fruit to someone's +shopping list would invalidate the total costs we already knew for +them). + +This opportunity to use recursion is the main practical reason for +requesting monotonic semantics of aggregates. + Recursive monotonic aggregates ------------------------------ diff --git a/docs/codeql/support/reusables/frameworks.rst b/docs/codeql/support/reusables/frameworks.rst index afdf63bdb7b..be9949aad77 100644 --- a/docs/codeql/support/reusables/frameworks.rst +++ b/docs/codeql/support/reusables/frameworks.rst @@ -129,6 +129,7 @@ JavaScript and TypeScript built-in support mssql, Database mysql, Database node, Runtime environment + nest.js, Server postgres, Database ramda, Utility library react, HTML framework @@ -156,8 +157,11 @@ Python built-in support Tornado, Web framework PyYAML, Serialization dill, Serialization + simplejson, Serialization + ujson, Serialization fabric, Utility library invoke, Utility library + idna, Utility library mysql-connector-python, Database MySQLdb, Database psycopg2, Database diff --git a/java/change-notes/2021-05-03-guava-first-non-null.md b/java/change-notes/2021-05-03-guava-first-non-null.md new file mode 100644 index 00000000000..3cd307d9455 --- /dev/null +++ b/java/change-notes/2021-05-03-guava-first-non-null.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Increase coverage of the Guava framework by adding support for `com.google.common.base.MoreObjects#firstNonNull`. diff --git a/java/change-notes/2021-05-03-jackson-dataflow-deserialization.md b/java/change-notes/2021-05-03-jackson-dataflow-deserialization.md new file mode 100644 index 00000000000..475f8dbee4f --- /dev/null +++ b/java/change-notes/2021-05-03-jackson-dataflow-deserialization.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Increase coverage of dataflow through Jackson JSON deserialized objects. diff --git a/java/change-notes/2021-05-05-kryo-improvements.md b/java/change-notes/2021-05-05-kryo-improvements.md new file mode 100644 index 00000000000..dbacb10099b --- /dev/null +++ b/java/change-notes/2021-05-05-kryo-improvements.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* Add support for version 5 of the Kryo serialization/deserialization framework. +* Add support for detecting safe uses of Kryo utilizing `KryoPool.Builder`. [#4992](https://github.com/github/codeql/issues/4992) diff --git a/java/change-notes/2021-05-12-xxe-fp-fix.md b/java/change-notes/2021-05-12-xxe-fp-fix.md new file mode 100644 index 00000000000..dd42bc71256 --- /dev/null +++ b/java/change-notes/2021-05-12-xxe-fp-fix.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The query "Resolving XML external entity in user-controlled data" (`java/xxe`) has been improved to report fewer false positives when a Builder / Factory (e.g. an `XMLInputFactory`) is configured safely by using a boxed boolean as second argument to one or more of its configuration methods. diff --git a/java/change-notes/2021-05-14-close-resource-leaks-improvements.md b/java/change-notes/2021-05-14-close-resource-leaks-improvements.md new file mode 100644 index 00000000000..08a77840a1f --- /dev/null +++ b/java/change-notes/2021-05-14-close-resource-leaks-improvements.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The "Potential input resource leak" (`java/input-resource-leak`) and "Potential output resource leak" (`java/output-resource-leak`) queries no longer confuse `java.io` classes such as `Reader` with others that happen to share the same base name. Additionally the number of false positives has been reduced by recognizing `CharArrayReader` and `CharArrayWriter` as types that don't need to be closed. diff --git a/java/change-notes/2021-05-20-savedrequest-taintsources.md b/java/change-notes/2021-05-20-savedrequest-taintsources.md new file mode 100644 index 00000000000..d22cb000c64 --- /dev/null +++ b/java/change-notes/2021-05-20-savedrequest-taintsources.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* Invocations of methods from `org.springframework.security.web.savedrequest.SavedRequest` + have been added as sources of tainted data for all security queries. diff --git a/java/change-notes/2021-05-28-remove-senderror-xss-sink.md b/java/change-notes/2021-05-28-remove-senderror-xss-sink.md new file mode 100644 index 00000000000..5dd245bcb52 --- /dev/null +++ b/java/change-notes/2021-05-28-remove-senderror-xss-sink.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The query "Cross-site scripting" (`java/xss`) has been improved to report fewer false positives by removing the `javax.servlet.http.HttpServletResponse.sendError` sink since Servlet API implementations generally already escape the error message, preventing script injection. diff --git a/java/documentation/library-coverage/cwe-sink.csv b/java/documentation/library-coverage/cwe-sink.csv new file mode 100644 index 00000000000..796f237c313 --- /dev/null +++ b/java/documentation/library-coverage/cwe-sink.csv @@ -0,0 +1,8 @@ +CWE,Sink identifier,Label +CWE‑089,sql,SQL injection +CWE‑022,create-file,Path injection +CWE‑036,url-open-stream,Path traversal +CWE‑094,bean-validation,Code injection +CWE‑319,open-url,Cleartext transmission +CWE‑079,xss,Cross-site scripting +CWE‑090,ldap,LDAP injection \ No newline at end of file diff --git a/java/documentation/library-coverage/flow-model-coverage.csv b/java/documentation/library-coverage/flow-model-coverage.csv new file mode 100644 index 00000000000..464228bb022 --- /dev/null +++ b/java/documentation/library-coverage/flow-model-coverage.csv @@ -0,0 +1,42 @@ +package,sink,source,summary,sink:bean-validation,sink:create-file,sink:header-splitting,sink:information-leak,sink:ldap,sink:open-url,sink:set-hostname-verifier,sink:url-open-stream,sink:xpath,sink:xss,source:remote,summary:taint,summary:value +android.util,,16,,,,,,,,,,,,16,, +android.webkit,3,2,,,,,,,,,,,3,2,, +com.esotericsoftware.kryo.io,,,1,,,,,,,,,,,,1, +com.esotericsoftware.kryo5.io,,,1,,,,,,,,,,,,1, +com.fasterxml.jackson.databind,,,2,,,,,,,,,,,,2, +com.google.common.base,,,28,,,,,,,,,,,,22,6 +com.google.common.io,6,,69,,,,,,,,6,,,,68,1 +com.unboundid.ldap.sdk,17,,,,,,,17,,,,,,,, +java.beans,,,1,,,,,,,,,,,,1, +java.io,3,,20,,3,,,,,,,,,,20, +java.lang,,,1,,,,,,,,,,,,1, +java.net,2,3,4,,,,,,2,,,,,3,4, +java.nio,10,,2,,10,,,,,,,,,,2, +java.util,,,13,,,,,,,,,,,,13, +javax.naming.directory,1,,,,,,,1,,,,,,,, +javax.net.ssl,2,,,,,,,,,2,,,,,, +javax.servlet,4,21,2,,,3,1,,,,,,,21,2, +javax.validation,1,1,,1,,,,,,,,,,1,, +javax.ws.rs.core,1,,,,,1,,,,,,,,,, +javax.xml.transform.sax,,,4,,,,,,,,,,,,4, +javax.xml.transform.stream,,,2,,,,,,,,,,,,2, +javax.xml.xpath,3,,,,,,,,,,,3,,,, +org.apache.commons.codec,,,2,,,,,,,,,,,,2, +org.apache.commons.io,,,22,,,,,,,,,,,,22, +org.apache.commons.lang3,,,313,,,,,,,,,,,,299,14 +org.apache.commons.text,,,203,,,,,,,,,,,,203, +org.apache.directory.ldap.client.api,1,,,,,,,1,,,,,,,, +org.apache.hc.core5.function,,,1,,,,,,,,,,,,1, +org.apache.hc.core5.http,1,2,39,,,,,,,,,,1,2,39, +org.apache.hc.core5.net,,,2,,,,,,,,,,,,2, +org.apache.hc.core5.util,,,22,,,,,,,,,,,,18,4 +org.apache.http,2,3,66,,,,,,,,,,2,3,59,7 +org.dom4j,20,,,,,,,,,,,20,,,, +org.springframework.ldap.core,14,,,,,,,14,,,,,,,, +org.springframework.security.web.savedrequest,,6,,,,,,,,,,,,6,, +org.springframework.web.client,,3,,,,,,,,,,,,3,, +org.springframework.web.context.request,,8,,,,,,,,,,,,8,, +org.springframework.web.multipart,,12,,,,,,,,,,,,12,, +org.xml.sax,,,1,,,,,,,,,,,,1, +org.xmlpull.v1,,3,,,,,,,,,,,,3,, +play.mvc,,4,,,,,,,,,,,,4,, diff --git a/java/documentation/library-coverage/flow-model-coverage.rst b/java/documentation/library-coverage/flow-model-coverage.rst new file mode 100644 index 00000000000..b9b54aad158 --- /dev/null +++ b/java/documentation/library-coverage/flow-model-coverage.rst @@ -0,0 +1,19 @@ +Java framework & library support +================================ + +.. csv-table:: + :header-rows: 1 + :class: fullWidthTable + :widths: auto + + Framework / library,Package,Remote flow sources,Taint & value steps,Sinks (total),`CWE‑022` :sub:`Path injection`,`CWE‑036` :sub:`Path traversal`,`CWE‑079` :sub:`Cross-site scripting`,`CWE‑089` :sub:`SQL injection`,`CWE‑090` :sub:`LDAP injection`,`CWE‑094` :sub:`Code injection`,`CWE‑319` :sub:`Cleartext transmission` + Android,``android.*``,18,,3,,,3,,,, + Apache,``org.apache.*``,5,648,4,,,3,,1,, + `Apache Commons IO `_,``org.apache.commons.io``,,22,,,,,,,, + Google,``com.google.common.*``,,97,6,,6,,,,, + Java Standard Library,``java.*``,3,41,15,13,,,,,,2 + Java extensions,``javax.*``,22,8,12,,,,,1,1, + `Spring `_,``org.springframework.*``,29,,14,,,,,14,, + Others,"``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.databind``, ``com.unboundid.ldap.sdk``, ``org.dom4j``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``",7,5,37,,,,,17,, + Totals,,84,821,91,13,6,6,,33,1,2 + diff --git a/java/documentation/library-coverage/frameworks.csv b/java/documentation/library-coverage/frameworks.csv new file mode 100644 index 00000000000..0e39bc51836 --- /dev/null +++ b/java/documentation/library-coverage/frameworks.csv @@ -0,0 +1,8 @@ +Framework name,URL,Package prefix +Java Standard Library,,java.* +Google,,com.google.common.* +Apache,,org.apache.* +Apache Commons IO,https://commons.apache.org/proper/commons-io/,org.apache.commons.io +Android,,android.* +Spring,https://spring.io/,org.springframework.* +Java extensions,,javax.* \ No newline at end of file diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.qhelp b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.qhelp index b0ded8e53a1..e5574bfc179 100644 --- a/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.qhelp +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.qhelp @@ -14,7 +14,7 @@ but not closed may cause a resource leak.

    Ensure that the resource is always closed to avoid a resource leak. Note that, because of exceptions, it is safest to close a resource in a finally block. (However, this is unnecessary for -subclasses of StringReader and ByteArrayInputStream.) +subclasses of CharArrayReader, StringReader and ByteArrayInputStream.)

    For Java 7 or later, the recommended way to close resources that implement java.lang.AutoCloseable diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql index 9d62237bd71..e93c59b0879 100644 --- a/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql @@ -17,16 +17,16 @@ import CloseType predicate readerType(RefType t) { exists(RefType sup | sup = t.getASupertype*() | - sup.hasName("Reader") or - sup.hasName("InputStream") or + sup.hasQualifiedName("java.io", ["Reader", "InputStream"]) or sup.hasQualifiedName("java.util.zip", "ZipFile") ) } predicate safeReaderType(RefType t) { exists(RefType sup | sup = t.getASupertype*() | - sup.hasName("StringReader") or - sup.hasName("ByteArrayInputStream") or + sup.hasQualifiedName("java.io", ["CharArrayReader", "StringReader", "ByteArrayInputStream"]) + or + // Note: It is unclear which specific class this is supposed to match sup.hasName("StringInputStream") ) } diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.qhelp b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.qhelp index 0b348a3f9b8..84a50f914f7 100644 --- a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.qhelp +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.qhelp @@ -14,7 +14,7 @@ but not properly closed later may cause a resource leak.

    Ensure that the resource is always closed to avoid a resource leak. Note that, because of exceptions, it is safest to close a resource properly in a finally block. (However, this is unnecessary for -subclasses of StringWriter and ByteArrayOutputStream.)

    +subclasses of CharArrayWriter, StringWriter and ByteArrayOutputStream.)

    For Java 7 or later, the recommended way to close resources that implement java.lang.AutoCloseable is to declare them within a try-with-resources statement, so that they are closed implicitly.

    diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql index 113f3bd3267..a0714fe3a2f 100644 --- a/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql @@ -17,15 +17,13 @@ import CloseType predicate writerType(RefType t) { exists(RefType sup | sup = t.getASupertype*() | - sup.hasName("Writer") or - sup.hasName("OutputStream") + sup.hasQualifiedName("java.io", ["Writer", "OutputStream"]) ) } predicate safeWriterType(RefType t) { exists(RefType sup | sup = t.getASupertype*() | - sup.hasName("StringWriter") or - sup.hasName("ByteArrayOutputStream") + sup.hasQualifiedName("java.io", ["CharArrayWriter", "StringWriter", "ByteArrayOutputStream"]) ) } diff --git a/java/ql/src/Metrics/Summaries/LinesOfCode.ql b/java/ql/src/Metrics/Summaries/LinesOfCode.ql index d6db7c6ee6b..c622f8b08ba 100644 --- a/java/ql/src/Metrics/Summaries/LinesOfCode.ql +++ b/java/ql/src/Metrics/Summaries/LinesOfCode.ql @@ -6,6 +6,7 @@ * or comments. * @kind metric * @tags summary + * lines-of-code */ import java diff --git a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql index 3426d9f6f62..233e2694afa 100644 --- a/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql +++ b/java/ql/src/Security/CWE/CWE-209/StackTraceExposure.ql @@ -15,7 +15,7 @@ import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking -import semmle.code.java.security.XSS +import semmle.code.java.security.InformationLeak /** * One of the `printStackTrace()` overloads on `Throwable`. @@ -83,14 +83,14 @@ predicate stackTraceExpr(Expr exception, MethodAccess stackTraceString) { ) } -class StackTraceStringToXssSinkFlowConfig extends TaintTracking::Configuration { - StackTraceStringToXssSinkFlowConfig() { - this = "StackTraceExposure::StackTraceStringToXssSinkFlowConfig" +class StackTraceStringToHttpResponseSinkFlowConfig extends TaintTracking::Configuration { + StackTraceStringToHttpResponseSinkFlowConfig() { + this = "StackTraceExposure::StackTraceStringToHttpResponseSinkFlowConfig" } override predicate isSource(DataFlow::Node src) { stackTraceExpr(_, src.asExpr()) } - override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink } + override predicate isSink(DataFlow::Node sink) { sink instanceof InformationLeakSink } } /** @@ -105,8 +105,8 @@ predicate printsStackExternally(MethodAccess call, Expr stackTrace) { /** * A stringified stack trace flows to an external sink. */ -predicate stringifiedStackFlowsExternally(XssSink externalExpr, Expr stackTrace) { - exists(MethodAccess stackTraceString, StackTraceStringToXssSinkFlowConfig conf | +predicate stringifiedStackFlowsExternally(DataFlow::Node externalExpr, Expr stackTrace) { + exists(MethodAccess stackTraceString, StackTraceStringToHttpResponseSinkFlowConfig conf | stackTraceExpr(stackTrace, stackTraceString) and conf.hasFlow(DataFlow::exprNode(stackTraceString), externalExpr) ) @@ -123,21 +123,21 @@ class GetMessageFlowSource extends MethodAccess { } } -class GetMessageFlowSourceToXssSinkFlowConfig extends TaintTracking::Configuration { - GetMessageFlowSourceToXssSinkFlowConfig() { - this = "StackTraceExposure::GetMessageFlowSourceToXssSinkFlowConfig" +class GetMessageFlowSourceToHttpResponseSinkFlowConfig extends TaintTracking::Configuration { + GetMessageFlowSourceToHttpResponseSinkFlowConfig() { + this = "StackTraceExposure::GetMessageFlowSourceToHttpResponseSinkFlowConfig" } override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof GetMessageFlowSource } - override predicate isSink(DataFlow::Node sink) { sink instanceof XssSink } + override predicate isSink(DataFlow::Node sink) { sink instanceof InformationLeakSink } } /** * A call to `getMessage()` that then flows to a servlet response. */ -predicate getMessageFlowsExternally(XssSink externalExpr, GetMessageFlowSource getMessage) { - any(GetMessageFlowSourceToXssSinkFlowConfig conf) +predicate getMessageFlowsExternally(DataFlow::Node externalExpr, GetMessageFlowSource getMessage) { + any(GetMessageFlowSourceToHttpResponseSinkFlowConfig conf) .hasFlow(DataFlow::exprNode(getMessage), externalExpr) } diff --git a/java/ql/src/experimental/Security/CWE/CWE-078/ExecCommon.qll b/java/ql/src/experimental/Security/CWE/CWE-078/ExecCommon.qll new file mode 100644 index 00000000000..c0025043fce --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-078/ExecCommon.qll @@ -0,0 +1,32 @@ +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.security.ExternalProcess +import semmle.code.java.security.CommandArguments + +private class RemoteUserInputToArgumentToExecFlowConfig extends TaintTracking::Configuration { + RemoteUserInputToArgumentToExecFlowConfig() { + this = "ExecCommon::RemoteUserInputToArgumentToExecFlowConfig" + } + + override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof ArgumentToExec } + + override predicate isSanitizer(DataFlow::Node node) { + node.getType() instanceof PrimitiveType + or + node.getType() instanceof BoxedType + or + isSafeCommandArgument(node.asExpr()) + } +} + +/** + * Implementation of `ExecTainted.ql`. It is extracted to a QLL + * so that it can be excluded from `ExecUnescaped.ql` to avoid + * reporting overlapping results. + */ +predicate execTainted(DataFlow::PathNode source, DataFlow::PathNode sink, ArgumentToExec execArg) { + exists(RemoteUserInputToArgumentToExecFlowConfig conf | + conf.hasFlowPath(source, sink) and sink.getNode() = DataFlow::exprNode(execArg) + ) +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-078/ExecTainted.ql b/java/ql/src/experimental/Security/CWE/CWE-078/ExecTainted.ql new file mode 100644 index 00000000000..b73203ecbbc --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-078/ExecTainted.ql @@ -0,0 +1,24 @@ +/** + * @name Uncontrolled command line + * @description Using externally controlled strings in a command line is vulnerable to malicious + * changes in the strings. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/command-line-injection + * @tags security + * external/cwe/cwe-078 + * external/cwe/cwe-088 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.security.ExternalProcess +import ExecCommon +import JSchOSInjection +import DataFlow::PathGraph + +from DataFlow::PathNode source, DataFlow::PathNode sink, ArgumentToExec execArg +where execTainted(source, sink, execArg) +select execArg, source, sink, "$@ flows to here and is used in a command.", source.getNode(), + "User-provided value" diff --git a/java/ql/src/experimental/Security/CWE/CWE-078/JSchOSInjection.qll b/java/ql/src/experimental/Security/CWE/CWE-078/JSchOSInjection.qll new file mode 100644 index 00000000000..ec1f4d0adfa --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-078/JSchOSInjection.qll @@ -0,0 +1,20 @@ +/** + * Provides classes for JSch OS command injection detection + */ + +import java + +/** The class `com.jcraft.jsch.ChannelExec`. */ +private class JSchChannelExec extends RefType { + JSchChannelExec() { this.hasQualifiedName("com.jcraft.jsch", "ChannelExec") } +} + +/** A method to set an OS Command for the execution. */ +private class ChannelExecSetCommandMethod extends Method, ExecCallable { + ChannelExecSetCommandMethod() { + this.hasName("setCommand") and + this.getDeclaringType() instanceof JSchChannelExec + } + + override int getAnExecutedArgument() { result = 0 } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-078/JSchOSInjectionBad.java b/java/ql/src/experimental/Security/CWE/CWE-078/JSchOSInjectionBad.java new file mode 100644 index 00000000000..ab4c3fb1c06 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-078/JSchOSInjectionBad.java @@ -0,0 +1,17 @@ +public class JSchOSInjectionBad { + void jschOsExecution(HttpServletRequest request) { + String command = request.getParameter("command"); + + JSch jsch = new JSch(); + Session session = jsch.getSession("user", "sshHost", 22); + session.setPassword("password"); + session.connect(); + + Channel channel = session.openChannel("exec"); + // BAD - untrusted user data is used directly in a command + ((ChannelExec) channel).setCommand("ping " + command); + + channel.connect(); + } +} + diff --git a/java/ql/src/experimental/Security/CWE/CWE-078/JSchOSInjectionSanitized.java b/java/ql/src/experimental/Security/CWE/CWE-078/JSchOSInjectionSanitized.java new file mode 100644 index 00000000000..b47a2b82ed7 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-078/JSchOSInjectionSanitized.java @@ -0,0 +1,46 @@ +public class JSchOSInjectionSanitized { + void jschOsExecutionPing(HttpServletRequest request) { + String untrusted = request.getParameter("command"); + + //GOOD - Validate user the input. + if (!com.google.common.net.InetAddresses.isInetAddress(untrusted)) { + System.out.println("Invalid IP address"); + return; + } + + JSch jsch = new JSch(); + Session session = jsch.getSession("user", "host", 22); + session.setPassword("password"); + session.connect(); + + Channel channel = session.openChannel("exec"); + ((ChannelExec) channel).setCommand("ping " + untrusted); + + channel.connect(); + } + + void jschOsExecutionDig(HttpServletRequest request) { + String untrusted = request.getParameter("command"); + + //GOOD - check whether the user input doesn't contain dangerous shell characters. + String[] badChars = new String[] {"^", "~" ," " , "&", "|", ";", "$", ">", "<", "`", "\\", ",", "!", "{", "}", "(", ")", "@", "%", "#", "%0A", "%0a", "\n", "\r\n"}; + + for (String badChar : badChars) { + if (untrusted.contains(badChar)) { + System.out.println("Invalid host"); + return; + } + } + + JSch jsch = new JSch(); + Session session = jsch.getSession("user", "host", 22); + session.setPassword("password"); + session.connect(); + + Channel channel = session.openChannel("exec"); + ((ChannelExec) channel).setCommand("dig " + untrusted); + + channel.connect(); + } +} + diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.java b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.java new file mode 100644 index 00000000000..5c1796e1f60 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.java @@ -0,0 +1,49 @@ +import org.python.util.PythonInterpreter; + +public class JythonInjection extends HttpServlet { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new PythonInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + + // BAD: allow execution of arbitrary Python code + interpreter.exec(code); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + out.close(); + } + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + + try { + interpreter = new PythonInterpreter(); + // BAD: allow execution of arbitrary Python code + PyObject py = interpreter.eval(code); + + response.getWriter().print(py.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.qhelp new file mode 100644 index 00000000000..8916296f93b --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.qhelp @@ -0,0 +1,34 @@ + + + + +

    Python has been the most widely used programming language in recent years, and Jython + (formerly known as JPython) is a popular Java implementation of Python. It allows + embedded Python scripting inside Java applications and provides an interactive interpreter + that can be used to interact with Java packages or with running Java applications. If an + expression is built using attacker-controlled data and then evaluated, it may allow the + attacker to run arbitrary code.

    +
    + + +

    In general, including user input in Jython expression should be avoided. If user input + must be included in an expression, it should be then evaluated in a safe context that + doesn't allow arbitrary code invocation.

    +
    + + +

    The following code could execute arbitrary code in Jython Interpreter

    + +
    + + +
  • + Jython Organization: Jython and Java Integration +
  • +
  • + PortSwigger: Python code injection +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.ql new file mode 100644 index 00000000000..c6a7f583b14 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.ql @@ -0,0 +1,114 @@ +/** + * @name Injection in Jython + * @description Evaluation of a user-controlled malicious expression in Java Python + * interpreter may lead to remote code execution. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/jython-injection + * @tags security + * external/cwe/cwe-094 + * external/cwe/cwe-095 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.frameworks.spring.SpringController +import DataFlow::PathGraph + +/** The class `org.python.util.PythonInterpreter`. */ +class PythonInterpreter extends RefType { + PythonInterpreter() { this.hasQualifiedName("org.python.util", "PythonInterpreter") } +} + +/** A method that evaluates, compiles or executes a Jython expression. */ +class InterpretExprMethod extends Method { + InterpretExprMethod() { + this.getDeclaringType().getAnAncestor*() instanceof PythonInterpreter and + getName().matches(["exec%", "run%", "eval", "compile"]) + } +} + +/** The class `org.python.core.BytecodeLoader`. */ +class BytecodeLoader extends RefType { + BytecodeLoader() { this.hasQualifiedName("org.python.core", "BytecodeLoader") } +} + +/** Holds if a Jython expression if evaluated, compiled or executed. */ +predicate runsCode(MethodAccess ma, Expr sink) { + exists(Method m | m = ma.getMethod() | + m instanceof InterpretExprMethod and + sink = ma.getArgument(0) + ) +} + +/** A method that loads Java class data. */ +class LoadClassMethod extends Method { + LoadClassMethod() { + this.getDeclaringType().getAnAncestor*() instanceof BytecodeLoader and + hasName(["makeClass", "makeCode"]) + } +} + +/** + * Holds if `ma` is a call to a class-loading method, and `sink` is the byte array + * representing the class to be loaded. + */ +predicate loadsClass(MethodAccess ma, Expr sink) { + exists(Method m, int i | m = ma.getMethod() | + m instanceof LoadClassMethod and + m.getParameter(i).getType() instanceof Array and // makeClass(java.lang.String name, byte[] data, ...) + sink = ma.getArgument(i) + ) +} + +/** The class `org.python.core.Py`. */ +class Py extends RefType { + Py() { this.hasQualifiedName("org.python.core", "Py") } +} + +/** A method declared on class `Py` or one of its descendants that compiles Python code. */ +class PyCompileMethod extends Method { + PyCompileMethod() { + this.getDeclaringType().getAnAncestor*() instanceof Py and + getName().matches("compile%") + } +} + +/** Holds if source code is compiled with `PyCompileMethod`. */ +predicate compile(MethodAccess ma, Expr sink) { + exists(Method m | m = ma.getMethod() | + m instanceof PyCompileMethod and + sink = ma.getArgument(0) + ) +} + +/** An expression loaded by Jython. */ +class CodeInjectionSink extends DataFlow::ExprNode { + MethodAccess methodAccess; + + CodeInjectionSink() { + runsCode(methodAccess, this.getExpr()) or + loadsClass(methodAccess, this.getExpr()) or + compile(methodAccess, this.getExpr()) + } + + MethodAccess getMethodAccess() { result = methodAccess } +} + +/** + * A taint configuration for tracking flow from `RemoteFlowSource` to a Jython method call + * `CodeInjectionSink` that executes injected code. + */ +class CodeInjectionConfiguration extends TaintTracking::Configuration { + CodeInjectionConfiguration() { this = "CodeInjectionConfiguration" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof CodeInjectionSink } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, CodeInjectionConfiguration conf +where conf.hasFlowPath(source, sink) +select sink.getNode().(CodeInjectionSink).getMethodAccess(), source, sink, "Jython evaluate $@.", + source.getNode(), "user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/RhinoInjection.java b/java/ql/src/experimental/Security/CWE/CWE-094/RhinoInjection.java new file mode 100644 index 00000000000..15adfbe4524 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/RhinoInjection.java @@ -0,0 +1,40 @@ +import org.mozilla.javascript.ClassShutter; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +public class RhinoInjection extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + Context ctx = Context.enter(); + try { + { + // BAD: allow arbitrary Java and JavaScript code to be executed + Scriptable scope = ctx.initStandardObjects(); + } + + { + // GOOD: enable the safe mode + Scriptable scope = ctx.initSafeStandardObjects(); + } + + { + // GOOD: enforce a constraint on allowed classes + Scriptable scope = ctx.initStandardObjects(); + ctx.setClassShutter(new ClassShutter() { + public boolean visibleToScripts(String className) { + return className.startsWith("com.example."); + } + }); + } + + Object result = ctx.evaluateString(scope, code, "", 1, null); + response.getWriter().print(Context.toString(result)); + } catch(RhinoException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + Context.exit(); + } + } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.qhelp deleted file mode 100644 index 74159c562c5..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.qhelp +++ /dev/null @@ -1,26 +0,0 @@ - - - - -

    The ScriptEngine API has been available since the release of Java 6. -It allows applications to interact with scripts written in languages such as JavaScript.

    -
    - - -

    Use "Cloudbees Rhino Sandbox" or sandboxing with SecurityManager or use graalvm instead.

    -
    - - -

    The following code could execute random JavaScript code

    - - -
    - - -
  • -CERT coding standard: ScriptEngine code injection -
  • -
    -
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.ql b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.ql deleted file mode 100644 index 5e52a61b5c3..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.ql +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @name ScriptEngine evaluation - * @description Malicious Javascript code could cause arbitrary command execution at the OS level - * @kind path-problem - * @problem.severity error - * @precision high - * @id java/unsafe-eval - * @tags security - * external/cwe/cwe-094 - */ - -import java -import semmle.code.java.dataflow.FlowSources -import DataFlow::PathGraph - -class ScriptEngineMethod extends Method { - ScriptEngineMethod() { - this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and - this.hasName("eval") - } -} - -predicate scriptEngine(MethodAccess ma, Expr sink) { - exists(Method m | m = ma.getMethod() | - m instanceof ScriptEngineMethod and - sink = ma.getArgument(0) - ) -} - -class ScriptEngineSink extends DataFlow::ExprNode { - ScriptEngineSink() { scriptEngine(_, this.getExpr()) } - - MethodAccess getMethodAccess() { scriptEngine(result, this.getExpr()) } -} - -class ScriptEngineConfiguration extends TaintTracking::Configuration { - ScriptEngineConfiguration() { this = "ScriptEngineConfiguration" } - - override predicate isSource(DataFlow::Node source) { - source instanceof RemoteFlowSource - or - source instanceof LocalUserInput - } - - override predicate isSink(DataFlow::Node sink) { sink instanceof ScriptEngineSink } -} - -from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptEngineConfiguration conf -where conf.hasFlowPath(source, sink) -select sink.getNode().(ScriptEngineSink).getMethodAccess(), source, sink, "ScriptEngine eval $@.", - source.getNode(), "user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.qhelp new file mode 100644 index 00000000000..2683cf9ad29 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.qhelp @@ -0,0 +1,52 @@ + + + + +

    The Java Scripting API has been available since the release of Java 6. It allows + applications to interact with scripts written in languages such as JavaScript. It serves + as an embedded scripting engine inside Java applications which allows Java-to-JavaScript + interoperability and provides a seamless integration between the two languages. If an + expression is built using attacker-controlled data, and then evaluated in a powerful + context, it may allow the attacker to run arbitrary code.

    +
    + + +

    In general, including user input in a Java Script Engine expression should be avoided. + If user input must be included in the expression, it should be then evaluated in a safe + context that doesn't allow arbitrary code invocation. Use "Cloudbees Rhino Sandbox" or + sandboxing with SecurityManager, which will be deprecated in a future release, or use + GraalVM instead.

    +
    + + +

    The following code could execute user-supplied JavaScript code in ScriptEngine

    + + + +

    The following example shows two ways of using Rhino expression. In the 'BAD' case, + an unsafe context is initialized with initStandardObjects that allows arbitrary + Java code to be executed. In the 'GOOD' case, a safe context is initialized with + initSafeStandardObjects or setClassShutter.

    + +
    + + +
  • +CERT coding standard: ScriptEngine code injection +
  • +
  • +GraalVM: Secure by Default +
  • +
  • + Mozilla Rhino: Rhino: JavaScript in Java +
  • +
  • + Rhino Sandbox: A sandbox to execute JavaScript code with Rhino in Java +
  • +
  • + GuardRails: Code Injection +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.ql new file mode 100644 index 00000000000..fb8bf867501 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.ql @@ -0,0 +1,146 @@ +/** + * @name Injection in Java Script Engine + * @description Evaluation of user-controlled data using the Java Script Engine may + * lead to remote code execution. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/unsafe-eval + * @tags security + * external/cwe/cwe-094 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import DataFlow::PathGraph + +/** A method of ScriptEngine that allows code injection. */ +class ScriptEngineMethod extends Method { + ScriptEngineMethod() { + this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and + this.hasName("eval") + or + this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "Compilable") and + this.hasName("compile") + or + this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngineFactory") and + this.hasName(["getProgram", "getMethodCallSyntax"]) + } +} + +/** The context class `org.mozilla.javascript.Context` of Rhino Java Script Engine. */ +class RhinoContext extends RefType { + RhinoContext() { this.hasQualifiedName("org.mozilla.javascript", "Context") } +} + +/** A method that evaluates a Rhino expression with `org.mozilla.javascript.Context`. */ +class RhinoEvaluateExpressionMethod extends Method { + RhinoEvaluateExpressionMethod() { + this.getDeclaringType().getAnAncestor*() instanceof RhinoContext and + this.hasName([ + "evaluateString", "evaluateReader", "compileFunction", "compileReader", "compileString" + ]) + } +} + +/** + * A method that compiles a Rhino expression with + * `org.mozilla.javascript.optimizer.ClassCompiler`. + */ +class RhinoCompileClassMethod extends Method { + RhinoCompileClassMethod() { + this.getDeclaringType() + .getASupertype*() + .hasQualifiedName("org.mozilla.javascript.optimizer", "ClassCompiler") and + this.hasName("compileToClassFiles") + } +} + +/** + * A method that defines a Java class from a Rhino expression with + * `org.mozilla.javascript.GeneratedClassLoader`. + */ +class RhinoDefineClassMethod extends Method { + RhinoDefineClassMethod() { + this.getDeclaringType() + .getASupertype*() + .hasQualifiedName("org.mozilla.javascript", "GeneratedClassLoader") and + this.hasName("defineClass") + } +} + +/** + * Holds if `ma` is a call to a `ScriptEngineMethod` and `sink` is an argument that + * will be executed. + */ +predicate isScriptArgument(MethodAccess ma, Expr sink) { + exists(ScriptEngineMethod m | + m = ma.getMethod() and + if m.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngineFactory") + then sink = ma.getArgument(_) // all arguments allow script injection + else sink = ma.getArgument(0) + ) +} + +/** + * Holds if a Rhino expression evaluation method is vulnerable to code injection. + */ +predicate evaluatesRhinoExpression(MethodAccess ma, Expr sink) { + exists(RhinoEvaluateExpressionMethod m | m = ma.getMethod() | + ( + if ma.getMethod().getName() = "compileReader" + then sink = ma.getArgument(0) // The first argument is the input reader + else sink = ma.getArgument(1) // The second argument is the JavaScript or Java input + ) and + not exists(MethodAccess ca | + ca.getMethod().hasName(["initSafeStandardObjects", "setClassShutter"]) and // safe mode or `ClassShutter` constraint is enforced + ma.getQualifier() = ca.getQualifier().(VarAccess).getVariable().getAnAccess() + ) + ) +} + +/** + * Holds if a Rhino expression compilation method is vulnerable to code injection. + */ +predicate compilesScript(MethodAccess ma, Expr sink) { + exists(RhinoCompileClassMethod m | m = ma.getMethod() | sink = ma.getArgument(0)) +} + +/** + * Holds if a Rhino class loading method is vulnerable to code injection. + */ +predicate definesRhinoClass(MethodAccess ma, Expr sink) { + exists(RhinoDefineClassMethod m | m = ma.getMethod() | sink = ma.getArgument(1)) +} + +/** A script injection sink. */ +class ScriptInjectionSink extends DataFlow::ExprNode { + MethodAccess methodAccess; + + ScriptInjectionSink() { + isScriptArgument(methodAccess, this.getExpr()) or + evaluatesRhinoExpression(methodAccess, this.getExpr()) or + compilesScript(methodAccess, this.getExpr()) or + definesRhinoClass(methodAccess, this.getExpr()) + } + + /** An access to the method associated with this sink. */ + MethodAccess getMethodAccess() { result = methodAccess } +} + +/** + * A taint tracking configuration that tracks flow from `RemoteFlowSource` to an argument + * of a method call that executes injected script. + */ +class ScriptInjectionConfiguration extends TaintTracking::Configuration { + ScriptInjectionConfiguration() { this = "ScriptInjectionConfiguration" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof ScriptInjectionSink } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptInjectionConfiguration conf +where conf.hasFlowPath(source, sink) +select sink.getNode().(ScriptInjectionSink).getMethodAccess(), source, sink, + "Java Script Engine evaluate $@.", source.getNode(), "user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.java b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.java new file mode 100644 index 00000000000..eba64aab6a8 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.java @@ -0,0 +1,46 @@ +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; + +@Controller +public class SpringUrlRedirect { + + private final static String VALID_REDIRECT = "http://127.0.0.1"; + + @GetMapping("url1") + public RedirectView bad1(String redirectUrl, HttpServletResponse response) throws Exception { + RedirectView rv = new RedirectView(); + rv.setUrl(redirectUrl); + return rv; + } + + @GetMapping("url2") + public String bad2(String redirectUrl) { + String url = "redirect:" + redirectUrl; + return url; + } + + @GetMapping("url3") + public RedirectView bad3(String redirectUrl) { + RedirectView rv = new RedirectView(redirectUrl); + return rv; + } + + @GetMapping("url4") + public ModelAndView bad4(String redirectUrl) { + return new ModelAndView("redirect:" + redirectUrl); + } + + @GetMapping("url5") + public RedirectView good1(String redirectUrl) { + RedirectView rv = new RedirectView(); + if (redirectUrl.startsWith(VALID_REDIRECT)){ + rv.setUrl(redirectUrl); + }else { + rv.setUrl(VALID_REDIRECT); + } + return rv; + } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qhelp b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qhelp new file mode 100644 index 00000000000..6fe70dfb113 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qhelp @@ -0,0 +1,37 @@ + + + + + +

    Directly incorporating user input into a URL redirect request without validating the input +can facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a +malicious site that looks very similar to the real site they intend to visit, but which is +controlled by the attacker.

    + +
    + + +

    To guard against untrusted URL redirection, it is advisable to avoid putting user input +directly into a redirect URL. Instead, maintain a list of authorized +redirects on the server; then choose from that list based on the user input provided.

    + +
    + + +

    The following examples show the bad case and the good case respectively. +In bad1 method and bad2 method and bad3 method and +bad4 method, shows an HTTP request parameter being used directly in a URL redirect +without validating the input, which facilitates phishing attacks. In good1 method, +shows how to solve this problem by verifying whether the user input is a known fixed string beginning. +

    + + + +
    + +
  • A Guide To Spring Redirects: Spring Redirects.
  • +
  • Url redirection - attack and defense: Url Redirection.
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.ql b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.ql new file mode 100644 index 00000000000..b02bd3e4c30 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.ql @@ -0,0 +1,66 @@ +/** + * @name Spring url redirection from remote source + * @description Spring url redirection based on unvalidated user-input + * may cause redirection to malicious web sites. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/spring-unvalidated-url-redirection + * @tags security + * external/cwe-601 + */ + +import java +import SpringUrlRedirect +import semmle.code.java.dataflow.FlowSources +import DataFlow::PathGraph + +private class StartsWithSanitizer extends DataFlow::BarrierGuard { + StartsWithSanitizer() { + this.(MethodAccess).getMethod().hasName("startsWith") and + this.(MethodAccess).getMethod().getDeclaringType() instanceof TypeString and + this.(MethodAccess).getMethod().getNumberOfParameters() = 1 + } + + override predicate checks(Expr e, boolean branch) { + e = this.(MethodAccess).getQualifier() and branch = true + } +} + +class SpringUrlRedirectFlowConfig extends TaintTracking::Configuration { + SpringUrlRedirectFlowConfig() { this = "SpringUrlRedirectFlowConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof SpringUrlRedirectSink } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof StartsWithSanitizer + } + + override predicate isSanitizer(DataFlow::Node node) { + // Exclude the case where the left side of the concatenated string is not `redirect:`. + // E.g: `String url = "/path?token=" + request.getParameter("token");` + // Note this is quite a broad sanitizer (it will also sanitize the right-hand side of `url = "http://" + request.getParameter("token")`); + // Consider making this stricter in future. + exists(AddExpr ae | + ae.getRightOperand() = node.asExpr() and + not ae instanceof RedirectBuilderExpr + ) + or + exists(MethodAccess ma, int index | + ma.getMethod().hasName("format") and + ma.getMethod().getDeclaringType() instanceof TypeString and + ma.getArgument(index) = node.asExpr() and + ( + index != 0 and + not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().regexpMatch("^%s.*") + ) + ) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, SpringUrlRedirectFlowConfig conf +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Potentially untrusted URL redirection due to $@.", + source.getNode(), "user-provided value" diff --git a/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qll b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qll new file mode 100644 index 00000000000..4a86640d4d4 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qll @@ -0,0 +1,73 @@ +import java +import DataFlow +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.dataflow.DataFlow2 +import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.frameworks.spring.SpringController + +/** + * A concatenate expression using the string `redirect:` or `ajaxredirect:` or `forward:` on the left. + * + * E.g: `"redirect:" + redirectUrl` + */ +class RedirectBuilderExpr extends AddExpr { + RedirectBuilderExpr() { + this.getLeftOperand().(CompileTimeConstantExpr).getStringValue() in [ + "redirect:", "ajaxredirect:", "forward:" + ] + } +} + +/** + * A call to `StringBuilder.append` or `StringBuffer.append` method, and the parameter value is + * `"redirect:"` or `"ajaxredirect:"` or `"forward:"`. + * + * E.g: `StringBuilder.append("redirect:")` + */ +class RedirectAppendCall extends MethodAccess { + RedirectAppendCall() { + this.getMethod().hasName("append") and + this.getMethod().getDeclaringType() instanceof StringBuildingType and + this.getArgument(0).(CompileTimeConstantExpr).getStringValue() in [ + "redirect:", "ajaxredirect:", "forward:" + ] + } +} + +/** A URL redirection sink from spring controller method. */ +class SpringUrlRedirectSink extends DataFlow::Node { + SpringUrlRedirectSink() { + exists(RedirectBuilderExpr rbe | + rbe.getRightOperand() = this.asExpr() and + any(SpringRequestMappingMethod sqmm).polyCalls*(this.getEnclosingCallable()) + ) + or + exists(MethodAccess ma, RedirectAppendCall rac | + DataFlow2::localExprFlow(rac.getQualifier(), ma.getQualifier()) and + ma.getMethod().hasName("append") and + ma.getArgument(0) = this.asExpr() and + any(SpringRequestMappingMethod sqmm).polyCalls*(this.getEnclosingCallable()) + ) + or + exists(MethodAccess ma | + ma.getMethod().hasName("setUrl") and + ma.getMethod() + .getDeclaringType() + .hasQualifiedName("org.springframework.web.servlet.view", "AbstractUrlBasedView") and + ma.getArgument(0) = this.asExpr() + ) + or + exists(ClassInstanceExpr cie | + cie.getConstructedType() + .hasQualifiedName("org.springframework.web.servlet.view", "RedirectView") and + cie.getArgument(0) = this.asExpr() + ) + or + exists(ClassInstanceExpr cie | + cie.getConstructedType().hasQualifiedName("org.springframework.web.servlet", "ModelAndView") and + exists(RedirectBuilderExpr rbe | + rbe = cie.getArgument(0) and rbe.getRightOperand() = this.asExpr() + ) + ) + } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.java b/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.java new file mode 100644 index 00000000000..387648a443e --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.java @@ -0,0 +1,38 @@ +package com.example.demo; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DemoApplication { + + @GetMapping("/string1") + public String string1(@RequestParam(value = "input", defaultValue = "test") String input, + @RequestParam(value = "pattern", defaultValue = ".*") String pattern) { + // BAD: Unsanitized user input is used to construct a regular expression + if (input.matches("^" + pattern + "=.*$")) + return "match!"; + + return "doesn't match!"; + } + + @GetMapping("/string2") + public String string2(@RequestParam(value = "input", defaultValue = "test") String input, + @RequestParam(value = "pattern", defaultValue = ".*") String pattern) { + // GOOD: User input is sanitized before constructing the regex + if (input.matches("^" + escapeSpecialRegexChars(pattern) + "=.*$")) + return "match!"; + + return "doesn't match!"; + } + + Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]"); + + String escapeSpecialRegexChars(String str) { + return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0"); + } +} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.qhelp new file mode 100644 index 00000000000..6cd80b52f75 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.qhelp @@ -0,0 +1,48 @@ + + + + +

    +Constructing a regular expression with unsanitized user input is dangerous as a malicious user may +be able to modify the meaning of the expression. In particular, such a user may be able to provide +a regular expression fragment that takes exponential time in the worst case, and use that to +perform a Denial of Service attack. +

    +
    + + +

    +Before embedding user input into a regular expression, use a sanitization function +to escape meta-characters that have special meaning. +

    +
    + + +

    +The following example shows a HTTP request parameter that is used to construct a regular expression: +

    + +

    +In the first case the user-provided regex is not escaped. +If a malicious user provides a regex that has exponential worst case performance, +then this could lead to a Denial of Service. +

    +

    +In the second case, the user input is escaped using escapeSpecialRegexChars before being included +in the regular expression. This ensures that the user cannot insert characters which have a special +meaning in regular expressions. +

    +
    + + +
  • +OWASP: +Regular expression Denial of Service - ReDoS. +
  • +
  • +Wikipedia: ReDoS. +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.ql new file mode 100644 index 00000000000..3b8b5dc759a --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.ql @@ -0,0 +1,89 @@ +/** + * @name Regular expression injection + * @description User input should not be used in regular expressions without first being sanitized, + * otherwise a malicious user may be able to provide a regex that could require + * exponential time on certain inputs. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/regex-injection + * @tags security + * external/cwe/cwe-730 + * external/cwe/cwe-400 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.dataflow.TaintTracking +import DataFlow::PathGraph + +/** + * A data flow sink for untrusted user input used to construct regular expressions. + */ +class RegexSink extends DataFlow::ExprNode { + RegexSink() { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + ( + m.getDeclaringType() instanceof TypeString and + ( + ma.getArgument(0) = this.asExpr() and + m.hasName(["matches", "split", "replaceFirst", "replaceAll"]) + ) + or + m.getDeclaringType().hasQualifiedName("java.util.regex", "Pattern") and + ( + ma.getArgument(0) = this.asExpr() and + m.hasName(["compile", "matches"]) + ) + or + m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "RegExUtils") and + ( + ma.getArgument(1) = this.asExpr() and + m.getParameterType(1) instanceof TypeString and + m.hasName([ + "removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst", + "replacePattern" + ]) + ) + ) + ) + } +} + +abstract class Sanitizer extends DataFlow::ExprNode { } + +/** + * A call to a function whose name suggests that it escapes regular + * expression meta-characters. + */ +class RegExpSanitizationCall extends Sanitizer { + RegExpSanitizationCall() { + exists(string calleeName, string sanitize, string regexp | + calleeName = this.asExpr().(Call).getCallee().getName() and + sanitize = "(?:escape|saniti[sz]e)" and + regexp = "regexp?" + | + calleeName + .regexpMatch("(?i)(" + sanitize + ".*" + regexp + ".*)" + "|(" + regexp + ".*" + sanitize + + ".*)") + ) + } +} + +/** + * A taint-tracking configuration for untrusted user input used to construct regular expressions. + */ +class RegexInjectionConfiguration extends TaintTracking::Configuration { + RegexInjectionConfiguration() { this = "RegexInjectionConfiguration" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof RegexSink } + + override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, RegexInjectionConfiguration c +where c.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "$@ is user controlled.", source.getNode(), + "This regular expression pattern" diff --git a/java/ql/src/semmle/code/FileSystem.qll b/java/ql/src/semmle/code/FileSystem.qll index 13908d547dc..986677dd2f0 100755 --- a/java/ql/src/semmle/code/FileSystem.qll +++ b/java/ql/src/semmle/code/FileSystem.qll @@ -146,9 +146,11 @@ class Container extends @container, Top { } /** - * Gets a textual representation of the path of this container. + * Gets a textual representation of this container. * - * This is the absolute path of the container. + * The default implementation gets the absolute path to the container, but subclasses may override + * to provide a different result. To get the absolute path of any `Container`, call + * `Container.getAbsolutePath()` directly. */ override string toString() { result = getAbsolutePath() } } diff --git a/java/ql/src/semmle/code/java/Statement.qll b/java/ql/src/semmle/code/java/Statement.qll index ba56564ae51..97a09ac4e6b 100755 --- a/java/ql/src/semmle/code/java/Statement.qll +++ b/java/ql/src/semmle/code/java/Statement.qll @@ -739,7 +739,7 @@ class LabeledStmt extends Stmt, @labeledstmt { /** This statement's Halstead ID (used to compute Halstead metrics). */ override string getHalsteadID() { result = this.getLabel() + ":" } - override string getAPrimaryQlClass() { result = "LabelStmt" } + override string getAPrimaryQlClass() { result = "LabeledStmt" } } /** An `assert` statement. */ diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll index afd2c1df676..443671babe1 100644 --- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll @@ -67,6 +67,7 @@ import java private import semmle.code.java.dataflow.DataFlow::DataFlow private import internal.DataFlowPrivate +private import internal.FlowSummaryImpl::Private::External private import FlowSummary /** @@ -77,7 +78,9 @@ private module Frameworks { private import semmle.code.java.frameworks.ApacheHttp private import semmle.code.java.frameworks.apache.Lang private import semmle.code.java.frameworks.guava.Guava + private import semmle.code.java.frameworks.jackson.JacksonSerializability private import semmle.code.java.security.ResponseSplitting + private import semmle.code.java.security.InformationLeak private import semmle.code.java.security.XSS private import semmle.code.java.security.LdapInjection private import semmle.code.java.security.XPath @@ -87,6 +90,13 @@ private module Frameworks { private predicate sourceModelCsv(string row) { row = [ + // org.springframework.security.web.savedrequest.SavedRequest + "org.springframework.security.web.savedrequest;SavedRequest;true;getRedirectUrl;;;ReturnValue;remote", + "org.springframework.security.web.savedrequest;SavedRequest;true;getCookies;;;ReturnValue;remote", + "org.springframework.security.web.savedrequest;SavedRequest;true;getHeaderValues;;;ReturnValue;remote", + "org.springframework.security.web.savedrequest;SavedRequest;true;getHeaderNames;;;ReturnValue;remote", + "org.springframework.security.web.savedrequest;SavedRequest;true;getParameterValues;;;ReturnValue;remote", + "org.springframework.security.web.savedrequest;SavedRequest;true;getParameterMap;;;ReturnValue;remote", // ServletRequestGetParameterMethod "javax.servlet;ServletRequest;false;getParameter;(String);;ReturnValue;remote", "javax.servlet;ServletRequest;false;getParameterValues;(String);;ReturnValue;remote", @@ -293,6 +303,7 @@ private predicate summaryModelCsv(string row) { "java.util;StringTokenizer;false;StringTokenizer;;;Argument[0];Argument[-1];taint", "java.beans;XMLDecoder;false;XMLDecoder;;;Argument[0];Argument[-1];taint", "com.esotericsoftware.kryo.io;Input;false;Input;;;Argument[0];Argument[-1];taint", + "com.esotericsoftware.kryo5.io;Input;false;Input;;;Argument[0];Argument[-1];taint", "java.io;BufferedInputStream;false;BufferedInputStream;;;Argument[0];Argument[-1];taint", "java.io;DataInputStream;false;DataInputStream;;;Argument[0];Argument[-1];taint", "java.io;ByteArrayInputStream;false;ByteArrayInputStream;;;Argument[0];Argument[-1];taint", @@ -490,10 +501,15 @@ module CsvValidation { or summaryModel(_, _, _, _, _, _, input, _, _) and pred = "summary" | - specSplit(input, part, _) and - not part.regexpMatch("|ReturnValue|ArrayElement|Element|MapKey|MapValue") and - not (part = "Argument" and pred = "sink") and - not parseArg(part, _) and + ( + invalidSpecComponent(input, part) and + not part = "" and + not (part = "Argument" and pred = "sink") and + not parseArg(part, _) + or + specSplit(input, part, _) and + parseParam(part, _) + ) and msg = "Unrecognized input specification \"" + part + "\" in " + pred + " model." ) or @@ -502,11 +518,9 @@ module CsvValidation { or summaryModel(_, _, _, _, _, _, _, output, _) and pred = "summary" | - specSplit(output, part, _) and - not part.regexpMatch("|ReturnValue|ArrayElement|Element|MapKey|MapValue") and + invalidSpecComponent(output, part) and + not part = "" and not (part = ["Argument", "Parameter"] and pred = "source") and - not parseArg(part, _) and - not parseParam(part, _) and msg = "Unrecognized output specification \"" + part + "\" in " + pred + " model." ) or @@ -616,7 +630,11 @@ private predicate sinkElement(Element e, string input, string kind) { ) } -private predicate summaryElement(Element e, string input, string output, string kind) { +/** + * Holds if an external flow summary exists for `e` with input specification + * `input`, output specification `output`, and kind `kind`. + */ +predicate summaryElement(Element e, string input, string output, string kind) { exists( string namespace, string type, boolean subtypes, string name, string signature, string ext | @@ -625,52 +643,14 @@ private predicate summaryElement(Element e, string input, string output, string ) } -private string inOutSpec() { +/** Gets a specification used in a source model, sink model, or summary model. */ +string inOutSpec() { sourceModel(_, _, _, _, _, _, result, _) or sinkModel(_, _, _, _, _, _, result, _) or summaryModel(_, _, _, _, _, _, result, _, _) or summaryModel(_, _, _, _, _, _, _, result, _) } -private predicate specSplit(string s, string c, int n) { - inOutSpec() = s and s.splitAt(" of ", n) = c -} - -private predicate len(string s, int len) { len = 1 + max(int n | specSplit(s, _, n)) } - -private string getLast(string s) { - exists(int len | - len(s, len) and - specSplit(s, result, len - 1) - ) -} - -private predicate parseParam(string c, int n) { - specSplit(_, c, _) and - ( - c.regexpCapture("Parameter\\[([-0-9]+)\\]", 1).toInt() = n - or - exists(int n1, int n2 | - c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and - c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and - n = [n1 .. n2] - ) - ) -} - -private predicate parseArg(string c, int n) { - specSplit(_, c, _) and - ( - c.regexpCapture("Argument\\[([-0-9]+)\\]", 1).toInt() = n - or - exists(int n1, int n2 | - c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and - c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and - n = [n1 .. n2] - ) - ) -} - private predicate inputNeedsReference(string c) { c = "Argument" or parseArg(c, _) @@ -685,7 +665,7 @@ private predicate outputNeedsReference(string c) { private predicate sourceElementRef(Top ref, string output, string kind) { exists(Element e | sourceElement(e, output, kind) and - if outputNeedsReference(getLast(output)) + if outputNeedsReference(specLast(output)) then ref.(Call).getCallee().getSourceDeclaration() = e else ref = e ) @@ -694,7 +674,7 @@ private predicate sourceElementRef(Top ref, string output, string kind) { private predicate sinkElementRef(Top ref, string input, string kind) { exists(Element e | sinkElement(e, input, kind) and - if inputNeedsReference(getLast(input)) + if inputNeedsReference(specLast(input)) then ref.(Call).getCallee().getSourceDeclaration() = e else ref = e ) @@ -703,81 +683,12 @@ private predicate sinkElementRef(Top ref, string input, string kind) { private predicate summaryElementRef(Top ref, string input, string output, string kind) { exists(Element e | summaryElement(e, input, output, kind) and - if inputNeedsReference(getLast(input)) + if inputNeedsReference(specLast(input)) then ref.(Call).getCallee().getSourceDeclaration() = e else ref = e ) } -private SummaryComponent interpretComponent(string c) { - specSplit(_, c, _) and - ( - exists(int pos | parseArg(c, pos) and result = SummaryComponent::argument(pos)) - or - exists(int pos | parseParam(c, pos) and result = SummaryComponent::parameter(pos)) - or - c = "ReturnValue" and result = SummaryComponent::return() - or - c = "ArrayElement" and result = SummaryComponent::content(any(ArrayContent c0)) - or - c = "Element" and result = SummaryComponent::content(any(CollectionContent c0)) - or - c = "MapKey" and result = SummaryComponent::content(any(MapKeyContent c0)) - or - c = "MapValue" and result = SummaryComponent::content(any(MapValueContent c0)) - ) -} - -private predicate interpretSpec(string spec, int idx, SummaryComponentStack stack) { - exists(string c | - summaryElement(_, spec, _, _) or - summaryElement(_, _, spec, _) - | - len(spec, idx + 1) and - specSplit(spec, c, idx) and - stack = SummaryComponentStack::singleton(interpretComponent(c)) - ) - or - exists(SummaryComponent head, SummaryComponentStack tail | - interpretSpec(spec, idx, head, tail) and - stack = SummaryComponentStack::push(head, tail) - ) -} - -private predicate interpretSpec( - string output, int idx, SummaryComponent head, SummaryComponentStack tail -) { - exists(string c | - interpretSpec(output, idx + 1, tail) and - specSplit(output, c, idx) and - head = interpretComponent(c) - ) -} - -private class MkStack extends RequiredSummaryComponentStack { - MkStack() { interpretSpec(_, _, _, this) } - - override predicate required(SummaryComponent c) { interpretSpec(_, _, c, this) } -} - -private class SummarizedCallableExternal extends SummarizedCallable { - SummarizedCallableExternal() { summaryElement(this, _, _, _) } - - override predicate propagatesFlow( - SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue - ) { - exists(string inSpec, string outSpec, string kind | - summaryElement(this, inSpec, outSpec, kind) and - interpretSpec(inSpec, 0, input) and - interpretSpec(outSpec, 0, output) - | - kind = "value" and preservesValue = true - or - kind = "taint" and preservesValue = false - ) - } -} - private newtype TAstOrNode = TAst(Top t) or TNode(Node n) @@ -787,7 +698,7 @@ private predicate interpretOutput(string output, int idx, Top ref, TAstOrNode no sourceElementRef(ref, output, _) or summaryElementRef(ref, _, output, _) ) and - len(output, idx) and + specLength(output, idx) and node = TAst(ref) or exists(Top mid, string c, Node n | @@ -819,7 +730,7 @@ private predicate interpretInput(string input, int idx, Top ref, TAstOrNode node sinkElementRef(ref, input, _) or summaryElementRef(ref, input, _, _) ) and - len(input, idx) and + specLength(input, idx) and node = TAst(ref) or exists(Top mid, string c, Node n | diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll index 3a98ed62847..2b6179bfc96 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowDispatch.qll @@ -106,7 +106,7 @@ private module DispatchImpl { mayBenefitFromCallContext(ma, c, i) and c = viableCallable(ctx) and contextArgHasType(ctx, i, t, exact) and - ma.getMethod() = def + ma.getMethod().getSourceDeclaration() = def | exact = true and result = VirtualDispatch::exactMethodImpl(def, t.getSourceDeclaration()) or diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl6.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl6.qll index 9498e51e7e6..9b14db7ef88 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl6.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl6.qll @@ -211,10 +211,7 @@ private predicate fullBarrier(Node node, Configuration config) { * Holds if data can flow in one local step from `node1` to `node2`. */ private predicate localFlowStep(Node node1, Node node2, Configuration config) { - ( - simpleLocalFlowStep(node1, node2) or - reverseStepThroughInputOutputAlias(node1, node2) - ) and + simpleLocalFlowStepExt(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -237,7 +234,7 @@ private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ private predicate jumpStep(Node node1, Node node2, Configuration config) { - jumpStep(node1, node2) and + jumpStepCached(node1, node2) and not outBarrier(node1, config) and not inBarrier(node2, config) and not fullBarrier(node1, config) and @@ -388,7 +385,7 @@ private module Stage1 { */ pragma[nomagic] private predicate fwdFlowIsEntered(DataFlowCall call, Cc cc, Configuration config) { - exists(ArgumentNode arg | + exists(ArgNode arg | fwdFlow(arg, cc, config) and viableParamArg(call, _, arg) ) @@ -515,29 +512,29 @@ private module Stage1 { pragma[nomagic] predicate viableParamArgNodeCandFwd1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { viableParamArg(call, p, arg) and fwdFlow(arg, config) } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config - ) { - exists(ParameterNode p | + private predicate revFlowIn(DataFlowCall call, ArgNode arg, boolean toReturn, Configuration config) { + exists(ParamNode p | revFlow(p, toReturn, config) and viableParamArgNodeCandFwd1(call, p, arg, config) ) } pragma[nomagic] - private predicate revFlowInToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + private predicate revFlowInToReturn(DataFlowCall call, ArgNode arg, Configuration config) { revFlowIn(call, arg, true, config) } /** - * Holds if an output from `call` is reached in the flow covered by `revFlow`. + * Holds if an output from `call` is reached in the flow covered by `revFlow` + * and data might flow through the target callable resulting in reverse flow + * reaching an argument of `call`. */ pragma[nomagic] private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) { @@ -597,7 +594,7 @@ private module Stage1 { * Holds if flow may enter through `p` and reach a return node making `p` a * candidate for the origin of a summary. */ - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnKindExt kind | throughFlowNodeCand(p, config) and returnFlowCallableNodeCand(c, kind, config) and @@ -610,6 +607,15 @@ private module Stage1 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(ArgNode arg, boolean toReturn | + revFlow(arg, toReturn, config) and + revFlowInToReturn(call, arg, config) and + revFlowIsReturned(call, toReturn, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, config)) and @@ -663,7 +669,7 @@ private predicate flowOutOfCallNodeCand1( pragma[nomagic] private predicate viableParamArgNodeCand1( - DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config + DataFlowCall call, ParamNode p, ArgNode arg, Configuration config ) { Stage1::viableParamArgNodeCandFwd1(call, p, arg, config) and Stage1::revFlow(arg, config) @@ -675,7 +681,7 @@ private predicate viableParamArgNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, Configuration config ) { viableParamArgNodeCand1(call, p, arg, config) and Stage1::revFlow(p, config) and @@ -735,8 +741,7 @@ private predicate flowOutOfCallNodeCand1( */ pragma[nomagic] private predicate flowIntoCallNodeCand1( - DataFlowCall call, ArgumentNode arg, ParameterNode p, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, arg, p, config) and exists(int b, int j | @@ -833,6 +838,16 @@ private module Stage2 { PrevStage::revFlow(node, _, _, apa, config) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -899,13 +914,11 @@ private module Stage2 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -944,10 +957,10 @@ private module Stage2 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -956,17 +969,14 @@ private module Stage2 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -981,7 +991,13 @@ private module Stage2 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -992,7 +1008,7 @@ private module Stage2 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, getApprox(ap), config) ) @@ -1012,6 +1028,27 @@ private module Stage2 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1077,14 +1114,12 @@ private module Stage2 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1132,13 +1167,10 @@ private module Stage2 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1146,9 +1178,14 @@ private module Stage2 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1199,13 +1236,13 @@ private module Stage2 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1218,6 +1255,15 @@ private module Stage2 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -1245,8 +1291,7 @@ private predicate flowOutOfCallNodeCand2( pragma[nomagic] private predicate flowIntoCallNodeCand2( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand1(call, node1, node2, allowsFieldFlow, config) and Stage2::revFlow(node2, pragma[only_bind_into](config)) and @@ -1260,8 +1305,8 @@ private module LocalFlowBigStep { */ private class FlowCheckNode extends Node { FlowCheckNode() { - this instanceof CastNode or - clearsContent(this, _) + castNode(this) or + clearsContentCached(this, _) } } @@ -1275,7 +1320,7 @@ private module LocalFlowBigStep { config.isSource(node) or jumpStep(_, node, config) or additionalJumpStep(_, node, config) or - node instanceof ParameterNode or + node instanceof ParamNode or node instanceof OutNodeExt or store(_, _, node, _) or read(_, _, node) or @@ -1321,21 +1366,21 @@ private module LocalFlowBigStep { Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, LocalCallContext cc ) { - not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, pragma[only_bind_into](config)) and ( localFlowStepNodeCand1(node1, node2, config) and preservesValue = true and - t = getNodeType(node1) + t = getNodeDataFlowType(node1) or additionalLocalFlowStepNodeCand2(node1, node2, config) and preservesValue = false and - t = getNodeType(node2) + t = getNodeDataFlowType(node2) ) and node1 != node2 and cc.relevantFor(getNodeEnclosingCallable(node1)) and - not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node1, cc.(LocalCallContextSpecificCall).getCall()) and Stage2::revFlow(node2, pragma[only_bind_into](config)) or exists(Node mid | @@ -1350,7 +1395,7 @@ private module LocalFlowBigStep { additionalLocalFlowStepNodeCand2(mid, node2, config) and not mid instanceof FlowCheckNode and preservesValue = false and - t = getNodeType(node2) and + t = getNodeDataFlowType(node2) and Stage2::revFlow(node2, pragma[only_bind_into](config)) ) ) @@ -1384,7 +1429,7 @@ private module Stage3 { private ApApprox getApprox(Ap ap) { result = ap.toBoolNonEmpty() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TFrontNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TFrontNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -1443,7 +1488,9 @@ private module Stage3 { bindingset[node, ap] private predicate filter(Node node, Ap ap) { not ap.isClearedAt(node) and - if node instanceof CastingNode then compatibleTypes(getNodeType(node), ap.getType()) else any() + if node instanceof CastingNode + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) + else any() } bindingset[ap, contentType] @@ -1465,6 +1512,16 @@ private module Stage3 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -1538,13 +1595,11 @@ private module Stage3 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -1583,10 +1638,10 @@ private module Stage3 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -1595,17 +1650,14 @@ private module Stage3 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -1620,7 +1672,13 @@ private module Stage3 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1631,7 +1689,7 @@ private module Stage3 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -1651,6 +1709,27 @@ private module Stage3 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -1716,14 +1795,12 @@ private module Stage3 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -1771,13 +1848,10 @@ private module Stage3 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -1785,9 +1859,14 @@ private module Stage3 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -1838,13 +1917,13 @@ private module Stage3 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -1857,6 +1936,15 @@ private module Stage3 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2088,7 +2176,7 @@ private module Stage4 { private ApApprox getApprox(Ap ap) { result = ap.getFront() } private ApNil getApNil(Node node) { - PrevStage::revFlow(node, _) and result = TNil(getNodeType(node)) + PrevStage::revFlow(node, _) and result = TNil(getNodeDataFlowType(node)) } bindingset[tc, tail] @@ -2127,8 +2215,6 @@ private module Stage4 { bindingset[innercc, inner, call] private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) { resolveReturn(innercc, inner, call) - or - innercc.(CallContextCall).matchesCall(call) } bindingset[node, cc, config] @@ -2155,8 +2241,7 @@ private module Stage4 { pragma[nomagic] private predicate flowIntoCall( - DataFlowCall call, ArgumentNode node1, ParameterNode node2, boolean allowsFieldFlow, - Configuration config + DataFlowCall call, ArgNode node1, ParamNode node2, boolean allowsFieldFlow, Configuration config ) { flowIntoCallNodeCand2(call, node1, node2, allowsFieldFlow, config) and PrevStage::revFlow(node2, _, _, _, pragma[only_bind_into](config)) and @@ -2182,6 +2267,16 @@ private module Stage4 { ) } + pragma[nomagic] + private predicate flowThroughOutOfCall( + DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config + ) { + flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and + PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _, + pragma[only_bind_into](config)) + } + /** * Holds if `node` is reachable with access path `ap` from a source in the * configuration `config`. @@ -2255,13 +2350,11 @@ private module Stage4 { ) or // flow out of a callable - exists(DataFlowCall call | - fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config) - or - exists(Ap argAp0 | - fwdFlowOutFromArg(call, node, argAp0, ap, config) and - fwdFlowIsEntered(call, cc, argAp, argAp0, config) - ) + fwdFlowOutNotFromArg(node, cc, argAp, ap, config) + or + exists(DataFlowCall call, Ap argAp0 | + fwdFlowOutFromArg(call, node, argAp0, ap, config) and + fwdFlowIsEntered(call, cc, argAp, argAp0, config) ) } @@ -2300,10 +2393,10 @@ private module Stage4 { pragma[nomagic] private predicate fwdFlowIn( - DataFlowCall call, ParameterNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, + DataFlowCall call, ParamNode p, Cc outercc, Cc innercc, ApOption argAp, Ap ap, Configuration config ) { - exists(ArgumentNode arg, boolean allowsFieldFlow | + exists(ArgNode arg, boolean allowsFieldFlow | fwdFlow(arg, outercc, argAp, ap, config) and flowIntoCall(call, arg, p, allowsFieldFlow, config) and innercc = getCallContextCall(call, getNodeEnclosingCallable(p), outercc) @@ -2312,17 +2405,14 @@ private module Stage4 { ) } - /** - * Holds if flow may exit from `call` at `out` with access path `ap`. The - * inner call context is `innercc`, but `ccOut` is just the call context - * based on the return step. In the case of through-flow `ccOut` is discarded - * and replaced by the outer call context as tracked by `fwdFlowIsEntered`. - */ pragma[nomagic] - private predicate fwdFlowOut( - DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config + private predicate fwdFlowOutNotFromArg( + Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config ) { - exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner | + exists( + DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc, + DataFlowCallable inner + | fwdFlow(ret, innercc, argAp, ap, config) and flowOutOfCall(call, ret, out, allowsFieldFlow, config) and inner = getNodeEnclosingCallable(ret) and @@ -2337,7 +2427,13 @@ private module Stage4 { private predicate fwdFlowOutFromArg( DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config ) { - fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config) + exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc | + fwdFlow(ret, ccc, apSome(argAp), ap, config) and + flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and + ccc.matchesCall(call) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2348,7 +2444,7 @@ private module Stage4 { private predicate fwdFlowIsEntered( DataFlowCall call, Cc cc, ApOption argAp, Ap ap, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | fwdFlowIn(call, p, cc, _, argAp, ap, config) and PrevStage::parameterMayFlowThrough(p, _, unbindApa(getApprox(ap)), config) ) @@ -2368,6 +2464,27 @@ private module Stage4 { fwdFlowConsCand(ap1, c, ap2, config) } + pragma[nomagic] + private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) { + exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap | + fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap, + pragma[only_bind_into](config)) and + fwdFlowOutFromArg(call, out, argAp0, ap, config) and + fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0, + pragma[only_bind_into](config)) + ) + } + + pragma[nomagic] + private predicate flowThroughIntoCall( + DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config + ) { + flowIntoCall(call, arg, p, allowsFieldFlow, config) and + fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and + PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and + callMayFlowThroughFwd(call, pragma[only_bind_into](config)) + } + /** * Holds if `node` with access path `ap` is part of a path from a source to a * sink in the configuration `config`. @@ -2433,14 +2550,12 @@ private module Stage4 { ) or // flow into a callable - exists(DataFlowCall call | - revFlowIn(call, node, toReturn, returnAp, ap, config) and - toReturn = false - or - exists(Ap returnAp0 | - revFlowInToReturn(call, node, returnAp0, ap, config) and - revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) - ) + revFlowInNotToReturn(node, returnAp, ap, config) and + toReturn = false + or + exists(DataFlowCall call, Ap returnAp0 | + revFlowInToReturn(call, node, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) ) or // flow out of a callable @@ -2488,13 +2603,10 @@ private module Stage4 { } pragma[nomagic] - private predicate revFlowIn( - DataFlowCall call, ArgumentNode arg, boolean toReturn, ApOption returnAp, Ap ap, - Configuration config - ) { - exists(ParameterNode p, boolean allowsFieldFlow | - revFlow(p, toReturn, returnAp, ap, config) and - flowIntoCall(call, arg, p, allowsFieldFlow, config) + private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) { + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, false, returnAp, ap, config) and + flowIntoCall(_, arg, p, allowsFieldFlow, config) | ap instanceof ApNil or allowsFieldFlow = true ) @@ -2502,9 +2614,14 @@ private module Stage4 { pragma[nomagic] private predicate revFlowInToReturn( - DataFlowCall call, ArgumentNode arg, Ap returnAp, Ap ap, Configuration config + DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config ) { - revFlowIn(call, arg, true, apSome(returnAp), ap, config) + exists(ParamNode p, boolean allowsFieldFlow | + revFlow(p, true, apSome(returnAp), ap, config) and + flowThroughIntoCall(call, arg, p, allowsFieldFlow, config) + | + ap instanceof ApNil or allowsFieldFlow = true + ) } /** @@ -2555,13 +2672,13 @@ private module Stage4 { pragma[noinline] private predicate parameterFlow( - ParameterNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config + ParamNode p, Ap ap, Ap ap0, DataFlowCallable c, Configuration config ) { revFlow(p, true, apSome(ap0), ap, config) and c = getNodeEnclosingCallable(p) } - predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, Ap ap, Configuration config) { + predicate parameterMayFlowThrough(ParamNode p, DataFlowCallable c, Ap ap, Configuration config) { exists(ReturnNodeExt ret, Ap ap0, ReturnKindExt kind, int pos | parameterFlow(p, ap, ap0, c, config) and c = getNodeEnclosingCallable(ret) and @@ -2574,6 +2691,15 @@ private module Stage4 { ) } + pragma[nomagic] + predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) { + exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap | + revFlow(arg, toReturn, returnAp, ap, config) and + revFlowInToReturn(call, arg, returnAp0, ap, config) and + revFlowIsReturned(call, toReturn, returnAp, returnAp0, config) + ) + } + predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) { fwd = true and nodes = count(Node node | fwdFlow(node, _, _, _, config)) and @@ -2606,7 +2732,7 @@ private predicate nodeMayUseSummary(Node n, AccessPathApprox apa, Configuration private newtype TSummaryCtx = TSummaryCtxNone() or - TSummaryCtxSome(ParameterNode p, AccessPath ap) { + TSummaryCtxSome(ParamNode p, AccessPath ap) { Stage4::parameterMayFlowThrough(p, _, ap.getApprox(), _) } @@ -2627,7 +2753,7 @@ private class SummaryCtxNone extends SummaryCtx, TSummaryCtxNone { /** A summary context from which a flow summary can be generated. */ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome { - private ParameterNode p; + private ParamNode p; private AccessPath ap; SummaryCtxSome() { this = TSummaryCtxSome(p, ap) } @@ -2758,7 +2884,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2979,7 +3105,7 @@ class PathNode extends TPathNode { Configuration getConfiguration() { none() } private predicate isHidden() { - nodeIsHidden(this.getNode()) and + hiddenNode(this.getNode()) and not this.isSource() and not this instanceof PathNodeSink } @@ -3148,7 +3274,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = TAccessPathNil(getNodeType(node)) + ap = TAccessPathNil(getNodeDataFlowType(node)) or exists(TypedContent tc | pathStoreStep(mid, node, ap.pop(tc), tc, cc)) and sc = mid.getSummaryCtx() @@ -3235,7 +3361,7 @@ pragma[noinline] private predicate pathIntoArg( PathNodeMid mid, int i, CallContext cc, DataFlowCall call, AccessPath ap, AccessPathApprox apa ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3248,7 +3374,7 @@ pragma[noinline] private predicate parameterCand( DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config ) { - exists(ParameterNode p | + exists(ParamNode p | Stage4::revFlow(p, _, _, apa, config) and p.isParameterOf(callable, i) ) @@ -3272,7 +3398,7 @@ private predicate pathIntoCallable0( * respectively. */ private predicate pathIntoCallable( - PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, + PathNodeMid mid, ParamNode p, CallContext outercc, CallContextCall innercc, SummaryCtx sc, DataFlowCall call ) { exists(int i, DataFlowCallable callable, AccessPath ap | @@ -3568,7 +3694,7 @@ private module FlowExploration { private newtype TSummaryCtx1 = TSummaryCtx1None() or - TSummaryCtx1Param(ParameterNode p) + TSummaryCtx1Param(ParamNode p) private newtype TSummaryCtx2 = TSummaryCtx2None() or @@ -3591,7 +3717,7 @@ private module FlowExploration { cc instanceof CallContextAny and sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and not fullBarrier(node, config) and exists(config.explorationLimit()) or @@ -3611,7 +3737,7 @@ private module FlowExploration { or exists(PartialPathNodeRev mid | revPartialPathStep(mid, node, sc1, sc2, ap, config) and - not clearsContent(node, ap.getHead()) and + not clearsContentCached(node, ap.getHead()) and not fullBarrier(node, config) and distSink(getNodeEnclosingCallable(node), config) <= config.explorationLimit() ) @@ -3625,9 +3751,9 @@ private module FlowExploration { exists(PartialPathNodeFwd mid | partialPathStep(mid, node, cc, sc1, sc2, ap, config) and not fullBarrier(node, config) and - not clearsContent(node, ap.getHead().getContent()) and + not clearsContentCached(node, ap.getHead().getContent()) and if node instanceof CastingNode - then compatibleTypes(getNodeType(node), ap.getType()) + then compatibleTypes(getNodeDataFlowType(node), ap.getType()) else any() ) } @@ -3783,7 +3909,7 @@ private module FlowExploration { PartialPathNodeFwd mid, Node node, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, PartialAccessPath ap, Configuration config ) { - not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and + not isUnreachableInCallCached(node, cc.(CallContextSpecificCall).getCall()) and ( localFlowStep(mid.getNode(), node, config) and cc = mid.getCallContext() and @@ -3797,7 +3923,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() ) or @@ -3813,7 +3939,7 @@ private module FlowExploration { sc1 = TSummaryCtx1None() and sc2 = TSummaryCtx2None() and mid.getAp() instanceof PartialAccessPathNil and - ap = TPartialNil(getNodeType(node)) and + ap = TPartialNil(getNodeDataFlowType(node)) and config = mid.getConfiguration() or partialPathStoreStep(mid, _, _, node, ap) and @@ -3827,7 +3953,7 @@ private module FlowExploration { sc1 = mid.getSummaryCtx1() and sc2 = mid.getSummaryCtx2() and apConsFwd(ap, tc, ap0, config) and - compatibleTypes(ap.getType(), getNodeType(node)) + compatibleTypes(ap.getType(), getNodeDataFlowType(node)) ) or partialPathIntoCallable(mid, node, _, cc, sc1, sc2, _, ap, config) @@ -3924,7 +4050,7 @@ private module FlowExploration { PartialPathNodeFwd mid, int i, CallContext cc, DataFlowCall call, PartialAccessPath ap, Configuration config ) { - exists(ArgumentNode arg | + exists(ArgNode arg | arg = mid.getNode() and cc = mid.getCallContext() and arg.argumentOf(call, i) and @@ -3943,7 +4069,7 @@ private module FlowExploration { } private predicate partialPathIntoCallable( - PartialPathNodeFwd mid, ParameterNode p, CallContext outercc, CallContextCall innercc, + PartialPathNodeFwd mid, ParamNode p, CallContext outercc, CallContextCall innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2, DataFlowCall call, PartialAccessPath ap, Configuration config ) { @@ -3980,7 +4106,7 @@ private module FlowExploration { DataFlowCall call, PartialPathNodeFwd mid, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config ) { - exists(ParameterNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | + exists(ParamNode p, CallContext innercc, TSummaryCtx1 sc1, TSummaryCtx2 sc2 | partialPathIntoCallable(mid, p, cc, innercc, sc1, sc2, call, _, config) and paramFlowsThroughInPartialPath(kind, innercc, sc1, sc2, ap, config) ) @@ -4037,7 +4163,7 @@ private module FlowExploration { apConsRev(ap, c, ap0, config) ) or - exists(ParameterNode p | + exists(ParamNode p | mid.getNode() = p and viableParamArg(_, p, node) and sc1 = mid.getSummaryCtx1() and @@ -4115,7 +4241,7 @@ private module FlowExploration { int pos, TRevSummaryCtx1Some sc1, TRevSummaryCtx2Some sc2, RevPartialAccessPath ap, Configuration config ) { - exists(PartialPathNodeRev mid, ParameterNode p | + exists(PartialPathNodeRev mid, ParamNode p | mid.getNode() = p and p.isParameterOf(_, pos) and sc1 = mid.getSummaryCtx1() and @@ -4138,7 +4264,7 @@ private module FlowExploration { pragma[nomagic] private predicate revPartialPathThroughCallable( - PartialPathNodeRev mid, ArgumentNode node, RevPartialAccessPath ap, Configuration config + PartialPathNodeRev mid, ArgNode node, RevPartialAccessPath ap, Configuration config ) { exists(DataFlowCall call, int pos | revPartialPathThroughCallable0(call, mid, pos, ap, config) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll index 966c30038cc..462e89ac9ed 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll @@ -35,22 +35,22 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) { * calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly. */ private module LambdaFlow { - private predicate viableParamNonLambda(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParamNonLambda(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallable(call), i) } - private predicate viableParamLambda(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParamLambda(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallableLambda(call, _), i) } - private predicate viableParamArgNonLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + private predicate viableParamArgNonLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParamNonLambda(call, i, p) and arg.argumentOf(call, i) ) } - private predicate viableParamArgLambda(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + private predicate viableParamArgLambda(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParamLambda(call, i, p) and arg.argumentOf(call, i) @@ -118,8 +118,8 @@ private module LambdaFlow { boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and - if node instanceof CastNode or node instanceof ArgumentNode or node instanceof ReturnNode - then compatibleTypes(t, getNodeType(node)) + if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode + then compatibleTypes(t, getNodeDataFlowType(node)) else any() } @@ -129,7 +129,7 @@ private module LambdaFlow { boolean toJump, DataFlowCallOption lastCall ) { lambdaCall(lambdaCall, kind, node) and - t = getNodeType(node) and + t = getNodeDataFlowType(node) and toReturn = false and toJump = false and lastCall = TDataFlowCallNone() @@ -146,7 +146,7 @@ private module LambdaFlow { getNodeEnclosingCallable(node) = getNodeEnclosingCallable(mid) | preservesValue = false and - t = getNodeType(node) + t = getNodeDataFlowType(node) or preservesValue = true and t = t0 @@ -160,7 +160,7 @@ private module LambdaFlow { toJump = true and lastCall = TDataFlowCallNone() | - jumpStep(node, mid) and + jumpStepCached(node, mid) and t = t0 or exists(boolean preservesValue | @@ -168,7 +168,7 @@ private module LambdaFlow { getNodeEnclosingCallable(node) != getNodeEnclosingCallable(mid) | preservesValue = false and - t = getNodeType(node) + t = getNodeDataFlowType(node) or preservesValue = true and t = t0 @@ -176,7 +176,7 @@ private module LambdaFlow { ) or // flow into a callable - exists(ParameterNode p, DataFlowCallOption lastCall0, DataFlowCall call | + exists(ParamNode p, DataFlowCallOption lastCall0, DataFlowCall call | revLambdaFlowIn(lambdaCall, kind, p, t, toJump, lastCall0) and ( if lastCall0 = TDataFlowCallNone() and toJump = false @@ -227,7 +227,7 @@ private module LambdaFlow { pragma[nomagic] predicate revLambdaFlowIn( - DataFlowCall lambdaCall, LambdaCallKind kind, ParameterNode p, DataFlowType t, boolean toJump, + DataFlowCall lambdaCall, LambdaCallKind kind, ParamNode p, DataFlowType t, boolean toJump, DataFlowCallOption lastCall ) { revLambdaFlow(lambdaCall, kind, p, t, false, toJump, lastCall) @@ -242,6 +242,89 @@ private DataFlowCallable viableCallableExt(DataFlowCall call) { cached private module Cached { + /** + * If needed, call this predicate from `DataFlowImplSpecific.qll` in order to + * force a stage-dependency on the `DataFlowImplCommon.qll` stage and therby + * collapsing the two stages. + */ + cached + predicate forceCachingInSameStage() { any() } + + cached + predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() } + + cached + predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) { + c = call.getEnclosingCallable() + } + + cached + predicate nodeDataFlowType(Node n, DataFlowType t) { t = getNodeType(n) } + + cached + predicate jumpStepCached(Node node1, Node node2) { jumpStep(node1, node2) } + + cached + predicate clearsContentCached(Node n, Content c) { clearsContent(n, c) } + + cached + predicate isUnreachableInCallCached(Node n, DataFlowCall call) { isUnreachableInCall(n, call) } + + cached + predicate outNodeExt(Node n) { + n instanceof OutNode + or + n.(PostUpdateNode).getPreUpdateNode() instanceof ArgNode + } + + cached + predicate hiddenNode(Node n) { nodeIsHidden(n) } + + cached + OutNodeExt getAnOutNodeExt(DataFlowCall call, ReturnKindExt k) { + result = getAnOutNode(call, k.(ValueReturnKind).getKind()) + or + exists(ArgNode arg | + result.(PostUpdateNode).getPreUpdateNode() = arg and + arg.argumentOf(call, k.(ParamUpdateReturnKind).getPosition()) + ) + } + + cached + predicate returnNodeExt(Node n, ReturnKindExt k) { + k = TValueReturn(n.(ReturnNode).getKind()) + or + exists(ParamNode p, int pos | + parameterValueFlowsToPreUpdate(p, n) and + p.isParameterOf(_, pos) and + k = TParamUpdate(pos) + ) + } + + cached + predicate castNode(Node n) { n instanceof CastNode } + + cached + predicate castingNode(Node n) { + castNode(n) or + n instanceof ParamNode or + n instanceof OutNodeExt or + // For reads, `x.f`, we want to check that the tracked type after the read (which + // is obtained by popping the head of the access path stack) is compatible with + // the type of `x.f`. + read(_, _, n) + } + + cached + predicate parameterNode(Node n, DataFlowCallable c, int i) { + n.(ParameterNode).isParameterOf(c, i) + } + + cached + predicate argumentNode(Node n, DataFlowCall call, int pos) { + n.(ArgumentNode).argumentOf(call, pos) + } + /** * Gets a viable target for the lambda call `call`. * @@ -261,7 +344,7 @@ private module Cached { * The instance parameter is considered to have index `-1`. */ pragma[nomagic] - private predicate viableParam(DataFlowCall call, int i, ParameterNode p) { + private predicate viableParam(DataFlowCall call, int i, ParamNode p) { p.isParameterOf(viableCallableExt(call), i) } @@ -270,11 +353,11 @@ private module Cached { * dispatch into account. */ cached - predicate viableParamArg(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + predicate viableParamArg(DataFlowCall call, ParamNode p, ArgNode arg) { exists(int i | viableParam(call, i, p) and arg.argumentOf(call, i) and - compatibleTypes(getNodeType(arg), getNodeType(p)) + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(p)) ) } @@ -312,7 +395,7 @@ private module Cached { * `read` indicates whether it is contents of `p` that can flow to `node`. */ pragma[nomagic] - private predicate parameterValueFlowCand(ParameterNode p, Node node, boolean read) { + private predicate parameterValueFlowCand(ParamNode p, Node node, boolean read) { p = node and read = false or @@ -325,30 +408,30 @@ private module Cached { // read exists(Node mid | parameterValueFlowCand(p, mid, false) and - readStep(mid, _, node) and + read(mid, _, node) and read = true ) or // flow through: no prior read - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArgCand(p, arg, false) and argumentValueFlowsThroughCand(arg, node, read) ) or // flow through: no read inside method - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArgCand(p, arg, read) and argumentValueFlowsThroughCand(arg, node, false) ) } pragma[nomagic] - private predicate parameterValueFlowArgCand(ParameterNode p, ArgumentNode arg, boolean read) { + private predicate parameterValueFlowArgCand(ParamNode p, ArgNode arg, boolean read) { parameterValueFlowCand(p, arg, read) } pragma[nomagic] - predicate parameterValueFlowsToPreUpdateCand(ParameterNode p, PostUpdateNode n) { + predicate parameterValueFlowsToPreUpdateCand(ParamNode p, PostUpdateNode n) { parameterValueFlowCand(p, n.getPreUpdateNode(), false) } @@ -360,7 +443,7 @@ private module Cached { * `read` indicates whether it is contents of `p` that can flow to the return * node. */ - predicate parameterValueFlowReturnCand(ParameterNode p, ReturnKind kind, boolean read) { + predicate parameterValueFlowReturnCand(ParamNode p, ReturnKind kind, boolean read) { exists(ReturnNode ret | parameterValueFlowCand(p, ret, read) and kind = ret.getKind() @@ -369,9 +452,9 @@ private module Cached { pragma[nomagic] private predicate argumentValueFlowsThroughCand0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind, boolean read + DataFlowCall call, ArgNode arg, ReturnKind kind, boolean read ) { - exists(ParameterNode param | viableParamArg(call, param, arg) | + exists(ParamNode param | viableParamArg(call, param, arg) | parameterValueFlowReturnCand(param, kind, read) ) } @@ -382,14 +465,14 @@ private module Cached { * * `read` indicates whether it is contents of `arg` that can flow to `out`. */ - predicate argumentValueFlowsThroughCand(ArgumentNode arg, Node out, boolean read) { + predicate argumentValueFlowsThroughCand(ArgNode arg, Node out, boolean read) { exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThroughCand0(call, arg, kind, read) and out = getAnOutNode(call, kind) ) } - predicate cand(ParameterNode p, Node n) { + predicate cand(ParamNode p, Node n) { parameterValueFlowCand(p, n, _) and ( parameterValueFlowReturnCand(p, _, _) @@ -416,21 +499,21 @@ private module Cached { * If a read step was taken, then `read` captures the `Content`, the * container type, and the content type. */ - predicate parameterValueFlow(ParameterNode p, Node node, ReadStepTypesOption read) { + predicate parameterValueFlow(ParamNode p, Node node, ReadStepTypesOption read) { parameterValueFlow0(p, node, read) and if node instanceof CastingNode then // normal flow through read = TReadStepTypesNone() and - compatibleTypes(getNodeType(p), getNodeType(node)) + compatibleTypes(getNodeDataFlowType(p), getNodeDataFlowType(node)) or // getter - compatibleTypes(read.getContentType(), getNodeType(node)) + compatibleTypes(read.getContentType(), getNodeDataFlowType(node)) else any() } pragma[nomagic] - private predicate parameterValueFlow0(ParameterNode p, Node node, ReadStepTypesOption read) { + private predicate parameterValueFlow0(ParamNode p, Node node, ReadStepTypesOption read) { p = node and Cand::cand(p, _) and read = TReadStepTypesNone() @@ -447,7 +530,7 @@ private module Cached { readStepWithTypes(mid, read.getContainerType(), read.getContent(), node, read.getContentType()) and Cand::parameterValueFlowReturnCand(p, _, true) and - compatibleTypes(getNodeType(p), read.getContainerType()) + compatibleTypes(getNodeDataFlowType(p), read.getContainerType()) ) or parameterValueFlow0_0(TReadStepTypesNone(), p, node, read) @@ -455,34 +538,32 @@ private module Cached { pragma[nomagic] private predicate parameterValueFlow0_0( - ReadStepTypesOption mustBeNone, ParameterNode p, Node node, ReadStepTypesOption read + ReadStepTypesOption mustBeNone, ParamNode p, Node node, ReadStepTypesOption read ) { // flow through: no prior read - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArg(p, arg, mustBeNone) and argumentValueFlowsThrough(arg, read, node) ) or // flow through: no read inside method - exists(ArgumentNode arg | + exists(ArgNode arg | parameterValueFlowArg(p, arg, read) and argumentValueFlowsThrough(arg, mustBeNone, node) ) } pragma[nomagic] - private predicate parameterValueFlowArg( - ParameterNode p, ArgumentNode arg, ReadStepTypesOption read - ) { + private predicate parameterValueFlowArg(ParamNode p, ArgNode arg, ReadStepTypesOption read) { parameterValueFlow(p, arg, read) and Cand::argumentValueFlowsThroughCand(arg, _, _) } pragma[nomagic] private predicate argumentValueFlowsThrough0( - DataFlowCall call, ArgumentNode arg, ReturnKind kind, ReadStepTypesOption read + DataFlowCall call, ArgNode arg, ReturnKind kind, ReadStepTypesOption read ) { - exists(ParameterNode param | viableParamArg(call, param, arg) | + exists(ParamNode param | viableParamArg(call, param, arg) | parameterValueFlowReturn(param, kind, read) ) } @@ -496,18 +577,18 @@ private module Cached { * container type, and the content type. */ pragma[nomagic] - predicate argumentValueFlowsThrough(ArgumentNode arg, ReadStepTypesOption read, Node out) { + predicate argumentValueFlowsThrough(ArgNode arg, ReadStepTypesOption read, Node out) { exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThrough0(call, arg, kind, read) and out = getAnOutNode(call, kind) | // normal flow through read = TReadStepTypesNone() and - compatibleTypes(getNodeType(arg), getNodeType(out)) + compatibleTypes(getNodeDataFlowType(arg), getNodeDataFlowType(out)) or // getter - compatibleTypes(getNodeType(arg), read.getContainerType()) and - compatibleTypes(read.getContentType(), getNodeType(out)) + compatibleTypes(getNodeDataFlowType(arg), read.getContainerType()) and + compatibleTypes(read.getContentType(), getNodeDataFlowType(out)) ) } @@ -516,7 +597,7 @@ private module Cached { * value-preserving steps and a single read step, not taking call * contexts into account, thus representing a getter-step. */ - predicate getterStep(ArgumentNode arg, Content c, Node out) { + predicate getterStep(ArgNode arg, Content c, Node out) { argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out) } @@ -529,7 +610,7 @@ private module Cached { * container type, and the content type. */ private predicate parameterValueFlowReturn( - ParameterNode p, ReturnKind kind, ReadStepTypesOption read + ParamNode p, ReturnKind kind, ReadStepTypesOption read ) { exists(ReturnNode ret | parameterValueFlow(p, ret, read) and @@ -553,7 +634,7 @@ private module Cached { private predicate mayBenefitFromCallContextExt(DataFlowCall call, DataFlowCallable callable) { mayBenefitFromCallContext(call, callable) or - callable = call.getEnclosingCallable() and + callEnclosingCallable(call, callable) and exists(viableCallableLambda(call, TDataFlowCallSome(_))) } @@ -611,7 +692,7 @@ private module Cached { mayBenefitFromCallContextExt(call, _) and c = viableCallableExt(call) and ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContextExt(call, ctx)) and - tgts = strictcount(DataFlowCall ctx | viableCallableExt(ctx) = call.getEnclosingCallable()) and + tgts = strictcount(DataFlowCall ctx | callEnclosingCallable(call, viableCallableExt(ctx))) and ctxtgts < tgts ) } @@ -635,8 +716,7 @@ private module Cached { * Holds if `p` can flow to the pre-update node associated with post-update * node `n`, in the same callable, using only value-preserving steps. */ - cached - predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) { + private predicate parameterValueFlowsToPreUpdate(ParamNode p, PostUpdateNode n) { parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone()) } @@ -644,9 +724,9 @@ private module Cached { Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType ) { storeStep(node1, c, node2) and - readStep(_, c, _) and - contentType = getNodeType(node1) and - containerType = getNodeType(node2) + read(_, c, _) and + contentType = getNodeDataFlowType(node1) and + containerType = getNodeDataFlowType(node2) or exists(Node n1, Node n2 | n1 = node1.(PostUpdateNode).getPreUpdateNode() and @@ -654,12 +734,15 @@ private module Cached { | argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1) or - readStep(n2, c, n1) and - contentType = getNodeType(n1) and - containerType = getNodeType(n2) + read(n2, c, n1) and + contentType = getNodeDataFlowType(n1) and + containerType = getNodeDataFlowType(n2) ) } + cached + predicate read(Node node1, Content c, Node node2) { readStep(node1, c, node2) } + /** * Holds if data can flow from `node1` to `node2` via a direct assignment to * `f`. @@ -678,8 +761,9 @@ private module Cached { * are aliases. A typical example is a function returning `this`, implementing a fluent * interface. */ - cached - predicate reverseStepThroughInputOutputAlias(PostUpdateNode fromNode, PostUpdateNode toNode) { + private predicate reverseStepThroughInputOutputAlias( + PostUpdateNode fromNode, PostUpdateNode toNode + ) { exists(Node fromPre, Node toPre | fromPre = fromNode.getPreUpdateNode() and toPre = toNode.getPreUpdateNode() @@ -688,14 +772,20 @@ private module Cached { // Does the language-specific simpleLocalFlowStep already model flow // from function input to output? fromPre = getAnOutNode(c, _) and - toPre.(ArgumentNode).argumentOf(c, _) and - simpleLocalFlowStep(toPre.(ArgumentNode), fromPre) + toPre.(ArgNode).argumentOf(c, _) and + simpleLocalFlowStep(toPre.(ArgNode), fromPre) ) or argumentValueFlowsThrough(toPre, TReadStepTypesNone(), fromPre) ) } + cached + predicate simpleLocalFlowStepExt(Node node1, Node node2) { + simpleLocalFlowStep(node1, node2) or + reverseStepThroughInputOutputAlias(node1, node2) + } + /** * Holds if the call context `call` either improves virtual dispatch in * `callable` or if it allows us to prune unreachable nodes in `callable`. @@ -704,7 +794,7 @@ private module Cached { predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) { reducedViableImplInCallContext(_, callable, call) or - exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCall(n, call)) + exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call)) } cached @@ -726,12 +816,12 @@ private module Cached { cached newtype TLocalFlowCallContext = TAnyLocalCall() or - TSpecificLocalCall(DataFlowCall call) { isUnreachableInCall(_, call) } + TSpecificLocalCall(DataFlowCall call) { isUnreachableInCallCached(_, call) } cached newtype TReturnKindExt = TValueReturn(ReturnKind kind) or - TParamUpdate(int pos) { exists(ParameterNode p | p.isParameterOf(_, pos)) } + TParamUpdate(int pos) { exists(ParamNode p | p.isParameterOf(_, pos)) } cached newtype TBooleanOption = @@ -761,23 +851,15 @@ private module Cached { * A `Node` at which a cast can occur such that the type should be checked. */ class CastingNode extends Node { - CastingNode() { - this instanceof ParameterNode or - this instanceof CastNode or - this instanceof OutNodeExt or - // For reads, `x.f`, we want to check that the tracked type after the read (which - // is obtained by popping the head of the access path stack) is compatible with - // the type of `x.f`. - readStep(_, _, this) - } + CastingNode() { castingNode(this) } } private predicate readStepWithTypes( Node n1, DataFlowType container, Content c, Node n2, DataFlowType content ) { - readStep(n1, c, n2) and - container = getNodeType(n1) and - content = getNodeType(n2) + read(n1, c, n2) and + container = getNodeDataFlowType(n1) and + content = getNodeDataFlowType(n2) } private newtype TReadStepTypesOption = @@ -854,7 +936,7 @@ class CallContextSomeCall extends CallContextCall, TSomeCall { override string toString() { result = "CcSomeCall" } override predicate relevantFor(DataFlowCallable callable) { - exists(ParameterNode p | getNodeEnclosingCallable(p) = callable) + exists(ParamNode p | getNodeEnclosingCallable(p) = callable) } override predicate matchesCall(DataFlowCall call) { any() } @@ -866,7 +948,7 @@ class CallContextReturn extends CallContextNoCall, TReturn { } override predicate relevantFor(DataFlowCallable callable) { - exists(DataFlowCall call | this = TReturn(_, call) and call.getEnclosingCallable() = callable) + exists(DataFlowCall call | this = TReturn(_, call) and callEnclosingCallable(call, callable)) } } @@ -899,7 +981,7 @@ class LocalCallContextSpecificCall extends LocalCallContext, TSpecificLocalCall } private predicate relevantLocalCCtx(DataFlowCall call, DataFlowCallable callable) { - exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCall(n, call)) + exists(Node n | getNodeEnclosingCallable(n) = callable and isUnreachableInCallCached(n, call)) } /** @@ -913,26 +995,37 @@ LocalCallContext getLocalCallContext(CallContext ctx, DataFlowCallable callable) else result instanceof LocalCallContextAny } +/** + * The value of a parameter at function entry, viewed as a node in a data + * flow graph. + */ +class ParamNode extends Node { + ParamNode() { parameterNode(this, _, _) } + + /** + * Holds if this node is the parameter of callable `c` at the specified + * (zero-based) position. + */ + predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) } +} + +/** A data-flow node that represents a call argument. */ +class ArgNode extends Node { + ArgNode() { argumentNode(this, _, _) } + + /** Holds if this argument occurs at the given position in the given call. */ + final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) } +} + /** * A node from which flow can return to the caller. This is either a regular * `ReturnNode` or a `PostUpdateNode` corresponding to the value of a parameter. */ class ReturnNodeExt extends Node { - ReturnNodeExt() { - this instanceof ReturnNode or - parameterValueFlowsToPreUpdate(_, this) - } + ReturnNodeExt() { returnNodeExt(this, _) } /** Gets the kind of this returned value. */ - ReturnKindExt getKind() { - result = TValueReturn(this.(ReturnNode).getKind()) - or - exists(ParameterNode p, int pos | - parameterValueFlowsToPreUpdate(p, this) and - p.isParameterOf(_, pos) and - result = TParamUpdate(pos) - ) - } + ReturnKindExt getKind() { returnNodeExt(this, result) } } /** @@ -940,11 +1033,7 @@ class ReturnNodeExt extends Node { * or a post-update node associated with a call argument. */ class OutNodeExt extends Node { - OutNodeExt() { - this instanceof OutNode - or - this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode - } + OutNodeExt() { outNodeExt(this) } } /** @@ -957,7 +1046,7 @@ abstract class ReturnKindExt extends TReturnKindExt { abstract string toString(); /** Gets a node corresponding to data flow out of `call`. */ - abstract OutNodeExt getAnOutNode(DataFlowCall call); + final OutNodeExt getAnOutNode(DataFlowCall call) { result = getAnOutNodeExt(call, this) } } class ValueReturnKind extends ReturnKindExt, TValueReturn { @@ -968,10 +1057,6 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn { ReturnKind getKind() { result = kind } override string toString() { result = kind.toString() } - - override OutNodeExt getAnOutNode(DataFlowCall call) { - result = getAnOutNode(call, this.getKind()) - } } class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { @@ -982,13 +1067,6 @@ class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate { int getPosition() { result = pos } override string toString() { result = "param update " + pos } - - override OutNodeExt getAnOutNode(DataFlowCall call) { - exists(ArgumentNode arg | - result.(PostUpdateNode).getPreUpdateNode() = arg and - arg.argumentOf(call, this.getPosition()) - ) - } } /** A callable tagged with a relevant return kind. */ @@ -1015,10 +1093,13 @@ class ReturnPosition extends TReturnPosition0 { */ pragma[inline] DataFlowCallable getNodeEnclosingCallable(Node n) { - exists(Node n0 | - pragma[only_bind_into](n0) = n and - pragma[only_bind_into](result) = n0.getEnclosingCallable() - ) + nodeEnclosingCallable(pragma[only_bind_out](n), pragma[only_bind_into](result)) +} + +/** Gets the type of `n` used for type pruning. */ +pragma[inline] +DataFlowType getNodeDataFlowType(Node n) { + nodeDataFlowType(pragma[only_bind_out](n), pragma[only_bind_into](result)) } pragma[noinline] @@ -1042,7 +1123,7 @@ predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall cc instanceof CallContextAny and callable = viableCallableExt(call) or exists(DataFlowCallable c0, DataFlowCall call0 | - call0.getEnclosingCallable() = callable and + callEnclosingCallable(call0, callable) and cc = TReturn(c0, call0) and c0 = prunedViableImplInCallContextReverse(call0, call) ) @@ -1063,8 +1144,6 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { result = viableCallableExt(call) and cc instanceof CallContextReturn } -predicate read = readStep/3; - /** An optional Boolean value. */ class BooleanOption extends TBooleanOption { string toString() { @@ -1116,7 +1195,7 @@ abstract class AccessPathFront extends TAccessPathFront { TypedContent getHead() { this = TFrontHead(result) } - predicate isClearedAt(Node n) { clearsContent(n, getHead().getContent()) } + predicate isClearedAt(Node n) { clearsContentCached(n, getHead().getContent()) } } class AccessPathFrontNil extends AccessPathFront, TFrontNil { diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll index 552849c2c34..c47efa287ad 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowNodes.qll @@ -4,71 +4,48 @@ private import semmle.code.java.dataflow.FlowSummary private import semmle.code.java.dataflow.TypeFlow private import DataFlowPrivate private import FlowSummaryImpl as FlowSummaryImpl +private import DataFlowImplCommon as DataFlowImplCommon cached -private module Cached { - cached - newtype TNode = - TExprNode(Expr e) { - not e.getType() instanceof VoidType and - not e.getParent*() instanceof Annotation - } or - TExplicitParameterNode(Parameter p) { - exists(p.getCallable().getBody()) or p.getCallable() instanceof SummarizedCallable - } or - TImplicitVarargsArray(Call c) { - c.getCallee().isVarargs() and - not exists(Argument arg | arg.getCall() = c and arg.isExplicitVarargsArray()) - } or - TInstanceParameterNode(Callable c) { - (exists(c.getBody()) or c instanceof SummarizedCallable) and - not c.isStatic() - } or - TImplicitInstanceAccess(InstanceAccessExt ia) { not ia.isExplicit(_) } or - TMallocNode(ClassInstanceExpr cie) or - TExplicitExprPostUpdate(Expr e) { - explicitInstanceArgument(_, e) - or - e instanceof Argument and not e.getType() instanceof ImmutableType - or - exists(FieldAccess fa | fa.getField() instanceof InstanceField and e = fa.getQualifier()) - or - exists(ArrayAccess aa | e = aa.getArray()) - } or - TImplicitExprPostUpdate(InstanceAccessExt ia) { - implicitInstanceArgument(_, ia) - or - exists(FieldAccess fa | - fa.getField() instanceof InstanceField and ia.isImplicitFieldQualifier(fa) - ) - } or - TSummaryInternalNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) { - FlowSummaryImpl::Private::summaryNodeRange(c, state) - } - - cached - predicate summaryOutNodeCached(DataFlowCall c, Node out) { - FlowSummaryImpl::Private::summaryOutNode(c, out, _) +newtype TNode = + TExprNode(Expr e) { + DataFlowImplCommon::forceCachingInSameStage() and + not e.getType() instanceof VoidType and + not e.getParent*() instanceof Annotation + } or + TExplicitParameterNode(Parameter p) { + exists(p.getCallable().getBody()) or p.getCallable() instanceof SummarizedCallable + } or + TImplicitVarargsArray(Call c) { + c.getCallee().isVarargs() and + not exists(Argument arg | arg.getCall() = c and arg.isExplicitVarargsArray()) + } or + TInstanceParameterNode(Callable c) { + (exists(c.getBody()) or c instanceof SummarizedCallable) and + not c.isStatic() + } or + TImplicitInstanceAccess(InstanceAccessExt ia) { not ia.isExplicit(_) } or + TMallocNode(ClassInstanceExpr cie) or + TExplicitExprPostUpdate(Expr e) { + explicitInstanceArgument(_, e) + or + e instanceof Argument and not e.getType() instanceof ImmutableType + or + exists(FieldAccess fa | fa.getField() instanceof InstanceField and e = fa.getQualifier()) + or + exists(ArrayAccess aa | e = aa.getArray()) + } or + TImplicitExprPostUpdate(InstanceAccessExt ia) { + implicitInstanceArgument(_, ia) + or + exists(FieldAccess fa | + fa.getField() instanceof InstanceField and ia.isImplicitFieldQualifier(fa) + ) + } or + TSummaryInternalNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) { + FlowSummaryImpl::Private::summaryNodeRange(c, state) } - cached - predicate summaryArgumentNodeCached(DataFlowCall c, Node arg, int i) { - FlowSummaryImpl::Private::summaryArgumentNode(c, arg, i) - } - - cached - predicate summaryPostUpdateNodeCached(Node post, ParameterNode pre) { - FlowSummaryImpl::Private::summaryPostUpdateNode(post, pre) - } - - cached - predicate summaryReturnNodeCached(Node ret) { - FlowSummaryImpl::Private::summaryReturnNode(ret, _) - } -} - -private import Cached - private predicate explicitInstanceArgument(Call call, Expr instarg) { call instanceof MethodAccess and instarg = call.getQualifier() and @@ -404,13 +381,15 @@ module Private { override string toString() { result = "[summary] " + state + " in " + c } /** Holds if this summary node is the `i`th argument of `call`. */ - predicate isArgumentOf(DataFlowCall call, int i) { summaryArgumentNodeCached(call, this, i) } + predicate isArgumentOf(DataFlowCall call, int i) { + FlowSummaryImpl::Private::summaryArgumentNode(call, this, i) + } /** Holds if this summary node is a return node. */ - predicate isReturn() { summaryReturnNodeCached(this) } + predicate isReturn() { FlowSummaryImpl::Private::summaryReturnNode(this, _) } /** Holds if this summary node is an out node for `call`. */ - predicate isOut(DataFlowCall call) { summaryOutNodeCached(call, this) } + predicate isOut(DataFlowCall call) { FlowSummaryImpl::Private::summaryOutNode(call, this, _) } } SummaryNode getSummaryNode(SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state) { @@ -439,7 +418,7 @@ private class MallocNode extends Node, TMallocNode { private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode { private Node pre; - SummaryPostUpdateNode() { summaryPostUpdateNodeCached(this, pre) } + SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) } override Node getPreUpdateNode() { result = pre } } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index 2e22b7e1963..c20fa87af6c 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -284,7 +284,6 @@ private class ConstantBooleanArgumentNode extends ArgumentNode, ExprNode { /** * Holds if the node `n` is unreachable when the call context is `call`. */ -cached predicate isUnreachableInCall(Node n, DataFlowCall call) { exists( ExplicitParameterNode paramNode, ConstantBooleanArgumentNode arg, SsaImplicitInit param, @@ -297,7 +296,9 @@ predicate isUnreachableInCall(Node n, DataFlowCall call) { // which is used in a guard param.getAUse() = guard and // which controls `n` with the opposite value of `arg` - guard.controls(n.asExpr().getBasicBlock(), arg.getBooleanValue().booleanNot()) + guard + .controls(n.asExpr().getBasicBlock(), + pragma[only_bind_out](arg.getBooleanValue()).booleanNot()) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll index 2ad09574015..e25ab24cfbb 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -11,6 +11,7 @@ private import semmle.code.java.dataflow.FlowSteps private import semmle.code.java.dataflow.FlowSummary import semmle.code.java.dataflow.InstanceAccess private import FlowSummaryImpl as FlowSummaryImpl +private import TaintTrackingUtil as TaintTrackingUtil import DataFlowNodes::Public /** Holds if `n` is an access to an unqualified `this` at `cfgnode`. */ @@ -112,6 +113,7 @@ predicate localFlowStep(Node node1, Node node2) { */ cached predicate simpleLocalFlowStep(Node node1, Node node2) { + TaintTrackingUtil::forceCachingInSameStage() and // Variable flow steps through adjacent def-use and use-use pairs. exists(SsaExplicitUpdate upd | upd.getDefiningExpr().(VariableAssign).getSource() = node1.asExpr() or diff --git a/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll index bdf5498d8c6..da6f89071ef 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll @@ -580,88 +580,112 @@ module Private { * summaries into a `SummarizedCallable`s. */ module External { - /** - * Provides a means of translating an externally (e.g., CSV) defined flow - * summary into a `SummarizedCallable`. - */ - abstract class ExternalSummaryCompilation extends string { - bindingset[this] - ExternalSummaryCompilation() { any() } + /** Holds if the `n`th component of specification `s` is `c`. */ + predicate specSplit(string s, string c, int n) { relevantSpec(s) and s.splitAt(" of ", n) = c } - /** Holds if this flow summary is for callable `c`. */ - abstract predicate callable(DataFlowCallable c, boolean preservesValue); + /** Holds if specification `s` has length `len`. */ + predicate specLength(string s, int len) { len = 1 + max(int n | specSplit(s, _, n)) } - /** Holds if the `i`th input component is `c`. */ - abstract predicate input(int i, SummaryComponent c); - - /** Holds if the `i`th output component is `c`. */ - abstract predicate output(int i, SummaryComponent c); - - /** - * Holds if the input components starting from index `i` translate into `suffix`. - */ - final predicate translateInput(int i, SummaryComponentStack suffix) { - exists(SummaryComponent comp | this.input(i, comp) | - i = max(int j | this.input(j, _)) and - suffix = TSingletonSummaryComponentStack(comp) - or - exists(TSummaryComponent head, SummaryComponentStack tail | - this.translateInputCons(i, head, tail) and - suffix = TConsSummaryComponentStack(head, tail) - ) - ) - } - - final predicate translateInputCons(int i, SummaryComponent head, SummaryComponentStack tail) { - this.input(i, head) and - this.translateInput(i + 1, tail) - } - - /** - * Holds if the output components starting from index `i` translate into `suffix`. - */ - predicate translateOutput(int i, SummaryComponentStack suffix) { - exists(SummaryComponent comp | this.output(i, comp) | - i = max(int j | this.output(j, _)) and - suffix = TSingletonSummaryComponentStack(comp) - or - exists(TSummaryComponent head, SummaryComponentStack tail | - this.translateOutputCons(i, head, tail) and - suffix = TConsSummaryComponentStack(head, tail) - ) - ) - } - - predicate translateOutputCons(int i, SummaryComponent head, SummaryComponentStack tail) { - this.output(i, head) and - this.translateOutput(i + 1, tail) - } + /** Gets the last component of specification `s`. */ + string specLast(string s) { + exists(int len | + specLength(s, len) and + specSplit(s, result, len - 1) + ) } - private class ExternalRequiredSummaryComponentStack extends RequiredSummaryComponentStack { - private SummaryComponent head; - - ExternalRequiredSummaryComponentStack() { - any(ExternalSummaryCompilation s).translateInputCons(_, head, this) or - any(ExternalSummaryCompilation s).translateOutputCons(_, head, this) - } - - override predicate required(SummaryComponent c) { c = head } + /** Holds if specification component `c` parses as parameter `n`. */ + predicate parseParam(string c, int n) { + specSplit(_, c, _) and + ( + c.regexpCapture("Parameter\\[([-0-9]+)\\]", 1).toInt() = n + or + exists(int n1, int n2 | + c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and + c.regexpCapture("Parameter\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and + n = [n1 .. n2] + ) + ) } - class ExternalSummarizedCallableAdaptor extends SummarizedCallable { - ExternalSummarizedCallableAdaptor() { any(ExternalSummaryCompilation s).callable(this, _) } + /** Holds if specification component `c` parses as argument `n`. */ + predicate parseArg(string c, int n) { + specSplit(_, c, _) and + ( + c.regexpCapture("Argument\\[([-0-9]+)\\]", 1).toInt() = n + or + exists(int n1, int n2 | + c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 1).toInt() = n1 and + c.regexpCapture("Argument\\[([-0-9]+)\\.\\.([0-9]+)\\]", 2).toInt() = n2 and + n = [n1 .. n2] + ) + ) + } + + private SummaryComponent interpretComponent(string c) { + specSplit(_, c, _) and + ( + exists(int pos | parseArg(c, pos) and result = SummaryComponent::argument(pos)) + or + exists(int pos | parseParam(c, pos) and result = SummaryComponent::parameter(pos)) + or + result = interpretComponentSpecific(c) + ) + } + + private predicate interpretSpec(string spec, int idx, SummaryComponentStack stack) { + exists(string c | + relevantSpec(spec) and + specLength(spec, idx + 1) and + specSplit(spec, c, idx) and + stack = SummaryComponentStack::singleton(interpretComponent(c)) + ) + or + exists(SummaryComponent head, SummaryComponentStack tail | + interpretSpec(spec, idx, head, tail) and + stack = SummaryComponentStack::push(head, tail) + ) + } + + private predicate interpretSpec( + string output, int idx, SummaryComponent head, SummaryComponentStack tail + ) { + exists(string c | + interpretSpec(output, idx + 1, tail) and + specSplit(output, c, idx) and + head = interpretComponent(c) + ) + } + + private class MkStack extends RequiredSummaryComponentStack { + MkStack() { interpretSpec(_, _, _, this) } + + override predicate required(SummaryComponent c) { interpretSpec(_, _, c, this) } + } + + private class SummarizedCallableExternal extends SummarizedCallable { + SummarizedCallableExternal() { externalSummary(this, _, _, _) } override predicate propagatesFlow( SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue ) { - exists(ExternalSummaryCompilation s | - s.callable(this, preservesValue) and - s.translateInput(0, input) and - s.translateOutput(0, output) + exists(string inSpec, string outSpec, string kind | + externalSummary(this, inSpec, outSpec, kind) and + interpretSpec(inSpec, 0, input) and + interpretSpec(outSpec, 0, output) + | + kind = "value" and preservesValue = true + or + kind = "taint" and preservesValue = false ) } } + + /** Holds if component `c` of specification `spec` cannot be parsed. */ + predicate invalidSpecComponent(string spec, string c) { + specSplit(spec, c, _) and + not exists(interpretComponent(c)) + } } /** Provides a query predicate for outputting a set of relevant flow summaries. */ diff --git a/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll b/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll index dae0571f0fa..327ddfb50dd 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll @@ -7,6 +7,7 @@ private import DataFlowPrivate private import DataFlowUtil private import FlowSummaryImpl::Private private import FlowSummaryImpl::Public +private import semmle.code.java.dataflow.ExternalFlow private module FlowSummaries { private import semmle.code.java.dataflow.FlowSummary as F @@ -49,3 +50,28 @@ DataFlowType getCallbackParameterType(DataFlowType t, int i) { none() } * callback of type `t`. */ DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { none() } + +/** Holds if `spec` is a relevant external specification. */ +predicate relevantSpec(string spec) { spec = inOutSpec() } + +/** + * Holds if an external flow summary exists for `c` with input specification + * `input`, output specification `output`, and kind `kind`. + */ +predicate externalSummary(DataFlowCallable c, string input, string output, string kind) { + summaryElement(c, input, output, kind) +} + +/** Gets the summary component for specification component `c`, if any. */ +bindingset[c] +SummaryComponent interpretComponentSpecific(string c) { + c = "ReturnValue" and result = SummaryComponent::return(_) + or + c = "ArrayElement" and result = SummaryComponent::content(any(ArrayContent c0)) + or + c = "Element" and result = SummaryComponent::content(any(CollectionContent c0)) + or + c = "MapKey" and result = SummaryComponent::content(any(MapKeyContent c0)) + or + c = "MapValue" and result = SummaryComponent::content(any(MapValueContent c0)) +} diff --git a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll index 262a3babc0b..4d88ef246d7 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll @@ -30,55 +30,69 @@ predicate localExprTaint(Expr src, Expr sink) { localTaint(DataFlow::exprNode(src), DataFlow::exprNode(sink)) } -/** - * Holds if taint can flow in one local step from `src` to `sink`. - */ -predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) { - DataFlow::localFlowStep(src, sink) or - localAdditionalTaintStep(src, sink) or - // Simple flow through library code is included in the exposed local - // step relation, even though flow is technically inter-procedural - FlowSummaryImpl::Private::Steps::summaryThroughStep(src, sink, false) +cached +private module Cached { + private import DataFlowImplCommon as DataFlowImplCommon + + cached + predicate forceCachingInSameStage() { DataFlowImplCommon::forceCachingInSameStage() } + + /** + * Holds if taint can flow in one local step from `src` to `sink`. + */ + cached + predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) { + DataFlow::localFlowStep(src, sink) or + localAdditionalTaintStep(src, sink) or + // Simple flow through library code is included in the exposed local + // step relation, even though flow is technically inter-procedural + FlowSummaryImpl::Private::Steps::summaryThroughStep(src, sink, false) + } + + /** + * Holds if taint can flow in one local step from `src` to `sink` excluding + * local data flow steps. That is, `src` and `sink` are likely to represent + * different objects. + */ + cached + predicate localAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) { + localAdditionalTaintExprStep(src.asExpr(), sink.asExpr()) + or + localAdditionalTaintUpdateStep(src.asExpr(), + sink.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()) + or + exists(Argument arg | + src.asExpr() = arg and + arg.isVararg() and + sink.(DataFlow::ImplicitVarargsArray).getCall() = arg.getCall() + ) + or + FlowSummaryImpl::Private::Steps::summaryLocalStep(src, sink, false) + } + + /** + * Holds if the additional step from `src` to `sink` should be included in all + * global taint flow configurations. + */ + cached + predicate defaultAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) { + localAdditionalTaintStep(src, sink) or + any(AdditionalTaintStep a).step(src, sink) + } + + /** + * Holds if `node` should be a sanitizer in all global taint flow configurations + * but not in local taint. + */ + cached + predicate defaultTaintSanitizer(DataFlow::Node node) { + // Ignore paths through test code. + node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or + node.asExpr() instanceof ValidatedVariableAccess + } } -/** - * Holds if taint can flow in one local step from `src` to `sink` excluding - * local data flow steps. That is, `src` and `sink` are likely to represent - * different objects. - */ -predicate localAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) { - localAdditionalTaintExprStep(src.asExpr(), sink.asExpr()) - or - localAdditionalTaintUpdateStep(src.asExpr(), - sink.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()) - or - exists(Argument arg | - src.asExpr() = arg and - arg.isVararg() and - sink.(DataFlow::ImplicitVarargsArray).getCall() = arg.getCall() - ) - or - FlowSummaryImpl::Private::Steps::summaryLocalStep(src, sink, false) -} - -/** - * Holds if the additional step from `src` to `sink` should be included in all - * global taint flow configurations. - */ -predicate defaultAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) { - localAdditionalTaintStep(src, sink) or - any(AdditionalTaintStep a).step(src, sink) -} - -/** - * Holds if `node` should be a sanitizer in all global taint flow configurations - * but not in local taint. - */ -predicate defaultTaintSanitizer(DataFlow::Node node) { - // Ignore paths through test code. - node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or - node.asExpr() instanceof ValidatedVariableAccess -} +import Cached /** * Holds if taint can flow in one local step from `src` to `sink` excluding diff --git a/java/ql/src/semmle/code/java/dispatch/VirtualDispatch.qll b/java/ql/src/semmle/code/java/dispatch/VirtualDispatch.qll index bb6884ced06..a26ff92b704 100644 --- a/java/ql/src/semmle/code/java/dispatch/VirtualDispatch.qll +++ b/java/ql/src/semmle/code/java/dispatch/VirtualDispatch.qll @@ -39,9 +39,12 @@ Callable viableCallable(Call c) { c instanceof ConstructorCall and result = c.getCallee().getSourceDeclaration() } -/** A method that is the target of a call. */ -class CalledMethod extends Method { - CalledMethod() { exists(MethodAccess ma | ma.getMethod() = this) } +/** The source declaration of a method that is the target of a virtual call. */ +class VirtCalledSrcMethod extends SrcMethod { + pragma[nomagic] + VirtCalledSrcMethod() { + exists(VirtualMethodAccess ma | ma.getMethod().getSourceDeclaration() = this) + } } cached @@ -73,7 +76,7 @@ private module Dispatch { ( exists(Method def, RefType t, boolean exact | qualType(ma, t, exact) and - def = ma.getMethod() + def = ma.getMethod().getSourceDeclaration() | exact = true and result = exactMethodImpl(def, t.getSourceDeclaration()) or @@ -185,8 +188,8 @@ private module Dispatch { not result.isAbstract() and if source instanceof VirtualMethodAccess then - exists(CalledMethod def, RefType t, boolean exact | - source.getMethod() = def and + exists(VirtCalledSrcMethod def, RefType t, boolean exact | + source.getMethod().getSourceDeclaration() = def and hasQualifierType(source, t, exact) | exact = true and result = exactMethodImpl(def, t.getSourceDeclaration()) @@ -301,14 +304,14 @@ private module Dispatch { /** Gets the implementation of `top` present on a value of precisely type `t`. */ cached - Method exactMethodImpl(CalledMethod top, SrcRefType t) { + Method exactMethodImpl(VirtCalledSrcMethod top, SrcRefType t) { hasSrcMethod(t, result) and - top.getAPossibleImplementation() = result + top.getAPossibleImplementationOfSrcMethod() = result } /** Gets the implementations of `top` present on viable subtypes of `t`. */ cached - Method viableMethodImpl(CalledMethod top, SrcRefType tsrc, RefType t) { + Method viableMethodImpl(VirtCalledSrcMethod top, SrcRefType tsrc, RefType t) { exists(SrcRefType sub | result = exactMethodImpl(top, sub) and tsrc = t.getSourceDeclaration() and diff --git a/java/ql/src/semmle/code/java/frameworks/Kryo.qll b/java/ql/src/semmle/code/java/frameworks/Kryo.qll index 049be97ea9c..317148d56b5 100644 --- a/java/ql/src/semmle/code/java/frameworks/Kryo.qll +++ b/java/ql/src/semmle/code/java/frameworks/Kryo.qll @@ -3,19 +3,60 @@ */ import java +private import semmle.code.java.dataflow.DataFlow +private import semmle.code.java.dataflow.FlowSteps /** * The type `com.esotericsoftware.kryo.Kryo`. */ class Kryo extends RefType { - Kryo() { this.hasQualifiedName("com.esotericsoftware.kryo", "Kryo") } + Kryo() { + hasQualifiedName("com.esotericsoftware.kryo", "Kryo") or + hasQualifiedName("com.esotericsoftware.kryo5", "Kryo") + } } /** * A Kryo input stream. */ class KryoInput extends RefType { - KryoInput() { this.hasQualifiedName("com.esotericsoftware.kryo.io", "Input") } + KryoInput() { + hasQualifiedName("com.esotericsoftware.kryo.io", "Input") or + hasQualifiedName("com.esotericsoftware.kryo5.io", "Input") + } +} + +/** + * A Kryo pool. + */ +class KryoPool extends RefType { + KryoPool() { + hasQualifiedName("com.esotericsoftware.kryo.pool", "KryoPool") or + hasQualifiedName("com.esotericsoftware.kryo5.pool", "KryoPool") + } +} + +/** + * A Kryo pool builder. + */ +class KryoPoolBuilder extends RefType { + KryoPoolBuilder() { + hasQualifiedName("com.esotericsoftware.kryo.pool", "KryoPool$Builder") or + hasQualifiedName("com.esotericsoftware.kryo5.pool", "KryoPool$Builder") + } +} + +/** + * A Kryo pool builder method used in a fluent API call chain. + */ +class KryoPoolBuilderMethod extends Method { + KryoPoolBuilderMethod() { + getDeclaringType() instanceof KryoPoolBuilder and + ( + getReturnType() instanceof KryoPoolBuilder or + getReturnType() instanceof KryoPool + ) + } } /** @@ -45,3 +86,13 @@ class KryoEnableWhiteListing extends MethodAccess { ) } } + +/** + * A KryoPool method that uses a Kryo instance. + */ +class KryoPoolRunMethod extends Method { + KryoPoolRunMethod() { + getDeclaringType() instanceof KryoPool and + hasName("run") + } +} diff --git a/java/ql/src/semmle/code/java/frameworks/guava/Base.qll b/java/ql/src/semmle/code/java/frameworks/guava/Base.qll index 194d723caf5..04a97f79f53 100644 --- a/java/ql/src/semmle/code/java/frameworks/guava/Base.qll +++ b/java/ql/src/semmle/code/java/frameworks/guava/Base.qll @@ -35,7 +35,8 @@ private class GuavaBaseCsv extends SummaryModelCsv { "com.google.common.base;Splitter;false;splitToList;(CharSequence);;Argument[0];ReturnValue;taint", "com.google.common.base;Splitter;false;splitToStream;(CharSequence);;Argument[0];ReturnValue;taint", "com.google.common.base;Splitter$MapSplitter;false;split;(CharSequence);;Argument[0];ReturnValue;taint", - "com.google.common.base;Preconditions;false;checkNotNull;;;Argument[0];ReturnValue;value" + "com.google.common.base;Preconditions;false;checkNotNull;;;Argument[0];ReturnValue;value", + "com.google.common.base;MoreObjects;false;firstNonNull;;;Argument[0..1];ReturnValue;value" ] } } diff --git a/java/ql/src/semmle/code/java/frameworks/jackson/JacksonSerializability.qll b/java/ql/src/semmle/code/java/frameworks/jackson/JacksonSerializability.qll index 3356e31d965..6cda84512a0 100644 --- a/java/ql/src/semmle/code/java/frameworks/jackson/JacksonSerializability.qll +++ b/java/ql/src/semmle/code/java/frameworks/jackson/JacksonSerializability.qll @@ -9,6 +9,7 @@ import semmle.code.java.Reflection import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.DataFlow5 import semmle.code.java.dataflow.FlowSteps +private import semmle.code.java.dataflow.ExternalFlow /** * A `@com.fasterxml.jackson.annotation.JsonIgnore` annoation. @@ -28,7 +29,7 @@ abstract class JacksonSerializableType extends Type { } * A method used for serializing objects using Jackson. The final parameter is the object to be * serialized. */ -library class JacksonWriteValueMethod extends Method, TaintPreservingCallable { +private class JacksonWriteValueMethod extends Method, TaintPreservingCallable { JacksonWriteValueMethod() { ( getDeclaringType().hasQualifiedName("com.fasterxml.jackson.databind", "ObjectWriter") or @@ -50,8 +51,20 @@ library class JacksonWriteValueMethod extends Method, TaintPreservingCallable { } } +private class JacksonReadValueMethod extends Method, TaintPreservingCallable { + JacksonReadValueMethod() { + ( + getDeclaringType().hasQualifiedName("com.fasterxml.jackson.databind", "ObjectReader") or + getDeclaringType().hasQualifiedName("com.fasterxml.jackson.databind", "ObjectMapper") + ) and + hasName(["readValue", "readValues"]) + } + + override predicate returnsTaintFrom(int arg) { arg = 0 } +} + /** A type whose values are explicitly serialized in a call to a Jackson method. */ -library class ExplicitlyWrittenJacksonSerializableType extends JacksonSerializableType { +private class ExplicitlyWrittenJacksonSerializableType extends JacksonSerializableType { ExplicitlyWrittenJacksonSerializableType() { exists(MethodAccess ma | // A call to a Jackson write method... @@ -63,7 +76,7 @@ library class ExplicitlyWrittenJacksonSerializableType extends JacksonSerializab } /** A type used in a `JacksonSerializableField` declaration. */ -library class FieldReferencedJacksonSerializableType extends JacksonSerializableType { +private class FieldReferencedJacksonSerializableType extends JacksonSerializableType { FieldReferencedJacksonSerializableType() { exists(JacksonSerializableField f | usesType(f.getType(), this)) } @@ -96,17 +109,24 @@ private class TypeLiteralToJacksonDatabindFlowConfiguration extends DataFlow5::C } /** A type whose values are explicitly deserialized in a call to a Jackson method. */ -library class ExplicitlyReadJacksonDeserializableType extends JacksonDeserializableType { +private class ExplicitlyReadJacksonDeserializableType extends JacksonDeserializableType { ExplicitlyReadJacksonDeserializableType() { exists(TypeLiteralToJacksonDatabindFlowConfiguration conf | usesType(conf.getSourceWithFlowToJacksonDatabind().getTypeName().getType(), this) ) + or + exists(MethodAccess ma | + // A call to a Jackson read method... + ma.getMethod() instanceof JacksonReadValueMethod and + // ...where `this` is used in the final argument, indicating that this type will be deserialized. + usesType(ma.getArgument(ma.getNumArgument() - 1).getType(), this) + ) } } /** A type used in a `JacksonDeserializableField` declaration. */ -library class FieldReferencedJacksonDeSerializableType extends JacksonDeserializableType { - FieldReferencedJacksonDeSerializableType() { +private class FieldReferencedJacksonDeserializableType extends JacksonDeserializableType { + FieldReferencedJacksonDeserializableType() { exists(JacksonDeserializableField f | usesType(f.getType(), this)) } } @@ -135,6 +155,21 @@ class JacksonDeserializableField extends DeserializableField { } } +/** A call to a field that may be deserialized using the Jackson JSON framework. */ +private class JacksonDeserializableFieldAccess extends FieldAccess { + JacksonDeserializableFieldAccess() { getField() instanceof JacksonDeserializableField } +} + +/** + * When an object is deserialized by the Jackson JSON framework using a tainted input source, + * the fields that the framework deserialized are themselves tainted input data. + */ +private class JacksonDeserializedTaintStep extends AdditionalTaintStep { + override predicate step(DataFlow::Node node1, DataFlow::Node node2) { + DataFlow::getFieldQualifier(node2.asExpr().(JacksonDeserializableFieldAccess)) = node1 + } +} + /** * A call to the `addMixInAnnotations` or `addMixIn` Jackson method. * @@ -239,3 +274,13 @@ class JacksonMixedInCallable extends Callable { ) } } + +private class JacksonModel extends SummaryModelCsv { + override predicate row(string row) { + row = + [ + "com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;Argument[0];ReturnValue;taint", + "com.fasterxml.jackson.databind;ObjectMapper;true;convertValue;;;Argument[0];ReturnValue;taint" + ] + } +} diff --git a/java/ql/src/semmle/code/java/security/InformationLeak.qll b/java/ql/src/semmle/code/java/security/InformationLeak.qll new file mode 100644 index 00000000000..f68ddd5b121 --- /dev/null +++ b/java/ql/src/semmle/code/java/security/InformationLeak.qll @@ -0,0 +1,27 @@ +/** Provides classes to reason about System Information Leak vulnerabilities. */ + +import java +import semmle.code.java.dataflow.DataFlow +import semmle.code.java.dataflow.ExternalFlow +import semmle.code.java.security.XSS + +/** CSV sink models representing methods not susceptible to XSS but outputing to an HTTP response body. */ +private class DefaultInformationLeakSinkModel extends SinkModelCsv { + override predicate row(string row) { + row = + [ + "javax.servlet.http;HttpServletResponse;false;sendError;(int,String);;Argument[1];information-leak" + ] + } +} + +/** A sink that represent a method that outputs data to an HTTP response. */ +abstract class InformationLeakSink extends DataFlow::Node { } + +/** A default sink representing methods outputing data to an HTTP response. */ +private class DefaultInformationLeakSink extends InformationLeakSink { + DefaultInformationLeakSink() { + sinkNode(this, "information-leak") or + this instanceof XssSink + } +} diff --git a/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll b/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll index ab809f07d6d..a1561c0e6cd 100644 --- a/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll +++ b/java/ql/src/semmle/code/java/security/UnsafeDeserialization.qll @@ -48,6 +48,65 @@ class SafeKryo extends DataFlow2::Configuration { ma.getMethod() instanceof KryoReadObjectMethod ) } + + override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + stepKryoPoolBuilderFactoryArgToConstructor(node1, node2) or + stepKryoPoolRunMethodAccessQualifierToFunctionalArgument(node1, node2) or + stepKryoPoolBuilderChainMethod(node1, node2) or + stepKryoPoolBorrowMethod(node1, node2) + } + + /** + * Holds when a functional expression is used to create a `KryoPool.Builder`. + * Eg. `new KryoPool.Builder(() -> new Kryo())` + */ + private predicate stepKryoPoolBuilderFactoryArgToConstructor( + DataFlow::Node node1, DataFlow::Node node2 + ) { + exists(ConstructorCall cc, FunctionalExpr fe | + cc.getConstructedType() instanceof KryoPoolBuilder and + fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and + node2.asExpr() = cc and + cc.getArgument(0) = fe + ) + } + + /** + * Holds when a `KryoPool.run` is called to use a `Kryo` instance. + * Eg. `pool.run(kryo -> ...)` + */ + private predicate stepKryoPoolRunMethodAccessQualifierToFunctionalArgument( + DataFlow::Node node1, DataFlow::Node node2 + ) { + exists(MethodAccess ma | + ma.getMethod() instanceof KryoPoolRunMethod and + node1.asExpr() = ma.getQualifier() and + ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter() + ) + } + + /** + * Holds when a `KryoPool.Builder` method is called fluently. + */ + private predicate stepKryoPoolBuilderChainMethod(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma | + ma.getMethod() instanceof KryoPoolBuilderMethod and + ma = node2.asExpr() and + ma.getQualifier() = node1.asExpr() + ) + } + + /** + * Holds when a `KryoPool.borrow` method is called. + */ + private predicate stepKryoPoolBorrowMethod(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma | + ma.getMethod() = + any(Method m | m.getDeclaringType() instanceof KryoPool and m.hasName("borrow")) and + node1.asExpr() = ma.getQualifier() and + node2.asExpr() = ma + ) + } } predicate unsafeDeserialization(MethodAccess ma, Expr sink) { diff --git a/java/ql/src/semmle/code/java/security/XSS.qll b/java/ql/src/semmle/code/java/security/XSS.qll index 486e8053953..14f10cad9c8 100644 --- a/java/ql/src/semmle/code/java/security/XSS.qll +++ b/java/ql/src/semmle/code/java/security/XSS.qll @@ -34,7 +34,6 @@ private class DefaultXssSinkModel extends SinkModelCsv { override predicate row(string row) { row = [ - "javax.servlet.http;HttpServletResponse;false;sendError;(int,String);;Argument[1];xss", "android.webkit;WebView;false;loadData;;;Argument[0];xss", "android.webkit;WebView;false;loadUrl;;;Argument[0];xss", "android.webkit;WebView;false;loadDataWithBaseURL;;;Argument[1];xss" diff --git a/java/ql/src/semmle/code/java/security/XmlParsers.qll b/java/ql/src/semmle/code/java/security/XmlParsers.qll index 685c5754fc9..5e2eb1caf8a 100644 --- a/java/ql/src/semmle/code/java/security/XmlParsers.qll +++ b/java/ql/src/semmle/code/java/security/XmlParsers.qll @@ -36,7 +36,10 @@ abstract class ParserConfig extends MethodAccess { */ predicate disables(Expr e) { this.getArgument(0) = e and - this.getArgument(1).(BooleanLiteral).getBooleanValue() = false + ( + this.getArgument(1).(BooleanLiteral).getBooleanValue() = false or + this.getArgument(1).(FieldAccess).getField().hasQualifiedName("java.lang", "Boolean", "FALSE") + ) } /** @@ -44,7 +47,10 @@ abstract class ParserConfig extends MethodAccess { */ predicate enables(Expr e) { this.getArgument(0) = e and - this.getArgument(1).(BooleanLiteral).getBooleanValue() = true + ( + this.getArgument(1).(BooleanLiteral).getBooleanValue() = true or + this.getArgument(1).(FieldAccess).getField().hasQualifiedName("java.lang", "Boolean", "TRUE") + ) } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-078/ExecTainted.expected b/java/ql/test/experimental/query-tests/security/CWE-078/ExecTainted.expected new file mode 100644 index 00000000000..6d87a2ffb89 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-078/ExecTainted.expected @@ -0,0 +1,11 @@ +edges +| JSchOSInjectionTest.java:14:30:14:60 | getParameter(...) : String | JSchOSInjectionTest.java:26:48:26:64 | ... + ... | +| JSchOSInjectionTest.java:38:30:38:60 | getParameter(...) : String | JSchOSInjectionTest.java:50:32:50:48 | ... + ... | +nodes +| JSchOSInjectionTest.java:14:30:14:60 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JSchOSInjectionTest.java:26:48:26:64 | ... + ... | semmle.label | ... + ... | +| JSchOSInjectionTest.java:38:30:38:60 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JSchOSInjectionTest.java:50:32:50:48 | ... + ... | semmle.label | ... + ... | +#select +| JSchOSInjectionTest.java:26:48:26:64 | ... + ... | JSchOSInjectionTest.java:14:30:14:60 | getParameter(...) : String | JSchOSInjectionTest.java:26:48:26:64 | ... + ... | $@ flows to here and is used in a command. | JSchOSInjectionTest.java:14:30:14:60 | getParameter(...) | User-provided value | +| JSchOSInjectionTest.java:50:32:50:48 | ... + ... | JSchOSInjectionTest.java:38:30:38:60 | getParameter(...) : String | JSchOSInjectionTest.java:50:32:50:48 | ... + ... | $@ flows to here and is used in a command. | JSchOSInjectionTest.java:38:30:38:60 | getParameter(...) | User-provided value | diff --git a/java/ql/test/experimental/query-tests/security/CWE-078/ExecTainted.qlref b/java/ql/test/experimental/query-tests/security/CWE-078/ExecTainted.qlref new file mode 100644 index 00000000000..714c3cadc5f --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-078/ExecTainted.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-078/ExecTainted.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-078/JSchOSInjectionTest.java b/java/ql/test/experimental/query-tests/security/CWE-078/JSchOSInjectionTest.java new file mode 100644 index 00000000000..08baf0a9772 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-078/JSchOSInjectionTest.java @@ -0,0 +1,56 @@ +import com.jcraft.jsch.*; + +import javax.servlet.http.*; +import javax.servlet.ServletException; +import java.io.IOException; + +public class JSchOSInjectionTest extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String host = "sshHost"; + String user = "user"; + String password = "password"; + String command = request.getParameter("command"); + + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); + + JSch jsch = new JSch(); + Session session = jsch.getSession(user, host, 22); + session.setPassword(password); + session.setConfig(config); + session.connect(); + + Channel channel = session.openChannel("exec"); + ((ChannelExec) channel).setCommand("ping " + command); + channel.setInputStream(null); + ((ChannelExec) channel).setErrStream(System.err); + + channel.connect(); + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String host = "sshHost"; + String user = "user"; + String password = "password"; + String command = request.getParameter("command"); + + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); + + JSch jsch = new JSch(); + Session session = jsch.getSession(user, host, 22); + session.setPassword(password); + session.setConfig(config); + session.connect(); + + ChannelExec channel = (ChannelExec)session.openChannel("exec"); + channel.setCommand("ping " + command); + channel.setInputStream(null); + channel.setErrStream(System.err); + + channel.connect(); + } +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-078/options b/java/ql/test/experimental/query-tests/security/CWE-078/options new file mode 100644 index 00000000000..eb7209ebe1e --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-078/options @@ -0,0 +1,2 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/jsch-0.1.55 + diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected new file mode 100644 index 00000000000..4f66cc83fbd --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected @@ -0,0 +1,21 @@ +edges +| JythonInjection.java:28:23:28:50 | getParameter(...) : String | JythonInjection.java:36:30:36:33 | code | +| JythonInjection.java:53:23:53:50 | getParameter(...) : String | JythonInjection.java:58:44:58:47 | code | +| JythonInjection.java:73:23:73:50 | getParameter(...) : String | JythonInjection.java:81:35:81:38 | code | +| JythonInjection.java:97:23:97:50 | getParameter(...) : String | JythonInjection.java:106:61:106:75 | getBytes(...) | +nodes +| JythonInjection.java:28:23:28:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JythonInjection.java:36:30:36:33 | code | semmle.label | code | +| JythonInjection.java:53:23:53:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JythonInjection.java:58:44:58:47 | code | semmle.label | code | +| JythonInjection.java:73:23:73:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JythonInjection.java:81:35:81:38 | code | semmle.label | code | +| JythonInjection.java:97:23:97:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JythonInjection.java:106:61:106:75 | getBytes(...) | semmle.label | getBytes(...) | +| JythonInjection.java:131:40:131:63 | getInputStream(...) | semmle.label | getInputStream(...) | +#select +| JythonInjection.java:36:13:36:34 | exec(...) | JythonInjection.java:28:23:28:50 | getParameter(...) : String | JythonInjection.java:36:30:36:33 | code | Jython evaluate $@. | JythonInjection.java:28:23:28:50 | getParameter(...) | user input | +| JythonInjection.java:58:27:58:48 | eval(...) | JythonInjection.java:53:23:53:50 | getParameter(...) : String | JythonInjection.java:58:44:58:47 | code | Jython evaluate $@. | JythonInjection.java:53:23:53:50 | getParameter(...) | user input | +| JythonInjection.java:81:13:81:39 | runsource(...) | JythonInjection.java:73:23:73:50 | getParameter(...) : String | JythonInjection.java:81:35:81:38 | code | Jython evaluate $@. | JythonInjection.java:73:23:73:50 | getParameter(...) | user input | +| JythonInjection.java:106:29:106:134 | makeCode(...) | JythonInjection.java:97:23:97:50 | getParameter(...) : String | JythonInjection.java:106:61:106:75 | getBytes(...) | Jython evaluate $@. | JythonInjection.java:97:23:97:50 | getParameter(...) | user input | +| JythonInjection.java:131:29:131:109 | compile(...) | JythonInjection.java:131:40:131:63 | getInputStream(...) | JythonInjection.java:131:40:131:63 | getInputStream(...) | Jython evaluate $@. | JythonInjection.java:131:40:131:63 | getInputStream(...) | user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.java b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.java new file mode 100644 index 00000000000..f9b29fec6cc --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.java @@ -0,0 +1,144 @@ +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.python.core.BytecodeLoader; +import org.python.core.Py; +import org.python.core.PyCode; +import org.python.core.PyException; +import org.python.core.PyObject; +import org.python.util.InteractiveInterpreter; +import org.python.util.PythonInterpreter; + +public class JythonInjection extends HttpServlet { + private static final long serialVersionUID = 1L; + + public JythonInjection() { + super(); + } + + // BAD: allow execution of arbitrary Python code + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new PythonInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + interpreter.exec(code); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + out.close(); + } + } + + // BAD: allow execution of arbitrary Python code + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + + try { + interpreter = new PythonInterpreter(); + PyObject py = interpreter.eval(code); + + response.getWriter().print(py.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } + + // BAD: allow arbitrary Jython expression to run + protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + InteractiveInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new InteractiveInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + interpreter.runsource(code); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } + + // BAD: load arbitrary class file to execute + protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new PythonInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + + PyCode pyCode = BytecodeLoader.makeCode("test", code.getBytes(), getServletContext().getRealPath("/com/example/test.pyc")); + interpreter.exec(pyCode); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } + + // BAD: Compile Python code to execute + protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + PythonInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new PythonInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + + PyCode pyCode = Py.compile(request.getInputStream(), "Test.py", org.python.core.CompileMode.eval); + interpreter.exec(pyCode); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.qlref new file mode 100644 index 00000000000..0ba9fd60621 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-094/JythonInjection.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/RhinoServlet.java b/java/ql/test/experimental/query-tests/security/CWE-094/RhinoServlet.java new file mode 100644 index 00000000000..e76a9543f87 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/RhinoServlet.java @@ -0,0 +1,91 @@ +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.mozilla.javascript.ClassShutter; +import org.mozilla.javascript.CompilerEnvirons; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.DefiningClassLoader; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.optimizer.ClassCompiler; + +/** + * Servlet implementation class RhinoServlet + */ +public class RhinoServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + public RhinoServlet() { + super(); + } + + // BAD: allow arbitrary Java and JavaScript code to be executed + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + Context ctx = Context.enter(); + try { + Scriptable scope = ctx.initStandardObjects(); + Object result = ctx.evaluateString(scope, code, "", 1, null); + response.getWriter().print(Context.toString(result)); + } catch(RhinoException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + Context.exit(); + } + } + + // GOOD: enable the safe mode + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + Context ctx = Context.enter(); + try { + Scriptable scope = ctx.initSafeStandardObjects(); + Object result = ctx.evaluateString(scope, code, "", 1, null); + response.getWriter().print(Context.toString(result)); + } catch(RhinoException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + Context.exit(); + } + } + + // GOOD: enforce a constraint on allowed classes + protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + Context ctx = Context.enter(); + try { + Scriptable scope = ctx.initStandardObjects(); + ctx.setClassShutter(new ClassShutter() { + public boolean visibleToScripts(String className) { + return className.startsWith("com.example."); + } + }); + + Object result = ctx.evaluateString(scope, code, "", 1, null); + response.getWriter().print(Context.toString(result)); + } catch(RhinoException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + Context.exit(); + } + } + + // BAD: allow arbitrary code to be compiled for subsequent execution + protected void doGet2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String code = request.getParameter("code"); + ClassCompiler compiler = new ClassCompiler(new CompilerEnvirons()); + Object[] objs = compiler.compileToClassFiles(code, "/sourceLocation", 1, "mainClassName"); + } + + // BAD: allow arbitrary code to be loaded for subsequent execution + protected void doPost2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String code = request.getParameter("code"); + Class clazz = new DefiningClassLoader().defineClass("Powerfunc", code.getBytes()); + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.expected b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.expected deleted file mode 100644 index a65faafc6bd..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.expected +++ /dev/null @@ -1,32 +0,0 @@ -edges -| ScriptEngineTest.java:8:44:8:55 | input : String | ScriptEngineTest.java:12:37:12:41 | input | -| ScriptEngineTest.java:15:51:15:62 | input : String | ScriptEngineTest.java:19:31:19:35 | input | -| ScriptEngineTest.java:23:58:23:69 | input : String | ScriptEngineTest.java:27:31:27:35 | input | -| ScriptEngineTest.java:30:46:30:57 | input : String | ScriptEngineTest.java:34:31:34:35 | input | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:38:56:38:62 | ...[...] : String | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:39:63:39:69 | ...[...] : String | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:40:70:40:76 | ...[...] : String | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:41:58:41:64 | ...[...] : String | -| ScriptEngineTest.java:38:56:38:62 | ...[...] : String | ScriptEngineTest.java:8:44:8:55 | input : String | -| ScriptEngineTest.java:39:63:39:69 | ...[...] : String | ScriptEngineTest.java:15:51:15:62 | input : String | -| ScriptEngineTest.java:40:70:40:76 | ...[...] : String | ScriptEngineTest.java:23:58:23:69 | input : String | -| ScriptEngineTest.java:41:58:41:64 | ...[...] : String | ScriptEngineTest.java:30:46:30:57 | input : String | -nodes -| ScriptEngineTest.java:8:44:8:55 | input : String | semmle.label | input : String | -| ScriptEngineTest.java:12:37:12:41 | input | semmle.label | input | -| ScriptEngineTest.java:15:51:15:62 | input : String | semmle.label | input : String | -| ScriptEngineTest.java:19:31:19:35 | input | semmle.label | input | -| ScriptEngineTest.java:23:58:23:69 | input : String | semmle.label | input : String | -| ScriptEngineTest.java:27:31:27:35 | input | semmle.label | input | -| ScriptEngineTest.java:30:46:30:57 | input : String | semmle.label | input : String | -| ScriptEngineTest.java:34:31:34:35 | input | semmle.label | input | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | semmle.label | args : String[] | -| ScriptEngineTest.java:38:56:38:62 | ...[...] : String | semmle.label | ...[...] : String | -| ScriptEngineTest.java:39:63:39:69 | ...[...] : String | semmle.label | ...[...] : String | -| ScriptEngineTest.java:40:70:40:76 | ...[...] : String | semmle.label | ...[...] : String | -| ScriptEngineTest.java:41:58:41:64 | ...[...] : String | semmle.label | ...[...] : String | -#select -| ScriptEngineTest.java:12:19:12:42 | eval(...) | ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:12:37:12:41 | input | ScriptEngine eval $@. | ScriptEngineTest.java:37:26:37:38 | args | user input | -| ScriptEngineTest.java:19:19:19:36 | eval(...) | ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:19:31:19:35 | input | ScriptEngine eval $@. | ScriptEngineTest.java:37:26:37:38 | args | user input | -| ScriptEngineTest.java:27:19:27:36 | eval(...) | ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:27:31:27:35 | input | ScriptEngine eval $@. | ScriptEngineTest.java:37:26:37:38 | args | user input | -| ScriptEngineTest.java:34:19:34:36 | eval(...) | ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:34:31:34:35 | input | ScriptEngine eval $@. | ScriptEngineTest.java:37:26:37:38 | args | user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.qlref b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.qlref deleted file mode 100644 index 9566c986778..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-094/ScriptEngine.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngineTest.java b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngineTest.java index e04ec615f30..ed7099d7598 100755 --- a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngineTest.java +++ b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngineTest.java @@ -1,9 +1,21 @@ +import javax.script.AbstractScriptEngine; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptException; + import jdk.nashorn.api.scripting.NashornScriptEngine; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; -import javax.script.*; +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; -public class ScriptEngineTest { +public class ScriptEngineTest extends HttpServlet { public void testWithScriptEngineReference(String input) throws ScriptException { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); @@ -33,26 +45,59 @@ public class ScriptEngineTest { MyCustomScriptEngine engine = (MyCustomScriptEngine) factory.getScriptEngine(new String[] { "-scripting" }); Object result = engine.eval(input); } - - public static void main(String[] args) throws ScriptException { - new ScriptEngineTest().testWithScriptEngineReference(args[0]); - new ScriptEngineTest().testNashornWithScriptEngineReference(args[0]); - new ScriptEngineTest().testNashornWithNashornScriptEngineReference(args[0]); - new ScriptEngineTest().testCustomScriptEngineReference(args[0]); + + public void testScriptEngineCompilable(String input) throws ScriptException { + NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); + Compilable engine = (Compilable) factory.getScriptEngine(new String[] { "-scripting" }); + CompiledScript script = engine.compile(input); + Object result = script.eval(); } - + + public void testScriptEngineGetProgram(String input) throws ScriptException { + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + ScriptEngine engine = scriptEngineManager.getEngineByName("nashorn"); + String program = engine.getFactory().getProgram(input); + Object result = engine.eval(program); + } + private static class MyCustomScriptEngine extends AbstractScriptEngine { - public Object eval(String var1) throws ScriptException { - return null; - } + public Object eval(String var1) throws ScriptException { return null; } + + @Override + public ScriptEngineFactory getFactory() { return null; } } private static class MyCustomFactory implements ScriptEngineFactory { public MyCustomFactory() { - } - - public ScriptEngine getScriptEngine() { return null; } + } + + @Override + public ScriptEngine getScriptEngine() { return null; } public ScriptEngine getScriptEngine(String... args) { return null; } + + @Override + public String getEngineName() { return null; } + + @Override + public String getMethodCallSyntax(final String obj, final String method, final String... args) { return null; } + + @Override + public String getProgram(final String... statements) { return null; } + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + try { + String code = request.getParameter("code"); + + new ScriptEngineTest().testWithScriptEngineReference(code); + new ScriptEngineTest().testNashornWithScriptEngineReference(code); + new ScriptEngineTest().testNashornWithNashornScriptEngineReference(code); + new ScriptEngineTest().testCustomScriptEngineReference(code); + new ScriptEngineTest().testScriptEngineCompilable(code); + new ScriptEngineTest().testScriptEngineGetProgram(code); + } catch (ScriptException se) { + throw new IOException(se.getMessage()); + } } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected new file mode 100644 index 00000000000..5f1d250e9a2 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected @@ -0,0 +1,58 @@ +edges +| RhinoServlet.java:28:23:28:50 | getParameter(...) : String | RhinoServlet.java:32:55:32:58 | code | +| RhinoServlet.java:81:23:81:50 | getParameter(...) : String | RhinoServlet.java:83:54:83:57 | code | +| RhinoServlet.java:88:23:88:50 | getParameter(...) : String | RhinoServlet.java:89:74:89:88 | getBytes(...) | +| ScriptEngineTest.java:20:44:20:55 | input : String | ScriptEngineTest.java:24:37:24:41 | input | +| ScriptEngineTest.java:27:51:27:62 | input : String | ScriptEngineTest.java:31:31:31:35 | input | +| ScriptEngineTest.java:35:58:35:69 | input : String | ScriptEngineTest.java:39:31:39:35 | input | +| ScriptEngineTest.java:42:46:42:57 | input : String | ScriptEngineTest.java:46:31:46:35 | input | +| ScriptEngineTest.java:49:41:49:52 | input : String | ScriptEngineTest.java:52:42:52:46 | input | +| ScriptEngineTest.java:56:41:56:52 | input : String | ScriptEngineTest.java:59:51:59:55 | input | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:93:57:93:60 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:94:64:94:67 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:95:71:95:74 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:96:59:96:62 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:97:54:97:57 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:98:54:98:57 | code : String | +| ScriptEngineTest.java:93:57:93:60 | code : String | ScriptEngineTest.java:20:44:20:55 | input : String | +| ScriptEngineTest.java:94:64:94:67 | code : String | ScriptEngineTest.java:27:51:27:62 | input : String | +| ScriptEngineTest.java:95:71:95:74 | code : String | ScriptEngineTest.java:35:58:35:69 | input : String | +| ScriptEngineTest.java:96:59:96:62 | code : String | ScriptEngineTest.java:42:46:42:57 | input : String | +| ScriptEngineTest.java:97:54:97:57 | code : String | ScriptEngineTest.java:49:41:49:52 | input : String | +| ScriptEngineTest.java:98:54:98:57 | code : String | ScriptEngineTest.java:56:41:56:52 | input : String | +nodes +| RhinoServlet.java:28:23:28:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RhinoServlet.java:32:55:32:58 | code | semmle.label | code | +| RhinoServlet.java:81:23:81:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RhinoServlet.java:83:54:83:57 | code | semmle.label | code | +| RhinoServlet.java:88:23:88:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RhinoServlet.java:89:74:89:88 | getBytes(...) | semmle.label | getBytes(...) | +| ScriptEngineTest.java:20:44:20:55 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:24:37:24:41 | input | semmle.label | input | +| ScriptEngineTest.java:27:51:27:62 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:31:31:31:35 | input | semmle.label | input | +| ScriptEngineTest.java:35:58:35:69 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:39:31:39:35 | input | semmle.label | input | +| ScriptEngineTest.java:42:46:42:57 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:46:31:46:35 | input | semmle.label | input | +| ScriptEngineTest.java:49:41:49:52 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:52:42:52:46 | input | semmle.label | input | +| ScriptEngineTest.java:56:41:56:52 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:59:51:59:55 | input | semmle.label | input | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| ScriptEngineTest.java:93:57:93:60 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:94:64:94:67 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:95:71:95:74 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:96:59:96:62 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:97:54:97:57 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:98:54:98:57 | code : String | semmle.label | code : String | +#select +| RhinoServlet.java:32:29:32:78 | evaluateString(...) | RhinoServlet.java:28:23:28:50 | getParameter(...) : String | RhinoServlet.java:32:55:32:58 | code | Java Script Engine evaluate $@. | RhinoServlet.java:28:23:28:50 | getParameter(...) | user input | +| RhinoServlet.java:83:25:83:97 | compileToClassFiles(...) | RhinoServlet.java:81:23:81:50 | getParameter(...) : String | RhinoServlet.java:83:54:83:57 | code | Java Script Engine evaluate $@. | RhinoServlet.java:81:23:81:50 | getParameter(...) | user input | +| RhinoServlet.java:89:23:89:89 | defineClass(...) | RhinoServlet.java:88:23:88:50 | getParameter(...) : String | RhinoServlet.java:89:74:89:88 | getBytes(...) | Java Script Engine evaluate $@. | RhinoServlet.java:88:23:88:50 | getParameter(...) | user input | +| ScriptEngineTest.java:24:19:24:42 | eval(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:24:37:24:41 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:31:19:31:36 | eval(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:31:31:31:35 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:39:19:39:36 | eval(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:39:31:39:35 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:46:19:46:36 | eval(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:46:31:46:35 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:52:27:52:47 | compile(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:52:42:52:46 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:59:20:59:56 | getProgram(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:59:51:59:55 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.qlref new file mode 100644 index 00000000000..da2b4287d0c --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-094/ScriptInjection.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/options b/java/ql/test/experimental/query-tests/security/CWE-094/options index 0104bad4f22..75905d00a27 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-094/options +++ b/java/ql/test/experimental/query-tests/security/CWE-094/options @@ -1,2 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/scriptengine:${testdir}/../../../../stubs/java-ee-el:${testdir}/../../../../stubs/juel-2.2:${testdir}/../../../stubs/groovy-all-3.0.7:${testdir}/../../../../stubs/servlet-api-2.4 - +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/scriptengine:${testdir}/../../../../stubs/java-ee-el:${testdir}/../../../../stubs/juel-2.2:${testdir}/../../../stubs/groovy-all-3.0.7:${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/jython-2.7.2:${testdir}/../../../../experimental/stubs/rhino-1.7.13 \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected new file mode 100644 index 00000000000..bcf5e892e1b --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected @@ -0,0 +1,35 @@ +edges +| SpringUrlRedirect.java:13:30:13:47 | redirectUrl : String | SpringUrlRedirect.java:15:19:15:29 | redirectUrl | +| SpringUrlRedirect.java:20:24:20:41 | redirectUrl : String | SpringUrlRedirect.java:21:36:21:46 | redirectUrl | +| SpringUrlRedirect.java:26:30:26:47 | redirectUrl : String | SpringUrlRedirect.java:27:44:27:54 | redirectUrl | +| SpringUrlRedirect.java:32:30:32:47 | redirectUrl : String | SpringUrlRedirect.java:33:47:33:57 | redirectUrl | +| SpringUrlRedirect.java:37:24:37:41 | redirectUrl : String | SpringUrlRedirect.java:40:29:40:39 | redirectUrl | +| SpringUrlRedirect.java:45:24:45:41 | redirectUrl : String | SpringUrlRedirect.java:48:30:48:40 | redirectUrl | +| SpringUrlRedirect.java:53:24:53:41 | redirectUrl : String | SpringUrlRedirect.java:54:30:54:66 | format(...) | +| SpringUrlRedirect.java:58:24:58:41 | redirectUrl : String | SpringUrlRedirect.java:59:30:59:76 | format(...) | +nodes +| SpringUrlRedirect.java:13:30:13:47 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:15:19:15:29 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:20:24:20:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:21:36:21:46 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:26:30:26:47 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:27:44:27:54 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:32:30:32:47 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:33:47:33:57 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:37:24:37:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:40:29:40:39 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:45:24:45:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:48:30:48:40 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:53:24:53:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:54:30:54:66 | format(...) | semmle.label | format(...) | +| SpringUrlRedirect.java:58:24:58:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:59:30:59:76 | format(...) | semmle.label | format(...) | +#select +| SpringUrlRedirect.java:15:19:15:29 | redirectUrl | SpringUrlRedirect.java:13:30:13:47 | redirectUrl : String | SpringUrlRedirect.java:15:19:15:29 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:13:30:13:47 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:21:36:21:46 | redirectUrl | SpringUrlRedirect.java:20:24:20:41 | redirectUrl : String | SpringUrlRedirect.java:21:36:21:46 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:20:24:20:41 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:27:44:27:54 | redirectUrl | SpringUrlRedirect.java:26:30:26:47 | redirectUrl : String | SpringUrlRedirect.java:27:44:27:54 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:26:30:26:47 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:33:47:33:57 | redirectUrl | SpringUrlRedirect.java:32:30:32:47 | redirectUrl : String | SpringUrlRedirect.java:33:47:33:57 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:32:30:32:47 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:40:29:40:39 | redirectUrl | SpringUrlRedirect.java:37:24:37:41 | redirectUrl : String | SpringUrlRedirect.java:40:29:40:39 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:37:24:37:41 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:48:30:48:40 | redirectUrl | SpringUrlRedirect.java:45:24:45:41 | redirectUrl : String | SpringUrlRedirect.java:48:30:48:40 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:45:24:45:41 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:54:30:54:66 | format(...) | SpringUrlRedirect.java:53:24:53:41 | redirectUrl : String | SpringUrlRedirect.java:54:30:54:66 | format(...) | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:53:24:53:41 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:59:30:59:76 | format(...) | SpringUrlRedirect.java:58:24:58:41 | redirectUrl : String | SpringUrlRedirect.java:59:30:59:76 | format(...) | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:58:24:58:41 | redirectUrl | user-provided value | diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.java b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.java new file mode 100644 index 00000000000..f3958cba102 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.java @@ -0,0 +1,83 @@ +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; + +@Controller +public class SpringUrlRedirect { + + private final static String VALID_REDIRECT = "http://127.0.0.1"; + + @GetMapping("url1") + public RedirectView bad1(String redirectUrl, HttpServletResponse response) throws Exception { + RedirectView rv = new RedirectView(); + rv.setUrl(redirectUrl); + return rv; + } + + @GetMapping("url2") + public String bad2(String redirectUrl) { + String url = "redirect:" + redirectUrl; + return url; + } + + @GetMapping("url3") + public RedirectView bad3(String redirectUrl) { + RedirectView rv = new RedirectView(redirectUrl); + return rv; + } + + @GetMapping("url4") + public ModelAndView bad4(String redirectUrl) { + return new ModelAndView("redirect:" + redirectUrl); + } + + @GetMapping("url5") + public String bad5(String redirectUrl) { + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append("redirect:"); + stringBuffer.append(redirectUrl); + return stringBuffer.toString(); + } + + @GetMapping("url6") + public String bad6(String redirectUrl) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("redirect:"); + stringBuilder.append(redirectUrl); + return stringBuilder.toString(); + } + + @GetMapping("url7") + public String bad7(String redirectUrl) { + return "redirect:" + String.format("%s/?aaa", redirectUrl); + } + + @GetMapping("url8") + public String bad8(String redirectUrl, String token) { + return "redirect:" + String.format(redirectUrl + "?token=%s", token); + } + + @GetMapping("url9") + public RedirectView good1(String redirectUrl) { + RedirectView rv = new RedirectView(); + if (redirectUrl.startsWith(VALID_REDIRECT)){ + rv.setUrl(redirectUrl); + }else { + rv.setUrl(VALID_REDIRECT); + } + return rv; + } + + @GetMapping("url10") + public ModelAndView good2(String token) { + String url = "/edit?token=" + token; + return new ModelAndView("redirect:" + url); + } + + @GetMapping("url11") + public String good3(String status) { + return "redirect:" + String.format("/stories/search/criteria?status=%s", status); + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.qlref b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.qlref new file mode 100644 index 00000000000..418be1d307b --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-601/SpringUrlRedirect.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/options b/java/ql/test/experimental/query-tests/security/CWE-601/options new file mode 100644 index 00000000000..a9289108747 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-601/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.2.3/ \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.expected new file mode 100644 index 00000000000..1dfe806f10b --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.expected @@ -0,0 +1,67 @@ +edges +| RegexInjection.java:13:22:13:52 | getParameter(...) : String | RegexInjection.java:16:26:16:47 | ... + ... | +| RegexInjection.java:20:22:20:52 | getParameter(...) : String | RegexInjection.java:23:24:23:30 | pattern | +| RegexInjection.java:27:22:27:52 | getParameter(...) : String | RegexInjection.java:30:31:30:37 | pattern | +| RegexInjection.java:34:22:34:52 | getParameter(...) : String | RegexInjection.java:37:29:37:35 | pattern | +| RegexInjection.java:41:22:41:52 | getParameter(...) : String | RegexInjection.java:44:34:44:40 | pattern | +| RegexInjection.java:51:22:51:52 | getParameter(...) : String | RegexInjection.java:54:28:54:34 | pattern | +| RegexInjection.java:58:22:58:52 | getParameter(...) : String | RegexInjection.java:61:28:61:34 | pattern | +| RegexInjection.java:65:22:65:52 | getParameter(...) : String | RegexInjection.java:68:36:68:42 | pattern : String | +| RegexInjection.java:68:32:68:43 | foo(...) : String | RegexInjection.java:68:26:68:52 | ... + ... | +| RegexInjection.java:68:36:68:42 | pattern : String | RegexInjection.java:68:32:68:43 | foo(...) : String | +| RegexInjection.java:84:22:84:52 | getParameter(...) : String | RegexInjection.java:90:26:90:47 | ... + ... | +| RegexInjection.java:100:22:100:52 | getParameter(...) : String | RegexInjection.java:103:40:103:46 | pattern | +| RegexInjection.java:107:22:107:52 | getParameter(...) : String | RegexInjection.java:110:42:110:48 | pattern | +| RegexInjection.java:114:22:114:52 | getParameter(...) : String | RegexInjection.java:117:44:117:50 | pattern | +| RegexInjection.java:121:22:121:52 | getParameter(...) : String | RegexInjection.java:124:41:124:47 | pattern | +| RegexInjection.java:128:22:128:52 | getParameter(...) : String | RegexInjection.java:131:43:131:49 | pattern | +| RegexInjection.java:143:22:143:52 | getParameter(...) : String | RegexInjection.java:146:45:146:51 | pattern | +nodes +| RegexInjection.java:13:22:13:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:16:26:16:47 | ... + ... | semmle.label | ... + ... | +| RegexInjection.java:20:22:20:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:23:24:23:30 | pattern | semmle.label | pattern | +| RegexInjection.java:27:22:27:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:30:31:30:37 | pattern | semmle.label | pattern | +| RegexInjection.java:34:22:34:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:37:29:37:35 | pattern | semmle.label | pattern | +| RegexInjection.java:41:22:41:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:44:34:44:40 | pattern | semmle.label | pattern | +| RegexInjection.java:51:22:51:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:54:28:54:34 | pattern | semmle.label | pattern | +| RegexInjection.java:58:22:58:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:61:28:61:34 | pattern | semmle.label | pattern | +| RegexInjection.java:65:22:65:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:68:26:68:52 | ... + ... | semmle.label | ... + ... | +| RegexInjection.java:68:32:68:43 | foo(...) : String | semmle.label | foo(...) : String | +| RegexInjection.java:68:36:68:42 | pattern : String | semmle.label | pattern : String | +| RegexInjection.java:84:22:84:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:90:26:90:47 | ... + ... | semmle.label | ... + ... | +| RegexInjection.java:100:22:100:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:103:40:103:46 | pattern | semmle.label | pattern | +| RegexInjection.java:107:22:107:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:110:42:110:48 | pattern | semmle.label | pattern | +| RegexInjection.java:114:22:114:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:117:44:117:50 | pattern | semmle.label | pattern | +| RegexInjection.java:121:22:121:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:124:41:124:47 | pattern | semmle.label | pattern | +| RegexInjection.java:128:22:128:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:131:43:131:49 | pattern | semmle.label | pattern | +| RegexInjection.java:143:22:143:52 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RegexInjection.java:146:45:146:51 | pattern | semmle.label | pattern | +#select +| RegexInjection.java:16:26:16:47 | ... + ... | RegexInjection.java:13:22:13:52 | getParameter(...) : String | RegexInjection.java:16:26:16:47 | ... + ... | $@ is user controlled. | RegexInjection.java:13:22:13:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:23:24:23:30 | pattern | RegexInjection.java:20:22:20:52 | getParameter(...) : String | RegexInjection.java:23:24:23:30 | pattern | $@ is user controlled. | RegexInjection.java:20:22:20:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:30:31:30:37 | pattern | RegexInjection.java:27:22:27:52 | getParameter(...) : String | RegexInjection.java:30:31:30:37 | pattern | $@ is user controlled. | RegexInjection.java:27:22:27:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:37:29:37:35 | pattern | RegexInjection.java:34:22:34:52 | getParameter(...) : String | RegexInjection.java:37:29:37:35 | pattern | $@ is user controlled. | RegexInjection.java:34:22:34:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:44:34:44:40 | pattern | RegexInjection.java:41:22:41:52 | getParameter(...) : String | RegexInjection.java:44:34:44:40 | pattern | $@ is user controlled. | RegexInjection.java:41:22:41:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:54:28:54:34 | pattern | RegexInjection.java:51:22:51:52 | getParameter(...) : String | RegexInjection.java:54:28:54:34 | pattern | $@ is user controlled. | RegexInjection.java:51:22:51:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:61:28:61:34 | pattern | RegexInjection.java:58:22:58:52 | getParameter(...) : String | RegexInjection.java:61:28:61:34 | pattern | $@ is user controlled. | RegexInjection.java:58:22:58:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:68:26:68:52 | ... + ... | RegexInjection.java:65:22:65:52 | getParameter(...) : String | RegexInjection.java:68:26:68:52 | ... + ... | $@ is user controlled. | RegexInjection.java:65:22:65:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:90:26:90:47 | ... + ... | RegexInjection.java:84:22:84:52 | getParameter(...) : String | RegexInjection.java:90:26:90:47 | ... + ... | $@ is user controlled. | RegexInjection.java:84:22:84:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:103:40:103:46 | pattern | RegexInjection.java:100:22:100:52 | getParameter(...) : String | RegexInjection.java:103:40:103:46 | pattern | $@ is user controlled. | RegexInjection.java:100:22:100:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:110:42:110:48 | pattern | RegexInjection.java:107:22:107:52 | getParameter(...) : String | RegexInjection.java:110:42:110:48 | pattern | $@ is user controlled. | RegexInjection.java:107:22:107:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:117:44:117:50 | pattern | RegexInjection.java:114:22:114:52 | getParameter(...) : String | RegexInjection.java:117:44:117:50 | pattern | $@ is user controlled. | RegexInjection.java:114:22:114:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:124:41:124:47 | pattern | RegexInjection.java:121:22:121:52 | getParameter(...) : String | RegexInjection.java:124:41:124:47 | pattern | $@ is user controlled. | RegexInjection.java:121:22:121:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:131:43:131:49 | pattern | RegexInjection.java:128:22:128:52 | getParameter(...) : String | RegexInjection.java:131:43:131:49 | pattern | $@ is user controlled. | RegexInjection.java:128:22:128:52 | getParameter(...) | This regular expression pattern | +| RegexInjection.java:146:45:146:51 | pattern | RegexInjection.java:143:22:143:52 | getParameter(...) : String | RegexInjection.java:146:45:146:51 | pattern | $@ is user controlled. | RegexInjection.java:143:22:143:52 | getParameter(...) | This regular expression pattern | diff --git a/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.java b/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.java new file mode 100644 index 00000000000..2aace93aeb8 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.java @@ -0,0 +1,148 @@ +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; + +import org.apache.commons.lang3.RegExUtils; + +public class RegexInjection extends HttpServlet { + public boolean string1(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return input.matches("^" + pattern + "=.*$"); // BAD + } + + public boolean string2(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return input.split(pattern).length > 0; // BAD + } + + public boolean string3(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return input.replaceFirst(pattern, "").length() > 0; // BAD + } + + public boolean string4(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return input.replaceAll(pattern, "").length() > 0; // BAD + } + + public boolean pattern1(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + Pattern pt = Pattern.compile(pattern); + Matcher matcher = pt.matcher(input); + + return matcher.find(); // BAD + } + + public boolean pattern2(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return Pattern.compile(pattern).matcher(input).matches(); // BAD + } + + public boolean pattern3(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return Pattern.matches(pattern, input); // BAD + } + + public boolean pattern4(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return input.matches("^" + foo(pattern) + "=.*$"); // BAD + } + + String foo(String str) { + return str; + } + + public boolean pattern5(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + // GOOD: User input is sanitized before constructing the regex + return input.matches("^" + escapeSpecialRegexChars(pattern) + "=.*$"); + } + + public boolean pattern6(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + escapeSpecialRegexChars(pattern); + + // BAD: the pattern is not really sanitized + return input.matches("^" + pattern + "=.*$"); + } + + Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]"); + + String escapeSpecialRegexChars(String str) { + return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0"); + } + + public boolean apache1(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return RegExUtils.removeAll(input, pattern).length() > 0; // BAD + } + + public boolean apache2(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return RegExUtils.removeFirst(input, pattern).length() > 0; // BAD + } + + public boolean apache3(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return RegExUtils.removePattern(input, pattern).length() > 0; // BAD + } + + public boolean apache4(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return RegExUtils.replaceAll(input, pattern, "").length() > 0; // BAD + } + + public boolean apache5(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return RegExUtils.replaceFirst(input, pattern, "").length() > 0; // BAD + } + + public boolean apache6(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + Pattern pt = (Pattern)(Object) pattern; + return RegExUtils.replaceFirst(input, pt, "").length() > 0; // GOOD, Pattern compile is the sink instead + } + + public boolean apache7(javax.servlet.http.HttpServletRequest request) { + String pattern = request.getParameter("pattern"); + String input = request.getParameter("input"); + + return RegExUtils.replacePattern(input, pattern, "").length() > 0; // BAD + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.qlref new file mode 100644 index 00000000000..dca594b38d2 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-730/RegexInjection.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-730/options b/java/ql/test/experimental/query-tests/security/CWE-730/options new file mode 100644 index 00000000000..73f1b5a3c3e --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-730/options @@ -0,0 +1 @@ +// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/apache-commons-lang3-3.7 \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/Channel.java b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/Channel.java new file mode 100644 index 00000000000..5b084813c61 --- /dev/null +++ b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/Channel.java @@ -0,0 +1,46 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +import java.io.*; + +public abstract class Channel implements Runnable { + public void connect() { + } + + public void setInputStream(InputStream in){ + } + + public void disconnect(){ + } + + public void run() { + } +} \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/ChannelExec.java b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/ChannelExec.java new file mode 100644 index 00000000000..6ff67754d99 --- /dev/null +++ b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/ChannelExec.java @@ -0,0 +1,40 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +import java.io.*; + +public class ChannelExec extends ChannelSession { + public void setErrStream(OutputStream out) { + } + + public void setCommand(String command){ + } +} \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/ChannelSession.java b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/ChannelSession.java new file mode 100644 index 00000000000..a6c97b334b6 --- /dev/null +++ b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/ChannelSession.java @@ -0,0 +1,33 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +class ChannelSession extends Channel { +} \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/JSch.java b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/JSch.java new file mode 100644 index 00000000000..b8ce91b9715 --- /dev/null +++ b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/JSch.java @@ -0,0 +1,40 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +public class JSch { + + public JSch() {} + + public Session getSession(String username, String host, int port) { + return null; + } + +} \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/Session.java b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/Session.java new file mode 100644 index 00000000000..4cf2015fceb --- /dev/null +++ b/java/ql/test/experimental/stubs/jsch-0.1.55/com/jcraft/jsch/Session.java @@ -0,0 +1,55 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* +Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package com.jcraft.jsch; + +public class Session implements Runnable { + + public Channel openChannel(String type) { + if ("exec".equals(type)) + return new ChannelExec(); + + return null; + } + + public void setPassword(String password) { + } + + public void setConfig(java.util.Properties newconf) { + } + + public void connect() { + } + + public void run(){ + } + + public void disconnect(){ + } +} \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ClassShutter.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ClassShutter.java new file mode 100644 index 00000000000..f425e08e966 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ClassShutter.java @@ -0,0 +1,56 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** +Embeddings that wish to filter Java classes that are visible to scripts +through the LiveConnect, should implement this interface. + +@see Context#setClassShutter(ClassShutter) +@since 1.5 Release 4 +@author Norris Boyd +*/ + + public interface ClassShutter { + + /** + * Return true iff the Java class with the given name should be exposed + * to scripts. + *

    + * An embedding may filter which Java classes are exposed through + * LiveConnect to JavaScript scripts. + *

    + * Due to the fact that there is no package reflection in Java, + * this method will also be called with package names. There + * is no way for Rhino to tell if "Packages.a.b" is a package name + * or a class that doesn't exist. What Rhino does is attempt + * to load each segment of "Packages.a.b.c": It first attempts to + * load class "a", then attempts to load class "a.b", then + * finally attempts to load class "a.b.c". On a Rhino installation + * without any ClassShutter set, and without any of the + * above classes, the expression "Packages.a.b.c" will result in + * a [JavaPackage a.b.c] and not an error. + *

    + * With ClassShutter supplied, Rhino will first call + * visibleToScripts before attempting to look up the class name. If + * visibleToScripts returns false, the class name lookup is not + * performed and subsequent Rhino execution assumes the class is + * not present. So for "java.lang.System.out.println" the lookup + * of "java.lang.System" is skipped and thus Rhino assumes that + * "java.lang.System" doesn't exist. So then for "java.lang.System.out", + * Rhino attempts to load the class "java.lang.System.out" because + * it assumes that "java.lang.System" is a package name. + *

    + * @param fullClassName the full name of the class (including the package + * name, with '.' as a delimiter). For example the + * standard string class is "java.lang.String" + * @return whether or not to reveal this class to scripts + */ + public boolean visibleToScripts(String fullClassName); +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/CompilerEnvirons.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/CompilerEnvirons.java new file mode 100644 index 00000000000..3cb0619499e --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/CompilerEnvirons.java @@ -0,0 +1,12 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + package org.mozilla.javascript; + + public class CompilerEnvirons { + public CompilerEnvirons() { + } + } \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Context.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Context.java new file mode 100644 index 00000000000..1bda212cfa4 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Context.java @@ -0,0 +1,695 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.util.Locale; + +/** + * This class represents the runtime context of an executing script. + * + * Before executing a script, an instance of Context must be created + * and associated with the thread that will be executing the script. + * The Context will be used to store information about the executing + * of the script such as the call stack. Contexts are associated with + * the current thread using the {@link #call(ContextAction)} + * or {@link #enter()} methods.

    + * + * Different forms of script execution are supported. Scripts may be + * evaluated from the source directly, or first compiled and then later + * executed. Interactive execution is also supported.

    + * + * Some aspects of script execution, such as type conversions and + * object creation, may be accessed directly through methods of + * Context. + * + * @see Scriptable + * @author Norris Boyd + * @author Brendan Eich + */ + +public class Context + implements Closeable +{ + /** + * Creates a new Context. The context will be associated with the {@link + * ContextFactory#getGlobal() global context factory}. + * + * Note that the Context must be associated with a thread before + * it can be used to execute a script. + * @deprecated this constructor is deprecated because it creates a + * dependency on a static singleton context factory. Use + * {@link ContextFactory#enter()} or + * {@link ContextFactory#call(ContextAction)} instead. If you subclass + * this class, consider using {@link #Context(ContextFactory)} constructor + * instead in the subclasses' constructors. + */ + @Deprecated + public Context() + { + } + + /** + * Creates a new context. Provided as a preferred super constructor for + * subclasses in place of the deprecated default public constructor. + * @param factory the context factory associated with this context (most + * likely, the one that created the context). Can not be null. The context + * features are inherited from the factory, and the context will also + * otherwise use its factory's services. + * @throws IllegalArgumentException if factory parameter is null. + */ + protected Context(ContextFactory factory) + { + } + + /** + * Get the current Context. + * + * The current Context is per-thread; this method looks up + * the Context associated with the current thread.

    + * + * @return the Context associated with the current thread, or + * null if no context is associated with the current + * thread. + * @see ContextFactory#enterContext() + * @see ContextFactory#call(ContextAction) + */ + public static Context getCurrentContext() + { + return null; + } + + /** + * Same as calling {@link ContextFactory#enterContext()} on the global + * ContextFactory instance. + * @return a Context associated with the current thread + * @see #getCurrentContext() + * @see #exit() + * @see #call(ContextAction) + */ + public static Context enter() + { + return null; + } + + /** + * Get a Context associated with the current thread, using + * the given Context if need be. + *

    + * The same as enter() except that cx + * is associated with the current thread and returned if + * the current thread has no associated context and cx + * is not associated with any other thread. + * @param cx a Context to associate with the thread if possible + * @return a Context associated with the current thread + * @deprecated use {@link ContextFactory#enterContext(Context)} instead as + * this method relies on usage of a static singleton "global" ContextFactory. + * @see ContextFactory#enterContext(Context) + * @see ContextFactory#call(ContextAction) + */ + @Deprecated + public static Context enter(Context cx) + { + return null; + } + + static final Context enter(Context cx, ContextFactory factory) + { + return null; + } + + /** + * Exit a block of code requiring a Context. + * + * Calling exit() will remove the association between + * the current thread and a Context if the prior call to + * {@link ContextFactory#enterContext()} on this thread newly associated a + * Context with this thread. Once the current thread no longer has an + * associated Context, it cannot be used to execute JavaScript until it is + * again associated with a Context. + * @see ContextFactory#enterContext() + */ + public static void exit() + { + } + + @Override + public void close() { + } + + /** + * Return {@link ContextFactory} instance used to create this Context. + */ + public final ContextFactory getFactory() + { + return null; + } + + /** + * Checks if this is a sealed Context. A sealed Context instance does not + * allow to modify any of its properties and will throw an exception + * on any such attempt. + * @see #seal(Object sealKey) + */ + public final boolean isSealed() + { + return false; + } + + /** + * Seal this Context object so any attempt to modify any of its properties + * including calling {@link #enter()} and {@link #exit()} methods will + * throw an exception. + *

    + * If sealKey is not null, calling + * {@link #unseal(Object sealKey)} with the same key unseals + * the object. If sealKey is null, unsealing is no longer possible. + * + * @see #isSealed() + * @see #unseal(Object) + */ + public final void seal(Object sealKey) + { + } + + /** + * Unseal previously sealed Context object. + * The sealKey argument should not be null and should match + * sealKey suplied with the last call to + * {@link #seal(Object)} or an exception will be thrown. + * + * @see #isSealed() + * @see #seal(Object sealKey) + */ + public final void unseal(Object sealKey) + { + } + + /** + * Get the current language version. + *

    + * The language version number affects JavaScript semantics as detailed + * in the overview documentation. + * + * @return an integer that is one of VERSION_1_0, VERSION_1_1, etc. + */ + public final int getLanguageVersion() + { + return -1; + } + + /** + * Set the language version. + * + *

    + * Setting the language version will affect functions and scripts compiled + * subsequently. See the overview documentation for version-specific + * behavior. + * + * @param version the version as specified by VERSION_1_0, VERSION_1_1, etc. + */ + public void setLanguageVersion(int version) + { + } + + public static boolean isValidLanguageVersion(int version) + { + return false; + } + + public static void checkLanguageVersion(int version) + { + } + + /** + * Get the implementation version. + * + *

    + * The implementation version is of the form + *

    +     *    "name langVer release relNum date"
    +     * 
    + * where name is the name of the product, langVer is + * the language version, relNum is the release number, and + * date is the release date for that specific + * release in the form "yyyy mm dd". + * + * @return a string that encodes the product, language version, release + * number, and date. + */ + public final String getImplementationVersion() { + return null; + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon. + * + * @return the initialized scope + */ + public final ScriptableObject initStandardObjects() + { + return null; + } + + /** + * Initialize the standard objects, leaving out those that offer access directly + * to Java classes. This sets up "scope" to have access to all the standard + * JavaScript classes, but does not create global objects for any top-level + * Java packages. In addition, the "Packages," "JavaAdapter," and + * "JavaImporter" classes, and the "getClass" function, are not + * initialized. + * + * The result of this function is a scope that may be safely used in a "sandbox" + * environment where it is not desirable to give access to Java code from JavaScript. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon. + * + * @return the initialized scope + */ + public final ScriptableObject initSafeStandardObjects() + { + return null; + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object which + * is an instance {@link ScriptableObject}. + */ + public final Scriptable initStandardObjects(ScriptableObject scope) + { + return null; + } + + /** + * Initialize the standard objects, leaving out those that offer access directly + * to Java classes. This sets up "scope" to have access to all the standard + * JavaScript classes, but does not create global objects for any top-level + * Java packages. In addition, the "Packages," "JavaAdapter," and + * "JavaImporter" classes, and the "getClass" function, are not + * initialized. + * + * The result of this function is a scope that may be safely used in a "sandbox" + * environment where it is not desirable to give access to Java code from JavaScript. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object which + * is an instance {@link ScriptableObject}. + */ + public final Scriptable initSafeStandardObjects(ScriptableObject scope) + { + return null; + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon.

    + * + * This form of the method also allows for creating "sealed" standard + * objects. An object that is sealed cannot have properties added, changed, + * or removed. This is useful to create a "superglobal" that can be shared + * among several top-level objects. Note that sealing is not allowed in + * the current ECMA/ISO language specification, but is likely for + * the next version. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @param sealed whether or not to create sealed standard objects that + * cannot be modified. + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object. + * @since 1.4R3 + */ + public ScriptableObject initStandardObjects(ScriptableObject scope, + boolean sealed) + { + return null; + } + + /** + * Initialize the standard objects, leaving out those that offer access directly + * to Java classes. This sets up "scope" to have access to all the standard + * JavaScript classes, but does not create global objects for any top-level + * Java packages. In addition, the "Packages," "JavaAdapter," and + * "JavaImporter" classes, and the "getClass" function, are not + * initialized. + * + * The result of this function is a scope that may be safely used in a "sandbox" + * environment where it is not desirable to give access to Java code from JavaScript. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon.

    + * + * This form of the method also allows for creating "sealed" standard + * objects. An object that is sealed cannot have properties added, changed, + * or removed. This is useful to create a "superglobal" that can be shared + * among several top-level objects. Note that sealing is not allowed in + * the current ECMA/ISO language specification, but is likely for + * the next version. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @param sealed whether or not to create sealed standard objects that + * cannot be modified. + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object. + * @since 1.7.6 + */ + public ScriptableObject initSafeStandardObjects(ScriptableObject scope, + boolean sealed) + { + return null; + } + + /** + * Get the singleton object that represents the JavaScript Undefined value. + */ + public static Object getUndefinedValue() + { + return null; + } + + /** + * Evaluate a JavaScript source string. + * + * The provided source name and line number are used for error messages + * and for producing debug information. + * + * @param scope the scope to execute in + * @param source the JavaScript source + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return the result of evaluating the string + * @see org.mozilla.javascript.SecurityController + */ + public final Object evaluateString(Scriptable scope, String source, + String sourceName, int lineno, + Object securityDomain) + { + return null; + } + + /** + * Evaluate a reader as JavaScript source. + * + * All characters of the reader are consumed. + * + * @param scope the scope to execute in + * @param in the Reader to get JavaScript source from + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return the result of evaluating the source + * + * @exception IOException if an IOException was generated by the Reader + */ + public final Object evaluateReader(Scriptable scope, Reader in, + String sourceName, int lineno, + Object securityDomain) + throws IOException + { + return null; + } + + /** + * @deprecated + * @see #compileReader(Reader in, String sourceName, int lineno, Object securityDomain) + */ + @Deprecated + public final Script compileReader( + Scriptable scope, Reader in, String sourceName, int lineno, Object securityDomain) + throws IOException { + return null; + } + + /** + * Compiles the source in the given reader. + * + *

    Returns a script that may later be executed. Will consume all the source in the reader. + * + * @param in the input reader + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number for reporting errors + * @param securityDomain an arbitrary object that specifies security information about the + * origin or owner of the script. For implementations that don't care about security, this + * value may be null. + * @return a script that may later be executed + * @exception IOException if an IOException was generated by the Reader + * @see org.mozilla.javascript.Script + */ + public final Script compileReader( + Reader in, String sourceName, int lineno, Object securityDomain) throws IOException { + return null; + } + + /** + * Compiles the source in the given string. + * + *

    Returns a script that may later be executed. + * + * @param source the source string + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number for reporting errors. Use 0 if the line number is + * unknown. + * @param securityDomain an arbitrary object that specifies security information about the + * origin or owner of the script. For implementations that don't care about security, this + * value may be null. + * @return a script that may later be executed + * @see org.mozilla.javascript.Script + */ + public final Script compileString( + String source, String sourceName, int lineno, Object securityDomain) { + return null; + } + + /** + * Compile a JavaScript function. + * + *

    The function source must be a function definition as defined by ECMA (e.g., "function f(a) + * { return a; }"). + * + * @param scope the scope to compile relative to + * @param source the function definition source + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security information about the + * origin or owner of the script. For implementations that don't care about security, this + * value may be null. + * @return a Function that may later be called + * @see org.mozilla.javascript.Function + */ + public final Function compileFunction( + Scriptable scope, String source, String sourceName, int lineno, Object securityDomain) { + return null; + } + + /** + * Convert the value to a JavaScript boolean value. + *

    + * See ECMA 9.2. + * + * @param value a JavaScript value + * @return the corresponding boolean value converted using + * the ECMA rules + */ + public static boolean toBoolean(Object value) + { + return false; + } + + /** + * Convert the value to a JavaScript Number value. + *

    + * Returns a Java double for the JavaScript Number. + *

    + * See ECMA 9.3. + * + * @param value a JavaScript value + * @return the corresponding double value converted using + * the ECMA rules + */ + public static double toNumber(Object value) + { + return -1; + } + + /** + * Convert the value to a JavaScript String value. + *

    + * See ECMA 9.8. + *

    + * @param value a JavaScript value + * @return the corresponding String value converted using + * the ECMA rules + */ + public static String toString(Object value) + { + return null; + } + + /** + * Convert the value to an JavaScript object value. + *

    + * Note that a scope must be provided to look up the constructors + * for Number, Boolean, and String. + *

    + * See ECMA 9.9. + *

    + * Additionally, arbitrary Java objects and classes will be + * wrapped in a Scriptable object with its Java fields and methods + * reflected as JavaScript properties of the object. + * + * @param value any Java object + * @param scope global scope containing constructors for Number, + * Boolean, and String + * @return new JavaScript object + */ + public static Scriptable toObject(Object value, Scriptable scope) + { + return null; + } + + /** + * Convenient method to convert java value to its closest representation + * in JavaScript. + *

    + * If value is an instance of String, Number, Boolean, Function or + * Scriptable, it is returned as it and will be treated as the corresponding + * JavaScript type of string, number, boolean, function and object. + *

    + * Note that for Number instances during any arithmetic operation in + * JavaScript the engine will always use the result of + * Number.doubleValue() resulting in a precision loss if + * the number can not fit into double. + *

    + * If value is an instance of Character, it will be converted to string of + * length 1 and its JavaScript type will be string. + *

    + * The rest of values will be wrapped as LiveConnect objects + * by calling {@link WrapFactory#wrap(Context cx, Scriptable scope, + * Object obj, Class staticType)} as in: + *

    +     *    Context cx = Context.getCurrentContext();
    +     *    return cx.getWrapFactory().wrap(cx, scope, value, null);
    +     * 
    + * + * @param value any Java object + * @param scope top scope object + * @return value suitable to pass to any API that takes JavaScript values. + */ + public static Object javaToJS(Object value, Scriptable scope) + { + return null; + } + + /** + * Convert a JavaScript value into the desired type. + * Uses the semantics defined with LiveConnect3 and throws an + * Illegal argument exception if the conversion cannot be performed. + * @param value the JavaScript value to convert + * @param desiredType the Java type to convert to. Primitive Java + * types are represented using the TYPE fields in the corresponding + * wrapper class in java.lang. + * @return the converted value + * @throws EvaluatorException if the conversion cannot be performed + */ + public static Object jsToJava(Object value, Class desiredType) + { + return null; + } + + /** + * Set the LiveConnect access filter for this context. + *

    {@link ClassShutter} may only be set if it is currently null. + * Otherwise a SecurityException is thrown. + * @param shutter a ClassShutter object + * @throws SecurityException if there is already a ClassShutter + * object for this Context + */ + public synchronized final void setClassShutter(ClassShutter shutter) + { + } + + final synchronized ClassShutter getClassShutter() + { + return null; + } + + public interface ClassShutterSetter { + public void setClassShutter(ClassShutter shutter); + public ClassShutter getClassShutter(); + } + + public final synchronized ClassShutterSetter getClassShutterSetter() { + return null; + } +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ContextFactory.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ContextFactory.java new file mode 100644 index 00000000000..a7f83f2095d --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ContextFactory.java @@ -0,0 +1,314 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * Factory class that Rhino runtime uses to create new {@link Context} + * instances. A ContextFactory can also notify listeners + * about context creation and release. + *

    + * When the Rhino runtime needs to create new {@link Context} instance during + * execution of {@link Context#enter()} or {@link Context}, it will call + * {@link #makeContext()} of the current global ContextFactory. + * See {@link #getGlobal()} and {@link #initGlobal(ContextFactory)}. + *

    + * It is also possible to use explicit ContextFactory instances for Context + * creation. This is useful to have a set of independent Rhino runtime + * instances under single JVM. See {@link #call(ContextAction)}. + *

    + * The following example demonstrates Context customization to terminate + * scripts running more then 10 seconds and to provide better compatibility + * with JavaScript code using MSIE-specific features. + *

    + * import org.mozilla.javascript.*;
    + *
    + * class MyFactory extends ContextFactory
    + * {
    + *
    + *     // Custom {@link Context} to store execution time.
    + *     private static class MyContext extends Context
    + *     {
    + *         long startTime;
    + *     }
    + *
    + *     static {
    + *         // Initialize GlobalFactory with custom factory
    + *         ContextFactory.initGlobal(new MyFactory());
    + *     }
    + *
    + *     // Override {@link #makeContext()}
    + *     protected Context makeContext()
    + *     {
    + *         MyContext cx = new MyContext();
    + *         // Make Rhino runtime to call observeInstructionCount
    + *         // each 10000 bytecode instructions
    + *         cx.setInstructionObserverThreshold(10000);
    + *         return cx;
    + *     }
    + *
    + *     // Override {@link #hasFeature(Context, int)}
    + *     public boolean hasFeature(Context cx, int featureIndex)
    + *     {
    + *         // Turn on maximum compatibility with MSIE scripts
    + *         switch (featureIndex) {
    + *             case {@link Context#FEATURE_NON_ECMA_GET_YEAR}:
    + *                 return true;
    + *
    + *             case {@link Context#FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME}:
    + *                 return true;
    + *
    + *             case {@link Context#FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER}:
    + *                 return true;
    + *
    + *             case {@link Context#FEATURE_PARENT_PROTO_PROPERTIES}:
    + *                 return false;
    + *         }
    + *         return super.hasFeature(cx, featureIndex);
    + *     }
    + *
    + *     // Override {@link #observeInstructionCount(Context, int)}
    + *     protected void observeInstructionCount(Context cx, int instructionCount)
    + *     {
    + *         MyContext mcx = (MyContext)cx;
    + *         long currentTime = System.currentTimeMillis();
    + *         if (currentTime - mcx.startTime > 10*1000) {
    + *             // More then 10 seconds from Context creation time:
    + *             // it is time to stop the script.
    + *             // Throw Error instance to ensure that script will never
    + *             // get control back through catch or finally.
    + *             throw new Error();
    + *         }
    + *     }
    + *
    + *     // Override {@link #doTopCall(Callable,
    +                               Context, Scriptable,
    +                               Scriptable, Object[])}
    + *     protected Object doTopCall(Callable callable,
    + *                                Context cx, Scriptable scope,
    + *                                Scriptable thisObj, Object[] args)
    + *     {
    + *         MyContext mcx = (MyContext)cx;
    + *         mcx.startTime = System.currentTimeMillis();
    + *
    + *         return super.doTopCall(callable, cx, scope, thisObj, args);
    + *     }
    + *
    + * }
    + * 
    + */ + +public class ContextFactory +{ + + /** + * Listener of {@link Context} creation and release events. + */ + public interface Listener + { + /** + * Notify about newly created {@link Context} object. + */ + public void contextCreated(Context cx); + + /** + * Notify that the specified {@link Context} instance is no longer + * associated with the current thread. + */ + public void contextReleased(Context cx); + } + + /** + * Get global ContextFactory. + * + * @see #hasExplicitGlobal() + * @see #initGlobal(ContextFactory) + */ + public static ContextFactory getGlobal() + { + return null; + } + + /** + * Check if global factory was set. + * Return true to indicate that {@link #initGlobal(ContextFactory)} was + * already called and false to indicate that the global factory was not + * explicitly set. + * + * @see #getGlobal() + * @see #initGlobal(ContextFactory) + */ + public static boolean hasExplicitGlobal() + { + return false; + } + + /** + * Set global ContextFactory. + * The method can only be called once. + * + * @see #getGlobal() + * @see #hasExplicitGlobal() + */ + public synchronized static void initGlobal(ContextFactory factory) + { + } + + public interface GlobalSetter { + public void setContextFactoryGlobal(ContextFactory factory); + public ContextFactory getContextFactoryGlobal(); + } + + public synchronized static GlobalSetter getGlobalSetter() { + return null; + } + + /** + * Create new {@link Context} instance to be associated with the current + * thread. + * This is a callback method used by Rhino to create {@link Context} + * instance when it is necessary to associate one with the current + * execution thread. makeContext() is allowed to call + * {@link Context#seal(Object)} on the result to prevent + * {@link Context} changes by hostile scripts or applets. + */ + protected Context makeContext() + { + return null; + } + + /** + * Implementation of {@link Context#hasFeature(int featureIndex)}. + * This can be used to customize {@link Context} without introducing + * additional subclasses. + */ + protected boolean hasFeature(Context cx, int featureIndex) + { + return false; + } + + /** + * Get ClassLoader to use when searching for Java classes. + * Unless it was explicitly initialized with + * {@link #initApplicationClassLoader(ClassLoader)} the method returns + * null to indicate that Thread.getContextClassLoader() should be used. + */ + public final ClassLoader getApplicationClassLoader() + { + return null; + } + + /** + * Set explicit class loader to use when searching for Java classes. + * + * @see #getApplicationClassLoader() + */ + public final void initApplicationClassLoader(ClassLoader loader) + { + } + + /** + * Checks if this is a sealed ContextFactory. + * @see #seal() + */ + public final boolean isSealed() + { + return false; + } + + /** + * Seal this ContextFactory so any attempt to modify it like to add or + * remove its listeners will throw an exception. + * @see #isSealed() + */ + public final void seal() + { + } + + /** + * Get a context associated with the current thread, creating one if need + * be. The Context stores the execution state of the JavaScript engine, so + * it is required that the context be entered before execution may begin. + * Once a thread has entered a Context, then getCurrentContext() may be + * called to find the context that is associated with the current thread. + *

    + * Calling enterContext() will return either the Context + * currently associated with the thread, or will create a new context and + * associate it with the current thread. Each call to + * enterContext() must have a matching call to + * {@link Context#exit()}. + *

    +     *      Context cx = contextFactory.enterContext();
    +     *      try {
    +     *          ...
    +     *          cx.evaluateString(...);
    +     *      } finally {
    +     *          Context.exit();
    +     *      }
    +     * 
    + * Instead of using enterContext(), exit() pair consider + * using {@link #call(ContextAction)} which guarantees proper association + * of Context instances with the current thread. + * With this method the above example becomes: + *
    +     *      ContextFactory.call(new ContextAction() {
    +     *          public Object run(Context cx) {
    +     *              ...
    +     *              cx.evaluateString(...);
    +     *              return null;
    +     *          }
    +     *      });
    +     * 
    + * @return a Context associated with the current thread + * @see Context#getCurrentContext() + * @see Context#exit() + * @see #call(ContextAction) + */ + public Context enterContext() + { + return null; + } + + /** + * @deprecated use {@link #enterContext()} instead + * @return a Context associated with the current thread + */ + @Deprecated + public final Context enter() + { + return null; + } + + /** + * @deprecated Use {@link Context#exit()} instead. + */ + @Deprecated + public final void exit() + { + } + + /** + * Get a Context associated with the current thread, using the given + * Context if need be. + *

    + * The same as enterContext() except that cx + * is associated with the current thread and returned if the current thread + * has no associated context and cx is not associated with any + * other thread. + * @param cx a Context to associate with the thread if possible + * @return a Context associated with the current thread + * @see #enterContext() + * @see #call(ContextAction) + * @throws IllegalStateException if cx is already associated + * with a different thread + */ + public final Context enterContext(Context cx) + { + return null; + } +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/DefiningClassLoader.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/DefiningClassLoader.java new file mode 100644 index 00000000000..3819798c351 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/DefiningClassLoader.java @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + package org.mozilla.javascript; + + /** + * Load generated classes. + * + * @author Norris Boyd + */ + public class DefiningClassLoader extends ClassLoader + implements GeneratedClassLoader + { + public DefiningClassLoader() { + } + + public DefiningClassLoader(ClassLoader parentLoader) { + } + + @Override + public Class defineClass(String name, byte[] data) { + return null; + } + + @Override + public void linkClass(Class cl) { + } + + @Override + public Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + return null; + } + } \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Function.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Function.java new file mode 100644 index 00000000000..a35a7c2dfba --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Function.java @@ -0,0 +1,46 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * This is interface that all functions in JavaScript must implement. The interface provides for + * calling functions and constructors. + * + * @see org.mozilla.javascript.Scriptable + * @author Norris Boyd + */ +public interface Function extends Scriptable { + /** + * Call the function. + * + *

    Note that the array of arguments is not guaranteed to have length greater than 0. + * + * @param cx the current Context for this thread + * @param scope the scope to execute the function relative to. This is set to the value returned + * by getParentScope() except when the function is called from a closure. + * @param thisObj the JavaScript this object + * @param args the array of arguments + * @return the result of the call + */ + Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args); + + /** + * Call the function as a constructor. + * + *

    This method is invoked by the runtime in order to satisfy a use of the JavaScript + * new operator. This method is expected to create a new object and return it. + * + * @param cx the current Context for this thread + * @param scope an enclosing scope of the caller except when the function is called from a + * closure. + * @param args the array of arguments + * @return the allocated object + */ + Scriptable construct(Context cx, Scriptable scope, Object[] args); +} \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/GeneratedClassLoader.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/GeneratedClassLoader.java new file mode 100644 index 00000000000..c7450862917 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/GeneratedClassLoader.java @@ -0,0 +1,34 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * Interface to define classes from generated byte code. + */ +public interface GeneratedClassLoader { + + /** + * Define a new Java class. + * Classes created via this method should have the same class loader. + * + * @param name fully qualified class name + * @param data class byte code + * @return new class object + */ + public Class defineClass(String name, byte[] data); + + /** + * Link the given class. + * + * @param cl Class instance returned from the previous call to + * {@link #defineClass(String, byte[])} + * @see java.lang.ClassLoader + */ + public void linkClass(Class cl); +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/RhinoException.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/RhinoException.java new file mode 100644 index 00000000000..b11befb4a63 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/RhinoException.java @@ -0,0 +1,15 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +package org.mozilla.javascript; + +/** + * The class of exceptions thrown by the JavaScript engine. + */ +public abstract class RhinoException extends RuntimeException +{ +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Script.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Script.java new file mode 100644 index 00000000000..824dc0241c1 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Script.java @@ -0,0 +1,41 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * All compiled scripts implement this interface. + *

    + * This class encapsulates script execution relative to an + * object scope. + * @since 1.3 + * @author Norris Boyd + */ + +public interface Script { + + /** + * Execute the script. + *

    + * The script is executed in a particular runtime Context, which + * must be associated with the current thread. + * The script is executed relative to a scope--definitions and + * uses of global top-level variables and functions will access + * properties of the scope object. For compliant ECMA + * programs, the scope must be an object that has been initialized + * as a global object using Context.initStandardObjects. + *

    + * + * @param cx the Context associated with the current thread + * @param scope the scope to execute relative to + * @return the result of executing the script + * @see org.mozilla.javascript.Context#initStandardObjects() + */ + public Object exec(Context cx, Scriptable scope); + +} \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Scriptable.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Scriptable.java new file mode 100644 index 00000000000..34616f7ad74 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Scriptable.java @@ -0,0 +1,304 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * This is interface that all objects in JavaScript must implement. + * The interface provides for the management of properties and for + * performing conversions. + *

    + * Host system implementors may find it easier to extend the ScriptableObject + * class rather than implementing Scriptable when writing host objects. + *

    + * There are many static methods defined in ScriptableObject that perform + * the multiple calls to the Scriptable interface needed in order to + * manipulate properties in prototype chains. + *

    + * + * @see org.mozilla.javascript.ScriptableObject + * @author Norris Boyd + * @author Nick Thompson + * @author Brendan Eich + */ + +public interface Scriptable { + + /** + * Get the name of the set of objects implemented by this Java class. + * This corresponds to the [[Class]] operation in ECMA and is used + * by Object.prototype.toString() in ECMA.

    + * See ECMA 8.6.2 and 15.2.4.2. + */ + public String getClassName(); + + /** + * Get a named property from the object. + * + * Looks property up in this object and returns the associated value + * if found. Returns NOT_FOUND if not found. + * Note that this method is not expected to traverse the prototype + * chain. This is different from the ECMA [[Get]] operation. + * + * Depending on the property selector, the runtime will call + * this method or the form of get that takes an + * integer: + * + * + * + * + * + * + * + * + * + * + *
    JavaScript codeJava code
    a.b a.get("b", a)
    a["foo"] a.get("foo", a)
    a[3] a.get(3, a)
    a["3"] a.get(3, a)
    a[3.0] a.get(3, a)
    a["3.0"] a.get("3.0", a)
    a[1.1] a.get("1.1", a)
    a[-4] a.get(-4, a)
    + *

    + * The values that may be returned are limited to the following: + *

      + *
    • java.lang.Boolean objects
    • + *
    • java.lang.String objects
    • + *
    • java.lang.Number objects
    • + *
    • org.mozilla.javascript.Scriptable objects
    • + *
    • null
    • + *
    • The value returned by Context.getUndefinedValue()
    • + *
    • NOT_FOUND
    • + *
    + * @param name the name of the property + * @param start the object in which the lookup began + * @return the value of the property (may be null), or NOT_FOUND + * @see org.mozilla.javascript.Context#getUndefinedValue + */ + public Object get(String name, Scriptable start); + + /** + * Get a property from the object selected by an integral index. + * + * Identical to get(String, Scriptable) except that + * an integral index is used to select the property. + * + * @param index the numeric index for the property + * @param start the object in which the lookup began + * @return the value of the property (may be null), or NOT_FOUND + * @see org.mozilla.javascript.Scriptable#get(String,Scriptable) + */ + public Object get(int index, Scriptable start); + + /** + * Indicates whether or not a named property is defined in an object. + * + * Does not traverse the prototype chain.

    + * + * The property is specified by a String name + * as defined for the get method.

    + * + * @param name the name of the property + * @param start the object in which the lookup began + * @return true if and only if the named property is found in the object + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#getProperty(Scriptable, String) + */ + public boolean has(String name, Scriptable start); + + /** + * Indicates whether or not an indexed property is defined in an object. + * + * Does not traverse the prototype chain.

    + * + * The property is specified by an integral index + * as defined for the get method.

    + * + * @param index the numeric index for the property + * @param start the object in which the lookup began + * @return true if and only if the indexed property is found in the object + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#getProperty(Scriptable, int) + */ + public boolean has(int index, Scriptable start); + + /** + * Sets a named property in this object. + *

    + * The property is specified by a string name + * as defined for get. + *

    + * The possible values that may be passed in are as defined for + * get. A class that implements this method may choose + * to ignore calls to set certain properties, in which case those + * properties are effectively read-only.

    + * For properties defined in a prototype chain, + * use putProperty in ScriptableObject.

    + * Note that if a property a is defined in the prototype p + * of an object o, then evaluating o.a = 23 will cause + * set to be called on the prototype p with + * o as the start parameter. + * To preserve JavaScript semantics, it is the Scriptable + * object's responsibility to modify o.

    + * This design allows properties to be defined in prototypes and implemented + * in terms of getters and setters of Java values without consuming slots + * in each instance. + *

    + * The values that may be set are limited to the following: + *

      + *
    • java.lang.Boolean objects
    • + *
    • java.lang.String objects
    • + *
    • java.lang.Number objects
    • + *
    • org.mozilla.javascript.Scriptable objects
    • + *
    • null
    • + *
    • The value returned by Context.getUndefinedValue()
    • + *

    + * Arbitrary Java objects may be wrapped in a Scriptable by first calling + * Context.toObject. This allows the property of a JavaScript + * object to contain an arbitrary Java object as a value.

    + * Note that has will be called by the runtime first before + * set is called to determine in which object the + * property is defined. + * Note that this method is not expected to traverse the prototype chain, + * which is different from the ECMA [[Put]] operation. + * @param name the name of the property + * @param start the object whose property is being set + * @param value value to set the property to + * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, String, Object) + * @see org.mozilla.javascript.Context#toObject(Object, Scriptable) + */ + public void put(String name, Scriptable start, Object value); + + /** + * Sets an indexed property in this object. + *

    + * The property is specified by an integral index + * as defined for get.

    + * + * Identical to put(String, Scriptable, Object) except that + * an integral index is used to select the property. + * + * @param index the numeric index for the property + * @param start the object whose property is being set + * @param value value to set the property to + * @see org.mozilla.javascript.Scriptable#has(int, Scriptable) + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, int, Object) + * @see org.mozilla.javascript.Context#toObject(Object, Scriptable) + */ + public void put(int index, Scriptable start, Object value); + + /** + * Removes a property from this object. + * This operation corresponds to the ECMA [[Delete]] except that + * the no result is returned. The runtime will guarantee that this + * method is called only if the property exists. After this method + * is called, the runtime will call Scriptable.has to see if the + * property has been removed in order to determine the boolean + * result of the delete operator as defined by ECMA 11.4.1. + *

    + * A property can be made permanent by ignoring calls to remove + * it.

    + * The property is specified by a String name + * as defined for get. + *

    + * To delete properties defined in a prototype chain, + * see deleteProperty in ScriptableObject. + * @param name the identifier for the property + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#deleteProperty(Scriptable, String) + */ + public void delete(String name); + + /** + * Removes a property from this object. + * + * The property is specified by an integral index + * as defined for get. + *

    + * To delete properties defined in a prototype chain, + * see deleteProperty in ScriptableObject. + * + * Identical to delete(String) except that + * an integral index is used to select the property. + * + * @param index the numeric index for the property + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#deleteProperty(Scriptable, int) + */ + public void delete(int index); + + /** + * Get the prototype of the object. + * @return the prototype + */ + public Scriptable getPrototype(); + + /** + * Set the prototype of the object. + * @param prototype the prototype to set + */ + public void setPrototype(Scriptable prototype); + + /** + * Get the parent scope of the object. + * @return the parent scope + */ + public Scriptable getParentScope(); + + /** + * Set the parent scope of the object. + * @param parent the parent scope to set + */ + public void setParentScope(Scriptable parent); + + /** + * Get an array of property ids. + * + * Not all property ids need be returned. Those properties + * whose ids are not returned are considered non-enumerable. + * + * @return an array of Objects. Each entry in the array is either + * a java.lang.String or a java.lang.Number + */ + public Object[] getIds(); + + /** + * Get the default value of the object with a given hint. + * The hints are String.class for type String, Number.class for type + * Number, Scriptable.class for type Object, and Boolean.class for + * type Boolean.

    + * + * A hint of null means "no hint". + * + * See ECMA 8.6.2.6. + * + * @param hint the type hint + * @return the default value + */ + public Object getDefaultValue(Class hint); + + /** + * The instanceof operator. + * + *

    + * The JavaScript code "lhs instanceof rhs" causes rhs.hasInstance(lhs) to + * be called. + * + *

    + * The return value is implementation dependent so that embedded host objects can + * return an appropriate value. See the JS 1.3 language documentation for more + * detail. + * + *

    This operator corresponds to the proposed EMCA [[HasInstance]] operator. + * + * @param instance The value that appeared on the LHS of the instanceof + * operator + * + * @return an implementation dependent value + */ + public boolean hasInstance(Scriptable instance); +} + diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ScriptableObject.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ScriptableObject.java new file mode 100644 index 00000000000..298c4fc7fb0 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ScriptableObject.java @@ -0,0 +1,27 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * This is the default implementation of the Scriptable interface. This + * class provides convenient default behavior that makes it easier to + * define host objects. + *

    + * Various properties and methods of JavaScript objects can be conveniently + * defined using methods of ScriptableObject. + *

    + * Classes extending ScriptableObject must define the getClassName method. + * + * @see org.mozilla.javascript.Scriptable + * @author Norris Boyd + */ + +public abstract class ScriptableObject implements Scriptable +{ +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/optimizer/ClassCompiler.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/optimizer/ClassCompiler.java new file mode 100644 index 00000000000..cb2332d3f61 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/optimizer/ClassCompiler.java @@ -0,0 +1,112 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + package org.mozilla.javascript.optimizer; + + import org.mozilla.javascript.CompilerEnvirons; + + /** + * Generates class files from script sources. + * + * since 1.5 Release 5 + * @author Igor Bukanov + */ + + public class ClassCompiler + { + /** + * Construct ClassCompiler that uses the specified compiler environment + * when generating classes. + */ + public ClassCompiler(CompilerEnvirons compilerEnv) + { + } + + /** + * Set the class name to use for main method implementation. + * The class must have a method matching + * public static void main(Script sc, String[] args), it will be + * called when main(String[] args) is called in the generated + * class. The class name should be fully qulified name and include the + * package name like in org.foo.Bar. + */ + public void setMainMethodClass(String className) + { + } + + /** + * Get the name of the class for main method implementation. + * @see #setMainMethodClass(String) + */ + public String getMainMethodClass() + { + return null; + } + + /** + * Get the compiler environment the compiler uses. + */ + public CompilerEnvirons getCompilerEnv() + { + return null; + } + + /** + * Get the class that the generated target will extend. + */ + public Class getTargetExtends() + { + return null; + } + + /** + * Set the class that the generated target will extend. + * + * @param extendsClass the class it extends + */ + public void setTargetExtends(Class extendsClass) + { + } + + /** + * Get the interfaces that the generated target will implement. + */ + public Class[] getTargetImplements() + { + return null; + } + + /** + * Set the interfaces that the generated target will implement. + * + * @param implementsClasses an array of Class objects, one for each + * interface the target will extend + */ + public void setTargetImplements(Class[] implementsClasses) + { + } + + /** + * Compile JavaScript source into one or more Java class files. + * The first compiled class will have name mainClassName. + * If the results of {@link #getTargetExtends()} or + * {@link #getTargetImplements()} are not null, then the first compiled + * class will extend the specified super class and implement + * specified interfaces. + * + * @return array where elements with even indexes specifies class name + * and the following odd index gives class file body as byte[] + * array. The initial element of the array always holds + * mainClassName and array[1] holds its byte code. + */ + public Object[] compileToClassFiles(String source, + String sourceLocation, + int lineno, + String mainClassName) + { + return null; + } + } \ No newline at end of file diff --git a/java/ql/test/library-tests/dataflow/taint-jackson/Test.java b/java/ql/test/library-tests/dataflow/taint-jackson/Test.java index 2caf9e4ee80..3be85336e26 100644 --- a/java/ql/test/library-tests/dataflow/taint-jackson/Test.java +++ b/java/ql/test/library-tests/dataflow/taint-jackson/Test.java @@ -3,33 +3,53 @@ import java.io.FileOutputStream; import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; +import java.util.Iterator; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.ObjectReader; class Test { + public static class Potato { + private String name; + + private String getName() { + return name; + } + } + public static String taint() { return "tainted"; } + public static void sink(Object any) {} + public static void jacksonObjectMapper() throws java.io.FileNotFoundException, java.io.UnsupportedEncodingException { String s = taint(); ObjectMapper om = new ObjectMapper(); File file = new File("testFile"); om.writeValue(file, s); + sink(file); //$hasTaintFlow OutputStream out = new FileOutputStream(file); om.writeValue(out, s); + sink(file); //$hasTaintFlow Writer writer = new StringWriter(); om.writeValue(writer, s); + sink(writer); //$hasTaintFlow JsonGenerator generator = new JsonFactory().createGenerator(new StringWriter()); om.writeValue(generator, s); + sink(generator); //$hasTaintFlow String t = om.writeValueAsString(s); - System.out.println(t); + sink(t); //$hasTaintFlow byte[] bs = om.writeValueAsBytes(s); String reconstructed = new String(bs, "utf-8"); - System.out.println(reconstructed); + sink(bs); //$hasTaintFlow + sink(reconstructed); //$hasTaintFlow } public static void jacksonObjectWriter() throws java.io.FileNotFoundException, java.io.UnsupportedEncodingException { @@ -37,16 +57,56 @@ class Test { ObjectWriter ow = new ObjectWriter(); File file = new File("testFile"); ow.writeValue(file, s); + sink(file); //$hasTaintFlow OutputStream out = new FileOutputStream(file); ow.writeValue(out, s); + sink(out); //$hasTaintFlow Writer writer = new StringWriter(); ow.writeValue(writer, s); + sink(writer); //$hasTaintFlow JsonGenerator generator = new JsonFactory().createGenerator(new StringWriter()); ow.writeValue(generator, s); + sink(generator); //$hasTaintFlow String t = ow.writeValueAsString(s); - System.out.println(t); + sink(t); //$hasTaintFlow byte[] bs = ow.writeValueAsBytes(s); String reconstructed = new String(bs, "utf-8"); - System.out.println(reconstructed); + sink(bs); //$hasTaintFlow + sink(reconstructed); //$hasTaintFlow + } + + public static void jacksonObjectReader() throws java.io.IOException { + String s = taint(); + ObjectMapper om = new ObjectMapper(); + ObjectReader reader = om.readerFor(Potato.class); + sink(reader.readValue(s)); //$hasTaintFlow + sink(reader.readValue(s, Potato.class).name); //$hasTaintFlow + sink(reader.readValue(s, Potato.class).getName()); //$hasTaintFlow + } + + public static void jacksonObjectReaderIterable() throws java.io.IOException { + String s = taint(); + ObjectMapper om = new ObjectMapper(); + ObjectReader reader = om.readerFor(Potato.class); + sink(reader.readValues(s)); //$hasTaintFlow + Iterator pIterator = reader.readValues(s, Potato.class); + while(pIterator.hasNext()) { + Potato p = pIterator.next(); + sink(p); //$hasTaintFlow + sink(p.name); //$hasTaintFlow + sink(p.getName()); //$hasTaintFlow + } + } + + public static void jacksonTwoStepDeserialization() throws java.io.IOException { + String s = taint(); + Map taintedParams = new HashMap<>(); + taintedParams.put("name", s); + ObjectMapper om = new ObjectMapper(); + JsonNode jn = om.valueToTree(taintedParams); + sink(jn); //$hasTaintFlow + Potato p = om.convertValue(jn, Potato.class); + sink(p); //$hasTaintFlow + sink(p.getName()); //$hasTaintFlow } } diff --git a/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.expected b/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.expected index 122b21d50fe..e69de29bb2d 100644 --- a/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.expected +++ b/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.expected @@ -1,48 +0,0 @@ -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:10:43:10:54 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:13:73:13:84 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:16:44:16:55 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:19:36:19:47 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:22:35:22:46 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:26:36:26:47 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:10:43:10:54 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:13:73:13:84 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:16:44:16:55 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:19:36:19:47 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:22:35:22:46 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:26:36:26:47 | value | -| Test.java:18:14:18:20 | taint(...) | -| Test.java:21:17:21:20 | file [post update] | -| Test.java:21:23:21:23 | s | -| Test.java:22:43:22:46 | file | -| Test.java:23:17:23:19 | out [post update] | -| Test.java:23:22:23:22 | s | -| Test.java:25:17:25:22 | writer [post update] | -| Test.java:25:25:25:25 | s | -| Test.java:27:17:27:25 | generator [post update] | -| Test.java:27:28:27:28 | s | -| Test.java:28:14:28:37 | writeValueAsString(...) | -| Test.java:28:36:28:36 | s | -| Test.java:29:22:29:22 | t | -| Test.java:30:15:30:37 | writeValueAsBytes(...) | -| Test.java:30:36:30:36 | s | -| Test.java:31:26:31:48 | new String(...) | -| Test.java:31:37:31:38 | bs | -| Test.java:32:22:32:34 | reconstructed | -| Test.java:36:14:36:20 | taint(...) | -| Test.java:39:17:39:20 | file [post update] | -| Test.java:39:23:39:23 | s | -| Test.java:40:43:40:46 | file | -| Test.java:41:17:41:19 | out [post update] | -| Test.java:41:22:41:22 | s | -| Test.java:43:17:43:22 | writer [post update] | -| Test.java:43:25:43:25 | s | -| Test.java:45:17:45:25 | generator [post update] | -| Test.java:45:28:45:28 | s | -| Test.java:46:14:46:37 | writeValueAsString(...) | -| Test.java:46:36:46:36 | s | -| Test.java:47:22:47:22 | t | -| Test.java:48:15:48:37 | writeValueAsBytes(...) | -| Test.java:48:36:48:36 | s | -| Test.java:49:26:49:48 | new String(...) | -| Test.java:49:37:49:38 | bs | -| Test.java:50:22:50:34 | reconstructed | diff --git a/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.ql b/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.ql index 333cf485f07..0836906530b 100644 --- a/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.ql +++ b/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.ql @@ -1,17 +1,34 @@ +import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking import semmle.code.java.dataflow.FlowSources +import TestUtilities.InlineExpectationsTest class Conf extends TaintTracking::Configuration { Conf() { this = "qltest:dataflow:jackson" } - override predicate isSource(DataFlow::Node source) { - source.asExpr().(MethodAccess).getMethod().hasName("taint") + override predicate isSource(DataFlow::Node n) { + n.asExpr().(MethodAccess).getMethod().hasName("taint") + or + n instanceof RemoteFlowSource } - override predicate isSink(DataFlow::Node sink) { any() } + override predicate isSink(DataFlow::Node n) { + exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument()) + } } -from DataFlow::Node source, DataFlow::Node sink, Conf config -where config.hasFlow(source, sink) -select sink +class HasFlowTest extends InlineExpectationsTest { + HasFlowTest() { this = "HasFlowTest" } + + override string getARelevantTag() { result = "hasTaintFlow" } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "hasTaintFlow" and + exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) | + sink.getLocation() = location and + element = sink.toString() and + value = "" + ) + } +} diff --git a/java/ql/test/library-tests/dataflow/taintsources/SpringSavedRequest.java b/java/ql/test/library-tests/dataflow/taintsources/SpringSavedRequest.java new file mode 100644 index 00000000000..a4ee8c018a2 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/taintsources/SpringSavedRequest.java @@ -0,0 +1,26 @@ +import org.springframework.security.web.savedrequest.SavedRequest; +import org.springframework.security.web.savedrequest.SimpleSavedRequest; + +public class SpringSavedRequest { + SavedRequest sr; + + public void test() { + sr.getRedirectUrl(); + sr.getCookies(); + sr.getHeaderValues("name"); + sr.getHeaderNames(); + sr.getParameterValues("name"); + sr.getParameterMap(); + } + + SimpleSavedRequest ssr; + + public void test2() { + ssr.getRedirectUrl(); + ssr.getCookies(); + ssr.getHeaderValues("name"); + ssr.getHeaderNames(); + ssr.getParameterValues("name"); + ssr.getParameterMap(); + } +} diff --git a/java/ql/test/library-tests/dataflow/taintsources/remote.expected b/java/ql/test/library-tests/dataflow/taintsources/remote.expected index ba615b628cc..f3b5685d4b9 100644 --- a/java/ql/test/library-tests/dataflow/taintsources/remote.expected +++ b/java/ql/test/library-tests/dataflow/taintsources/remote.expected @@ -47,3 +47,15 @@ | SpringMultiPart.java:21:3:21:26 | getFiles(...) | SpringMultiPart.java:21:3:21:26 | getFiles(...) | | SpringMultiPart.java:22:3:22:27 | getMultiFileMap(...) | SpringMultiPart.java:22:3:22:27 | getMultiFileMap(...) | | SpringMultiPart.java:23:3:23:41 | getMultipartContentType(...) | SpringMultiPart.java:23:3:23:41 | getMultipartContentType(...) | +| SpringSavedRequest.java:8:3:8:21 | getRedirectUrl(...) | SpringSavedRequest.java:8:3:8:21 | getRedirectUrl(...) | +| SpringSavedRequest.java:9:3:9:17 | getCookies(...) | SpringSavedRequest.java:9:3:9:17 | getCookies(...) | +| SpringSavedRequest.java:10:3:10:28 | getHeaderValues(...) | SpringSavedRequest.java:10:3:10:28 | getHeaderValues(...) | +| SpringSavedRequest.java:11:3:11:21 | getHeaderNames(...) | SpringSavedRequest.java:11:3:11:21 | getHeaderNames(...) | +| SpringSavedRequest.java:12:3:12:31 | getParameterValues(...) | SpringSavedRequest.java:12:3:12:31 | getParameterValues(...) | +| SpringSavedRequest.java:13:3:13:22 | getParameterMap(...) | SpringSavedRequest.java:13:3:13:22 | getParameterMap(...) | +| SpringSavedRequest.java:19:3:19:22 | getRedirectUrl(...) | SpringSavedRequest.java:19:3:19:22 | getRedirectUrl(...) | +| SpringSavedRequest.java:20:3:20:18 | getCookies(...) | SpringSavedRequest.java:20:3:20:18 | getCookies(...) | +| SpringSavedRequest.java:21:3:21:29 | getHeaderValues(...) | SpringSavedRequest.java:21:3:21:29 | getHeaderValues(...) | +| SpringSavedRequest.java:22:3:22:22 | getHeaderNames(...) | SpringSavedRequest.java:22:3:22:22 | getHeaderNames(...) | +| SpringSavedRequest.java:23:3:23:32 | getParameterValues(...) | SpringSavedRequest.java:23:3:23:32 | getParameterValues(...) | +| SpringSavedRequest.java:24:3:24:23 | getParameterMap(...) | SpringSavedRequest.java:24:3:24:23 | getParameterMap(...) | diff --git a/java/ql/test/library-tests/frameworks/guava/TestBase.java b/java/ql/test/library-tests/frameworks/guava/TestBase.java index 9ce907f4551..ad75f5cafbf 100644 --- a/java/ql/test/library-tests/frameworks/guava/TestBase.java +++ b/java/ql/test/library-tests/frameworks/guava/TestBase.java @@ -60,4 +60,10 @@ class TestBase { void test4() { sink(Preconditions.checkNotNull(taint())); // $numTaintFlow=1 } + + void test5() { + sink(MoreObjects.firstNonNull(taint(), taint())); // $numTaintFlow=2 + sink(MoreObjects.firstNonNull(null, taint())); // $numTaintFlow=1 + sink(MoreObjects.firstNonNull(taint(), null)); // $numTaintFlow=1 + } } diff --git a/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.expected b/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.expected index 12d09e0d65a..e76a4c6d1e4 100644 --- a/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.expected +++ b/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.expected @@ -1,2 +1,4 @@ -| CloseReader.java:11:42:11:71 | new FileReader(...) | This FileReader is not always closed on method exit. | -| CloseReader.java:44:6:44:40 | new FileInputStream(...) | This FileInputStream is not always closed on method exit. | +| CloseReader.java:18:42:18:71 | new FileReader(...) | This FileReader is not always closed on method exit. | +| CloseReader.java:23:20:23:50 | new FileInputStream(...) | This FileInputStream is not always closed on method exit. | +| CloseReader.java:33:6:33:40 | new FileInputStream(...) | This FileInputStream is not always closed on method exit. | +| CloseReader.java:43:21:43:43 | new ZipFile(...) | This ZipFile is not always closed on method exit. | diff --git a/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.java b/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.java index 7d78f8dbfef..b77afc49105 100644 --- a/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.java +++ b/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.java @@ -1,41 +1,30 @@ import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.Closeable; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; -import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.zip.ZipFile; class CloseReader { - public static void test1() throws IOException { + void test1() throws IOException { BufferedReader br = new BufferedReader(new FileReader("C:\\test.txt")); System.out.println(br.readLine()); } - public static void test2() throws FileNotFoundException, IOException { - BufferedReader br = null; - try { - br = new BufferedReader(new FileReader("C:\\test.txt")); - System.out.println(br.readLine()); - } - finally { - if(br != null) - br.close(); // 'br' is closed - } + void test2() throws IOException { + InputStream in = new FileInputStream("file.bin"); + in.read(); } - public static void test3() throws IOException { - BufferedReader br = null; - try { - br = new BufferedReader(new FileReader("C:\\test.txt")); - System.out.println(br.readLine()); - } - finally { - cleanup(br); // 'br' is closed within a helper method - } - } - - public static void test4() throws IOException { + void test3() throws IOException { InputStreamReader reader = null; try { // InputStreamReader may throw an exception, in which case the ... @@ -50,7 +39,35 @@ class CloseReader { } } - public static void test5() throws IOException { + void test4() throws IOException { + ZipFile zipFile = new ZipFile("file.zip"); + System.out.println(zipFile.getComment()); + } + + void testCorrect1() throws IOException { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader("C:\\test.txt")); + System.out.println(br.readLine()); + } + finally { + if(br != null) + br.close(); // 'br' is closed + } + } + + void testCorrect2() throws IOException { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader("C:\\test.txt")); + System.out.println(br.readLine()); + } + finally { + cleanup(br); // 'br' is closed within a helper method + } + } + + void testCorrect3() throws IOException { FileInputStream fis = null; InputStreamReader reader = null; try { @@ -66,7 +83,7 @@ class CloseReader { } } - public static void test6() throws IOException { + void testCorrect4() throws IOException { BufferedReader br = null; try { br = new BufferedReader(new FileReader("C:\\test.txt")); @@ -77,15 +94,15 @@ class CloseReader { } } - public static void cleanup(java.io.Closeable... closeables) throws IOException { - for (java.io.Closeable c : closeables) { + void cleanup(Closeable... closeables) throws IOException { + for (Closeable c : closeables) { if (c != null) { c.close(); } } } - public static class LogFile { + static class LogFile { private BufferedReader fileRd; LogFile(String path) { FileReader fr = null; @@ -100,9 +117,21 @@ class CloseReader { private void init(InputStreamReader reader) { fileRd = new BufferedReader(reader); } - public void readStuff() throws java.io.IOException { + public void readStuff() throws IOException { System.out.println(fileRd.readLine()); fileRd.close(); } } + + // Classes which should be ignored + void testIgnore() throws IOException { + Reader r1 = new CharArrayReader(new char[] {'a'}); + r1.read(); + + Reader r2 = new StringReader("a"); + r2.read(); + + InputStream i1 = new ByteArrayInputStream(new byte[] {1}); + i1.read(); + } } diff --git a/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.qlref b/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.qlref index 3c76645e809..1c808bb9f46 100644 --- a/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.qlref +++ b/java/ql/test/query-tests/CloseResource/CloseReader/CloseReader.qlref @@ -1 +1 @@ -Likely Bugs/Resource Leaks/CloseReader.ql \ No newline at end of file +Likely Bugs/Resource Leaks/CloseReader.ql diff --git a/java/ql/test/query-tests/CloseResource/CloseWriter/CloseWriter.expected b/java/ql/test/query-tests/CloseResource/CloseWriter/CloseWriter.expected new file mode 100644 index 00000000000..efbfe15f2c9 --- /dev/null +++ b/java/ql/test/query-tests/CloseResource/CloseWriter/CloseWriter.expected @@ -0,0 +1,3 @@ +| CloseWriter.java:17:42:17:71 | new FileWriter(...) | This FileWriter is not always closed on method exit. | +| CloseWriter.java:22:22:22:53 | new FileOutputStream(...) | This FileOutputStream is not always closed on method exit. | +| CloseWriter.java:32:6:32:41 | new FileOutputStream(...) | This FileOutputStream is not always closed on method exit. | diff --git a/java/ql/test/query-tests/CloseResource/CloseWriter/CloseWriter.java b/java/ql/test/query-tests/CloseResource/CloseWriter/CloseWriter.java new file mode 100644 index 00000000000..3733237b8de --- /dev/null +++ b/java/ql/test/query-tests/CloseResource/CloseWriter/CloseWriter.java @@ -0,0 +1,131 @@ +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.Closeable; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.zip.ZipFile; + +class CloseWriter { + + void test1() throws IOException { + BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\test.txt")); + bw.write("test"); + } + + void test2() throws IOException { + OutputStream out = new FileOutputStream("test.bin"); + out.write(1); + } + + void test3() throws IOException { + OutputStreamWriter writer = null; + try { + // OutputStreamWriter may throw an exception, in which case the ... + writer = new OutputStreamWriter( + // ... FileOutputStream is not closed by the finally block + new FileOutputStream("C:\\test.txt"), "UTF-8"); + writer.write("test"); + } + finally { + if (writer != null) + writer.close(); + } + } + + void testCorrect1() throws IOException { + BufferedWriter bw = null; + try { + bw = new BufferedWriter(new FileWriter("C:\\test.txt")); + bw.write("test"); + } + finally { + if(bw != null) + bw.close(); // 'bw' is closed + } + } + + void testCorrect2() throws IOException { + BufferedWriter bw = null; + try { + bw = new BufferedWriter(new FileWriter("C:\\test.txt")); + bw.write("test"); + } + finally { + cleanup(bw); // 'bw' is closed within a helper method + } + } + + void testCorrect3() throws IOException { + FileOutputStream fos = null; + OutputStreamWriter writer = null; + try { + fos = new FileOutputStream("C:\\test.txt"); + writer = new OutputStreamWriter(fos); + writer.write("test"); + } + finally { + if (fos != null) + fos.close(); // 'fos' is closed + if (writer != null) + writer.close(); // 'writer' is closed + } + } + + void testCorrect4() throws IOException { + BufferedWriter bw = null; + try { + bw = new BufferedWriter(new FileWriter("C:\\test.txt")); + bw.write("test"); + } + finally { + cleanup(null, bw); // 'bw' is closed within a varargs helper method, invoked with multiple args + } + } + + void cleanup(Closeable... closeables) throws IOException { + for (Closeable c : closeables) { + if (c != null) { + c.close(); + } + } + } + + static class LogFile { + private BufferedWriter fileWr; + LogFile(String path) { + FileWriter fw = null; + try { + fw = new FileWriter(path); + } catch (IOException e) { + System.out.println("Error: File not readable: " + path); + System.exit(1); + } + init(fw); + } + private void init(OutputStreamWriter writer) { + fileWr = new BufferedWriter(writer); + } + public void writeStuff() throws IOException { + fileWr.write("test"); + fileWr.close(); + } + } + + // Classes which should be ignored + void testIgnore() throws IOException { + Writer w1 = new CharArrayWriter(); + w1.write("test"); + + Writer w2 = new StringWriter(); + w2.write("test"); + + OutputStream o1 = new ByteArrayOutputStream(); + o1.write(1); + } +} diff --git a/java/ql/test/query-tests/CloseResource/CloseWriter/CloseWriter.qlref b/java/ql/test/query-tests/CloseResource/CloseWriter/CloseWriter.qlref new file mode 100644 index 00000000000..88008367363 --- /dev/null +++ b/java/ql/test/query-tests/CloseResource/CloseWriter/CloseWriter.qlref @@ -0,0 +1 @@ +Likely Bugs/Resource Leaks/CloseWriter.ql diff --git a/java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.expected b/java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.expected index ed67a987e84..c800f627c64 100644 --- a/java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.expected +++ b/java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.expected @@ -1,19 +1,15 @@ edges | XSS.java:23:21:23:48 | getParameter(...) : String | XSS.java:23:5:23:70 | ... + ... | -| XSS.java:27:21:27:48 | getParameter(...) : String | XSS.java:27:5:27:70 | ... + ... | | XSS.java:38:67:38:87 | getPathInfo(...) : String | XSS.java:38:30:38:87 | ... + ... | | XSS.java:41:36:41:56 | getPathInfo(...) : String | XSS.java:41:36:41:67 | getBytes(...) | nodes | XSS.java:23:5:23:70 | ... + ... | semmle.label | ... + ... | | XSS.java:23:21:23:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | -| XSS.java:27:5:27:70 | ... + ... | semmle.label | ... + ... | -| XSS.java:27:21:27:48 | getParameter(...) : String | semmle.label | getParameter(...) : String | | XSS.java:38:30:38:87 | ... + ... | semmle.label | ... + ... | | XSS.java:38:67:38:87 | getPathInfo(...) : String | semmle.label | getPathInfo(...) : String | | XSS.java:41:36:41:56 | getPathInfo(...) : String | semmle.label | getPathInfo(...) : String | | XSS.java:41:36:41:67 | getBytes(...) | semmle.label | getBytes(...) | #select | XSS.java:23:5:23:70 | ... + ... | XSS.java:23:21:23:48 | getParameter(...) : String | XSS.java:23:5:23:70 | ... + ... | Cross-site scripting vulnerability due to $@. | XSS.java:23:21:23:48 | getParameter(...) | user-provided value | -| XSS.java:27:5:27:70 | ... + ... | XSS.java:27:21:27:48 | getParameter(...) : String | XSS.java:27:5:27:70 | ... + ... | Cross-site scripting vulnerability due to $@. | XSS.java:27:21:27:48 | getParameter(...) | user-provided value | | XSS.java:38:30:38:87 | ... + ... | XSS.java:38:67:38:87 | getPathInfo(...) : String | XSS.java:38:30:38:87 | ... + ... | Cross-site scripting vulnerability due to $@. | XSS.java:38:67:38:87 | getPathInfo(...) | user-provided value | | XSS.java:41:36:41:67 | getBytes(...) | XSS.java:41:36:41:56 | getPathInfo(...) : String | XSS.java:41:36:41:67 | getBytes(...) | Cross-site scripting vulnerability due to $@. | XSS.java:41:36:41:56 | getPathInfo(...) | user-provided value | diff --git a/java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.java b/java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.java index 54e60a13086..3ebaac47819 100644 --- a/java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.java +++ b/java/ql/test/query-tests/security/CWE-079/semmle/tests/XSS.java @@ -22,7 +22,7 @@ public class XSS extends HttpServlet { response.getWriter().print( "The page \"" + request.getParameter("page") + "\" was not found."); - // BAD: a request parameter is written directly to an error response page + // GOOD: servlet API encodes the error message HTML for the HTML context response.sendError(HttpServletResponse.SC_NOT_FOUND, "The page \"" + request.getParameter("page") + "\" was not found."); @@ -30,7 +30,7 @@ public class XSS extends HttpServlet { response.sendError(HttpServletResponse.SC_NOT_FOUND, "The page \"" + encodeForHtml(request.getParameter("page")) + "\" was not found."); - // FALSE NEGATIVE: passed through function that is not a secure check + // GOOD: servlet API encodes the error message HTML for the HTML context response.sendError(HttpServletResponse.SC_NOT_FOUND, "The page \"" + capitalizeName(request.getParameter("page")) + "\" was not found."); diff --git a/java/ql/test/query-tests/security/CWE-502/KryoTest.java b/java/ql/test/query-tests/security/CWE-502/KryoTest.java new file mode 100644 index 00000000000..8890bad91b9 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-502/KryoTest.java @@ -0,0 +1,34 @@ + +import java.io.*; +import java.net.Socket; +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.pool.KryoPool; +import com.esotericsoftware.kryo.io.Input; + +public class KryoTest { + + private Kryo getSafeKryo() { + Kryo kryo = new Kryo(); + kryo.setRegistrationRequired(true); + // ... kryo.register(A.class) ... + return kryo; + } + + public void kryoDeserialize(Socket sock) throws java.io.IOException { + KryoPool kryoPool = new KryoPool.Builder(this::getSafeKryo).softReferences().build(); + Input input = new Input(sock.getInputStream()); + Object o = kryoPool.run(kryo -> kryo.readClassAndObject(input)); // OK + } + + public void kryoDeserialize2(Socket sock) throws java.io.IOException { + KryoPool kryoPool = new KryoPool.Builder(this::getSafeKryo).softReferences().build(); + Input input = new Input(sock.getInputStream()); + Kryo k = kryoPool.borrow(); + try { + Object o = k.readClassAndObject(input); // OK + } finally { + kryoPool.release(k); + } + } + +} diff --git a/java/ql/test/stubs/guava-30.0/com/google/common/base/MoreObjects.java b/java/ql/test/stubs/guava-30.0/com/google/common/base/MoreObjects.java new file mode 100644 index 00000000000..3f4912b021b --- /dev/null +++ b/java/ql/test/stubs/guava-30.0/com/google/common/base/MoreObjects.java @@ -0,0 +1,9 @@ +package com.google.common.base; + +import org.checkerframework.checker.nullness.qual.Nullable; + +public final class MoreObjects { + public static T firstNonNull(@Nullable T first, @Nullable T second) { + return null; + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java index a26eb2592c6..b04572cd4da 100644 --- a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java @@ -1,6 +1,8 @@ package com.fasterxml.jackson.databind; -public class JsonNode { +import java.util.*; + +public abstract class JsonNode implements Iterable { public JsonNode() { } -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java new file mode 100644 index 00000000000..ac427ef01c9 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java @@ -0,0 +1,28 @@ +package com.fasterxml.jackson.databind; + +import java.io.Closeable; +import java.io.IOException; +import java.util.*; + +public class MappingIterator implements Iterator, Closeable { + + @Override + public boolean hasNext() { + return false; + } + + @Override + public T next() { + return null; + } + + @Override + public void remove() { + + } + + @Override + public void close() throws IOException { + + } +} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java index 455e0c0d309..71dc99a351d 100644 --- a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java @@ -26,4 +26,16 @@ public class ObjectMapper { public String writeValueAsString(Object value) { return null; } + + public ObjectReader readerFor(Class type) { + return null; + } + + public T valueToTree(Object fromValue) throws IllegalArgumentException { + return null; + } + + public T convertValue(Object fromValue, Class toValueType) throws IllegalArgumentException { + return null; + } } diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectReader.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectReader.java new file mode 100644 index 00000000000..f067a3e95a4 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectReader.java @@ -0,0 +1,82 @@ +package com.fasterxml.jackson.databind; + +import java.io.*; + +public class ObjectReader { + public ObjectReader forType(Class valueType) { + return null; + } + + public T readValue(String src) { + return null; + } + + public T readValue(String src, Class valueType) throws IOException { + return null; + } + + public T readValue(byte[] content) throws IOException { + return null; + } + + public T readValue(byte[] content, Class valueType) throws IOException { + return null; + } + + public T readValue(File src) throws IOException { + return null; + } + + public T readValue(InputStream src) throws IOException { + return null; + } + + public T readValue(InputStream src, Class valueType) throws IOException { + return null; + } + + public T readValue(Reader src) throws IOException { + return null; + } + + public T readValue(Reader src, Class valueType) throws IOException { + return null; + } + + public MappingIterator readValues(String src) { + return null; + } + + public MappingIterator readValues(String src, Class valueType) throws IOException { + return null; + } + + public MappingIterator readValues(byte[] content) throws IOException { + return null; + } + + public MappingIterator readValues(byte[] content, Class valueType) throws IOException { + return null; + } + + public MappingIterator readValues(File src) throws IOException { + return null; + } + + public MappingIterator readValues(InputStream src) throws IOException { + return null; + } + + public MappingIterator readValues(InputStream src, Class valueType) throws IOException { + return null; + } + + public MappingIterator readValues(Reader src) throws IOException { + return null; + } + + public MappingIterator readValues(Reader src, Class valueType) throws IOException { + return null; + } + +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/antlr/base/mod.java b/java/ql/test/stubs/jython-2.7.2/org/python/antlr/base/mod.java new file mode 100644 index 00000000000..785212f62fa --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/antlr/base/mod.java @@ -0,0 +1,5 @@ +// Autogenerated AST node +package org.python.antlr.base; + +public abstract class mod { +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/BytecodeLoader.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/BytecodeLoader.java new file mode 100644 index 00000000000..e414216ed03 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/BytecodeLoader.java @@ -0,0 +1,47 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +import java.util.List; + +/** + * Utility class for loading compiled Python modules and Java classes defined in Python modules. + */ +public class BytecodeLoader { + + /** + * Turn the Java class file data into a Java class. + * + * @param name fully-qualified binary name of the class + * @param data a class file as a byte array + * @param referents super-classes and interfaces that the new class will reference. + */ + @SuppressWarnings("unchecked") + public static Class makeClass(String name, byte[] data, Class... referents) { + return null; + } + + /** + * Turn the Java class file data into a Java class. + * + * @param name the name of the class + * @param referents super-classes and interfaces that the new class will reference. + * @param data a class file as a byte array + */ + public static Class makeClass(String name, List> referents, byte[] data) { + return null; + } + + /** + * Turn the Java class file data for a compiled Python module into a {@code PyCode} object, by + * constructing an instance of the named class and calling the instance's + * {@link PyRunnable#getMain()}. + * + * @param name fully-qualified binary name of the class + * @param data a class file as a byte array + * @param filename to provide to the constructor of the named class + * @return the {@code PyCode} object produced by the named class' {@code getMain} + */ + public static PyCode makeCode(String name, byte[] data, String filename) { + return null; + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/CompileMode.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/CompileMode.java new file mode 100644 index 00000000000..cf7ad1e7201 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/CompileMode.java @@ -0,0 +1,11 @@ +package org.python.core; + +public enum CompileMode { + eval, + single, + exec; + + public static CompileMode getMode(String mode) { + return null; + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/CompilerFlags.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/CompilerFlags.java new file mode 100644 index 00000000000..916b93c84ea --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/CompilerFlags.java @@ -0,0 +1,17 @@ +// At some future point this will also be extended - in conjunction with +// Py#compileFlags - to add +// support for a compiler factory that user code can choose in place of the +// normal compiler. +// (Perhaps a better name might have been "CompilerOptions".) + +package org.python.core; + +import java.io.Serializable; + +public class CompilerFlags implements Serializable { + public CompilerFlags() { + } + + public CompilerFlags(int co_flags) { + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/Py.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/Py.java new file mode 100644 index 00000000000..cc0c9f1e4bd --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/Py.java @@ -0,0 +1,134 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +import java.io.InputStream; +import java.io.Serializable; + +import org.python.antlr.base.mod; + +public final class Py { + /** + Convert a given PyObject to an instance of a Java class. + Identical to o.__tojava__(c) except that it will + raise a TypeError if the conversion fails. + @param o the PyObject to convert. + @param c the class to convert it to. + **/ + @SuppressWarnings("unchecked") + public static T tojava(PyObject o, Class c) { + return null; + } + + // ??pending: was @deprecated but is actually used by proxie code. + // Can get rid of it? + public static Object tojava(PyObject o, String s) { + return null; + } + + /** + * Uses the PyObjectAdapter passed to {@link PySystemState#initialize} to turn o into a PyObject. + * + * @see ClassicPyObjectAdapter - default PyObjectAdapter type + */ + public static PyObject java2py(Object o) { + return null; + } + + /** + * Uses the PyObjectAdapter passed to {@link PySystemState#initialize} to turn + * objects into an array of PyObjects. + * + * @see ClassicPyObjectAdapter - default PyObjectAdapter type + */ + public static PyObject[] javas2pys(Object... objects) { + return null; + } + + public static PyObject makeClass(String name, PyObject[] bases, PyCode code, + PyObject[] closure_cells) { + return null; + } + + public static PyObject makeClass(String name, PyObject base, PyObject dict) { + return null; + } + + /** + * Create a new Python class. + * + * @param name the String name of the class + * @param bases an array of PyObject base classes + * @param dict the class's namespace, containing the class body + * definition + * @return a new Python Class PyObject + */ + public static PyObject makeClass(String name, PyObject[] bases, PyObject dict) { + return null; + } + + public static CompilerFlags getCompilerFlags() { + return null; + } + + public static CompilerFlags getCompilerFlags(int flags, boolean dont_inherit) { + return null; + } + + public static CompilerFlags getCompilerFlags(CompilerFlags flags, boolean dont_inherit) { + return null; + } + + // w/o compiler-flags + public static PyCode compile(InputStream istream, String filename, CompileMode kind) { + return null; + } + + /** + * Entry point for compiling modules. + * + * @param node Module node, coming from the parsing process + * @param name Internal name for the compiled code. Typically generated by + * calling {@link #getName()}. + * @param filename Source file name + * @param linenumbers True to track source line numbers on the generated + * code + * @param printResults True to call the sys.displayhook on the result of + * the code + * @param cflags Compiler flags + * @return Code object for the compiled module + */ + public static PyCode compile_flags(mod node, String name, String filename, + boolean linenumbers, boolean printResults, + CompilerFlags cflags) { + return null; + } + + public static PyCode compile_flags(mod node, String filename, + CompileMode kind, CompilerFlags cflags) { + return null; + } + + /** + * Compiles python source code coming from a file or another external stream + */ + public static PyCode compile_flags(InputStream istream, String filename, + CompileMode kind, CompilerFlags cflags) { + return null; + } + + /** + * Compiles python source code coming from String (raw bytes) data. + * + * If the String is properly decoded (from PyUnicode) the PyCF_SOURCE_IS_UTF8 flag + * should be specified. + */ + public static PyCode compile_flags(String data, String filename, + CompileMode kind, CompilerFlags cflags) { + return null; + } + + public static PyObject compile_command_flags(String string, String filename, + CompileMode kind, CompilerFlags cflags, boolean stdprompt) { + return null; + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/PyCode.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyCode.java new file mode 100644 index 00000000000..9b7c99f94fa --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyCode.java @@ -0,0 +1,43 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +/** + * A super class for all python code implementations. + */ +public abstract class PyCode extends PyObject +{ + abstract public PyObject call(ThreadState state, + PyObject args[], String keywords[], + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject self, PyObject args[], + String keywords[], + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject arg1, PyObject globals, + PyObject[] defaults, PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject arg1, PyObject arg2, + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject arg1, PyObject arg2, PyObject arg3, + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject arg1, PyObject arg2, PyObject arg3, PyObject arg4, + PyObject globals, PyObject[] defaults, + PyObject closure); + +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/PyException.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyException.java new file mode 100644 index 00000000000..3a0a6c52c69 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyException.java @@ -0,0 +1,12 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; +import java.io.*; + +/** + * A wrapper for all python exception. Note that the well-known python exceptions are not + * subclasses of PyException. Instead the python exception class is stored in the type + * field and value or class instance is stored in the value field. + */ +public class PyException extends RuntimeException +{ +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/PyObject.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyObject.java new file mode 100644 index 00000000000..00993123461 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyObject.java @@ -0,0 +1,11 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +import java.io.Serializable; + +/** + * All objects known to the Jython runtime system are represented by an instance of the class + * {@code PyObject} or one of its subclasses. + */ +public class PyObject implements Serializable { +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/PySystemState.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/PySystemState.java new file mode 100644 index 00000000000..8444bbba70e --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/PySystemState.java @@ -0,0 +1,177 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +/** + * The "sys" module. + */ +// xxx Many have lamented, this should really be a module! +// but it will require some refactoring to see this wish come true. +public class PySystemState extends PyObject { + public PySystemState() { + } + + public static void classDictInit(PyObject dict) { + } + + public ClassLoader getSyspathJavaLoader() { + return null; + } + + // xxx fix this accessors + public PyObject __findattr_ex__(String name) { + return null; + } + + public void __setattr__(String name, PyObject value) { + } + + public void __delattr__(String name) { + } + + public PyObject gettrace() { + return null; + } + + public void settrace(PyObject tracefunc) { + } + + /** + * Change the current working directory to the specified path. + * + * path is assumed to be absolute and canonical (via os.path.realpath). + * + * @param path a path String + */ + public void setCurrentWorkingDir(String path) { + } + + /** + * Return a string representing the current working directory. + * + * @return a path String + */ + public String getCurrentWorkingDir() { + return null; + } + + /** + * Resolve a path. Returns the full path taking the current working directory into account. + * + * @param path a path String + * @return a resolved path String + */ + public String getPath(String path) { + return null; + } + + /** + * Resolve a path, returning a {@link File}, taking the current working directory into account. + * + * @param path a path String + * @return a resolved File + */ + public File getFile(String path) { + return null; + } + + public ClassLoader getClassLoader() { + return null; + } + + public void setClassLoader(ClassLoader classLoader) { + } + + public static Properties getBaseProperties() { + return null; + } + + public static synchronized void initialize() { + } + + public static synchronized void initialize(Properties preProperties, + Properties postProperties) { + } + + public static synchronized void initialize(Properties preProperties, Properties postProperties, + String[] argv) { + } + + public static synchronized void initialize(Properties preProperties, Properties postProperties, + String[] argv, ClassLoader classLoader) { + } + + /** + * Add a classpath directory to the list of places that are searched for java packages. + *

    + * Note. Classes found in directory and sub-directory are not made available to jython by + * this call. It only makes the java package found in the directory available. This call is + * mostly useful if jython is embedded in an application that deals with its own class loaders. + * A servlet container is a very good example. Calling + * {@code add_classdir("/WEB-INF/classes")} makes the java packages in WEB-INF classes + * available to jython import. However the actual class loading is completely handled by the + * servlet container's context classloader. + */ + public static void add_classdir(String directoryPath) { + } + + /** + * Add a .jar and .zip directory to the list of places that are searched for java .jar and .zip + * files. The .jar and .zip files found will not be cached. + *

    + * Note. Classes in .jar and .zip files found in the directory are not made available to + * jython by this call. See the note for add_classdir(dir) for more details. + * + * @param directoryPath The name of a directory. + * + * @see #add_classdir + */ + public static void add_extdir(String directoryPath) { + } + + /** + * Add a .jar and .zip directory to the list of places that are searched for java .jar and .zip + * files. + *

    + * Note. Classes in .jar and .zip files found in the directory are not made available to + * jython by this call. See the note for add_classdir(dir) for more details. + * + * @param directoryPath The name of a directory. + * @param cache Controls if the packages in the zip and jar file should be cached. + * + * @see #add_classdir + */ + public static void add_extdir(String directoryPath, boolean cache) { + } + + // Not public by design. We can't rebind the displayhook if + // a reflected function is inserted in the class dict. + + /** + * Exit a Python program with the given status. + * + * @param status the value to exit with + * @throws PyException {@code SystemExit} always throws this exception. When caught at top level + * the program will exit. + */ + public static void exit(PyObject status) { + } + + /** + * Exit a Python program with the status 0. + */ + public static void exit() { + } + + public static void exc_clear() { + } + + public void cleanup() { + } + + public void close() { + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/ThreadState.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/ThreadState.java new file mode 100644 index 00000000000..920270fe053 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/ThreadState.java @@ -0,0 +1,28 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +// a ThreadState refers to one PySystemState; this weak ref allows for tracking all ThreadState objects +// that refer to a given PySystemState + +public class ThreadState { + + public PyException exception; + + public ThreadState(PySystemState systemState) { + setSystemState(systemState); + } + + public void setSystemState(PySystemState systemState) { + } + + public PySystemState getSystemState() { + return null; + } + + public boolean enterRepr(PyObject obj) { + return false; + } + + public void exitRepr(PyObject obj) { + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/util/InteractiveInterpreter.java b/java/ql/test/stubs/jython-2.7.2/org/python/util/InteractiveInterpreter.java new file mode 100644 index 00000000000..b12fa617227 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/util/InteractiveInterpreter.java @@ -0,0 +1,114 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.util; + +import org.python.core.*; + +/** + * This class provides the interface for compiling and running code that supports an interactive + * interpreter. + */ +// Based on CPython-1.5.2's code module +public class InteractiveInterpreter extends PythonInterpreter { + + /** + * Construct an InteractiveInterpreter with all default characteristics: default state (from + * {@link Py#getSystemState()}), and a new empty dictionary of local variables. + * */ + public InteractiveInterpreter() { + } + + /** + * Construct an InteractiveInterpreter with state (from {@link Py#getSystemState()}), and the + * specified dictionary of local variables. + * + * @param locals dictionary to use, or if null, a new empty one will be created + */ + public InteractiveInterpreter(PyObject locals) { + } + + /** + * Construct an InteractiveInterpreter with, and system state the specified dictionary of local + * variables. + * + * @param locals dictionary to use, or if null, a new empty one will be created + * @param systemState interpreter state, or if null use {@link Py#getSystemState()} + */ + public InteractiveInterpreter(PyObject locals, PySystemState systemState) { + } + + /** + * Compile and run some source in the interpreter, in the mode {@link CompileMode#single} which + * is used for incremental compilation at the interactive console, known as {@code }. + * + * @param source Python code + * @return true to indicate a partial statement was entered + */ + public boolean runsource(String source) { + return false; + } + + /** + * Compile and run some source in the interpreter, in the mode {@link CompileMode#single} which + * is used for incremental compilation at the interactive console. + * + * @param source Python code + * @param filename name with which to label this console input (e.g. in error messages). + * @return true to indicate a partial statement was entered + */ + public boolean runsource(String source, String filename) { + return false; + } + + /** + * Compile and run some source in the interpreter, according to the {@link CompileMode} given. + * This method supports incremental compilation and interpretation through the return value, + * where {@code true} signifies that more input is expected in order to complete the Python + * statement. An interpreter can use this to decide whether to use {@code sys.ps1} + * ("{@code >>> }") or {@code sys.ps2} ("{@code ... }") to prompt the next line. The arguments + * are the same as the mandatory ones in the Python {@code compile()} command. + *

    + * One the following can happen: + *

      + *
    1. The input is incorrect; compilation raised an exception (SyntaxError or OverflowError). A + * syntax traceback will be printed by calling {@link #showexception(PyException)}. Return is + * {@code false}.
    2. + * + *
    3. The input is incomplete, and more input is required; compilation returned no code. + * Nothing happens. Return is {@code true}.
    4. + * + *
    5. The input is complete; compilation returned a code object. The code is executed by + * calling {@link #runcode(PyObject)} (which also handles run-time exceptions, except for + * SystemExit). Return is {@code false}.
    6. + *
    + * + * @param source Python code + * @param filename name with which to label this console input (e.g. in error messages). + * @param kind of compilation required: {@link CompileMode#eval}, {@link CompileMode#exec} or + * {@link CompileMode#single} + * @return {@code true} to indicate a partial statement was provided + */ + public boolean runsource(String source, String filename, CompileMode kind) { + return false; + } + + /** + * Execute a code object. When an exception occurs, {@link #showexception(PyException)} is + * called to display a stack trace, except in the case of SystemExit, which is re-raised. + *

    + * A note about KeyboardInterrupt: this exception may occur elsewhere in this code, and may not + * always be caught. The caller should be prepared to deal with it. + **/ + + // Make this run in another thread somehow???? + public void runcode(PyObject code) { + } + + public void showexception(PyException exc) { + } + + public void write(String data) { + } + + public void resetbuffer() { + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/util/PythonInterpreter.java b/java/ql/test/stubs/jython-2.7.2/org/python/util/PythonInterpreter.java new file mode 100644 index 00000000000..92c50917b59 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/util/PythonInterpreter.java @@ -0,0 +1,252 @@ +package org.python.util; + +import java.io.Closeable; +import java.io.Reader; +import java.io.StringReader; +import java.util.Properties; + +import org.python.core.PyCode; +import org.python.core.PyObject; + +/** + * The PythonInterpreter class is a standard wrapper for a Jython interpreter for embedding in a + * Java application. + */ +public class PythonInterpreter implements Closeable { + + /** + * Initializes the Jython runtime. This should only be called once, before any other Python + * objects (including PythonInterpreter) are created. + * + * @param preProperties A set of properties. Typically System.getProperties() is used. + * preProperties override properties from the registry file. + * @param postProperties Another set of properties. Values like python.home, python.path and all + * other values from the registry files can be added to this property set. + * postProperties override system properties and registry properties. + * @param argv Command line arguments, assigned to sys.argv. + */ + public static void + initialize(Properties preProperties, Properties postProperties, String[] argv) { + } + + /** + * Creates a new interpreter with an empty local namespace. + */ + public PythonInterpreter() { + } + + /** + * Creates a new interpreter with the ability to maintain a separate local namespace for each + * thread (set by invoking setLocals()). + * + * @param dict a Python mapping object (e.g., a dictionary) for use as the default namespace + */ + public static PythonInterpreter threadLocalStateInterpreter(PyObject dict) { + return null; + } + + /** + * Creates a new interpreter with a specified local namespace. + * + * @param dict a Python mapping object (e.g., a dictionary) for use as the namespace + */ + public PythonInterpreter(PyObject dict) { + } + + /** + * Sets a Python object to use for the standard output stream, sys.stdout. This + * stream is used in a byte-oriented way (mostly) that depends on the type of file-like object. + * The behaviour as implemented is: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Stream behaviour for various object types
    Python type of object o written
    str/bytesunicodeAny other type
    {@link PyFile}as bytes directlyrespect {@link PyFile#encoding}call str(o) first
    {@link PyFileWriter}each byte value as a charwrite as Java charscall o.toString() first
    Other {@link PyObject} finvoke f.write(str(o))invoke f.write(o)invoke f.write(str(o))
    + * + * @param outStream Python file-like object to use as the output stream + */ + public void setOut(PyObject outStream) { + } + + /** + * Sets a {@link java.io.Writer} to use for the standard output stream, sys.stdout. + * The behaviour as implemented is to output each object o by calling + * o.toString() and writing this as UTF-16. + * + * @param outStream to use as the output stream + */ + public void setOut(java.io.Writer outStream) { + } + + /** + * Sets a {@link java.io.OutputStream} to use for the standard output stream. + * + * @param outStream OutputStream to use as output stream + */ + public void setOut(java.io.OutputStream outStream) { + } + + /** + * Sets a Python object to use for the standard output stream, sys.stderr. This + * stream is used in a byte-oriented way (mostly) that depends on the type of file-like object, + * in the same way as {@link #setOut(PyObject)}. + * + * @param outStream Python file-like object to use as the error output stream + */ + public void setErr(PyObject outStream) { + } + + /** + * Sets a {@link java.io.Writer} to use for the standard output stream, sys.stdout. + * The behaviour as implemented is to output each object o by calling + * o.toString() and writing this as UTF-16. + * + * @param outStream to use as the error output stream + */ + public void setErr(java.io.Writer outStream) { + } + + public void setErr(java.io.OutputStream outStream) { + } + + /** + * Evaluates a string as a Python expression and returns the result. + */ + public PyObject eval(String s) { + return null; + } + + /** + * Evaluates a Python code object and returns the result. + */ + public PyObject eval(PyObject code) { + return null; + } + + /** + * Executes a string of Python source in the local namespace. + * + * In some environments, such as Windows, Unicode characters in the script will be converted + * into ascii question marks (?). This can be avoided by first compiling the fragment using + * PythonInterpreter.compile(), and using the overridden form of this method which takes a + * PyCode object. Code page declarations are not supported. + */ + public void exec(String s) { + } + + /** + * Executes a Python code object in the local namespace. + */ + public void exec(PyObject code) { + } + + /** + * Executes a file of Python source in the local namespace. + */ + public void execfile(String filename) { + } + + public void execfile(java.io.InputStream s) { + } + + public void execfile(java.io.InputStream s, String name) { + } + + /** + * Compiles a string of Python source as either an expression (if possible) or a module. + * + * Designed for use by a JSR 223 implementation: "the Scripting API does not distinguish between + * scripts which return values and those which do not, nor do they make the corresponding + * distinction between evaluating or executing objects." (SCR.4.2.1) + */ + public PyCode compile(String script) { + return null; + } + + public PyCode compile(Reader reader) { + return null; + } + + public PyCode compile(String script, String filename) { + return null; + } + + public PyCode compile(Reader reader, String filename) { + return null; + } + + /** + * Sets a variable in the local namespace. + * + * @param name the name of the variable + * @param value the object to set the variable to (as converted to an appropriate Python object) + */ + public void set(String name, Object value) { + } + + /** + * Sets a variable in the local namespace. + * + * @param name the name of the variable + * @param value the Python object to set the variable to + */ + public void set(String name, PyObject value) { + } + + /** + * Returns the value of a variable in the local namespace. + * + * @param name the name of the variable + * @return the value of the variable, or null if that name isn't assigned + */ + public PyObject get(String name) { + return null; + } + + /** + * Returns the value of a variable in the local namespace. + * + * The value will be returned as an instance of the given Java class. + * interp.get("foo", Object.class) will return the most appropriate generic Java + * object. + * + * @param name the name of the variable + * @param javaclass the class of object to return + * @return the value of the variable as the given class, or null if that name isn't assigned + */ + public T get(String name, Class javaclass) { + return null; + } + + public void cleanup() { + } + + public void close() { + } +} diff --git a/java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/pool/KryoCallback.java b/java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/pool/KryoCallback.java new file mode 100644 index 00000000000..729426aba62 --- /dev/null +++ b/java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/pool/KryoCallback.java @@ -0,0 +1,7 @@ +package com.esotericsoftware.kryo.pool; + +import com.esotericsoftware.kryo.Kryo; + +public interface KryoCallback { + T execute (Kryo kryo); +} diff --git a/java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/pool/KryoFactory.java b/java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/pool/KryoFactory.java new file mode 100644 index 00000000000..4dcda1445df --- /dev/null +++ b/java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/pool/KryoFactory.java @@ -0,0 +1,7 @@ +package com.esotericsoftware.kryo.pool; + +import com.esotericsoftware.kryo.Kryo; + +public interface KryoFactory { + Kryo create (); +} diff --git a/java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/pool/KryoPool.java b/java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/pool/KryoPool.java new file mode 100644 index 00000000000..c005442c9e8 --- /dev/null +++ b/java/ql/test/stubs/kryo-4.0.2/com/esotericsoftware/kryo/pool/KryoPool.java @@ -0,0 +1,30 @@ +package com.esotericsoftware.kryo.pool; + +import com.esotericsoftware.kryo.Kryo; +import java.util.Queue; + +public interface KryoPool { + + Kryo borrow (); + + void release (Kryo kryo); + + T run (KryoCallback callback); + + static class Builder { + public Builder (KryoFactory factory) { + } + + public Builder queue (Queue queue) { + return null; + } + + public Builder softReferences () { + return null; + } + + public KryoPool build () { + return null; + } + } +} diff --git a/java/ql/test/stubs/scriptengine/javax/script/Bindings.java b/java/ql/test/stubs/scriptengine/javax/script/Bindings.java new file mode 100644 index 00000000000..a8eeeb6fe5e --- /dev/null +++ b/java/ql/test/stubs/scriptengine/javax/script/Bindings.java @@ -0,0 +1,14 @@ +package javax.script; +import java.util.Map; + +public interface Bindings extends Map { + public Object put(String name, Object value); + + public void putAll(Map toMerge); + + public boolean containsKey(Object key); + + public Object get(Object key); + + public Object remove(Object key); +} diff --git a/java/ql/test/stubs/scriptengine/javax/script/Compilable.java b/java/ql/test/stubs/scriptengine/javax/script/Compilable.java new file mode 100644 index 00000000000..ce6700c5a66 --- /dev/null +++ b/java/ql/test/stubs/scriptengine/javax/script/Compilable.java @@ -0,0 +1,9 @@ +package javax.script; + +import java.io.Reader; + +public interface Compilable { + public CompiledScript compile(String script) throws ScriptException; + + public CompiledScript compile(Reader script) throws ScriptException; +} diff --git a/java/ql/test/stubs/scriptengine/javax/script/CompiledScript.java b/java/ql/test/stubs/scriptengine/javax/script/CompiledScript.java new file mode 100644 index 00000000000..2f03d58c9a7 --- /dev/null +++ b/java/ql/test/stubs/scriptengine/javax/script/CompiledScript.java @@ -0,0 +1,17 @@ +package javax.script; + +public abstract class CompiledScript { + + public abstract Object eval(ScriptContext context) throws ScriptException; + + public Object eval(Bindings bindings) throws ScriptException { + return null; + } + + public Object eval() throws ScriptException { + return null; + } + + public abstract ScriptEngine getEngine(); + +} \ No newline at end of file diff --git a/java/ql/test/stubs/scriptengine/javax/script/ScriptEngine.java b/java/ql/test/stubs/scriptengine/javax/script/ScriptEngine.java index 4dc4f8c3186..35b91119e4f 100644 --- a/java/ql/test/stubs/scriptengine/javax/script/ScriptEngine.java +++ b/java/ql/test/stubs/scriptengine/javax/script/ScriptEngine.java @@ -2,5 +2,7 @@ package javax.script; public interface ScriptEngine { Object eval(String var1) throws ScriptException; + + public ScriptEngineFactory getFactory(); } diff --git a/java/ql/test/stubs/scriptengine/javax/script/ScriptEngineFactory.java b/java/ql/test/stubs/scriptengine/javax/script/ScriptEngineFactory.java index 7441c8a4ade..f802d86f80b 100644 --- a/java/ql/test/stubs/scriptengine/javax/script/ScriptEngineFactory.java +++ b/java/ql/test/stubs/scriptengine/javax/script/ScriptEngineFactory.java @@ -1,6 +1,11 @@ package javax.script; public interface ScriptEngineFactory { + public String getEngineName(); + + public String getMethodCallSyntax(String obj, String m, String... args); + + public String getProgram(String... statements); + ScriptEngine getScriptEngine(); } - diff --git a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/ClassFilter.java b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/ClassFilter.java new file mode 100644 index 00000000000..fcc624fc106 --- /dev/null +++ b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/ClassFilter.java @@ -0,0 +1,5 @@ +package jdk.nashorn.api.scripting; + +public interface ClassFilter { + public boolean exposeToScripts(String className); +} diff --git a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngine.java b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngine.java index 8dc3c1afa10..e89282dfa27 100644 --- a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngine.java +++ b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngine.java @@ -1,10 +1,31 @@ package jdk.nashorn.api.scripting; -import javax.script.*; +import java.io.Reader; -public final class NashornScriptEngine extends AbstractScriptEngine { +import javax.script.AbstractScriptEngine; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptException; + +public final class NashornScriptEngine extends AbstractScriptEngine implements Compilable { public Object eval(String var1) throws ScriptException { return null; } -} + @Override + public ScriptEngineFactory getFactory() { + return null; + } + + @Override + public CompiledScript compile(final Reader reader) throws ScriptException { + return null; + } + + @Override + public CompiledScript compile(final String str) throws ScriptException { + return null; + } +} diff --git a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java index 763e098ddbe..177bf463eb3 100644 --- a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java +++ b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java @@ -3,20 +3,48 @@ package jdk.nashorn.api.scripting; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; - public final class NashornScriptEngineFactory implements ScriptEngineFactory { public NashornScriptEngineFactory() { } + @Override + public String getEngineName() { + return null; + } + @Override + public String getMethodCallSyntax(final String obj, final String method, final String... args) { + return null; + } + + @Override + public String getProgram(final String... statements) { + return null; + } + + @Override public ScriptEngine getScriptEngine() { return null; } + public ScriptEngine getScriptEngine(final ClassLoader appLoader) { + return null; + } - public ScriptEngine getScriptEngine(String... args) { + public ScriptEngine getScriptEngine(final ClassFilter classFilter) { + return null; + } + + public ScriptEngine getScriptEngine(final String... args) { + return null; + } + + public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader) { + return null; + } + + public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) { return null; } } - diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/security/web/savedrequest/SavedRequest.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/security/web/savedrequest/SavedRequest.java new file mode 100644 index 00000000000..6a2984c7329 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/security/web/savedrequest/SavedRequest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.savedrequest; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.http.Cookie; + +/** + * Encapsulates the functionality required of a cached request for both an authentication + * mechanism (typically form-based login) to redirect to the original URL and for a + * RequestCache to build a wrapped request, reproducing the original request + * data. + * + * @author Luke Taylor + * @since 3.0 + */ +public interface SavedRequest extends java.io.Serializable { + + /** + * @return the URL for the saved request, allowing a redirect to be performed. + */ + String getRedirectUrl(); + + List getCookies(); + + String getMethod(); + + List getHeaderValues(String name); + + Collection getHeaderNames(); + + List getLocales(); + + String[] getParameterValues(String name); + + Map getParameterMap(); + +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/security/web/savedrequest/SimpleSavedRequest.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/security/web/savedrequest/SimpleSavedRequest.java new file mode 100644 index 00000000000..b18dbb3ab39 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/security/web/savedrequest/SimpleSavedRequest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.savedrequest; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.http.Cookie; + +/** + * A Bean implementation of SavedRequest + * + * @author Rob Winch + * @since 5.1 + */ +public class SimpleSavedRequest implements SavedRequest { + + public SimpleSavedRequest() { + } + + public SimpleSavedRequest(String redirectUrl) { + } + + public SimpleSavedRequest(SavedRequest request) { + } + + @Override + public String getRedirectUrl() { + return null; + } + + @Override + public List getCookies() { + return null; + } + + @Override + public String getMethod() { + return null; + } + + @Override + public List getHeaderValues(String name) { + return null; + } + + @Override + public Collection getHeaderNames() { + return null; + } + + @Override + public List getLocales() { + return null; + } + + @Override + public String[] getParameterValues(String name) { + return null; + } + + @Override + public Map getParameterMap() { + return null; + } + + public void setRedirectUrl(String redirectUrl) { + } + + public void setCookies(List cookies) { + } + + public void setMethod(String method) { + } + + public void setHeaders(Map> headers) { + } + + public void setLocales(List locales) { + } + + public void setParameters(Map parameters) { + } + +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/ModelAndView.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/ModelAndView.java new file mode 100644 index 00000000000..53e337d5053 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/ModelAndView.java @@ -0,0 +1,107 @@ +package org.springframework.web.servlet; + +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.lang.Nullable; + +public class ModelAndView { + @Nullable + private Object view; + @Nullable + private HttpStatus status; + private boolean cleared = false; + + public ModelAndView() { + } + + public ModelAndView(String viewName) { + this.view = viewName; + } + + public ModelAndView(View view) { + this.view = view; + } + + public ModelAndView(String viewName, @Nullable Map model) { } + + public ModelAndView(View view, @Nullable Map model) { } + + public ModelAndView(String viewName, HttpStatus status) { } + + public ModelAndView(@Nullable String viewName, @Nullable Map model, @Nullable HttpStatus status) { } + + public ModelAndView(String viewName, String modelName, Object modelObject) { } + + public ModelAndView(View view, String modelName, Object modelObject) { } + + public void setViewName(@Nullable String viewName) { + this.view = viewName; + } + + @Nullable + public String getViewName() { + return ""; + } + + public void setView(@Nullable View view) { } + + @Nullable + public View getView() { + return null; + } + + public boolean hasView() { + return true; + } + + public boolean isReference() { + return true; + } + + @Nullable + protected Map getModelInternal() { + return null; + } + + public Map getModel() { + return null; + } + + public void setStatus(@Nullable HttpStatus status) { } + + @Nullable + public HttpStatus getStatus() { + return this.status; + } + + public ModelAndView addObject(String attributeName, @Nullable Object attributeValue) { + return null; + } + + public ModelAndView addObject(Object attributeValue) { + return null; + } + + public ModelAndView addAllObjects(@Nullable Map modelMap) { + return null; + } + + public void clear() { } + + public boolean isEmpty() { + return true; + } + + public boolean wasCleared() { + return true; + } + + public String toString() { + return ""; + } + + private String formatView() { + return ""; + } +} + diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/View.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/View.java new file mode 100644 index 00000000000..b2281b8c250 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/View.java @@ -0,0 +1,20 @@ +package org.springframework.web.servlet; + +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.lang.Nullable; + +public interface View { + String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus"; + String PATH_VARIABLES = View.class.getName() + ".pathVariables"; + String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType"; + + @Nullable + default String getContentType() { + return null; + } + + void render(@Nullable Map var1, HttpServletRequest var2, HttpServletResponse var3) throws Exception; +} + diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/AbstractUrlBasedView.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/AbstractUrlBasedView.java new file mode 100644 index 00000000000..9efd87af12f --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/AbstractUrlBasedView.java @@ -0,0 +1,39 @@ +package org.springframework.web.servlet.view; + +import java.util.Locale; +import org.springframework.lang.Nullable; + +public abstract class AbstractUrlBasedView { + @Nullable + private String url; + + protected AbstractUrlBasedView() { } + + protected AbstractUrlBasedView(String url) { + this.url = url; + } + + public void setUrl(@Nullable String url) { + this.url = url; + } + + @Nullable + public String getUrl() { + return ""; + } + + public void afterPropertiesSet() throws Exception { } + + protected boolean isUrlRequired() { + return true; + } + + public boolean checkResource(Locale locale) throws Exception { + return true; + } + + public String toString() { + return ""; + } +} + diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/RedirectView.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/RedirectView.java new file mode 100644 index 00000000000..ee18868231a --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/RedirectView.java @@ -0,0 +1,129 @@ +package org.springframework.web.servlet.view; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; +import org.springframework.lang.Nullable; + +public class RedirectView extends AbstractUrlBasedView { + private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); + private boolean contextRelative = false; + private boolean http10Compatible = true; + private boolean exposeModelAttributes = true; + @Nullable + private String encodingScheme; + @Nullable + private HttpStatus statusCode; + private boolean expandUriTemplateVariables = true; + private boolean propagateQueryParams = false; + @Nullable + private String[] hosts; + + public RedirectView() { } + + public RedirectView(String url) { } + + public RedirectView(String url, boolean contextRelative) { } + + public RedirectView(String url, boolean contextRelative, boolean http10Compatible) { } + + public RedirectView(String url, boolean contextRelative, boolean http10Compatible, boolean exposeModelAttributes) { } + + public void setContextRelative(boolean contextRelative) { } + + public void setHttp10Compatible(boolean http10Compatible) { } + + public void setExposeModelAttributes(boolean exposeModelAttributes) { } + + public void setEncodingScheme(String encodingScheme) { } + + public void setStatusCode(HttpStatus statusCode) { } + + public void setExpandUriTemplateVariables(boolean expandUriTemplateVariables) { } + + public void setPropagateQueryParams(boolean propagateQueryParams) { } + + public boolean isPropagateQueryProperties() { + return true; + } + + public void setHosts(@Nullable String... hosts) { } + + @Nullable + public String[] getHosts() { + return this.hosts; + } + + public boolean isRedirectView() { + return true; + } + + protected boolean isContextRequired() { + return false; + } + + protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws IOException { } + + protected final String createTargetUrl(Map model, HttpServletRequest request) throws UnsupportedEncodingException { + return ""; + } + + private String getContextPath(HttpServletRequest request) { + return ""; + } + + protected StringBuilder replaceUriTemplateVariables(String targetUrl, Map model, Map currentUriVariables, String encodingScheme) throws UnsupportedEncodingException { + return null; + } + + private Map getCurrentRequestUriVariables(HttpServletRequest request) { + return null; + } + + protected void appendCurrentQueryParams(StringBuilder targetUrl, HttpServletRequest request) { } + + protected void appendQueryProperties(StringBuilder targetUrl, Map model, String encodingScheme) throws UnsupportedEncodingException { } + + protected Map queryProperties(Map model) { + return null; + } + + protected boolean isEligibleProperty(String key, @Nullable Object value) { + return true; + } + + protected boolean isEligibleValue(@Nullable Object value) { + return true; + } + + protected String urlEncode(String input, String encodingScheme) throws UnsupportedEncodingException { + return ""; + } + + protected String updateTargetUrl(String targetUrl, Map model, HttpServletRequest request, HttpServletResponse response) { + return ""; + } + + protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) throws IOException { } + + protected boolean isRemoteHost(String targetUrl) { + return true; + } + + protected HttpStatus getHttp11StatusCode(HttpServletRequest request, HttpServletResponse response, String targetUrl) { + return this.statusCode; + } +} + diff --git a/javascript/change-notes/2021-04-15-fs-promises.md b/javascript/change-notes/2021-04-15-fs-promises.md new file mode 100644 index 00000000000..f410f00edb1 --- /dev/null +++ b/javascript/change-notes/2021-04-15-fs-promises.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* Support for `fs.promises` has been added, leading to more results for security queries + related to file system access. diff --git a/javascript/change-notes/2021-04-15-nestjs.md b/javascript/change-notes/2021-04-15-nestjs.md new file mode 100644 index 00000000000..5eaa0e5af69 --- /dev/null +++ b/javascript/change-notes/2021-04-15-nestjs.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* Support for Nest.js has been added. The security queries now recognize sources and sinks + specific to the Nest.js framework. diff --git a/javascript/change-notes/2021-04-26-unsafe-html-construction.md b/javascript/change-notes/2021-04-26-unsafe-html-construction.md new file mode 100644 index 00000000000..8b0f513d760 --- /dev/null +++ b/javascript/change-notes/2021-04-26-unsafe-html-construction.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* A new query, `js/html-constructed-from-input`, has been added to the query suite, + highlighting libraries that may leave clients vulnerable to cross-site-scripting attacks. diff --git a/javascript/change-notes/2021-05-10-sqlite3-chaining.md b/javascript/change-notes/2021-05-10-sqlite3-chaining.md new file mode 100644 index 00000000000..2b541440308 --- /dev/null +++ b/javascript/change-notes/2021-05-10-sqlite3-chaining.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* Modelling of chaining methods in the `sqlite3` package has improved, which may lead to + additional results from the `js/sql-injection` query. diff --git a/javascript/change-notes/2021-05-18-clone.md b/javascript/change-notes/2021-05-18-clone.md new file mode 100644 index 00000000000..42d52d72a3d --- /dev/null +++ b/javascript/change-notes/2021-05-18-clone.md @@ -0,0 +1,4 @@ +lgtm,codescanning +* The dataflow libraries now model dataflow in the `clone` library. + Affected packages are + [clone](https://npmjs.com/package/clone) diff --git a/javascript/change-notes/2021-05-31-typescript-4.3.md b/javascript/change-notes/2021-05-31-typescript-4.3.md new file mode 100644 index 00000000000..21960a98dce --- /dev/null +++ b/javascript/change-notes/2021-05-31-typescript-4.3.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* TypeScript 4.3 is now supported. diff --git a/javascript/change-notes/2021-06-02-debug.md b/javascript/change-notes/2021-06-02-debug.md new file mode 100644 index 00000000000..324ea96e2bf --- /dev/null +++ b/javascript/change-notes/2021-06-02-debug.md @@ -0,0 +1,4 @@ +lgtm,codescanning +* Logging calls using the [debug](https://npmjs.com/package/immutable) library are now recognized. + Affected packages are + [debug](https://npmjs.com/package/debug) diff --git a/javascript/change-notes/2021-06-02-webpack-merge.md b/javascript/change-notes/2021-06-02-webpack-merge.md new file mode 100644 index 00000000000..87ab83fffa6 --- /dev/null +++ b/javascript/change-notes/2021-06-02-webpack-merge.md @@ -0,0 +1,4 @@ +lgtm,codescanning +* The security queries recognize the merge call from [webpack-merge](https://npmjs.com/package/webpack-merge). + Affected packages are + [webpack-merge](https://npmjs.com/package/webpack-merge) diff --git a/javascript/extractor/lib/typescript/package.json b/javascript/extractor/lib/typescript/package.json index cb3119c7ab2..22b130f3bec 100644 --- a/javascript/extractor/lib/typescript/package.json +++ b/javascript/extractor/lib/typescript/package.json @@ -2,7 +2,7 @@ "name": "typescript-parser-wrapper", "private": true, "dependencies": { - "typescript": "4.2.2" + "typescript": "4.3.2" }, "scripts": { "build": "tsc --project tsconfig.json", diff --git a/javascript/extractor/lib/typescript/yarn.lock b/javascript/extractor/lib/typescript/yarn.lock index 84f20b2c51c..7488658d581 100644 --- a/javascript/extractor/lib/typescript/yarn.lock +++ b/javascript/extractor/lib/typescript/yarn.lock @@ -6,7 +6,7 @@ version "12.7.11" resolved node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446 -typescript@4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.2.tgz#1450f020618f872db0ea17317d16d8da8ddb8c4c" - integrity sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ== +typescript@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" + integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== diff --git a/javascript/extractor/src/com/semmle/js/extractor/Main.java b/javascript/extractor/src/com/semmle/js/extractor/Main.java index 7c5a8a25984..5d417940d75 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/Main.java +++ b/javascript/extractor/src/com/semmle/js/extractor/Main.java @@ -43,7 +43,7 @@ public class Main { * A version identifier that should be updated every time the extractor changes in such a way that * it may produce different tuples for the same file under the same {@link ExtractorConfig}. */ - public static final String EXTRACTOR_VERSION = "2021-03-19"; + public static final String EXTRACTOR_VERSION = "2021-05-31"; public static final Pattern NEWLINE = Pattern.compile("\n"); diff --git a/javascript/ql/src/Security/CWE-079/UnsafeHtmlConstruction.qhelp b/javascript/ql/src/Security/CWE-079/UnsafeHtmlConstruction.qhelp new file mode 100644 index 00000000000..0bc3bd53a0d --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/UnsafeHtmlConstruction.qhelp @@ -0,0 +1,77 @@ + + + +

    + When a library function dynamically constructs HTML in a potentially unsafe + way, then it's important to document to clients of the library that the function + should only be used with trusted inputs. + + If the function is not documented as being potentially unsafe, then a client + may inadvertently use inputs containing unsafe HTML fragments, and thereby leave + the client vulnerable to cross-site scripting attacks. +

    + + + +

    + Document all library functions that can lead to cross-site scripting + attacks, and guard against unsafe inputs where dynamic HTML + construction is not intended. +

    +
    + + +

    + The following example has a library function that renders a boldface name + by writing to the innerHTML property of an element. +

    + + + +

    + This library function, however, does not escape unsafe HTML, and a client + that calls the function with user-supplied input may be vulnerable to + cross-site scripting attacks. +

    + +

    + The library could either document that this function should not be used + with unsafe inputs, or use safe APIs such as innerText. +

    + + + +

    + Alternatively, a HTML sanitizer can be used to remove unsafe content. +

    + + + +
    + +
  • + OWASP: + DOM based + XSS Prevention Cheat Sheet. +
  • +
  • + OWASP: + XSS + (Cross Site Scripting) Prevention Cheat Sheet. +
  • +
  • + OWASP + DOM Based XSS. +
  • +
  • + OWASP + Types of Cross-Site + Scripting. +
  • +
  • + Wikipedia: Cross-site scripting. +
  • +
    + diff --git a/javascript/ql/src/Security/CWE-079/UnsafeHtmlConstruction.ql b/javascript/ql/src/Security/CWE-079/UnsafeHtmlConstruction.ql new file mode 100644 index 00000000000..d6199af4598 --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/UnsafeHtmlConstruction.ql @@ -0,0 +1,22 @@ +/** + * @name Unsafe HTML constructed from library input + * @description Using externally controlled strings to construct HTML might allow a malicious + * user to perform a cross-site scripting attack. + * @kind path-problem + * @problem.severity error + * @precision high + * @id js/html-constructed-from-input + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-116 + */ + +import javascript +import DataFlow::PathGraph +import semmle.javascript.security.dataflow.UnsafeHtmlConstruction::UnsafeHtmlConstruction + +from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode +where cfg.hasFlowPath(source, sink) and sink.getNode() = sinkNode +select sinkNode, source, sink, "$@ based on $@ might later cause $@.", sinkNode, + sinkNode.describe(), source.getNode(), "library input", sinkNode.getSink(), + sinkNode.getVulnerabilityKind().toLowerCase() diff --git a/javascript/ql/src/Security/CWE-079/examples/unsafe-html-construction.js b/javascript/ql/src/Security/CWE-079/examples/unsafe-html-construction.js new file mode 100644 index 00000000000..ab4bfee0a25 --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/examples/unsafe-html-construction.js @@ -0,0 +1,3 @@ +module.exports = function showBoldName(name) { + document.getElementById('name').innerHTML = "" + name + ""; +} diff --git a/javascript/ql/src/Security/CWE-079/examples/unsafe-html-construction_safe.js b/javascript/ql/src/Security/CWE-079/examples/unsafe-html-construction_safe.js new file mode 100644 index 00000000000..b2b3af9bcfb --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/examples/unsafe-html-construction_safe.js @@ -0,0 +1,5 @@ +module.exports = function showBoldName(name) { + const bold = document.createElement('b'); + bold.innerText = name; + document.getElementById('name').appendChild(bold); +} diff --git a/javascript/ql/src/Security/CWE-079/examples/unsafe-html-construction_sanitizer.js b/javascript/ql/src/Security/CWE-079/examples/unsafe-html-construction_sanitizer.js new file mode 100644 index 00000000000..9c63e278720 --- /dev/null +++ b/javascript/ql/src/Security/CWE-079/examples/unsafe-html-construction_sanitizer.js @@ -0,0 +1,5 @@ + +const striptags = require('striptags'); +module.exports = function showBoldName(name) { + document.getElementById('name').innerHTML = "" + striptags(name) + ""; +} diff --git a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.qhelp b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.qhelp index 11e48e98c74..a019d65c02b 100644 --- a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.qhelp +++ b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.qhelp @@ -58,5 +58,6 @@
  • OWASP: Cross-Site Request Forgery (CSRF)
  • +
  • NPM: csurf
  • diff --git a/javascript/ql/src/Summary/LinesOfCode.ql b/javascript/ql/src/Summary/LinesOfCode.ql index 9f89e0e2163..cadf0a9cf8f 100644 --- a/javascript/ql/src/Summary/LinesOfCode.ql +++ b/javascript/ql/src/Summary/LinesOfCode.ql @@ -4,6 +4,7 @@ * @description The total number of lines of JavaScript or TypeScript code across all files checked into the repository, except in `node_modules`. This is a useful metric of the size of a database. For all files that were seen during extraction, this query counts the lines of code, excluding whitespace or comments. * @kind metric * @tags summary + * lines-of-code */ import javascript diff --git a/javascript/ql/src/Summary/TaintSinks.ql b/javascript/ql/src/Summary/TaintSinks.ql new file mode 100644 index 00000000000..2da65398935 --- /dev/null +++ b/javascript/ql/src/Summary/TaintSinks.ql @@ -0,0 +1,15 @@ +/** + * @name Taint sinks + * @description Expressions that are vulnerable if containing untrusted data. + * @kind problem + * @problem.severity info + * @id js/summary/taint-sinks + * @tags summary + * @precision medium + */ + +import javascript +import meta.internal.TaintMetrics + +from string kind +select relevantTaintSink(kind), kind + " sink" diff --git a/javascript/ql/src/Summary/TaintSources.ql b/javascript/ql/src/Summary/TaintSources.ql new file mode 100644 index 00000000000..78e544f0bd5 --- /dev/null +++ b/javascript/ql/src/Summary/TaintSources.ql @@ -0,0 +1,16 @@ +/** + * @name Taint sources + * @description Sources of untrusted input. + * @kind problem + * @problem.severity info + * @id js/summary/taint-sources + * @tags summary + * @precision medium + */ + +import javascript +import meta.internal.TaintMetrics + +from RemoteFlowSource node +where node = relevantTaintSource() +select node, node.getSourceType() diff --git a/javascript/ql/src/experimental/StandardLibrary/MultipleArgumentsToSetConstructor.qhelp b/javascript/ql/src/experimental/StandardLibrary/MultipleArgumentsToSetConstructor.qhelp new file mode 100644 index 00000000000..22890d6c3cf --- /dev/null +++ b/javascript/ql/src/experimental/StandardLibrary/MultipleArgumentsToSetConstructor.qhelp @@ -0,0 +1,43 @@ + + + + +

    +The Set constructor accepts an arbitrary number of arguments, but only the first one +is used to construct the set. The remaining arguments are ignored. +Code that invokes the Set constructor with multiple arguments is therefore likely to +be incorrect. +

    +
    + + +

    +Only pass a single argument to the Set constructor, which should be an iterable object +(such as an array). +

    +
    + + +

    +The following example creates a set containing the vowels in the English language, and defines +a function that returns a boolean indicating whether a given character is a vowel: +

    + + +

    +However, this code does not work as intended: the Set constructor ignores all but +the first argument, so the vowels set only contains the letter a. +

    + +

    +Instead, the list of vowels should be wrapped into an array: +

    + +
    + + +
  • MDN Web Docs: Set() constructor
  • +
    +
    diff --git a/javascript/ql/src/experimental/StandardLibrary/MultipleArgumentsToSetConstructor.ql b/javascript/ql/src/experimental/StandardLibrary/MultipleArgumentsToSetConstructor.ql new file mode 100644 index 00000000000..6ee75dce7a6 --- /dev/null +++ b/javascript/ql/src/experimental/StandardLibrary/MultipleArgumentsToSetConstructor.ql @@ -0,0 +1,22 @@ +/** + * @name Multiple arguments to `Set` constructor + * @description The `Set` constructor ignores all but the first argument, so passing multiple + * arguments may indicate a mistake. + * @kind problem + * @problem.severity warning + * @precision high + * @id js/multiple-arguments-to-set-constructor + * @tags correctness + */ + +import javascript + +from DataFlow::NewNode newSet, DataFlow::Node ignoredArg +where + newSet = DataFlow::globalVarRef("Set").getAnInstantiation() and + ( + ignoredArg = newSet.getArgument(any(int n | n > 0)) + or + ignoredArg = newSet.getASpreadArgument() + ) +select ignoredArg, "All but the first argument to the Set constructor are ignored." diff --git a/javascript/ql/src/experimental/StandardLibrary/examples/MultipleArgumentsToSetConstructorBad.js b/javascript/ql/src/experimental/StandardLibrary/examples/MultipleArgumentsToSetConstructorBad.js new file mode 100644 index 00000000000..4bce4b54c1b --- /dev/null +++ b/javascript/ql/src/experimental/StandardLibrary/examples/MultipleArgumentsToSetConstructorBad.js @@ -0,0 +1,5 @@ +const vowels = new Set('a', 'e', 'i', 'o', 'u'); + +function isVowel(char) { + return vowels.has(char.toLowerCase()); +} diff --git a/javascript/ql/src/experimental/StandardLibrary/examples/MultipleArgumentsToSetConstructorGood.js b/javascript/ql/src/experimental/StandardLibrary/examples/MultipleArgumentsToSetConstructorGood.js new file mode 100644 index 00000000000..36ab0f01c1d --- /dev/null +++ b/javascript/ql/src/experimental/StandardLibrary/examples/MultipleArgumentsToSetConstructorGood.js @@ -0,0 +1,5 @@ +const vowels = new Set(['a', 'e', 'i', 'o', 'u']); + +function isVowel(char) { + return vowels.has(char.toLowerCase()); +} diff --git a/javascript/ql/src/javascript.qll b/javascript/ql/src/javascript.qll index 4660d7eaf93..95db948bbd4 100644 --- a/javascript/ql/src/javascript.qll +++ b/javascript/ql/src/javascript.qll @@ -76,6 +76,7 @@ import semmle.javascript.frameworks.Babel import semmle.javascript.frameworks.Cheerio import semmle.javascript.frameworks.ComposedFunctions import semmle.javascript.frameworks.Classnames +import semmle.javascript.frameworks.ClassValidator import semmle.javascript.frameworks.ClientRequests import semmle.javascript.frameworks.ClosureLibrary import semmle.javascript.frameworks.CookieLibraries @@ -99,6 +100,7 @@ import semmle.javascript.frameworks.Logging import semmle.javascript.frameworks.HttpFrameworks import semmle.javascript.frameworks.HttpProxy import semmle.javascript.frameworks.Markdown +import semmle.javascript.frameworks.Nest import semmle.javascript.frameworks.Next import semmle.javascript.frameworks.NoSQL import semmle.javascript.frameworks.PkgCloud diff --git a/javascript/ql/src/meta/internal/TaintMetrics.qll b/javascript/ql/src/meta/internal/TaintMetrics.qll index 6d10b2c6ad6..f6eae2eaa6e 100644 --- a/javascript/ql/src/meta/internal/TaintMetrics.qll +++ b/javascript/ql/src/meta/internal/TaintMetrics.qll @@ -75,16 +75,9 @@ DataFlow::Node relevantTaintSink(string kind) { DataFlow::Node relevantTaintSink() { result = relevantTaintSink(_) } /** - * Gets a remote flow source or `document.location` source. + * Gets a relevant remote flow source. */ -DataFlow::Node relevantTaintSource() { - not result.getFile() instanceof IgnoredFile and - ( - result instanceof RemoteFlowSource - or - result = DOM::locationSource() - ) -} +RemoteFlowSource relevantTaintSource() { not result.getFile() instanceof IgnoredFile } /** * Gets the output of a call that shows intent to sanitize a value diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index 385dc1e76d3..590142590a4 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -183,6 +183,11 @@ module API { */ Node getPromised() { result = getASuccessor(Label::promised()) } + /** + * Gets a node representing the error wrapped in the `Promise` object represented by this node. + */ + Node getPromisedError() { result = getASuccessor(Label::promisedError()) } + /** * Gets a string representation of the lexicographically least among all shortest access paths * from the root to this node. @@ -468,6 +473,9 @@ module API { or lbl = Label::promised() and PromiseFlow::storeStep(rhs, pred, Promises::valueProp()) + or + lbl = Label::promisedError() and + PromiseFlow::storeStep(rhs, pred, Promises::errorProp()) ) or exists(DataFlow::ClassNode cls, string name | @@ -482,6 +490,12 @@ module API { rhs = f.getAReturn() ) or + exists(DataFlow::FunctionNode f | + base = MkAsyncFuncResult(f) and + lbl = Label::promisedError() and + rhs = f.getExceptionalReturn() + ) + or exists(int i | lbl = Label::parameter(i) and argumentPassing(base, i, rhs) @@ -559,6 +573,9 @@ module API { or lbl = Label::promised() and PromiseFlow::loadStep(pred, ref, Promises::valueProp()) + or + lbl = Label::promisedError() and + PromiseFlow::loadStep(pred, ref, Promises::errorProp()) ) or exists(DataFlow::Node def, DataFlow::FunctionNode fn | @@ -962,6 +979,9 @@ private module Label { /** Gets the `promised` edge label connecting a promise to its contained value. */ string promised() { result = "promised" } + + /** Gets the `promisedError` edge label connecting a promise to its rejected value. */ + string promisedError() { result = "promisedError" } } private class NodeModuleSourcesNodes extends DataFlow::SourceNode::Range { diff --git a/javascript/ql/src/semmle/javascript/Extend.qll b/javascript/ql/src/semmle/javascript/Extend.qll index 9b7255983b9..d6c2087e39b 100644 --- a/javascript/ql/src/semmle/javascript/Extend.qll +++ b/javascript/ql/src/semmle/javascript/Extend.qll @@ -174,3 +174,37 @@ private class ExtendCallTaintStep extends TaintTracking::SharedTaintStep { ) } } + +private import semmle.javascript.dataflow.internal.PreCallGraphStep + +/** + * A step for the `clone` package. + */ +private class CloneStep extends PreCallGraphStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(DataFlow::CallNode call | call = DataFlow::moduleImport("clone").getACall() | + pred = call.getArgument(0) and + succ = call + ) + } +} + +/** + * A deep extend call from the [webpack-merge](https://npmjs.org/package/webpack-merge) library. + */ +private class WebpackMergeDeep extends ExtendCall, DataFlow::CallNode { + WebpackMergeDeep() { + this = DataFlow::moduleMember("webpack-merge", "merge").getACall() + or + this = + DataFlow::moduleMember("webpack-merge", ["mergeWithCustomize", "mergeWithRules"]) + .getACall() + .getACall() + } + + override DataFlow::Node getASourceOperand() { result = getAnArgument() } + + override DataFlow::Node getDestinationOperand() { none() } + + override predicate isDeep() { any() } +} diff --git a/javascript/ql/src/semmle/javascript/PrintAst.qll b/javascript/ql/src/semmle/javascript/PrintAst.qll index 0499e3bd547..029cfd536fc 100644 --- a/javascript/ql/src/semmle/javascript/PrintAst.qll +++ b/javascript/ql/src/semmle/javascript/PrintAst.qll @@ -73,7 +73,12 @@ private newtype TPrintAstNode = THTMLAttributesNodes(HTML::Element e) { shouldPrint(e, _) and not isNotNeeded(e) } or THTMLAttributeNode(HTML::Attribute attr) { shouldPrint(attr, _) and not isNotNeeded(attr) } or THTMLScript(Script script) { shouldPrint(script, _) and not isNotNeeded(script) } or - THTMLCodeInAttr(CodeInAttribute attr) { shouldPrint(attr, _) and not isNotNeeded(attr) } + THTMLCodeInAttr(CodeInAttribute attr) { shouldPrint(attr, _) and not isNotNeeded(attr) } or + TRegExpTermNode(RegExpTerm term) { + shouldPrint(term, _) and + term.isUsedAsRegExp() and + any(RegExpLiteral lit).getRoot() = term.getRootTerm() + } /** * A node in the output tree. @@ -282,6 +287,39 @@ private module PrintJavaScript { } } + /** + * A print node for regexp literals. + * + * The single child of this node is the root `RegExpTerm`. + */ + class RegexpNode extends ElementNode { + override RegExpLiteral element; + + override PrintAstNode getChild(int childIndex) { + childIndex = 0 and + result.(RegExpTermNode).getTerm() = element.getRoot() + } + } + + /** + * A print node for regexp terms. + */ + class RegExpTermNode extends PrintAstNode, TRegExpTermNode { + RegExpTerm term; + + RegExpTermNode() { this = TRegExpTermNode(term) } + + RegExpTerm getTerm() { result = term } + + override PrintAstNode getChild(int childIndex) { + result.(RegExpTermNode).getTerm() = term.getChild(childIndex) + } + + override string toString() { result = getQlClass(term) + term.toString() } + + override Location getLocation() { result = term.getLocation() } + } + /** * An aggregate node representing all the arguments for an function invocation. */ diff --git a/javascript/ql/src/semmle/javascript/Regexp.qll b/javascript/ql/src/semmle/javascript/Regexp.qll index a34b052005d..d3b7e9cac7e 100644 --- a/javascript/ql/src/semmle/javascript/Regexp.qll +++ b/javascript/ql/src/semmle/javascript/Regexp.qll @@ -215,7 +215,9 @@ class InfiniteRepetitionQuantifier extends RegExpQuantifier { * \w * ``` */ -class RegExpEscape extends RegExpTerm, @regexp_escape { } +class RegExpEscape extends RegExpTerm, @regexp_escape { + override string getAPrimaryQlClass() { result = "RegExpEscape" } +} /** * A constant regular expression term, that is, a regular expression @@ -240,6 +242,8 @@ class RegExpConstant extends RegExpTerm, @regexp_constant { override predicate isNullable() { none() } override string getConstantValue() { result = getValue() } + + override string getAPrimaryQlClass() { result = "RegExpConstant" } } /** @@ -264,6 +268,8 @@ class RegExpCharEscape extends RegExpEscape, RegExpConstant, @regexp_char_escape ) ) } + + override string getAPrimaryQlClass() { result = "RegExpCharEscape" } } /** @@ -285,6 +291,8 @@ class RegExpAlt extends RegExpTerm, @regexp_alt { override predicate isNullable() { getAlternative().isNullable() } override string getAMatchedString() { result = getAlternative().getAMatchedString() } + + override string getAPrimaryQlClass() { result = "RegExpAlt" } } /** @@ -332,6 +340,8 @@ class RegExpSequence extends RegExpTerm, @regexp_seq { result = this.getChild(i + 1) ) } + + override string getAPrimaryQlClass() { result = "RegExpSequence" } } /** @@ -346,6 +356,8 @@ class RegExpSequence extends RegExpTerm, @regexp_seq { */ class RegExpAnchor extends RegExpTerm, @regexp_anchor { override predicate isNullable() { any() } + + override string getAPrimaryQlClass() { result = "RegExpAnchor" } } /** @@ -357,7 +369,9 @@ class RegExpAnchor extends RegExpTerm, @regexp_anchor { * ^ * ``` */ -class RegExpCaret extends RegExpAnchor, @regexp_caret { } +class RegExpCaret extends RegExpAnchor, @regexp_caret { + override string getAPrimaryQlClass() { result = "RegExpCaret" } +} /** * A dollar assertion `$` matching the end of a line. @@ -368,7 +382,9 @@ class RegExpCaret extends RegExpAnchor, @regexp_caret { } * $ * ``` */ -class RegExpDollar extends RegExpAnchor, @regexp_dollar { } +class RegExpDollar extends RegExpAnchor, @regexp_dollar { + override string getAPrimaryQlClass() { result = "RegExpDollar" } +} /** * A word boundary assertion. @@ -381,6 +397,8 @@ class RegExpDollar extends RegExpAnchor, @regexp_dollar { } */ class RegExpWordBoundary extends RegExpTerm, @regexp_wordboundary { override predicate isNullable() { any() } + + override string getAPrimaryQlClass() { result = "RegExpWordBoundary" } } /** @@ -394,6 +412,8 @@ class RegExpWordBoundary extends RegExpTerm, @regexp_wordboundary { */ class RegExpNonWordBoundary extends RegExpTerm, @regexp_nonwordboundary { override predicate isNullable() { any() } + + override string getAPrimaryQlClass() { result = "RegExpNonWordBoundary" } } /** @@ -425,7 +445,9 @@ class RegExpSubPattern extends RegExpTerm, @regexp_subpattern { * (?!\n) * ``` */ -class RegExpLookahead extends RegExpSubPattern, @regexp_lookahead { } +class RegExpLookahead extends RegExpSubPattern, @regexp_lookahead { + override string getAPrimaryQlClass() { result = "RegExpLookahead" } +} /** * A zero-width lookbehind assertion. @@ -437,7 +459,9 @@ class RegExpLookahead extends RegExpSubPattern, @regexp_lookahead { } * (?` @@ -770,6 +832,8 @@ class RegExpBackRef extends RegExpTerm, @regexp_backref { } override predicate isNullable() { getGroup().isNullable() } + + override string getAPrimaryQlClass() { result = "RegExpBackRef" } } /** @@ -808,6 +872,8 @@ class RegExpCharacterClass extends RegExpTerm, @regexp_char_class { cce1 != cce2 and cce1.toLowerCase() = cce2.toLowerCase() ) } + + override string getAPrimaryQlClass() { result = "RegExpCharacterClass" } } /** @@ -827,6 +893,8 @@ class RegExpCharacterRange extends RegExpTerm, @regexp_char_range { lo = getChild(0).(RegExpConstant).getValue() and hi = getChild(1).(RegExpConstant).getValue() } + + override string getAPrimaryQlClass() { result = "RegExpCharacterRange" } } /** A parse error encountered while processing a regular expression literal. */ diff --git a/javascript/ql/src/semmle/javascript/TypeScript.qll b/javascript/ql/src/semmle/javascript/TypeScript.qll index 4fe263c8389..0b611140449 100644 --- a/javascript/ql/src/semmle/javascript/TypeScript.qll +++ b/javascript/ql/src/semmle/javascript/TypeScript.qll @@ -725,7 +725,7 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef { spec.getImportedName() = exportedName and this = spec.getLocal().(TypeDecl).getLocalTypeName().getAnAccess() or - spec instanceof ImportNamespaceSpecifier and + (spec instanceof ImportNamespaceSpecifier or spec instanceof ImportDefaultSpecifier) and this = spec.getLocal().(LocalNamespaceDecl).getLocalNamespaceName().getAMemberAccess(exportedName) ) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index b4842026e24..45e7515df91 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -2068,3 +2068,14 @@ class VarAccessBarrier extends DataFlow::Node { ) } } + +/** + * Holds if there is a path without unmatched return steps from `source` to `sink`. + */ +predicate hasPathWithoutUnmatchedReturn(SourcePathNode source, SinkPathNode sink) { + exists(MidPathNode mid | + source.getASuccessor*() = mid and + sink = mid.getASuccessor() and + mid.getPathSummary().hasReturn() = false + ) +} diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll index c73c894ca4b..f02e9b0f287 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll @@ -239,7 +239,6 @@ module DataFlow { private TypeAnnotation getFallbackTypeAnnotation() { exists(BindingPattern pattern | this = valueNode(pattern) and - not ast_node_type(pattern, _) and result = pattern.getTypeAnnotation() ) or diff --git a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll index 9c8d13582c3..19fe93da259 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll @@ -47,6 +47,9 @@ class ParameterNode extends DataFlow::SourceNode { /** Holds if this parameter is a rest parameter. */ predicate isRestParameter() { p.isRestParameter() } + + /** Gets the data flow node for an expression that is applied to this decorator. */ + DataFlow::Node getADecorator() { result = getParameter().getADecorator().getExpression().flow() } } /** @@ -1078,35 +1081,42 @@ module ClassNode { * Subclass this to introduce new kinds of class nodes. If you want to refine * the definition of existing class nodes, subclass `DataFlow::ClassNode` instead. */ + cached abstract class Range extends DataFlow::SourceNode { /** * Gets the name of the class, if it has one. */ + cached abstract string getName(); /** * Gets a description of the class. */ + cached abstract string describe(); /** * Gets the constructor function of this class. */ + cached abstract FunctionNode getConstructor(); /** * Gets the instance member with the given name and kind. */ + cached abstract FunctionNode getInstanceMember(string name, MemberKind kind); /** * Gets an instance member with the given kind. */ + cached abstract FunctionNode getAnInstanceMember(MemberKind kind); /** * Gets the static method of this class with the given name. */ + cached abstract FunctionNode getStaticMethod(string name); /** @@ -1114,20 +1124,24 @@ module ClassNode { * * The constructor is not considered a static method. */ + cached abstract FunctionNode getAStaticMethod(); /** * Gets a dataflow node representing a class to be used as the super-class * of this node. */ + cached abstract DataFlow::Node getASuperClassNode(); /** * Gets the type annotation for the field `fieldName`, if any. */ + cached TypeAnnotation getFieldTypeAnnotation(string fieldName) { none() } /** Gets a decorator applied to this class. */ + cached DataFlow::Node getADecorator() { none() } } diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll index 251a691501e..1a642d980bd 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll @@ -550,7 +550,8 @@ module TaintTracking { // reading from a tainted object yields a tainted result succ.(DataFlow::PropRead).getBase() = pred and not AccessPath::DominatingPaths::hasDominatingWrite(succ) and - not isSafeClientSideUrlProperty(succ) + not isSafeClientSideUrlProperty(succ) and + not ClassValidator::isAccessToSanitizedField(succ) or // iterating over a tainted iterator taints the loop variable exists(ForOfStmt fos | diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/StepSummary.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/StepSummary.qll index 2c1362c871d..14a6cdf0d1e 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/StepSummary.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/StepSummary.qll @@ -27,6 +27,8 @@ private module Cached { SharedTypeTrackingStep::loadStoreStep(_, _, this, _) or SharedTypeTrackingStep::loadStoreStep(_, _, _, this) + or + this = DataFlow::PseudoProperties::arrayLikeElement() } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClassValidator.qll b/javascript/ql/src/semmle/javascript/frameworks/ClassValidator.qll new file mode 100644 index 00000000000..381451c393c --- /dev/null +++ b/javascript/ql/src/semmle/javascript/frameworks/ClassValidator.qll @@ -0,0 +1,67 @@ +/** + * Provides predicates for reasoning about sanitization via the `class-validator` library. + */ + +import javascript + +/** + * Provides predicates for reasoning about sanitization via the `class-validator` library. + */ +module ClassValidator { + /** + * Holds if the decorator with the given name sanitizes the input, for the purpose of taint tracking. + */ + bindingset[name] + private predicate isSanitizingDecoratorName(string name) { + // Most decorators do sanitize the input, so only list those that don't. + not name = + [ + "IsDefined", "IsOptional", "NotEquals", "IsNotEmpty", "IsNotIn", "IsString", "IsArray", + "Contains", "NotContains", "IsAscii", "IsByteLength", "IsDataURI", "IsFQDN", "IsJSON", + "IsJWT", "IsObject", "IsNotEmptyObject", "IsLowercase", "IsSurrogatePair", "IsUrl", + "IsUppercase", "Length", "MinLength", "MaxLength", "ArrayContains", "ArrayNotContains", + "ArrayNotEmpty", "ArrayMinSize", "ArrayMaxSize", "ArrayUnique", "Allow", "ValidateNested", + "Validate", + // Consider "Matches" to be non-sanitizing as it is special-cased below + "Matches" + ] + } + + /** Holds if the given call is a decorator that sanitizes values for the purpose of taint tracking, such as `IsBoolean()`. */ + API::CallNode sanitizingDecorator() { + exists(string name | result = API::moduleImport("class-validator").getMember(name).getACall() | + isSanitizingDecoratorName(name) + or + name = "Matches" and + RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(result.getArgument(0)), true) + ) + } + + /** Holds if the given field has a decorator that sanitizes its value for the purpose of taint tracking. */ + predicate isFieldSanitizedByDecorator(FieldDefinition field) { + field.getADecorator().getExpression().flow() = sanitizingDecorator().getReturn().getAUse() + } + + pragma[noinline] + private predicate isFieldSanitizedByDecorator(ClassDefinition cls, string name) { + isFieldSanitizedByDecorator(cls.getField(name)) + } + + pragma[noinline] + private ClassDefinition getClassReferencedByPropRead(DataFlow::PropRead read) { + read.getBase().asExpr().getType().unfold().(ClassType).getClass() = result + } + + /** + * Holds if the given property read refers to a field that has a sanitizing decorator. + * + * Only holds when TypeScript types are available. + */ + pragma[noinline] + predicate isAccessToSanitizedField(DataFlow::PropRead read) { + exists(ClassDefinition class_ | + class_ = getClassReferencedByPropRead(read) and + isFieldSanitizedByDecorator(class_, read.getPropertyName()) + ) + } +} diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll index 451ad3f24d2..18204c5b59b 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -206,19 +206,14 @@ module ClientRequest { /** * A model of a URL request made using the `axios` library. */ - class AxiosUrlRequest extends ClientRequest::Range { + class AxiosUrlRequest extends ClientRequest::Range, API::CallNode { string method; AxiosUrlRequest() { - exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() | - moduleName = "axios" and - ( - callee = DataFlow::moduleImport(moduleName) and method = "request" - or - callee = DataFlow::moduleMember(moduleName, method) and - (method = httpMethodName() or method = "request") - ) - ) + this = API::moduleImport("axios").getACall() and method = "request" + or + this = API::moduleImport("axios").getMember(method).getACall() and + method = [httpMethodName(), "request"] } private int getOptionsArgIndex() { @@ -247,12 +242,10 @@ module ClientRequest { method = "request" and result = getOptionArgument(0, "data") or - (method = "post" or method = "put" or method = "put") and - (result = getArgument(1) or result = getOptionArgument(2, "data")) + method = ["post", "put"] and + result = [getArgument(1), getOptionArgument(2, "data")] or - exists(string name | name = "headers" or name = "params" | - result = getOptionArgument([0 .. 2], name) - ) + result = getOptionArgument([0 .. 2], ["headers", "params"]) } /** Gets the response type from the options passed in. */ @@ -275,6 +268,10 @@ module ClientRequest { responseType = getResponseType() and promise = true and result = this + or + responseType = getResponseType() and + promise = false and + result = getReturn().getPromisedError().getMember("response").getAnImmediateUse() } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Express.qll b/javascript/ql/src/semmle/javascript/frameworks/Express.qll index 0a618c7f125..7ef4bf7525a 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Express.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Express.qll @@ -19,6 +19,9 @@ module Express { or // `app = express.createServer()` result = DataFlow::moduleMember("express", "createServer").getAnInvocation() + or + // `app = express().disable(x)`, and other chaining methods + result = appCreation().getAMemberCall(["engine", "set", "param", "enable", "disable", "on"]) } /** @@ -396,7 +399,7 @@ module Express { } /** An Express response source. */ - abstract private class ResponseSource extends HTTP::Servers::ResponseSource { } + abstract class ResponseSource extends HTTP::Servers::ResponseSource { } /** * An Express response source, that is, the response parameter of a @@ -427,7 +430,7 @@ module Express { } /** An Express request source. */ - abstract private class RequestSource extends HTTP::Servers::RequestSource { } + abstract class RequestSource extends HTTP::Servers::RequestSource { } /** * An Express request source, that is, the request parameter of a @@ -471,73 +474,98 @@ module Express { * Gets a reference to the "query" object from a request-object originating from route-handler `rh`. */ DataFlow::SourceNode getAQueryObjectReference(DataFlow::TypeTracker t, RouteHandler rh) { - t.startInProp("query") and - result = rh.getARequestSource() - or - exists(DataFlow::TypeTracker t2 | result = getAQueryObjectReference(t2, rh).track(t2, t)) + result = queryRef(rh.getARequestSource(), t) } /** * Gets a reference to the "params" object from a request-object originating from route-handler `rh`. */ DataFlow::SourceNode getAParamsObjectReference(DataFlow::TypeTracker t, RouteHandler rh) { - t.startInProp("params") and - result = rh.getARequestSource() + result = paramsRef(rh.getARequestSource(), t) + } + + /** The input parameter to an `app.param()` route handler. */ + private class ParamHandlerInputAccess extends HTTP::RequestInputAccess { + RouteHandler rh; + + ParamHandlerInputAccess() { + exists(RouteSetup setup | rh = setup.getARouteHandler() | + this = DataFlow::parameterNode(rh.getRouteHandlerParameter("parameter")) + ) + } + + override HTTP::RouteHandler getRouteHandler() { result = rh } + + override string getKind() { result = "parameter" } + } + + /** Gets a data flow node referring to `req.query`. */ + private DataFlow::SourceNode queryRef(RequestSource req, DataFlow::TypeTracker t) { + t.start() and + result = req.ref().getAPropertyRead("query") or - exists(DataFlow::TypeTracker t2 | result = getAParamsObjectReference(t2, rh).track(t2, t)) + exists(DataFlow::TypeTracker t2 | result = queryRef(req, t2).track(t2, t)) + } + + /** Gets a data flow node referring to `req.query`. */ + private DataFlow::SourceNode queryRef(RequestSource req) { + result = queryRef(req, DataFlow::TypeTracker::end()) + } + + /** Gets a data flow node referring to `req.params`. */ + private DataFlow::SourceNode paramsRef(RequestSource req, DataFlow::TypeTracker t) { + t.start() and + result = req.ref().getAPropertyRead("params") + or + exists(DataFlow::TypeTracker t2 | result = paramsRef(req, t2).track(t2, t)) + } + + /** Gets a data flow node referring to `req.params`. */ + private DataFlow::SourceNode paramsRef(RequestSource req) { + result = paramsRef(req, DataFlow::TypeTracker::end()) } /** * An access to a user-controlled Express request input. */ class RequestInputAccess extends HTTP::RequestInputAccess { - RouteHandler rh; + RequestSource request; string kind; RequestInputAccess() { kind = "parameter" and - this = - [ - getAQueryObjectReference(DataFlow::TypeTracker::end(), rh), - getAParamsObjectReference(DataFlow::TypeTracker::end(), rh) - ].getAPropertyRead() + this = [queryRef(request), paramsRef(request)].getAPropertyRead() or - exists(DataFlow::SourceNode request | request = rh.getARequestSource().ref() | + exists(DataFlow::SourceNode ref | ref = request.ref() | kind = "parameter" and - this = request.getAMethodCall("param") + this = ref.getAMethodCall("param") or // `req.originalUrl` kind = "url" and - this = request.getAPropertyRead("originalUrl") + this = ref.getAPropertyRead("originalUrl") or // `req.cookies` kind = "cookie" and - this = request.getAPropertyRead("cookies") + this = ref.getAPropertyRead("cookies") or // `req.files`, treated the same as `req.body`. // `express-fileupload` uses .files, and `multer` uses .files or .file kind = "body" and - this = request.getAPropertyRead(["files", "file"]) - ) - or - kind = "body" and - this.asExpr() = rh.getARequestBodyAccess() - or - // `value` in `router.param('foo', (req, res, next, value) => { ... })` - kind = "parameter" and - exists(RouteSetup setup | rh = setup.getARouteHandler() | - this = DataFlow::parameterNode(rh.getRouteHandlerParameter("parameter")) + this = ref.getAPropertyRead(["files", "file"]) + or + kind = "body" and + this = ref.getAPropertyRead("body") ) } - override RouteHandler getRouteHandler() { result = rh } + override RouteHandler getRouteHandler() { result = request.getRouteHandler() } override string getKind() { result = kind } override predicate isUserControlledObject() { kind = "body" and exists(ExpressLibraries::BodyParser bodyParser, RouteHandlerExpr expr | - expr.getBody() = rh and + expr.getBody() = request.getRouteHandler() and bodyParser.producesUserControlledObjects() and bodyParser.flowsToExpr(expr.getAMatchingAncestor()) ) @@ -548,13 +576,11 @@ module Express { forall(ExpressLibraries::BodyParser bodyParser | bodyParser.producesUserControlledObjects()) or kind = "parameter" and - exists(DataFlow::Node request | request = DataFlow::valueNode(rh.getARequestExpr()) | - this.(DataFlow::MethodCallNode).calls(request, "param") - ) + this = request.ref().getAMethodCall("param") or // `req.query.name` kind = "parameter" and - this = getAQueryObjectReference(DataFlow::TypeTracker::end(), rh).getAPropertyRead() + this = queryRef(request).getAPropertyRead() } } @@ -562,29 +588,14 @@ module Express { * An access to a header on an Express request. */ private class RequestHeaderAccess extends HTTP::RequestHeaderAccess { - RouteHandler rh; + RequestSource request; RequestHeaderAccess() { - exists(DataFlow::Node request | request = DataFlow::valueNode(rh.getARequestExpr()) | - exists(string methodName | - // `req.get(...)` or `req.header(...)` - this.(DataFlow::MethodCallNode).calls(request, methodName) - | - methodName = "get" or - methodName = "header" - ) - or - exists(DataFlow::PropRead headers | - // `req.headers.name` - headers.accesses(request, "headers") and - this = headers.getAPropertyRead() - ) - or - exists(string propName | propName = "host" or propName = "hostname" | - // `req.host` and `req.hostname` are derived from headers - this.(DataFlow::PropRead).accesses(request, propName) - ) - ) + this = request.ref().getAMethodCall(["get", "header"]) + or + this = request.ref().getAPropertyRead("headers").getAPropertyRead() + or + this = request.ref().getAPropertyRead(["host", "hostname"]) } override string getAHeaderName() { @@ -597,7 +608,7 @@ module Express { ) } - override RouteHandler getRouteHandler() { result = rh } + override RouteHandler getRouteHandler() { result = request.getRouteHandler() } override string getKind() { result = "header" } } @@ -605,14 +616,7 @@ module Express { /** * HTTP headers created by Express calls */ - abstract private class ExplicitHeader extends HTTP::ExplicitHeaderDefinition { - Expr response; - - /** - * Gets the response expression that this header is set on. - */ - Expr getResponse() { result = response } - } + abstract private class ExplicitHeader extends HTTP::ExplicitHeaderDefinition { } /** * Holds if `e` is an HTTP request object. @@ -641,16 +645,13 @@ module Express { * An invocation of the `redirect` method of an HTTP response object. */ private class RedirectInvocation extends HTTP::RedirectInvocation, MethodCallExpr { - RouteHandler rh; + ResponseSource response; - RedirectInvocation() { - getReceiver() = rh.getAResponseExpr() and - getMethodName() = "redirect" - } + RedirectInvocation() { this = response.ref().getAMethodCall("redirect").asExpr() } override Expr getUrlArgument() { result = getLastArgument() } - override RouteHandler getRouteHandler() { result = rh } + override RouteHandler getRouteHandler() { result = response.getRouteHandler() } } /** @@ -668,21 +669,18 @@ module Express { * An invocation of the `set` or `header` method on an HTTP response object that * sets multiple headers. */ - class SetMultipleHeaders extends ExplicitHeader, DataFlow::ValueNode { - override MethodCallExpr astNode; - RouteHandler rh; + class SetMultipleHeaders extends ExplicitHeader, DataFlow::MethodCallNode { + ResponseSource response; SetMultipleHeaders() { - astNode.getReceiver() = rh.getAResponseExpr() and - response = astNode.getReceiver() and - astNode.getMethodName() = any(string n | n = "set" or n = "header") and - astNode.getNumArgument() = 1 + this = response.ref().getAMethodCall(["set", "header"]) and + getNumArgument() = 1 } /** * Gets a reference to the multiple headers object that is to be set. */ - private DataFlow::SourceNode getAHeaderSource() { result.flowsToExpr(astNode.getArgument(0)) } + private DataFlow::SourceNode getAHeaderSource() { result.flowsTo(getArgument(0)) } override predicate definesExplicitly(string headerName, Expr headerValue) { exists(string header | @@ -691,7 +689,7 @@ module Express { ) } - override RouteHandler getRouteHandler() { result = rh } + override RouteHandler getRouteHandler() { result = response.getRouteHandler() } override Expr getNameExpr() { exists(DataFlow::PropWrite write | getAHeaderSource().getAPropertyWrite() = write | @@ -711,31 +709,26 @@ module Express { * An argument passed to the `send` or `end` method of an HTTP response object. */ private class ResponseSendArgument extends HTTP::ResponseSendArgument { - RouteHandler rh; + ResponseSource response; - ResponseSendArgument() { - exists(MethodCallExpr mce | - mce.calls(rh.getAResponseExpr(), "send") and - this = mce.getArgument(0) - ) - } + ResponseSendArgument() { this = response.ref().getAMethodCall("send").getArgument(0).asExpr() } - override RouteHandler getRouteHandler() { result = rh } + override RouteHandler getRouteHandler() { result = response.getRouteHandler() } } /** * An invocation of the `cookie` method on an HTTP response object. */ class SetCookie extends HTTP::CookieDefinition, MethodCallExpr { - RouteHandler rh; + ResponseSource response; - SetCookie() { calls(rh.getAResponseExpr(), "cookie") } + SetCookie() { this = response.ref().getAMethodCall("cookie").asExpr() } override Expr getNameArgument() { result = getArgument(0) } override Expr getValueArgument() { result = getArgument(1) } - override RouteHandler getRouteHandler() { result = rh } + override RouteHandler getRouteHandler() { result = response.getRouteHandler() } } /** @@ -756,11 +749,11 @@ module Express { * An object passed to the `render` method of an HTTP response object. */ class TemplateObjectInput extends DataFlow::Node { - RouteHandler rh; + ResponseSource response; TemplateObjectInput() { exists(DataFlow::MethodCallNode render | - render.calls(rh.getAResponseExpr().flow(), "render") and + render = response.ref().getAMethodCall("render") and this = render.getArgument(1) ) } @@ -768,7 +761,7 @@ module Express { /** * Gets the route handler that uses this object. */ - RouteHandler getRouteHandler() { result = rh } + RouteHandler getRouteHandler() { result = response.getRouteHandler() } } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll index cf632cc6e1e..f6c02a54e8c 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll @@ -192,3 +192,12 @@ private module Fancylog { override DataFlow::Node getAMessageComponent() { result = getAnArgument() } } } + +/** + * A class modelling [debug](https://npmjs.org/package/debug) as a logging mechanism. + */ +private class DebugLoggerCall extends LoggerCall, API::CallNode { + DebugLoggerCall() { this = API::moduleImport("debug").getReturn().getACall() } + + override DataFlow::Node getAMessageComponent() { result = getAnArgument() } +} diff --git a/javascript/ql/src/semmle/javascript/frameworks/Markdown.qll b/javascript/ql/src/semmle/javascript/frameworks/Markdown.qll index 743341325a7..b2235577b04 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Markdown.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Markdown.qll @@ -2,159 +2,188 @@ * Provides classes for modelling common markdown parsers and generators. */ +import semmle.javascript.Unit import javascript /** - * A taint step for the `marked` library, that converts markdown to HTML. + * A module providing taint-steps for common markdown parsers and generators. */ -private class MarkedStep extends TaintTracking::SharedTaintStep { - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - exists(DataFlow::CallNode call | - call = DataFlow::globalVarRef("marked").getACall() - or - call = DataFlow::moduleImport("marked").getACall() - | - succ = call and - pred = call.getArgument(0) - ) - } -} - -/** - * A taint step for the `markdown-table` library. - */ -private class MarkdownTableStep extends TaintTracking::SharedTaintStep { - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - exists(DataFlow::CallNode call | call = DataFlow::moduleImport("markdown-table").getACall() | - succ = call and - pred = call.getArgument(0) - ) - } -} - -/** - * A taint step for the `showdown` library. - */ -private class ShowDownStep extends TaintTracking::SharedTaintStep { - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - exists(DataFlow::CallNode call | - call = - [DataFlow::globalVarRef("showdown"), DataFlow::moduleImport("showdown")] - .getAConstructorInvocation("Converter") - .getAMemberCall(["makeHtml", "makeMd"]) - | - succ = call and - pred = call.getArgument(0) - ) - } -} - -/** - * Classes and predicates for modelling taint steps in `unified` and `remark`. - */ -private module Unified { +module Markdown { /** - * The creation of a parser from `unified`. - * The `remark` module is a shorthand that initializes `unified` with a markdown parser. + * A taint-step that parses a markdown document, but preserves taint. */ - DataFlow::CallNode unified() { result = DataFlow::moduleImport(["unified", "remark"]).getACall() } - - /** - * A chain of method calls that process an input with `unified`. - */ - class UnifiedChain extends DataFlow::CallNode { - DataFlow::CallNode root; - - UnifiedChain() { - root = unified() and - this = root.getAChainedMethodCall(["process", "processSync"]) - } + class MarkdownStep extends Unit { + /** + * Holds if there is a taint-step from `pred` to `succ` for a taint-preserving markdown parser. + */ + abstract predicate step(DataFlow::Node pred, DataFlow::Node succ); /** - * Gets a plugin that is used in this chain. + * Holds if the taint-step preserves HTML. */ - DataFlow::Node getAUsedPlugin() { result = root.getAChainedMethodCall("use").getArgument(0) } - - /** - * Gets the input that is processed. - */ - DataFlow::Node getInput() { result = getArgument(0) } - - /** - * Gets the processed output. - */ - DataFlow::Node getOutput() { - this.getCalleeName() = "process" and result = getABoundCallbackParameter(1, 1) - or - this.getCalleeName() = "processSync" and result = this - } + predicate preservesHTML() { any() } } - /** - * A taint step for the `unified` library. - */ - class UnifiedStep extends TaintTracking::SharedTaintStep { + private class MarkdownStepAsTaintStep extends TaintTracking::SharedTaintStep { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - exists(UnifiedChain chain | - // sanitizer. Mostly looking for `rehype-sanitize`, but also other plugins with `sanitize` in their name. - not chain.getAUsedPlugin().getALocalSource() = - DataFlow::moduleImport(any(string s | s.matches("%sanitize%"))) - | - pred = chain.getInput() and - succ = chain.getOutput() - ) + any(MarkdownStep step).step(pred, succ) } } -} - -/** - * A taint step for the `snarkdown` library. - */ -private class SnarkdownStep extends TaintTracking::SharedTaintStep { - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - exists(DataFlow::CallNode call | call = DataFlow::moduleImport("snarkdown").getACall() | - call = succ and - pred = call.getArgument(0) - ) - } -} - -/** - * Classes and predicates for modelling taint steps the `markdown-it` library. - */ -private module MarkdownIt { - /** - * The creation of a parser from `markdown-it`. - */ - private API::Node markdownIt() { - exists(API::InvokeNode call | - call = API::moduleImport("markdown-it").getAnInvocation() - or - call = API::moduleImport("markdown-it").getMember("Markdown").getAnInvocation() - | - call.getParameter(0).getMember("html").getARhs().mayHaveBooleanValue(true) and - result = call.getReturn() - ) - or - exists(API::CallNode call | - call = markdownIt().getMember(["use", "set", "configure", "enable", "disable"]).getACall() and - result = call.getReturn() and - not call.getParameter(0).getAValueReachingRhs() = - DataFlow::moduleImport("markdown-it-sanitizer") - ) - } /** - * A taint step for the `markdown-it` library. + * A taint step for the `marked` library, that converts markdown to HTML. */ - private class MarkdownItStep extends TaintTracking::SharedTaintStep { + private class MarkedStep extends MarkdownStep { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - exists(API::CallNode call | - call = markdownIt().getMember(["render", "renderInline"]).getACall() + exists(DataFlow::CallNode call | + call = DataFlow::globalVarRef("marked").getACall() + or + call = DataFlow::moduleImport("marked").getACall() | succ = call and pred = call.getArgument(0) ) } } + + /** + * A taint step for the `markdown-table` library. + */ + private class MarkdownTableStep extends MarkdownStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(DataFlow::CallNode call | call = DataFlow::moduleImport("markdown-table").getACall() | + succ = call and + pred = call.getArgument(0) + ) + } + } + + /** + * A taint step for the `showdown` library. + */ + private class ShowDownStep extends MarkdownStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(DataFlow::CallNode call | + call = + [DataFlow::globalVarRef("showdown"), DataFlow::moduleImport("showdown")] + .getAConstructorInvocation("Converter") + .getAMemberCall(["makeHtml", "makeMd"]) + | + succ = call and + pred = call.getArgument(0) + ) + } + } + + /** + * Classes and predicates for modelling taint steps in `unified` and `remark`. + */ + private module Unified { + /** + * The creation of a parser from `unified`. + * The `remark` module is a shorthand that initializes `unified` with a markdown parser. + */ + DataFlow::CallNode unified() { + result = DataFlow::moduleImport(["unified", "remark"]).getACall() + } + + /** + * A chain of method calls that process an input with `unified`. + */ + class UnifiedChain extends DataFlow::CallNode { + DataFlow::CallNode root; + + UnifiedChain() { + root = unified() and + this = root.getAChainedMethodCall(["process", "processSync"]) + } + + /** + * Gets a plugin that is used in this chain. + */ + DataFlow::Node getAUsedPlugin() { result = root.getAChainedMethodCall("use").getArgument(0) } + + /** + * Gets the input that is processed. + */ + DataFlow::Node getInput() { result = getArgument(0) } + + /** + * Gets the processed output. + */ + DataFlow::Node getOutput() { + this.getCalleeName() = "process" and result = getABoundCallbackParameter(1, 1) + or + this.getCalleeName() = "processSync" and result = this + } + } + + /** + * A taint step for the `unified` library. + */ + class UnifiedStep extends MarkdownStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(UnifiedChain chain | + // sanitizer. Mostly looking for `rehype-sanitize`, but also other plugins with `sanitize` in their name. + not chain.getAUsedPlugin().getALocalSource() = + DataFlow::moduleImport(any(string s | s.matches("%sanitize%"))) + | + pred = chain.getInput() and + succ = chain.getOutput() + ) + } + } + } + + /** + * A taint step for the `snarkdown` library. + */ + private class SnarkdownStep extends MarkdownStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(DataFlow::CallNode call | call = DataFlow::moduleImport("snarkdown").getACall() | + call = succ and + pred = call.getArgument(0) + ) + } + } + + /** + * Classes and predicates for modelling taint steps the `markdown-it` library. + */ + private module MarkdownIt { + /** + * The creation of a parser from `markdown-it`. + */ + private API::Node markdownIt() { + exists(API::InvokeNode call | + call = API::moduleImport("markdown-it").getAnInvocation() + or + call = API::moduleImport("markdown-it").getMember("Markdown").getAnInvocation() + | + call.getParameter(0).getMember("html").getARhs().mayHaveBooleanValue(true) and + result = call.getReturn() + ) + or + exists(API::CallNode call | + call = markdownIt().getMember(["use", "set", "configure", "enable", "disable"]).getACall() and + result = call.getReturn() and + not call.getParameter(0).getAValueReachingRhs() = + DataFlow::moduleImport("markdown-it-sanitizer") + ) + } + + /** + * A taint step for the `markdown-it` library. + */ + private class MarkdownItStep extends MarkdownStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(API::CallNode call | + call = markdownIt().getMember(["render", "renderInline"]).getACall() + | + succ = call and + pred = call.getArgument(0) + ) + } + } + } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Nest.qll b/javascript/ql/src/semmle/javascript/frameworks/Nest.qll new file mode 100644 index 00000000000..d216fac1712 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/frameworks/Nest.qll @@ -0,0 +1,465 @@ +/** + * Provides classes and predicates for reasoning about [Nest](https://nestjs.com/). + */ + +import javascript +private import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations + +/** + * Provides classes and predicates for reasoning about [Nest](https://nestjs.com/). + */ +module NestJS { + /** Gets an API node referring to the `@nestjs/common` module. */ + private API::Node nestjs() { result = API::moduleImport("@nestjs/common") } + + /** + * Gets a data flow node that is applied as a decorator on the given function. + * + * Note that only methods in a class can have decorators. + */ + private DataFlow::Node getAFunctionDecorator(DataFlow::FunctionNode fun) { + exists(MethodDefinition method | + fun = method.getInit().flow() and + result = method.getADecorator().getExpression().flow() + ) + } + + /** + * A method that is declared as a route handler using a decorator, for example: + * + * ```js + * class C { + * @Get('posts') + * getPosts() { .. } + * } + * ``` + */ + private class NestJSRouteHandler extends HTTP::RouteHandler, DataFlow::FunctionNode { + NestJSRouteHandler() { + getAFunctionDecorator(this) = + nestjs() + .getMember(["Get", "Post", "Put", "Delete", "Patch", "Options", "Head", "All"]) + .getACall() + } + + override HTTP::HeaderDefinition getAResponseHeader(string name) { none() } + + /** + * Holds if this has the `@Redirect()` decorator. + */ + predicate hasRedirectDecorator() { + getAFunctionDecorator(this) = nestjs().getMember("Redirect").getACall() + } + + /** + * Holds if the return value is sent back in the response. + */ + predicate isReturnValueReflected() { + getAFunctionDecorator(this) = nestjs().getMember(["Get", "Post"]).getACall() and + not hasRedirectDecorator() and + not getAFunctionDecorator(this) = nestjs().getMember("Render").getACall() + } + + /** Gets a pipe applied to the inputs of this route handler, not including global pipes. */ + DataFlow::Node getAPipe() { + exists(DataFlow::CallNode decorator | + decorator = nestjs().getMember("UsePipes").getACall() and + result = decorator.getAnArgument() + | + decorator = getAFunctionDecorator(this) + or + exists(DataFlow::ClassNode cls | + this = cls.getAnInstanceMember() and + decorator = cls.getADecorator() + ) + ) + } + } + + /** + * A parameter with a decorator that makes it receive a value derived from the incoming request. + * + * For example, in the following, + * ```js + * @Get(':foo') + * foo(@Param('foo') foo, @Query() query) { ... } + * ``` + * the `foo` and `query` parameters receive (part of) the path and query string, respectively. + */ + private class NestJSRequestInput extends DataFlow::ParameterNode { + DataFlow::CallNode decorator; + string decoratorName; + + NestJSRequestInput() { + decoratorName = + ["Query", "Param", "Headers", "Body", "HostParam", "UploadedFile", "UploadedFiles"] and + decorator = getADecorator() and + decorator = nestjs().getMember(decoratorName).getACall() + } + + /** Gets the decorator marking this as a request input. */ + DataFlow::CallNode getDecorator() { result = decorator } + + /** Gets the route handler on which this parameter appears. */ + NestJSRouteHandler getNestRouteHandler() { result.getAParameter() = this } + + /** Gets a pipe applied to this parameter, not including global pipes. */ + DataFlow::Node getAPipe() { + result = getNestRouteHandler().getAPipe() + or + result = decorator.getArgument(1) + or + decorator.getNumArgument() = 1 and + not decorator.getArgument(0).mayHaveStringValue(_) and + result = decorator.getArgument(0) // One-argument version can either take a pipe or a property name + } + + /** Gets the kind of parameter, for use in `HTTP::RequestInputAccess`. */ + string getInputKind() { + decoratorName = ["Param", "Query"] and result = "parameter" + or + decoratorName = ["Headers", "HostParam"] and result = "header" + or + decoratorName = ["Body", "UploadedFile", "UploadedFiles"] and result = "body" + } + + /** + * Holds if this is sanitized by a sanitizing pipe, for example, a parameter + * with the decorator `@Param('x', ParseIntPipe)` is parsed as an integer, and + * is thus considered to be sanitized. + */ + predicate isSanitizedByPipe() { + hasSanitizingPipe(this, false) + or + hasSanitizingPipe(this, true) and + isSanitizingType(getParameter().getType().unfold()) + } + } + + /** API node entry point for custom implementations of `ValidationPipe` (a common pattern). */ + private class ValidationNodeEntry extends API::EntryPoint { + ValidationNodeEntry() { this = "ValidationNodeEntry" } + + override DataFlow::SourceNode getAUse() { + result.(DataFlow::ClassNode).getName() = "ValidationPipe" + } + + override DataFlow::Node getARhs() { none() } + } + + /** Gets an API node referring to the constructor of `ValidationPipe` */ + private API::Node validationPipe() { + result = nestjs().getMember("ValidationPipe") + or + result = API::root().getASuccessor(any(ValidationNodeEntry e)) + } + + /** + * Gets a pipe (instance or constructor) which causes its input to be sanitized, and thus not seen as a `RequestInputAccess`. + * + * If `dependsOnType` is `true`, then the validation depends on the declared type of the input, + * and some types may not be enough to be considered sanitized. + */ + private API::Node sanitizingPipe(boolean dependsOnType) { + exists(API::Node ctor | + dependsOnType = false and + ctor = nestjs().getMember(["ParseIntPipe", "ParseBoolPipe", "ParseUUIDPipe"]) + or + dependsOnType = true and + ctor = validationPipe() + | + result = [ctor, ctor.getInstance()] + ) + } + + /** + * Holds if `ValidationPipe` is installed as a global pipe by a file in the given folder + * or one of its enclosing folders. + * + * We use folder hierarchy to approximate the scope of globally-installed pipes. + */ + predicate hasGlobalValidationPipe(Folder folder) { + exists(DataFlow::CallNode call | + call.getCalleeName() = "useGlobalPipes" and + call.getArgument(0) = validationPipe().getInstance().getAUse() and + folder = call.getFile().getParentContainer() + ) + or + exists(API::CallNode decorator | + decorator = nestjs().getMember("Module").getACall() and + decorator + .getParameter(0) + .getMember("providers") + .getAMember() + .getMember("useFactory") + .getReturn() + .getARhs() = validationPipe().getInstance().getAUse() and + folder = decorator.getFile().getParentContainer() + ) + or + hasGlobalValidationPipe(folder.getParentContainer()) + } + + /** + * Holds if `param` is affected by a pipe that sanitizes inputs. + */ + private predicate hasSanitizingPipe(NestJSRequestInput param, boolean dependsOnType) { + param.getAPipe() = sanitizingPipe(dependsOnType).getAUse() + or + hasGlobalValidationPipe(param.getFile().getParentContainer()) and + dependsOnType = true + } + + /** + * Holds if a parameter of type `t` is considered sanitized, provided it has been checked by `ValidationPipe` + * (which relies on metadata emitted by the TypeScript compiler). + */ + private predicate isSanitizingType(Type t) { + t instanceof NumberType + or + t instanceof BooleanType + // + // Note: we could consider types with class-validator decorators to be sanitized here, but instead we consider the root + // object to be tainted, but omit taint steps for the individual properties names that have sanitizing decorators. See ClassValidator.qll. + } + + /** + * A user-defined pipe class, for example: + * ```js + * class MyPipe implements PipeTransform { + * transform(value) { return value + '!' } + * } + * ``` + * This can be used as a pipe, for example, `@Param('x', MyPipe)` would pipe + * the request parameter `x` through the `transform` function before flowing into + * the route handler. + */ + private class CustomPipeClass extends DataFlow::ClassNode { + CustomPipeClass() { + exists(ClassDefinition cls | + this = cls.flow() and + cls.getASuperInterface().hasQualifiedName("@nestjs/common", "PipeTransform") + ) + } + + DataFlow::FunctionNode getTransformFunction() { result = getInstanceMethod("transform") } + + DataFlow::ParameterNode getInputData() { result = getTransformFunction().getParameter(0) } + + DataFlow::Node getOutputData() { result = getTransformFunction().getReturnNode() } + + NestJSRequestInput getAnAffectedParameter() { + [getAnInstanceReference(), getAClassReference()].flowsTo(result.getAPipe()) + } + } + + /** + * The input to a custom pipe, seen as a remote flow source. + * + * The type of remote flow depends on which decorator is applied at the parameter, so + * we just classify it as a `RemoteFlowSource`. + */ + private class NestJSCustomPipeInput extends HTTP::RequestInputAccess { + CustomPipeClass pipe; + + NestJSCustomPipeInput() { + this = pipe.getInputData() and + exists(NestJSRequestInput input | + input = pipe.getAnAffectedParameter() and + not input.isSanitizedByPipe() + ) + } + + override string getKind() { + // Use any input kind that the pipe is applied to. + result = pipe.getAnAffectedParameter().getInputKind() + } + + override HTTP::RouteHandler getRouteHandler() { + result = pipe.getAnAffectedParameter().getNestRouteHandler() + } + } + + /** + * A step from the result of a custom pipe, to an affected parameter. + */ + private class CustomPipeStep extends DataFlow::SharedFlowStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(CustomPipeClass pipe | + pred = pipe.getOutputData() and + succ = pipe.getAnAffectedParameter() + ) + } + } + + /** + * A request input parameter that is not sanitized by a pipe, and therefore treated + * as a source of untrusted data. + */ + private class NestJSRequestInputAsRequestInputAccess extends NestJSRequestInput, + HTTP::RequestInputAccess { + NestJSRequestInputAsRequestInputAccess() { + not isSanitizedByPipe() and + not this = any(CustomPipeClass cls).getAnAffectedParameter() + } + + override HTTP::RouteHandler getRouteHandler() { result = getNestRouteHandler() } + + override string getKind() { result = getInputKind() } + + override predicate isUserControlledObject() { + not exists(getAPipe()) and // value is not transformed by a pipe + ( + decorator.getNumArgument() = 0 + or + decoratorName = ["Query", "Body"] + ) + } + } + + private class NestJSHeaderAccess extends NestJSRequestInputAsRequestInputAccess, + HTTP::RequestHeaderAccess { + NestJSHeaderAccess() { decoratorName = "Headers" and decorator.getNumArgument() > 0 } + + override string getAHeaderName() { + result = decorator.getArgument(0).getStringValue().toLowerCase() + } + } + + private predicate isStringType(Type type) { + type instanceof StringType + or + type instanceof AnyType + or + isStringType(type.(PromiseType).getElementType().unfold()) + } + + /** + * A return value from a route handler, seen as an argument to `res.send()`. + * + * For example, + * ```js + * @Get() + * foo() { + * return 'Hello'; + * } + * ``` + * writes `Hello` to the response. + */ + private class ReturnValueAsResponseSend extends HTTP::ResponseSendArgument { + NestJSRouteHandler handler; + + ReturnValueAsResponseSend() { + handler.isReturnValueReflected() and + this = handler.getAReturn().asExpr() and + // Only returned strings are sinks + not exists(Type type | + type = getType() and + not isStringType(type.unfold()) + ) + } + + override HTTP::RouteHandler getRouteHandler() { result = handler } + } + + /** + * A return value from a redirecting route handler, seen as a sink for server-side redirect. + * + * For example, + * ```js + * @Get() + * @Redirect + * foo() { + * return { url: 'https://example.com' } + * } + * ``` + * redirects to `https://example.com`. + */ + private class ReturnValueAsRedirection extends ServerSideUrlRedirect::Sink { + NestJSRouteHandler handler; + + ReturnValueAsRedirection() { + handler.hasRedirectDecorator() and + this = handler.getAReturn().getALocalSource().getAPropertyWrite("url").getRhs() + } + } + + /** + * A parameter decorator created using `createParamDecorator`. + */ + private class CustomParameterDecorator extends API::CallNode { + CustomParameterDecorator() { this = nestjs().getMember("createParamDecorator").getACall() } + + /** Gets the `context` parameter. */ + API::Node getExecutionContext() { result = getParameter(0).getParameter(1) } + + /** Gets a parameter with this decorator applied. */ + DataFlow::ParameterNode getADecoratedParameter() { + result.getADecorator() = getReturn().getReturn().getAUse() + } + + /** Gets a value returned by the decorator's callback, which becomes the value of the decorated parameter. */ + DataFlow::Node getResult() { result = getParameter(0).getReturn().getARhs() } + } + + /** + * A flow step from a custom parameter decorator to a decorated parameter. + */ + private class CustomParameterFlowStep extends DataFlow::SharedFlowStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(CustomParameterDecorator dec | + pred = dec.getResult() and + succ = dec.getADecoratedParameter() + ) + } + } + + private API::Node executionContext() { + result = API::Node::ofType("@nestjs/common", "ExecutionContext") + or + result = any(CustomParameterDecorator d).getExecutionContext() + } + + /** + * A source of `express` request objects, based on the `@Req()` decorator, + * or the context object in a custom decorator. + */ + private class ExpressRequestSource extends Express::RequestSource { + ExpressRequestSource() { + this.(DataFlow::ParameterNode).getADecorator() = + nestjs().getMember(["Req", "Request"]).getReturn().getAnImmediateUse() + or + this = + executionContext() + .getMember("switchToHttp") + .getReturn() + .getMember("getRequest") + .getReturn() + .getAnImmediateUse() + } + + /** + * Gets the route handler that handles this request. + */ + override HTTP::RouteHandler getRouteHandler() { + result.(DataFlow::FunctionNode).getAParameter() = this + } + } + + /** + * A source of `express` response objects, based on the `@Res()` decorator. + */ + private class ExpressResponseSource extends Express::ResponseSource { + ExpressResponseSource() { + this.(DataFlow::ParameterNode).getADecorator() = + nestjs().getMember(["Res", "Response"]).getReturn().getAnImmediateUse() + } + + /** + * Gets the route handler that handles this request. + */ + override HTTP::RouteHandler getRouteHandler() { + result.(DataFlow::FunctionNode).getAParameter() = this + } + } +} diff --git a/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll b/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll index f48dacea700..b27641e313b 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll @@ -166,7 +166,7 @@ private module Mongoose { /** * A Mongoose function. */ - private class MongooseFunction extends API::Node { + abstract private class MongooseFunction extends API::Node { /** * Gets the API-graph node for the result from this function (if the function returns a `Query`). */ diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index bb9b0870a8b..840e9ea9b78 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -465,6 +465,9 @@ module NodeJSLib { ) and t.start() or + t.start() and + result = DataFlow::moduleMember("fs", "promises") + or exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred | pred = fsModule(t2) | result = pred.track(t2, t) or diff --git a/javascript/ql/src/semmle/javascript/frameworks/RxJS.qll b/javascript/ql/src/semmle/javascript/frameworks/RxJS.qll index aff312ee9b0..8cd05f5047d 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/RxJS.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/RxJS.qll @@ -23,7 +23,7 @@ private class RxJsSubscribeStep extends TaintTracking::SharedTaintStep { * created by the `map` call. */ private DataFlow::Node pipeInput(DataFlow::CallNode pipe) { - pipe = DataFlow::moduleMember("rxjs/operators", ["map", "filter"]).getACall() and + pipe = DataFlow::moduleMember("rxjs/operators", any(string s | not s = "catchError")).getACall() and result = pipe.getCallback(0).getParameter(0) } @@ -34,7 +34,8 @@ private DataFlow::Node pipeInput(DataFlow::CallNode pipe) { * the pipe. */ private DataFlow::Node pipeOutput(DataFlow::CallNode pipe) { - pipe = DataFlow::moduleMember("rxjs/operators", "map").getACall() and + // we assume if there is a return, it is an output. + pipe = DataFlow::moduleMember("rxjs/operators", _).getACall() and result = pipe.getCallback(0).getReturnNode() or pipe = DataFlow::moduleMember("rxjs/operators", "filter").getACall() and @@ -48,7 +49,46 @@ private DataFlow::Node pipeOutput(DataFlow::CallNode pipe) { * be special-cased. */ private predicate isIdentityPipe(DataFlow::CallNode pipe) { - pipe = DataFlow::moduleMember("rxjs/operators", "catchError").getACall() + pipe = DataFlow::moduleMember("rxjs/operators", ["catchError", "tap"]).getACall() +} + +/** + * A call to `pipe`, which is assumed to be an `rxjs/operators` pipe. + * + * Has utility methods `getInput`/`getOutput` to get the input/output of each + * element of the pipe. + * These utility methods automatically handle itentity pipes, and the + * first/last elements of the pipe. + */ +private class RxJSPipe extends DataFlow::MethodCallNode { + RxJSPipe() { this.getMethodName() = "pipe" } + + /** + * Gets an input to pipe element `i`. + * Or if `i` is equal to the number of elements, gets the output of the pipe (the call itself) + */ + DataFlow::Node getInput(int i) { + result = pipeInput(this.getArgument(i).getALocalSource()) + or + i = this.getNumArgument() and + result = this + } + + /** + * Gets an output from pipe element `i`. + * Handles identity pipes by getting the output from the previous element. + * If `i` is -1, gets the receiver to the call, which started the pipe. + */ + DataFlow::Node getOutput(int i) { + isIdentityPipe(this.getArgument(i).getALocalSource()) and + result = getOutput(i - 1) + or + not isIdentityPipe(this.getArgument(i).getALocalSource()) and + result = pipeOutput(this.getArgument(i).getALocalSource()) + or + i = -1 and + result = this.getReceiver() + } } /** @@ -56,22 +96,9 @@ private predicate isIdentityPipe(DataFlow::CallNode pipe) { */ private class RxJsPipeMapStep extends TaintTracking::SharedTaintStep { override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) { - exists(DataFlow::MethodCallNode call | call.getMethodName() = "pipe" | - pred = call.getReceiver() and - succ = pipeInput(call.getArgument(0).getALocalSource()) - or - exists(int i | - pred = pipeOutput(call.getArgument(i).getALocalSource()) and - succ = pipeInput(call.getArgument(i + 1).getALocalSource()) - ) - or - pred = pipeOutput(call.getLastArgument().getALocalSource()) and - succ = call - or - // Handle a common case where the last step is `catchError`. - isIdentityPipe(call.getLastArgument().getALocalSource()) and - pred = pipeOutput(call.getArgument(call.getNumArgument() - 2)) and - succ = call + exists(RxJSPipe pipe, int i | + pred = pipe.getOutput(i) and + succ = pipe.getInput(i + 1) ) } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll index ce702decc96..4d345142eb4 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll @@ -341,18 +341,28 @@ private module Sqlite { result = sqlite().getMember("verbose").getReturn() } - /** Gets an expression that constructs a Sqlite database instance. */ + /** Gets an expression that constructs or returns a Sqlite database instance. */ API::Node database() { // new require('sqlite3').Database() result = sqlite().getMember("Database").getInstance() or + // chained call + result = getAChainingQueryCall() + or result = API::Node::ofType("sqlite3", "Database") } + /** A call to a query method on a Sqlite database instance that returns the same instance. */ + private API::Node getAChainingQueryCall() { + result = database().getMember(["all", "each", "exec", "get", "run"]).getReturn() + } + /** A call to a Sqlite query method. */ private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode { QueryCall() { - this = database().getMember(["all", "each", "exec", "get", "prepare", "run"]).getACall() + this = getAChainingQueryCall().getAnImmediateUse() + or + this = database().getMember("prepare").getACall() } override DataFlow::Node getAQueryArgument() { result = getArgument(0) } diff --git a/javascript/ql/src/semmle/javascript/internal/CachedStages.qll b/javascript/ql/src/semmle/javascript/internal/CachedStages.qll index 40b5e5ba626..ece8aa3734d 100644 --- a/javascript/ql/src/semmle/javascript/internal/CachedStages.qll +++ b/javascript/ql/src/semmle/javascript/internal/CachedStages.qll @@ -135,6 +135,8 @@ module Stages { exists(any(AccessPath a).getAnInstanceIn(_)) or exists(any(DataFlow::PropRef ref).getBase()) + or + exists(any(DataFlow::ClassNode cls)) } } diff --git a/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll b/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll index 0a5a701ab82..d66fe65b747 100644 --- a/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll +++ b/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll @@ -10,67 +10,7 @@ */ import javascript - -/** - * Provides heuristics for identifying names related to sensitive information. - * - * INTERNAL: Do not use directly. - */ -module HeuristicNames { - /** - * Gets a regular expression that identifies strings that may indicate the presence of secret - * or trusted data. - */ - string maybeSecret() { result = "(?is).*((?` tag created using `document.createElement`. + */ + private DataFlow::SourceNode scriptTag(DataFlow::TypeTracker t) { + t.start() and + exists(DataFlow::CallNode call | call = result | + call = DOM::documentRef().getAMethodCall("createElement") and + call.getArgument(0).mayHaveStringValue("script") + ) + or + exists(DataFlow::TypeTracker t2 | result = scriptTag(t2).track(t2, t)) + } + + /** + * Gets a reference to a `