diff --git a/.lgtm.yml b/.lgtm.yml new file mode 100755 index 00000000000..4098d8b1ce6 --- /dev/null +++ b/.lgtm.yml @@ -0,0 +1,17 @@ +path_classifiers: + library: + - javascript/externs + + test: + - csharp/ql/src + - csharp/ql/test + - javascript/ql/src + - javascript/ql/test + +queries: + - include: "*" + +extraction: + python: + python_setup: + version: 3 diff --git a/change-notes/1.19/analysis-cpp.md b/change-notes/1.19/analysis-cpp.md index aa74fddba1b..c5308bc5a71 100644 --- a/change-notes/1.19/analysis-cpp.md +++ b/change-notes/1.19/analysis-cpp.md @@ -6,16 +6,18 @@ | **Query** | **Tags** | **Purpose** | |-----------------------------|-----------|--------------------------------------------------------------------| -| Cast between HRESULT and a Boolean type (`cpp/hresult-boolean-conversion`) | external/cwe/cwe-253 | Finds logic errors caused by mistakenly treating the Windows `HRESULT` type as a Boolean instead of testing it with the appropriate macros. Enabled by default. | +| Cast between `HRESULT` and a Boolean type (`cpp/hresult-boolean-conversion`) | external/cwe/cwe-253 | Finds logic errors caused by mistakenly treating the Windows `HRESULT` type as a Boolean instead of testing it with the appropriate macros. Enabled by default. | | Setting a DACL to `NULL` in a `SECURITY_DESCRIPTOR` (`cpp/unsafe-dacl-security-descriptor`) | external/cwe/cwe-732 | This query finds code that creates world-writable objects on Windows by setting their DACL to `NULL`. Enabled by default. | -| Cast from char* to wchar_t* | security, external/cwe/cwe-704 | Detects potentially dangerous casts from char* to wchar_t*. Enabled by default on LGTM. | +| Cast from `char*` to `wchar_t*` | security, external/cwe/cwe-704 | Detects potentially dangerous casts from `char*` to `wchar_t*`. Enabled by default on LGTM. | +| Dead code due to `goto` or `break` statement (`cpp/dead-code-goto`) | maintainability, external/cwe/cwe-561 | Detects dead code following a goto or break statement. Enabled by default on LGTM. | ## Changes to existing queries | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| -| Resource not released in destructor | Fewer false positive results | Placement new is now excluded from the query. | +| Resource not released in destructor | Fewer false positive results | Placement new is now excluded from the query. Also fixed an issue where false positives could occur if the destructor body was not in the snapshot. | | Missing return statement (`cpp/missing-return`) | Visible by default | The precision of this query has been increased from 'medium' to 'high', which makes it visible by default in LGTM. It was 'medium' in release 1.17 and 1.18 because it had false positives due to an extractor bug that was fixed in 1.18. | +| Missing return statement | Fewer false positive results | The query is now produces correct results when a function returns a template-dependent type. | | Call to memory access function may overflow buffer | More correct results | Array indexing with a negative index is now detected by this query. | | Suspicious add with sizeof | Fewer false positive results | Arithmetic with void pointers (where allowed) is now excluded from this query. | | Wrong type of arguments to formatting function | Fewer false positive results | False positive results involving typedefs have been removed. Expected argument types are determined more accurately, especially for wide string and pointer types. Custom (non-standard) formatting functions are also identified more accurately. | diff --git a/change-notes/1.19/analysis-csharp.md b/change-notes/1.19/analysis-csharp.md index 3b85b8f367c..963bf907622 100644 --- a/change-notes/1.19/analysis-csharp.md +++ b/change-notes/1.19/analysis-csharp.md @@ -12,8 +12,8 @@ ## Changes to existing queries -| **Query** | **Expected impact** | **Change** | -|----------------------------|------------------------|------------------------------------------------------------------| +| Inconsistent lock sequence (`cs/inconsistent-lock-sequence`) | More results | This query now finds inconsistent lock sequences globally across calls. | + | *@name of query (Query ID)*| *Impact on results* | *How/why the query has changed* | diff --git a/change-notes/1.19/analysis-java.md b/change-notes/1.19/analysis-java.md index becec5b9d98..f2d0d5adf97 100644 --- a/change-notes/1.19/analysis-java.md +++ b/change-notes/1.19/analysis-java.md @@ -4,13 +4,18 @@ ## New queries -| **Query** | **Tags** | **Purpose** | -|-----------------------------------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Query** | **Tags** | **Purpose** | +|-----------------------------|-----------|--------------------------------------------------------------------| ## Changes to existing queries -| **Query** | **Expected impact** | **Change** | -| Unreachable catch clause (`java/unreachable-catch-clause`) | Fewer false-positive results | This rule now accounts for calls to generic methods that throw generic exceptions. | +| **Query** | **Expected impact** | **Change** | +|----------------------------|------------------------|------------------------------------------------------------------| +| Array index out of bounds (`java/index-out-of-bounds`) | Fewer false positive results | False positives involving arrays with a length evenly divisible by 3 or some greater number and an index being increased with a similar stride length are no longer reported. | +| Unreachable catch clause (`java/unreachable-catch-clause`) | Fewer false positive results | This rule now accounts for calls to generic methods that throw generic exceptions. | +| Useless comparison test (`java/constant-comparison`) | Fewer false positive results | Constant comparisons guarding `java.util.ConcurrentModificationException` are no longer reported, as they are intended to always be false in the absence of API misuse. | ## Changes to QL libraries +* The `ParityAnalysis` library is replaced with the more general `ModulusAnalysis` library, which improves the range analysis. + diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md index beedd61d6da..9e2c73dd3d4 100644 --- a/change-notes/1.19/analysis-javascript.md +++ b/change-notes/1.19/analysis-javascript.md @@ -35,6 +35,7 @@ | Remote property injection | Fewer results | The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. | | Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. | | Server-side URL redirect | More results | This rule now recognizes redirection calls in more cases. | +| Unused variable, import, function or class | Fewer results | This rule now flags import statements with multiple unused imports once. | | User-controlled bypass of security check | Fewer results | This rule no longer flags conditions that guard early returns. The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. | | Whitespace contradicts operator precedence | Fewer false-positive results | This rule no longer flags operators with asymmetric whitespace. | diff --git a/cpp/config/suites/security/cwe-428 b/cpp/config/suites/security/cwe-428 new file mode 100644 index 00000000000..bef395b717a --- /dev/null +++ b/cpp/config/suites/security/cwe-428 @@ -0,0 +1,3 @@ +# CWE-428: Unquoted Search Path or Element ++ semmlecode-cpp-queries/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql: /CWE/CWE-428 + @name NULL application name with an unquoted path in call to CreateProcess (CWE-428) diff --git a/cpp/config/suites/security/default b/cpp/config/suites/security/default index baa8a3f5bc8..9ce1f2e8fc9 100644 --- a/cpp/config/suites/security/default +++ b/cpp/config/suites/security/default @@ -19,6 +19,7 @@ @import "cwe-327" @import "cwe-367" @import "cwe-416" +@import "cwe-428" @import "cwe-457" @import "cwe-468" @import "cwe-676" diff --git a/cpp/ql/src/Critical/DeadCodeGoto.cpp b/cpp/ql/src/Critical/DeadCodeGoto.cpp new file mode 100644 index 00000000000..3dd2be64e0e --- /dev/null +++ b/cpp/ql/src/Critical/DeadCodeGoto.cpp @@ -0,0 +1,7 @@ +goto err1; +free(pointer); // BAD: this line is unreachable +err1: return -1; + +free(pointer); // GOOD: this line is reachable +goto err2; +err2: return -1; diff --git a/cpp/ql/src/Critical/DeadCodeGoto.qhelp b/cpp/ql/src/Critical/DeadCodeGoto.qhelp new file mode 100644 index 00000000000..8b0dacae65b --- /dev/null +++ b/cpp/ql/src/Critical/DeadCodeGoto.qhelp @@ -0,0 +1,28 @@ + + + + + +

+Code immediately following a goto or break statement will not be executed, +unless there is a label or switch case. When the code is necessary, this leads to logical errors or +resource leaks. If the code is unnecessary, it may confuse readers. +

+
+ +

+If the unreachable code is necessary, move the goto or break statement to +after the code. Otherwise, delete the unreachable code. +

+ +
+ + + +
  • + The CERT C Secure Coding Standard: MSC12-C. Detect and remove code that has no effect or is never executed. +
  • +
    +
    diff --git a/cpp/ql/src/Critical/DeadCodeGoto.ql b/cpp/ql/src/Critical/DeadCodeGoto.ql new file mode 100644 index 00000000000..831b7e1cfc3 --- /dev/null +++ b/cpp/ql/src/Critical/DeadCodeGoto.ql @@ -0,0 +1,33 @@ +/** + * @name Dead code due to goto or break statement + * @description A goto or break statement is followed by unreachable code. + * @kind problem + * @problem.severity warning + * @precision high + * @id cpp/dead-code-goto + * @tags maintainability + * external/cwe/cwe-561 + */ + +import cpp + +Stmt getNextRealStmt(Block b, int i) { + result = b.getStmt(i + 1) and + not result instanceof EmptyStmt + or + b.getStmt(i + 1) instanceof EmptyStmt and + result = getNextRealStmt(b, i + 1) +} + +from JumpStmt js, Block b, int i, Stmt s +where b.getStmt(i) = js + and s = getNextRealStmt(b, i) + // the next statement isn't jumped to + and not s instanceof LabelStmt + and not s instanceof SwitchCase + // the next statement isn't breaking out of a switch + and not s.(BreakStmt).getBreakable() instanceof SwitchStmt + // the next statement isn't a loop that can be jumped into + and not exists (LabelStmt ls | s.(Loop).getStmt().getAChild*() = ls) + and not exists (SwitchCase sc | s.(Loop).getStmt().getAChild*() = sc) +select js, "This statement makes $@ unreachable.", s, s.toString() diff --git a/cpp/ql/src/Critical/OverflowDestination.cpp b/cpp/ql/src/Critical/OverflowDestination.cpp index 1a758430bf4..02c5281d0b6 100644 --- a/cpp/ql/src/Critical/OverflowDestination.cpp +++ b/cpp/ql/src/Critical/OverflowDestination.cpp @@ -1,13 +1,13 @@ int main(int argc, char* argv[]) { - char param[SIZE]; + char param[20]; + char *arg1; - char arg1[10]; - char arg2[20]; + arg1 = argv[1]; //wrong: only uses the size of the source (argv[1]) when using strncpy - strncpy(param, argv[1], strlen(arg1)); + strncpy(param, arg1, strlen(arg1)); //correct: uses the size of the destination array as well - strncpy(param, argv[1], min(strlen(arg1, sizeof(param) -1))); + strncpy(param, arg1, min(strlen(arg1), sizeof(param) -1)); } diff --git a/cpp/ql/src/Critical/OverflowDestination.ql b/cpp/ql/src/Critical/OverflowDestination.ql index ea7e759bd9d..9f03146e366 100644 --- a/cpp/ql/src/Critical/OverflowDestination.ql +++ b/cpp/ql/src/Critical/OverflowDestination.ql @@ -5,26 +5,38 @@ * @kind problem * @id cpp/overflow-destination * @problem.severity warning + * @precision low * @tags reliability * security * external/cwe/cwe-119 * external/cwe/cwe-131 */ import cpp -import semmle.code.cpp.pointsto.PointsTo +import semmle.code.cpp.security.TaintTracking -predicate sourceSized(FunctionCall fc) +/** + * Holds if `fc` is a call to a copy operation where the size argument contains + * a reference to the source argument. For example: + * ``` + * memcpy(dest, src, sizeof(src)); + * ``` + */ +predicate sourceSized(FunctionCall fc, Expr src) { exists(string name | (name = "strncpy" or name = "strncat" or name = "memcpy" or name = "memmove") and fc.getTarget().hasQualifiedName(name)) and - exists(Expr dest, Expr src, Expr size, Variable v | + exists(Expr dest, Expr size, Variable v | fc.getArgument(0) = dest and fc.getArgument(1) = src and fc.getArgument(2) = size and src = v.getAnAccess() and size.getAChild+() = v.getAnAccess() and + + // exception: `dest` is also referenced in the size argument not exists(Variable other | dest = other.getAnAccess() and size.getAChild+() = other.getAnAccess()) and + + // exception: `src` and `dest` are both arrays of the same type and size not exists(ArrayType srctype, ArrayType desttype | dest.getType().getUnderlyingType() = desttype and src.getType().getUnderlyingType() = srctype and @@ -32,48 +44,7 @@ predicate sourceSized(FunctionCall fc) desttype.getArraySize() = srctype.getArraySize())) } -class VulnerableArgument extends PointsToExpr -{ - VulnerableArgument() { sourceSized(this.getParent()) } - override predicate interesting() { sourceSized(this.getParent()) } -} - -predicate taintingFunction(Function f, int buf) -{ - (f.hasQualifiedName("read") and buf = 1) or - (f.hasQualifiedName("fgets") and buf = 0) or - (f.hasQualifiedName("fread") and buf = 0) -} - -// Taint `argv[i]`, for all i, but also `*argv`, etc. -predicate commandLineArg(Expr e) -{ - exists(Function f, Parameter argv, VariableAccess access | - f.hasQualifiedName("main") and f.getParameter(1) = argv and - argv.getAnAccess() = access and access.isRValue() and - pointer(access, e)) -} - -predicate tainted(Expr e) -{ - exists(FunctionCall fc, int arg | - taintingFunction(fc.getTarget(), arg) and - e = fc.getArgument(arg)) - or - e.(FunctionCall).getTarget().hasQualifiedName("getenv") - or - commandLineArg(e) -} - -class TaintedArgument extends PointsToExpr -{ - TaintedArgument() { tainted(this) } - override predicate interesting() { tainted(this) } -} - -from FunctionCall fc, VulnerableArgument vuln, TaintedArgument tainted -where sourceSized(fc) - and fc.getArgument(1) = vuln - and vuln.pointsTo() = tainted.pointsTo() - and vuln.confidence() > 0.01 +from FunctionCall fc, Expr vuln, Expr taintSource +where sourceSized(fc, vuln) + and tainted(taintSource, vuln) select fc, "To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size." diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.c b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.c new file mode 100644 index 00000000000..e68abdc2203 --- /dev/null +++ b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.c @@ -0,0 +1,7 @@ +void f() +{ + for (signed char i = 0; i < 100; i--) + { + // code ... + } +} \ No newline at end of file diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp new file mode 100644 index 00000000000..fd28b37d878 --- /dev/null +++ b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp @@ -0,0 +1,27 @@ + + + + +

    A for-loop iteration expression goes backwards with respect of the initialization statement and condition expression.

    +

    This warning indicates that a for-loop might not function as intended.

    +
    + + +

    To fix this issue, check that the loop condition is correct and change the iteration expression to match.

    +
    + + +

    In the following example, the initialization statement (i = 0) and the condition expression (i < 100) indicate that the intended iteration expression should have been incrementing, but instead a postfix decrement operator is used (i--).

    + + +

    To fix this issue, change the iteration expression to match the direction of the initialization statement and the condition expression: i++.

    +
    + + +
  • warning C6293: Ill-defined for-loop: counts down from minimum +
  • +
    + +
    diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.ql b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.ql new file mode 100644 index 00000000000..a12cbe08b03 --- /dev/null +++ b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.ql @@ -0,0 +1,116 @@ +/** + * @name Inconsistent direction of for loop + * @description A for-loop iteration expression goes backward with respect of the initialization statement and condition expression. + * @kind problem + * @problem.severity error + * @precision high + * @id cpp/inconsistent-loop-direction + * @tags correctness + * external/cwe/cwe-835 + * external/microsoft/6293 + * @msrc.severity important + */ +import cpp +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis +import semmle.code.cpp.dataflow.DataFlow + +predicate illDefinedDecrForStmt( ForStmt forstmt, Variable v, Expr initialCondition, Expr terminalCondition ) { + v.getAnAssignedValue() = initialCondition + and + exists( + RelationalOperation rel | + rel = forstmt.getCondition() | + terminalCondition = rel.getGreaterOperand() + and v.getAnAccess() = rel.getLesserOperand() + and + DataFlow::localFlowStep(DataFlow::exprNode(initialCondition), DataFlow::exprNode(rel.getLesserOperand())) + ) + and + exists( + DecrementOperation dec | + dec = forstmt.getUpdate().(DecrementOperation) + and dec.getAnOperand() = v.getAnAccess() + ) + and + ( + ( upperBound(initialCondition) < lowerBound(terminalCondition) ) + or + ( forstmt.conditionAlwaysFalse() or forstmt.conditionAlwaysTrue() ) + ) +} + +predicate illDefinedIncrForStmt( ForStmt forstmt, Variable v, Expr initialCondition, Expr terminalCondition ) { + v.getAnAssignedValue() = initialCondition + and + exists( + RelationalOperation rel | + rel = forstmt.getCondition() | + terminalCondition = rel.getLesserOperand() + and v.getAnAccess() = rel.getGreaterOperand() + and + DataFlow::localFlowStep(DataFlow::exprNode(initialCondition), DataFlow::exprNode(rel.getGreaterOperand())) + ) + and + exists( IncrementOperation incr | + incr = forstmt.getUpdate().(IncrementOperation) + and + incr.getAnOperand() = v.getAnAccess() + ) + and + ( + ( upperBound(terminalCondition) < lowerBound(initialCondition)) + or + ( forstmt.conditionAlwaysFalse() or forstmt.conditionAlwaysTrue()) + ) +} + +predicate illDefinedForStmtWrongDirection( ForStmt forstmt, Variable v, Expr initialCondition, Expr terminalCondition + , boolean isIncr ) { + ( illDefinedDecrForStmt( forstmt, v, initialCondition, terminalCondition) and isIncr = false ) + or + ( illDefinedIncrForStmt( forstmt, v, initialCondition, terminalCondition) and isIncr = true) +} + +bindingset[b] +private string forLoopdirection(boolean b){ + if( b = true ) then result = "upward" + else result = "downward" +} + +bindingset[b] +private string forLoopTerminalConditionRelationship(boolean b){ + if( b = true ) then result = "lower" + else result = "higher" +} + + predicate illDefinedForStmt( ForStmt for, string message ) { + exists( + boolean isIncr, + Variable v, + Expr initialCondition, + Expr terminalCondition | + illDefinedForStmtWrongDirection(for, v, initialCondition, terminalCondition, isIncr) + and + if( for.conditionAlwaysFalse() ) then + ( + message = "Ill-defined for-loop: a loop using variable \"" + v + "\" counts " + + forLoopdirection(isIncr) + " from a value ("+ initialCondition +"), but the terminal condition is always false." + ) + else if + ( + for.conditionAlwaysTrue() ) then ( + message = "Ill-defined for-loop: a loop using variable \"" + v + "\" counts " + + forLoopdirection(isIncr) + " from a value ("+ initialCondition +"), but the terminal condition is always true." + ) + else + ( + message = "Ill-defined for-loop: a loop using variable \"" + v + "\" counts " + + forLoopdirection(isIncr) + " from a value ("+ initialCondition +"), but the terminal condition is " + + forLoopTerminalConditionRelationship(isIncr) + " (" + terminalCondition + ")." + ) + ) +} + +from ForStmt forstmt, string message + where illDefinedForStmt(forstmt, message) +select forstmt, message diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp new file mode 100644 index 00000000000..62d2af1d17a --- /dev/null +++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp @@ -0,0 +1,11 @@ +STARTUPINFOW si; +PROCESS_INFORMATION pi; + +// ... + +CreateProcessW( // BUG + NULL, // lpApplicationName + (LPWSTR)L"C:\\Program Files\\MyApp", // lpCommandLine + NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + +// ... \ No newline at end of file diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp new file mode 100644 index 00000000000..d4691284c3c --- /dev/null +++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp @@ -0,0 +1,46 @@ + + + + +

    This query indicates that there is a call to a function of the CreateProcess* family of functions, which introduces a security vulnerability.

    +
    + + +

    Do not use NULL for the lpApplicationName argument to the CreateProcess* function.

    +

    If you pass NULL for lpApplicationName, use quotation marks around the executable path in lpCommandLine.

    +
    + + +

    In the following example, CreateProcessW is called with a NULL value for lpApplicationName, +and the value for lpCommandLine that represent the application path is not quoted and has spaces in it.

    +

    If an attacker has access to the file system, they can elevate privileges by creating a file such as C:\Program.exe that will be executed instead of the intended application.

    + + +

    To fix this issue, specify a valid string for lpApplicationName, or quote the path for lpCommandLine. For example:

    +

    (LPWSTR)L"\"C:\\Program Files\\MyApp\"", // lpCommandLine

    +
    + + +
  • + CreateProcessA function (Microsoft documentation). +
  • +
  • + CreateProcessW function (Microsoft documentation). +
  • +
  • + CreateProcessAsUserA function (Microsoft documentation). +
  • +
  • + CreateProcessAsUserW function (Microsoft documentation). +
  • +
  • + CreateProcessWithLogonW function (Microsoft documentation). +
  • +
  • + CreateProcessWithTokenW function (Microsoft documentation). +
  • +
    + +
    diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql new file mode 100644 index 00000000000..1518c6c1f0f --- /dev/null +++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql @@ -0,0 +1,130 @@ +/** + * @name NULL application name with an unquoted path in call to CreateProcess + * @description Calling a function of the CreateProcess* family of functions, where the path contains spaces, introduces a security vulnerability. + * @id cpp/unsafe-create-process-call + * @kind problem + * @problem.severity error + * @precision medium + * @msrc.severity important + * @tags security + * external/cwe/cwe-428 + * external/microsoft/C6277 + */ + +import cpp +import semmle.code.cpp.dataflow.DataFlow +import semmle.code.cpp.dataflow.DataFlow2 + +predicate isCreateProcessFunction(FunctionCall call, int applicationNameIndex, int commandLineIndex) { + ( + call.getTarget().hasGlobalName("CreateProcessA") + and applicationNameIndex = 0 + and commandLineIndex = 1 + ) or ( + call.getTarget().hasGlobalName("CreateProcessW") + and applicationNameIndex = 0 + and commandLineIndex = 1 + ) or ( + call.getTarget().hasGlobalName("CreateProcessWithTokenW") + and applicationNameIndex = 2 + and commandLineIndex = 3 + ) or ( + call.getTarget().hasGlobalName("CreateProcessWithLogonW") + and applicationNameIndex = 4 + and commandLineIndex = 5 + ) or ( + call.getTarget().hasGlobalName("CreateProcessAsUserA") + and applicationNameIndex = 1 + and commandLineIndex = 2 + ) or ( + call.getTarget().hasGlobalName("CreateProcessAsUserW") + and applicationNameIndex = 1 + and commandLineIndex = 2 + ) +} +/** + * A function call to CreateProcess (either wide-char or single byte string versions) + */ +class CreateProcessFunctionCall extends FunctionCall { + CreateProcessFunctionCall() { + isCreateProcessFunction( this, _, _) + } + + int getApplicationNameArgumentId() { + isCreateProcessFunction( this, result, _) + } + + int getCommandLineArgumentId() { + isCreateProcessFunction( this, _, result) + } +} + +/** + * Dataflow that detects a call to CreateProcess with a NULL value for lpApplicationName argument + */ +class NullAppNameCreateProcessFunctionConfiguration extends DataFlow::Configuration { + NullAppNameCreateProcessFunctionConfiguration() { + this = "NullAppNameCreateProcessFunctionConfiguration" + } + + override predicate isSource(DataFlow::Node source) { + nullValue(source.asExpr()) + } + + override predicate isSink(DataFlow::Node sink) { + exists( + CreateProcessFunctionCall call, Expr val | + val = sink.asExpr() | + val = call.getArgument(call.getApplicationNameArgumentId()) + ) + } +} + +/** + * Dataflow that detects a call to CreateProcess with an unquoted commandLine argument + */ +class QuotedCommandInCreateProcessFunctionConfiguration extends DataFlow2::Configuration { + QuotedCommandInCreateProcessFunctionConfiguration() { + this = "QuotedCommandInCreateProcessFunctionConfiguration" + } + + override predicate isSource(DataFlow2::Node source) { + exists( string s | + s = source.asExpr().getValue().toString() + and + not isQuotedOrNoSpaceApplicationNameOnCmd(s) + ) + } + + override predicate isSink(DataFlow2::Node sink) { + exists( + CreateProcessFunctionCall call, Expr val | + val = sink.asExpr() | + val = call.getArgument(call.getCommandLineArgumentId()) + ) + } +} + +bindingset[s] +predicate isQuotedOrNoSpaceApplicationNameOnCmd(string s){ + s.regexpMatch("\"([^\"])*\"(\\s|.)*") // The first element (path) is quoted + or + s.regexpMatch("[^\\s]+") // There are no spaces in the string +} + +from CreateProcessFunctionCall call, string msg1, string msg2 +where + exists( Expr source, Expr appName, + NullAppNameCreateProcessFunctionConfiguration nullAppConfig | + appName = call.getArgument(call.getApplicationNameArgumentId()) + and nullAppConfig.hasFlow(DataFlow2::exprNode(source), DataFlow2::exprNode(appName)) + and msg1 = call.toString() + " with lpApplicationName == NULL (" + appName + ")" + ) + and + exists( Expr source, Expr cmd, + QuotedCommandInCreateProcessFunctionConfiguration quotedConfig | + cmd = call.getArgument(call.getCommandLineArgumentId()) + and quotedConfig.hasFlow(DataFlow2::exprNode(source), DataFlow2::exprNode(cmd)) + and msg2 = " and with an unquoted lpCommandLine (" + cmd + ") introduces a security vulnerability if the path contains spaces." + ) +select call, msg1 + " " + msg2 diff --git a/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql b/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql index b4eabe1f175..1f1ddef6a9c 100644 --- a/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql +++ b/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql @@ -107,15 +107,17 @@ predicate defUndef(File f, string macroName) { } /** - * Header file `hf` looks like it contains an x-macro called - * `macroName`. That is, a macro that is used to interpret the + * Header file `hf` looks like it contains an x-macro. + * That is, a macro that is used to interpret the * data in `hf`, usually defined just before including that file * and undefined immediately afterwards. */ -predicate hasXMacro(HeaderFile hf, string macroName) { - usesMacro(hf, macroName) and - forex(Include i | i.getIncludedFile() = hf | - defUndef(i.getFile(), macroName) +predicate hasXMacro(HeaderFile hf) { + exists(string macroName | + usesMacro(hf, macroName) and + forex(File f | f.getAnIncludedFile() = hf | + defUndef(f, macroName) + ) ) } @@ -135,7 +137,7 @@ where not hf instanceof IncludeGuardedHeader exists(UsingEntry ue | ue.getFile() = hf) ) // Exclude files which look like they contain 'x-macros' - and not hasXMacro(hf, _) + and not hasXMacro(hf) // Exclude files which are always #imported. and not forex(Include i | i.getIncludedFile() = hf | i instanceof Import) // Exclude files which are only included once. diff --git a/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql b/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql index 853285050a4..6ad499a8337 100644 --- a/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql +++ b/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql @@ -159,6 +159,17 @@ predicate unreleasedResource(Resource r, Expr acquire, File f, int acquireLine) ) and f = acquire.getFile() and acquireLine = acquire.getLocation().getStartLine() + + // check that any destructor for this class has a block; if it doesn't, + // we must be missing information. + and forall(Class c, Destructor d | + r.getDeclaringType().isConstructedFrom*(c) and + d = c.getAMember() and + not d.isCompilerGenerated() and + not d.isDefaulted() and + not d.isDeleted() | + exists(d.getBlock()) + ) } predicate freedInSameMethod(Resource r, Expr acquire) { diff --git a/cpp/ql/src/jsf/4.13 Functions/AV Rule 114.ql b/cpp/ql/src/jsf/4.13 Functions/AV Rule 114.ql index 61c3413d35d..9a9760e3d5f 100644 --- a/cpp/ql/src/jsf/4.13 Functions/AV Rule 114.ql +++ b/cpp/ql/src/jsf/4.13 Functions/AV Rule 114.ql @@ -19,7 +19,11 @@ import cpp predicate functionsMissingReturnStmt(Function f, ControlFlowNode blame) { f.fromSource() and - not f.getType().getUnderlyingType().getUnspecifiedType() instanceof VoidType and + exists(Type returnType | + returnType = f.getType().getUnderlyingType().getUnspecifiedType() and + not returnType instanceof VoidType and + not returnType instanceof TemplateParameter + ) and exists(ReturnStmt s | f.getAPredecessor() = s | blame = s.getAPredecessor())} /* If a function has a value-carrying return statement, but the extractor hit a snag @@ -32,13 +36,11 @@ predicate functionImperfectlyExtracted(Function f) { exists(ErrorExpr ee | ee.getEnclosingFunction() = f) } -from Stmt stmt, string msg +from Stmt stmt, string msg, Function f, ControlFlowNode blame where - exists(Function f, ControlFlowNode blame | - functionsMissingReturnStmt(f, blame) and - reachable(blame) and - not functionImperfectlyExtracted(f) and - (blame = stmt or blame.(Expr).getEnclosingStmt() = stmt) and - msg = "Function " + f.getName() + " should return a value of type " + f.getType().getName() + " but does not return a value here" - ) + functionsMissingReturnStmt(f, blame) and + reachable(blame) and + not functionImperfectlyExtracted(f) and + (blame = stmt or blame.(Expr).getEnclosingStmt() = stmt) and + msg = "Function " + f.getName() + " should return a value of type " + f.getType().getName() + " but does not return a value here" select stmt, msg 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 57eade7058e..9d28e37cc8a 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 57eade7058e..9d28e37cc8a 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 57eade7058e..9d28e37cc8a 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 57eade7058e..9d28e37cc8a 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 fc1d6e5053e..02c9919723b 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll @@ -118,7 +118,7 @@ private module ImplCommon { node1.(ArgumentNode).argumentOf(call, i1) and node2.getPreUpdateNode().(ArgumentNode).argumentOf(call, i2) and compatibleTypes(node1.getTypeBound(), f.getType()) and - compatibleTypes(node2.getTypeBound(), f.getDeclaringType()) + compatibleTypes(node2.getTypeBound(), f.getContainerType()) ) } @@ -149,7 +149,7 @@ private module ImplCommon { setterReturn(p, f) and arg.argumentOf(node2.asExpr(), _) and compatibleTypes(node1.getTypeBound(), f.getType()) and - compatibleTypes(node2.getTypeBound(), f.getDeclaringType()) + compatibleTypes(node2.getTypeBound(), f.getContainerType()) ) } @@ -174,7 +174,7 @@ private module ImplCommon { viableParamArg(p, arg) and getter(p, f) and arg.argumentOf(node2.asExpr(), _) and - compatibleTypes(node1.getTypeBound(), f.getDeclaringType()) and + compatibleTypes(node1.getTypeBound(), f.getContainerType()) and compatibleTypes(node2.getTypeBound(), f.getType()) ) } diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll index a3d9c966428..ce76a5b7a17 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll @@ -69,18 +69,9 @@ class Content extends TContent { path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0 } /** Gets the type of the object containing this content. */ - abstract RefType getDeclaringType(); + abstract RefType getContainerType(); /** Gets the type of this content. */ abstract Type getType(); - /** - * Holds if this content may contain an object of the same type as the one - * that contains this content, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ - predicate isSelfRef() { none() } } private class FieldContent extends Content, TFieldContent { Field f; @@ -90,17 +81,17 @@ private class FieldContent extends Content, TFieldContent { override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { f.getLocation().hasLocationInfo(path, sl, sc, el, ec) } - override RefType getDeclaringType() { result = f.getDeclaringType() } + override RefType getContainerType() { result = f.getDeclaringType() } override Type getType() { result = f.getType() } } private class CollectionContent extends Content, TCollectionContent { override string toString() { result = "collection" } - override RefType getDeclaringType() { none() } + override RefType getContainerType() { none() } override Type getType() { none() } } private class ArrayContent extends Content, TArrayContent { override string toString() { result = "array" } - override RefType getDeclaringType() { none() } + override RefType getContainerType() { none() } override Type getType() { none() } } @@ -132,6 +123,11 @@ RefType getErasedRepr(Type t) { result instanceof VoidType // stub implementation } +/** Gets a string representation of a type returned by `getErasedRepr`. */ +string ppReprType(Type t) { + result = t.toString() +} + /** * Holds if `t1` and `t2` are compatible, that is, whether data can flow from * a node of type `t1` to a node of type `t2`. diff --git a/cpp/ql/src/semmle/code/cpp/headers/MultipleInclusion.qll b/cpp/ql/src/semmle/code/cpp/headers/MultipleInclusion.qll index d259d1ef805..299998647c1 100644 --- a/cpp/ql/src/semmle/code/cpp/headers/MultipleInclusion.qll +++ b/cpp/ql/src/semmle/code/cpp/headers/MultipleInclusion.qll @@ -114,8 +114,11 @@ pragma[noopt] predicate correctIncludeGuard(HeaderFile hf, PreprocessorDirective */ predicate startsWithIfndef(HeaderFile hf, PreprocessorDirective ifndef, string macroName) { ifndefDirective(ifndef, macroName) and - ifndef.getFile() = hf and - ifndef.getLocation().getStartLine() = min(int l | includeGuardRelevantLine(hf, l)) + exists(Location loc | + loc = ifndef.getLocation() and + loc.getFile() = hf and + loc.getStartLine() = min(int l | includeGuardRelevantLine(hf, l)) + ) } private predicate endifLocation(PreprocessorEndif endif, File f, int line) { diff --git a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected new file mode 100644 index 00000000000..5c239ca92ad --- /dev/null +++ b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected @@ -0,0 +1,3 @@ +| test.cpp:2:2:2:12 | goto ... | This statement makes $@ unreachable. | test.cpp:3:2:3:5 | ExprStmt | ExprStmt | +| test.cpp:9:3:9:8 | break; | This statement makes $@ unreachable. | test.cpp:10:3:10:6 | ExprStmt | ExprStmt | +| test.cpp:37:3:37:8 | break; | This statement makes $@ unreachable. | test.cpp:38:3:38:11 | return ... | return ... | diff --git a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.qlref b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.qlref new file mode 100644 index 00000000000..0786047da5f --- /dev/null +++ b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.qlref @@ -0,0 +1 @@ +Critical/DeadCodeGoto.ql diff --git a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp new file mode 100644 index 00000000000..47ac10b7751 --- /dev/null +++ b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp @@ -0,0 +1,83 @@ +int test1(int x) { + goto label; // BAD + x++; + label: return x; +} + +int test2(int x) { + do { + break; // BAD + x++; + } while(false); + return x; +} + +int test3(int x) { + goto label; // GOOD + label: x++; + return x; +} + +int test4(int x) { + goto label; // GOOD + do { + label: x++; + } while(false); + return x; +} + +int test5(int x, int y) { + switch(y) { + case 0: + break; // GOOD + case 1: + goto label; // GOOD + break; + case 2: + break; // BAD + return x; + case 3: + return x; + break; // GOOD + case 4: + goto label; // GOOD + case 5: + goto label;; // GOOD + default: + x++; + } + label: + return x; +} + +void test6(int x, int cond) { + if (cond) { + x++; + } else goto end; // GOOD + x++; + end: +} + +void test7(int x, int cond) { + if (cond) + { + goto target; + } + goto somewhere_else; // GOOD + while (x < 10) // not dead code + { + target: + x++; + } + somewhere_else: + switch (1) + { + goto end; + while (x < 10) // not dead code + { + case 1: + x++; + } break; + } + end: +} diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.c b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.c new file mode 100644 index 00000000000..d66e027bdc1 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.c @@ -0,0 +1,89 @@ +void Signed() +{ + signed char i; + + for (i = 0; i < 100; i--) //BUG + { + } + + for (i = 0; i < 100; i++) + { + } + + for (i = 100; i >= 0; i++) //BUG + { + } + + for (i = 100; i >= 0; i--) + { + } + +} + +void Unsigned() +{ + unsigned long i; + + for (i = 0; i < 100; i--) //BUG + { + } + + for (i = 0; i < 100; i++) + { + } + + for (i = 100; i >= 0; i++) //BUG + { + } + + for (i = 100; i >= 0; i--) + { + } +} + +void InitializationOutsideLoop() +{ + signed char i = 0; + + for (; i < 100; i--) //BUG + { + } + + i = 0; + for (; i < 100; i++) + { + } + + i = 100; + for (; i >= 0; i++) //BUG + { + } + + i = 100; + for (; i >= 0; i--) + { + } +} + +void NegativeTestCase() +{ + int i; + for (i = 0; (200 - i) < 100; i--) + { + // code ... + } +} + +void NegativeTestCaseNested() +{ + int k; + int i; + + for (k = 200; k < 300; k++) + { + for (i = 0; (k - i) < 100; i--) + { + // code ... + } + } +} diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.cpp b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.cpp new file mode 100644 index 00000000000..e6e743382ed --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.cpp @@ -0,0 +1,180 @@ +void Signed() +{ + signed char i; + + for (i = 0; i < 100; i--) //BUG + { + } + + for (i = 0; i < 100; i++) + { + } + + for (i = 100; i >= 0; i++) //BUG + { + } + + for (i = 100; i >= 0; i--) + { + } + +} + +void Unsigned() +{ + unsigned long i; + + for (i = 0; i < 100; i--) //BUG + { + } + + for (i = 0; i < 100; i++) + { + } + + for (i = 100; i >= 0; i++) //BUG + { + } + + for (i = 100; i >= 0; i--) + { + } +} + +void DeclarationInLoop() +{ + for (signed char i = 0; i < 100; --i) //BUG + { + } + + for (signed char i = 0; i < 100; ++i) + { + } + + for (unsigned char i = 100; i >= 0; ++i) //BUG + { + } + + for (unsigned char i = 100; i >= 0; --i) + { + } +} + +void SignedWithVariables() +{ + signed char i; + signed char min = 0; + signed char max = 100; + + for (i = min; i < max; i--) //BUG + { + } + + for (i = min; i < max; i++) + { + } + + for (i = max; i >= min; i++) //BUG + { + } + + for (i = max; i >= min; i--) + { + } + +} + +void InitializationOutsideLoop() +{ + signed char i = 0; + + for (; i < 100; --i) //BUG + { + } + + i = 0; + for (; i < 100; ++i) + { + } + + i = 100; + for (; i >= 0; ++i) //BUG + { + } + + i = 100; + for (; i >= 0; --i) + { + } +} + + +void InvalidCondition() +{ + signed char i; + signed char min = 0; + signed char max = 100; + + for (i = max; i < min; i--) //BUG + { + } + + for (i = min; i > max; i++) //BUG + { + } +} + +void InvalidConditionUnsignedCornerCase() +{ + unsigned char i; + unsigned char min = 0; + unsigned char max = 100; + + for (i = 100; i < 0; i--) //BUG + { + } + + // Limitation. + // Currently odasa will not detect this for-loop condition as always true + // The rule will still detect the mismatch iterator, but the error message may change in the future. + for (i = 200; i >= 0; i++) //BUG + { + } +} + +void NegativeTestCase() +{ + for (int i = 0; (100 - i) < 200; i--) + { + // code ... + } +} + +void NegativeTestCaseNested() +{ + for (int k = 200; k < 300; k++) + { + for (int i = 0; (k - i) < 100; i--) + { + // code ... + } + } +} + +////////////////////////////////////// +// Query limitation: +// +// The following test cases are bugs, +// but will not be found due to the itearion expression +// not being a prefix or postfix increment/decrement +// +void FalseNegativeTestCases() +{ + for (int i = 0; i < 10; i = i - 1) {} + // For comparison + for (int i = 0; i < 10; i-- ) {} // BUG + + for (int i = 100; i > 0; i += 2) {} + // For comparison + for (int i = 100; i > 0; i ++ ) {} // BUG +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.expected b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.expected new file mode 100644 index 00000000000..f5ff346271f --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.expected @@ -0,0 +1,22 @@ +| inconsistentLoopDirection.c:5:5:7:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). | +| inconsistentLoopDirection.c:13:5:15:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). | +| inconsistentLoopDirection.c:27:5:29:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). | +| inconsistentLoopDirection.c:35:5:37:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). | +| inconsistentLoopDirection.c:48:5:50:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). | +| inconsistentLoopDirection.c:58:5:60:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). | +| inconsistentLoopDirection.cpp:5:5:7:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). | +| inconsistentLoopDirection.cpp:13:5:15:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). | +| inconsistentLoopDirection.cpp:27:5:29:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). | +| inconsistentLoopDirection.cpp:35:5:37:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). | +| inconsistentLoopDirection.cpp:46:5:48:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). | +| inconsistentLoopDirection.cpp:54:5:56:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). | +| inconsistentLoopDirection.cpp:69:5:71:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (min), but the terminal condition is higher (max). | +| inconsistentLoopDirection.cpp:77:5:79:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (max), but the terminal condition is lower (min). | +| inconsistentLoopDirection.cpp:91:5:93:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). | +| inconsistentLoopDirection.cpp:101:5:103:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). | +| inconsistentLoopDirection.cpp:118:5:120:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (max), but the terminal condition is always false. | +| inconsistentLoopDirection.cpp:122:5:124:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (min), but the terminal condition is always false. | +| inconsistentLoopDirection.cpp:133:5:135:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (100), but the terminal condition is always false. | +| inconsistentLoopDirection.cpp:140:5:142:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (200), but the terminal condition is lower (0). | +| inconsistentLoopDirection.cpp:175:5:175:36 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (10). | +| inconsistentLoopDirection.cpp:179:5:179:38 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.qlref b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.qlref new file mode 100644 index 00000000000..af5f0a899cb --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.qlref @@ -0,0 +1 @@ +Likely Bugs/Likely Typos/inconsistentLoopDirection.ql \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp new file mode 100644 index 00000000000..6f3f76001b5 --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp @@ -0,0 +1,430 @@ +// semmle-extractor-options: --microsoft +#define NULL 0 +#define FALSE 0 +#define LOGON_WITH_PROFILE 0x00000001 + +int +CreateProcessA( + const char* lpApplicationName, + char* lpCommandLine, + void* lpProcessAttributes, + void* lpThreadAttributes, + int bInheritHandles, + unsigned long dwCreationFlags, + void* lpEnvironment, + const char* lpCurrentDirectory, + void* lpStartupInfo, + void* lpProcessInformation +); + +int +CreateProcessW( + const wchar_t* lpApplicationName, + wchar_t* lpCommandLine, + void* lpProcessAttributes, + void* lpThreadAttributes, + int bInheritHandles, + unsigned long dwCreationFlags, + void* lpEnvironment, + const wchar_t* lpCurrentDirectory, + void* lpStartupInfo, + void* lpProcessInformation +); + +#define CreateProcess CreateProcessW + +int +CreateProcessWithTokenW( + void* hToken, + unsigned long dwLogonFlags, + const wchar_t* lpApplicationName, + wchar_t* lpCommandLine, + unsigned long dwCreationFlags, + void* lpEnvironment, + const wchar_t* lpCurrentDirectory, + void* lpStartupInfo, + void* lpProcessInformation +); + +int +CreateProcessWithLogonW( + const wchar_t* lpUsername, + const wchar_t* lpDomain, + const wchar_t* lpPassword, + unsigned long dwLogonFlags, + const wchar_t* lpApplicationName, + wchar_t* lpCommandLine, + unsigned long dwCreationFlags, + void* lpEnvironment, + const wchar_t* lpCurrentDirectory, + void* lpStartupInfo, + void* lpProcessInformation +); + +int +CreateProcessAsUserA( + void* hToken, + const char* lpApplicationName, + char* lpCommandLine, + void* lpProcessAttributes, + void* lpThreadAttributes, + int bInheritHandles, + unsigned long dwCreationFlags, + void* lpEnvironment, + const char* lpCurrentDirectory, + void* lpStartupInfo, + void* lpProcessInformation +); + +int +CreateProcessAsUserW( + void* hToken, + const wchar_t* lpApplicationName, + wchar_t* lpCommandLine, + void* lpProcessAttributes, + void* lpThreadAttributes, + int bInheritHandles, + unsigned long dwCreationFlags, + void* lpEnvironment, + const wchar_t* lpCurrentDirectory, + void* lpStartupInfo, + void* lpProcessInformation +); + +#define CreateProcessAsUser CreateProcessAsUserW + +void positiveTestCases() +{ + const wchar_t* lpCommandLine = (const wchar_t*)L"C:\\Program Files\\MyApp"; + void* h = 0; + wchar_t* lpApplicationName = NULL; + + // CreatePorcessA + CreateProcessA( //BUG + NULL, + (char*)"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreatePorcessW + CreateProcessW( //BUG + NULL, + (wchar_t*)L"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreatePorcess + CreateProcess( //BUG + NULL, + (wchar_t*)L"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // lpCommandLine as hardcoded variable + CreateProcess( //BUG + NULL, + (wchar_t*)lpCommandLine, + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreateProcessWithTokenW + CreateProcessWithTokenW( //BUG + h, + LOGON_WITH_PROFILE, + NULL, + (wchar_t*)L"C:\\Program Files\\MyApp", + 0, NULL, NULL, NULL, NULL); + + // CreateProcessWithLogonW + CreateProcessWithLogonW( //BUG + (const wchar_t*)L"UserName", + (const wchar_t*)L"CONTOSO", + (const wchar_t*)L"", + LOGON_WITH_PROFILE, + NULL, + (wchar_t*)L"C:\\Program Files\\MyApp", + 0, NULL, NULL, NULL, NULL); + + // CreateProcessAsUserA + CreateProcessAsUserA( //BUG + h, + NULL, + (char*)"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreateProcessAsUserW + CreateProcessAsUserW( //BUG + h, + NULL, + (wchar_t*)L"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreateProcessAsUser + CreateProcessAsUser( //BUG + h, + NULL, + (wchar_t*)L"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreatePorcess with a hardcoded variable for application Name (NULL) + // Variation: tab instead of space + CreateProcess( //BUG + lpApplicationName, + (wchar_t*)L"C:\\Program\tFiles\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); +} + +void PositiveTestCasesWithCmdLineParameter(wchar_t* lpCommandLine) +{ + // lpCommandLine as variable + CreateProcess( //BUG - Depends on the caller + NULL, + lpCommandLine, + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); +} + +void PositiveTestCasesWithCmdLineParameter_caller() +{ + PositiveTestCasesWithCmdLineParameter((wchar_t*)L"C:\\Program Files\\MyApp"); +} + +// NOTE: This function will not be flagged as having a bug by this rule. +// but as it is, the function can still be misused +void FalseNegativeTestCasesWithCmdLineParameter(wchar_t* lpCommandLine) +{ + // lpCommandLine as variable + CreateProcess( //Depends on the caller, this time the caller will quote + NULL, + lpCommandLine, + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); +} + +void FalseNegativeTestCasesWithCmdLineParameter_caller() +{ + // No bug - escaped command line + // But compare with "PositiveTestCasesWithCmdLineParameter" + FalseNegativeTestCasesWithCmdLineParameter((wchar_t*)L"\"C:\\Program Files\\MyApp\""); +} + +void PositiveTestCasesWithAppNameParameter(wchar_t* lpApplicationName) +{ + void* h = 0; + + CreateProcessWithTokenW( //BUG - Depends on the caller. In this case the caller sends NULL + h, + LOGON_WITH_PROFILE, + lpApplicationName, + (wchar_t*)L"C:\\Program Files\\MyApp", + 0, NULL, NULL, NULL, NULL); +} + +void PositiveTestCasesWithAppNameParameter_caller() +{ + PositiveTestCasesWithAppNameParameter(NULL); +} + +// NOTE: This function will not be flagged as having a bug by this rule. +// but as it is, the function can still be misused +void FalseNegativeTestCasesWithAppNameParameter(wchar_t* lpApplicationName) +{ + void* h = 0; + + CreateProcessWithTokenW( // Depends on the caller. In this case the caller sends an ApplicatioName + h, + LOGON_WITH_PROFILE, + lpApplicationName, + (wchar_t*)L"C:\\Program Files\\MyApp", + 0, NULL, NULL, NULL, NULL); +} + +void FalseNegativeTestCasesWithAppNameParameter_caller() +{ + // No bug - escaped command line + // But compare with "PositiveTestCasesWithAppNameParameter" + FalseNegativeTestCasesWithAppNameParameter((wchar_t*)L"MyApp.exe"); +} + +int MayReturnFalse() +{ + // return ((rand() % 2) == 0); + return true; +} + +void TestCaseProbablyBug() +{ + const wchar_t* lpApplicationName = NULL; + + if (!MayReturnFalse()) + { + lpApplicationName = (const wchar_t*)L"app.exe"; + } + + CreateProcessWithLogonW( // BUG (Probably - depends on a condition that may be false) + (const wchar_t*)L"UserName", + (const wchar_t*)L"CONTOSO", + (const wchar_t*)L"", + LOGON_WITH_PROFILE, + (wchar_t*)lpApplicationName, + (wchar_t*)L"C:\\Program Files\\MyApp", + 0, NULL, NULL, NULL, NULL); + + if (lpApplicationName) + { + delete[] lpApplicationName; + } +} + +void negativeTestCases_quotedCommandLine() +{ + const wchar_t* lpCommandLine = (const wchar_t*)L"\"C:\\Program Files\\MyApp\" with additional params"; + void* h = 0; + wchar_t* lpApplicationName = NULL; + + // CreatePorcessA + CreateProcessA( + NULL, + (char*)"\"C:\\Program Files\\MyApp\"", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreatePorcessW + CreateProcessW( + NULL, + (wchar_t*)L"\"C:\\Program Files\\MyApp\"", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreatePorcess + CreateProcess( + NULL, + (wchar_t*)L"\"C:\\Program Files\\MyApp\"", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // lpCommandLine as hardcoded variable + CreateProcess( + NULL, + (wchar_t*)lpCommandLine, + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreateProcessWithTokenW + CreateProcessWithTokenW( + h, + LOGON_WITH_PROFILE, + NULL, + (wchar_t*)L"\"C:\\Program Files\\MyApp\"", + 0, NULL, NULL, NULL, NULL); + + // CreateProcessWithLogonW + CreateProcessWithLogonW( + (const wchar_t*)L"UserName", + (const wchar_t*)L"CONTOSO", + (const wchar_t*)L"", + LOGON_WITH_PROFILE, + NULL, + (wchar_t*)L"\"C:\\Program Files\\MyApp\"", + 0, NULL, NULL, NULL, NULL); + + // CreateProcessAsUserA + CreateProcessAsUserA( + h, + NULL, + (char*)"\"C:\\Program Files\\MyApp\"", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreateProcessAsUserW + CreateProcessAsUserW( + h, + NULL, + (wchar_t*)L"\"C:\\Program Files\\MyApp\"", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreateProcessAsUser + CreateProcessAsUser( + h, + NULL, + (wchar_t*)L"\"C:\\Program Files\\MyApp\"", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreatePorcess with a hardcoded variable for application Name (NULL) + CreateProcess( + lpApplicationName, + (wchar_t*)L"\"C:\\Program Files\\MyApp\"", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // Null AppName, but lpComamndLine has no spaces/tabs + CreateProcessA( + NULL, + (char*)"C:\\MyFolder\\MyApp.exe", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + +} + +void negativeTestCases_AppNameSet() +{ + const wchar_t* lpCommandLine = (const wchar_t*)L"C:\\Program Files\\MyApp"; + void* h = 0; + const wchar_t* lpApplicationName = (const wchar_t*)L"MyApp.exe"; + + // CreatePorcessA + CreateProcessA( + (char*)"MyApp.exe", + (char*)"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreatePorcessW + CreateProcessW( + (wchar_t*)L"MyApp.exe", + (wchar_t*)L"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreatePorcess + CreateProcess( + (wchar_t*)L"MyApp.exe", + (wchar_t*)L"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // lpCommandLine as hardcoded variable + CreateProcess( + (wchar_t*)L"MyApp.exe", + (wchar_t*)lpCommandLine, + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreateProcessWithTokenW + CreateProcessWithTokenW( + h, + LOGON_WITH_PROFILE, + (wchar_t*)L"MyApp.exe", + (wchar_t*)L"C:\\Program Files\\MyApp", + 0, NULL, NULL, NULL, NULL); + + // CreateProcessWithLogonW + CreateProcessWithLogonW( + (const wchar_t*)L"UserName", + (const wchar_t*)L"CONTOSO", + (const wchar_t*)L"", + LOGON_WITH_PROFILE, + (wchar_t*)L"MyApp.exe", + (wchar_t*)L"C:\\Program Files\\MyApp", + 0, NULL, NULL, NULL, NULL); + + // CreateProcessAsUserA + CreateProcessAsUserA( + h, + (char*)"MyApp.exe", + (char*)"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreateProcessAsUserW + CreateProcessAsUserW( + h, + (wchar_t*)L"MyApp.exe", + (wchar_t*)L"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreateProcessAsUser + CreateProcessAsUser( + h, + (wchar_t*)L"MyApp.exe", + (wchar_t*)L"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + + // CreatePorcess with a hardcoded variable for application Name (NULL) + CreateProcess( + (wchar_t*)lpApplicationName, + (wchar_t*)L"C:\\Program Files\\MyApp", + NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); +} diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected new file mode 100644 index 00000000000..7295f1197b8 --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected @@ -0,0 +1,13 @@ +| UnsafeCreateProcessCall.cpp:103:5:103:18 | call to CreateProcessA | call to CreateProcessA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:109:5:109:18 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:115:5:115:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:121:5:121:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:127:5:127:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:135:5:135:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:145:5:145:24 | call to CreateProcessAsUserA | call to CreateProcessAsUserA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:152:5:152:24 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:159:5:159:23 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:167:5:167:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program\tFiles\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:176:5:176:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:209:5:209:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | +| UnsafeCreateProcessCall.cpp:258:5:258:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.qlref new file mode 100644 index 00000000000..f2012f0c678 --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.qlref @@ -0,0 +1 @@ +Security/CWE/CWE-428/UnsafeCreateProcessCall.ql \ No newline at end of file diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected index eed2f14a99f..048d63477d6 100644 --- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected @@ -17,3 +17,5 @@ | Variants.cpp:65:3:65:17 | ... = ... | Resource a is acquired by class MyClass6 but not released anywhere in this class. | | Variants.cpp:66:3:66:36 | ... = ... | Resource b is acquired by class MyClass6 but not released anywhere in this class. | | Variants.cpp:67:3:67:41 | ... = ... | Resource c is acquired by class MyClass6 but not released anywhere in this class. | +| Wrapped.cpp:46:3:46:22 | ... = ... | Resource ptr2 is acquired by class Wrapped2 but not released anywhere in this class. | +| Wrapped.cpp:59:3:59:22 | ... = ... | Resource ptr4 is acquired by class Wrapped2 but not released anywhere in this class. | diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp index c732e18d261..f5d2b02efaa 100644 --- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp @@ -66,3 +66,25 @@ public: n = new MyNumber(200); // GOOD: deleted in base class } }; + +template +class TemplateWithDestructor +{ +public: + TemplateWithDestructor(int len) { + ptr = new char[len]; // GOOD + } + + ~TemplateWithDestructor() + { + delete [] ptr; + } + +private: + char *ptr; +}; + +void test() { + TemplateWithDestructor *t_ptr = new TemplateWithDestructor(10); + //delete t_ptr; --- destructor never used +} diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/Wrapped.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/Wrapped.cpp index e682cf2da07..6b0eb79f41c 100644 --- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/Wrapped.cpp +++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/Wrapped.cpp @@ -37,3 +37,34 @@ public: private: char *ptr1, *ptr2, *ptr3; }; + +class Wrapped2 +{ +public: + Wrapped2(int len) { + ptr1 = new char[len]; // GOOD + ptr2 = new char[len]; // BAD: not released in destructor + + Init(len); + } + + ~Wrapped2() + { + Shutdown(); + } + + void Init(int len) + { + ptr3 = new char[len]; // GOOD + ptr4 = new char[len]; // BAD: not released in destructor + } + + void Shutdown() + { + delete [] ptr1; + delete [] ptr3; + } + +private: + char *ptr1, *ptr2, *ptr3, *ptr4; +}; diff --git a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected index 2bf6f9673a3..e55d3bc27ea 100644 --- a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected +++ b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected @@ -3,3 +3,5 @@ | test.c:39:9:39:14 | ExprStmt | Function f6 should return a value of type int but does not return a value here | | test.cpp:16:1:18:1 | { ... } | Function g2 should return a value of type MyValue but does not return a value here | | test.cpp:48:2:48:26 | if (...) ... | Function g7 should return a value of type MyValue but does not return a value here | +| test.cpp:74:1:76:1 | { ... } | Function g10 should return a value of type second but does not return a value here | +| test.cpp:86:1:88:1 | { ... } | Function g12 should return a value of type second but does not return a value here | diff --git a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp index 4605fee5a63..a2da6ed3cbf 100644 --- a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp +++ b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp @@ -50,3 +50,45 @@ MyValue g7(bool c) DONOTHING // BAD [the alert here is unfortunately placed] } + +typedef void MYVOID; +MYVOID g8() +{ + // GOOD +} + +template +class TypePair +{ +public: + typedef T first; + typedef U second; +}; + +TypePair::first g9() +{ + // GOOD (the return type amounts to void) +} + +TypePair::second g10() +{ + // BAD (the return type amounts to int) +} + +template +typename TypePair::first g11() +{ + // GOOD (the return type amounts to void) +} + +template +typename TypePair::second g12() +{ + // BAD (the return type amounts to T / int) +} + +void instantiate() +{ + g11(); + g12(); +} diff --git a/csharp/config/tracer/linux/csharp-compiler-settings b/csharp/config/tracer/linux/csharp-compiler-settings index 96bfd151998..bf6ceede082 100644 --- a/csharp/config/tracer/linux/csharp-compiler-settings +++ b/csharp/config/tracer/linux/csharp-compiler-settings @@ -1,6 +1,8 @@ **/mcs.exe: **/csc.exe: invoke ${env.SEMMLE_PLATFORM_TOOLS}/csharp/Semmle.Extraction.CSharp.Driver + prepend --compiler + prepend "${compiler}" prepend --cil **/bin/mono*: **/dotnet: diff --git a/csharp/config/tracer/linux/extract-csharp.sh b/csharp/config/tracer/linux/extract-csharp.sh index 9118fa5533b..00bf654d792 100755 --- a/csharp/config/tracer/linux/extract-csharp.sh +++ b/csharp/config/tracer/linux/extract-csharp.sh @@ -9,7 +9,7 @@ do if [[ `basename -- "$i"` =~ csc.exe|mcs.exe|csc.dll ]] then echo extract-csharp.sh: exec $extractor --cil $@ - exec "$extractor" --cil $@ + exec "$extractor" --compiler $i --cil $@ fi done diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs index 7b05a7bca0d..df2d709dc5e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs @@ -46,10 +46,12 @@ namespace Semmle.Extraction.CSharp /// Arguments passed to csc. /// The Roslyn compilation. /// Extractor options. + /// The arguments passed to Roslyn. public void Initialize( CSharpCommandLineArguments commandLineArguments, CSharpCompilation compilationIn, - Options options) + Options options, + string[] roslynArgs) { compilation = compilationIn; @@ -58,7 +60,7 @@ namespace Semmle.Extraction.CSharp extractor = new Extraction.Extractor(false, GetOutputName(compilation, commandLineArguments), Logger); - LogDiagnostics(); + LogDiagnostics(roslynArgs); SetReferencePaths(); CompilationErrors += FilteredDiagnostics.Count(); @@ -113,7 +115,7 @@ namespace Semmle.Extraction.CSharp layout = new Layout(); extractor = new Extraction.Extractor(true, null, Logger); this.options = options; - LogDiagnostics(); + LogDiagnostics(null); SetReferencePaths(); } @@ -409,7 +411,8 @@ namespace Semmle.Extraction.CSharp /// Logs detailed information about this invocation, /// in the event that errors were detected. /// - public void LogDiagnostics() + /// The arguments passed to Roslyn. + public void LogDiagnostics(string[] roslynArgs) { Logger.Log(Severity.Info, " Current working directory: {0}", Directory.GetCurrentDirectory()); Logger.Log(Severity.Info, " Extractor: {0}", Environment.GetCommandLineArgs().First()); @@ -438,6 +441,9 @@ namespace Semmle.Extraction.CSharp } Logger.Log(Severity.Info, sb.ToString()); + if (roslynArgs != null) + Logger.Log(Severity.Info, $" Arguments to Roslyn: {string.Join(' ', roslynArgs)}"); + foreach (var error in FilteredDiagnostics) { Logger.Log(Severity.Error, " Compilation error: {0}", error); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs b/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs index ee1fca5bc2d..6f92b639e1a 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs @@ -1,5 +1,7 @@ +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.InteropServices; namespace Semmle.Extraction.CSharp @@ -53,24 +55,34 @@ namespace Semmle.Extraction.CSharp var versionInfo = FileVersionInfo.GetVersionInfo(SpecifiedCompiler); var compilerDir = Path.GetDirectoryName(SpecifiedCompiler); - bool known_compiler_name = versionInfo.OriginalFilename == "csc.exe" || versionInfo.OriginalFilename == "csc2.exe"; - bool copyright_microsoft = versionInfo.LegalCopyright != null && versionInfo.LegalCopyright.Contains("Microsoft"); - bool mscorlib_exists = File.Exists(Path.Combine(compilerDir, "mscorlib.dll")); + var knownCompilerNames = new Dictionary + { + { "csc.exe", "Microsoft" }, + { "csc2.exe", "Microsoft" }, + { "csc.dll", "Microsoft" }, + { "mcs.exe", "Novell" } + }; + var mscorlibExists = File.Exists(Path.Combine(compilerDir, "mscorlib.dll")); - if (specifiedFramework == null && mscorlib_exists) + if (specifiedFramework == null && mscorlibExists) { specifiedFramework = compilerDir; } - if (!known_compiler_name) + if (!knownCompilerNames.TryGetValue(versionInfo.OriginalFilename, out var vendor)) { - SkipExtractionBecause("the exe name is not recognised"); + SkipExtractionBecause("the compiler name is not recognised"); + return; } - else if (!copyright_microsoft) + + if (versionInfo.LegalCopyright == null || !versionInfo.LegalCopyright.Contains(vendor)) { - SkipExtractionBecause("the exe isn't copyright Microsoft"); + SkipExtractionBecause($"the compiler isn't copyright {vendor}, but instead {versionInfo.LegalCopyright ?? ""}"); + return; } } + + ArgsWithResponse = AddDefaultResponse(CscRsp, options.CompilerArguments).ToArray(); } void SkipExtractionBecause(string reason) @@ -87,7 +99,7 @@ namespace Semmle.Extraction.CSharp /// /// The file csc.rsp. /// - public string CscRsp => Path.Combine(FrameworkPath, csc_rsp); + string CscRsp => Path.Combine(FrameworkPath, csc_rsp); /// /// Should we skip extraction? @@ -103,5 +115,25 @@ namespace Semmle.Extraction.CSharp /// Gets additional reference directories - the compiler directory. /// public string AdditionalReferenceDirectories => SpecifiedCompiler != null ? Path.GetDirectoryName(SpecifiedCompiler) : null; + + /// + /// Adds @csc.rsp to the argument list to mimic csc.exe. + /// + /// The full pathname of csc.rsp. + /// The other command line arguments. + /// Modified list of arguments. + static IEnumerable AddDefaultResponse(string responseFile, IEnumerable args) + { + return SuppressDefaultResponseFile(args) && File.Exists(responseFile) ? + args : + new[] { "@" + responseFile }.Concat(args); + } + + static bool SuppressDefaultResponseFile(IEnumerable args) + { + return args.Any(arg => new[] { "/noconfig", "-noconfig" }.Contains(arg.ToLowerInvariant())); + } + + public readonly string[] ArgsWithResponse; } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs index 10a145d9e2f..83f2d39eda9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs @@ -87,11 +87,9 @@ namespace Semmle.Extraction.CSharp return ExitCode.Ok; } - var argsWithResponse = AddDefaultResponse(compilerVersion.CscRsp, commandLineArguments.CompilerArguments); - var cwd = Directory.GetCurrentDirectory(); var compilerArguments = CSharpCommandLineParser.Default.Parse( - argsWithResponse, + compilerVersion.ArgsWithResponse, cwd, compilerVersion.FrameworkPath, compilerVersion.AdditionalReferenceDirectories @@ -128,7 +126,7 @@ namespace Semmle.Extraction.CSharp { logger.Log(Severity.Error, " No source files"); ++analyser.CompilationErrors; - analyser.LogDiagnostics(); + analyser.LogDiagnostics(compilerVersion.ArgsWithResponse); return ExitCode.Failed; } @@ -146,7 +144,7 @@ namespace Semmle.Extraction.CSharp // already. ); - analyser.Initialize(compilerArguments, compilation, commandLineArguments); + analyser.Initialize(compilerArguments, compilation, commandLineArguments, compilerVersion.ArgsWithResponse); analyser.AnalyseReferences(); foreach (var tree in compilation.SyntaxTrees) @@ -172,24 +170,6 @@ namespace Semmle.Extraction.CSharp } } - internal static bool SuppressDefaultResponseFile(IEnumerable args) - { - return args.Any(arg => new[] { "/noconfig", "-noconfig" }.Contains(arg.ToLowerInvariant())); - } - - /// - /// Adds @csc.rsp to the argument list to mimic csc.exe. - /// - /// The full pathname of csc.rsp. - /// The other command line arguments. - /// Modified list of arguments. - static IEnumerable AddDefaultResponse(string responseFile, IEnumerable args) - { - return SuppressDefaultResponseFile(args) && File.Exists(responseFile) ? - args : - new[] { "@" + responseFile }.Concat(args); - } - /// /// Gets the complete list of locations to locate references. /// diff --git a/csharp/extractor/Semmle.Extraction/Context.cs b/csharp/extractor/Semmle.Extraction/Context.cs index b66370e896b..b2b3c42765f 100644 --- a/csharp/extractor/Semmle.Extraction/Context.cs +++ b/csharp/extractor/Semmle.Extraction/Context.cs @@ -171,6 +171,10 @@ namespace Semmle.Extraction { populateQueue.Dequeue()(); } + catch (InternalError e) + { + Extractor.Message(e.ExtractionMessage); + } catch (Exception e) { Extractor.Message(new Message { severity = Severity.Error, exception = e, message = "Uncaught exception" }); diff --git a/csharp/extractor/Semmle.Extraction/TrapWriter.cs b/csharp/extractor/Semmle.Extraction/TrapWriter.cs index 3e57e9e7918..cdc31019d1f 100644 --- a/csharp/extractor/Semmle.Extraction/TrapWriter.cs +++ b/csharp/extractor/Semmle.Extraction/TrapWriter.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.IO.Compression; +using System.Security.Cryptography; using System.Text; using Semmle.Util; using Semmle.Util.Logging; @@ -170,21 +171,23 @@ namespace Semmle.Extraction WriterLazy.Value.Close(); if (TryMove(tmpFile, TrapFile)) return; - else if (discardDuplicates) + + if (discardDuplicates) { FileUtils.TryDelete(tmpFile); return; } - string root = TrapFile.Substring(0, TrapFile.Length - 8); // Remove trailing ".trap.gz" - - // Loop until we find an available trap filename. - for (int n = 0; n < 100; ++n) + var existingHash = ComputeHash(TrapFile); + var hash = ComputeHash(tmpFile); + if (existingHash != hash) { - if (TryMove(tmpFile, string.Format("{0}.{1}.trap.gz", root, n))) + var root = TrapFile.Substring(0, TrapFile.Length - 8); // Remove trailing ".trap.gz" + if (TryMove(tmpFile, $"{root}-{hash}.trap.gz")) return; } - Logger.Log(Severity.Error, "Failed to move the trap file from {0} to {1}", tmpFile, TrapFile); + Logger.Log(Severity.Info, "Identical trap file for {0} already exists", TrapFile); + FileUtils.TryDelete(tmpFile); } } catch (Exception ex) @@ -203,6 +206,22 @@ namespace Semmle.Extraction //#################### PRIVATE METHODS #################### #region + /// + /// Computes the hash of . + /// + static string ComputeHash(string filePath) + { + using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var shaAlg = new SHA256Managed()) + { + var sha = shaAlg.ComputeHash(fileStream); + var hex = new StringBuilder(sha.Length * 2); + foreach (var b in sha) + hex.AppendFormat("{0:x2}", b); + return hex.ToString(); + } + } + class TrapBuilder : ITrapBuilder { readonly StreamWriter StreamWriter; diff --git a/csharp/extractor/global.json b/csharp/extractor/global.json deleted file mode 100644 index 664de46cc7d..00000000000 --- a/csharp/extractor/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sdk": { - "version": "2.1.103" - } -} \ No newline at end of file diff --git a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql index db38047c4e2..fc6270dce0e 100644 --- a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql +++ b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql @@ -79,7 +79,7 @@ class ConstantNullnessCondition extends ConstantCondition { cfn = this.getAControlFlowNode() | exists(ControlFlow::SuccessorTypes::NullnessSuccessor t | exists(cfn.getASuccessorByType(t)) | - if t.isNull() then b = true else b = false + b = t.getValue() ) and strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1 ) @@ -102,7 +102,7 @@ class ConstantMatchingCondition extends ConstantCondition { cfn = this.getAControlFlowNode() | exists(ControlFlow::SuccessorTypes::MatchingSuccessor t | exists(cfn.getASuccessorByType(t)) | - if t.isMatch() then b = true else b = false + b = t.getValue() ) and strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1 ) diff --git a/csharp/ql/src/Concurrency/LockOrder.qhelp b/csharp/ql/src/Concurrency/LockOrder.qhelp index 2d592c6b60d..8032e2f3369 100644 --- a/csharp/ql/src/Concurrency/LockOrder.qhelp +++ b/csharp/ql/src/Concurrency/LockOrder.qhelp @@ -17,10 +17,10 @@ variables in the same sequence.

    The following example shows a program running two threads, which deadlocks because thread1 holds lock1 and is waiting to acquire lock2, whilst thread2 holds lock2 and is waiting to acquire lock1.

    - +

    This problem is resolved by reordering the lock variables as shown below.

    - + diff --git a/csharp/ql/src/Concurrency/LockOrder.ql b/csharp/ql/src/Concurrency/LockOrder.ql index 7d1e7211e6b..4ab41e85fda 100644 --- a/csharp/ql/src/Concurrency/LockOrder.ql +++ b/csharp/ql/src/Concurrency/LockOrder.ql @@ -13,13 +13,50 @@ import csharp -from LockStmt l1, LockStmt l2, LockStmt l3, LockStmt l4, Variable v1, Variable v2 -where l1.getALockedStmt()=l2 -and l3.getALockedStmt()=l4 -and l1.getLockVariable()=v1 -and l2.getLockVariable()=v2 -and l3.getLockVariable()=v2 -and l4.getLockVariable()=v1 -and v1!=v2 -select l4, "Inconsistent lock sequence. The locks " + v1 + " and " + v2 + " are locked in a different sequence $@.", - l2, "here" +/** + * Gets a call target conservatively only when there is + * one runtime target. + */ +Callable getCallTarget(Call c) { + count(c.getARuntimeTarget()) = 1 and + result = c.getARuntimeTarget() +} + +/** Gets a lock statement reachable from a callable. */ +LockStmt getAReachableLockStmt(Callable callable) { + result.getEnclosingCallable() = callable + or + exists(Call call | call.getEnclosingCallable() = callable | + result = getAReachableLockStmt(getCallTarget(call)) + ) +} + +/** + * Holds if there is nested pairs of lock statements, either + * inter-procedurally or intra-procedurally. + */ +predicate nestedLocks(Variable outerVariable, Variable innerVariable, LockStmt outer, LockStmt inner) { + outerVariable = outer.getLockVariable() and + innerVariable = inner.getLockVariable() and + outerVariable != innerVariable and ( + inner = outer.getALockedStmt() + or + exists(Call call | call.getEnclosingStmt() = outer.getALockedStmt() | + inner = getAReachableLockStmt(getCallTarget(call)) + ) and + outerVariable.(Modifiable).isStatic() and + innerVariable.(Modifiable).isStatic() + ) +} + +from LockStmt outer1, LockStmt inner1, LockStmt outer2, LockStmt inner2, Variable v1, Variable v2 +where + nestedLocks(v1, v2, outer1, inner1) and + nestedLocks(v2, v1, outer2, inner2) and + v1.getName() <= v2.getName() +select v1, "Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found.", + v2, v2.getName(), + outer1, v1.getName(), + inner1, v2.getName(), + outer2, v2.getName(), + inner2, v1.getName() diff --git a/csharp/ql/src/Concurrency/LockOrder.cs b/csharp/ql/src/Concurrency/LockOrderBad.cs similarity index 94% rename from csharp/ql/src/Concurrency/LockOrder.cs rename to csharp/ql/src/Concurrency/LockOrderBad.cs index a8c8025143b..a9dd05ac866 100644 --- a/csharp/ql/src/Concurrency/LockOrder.cs +++ b/csharp/ql/src/Concurrency/LockOrderBad.cs @@ -1,3 +1,6 @@ +using System; +using System.Threading; + class Deadlock { private readonly Object lock1 = new Object(); diff --git a/csharp/ql/src/Concurrency/LockOrderFix.cs b/csharp/ql/src/Concurrency/LockOrderGood.cs similarity index 94% rename from csharp/ql/src/Concurrency/LockOrderFix.cs rename to csharp/ql/src/Concurrency/LockOrderGood.cs index f1548b956eb..1aaeef6a045 100644 --- a/csharp/ql/src/Concurrency/LockOrderFix.cs +++ b/csharp/ql/src/Concurrency/LockOrderGood.cs @@ -1,3 +1,6 @@ +using System; +using System.Threading; + class DeadlockFixed { private readonly Object lock1 = new Object(); diff --git a/csharp/ql/src/semmle/code/csharp/commons/StructuralComparison.qll b/csharp/ql/src/semmle/code/csharp/commons/StructuralComparison.qll index deec41643ee..82c4b5cb8b7 100644 --- a/csharp/ql/src/semmle/code/csharp/commons/StructuralComparison.qll +++ b/csharp/ql/src/semmle/code/csharp/commons/StructuralComparison.qll @@ -85,6 +85,7 @@ abstract class StructuralComparisonConfiguration extends string { value = x.getValue() } + pragma [nomagic] private predicate sameByStructure(Element x, Element y) { // At least one of `x` and `y` must not have a value, they must have // the same kind, and the same number of children @@ -121,6 +122,7 @@ abstract class StructuralComparisonConfiguration extends string { not (x.(Expr).hasValue() and y.(Expr).hasValue()) } + pragma [nomagic] private predicate sameInternal(Element x, Element y) { sameByValue(x, y) or @@ -210,6 +212,7 @@ module Internal { value = x.getValue() } + pragma [nomagic] private predicate sameByStructure(Element x, Element y) { // At least one of `x` and `y` must not have a value, they must have // the same kind, and the same number of children @@ -246,6 +249,7 @@ module Internal { not (x.(Expr).hasValue() and y.(Expr).hasValue()) } + pragma [nomagic] private predicate sameInternal(Element x, Element y) { sameByValue(x, y) or diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll b/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll index 2bacb951da5..3c6926d8ae9 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll @@ -3,6 +3,7 @@ */ import csharp +private import ControlFlow::SuccessorTypes /** * A basic block, that is, a maximal straight-line sequence of control flow nodes @@ -14,6 +15,11 @@ class BasicBlock extends TBasicBlockStart { result.getFirstNode() = getLastNode().getASuccessor() } + /** Gets an immediate successor of this basic block of a given flow type, if any. */ + BasicBlock getASuccessorByType(ControlFlow::SuccessorType t) { + result.getFirstNode() = this.getLastNode().getASuccessorByType(t) + } + /** Gets an immediate predecessor of this basic block, if any. */ BasicBlock getAPredecessor() { result.getASuccessor() = this @@ -75,6 +81,7 @@ class BasicBlock extends TBasicBlockStart { * The node on line 2 is an immediate `null` successor of the node * `x` on line 1. */ + deprecated BasicBlock getANullSuccessor() { result.getFirstNode() = getLastNode().getANullSuccessor() } @@ -94,6 +101,7 @@ class BasicBlock extends TBasicBlockStart { * The node `x?.M()`, representing the call to `M`, is a non-`null` successor * of the node `x`. */ + deprecated BasicBlock getANonNullSuccessor() { result.getFirstNode() = getLastNode().getANonNullSuccessor() } @@ -430,7 +438,7 @@ class ConditionBlock extends BasicBlock { * the callable entry point by going via the true edge (`testIsTrue = true`) * or false edge (`testIsTrue = false`) out of this basic block. */ - predicate controls(BasicBlock controlled, boolean testIsTrue) { + predicate controls(BasicBlock controlled, ConditionalSuccessor s) { /* * For this block to control the block `controlled` with `testIsTrue` the following must be true: * Execution must have passed through the test i.e. `this` must strictly dominate `controlled`. @@ -465,7 +473,7 @@ class ConditionBlock extends BasicBlock { * directly. */ exists(BasicBlock succ | - isCandidateSuccessor(succ, testIsTrue) | + isCandidateSuccessor(succ, s) | succ.dominates(controlled) ) } @@ -476,11 +484,9 @@ class ConditionBlock extends BasicBlock { * the callable entry point by going via the `null` edge (`isNull = true`) * or non-`null` edge (`isNull = false`) out of this basic block. */ + deprecated predicate controlsNullness(BasicBlock controlled, boolean isNull) { - exists(BasicBlock succ | - isCandidateSuccessorNullness(succ, isNull) | - succ.dominates(controlled) - ) + this.controls(controlled, any(NullnessSuccessor s | s.getValue() = isNull)) } /** @@ -520,29 +526,11 @@ class ConditionBlock extends BasicBlock { // only `x & y` controls `A` if we do not take sub conditions into account. predicate controlsSubCond(BasicBlock controlled, boolean testIsTrue, Expr cond, boolean condIsTrue) { impliesSub(getLastNode().getElement(), cond, testIsTrue, condIsTrue) and - controls(controlled, testIsTrue) + controls(controlled, any(BooleanSuccessor s | s.getValue() = testIsTrue)) } - private predicate isCandidateSuccessor(BasicBlock succ, boolean testIsTrue) { - ( - testIsTrue = true and succ = this.getATrueSuccessor() - or - testIsTrue = false and succ = this.getAFalseSuccessor() - ) - and - forall(BasicBlock pred | - pred = succ.getAPredecessor() and pred != this | - succ.dominates(pred) - ) - } - - private predicate isCandidateSuccessorNullness(BasicBlock succ, boolean isNull) { - ( - isNull = true and succ = this.getANullSuccessor() - or - isNull = false and succ = this.getANonNullSuccessor() - ) - and + private predicate isCandidateSuccessor(BasicBlock succ, ConditionalSuccessor s) { + succ = this.getASuccessorByType(s) and forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != this | succ.dominates(pred) diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll b/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll index 4a22bcc739e..7c74326ed1f 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll @@ -401,10 +401,21 @@ private predicate inNullnessContext(Expr e, boolean isNullnessCompletionForParen ) } +/** + * Holds if `cfe` is the element inside case statement `cs`, belonging to `switch` + * statement `ss`, that has the matching completion. + */ +predicate switchMatching(SwitchStmt ss, CaseStmt cs, ControlFlowElement cfe) { + ss.getACase() = cs and + ( + cfe = cs.(ConstCase).getExpr() + or + cfe = cs.(TypeCase).getTypeAccess() // use type access to represent the type test + ) +} + private predicate mustHaveMatchingCompletion(SwitchStmt ss, ControlFlowElement cfe) { - cfe = ss.getAConstCase().getExpr() - or - cfe = ss.getATypeCase().getTypeAccess() // use type access to represent the type test + switchMatching(ss, _, cfe) } /** diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll index f35c271adfa..20878834ff6 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll @@ -255,6 +255,7 @@ module ControlFlow { * The node on line 2 is an immediate `null` successor of the node * `x` on line 1. */ + deprecated Node getANullSuccessor() { result = getASuccessorByType(any(NullnessSuccessor t | t.isNull())) } @@ -274,6 +275,7 @@ module ControlFlow { * The node `x?.M()`, representing the call to `M`, is a non-`null` successor * of the node `x`. */ + deprecated Node getANonNullSuccessor() { result = getASuccessorByType(any(NullnessSuccessor t | not t.isNull())) } @@ -400,7 +402,10 @@ module ControlFlow { * a nullness successor (`NullnessSuccessor`), a matching successor (`MatchingSuccessor`), * or an emptiness successor (`EmptinessSuccessor`). */ - abstract class ConditionalSuccessor extends SuccessorType { } + abstract class ConditionalSuccessor extends SuccessorType { + /** Gets the Boolean value of this successor. */ + abstract boolean getValue(); + } /** * A Boolean control flow successor. @@ -429,8 +434,7 @@ module ControlFlow { * ``` */ class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor { - /** Gets the value of this Boolean successor. */ - boolean getValue() { this = TBooleanSuccessor(result) } + override boolean getValue() { this = TBooleanSuccessor(result) } override string toString() { result = getValue().toString() } @@ -469,6 +473,8 @@ module ControlFlow { /** Holds if this is a `null` successor. */ predicate isNull() { this = TNullnessSuccessor(true) } + override boolean getValue() { this = TNullnessSuccessor(result) } + override string toString() { if this.isNull() then result = "null" @@ -520,6 +526,8 @@ module ControlFlow { /** Holds if this is a match successor. */ predicate isMatch() { this = TMatchingSuccessor(true) } + override boolean getValue() { this = TMatchingSuccessor(result) } + override string toString() { if this.isMatch() then result = "match" @@ -571,6 +579,8 @@ module ControlFlow { /** Holds if this is an empty successor. */ predicate isEmpty() { this = TEmptinessSuccessor(true) } + override boolean getValue() { this = TEmptinessSuccessor(result) } + override string toString() { if this.isEmpty() then result = "empty" diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll index 8dcc683f5c3..d96a0abcb62 100644 --- a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll +++ b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll @@ -10,14 +10,12 @@ private import semmle.code.csharp.frameworks.System /** An expression that accesses/calls a declaration. */ class AccessOrCallExpr extends Expr { - AccessOrCallExpr() { - exists(getDeclarationTarget(this)) - } + private Declaration target; + + AccessOrCallExpr() { target = getDeclarationTarget(this) } /** Gets the target of this expression. */ - Declaration getTarget() { - result = getDeclarationTarget(this) - } + Declaration getTarget() { result = target } /** * Gets the (non-trivial) SSA definition corresponding to the longest @@ -35,16 +33,12 @@ class AccessOrCallExpr extends Expr { * x; // SSA qualifier: SSA definition for `x` * ``` */ - Ssa::Definition getSsaQualifier() { - result = getSsaQualifier(this) - } + Ssa::Definition getSsaQualifier() { result = getSsaQualifier(this) } /** * Holds if this expression has an SSA qualifier. */ - predicate hasSsaQualifier() { - exists(getSsaQualifier()) - } + predicate hasSsaQualifier() { exists(this.getSsaQualifier()) } } private Declaration getDeclarationTarget(Expr e) { @@ -103,8 +97,12 @@ private AssignableRead getATrackedRead(Ssa::Definition def) { * definition). */ class GuardedExpr extends AccessOrCallExpr { + private Expr cond0; + private AccessOrCallExpr e0; + private boolean b0; + GuardedExpr() { - Internal::isGuardedBy(this, _, _, _) + Internal::isGuardedBy(this, cond0, e0, b0) } /** @@ -117,14 +115,16 @@ class GuardedExpr extends AccessOrCallExpr { * variable). */ predicate isGuardedBy(Expr cond, Expr e, boolean b) { - Internal::isGuardedBy(this, cond, e, b) + cond = cond0 and + e = e0 and + b = b0 } } /** * A nullness guarded expression. * - * A nullness guarded expression is an access or a call that is reached only + * A nullness guarded expression is an access or a call that is reached only * when a nullness condition containing a structurally equal expression * evaluates to one of `null` or non-`null`. * @@ -136,8 +136,11 @@ class GuardedExpr extends AccessOrCallExpr { * ``` */ class NullnessGuardedExpr extends AccessOrCallExpr { + private Expr e0; + private boolean isNull0; + NullnessGuardedExpr() { - Internal::isGuardedByNullness(this, _, _) + Internal::isGuardedByNullness(this, e0, isNull0) } /** @@ -150,13 +153,64 @@ class NullnessGuardedExpr extends AccessOrCallExpr { * variable). */ predicate isGuardedBy(Expr e, boolean isNull) { - Internal::isGuardedByNullness(this, e, isNull) + e = e0 and + isNull = isNull0 + } +} + +/** + * A matching guarded expression. + * + * A matching guarded expression is an access or a call that is reached only + * when a pattern, matching against a structurally equal expression, matches + * or non-matches. + * + * For example, the access to `o` on line 8 is only evaluated when `case null` + * does not match. + * + * ``` + * string M(object o) + * { + * switch (o) + * { + * case null: + * return ""; + * default: + * return o.ToString(); + * } + * } + * ``` + */ +class MatchingGuardedExpr extends AccessOrCallExpr { + private AccessOrCallExpr e0; + private CaseStmt cs0; + private boolean isMatch0; + + MatchingGuardedExpr() { + Internal::isGuardedByMatching(this, e0, cs0, isMatch0) + } + + /** + * Holds if this expression is guarded by case statement `cs` matching + * (`isMatch = true`) or non-matching (`isMatch = false`). The expression + * `e` is structurally equal to this expression being matched against in + * `cs`. + * + * In case this expression or `e` accesses an SSA variable in its + * left-most qualifier, then so must the other (accessing the same SSA + * variable). + */ + predicate isGuardedBy(AccessOrCallExpr e, CaseStmt cs, boolean isMatch) { + e = e0 and + cs0 = cs and + isMatch0 = isMatch } } /** An expression guarded by a `null` check. */ class NullGuardedExpr extends AccessOrCallExpr { NullGuardedExpr() { + this.getType() instanceof RefType and exists(Expr cond, Expr sub, boolean b | this.(GuardedExpr).isGuardedBy(cond, sub, b) | // Comparison with `null`, for example `x != null` @@ -182,19 +236,47 @@ class NullGuardedExpr extends AccessOrCallExpr { ) or // Call to `string.IsNullOrEmpty()` - exists(MethodCall mc | - mc = cond and + cond = any(MethodCall mc | mc.getTarget() = any(SystemStringClass c).getIsNullOrEmptyMethod() and mc.getArgument(0) = sub and b = false ) + or + cond = any(IsExpr ie | + ie.getExpr() = sub and + if ie.(IsConstantExpr).getConstant() instanceof NullLiteral then + // E.g. `x is null` + b = false + else + // E.g. `x is string` + b = true + ) ) or this.(NullnessGuardedExpr).isGuardedBy(_, false) + or + exists(CaseStmt cs, boolean isMatch | + this.(MatchingGuardedExpr).isGuardedBy(_, cs, isMatch) | + // E.g. `case string` + cs instanceof TypeCase and + isMatch = true + or + cs = any(ConstCase cc | + if cc.getExpr() instanceof NullLiteral then + // `case null` + isMatch = false + else + // E.g. `case ""` + isMatch = true + ) + ) } } private module Internal { + private import semmle.code.csharp.controlflow.Completion + private import ControlFlow::SuccessorTypes + private cached module Cached { cached predicate isGuardedBy(AccessOrCallExpr guarded, Expr cond, AccessOrCallExpr e, boolean b) { exists(BasicBlock bb | @@ -217,6 +299,17 @@ private module Internal { guarded.getSsaQualifier() = e.getSsaQualifier() ) } + + cached predicate isGuardedByMatching(AccessOrCallExpr guarded, AccessOrCallExpr e, CaseStmt cs, boolean isMatch) { + exists(BasicBlock bb | + controlsMatching(e, cs, bb, isMatch) and + bb = guarded.getAControlFlowNode().getBasicBlock() and + exists(ConditionOnExprComparisonConfig c | c.same(e, guarded)) | + not guarded.hasSsaQualifier() and not e.hasSsaQualifier() + or + guarded.getSsaQualifier() = e.getSsaQualifier() + ) + } } import Cached @@ -263,12 +356,27 @@ private module Internal { * (`isNull = true`) or when `e` evaluates to non-`null` (`isNull = false`). */ private predicate controlsNullness(Expr e, BasicBlock bb, boolean isNull) { - exists(ConditionBlock cb | - cb.controlsNullness(bb, isNull) | + exists(ConditionBlock cb, NullnessSuccessor s | + cb.controls(bb, s) | + isNull = s.getValue() and e = cb.getLastNode().getElement() ) } + /** + * Holds if basic block `bb` only is reached when `e` matches case `cs` + * (`isMatch = true`) or when `e` does not match `cs` (`isMatch = false`). + */ + private predicate controlsMatching(Expr e, CaseStmt cs, BasicBlock bb, boolean isMatch) { + exists(ConditionBlock cb, SwitchStmt ss, ControlFlowElement cfe, MatchingSuccessor s | + cb.controls(bb, s) | + cfe = cb.getLastNode().getElement() and + switchMatching(ss, cs, cfe) and + e = ss.getCondition() and + isMatch = s.getValue() + ) + } + /** * A helper class for calculating structurally equal access/call expressions. */ @@ -280,7 +388,10 @@ private module Internal { override predicate candidate(Element x, Element y) { exists(BasicBlock bb, Declaration d | candidateAux(x, d, bb) and - y = any(AccessOrCallExpr e | e.getAControlFlowNode().getBasicBlock() = bb and e.getTarget() = d) + y = any(AccessOrCallExpr e | + e.getAControlFlowNode().getBasicBlock() = bb and + e.getTarget() = d + ) ) } @@ -289,10 +400,16 @@ private module Internal { * is a sub expression of a condition that controls whether basic block * `bb` is reached. */ - pragma [noinline] // predicate folding for proper join-order + pragma [noinline] private predicate candidateAux(AccessOrCallExpr e, Declaration target, BasicBlock bb) { - (controls(_, e, bb, _) or controlsNullness(e, bb, _)) and - target = e.getTarget() + target = e.getTarget() and + ( + controls(_, e, bb, _) + or + controlsNullness(e, bb, _) + or + controlsMatching(e, _, bb, _) + ) } } } diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/ConditionalBypass.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/ConditionalBypass.qll index 5b446c2dc70..e5f0d5f396f 100644 --- a/csharp/ql/src/semmle/code/csharp/security/dataflow/ConditionalBypass.qll +++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/ConditionalBypass.qll @@ -73,14 +73,20 @@ module UserControlledBypassOfSensitiveMethod { * on the given expression. */ predicate conditionControlsMethod(MethodCall call, Expr e) { - exists (ConditionBlock cb, SensitiveExecutionMethod def, boolean cond | - cb.controls(call.getAControlFlowNode().getBasicBlock(), cond) and + exists(ConditionBlock cb, SensitiveExecutionMethod def, boolean cond | + exists(ControlFlow::SuccessorTypes::BooleanSuccessor s | + cond = s.getValue() | + cb.controls(call.getAControlFlowNode().getBasicBlock(), s) + ) and def = call.getTarget() and /* * Exclude this condition if the other branch also contains a call to the same security * sensitive method. */ - not cb.controls(def.getACall().getAControlFlowNode().getBasicBlock(), cond.booleanNot()) and + not exists(ControlFlow::SuccessorTypes::BooleanSuccessor s | + cond = s.getValue().booleanNot() | + cb.controls(def.getACall().getAControlFlowNode().getBasicBlock(), s) + ) and e = cb.getLastNode().getElement() ) } diff --git a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected index 28d5589c75a..85d1641124d 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected +++ b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected @@ -1,5 +1,12 @@ +| BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | BreakInTry.cs:7:26:7:28 | String arg | false | +| BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | BreakInTry.cs:10:21:10:26 | break; | false | | BreakInTry.cs:9:21:9:31 | ... == ... | BreakInTry.cs:10:21:10:26 | break; | true | | BreakInTry.cs:15:17:15:28 | ... == ... | BreakInTry.cs:16:17:16:17 | ; | true | +| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:22:22:22:24 | String arg | false | +| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:27:21:27:26 | break; | false | +| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:30:13:33:13 | {...} | false | +| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:32:21:32:21 | ; | false | +| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:32:21:32:21 | [finally: break] ; | false | | BreakInTry.cs:26:21:26:31 | ... == ... | BreakInTry.cs:27:21:27:26 | break; | true | | BreakInTry.cs:26:21:26:31 | ... == ... | BreakInTry.cs:30:13:33:13 | {...} | false | | BreakInTry.cs:26:21:26:31 | ... == ... | BreakInTry.cs:32:21:32:21 | ; | false | @@ -15,6 +22,10 @@ | BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:50:21:50:26 | [finally: return] break; | true | | BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:50:21:50:26 | break; | false | | BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:53:7:53:7 | ; | false | +| BreakInTry.cs:47:13:51:13 | [finally: return] foreach (... ... in ...) ... | BreakInTry.cs:47:26:47:28 | [finally: return] String arg | false | +| BreakInTry.cs:47:13:51:13 | [finally: return] foreach (... ... in ...) ... | BreakInTry.cs:50:21:50:26 | [finally: return] break; | false | +| BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | BreakInTry.cs:47:26:47:28 | String arg | false | +| BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | BreakInTry.cs:50:21:50:26 | break; | false | | BreakInTry.cs:49:21:49:31 | ... == ... | BreakInTry.cs:50:21:50:26 | break; | true | | BreakInTry.cs:49:21:49:31 | [finally: return] ... == ... | BreakInTry.cs:50:21:50:26 | [finally: return] break; | true | | BreakInTry.cs:60:17:60:28 | ... == ... | BreakInTry.cs:61:17:61:23 | return ...; | true | @@ -25,6 +36,10 @@ | BreakInTry.cs:60:17:60:28 | ... == ... | BreakInTry.cs:65:26:65:28 | [finally: return] String arg | true | | BreakInTry.cs:60:17:60:28 | ... == ... | BreakInTry.cs:68:21:68:26 | [finally: return] break; | true | | BreakInTry.cs:60:17:60:28 | ... == ... | BreakInTry.cs:68:21:68:26 | break; | false | +| BreakInTry.cs:65:13:69:13 | [finally: return] foreach (... ... in ...) ... | BreakInTry.cs:65:26:65:28 | [finally: return] String arg | false | +| BreakInTry.cs:65:13:69:13 | [finally: return] foreach (... ... in ...) ... | BreakInTry.cs:68:21:68:26 | [finally: return] break; | false | +| BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | BreakInTry.cs:65:26:65:28 | String arg | false | +| BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | BreakInTry.cs:68:21:68:26 | break; | false | | BreakInTry.cs:67:21:67:31 | ... == ... | BreakInTry.cs:68:21:68:26 | break; | true | | BreakInTry.cs:67:21:67:31 | [finally: return] ... == ... | BreakInTry.cs:68:21:68:26 | [finally: return] break; | true | | CatchInFinally.cs:9:17:9:28 | ... == ... | CatchInFinally.cs:10:17:10:50 | throw ...; | true | @@ -58,8 +73,16 @@ | CatchInFinally.cs:16:21:16:36 | [finally: exception(ArgumentNullException)] ... == ... | CatchInFinally.cs:17:41:17:43 | [finally: exception(ArgumentNullException)] "1" | true | | CatchInFinally.cs:16:21:16:36 | [finally: exception(Exception)] ... == ... | CatchInFinally.cs:17:21:17:45 | [finally: exception(Exception)] throw ...; | true | | CatchInFinally.cs:16:21:16:36 | [finally: exception(Exception)] ... == ... | CatchInFinally.cs:17:41:17:43 | [finally: exception(Exception)] "1" | true | +| ConditionalAccess.cs:3:26:3:26 | access to parameter i | ConditionalAccess.cs:3:28:3:38 | call to method ToString | false | +| ConditionalAccess.cs:3:26:3:26 | access to parameter i | ConditionalAccess.cs:3:40:3:49 | call to method ToLower | false | +| ConditionalAccess.cs:3:28:3:38 | call to method ToString | ConditionalAccess.cs:3:40:3:49 | call to method ToLower | false | +| ConditionalAccess.cs:5:26:5:26 | access to parameter s | ConditionalAccess.cs:5:28:5:34 | access to property Length | false | +| ConditionalAccess.cs:7:39:7:40 | access to parameter s1 | ConditionalAccess.cs:7:45:7:46 | access to parameter s2 | true | +| ConditionalAccess.cs:9:25:9:25 | access to parameter s | ConditionalAccess.cs:9:27:9:33 | access to property Length | false | +| ConditionalAccess.cs:13:13:13:13 | access to parameter s | ConditionalAccess.cs:13:15:13:21 | access to property Length | false | | ConditionalAccess.cs:13:13:13:25 | ... > ... | ConditionalAccess.cs:14:20:14:20 | 0 | true | | ConditionalAccess.cs:13:13:13:25 | ... > ... | ConditionalAccess.cs:16:20:16:20 | 1 | false | +| ConditionalAccess.cs:19:40:19:41 | access to parameter s1 | ConditionalAccess.cs:19:58:19:59 | access to parameter s2 | false | | Conditions.cs:5:13:5:15 | access to parameter inc | Conditions.cs:6:13:6:16 | [inc (line 3): true] ...; | true | | Conditions.cs:5:13:5:15 | access to parameter inc | Conditions.cs:7:9:8:16 | [inc (line 3): false] if (...) ... | false | | Conditions.cs:14:13:14:13 | access to parameter b | Conditions.cs:15:13:15:16 | [b (line 11): true] ...; | true | @@ -106,9 +129,23 @@ | Conditions.cs:62:17:62:17 | access to parameter b | Conditions.cs:63:17:63:20 | [b (line 57): true] ...; | true | | Conditions.cs:62:17:62:17 | access to parameter b | Conditions.cs:65:9:66:16 | [b (line 57): false] if (...) ... | false | | Conditions.cs:62:17:62:17 | access to parameter b | Conditions.cs:65:9:66:16 | [b (line 57): true] if (...) ... | true | +| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:75:9:80:9 | {...} | false | +| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:77:17:77:20 | ...; | false | +| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:78:13:79:26 | if (...) ... | false | +| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:79:17:79:26 | ...; | false | +| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:81:9:82:16 | if (...) ... | true | +| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:82:13:82:16 | ...; | true | +| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:83:16:83:16 | access to local variable x | true | | Conditions.cs:76:17:76:17 | access to local variable b | Conditions.cs:77:17:77:20 | ...; | true | | Conditions.cs:78:17:78:21 | ... > ... | Conditions.cs:79:17:79:26 | ...; | true | | Conditions.cs:81:12:81:12 | access to local variable b | Conditions.cs:82:13:82:16 | ...; | true | +| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:91:9:98:9 | {...} | false | +| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:93:17:93:20 | ...; | false | +| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:94:13:95:26 | if (...) ... | false | +| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:95:17:95:26 | ...; | false | +| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:96:13:97:20 | if (...) ... | false | +| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:97:17:97:20 | ...; | false | +| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:99:16:99:16 | access to local variable x | true | | Conditions.cs:92:17:92:17 | access to local variable b | Conditions.cs:93:17:93:20 | ...; | true | | Conditions.cs:94:17:94:21 | ... > ... | Conditions.cs:95:17:95:26 | ...; | true | | Conditions.cs:96:17:96:17 | access to local variable b | Conditions.cs:97:17:97:20 | ...; | true | @@ -118,6 +155,7 @@ | Conditions.cs:105:13:105:13 | access to parameter b | Conditions.cs:108:13:109:24 | [b (line 102): true] if (...) ... | true | | Conditions.cs:107:13:107:24 | [b (line 102): false] ... > ... | Conditions.cs:108:13:109:24 | [b (line 102): false] if (...) ... | true | | Conditions.cs:107:13:107:24 | [b (line 102): true] ... > ... | Conditions.cs:108:13:109:24 | [b (line 102): true] if (...) ... | true | +| ExitMethods.cs:42:9:45:9 | [exception: Exception] catch (...) {...} | ExitMethods.cs:46:9:49:9 | [exception: Exception] catch (...) {...} | false | | ExitMethods.cs:54:13:54:13 | access to parameter b | ExitMethods.cs:55:19:55:33 | object creation of type Exception | true | | ExitMethods.cs:60:13:60:13 | access to parameter b | ExitMethods.cs:61:19:61:33 | object creation of type Exception | true | | ExitMethods.cs:60:13:60:13 | access to parameter b | ExitMethods.cs:63:41:63:43 | "b" | false | @@ -125,9 +163,23 @@ | ExitMethods.cs:78:16:78:25 | ... != ... | ExitMethods.cs:78:69:78:75 | "input" | false | | ExitMethods.cs:83:16:83:30 | call to method Contains | ExitMethods.cs:83:34:83:34 | 0 | true | | ExitMethods.cs:83:16:83:30 | call to method Contains | ExitMethods.cs:83:38:83:38 | 1 | false | +| Foreach.cs:8:9:9:13 | foreach (... ... in ...) ... | Foreach.cs:6:10:6:11 | exit M1 | true | +| Foreach.cs:8:9:9:13 | foreach (... ... in ...) ... | Foreach.cs:8:22:8:24 | String arg | false | +| Foreach.cs:14:9:15:13 | foreach (... ... in ...) ... | Foreach.cs:12:10:12:11 | exit M2 | true | +| Foreach.cs:14:9:15:13 | foreach (... ... in ...) ... | Foreach.cs:15:13:15:13 | ; | false | +| Foreach.cs:20:9:21:11 | foreach (... ... in ...) ... | Foreach.cs:18:10:18:11 | exit M3 | true | +| Foreach.cs:20:9:21:11 | foreach (... ... in ...) ... | Foreach.cs:20:22:20:22 | String x | false | +| Foreach.cs:20:27:20:27 | access to parameter e | Foreach.cs:20:29:20:38 | call to method ToArray | false | +| NullCoalescing.cs:3:23:3:23 | access to parameter i | NullCoalescing.cs:3:28:3:28 | 0 | true | +| NullCoalescing.cs:5:25:5:25 | access to parameter b | NullCoalescing.cs:5:30:5:34 | false | true | | NullCoalescing.cs:5:25:5:25 | access to parameter b | NullCoalescing.cs:5:39:5:39 | 0 | true | +| NullCoalescing.cs:7:40:7:41 | access to parameter s1 | NullCoalescing.cs:7:46:7:53 | ... ?? ... | true | +| NullCoalescing.cs:7:40:7:41 | access to parameter s1 | NullCoalescing.cs:7:52:7:53 | "" | true | +| NullCoalescing.cs:7:46:7:47 | access to parameter s2 | NullCoalescing.cs:7:52:7:53 | "" | true | | NullCoalescing.cs:9:37:9:37 | access to parameter b | NullCoalescing.cs:9:41:9:41 | access to parameter s | true | | NullCoalescing.cs:9:37:9:37 | access to parameter b | NullCoalescing.cs:9:45:9:45 | access to parameter s | false | +| NullCoalescing.cs:11:44:11:45 | access to parameter b1 | NullCoalescing.cs:11:51:11:58 | ... && ... | true | +| NullCoalescing.cs:11:44:11:45 | access to parameter b1 | NullCoalescing.cs:11:57:11:58 | access to parameter b3 | true | | NullCoalescing.cs:11:51:11:52 | access to parameter b2 | NullCoalescing.cs:11:57:11:58 | access to parameter b3 | true | | Patterns.cs:8:13:8:23 | ... is ... | Patterns.cs:9:9:11:9 | {...} | true | | Patterns.cs:8:13:8:23 | ... is ... | Patterns.cs:12:14:18:9 | if (...) ... | false | @@ -138,16 +190,123 @@ | Patterns.cs:12:18:12:31 | ... is ... | Patterns.cs:16:14:18:9 | if (...) ... | false | | Patterns.cs:12:18:12:31 | ... is ... | Patterns.cs:17:9:18:9 | {...} | false | | Patterns.cs:16:18:16:28 | ... is ... | Patterns.cs:17:9:18:9 | {...} | true | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:23:17:23:22 | break; | true | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:24:13:24:36 | case Int32 i2: | false | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:24:18:24:23 | Int32 i2 | false | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:25:17:25:52 | ...; | false | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:27:13:27:24 | case Int32 i3: | false | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:27:18:27:23 | Int32 i3 | false | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:30:13:30:27 | case String s2: | false | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:30:18:30:26 | String s2 | false | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:33:13:33:24 | case Object v2: | false | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:33:18:33:23 | Object v2 | false | +| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:35:13:35:20 | default: | false | +| Patterns.cs:24:18:24:20 | access to type Int32 | Patterns.cs:24:18:24:23 | Int32 i2 | true | +| Patterns.cs:24:18:24:20 | access to type Int32 | Patterns.cs:25:17:25:52 | ...; | true | | Patterns.cs:24:30:24:35 | ... > ... | Patterns.cs:25:17:25:52 | ...; | true | +| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:27:18:27:23 | Int32 i3 | true | +| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:30:13:30:27 | case String s2: | false | +| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:30:18:30:26 | String s2 | false | +| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:33:13:33:24 | case Object v2: | false | +| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:33:18:33:23 | Object v2 | false | +| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:35:13:35:20 | default: | false | +| Patterns.cs:30:18:30:23 | access to type String | Patterns.cs:30:18:30:26 | String s2 | true | +| Patterns.cs:30:18:30:23 | access to type String | Patterns.cs:33:13:33:24 | case Object v2: | false | +| Patterns.cs:30:18:30:23 | access to type String | Patterns.cs:33:18:33:23 | Object v2 | false | +| Patterns.cs:30:18:30:23 | access to type String | Patterns.cs:35:13:35:20 | default: | false | +| Patterns.cs:33:18:33:20 | access to type Object | Patterns.cs:33:18:33:23 | Object v2 | true | +| Patterns.cs:33:18:33:20 | access to type Object | Patterns.cs:35:13:35:20 | default: | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:15:17:15:23 | return ...; | true | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:16:13:16:19 | case ...: | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:17:23:17:37 | object creation of type Exception | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:18:13:18:22 | case ...: | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:19:17:19:29 | goto default; | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:20:13:20:23 | case Int32 i: | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:20:18:20:22 | Int32 i | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:22:21:22:27 | return ...; | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:23:27:23:27 | 0 | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:24:13:24:56 | case String s: | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:24:18:24:25 | String s | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:24:48:24:48 | access to local variable s | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:25:17:25:37 | ...; | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:27:13:27:39 | case Double d: | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:27:18:27:25 | Double d | false | +| Switch.cs:14:18:14:20 | "a" | Switch.cs:30:13:30:20 | default: | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:18:13:18:22 | case ...: | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:19:17:19:29 | goto default; | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:20:13:20:23 | case Int32 i: | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:20:18:20:22 | Int32 i | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:22:21:22:27 | return ...; | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:23:27:23:27 | 0 | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:24:13:24:56 | case String s: | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:24:18:24:25 | String s | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:24:48:24:48 | access to local variable s | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:25:17:25:37 | ...; | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:27:13:27:39 | case Double d: | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:27:18:27:25 | Double d | false | +| Switch.cs:16:18:16:18 | 0 | Switch.cs:30:13:30:20 | default: | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:19:17:19:29 | goto default; | true | +| Switch.cs:18:18:18:21 | null | Switch.cs:20:13:20:23 | case Int32 i: | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:20:18:20:22 | Int32 i | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:22:21:22:27 | return ...; | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:23:27:23:27 | 0 | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:24:13:24:56 | case String s: | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:24:18:24:25 | String s | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:24:48:24:48 | access to local variable s | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:25:17:25:37 | ...; | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:27:13:27:39 | case Double d: | false | +| Switch.cs:18:18:18:21 | null | Switch.cs:27:18:27:25 | Double d | false | +| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:20:18:20:22 | Int32 i | true | +| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:22:21:22:27 | return ...; | true | +| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:23:27:23:27 | 0 | true | +| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:24:13:24:56 | case String s: | false | +| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:24:18:24:25 | String s | false | +| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:24:48:24:48 | access to local variable s | false | +| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:25:17:25:37 | ...; | false | +| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:27:13:27:39 | case Double d: | false | +| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:27:18:27:25 | Double d | false | | Switch.cs:21:21:21:29 | ... == ... | Switch.cs:22:21:22:27 | return ...; | true | | Switch.cs:21:21:21:29 | ... == ... | Switch.cs:23:27:23:27 | 0 | false | +| Switch.cs:24:18:24:23 | access to type String | Switch.cs:24:18:24:25 | String s | true | +| Switch.cs:24:18:24:23 | access to type String | Switch.cs:24:48:24:48 | access to local variable s | true | +| Switch.cs:24:18:24:23 | access to type String | Switch.cs:25:17:25:37 | ...; | true | | Switch.cs:24:32:24:43 | ... > ... | Switch.cs:24:48:24:48 | access to local variable s | true | | Switch.cs:24:32:24:43 | ... > ... | Switch.cs:25:17:25:37 | ...; | true | | Switch.cs:24:48:24:55 | ... != ... | Switch.cs:25:17:25:37 | ...; | true | +| Switch.cs:27:18:27:23 | access to type Double | Switch.cs:27:18:27:25 | Double d | true | +| Switch.cs:48:18:48:20 | access to type Int32 | Switch.cs:49:17:49:22 | break; | true | +| Switch.cs:48:18:48:20 | access to type Int32 | Switch.cs:50:13:50:39 | case Boolean: | false | +| Switch.cs:48:18:48:20 | access to type Int32 | Switch.cs:50:30:50:30 | access to parameter o | false | +| Switch.cs:48:18:48:20 | access to type Int32 | Switch.cs:51:17:51:22 | break; | false | +| Switch.cs:50:18:50:21 | access to type Boolean | Switch.cs:50:30:50:30 | access to parameter o | true | +| Switch.cs:50:18:50:21 | access to type Boolean | Switch.cs:51:17:51:22 | break; | true | | Switch.cs:50:30:50:38 | ... != ... | Switch.cs:51:17:51:22 | break; | true | +| Switch.cs:72:18:72:19 | "" | Switch.cs:73:15:73:20 | break; | true | +| Switch.cs:81:18:81:18 | 1 | Switch.cs:82:22:82:25 | true | true | +| Switch.cs:81:18:81:18 | 1 | Switch.cs:83:13:83:20 | case ...: | false | +| Switch.cs:81:18:81:18 | 1 | Switch.cs:84:15:85:22 | if (...) ... | false | +| Switch.cs:81:18:81:18 | 1 | Switch.cs:85:17:85:22 | break; | false | +| Switch.cs:81:18:81:18 | 1 | Switch.cs:86:22:86:25 | true | false | +| Switch.cs:81:18:81:18 | 1 | Switch.cs:88:16:88:20 | false | false | +| Switch.cs:83:18:83:18 | 2 | Switch.cs:84:15:85:22 | if (...) ... | true | +| Switch.cs:83:18:83:18 | 2 | Switch.cs:85:17:85:22 | break; | true | +| Switch.cs:83:18:83:18 | 2 | Switch.cs:86:22:86:25 | true | true | | Switch.cs:84:19:84:23 | ... > ... | Switch.cs:85:17:85:22 | break; | true | | Switch.cs:84:19:84:23 | ... > ... | Switch.cs:86:22:86:25 | true | false | +| Switch.cs:95:18:95:20 | access to type Int32 | Switch.cs:96:22:96:25 | true | true | +| Switch.cs:95:18:95:20 | access to type Int32 | Switch.cs:98:16:98:20 | false | false | +| Switch.cs:103:17:103:17 | access to parameter s | Switch.cs:103:19:103:25 | access to property Length | false | +| Switch.cs:105:18:105:18 | 0 | Switch.cs:105:29:105:29 | 0 | true | +| Switch.cs:105:18:105:18 | 0 | Switch.cs:106:13:106:20 | case ...: | false | +| Switch.cs:105:18:105:18 | 0 | Switch.cs:106:29:106:29 | 1 | false | +| Switch.cs:105:18:105:18 | 0 | Switch.cs:108:17:108:17 | 1 | false | +| Switch.cs:106:18:106:18 | 1 | Switch.cs:106:29:106:29 | 1 | true | +| Switch.cs:106:18:106:18 | 1 | Switch.cs:108:17:108:17 | 1 | false | +| Switch.cs:117:18:117:18 | 3 | Switch.cs:117:25:117:25 | access to parameter s | true | +| Switch.cs:117:18:117:18 | 3 | Switch.cs:117:43:117:43 | 1 | true | | Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:43:117:43 | 1 | true | +| Switch.cs:118:18:118:18 | 2 | Switch.cs:118:25:118:25 | access to parameter s | true | +| Switch.cs:118:18:118:18 | 2 | Switch.cs:118:42:118:42 | 2 | true | | Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:42:118:42 | 2 | true | | TypeAccesses.cs:7:13:7:22 | ... is ... | TypeAccesses.cs:7:25:7:25 | ; | true | | VarDecls.cs:25:20:25:20 | access to parameter b | VarDecls.cs:25:24:25:24 | access to local variable x | true | @@ -199,6 +358,35 @@ | cflow.cs:28:22:28:31 | ... == ... | cflow.cs:33:17:33:37 | ...; | false | | cflow.cs:30:22:30:31 | ... == ... | cflow.cs:31:17:31:42 | ...; | true | | cflow.cs:30:22:30:31 | ... == ... | cflow.cs:33:17:33:37 | ...; | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:37:17:37:22 | exit Switch | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:44:13:44:19 | case ...: | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:47:13:47:19 | case ...: | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:48:17:48:39 | ...; | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:51:9:59:9 | switch (...) {...} | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:54:17:54:48 | ...; | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:56:13:56:20 | default: | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:60:9:66:9 | switch (...) {...} | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:63:17:64:55 | if (...) ... | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:64:27:64:54 | object creation of type NullReferenceException | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:65:17:65:22 | break; | false | +| cflow.cs:41:18:41:18 | 1 | cflow.cs:67:16:67:16 | access to parameter a | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:37:17:37:22 | exit Switch | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:47:13:47:19 | case ...: | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:48:17:48:39 | ...; | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:51:9:59:9 | switch (...) {...} | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:54:17:54:48 | ...; | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:56:13:56:20 | default: | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:60:9:66:9 | switch (...) {...} | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:63:17:64:55 | if (...) ... | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:64:27:64:54 | object creation of type NullReferenceException | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:65:17:65:22 | break; | false | +| cflow.cs:44:18:44:18 | 2 | cflow.cs:67:16:67:16 | access to parameter a | false | +| cflow.cs:47:18:47:18 | 3 | cflow.cs:48:17:48:39 | ...; | true | +| cflow.cs:53:18:53:19 | 42 | cflow.cs:54:17:54:48 | ...; | true | +| cflow.cs:53:18:53:19 | 42 | cflow.cs:56:13:56:20 | default: | false | +| cflow.cs:62:18:62:18 | 0 | cflow.cs:63:17:64:55 | if (...) ... | true | +| cflow.cs:62:18:62:18 | 0 | cflow.cs:64:27:64:54 | object creation of type NullReferenceException | true | +| cflow.cs:62:18:62:18 | 0 | cflow.cs:65:17:65:22 | break; | true | | cflow.cs:63:23:63:33 | ... == ... | cflow.cs:64:27:64:54 | object creation of type NullReferenceException | false | | cflow.cs:63:23:63:33 | ... == ... | cflow.cs:65:17:65:22 | break; | true | | cflow.cs:72:13:72:21 | ... == ... | cflow.cs:73:13:73:19 | return ...; | true | @@ -225,6 +413,15 @@ | cflow.cs:108:13:108:21 | ... != ... | cflow.cs:116:9:116:29 | ...; | false | | cflow.cs:127:32:127:44 | ... == ... | cflow.cs:127:48:127:49 | "" | true | | cflow.cs:127:32:127:44 | ... == ... | cflow.cs:127:53:127:57 | this access | false | +| cflow.cs:162:9:165:9 | [exception: Exception] catch (...) {...} | cflow.cs:162:38:162:39 | [exception: Exception] IOException ex | true | +| cflow.cs:162:9:165:9 | [exception: Exception] catch (...) {...} | cflow.cs:166:9:176:9 | [exception: Exception] catch (...) {...} | false | +| cflow.cs:162:9:165:9 | [exception: Exception] catch (...) {...} | cflow.cs:166:41:166:42 | [exception: Exception] ArgumentException ex | false | +| cflow.cs:162:9:165:9 | [exception: Exception] catch (...) {...} | cflow.cs:177:9:179:9 | [exception: Exception] catch (...) {...} | false | +| cflow.cs:166:9:176:9 | [exception: Exception] catch (...) {...} | cflow.cs:166:41:166:42 | [exception: Exception] ArgumentException ex | true | +| cflow.cs:166:9:176:9 | [exception: Exception] catch (...) {...} | cflow.cs:177:9:179:9 | [exception: Exception] catch (...) {...} | false | +| cflow.cs:194:9:197:9 | [exception: Exception] catch (...) {...} | cflow.cs:194:38:194:39 | [exception: Exception] IOException ex | true | +| cflow.cs:194:9:197:9 | [exception: Exception] catch (...) {...} | cflow.cs:198:9:200:9 | [exception: Exception] catch (...) {...} | false | +| cflow.cs:194:9:197:9 | [exception: Exception] catch (...) {...} | cflow.cs:202:9:204:9 | [finally: exception(Exception)] {...} | false | | cflow.cs:207:16:207:20 | ... > ... | cflow.cs:208:9:230:9 | {...} | true | | cflow.cs:207:16:207:20 | ... > ... | cflow.cs:212:21:212:27 | return ...; | true | | cflow.cs:207:16:207:20 | ... > ... | cflow.cs:213:17:214:29 | if (...) ... | true | @@ -383,6 +580,10 @@ | cflow.cs:315:17:315:32 | ... > ... | cflow.cs:319:13:322:13 | if (...) ... | false | | cflow.cs:315:17:315:32 | ... > ... | cflow.cs:320:13:322:13 | {...} | false | | cflow.cs:319:17:319:32 | ... < ... | cflow.cs:320:13:322:13 | {...} | true | +| cflow.cs:328:9:339:9 | foreach (... ... in ...) ... | cflow.cs:328:22:328:22 | String x | false | +| cflow.cs:328:9:339:9 | foreach (... ... in ...) ... | cflow.cs:332:13:334:13 | {...} | false | +| cflow.cs:328:9:339:9 | foreach (... ... in ...) ... | cflow.cs:335:13:338:13 | if (...) ... | false | +| cflow.cs:328:9:339:9 | foreach (... ... in ...) ... | cflow.cs:336:13:338:13 | {...} | false | | cflow.cs:331:17:331:32 | ... > ... | cflow.cs:332:13:334:13 | {...} | true | | cflow.cs:331:17:331:32 | ... > ... | cflow.cs:335:13:338:13 | if (...) ... | false | | cflow.cs:331:17:331:32 | ... > ... | cflow.cs:336:13:338:13 | {...} | false | @@ -397,6 +598,15 @@ | cflow.cs:346:13:346:28 | ... > ... | cflow.cs:355:13:355:19 | case ...: | false | | cflow.cs:346:13:346:28 | ... > ... | cflow.cs:356:17:356:27 | goto ...; | false | | cflow.cs:346:13:346:28 | ... > ... | cflow.cs:357:13:357:20 | default: | false | +| cflow.cs:350:18:350:18 | 0 | cflow.cs:351:17:351:29 | goto default; | true | +| cflow.cs:350:18:350:18 | 0 | cflow.cs:352:13:352:19 | case ...: | false | +| cflow.cs:350:18:350:18 | 0 | cflow.cs:353:17:353:37 | ...; | false | +| cflow.cs:350:18:350:18 | 0 | cflow.cs:355:13:355:19 | case ...: | false | +| cflow.cs:350:18:350:18 | 0 | cflow.cs:356:17:356:27 | goto ...; | false | +| cflow.cs:352:18:352:18 | 1 | cflow.cs:353:17:353:37 | ...; | true | +| cflow.cs:352:18:352:18 | 1 | cflow.cs:355:13:355:19 | case ...: | false | +| cflow.cs:352:18:352:18 | 1 | cflow.cs:356:17:356:27 | goto ...; | false | +| cflow.cs:355:18:355:18 | 2 | cflow.cs:356:17:356:27 | goto ...; | true | | cflow.cs:366:25:366:30 | ... < ... | cflow.cs:367:9:369:9 | {...} | true | | cflow.cs:366:25:366:30 | ... < ... | cflow.cs:370:9:378:9 | try {...} ... | false | | cflow.cs:419:46:419:50 | ... > ... | cflow.cs:419:56:419:56 | access to parameter s | false | diff --git a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql index 7f0035c2ea0..c2dc8a9a103 100644 --- a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql +++ b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql @@ -1,5 +1,6 @@ import csharp +import ControlFlow -from ControlFlow::BasicBlocks::ConditionBlock cb, ControlFlow::BasicBlock controlled, boolean testIsTrue -where cb.controls(controlled, testIsTrue) +from BasicBlocks::ConditionBlock cb, BasicBlock controlled, boolean testIsTrue +where cb.controls(controlled,any(SuccessorTypes::ConditionalSuccessor s | testIsTrue = s.getValue())) select cb.getLastNode(), controlled.getFirstNode(), testIsTrue diff --git a/csharp/ql/test/library-tests/controlflow/guards/GuardedExpr.expected b/csharp/ql/test/library-tests/controlflow/guards/GuardedExpr.expected index d476e65b3f6..e20fd4275d0 100644 --- a/csharp/ql/test/library-tests/controlflow/guards/GuardedExpr.expected +++ b/csharp/ql/test/library-tests/controlflow/guards/GuardedExpr.expected @@ -34,3 +34,8 @@ | Guards.cs:106:9:106:9 | access to parameter g | Guards.cs:104:13:104:45 | ... == ... | Guards.cs:104:13:104:13 | access to parameter g | false | | Guards.cs:107:27:107:27 | access to parameter g | Guards.cs:104:13:104:45 | ... == ... | Guards.cs:104:13:104:13 | access to parameter g | false | | Guards.cs:108:27:108:27 | access to parameter g | Guards.cs:104:13:104:45 | ... == ... | Guards.cs:104:13:104:13 | access to parameter g | false | +| Guards.cs:131:20:131:20 | access to parameter s | Guards.cs:130:13:130:21 | ... is ... | Guards.cs:130:13:130:13 | access to parameter s | true | +| Guards.cs:132:16:132:16 | access to parameter s | Guards.cs:130:13:130:21 | ... is ... | Guards.cs:130:13:130:13 | access to parameter s | false | +| Guards.cs:138:20:138:20 | access to parameter s | Guards.cs:137:13:137:23 | ... is ... | Guards.cs:137:13:137:13 | access to parameter s | true | +| Guards.cs:139:16:139:16 | access to parameter s | Guards.cs:137:13:137:23 | ... is ... | Guards.cs:137:13:137:13 | access to parameter s | false | +| Guards.cs:146:16:146:16 | access to parameter o | Guards.cs:144:13:144:25 | ... is ... | Guards.cs:144:13:144:13 | access to parameter o | false | diff --git a/csharp/ql/test/library-tests/controlflow/guards/Guards.cs b/csharp/ql/test/library-tests/controlflow/guards/Guards.cs index a143d8d66a7..7634b26f0cf 100644 --- a/csharp/ql/test/library-tests/controlflow/guards/Guards.cs +++ b/csharp/ql/test/library-tests/controlflow/guards/Guards.cs @@ -124,4 +124,42 @@ public class Guards var b1 = s1.Equals(s2); // not null guarded var b2 = s1?.Equals(s1); // null guarded } + + int M11(string s) + { + if (s is null) + return s.Length; // not null guarded + return s.Length; // null guarded + } + + int M12(string s) + { + if (s is string) + return s.Length; // null guarded + return s.Length; // not null guarded + } + + string M13(object o) + { + if (o is string s) + return s; // not null (but not a guard) + return o.ToString(); // not null guarded + } + + string M14(object o) + { + switch (o) + { + case Action _: + return o.ToString(); // null guarded + case Action a: + return a.ToString(); // not null (but not a guard) + case "": + return o.ToString(); // null guarded + case null: + return o.ToString(); // not null guarded + default: + return o.ToString(); // null guarded + } + } } diff --git a/csharp/ql/test/library-tests/controlflow/guards/MatchingGuardedExpr.expected b/csharp/ql/test/library-tests/controlflow/guards/MatchingGuardedExpr.expected new file mode 100644 index 00000000000..0f2679f1eb9 --- /dev/null +++ b/csharp/ql/test/library-tests/controlflow/guards/MatchingGuardedExpr.expected @@ -0,0 +1,12 @@ +| Guards.cs:154:24:154:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:153:13:153:34 | case Action: | true | +| Guards.cs:158:24:158:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:153:13:153:34 | case Action: | false | +| Guards.cs:158:24:158:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:155:13:155:34 | case Action a: | false | +| Guards.cs:158:24:158:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:157:13:157:20 | case ...: | true | +| Guards.cs:160:24:160:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:153:13:153:34 | case Action: | false | +| Guards.cs:160:24:160:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:155:13:155:34 | case Action a: | false | +| Guards.cs:160:24:160:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:157:13:157:20 | case ...: | false | +| Guards.cs:160:24:160:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:159:13:159:22 | case ...: | true | +| Guards.cs:162:24:162:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:153:13:153:34 | case Action: | false | +| Guards.cs:162:24:162:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:155:13:155:34 | case Action a: | false | +| Guards.cs:162:24:162:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:157:13:157:20 | case ...: | false | +| Guards.cs:162:24:162:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:159:13:159:22 | case ...: | false | diff --git a/csharp/ql/test/library-tests/controlflow/guards/MatchingGuardedExpr.ql b/csharp/ql/test/library-tests/controlflow/guards/MatchingGuardedExpr.ql new file mode 100644 index 00000000000..295df121c81 --- /dev/null +++ b/csharp/ql/test/library-tests/controlflow/guards/MatchingGuardedExpr.ql @@ -0,0 +1,6 @@ +import csharp +import semmle.code.csharp.controlflow.Guards + +from MatchingGuardedExpr mge, Expr e, CaseStmt cs, boolean isMatch +where mge.isGuardedBy(e, cs, isMatch) +select mge, e, cs, isMatch diff --git a/csharp/ql/test/library-tests/controlflow/guards/NullGuardedExpr.expected b/csharp/ql/test/library-tests/controlflow/guards/NullGuardedExpr.expected index 52405a21d07..50d7cd7fec2 100644 --- a/csharp/ql/test/library-tests/controlflow/guards/NullGuardedExpr.expected +++ b/csharp/ql/test/library-tests/controlflow/guards/NullGuardedExpr.expected @@ -23,3 +23,8 @@ | Guards.cs:97:31:97:31 | access to parameter s | | Guards.cs:116:27:116:51 | access to field Field | | Guards.cs:125:29:125:30 | access to parameter s1 | +| Guards.cs:132:16:132:16 | access to parameter s | +| Guards.cs:138:20:138:20 | access to parameter s | +| Guards.cs:154:24:154:24 | access to parameter o | +| Guards.cs:158:24:158:24 | access to parameter o | +| Guards.cs:162:24:162:24 | access to parameter o | diff --git a/csharp/ql/test/library-tests/controlflow/guards/NullnessGuardedExpr.expected b/csharp/ql/test/library-tests/controlflow/guards/NullnessGuardedExpr.expected new file mode 100644 index 00000000000..30c4c9148c6 --- /dev/null +++ b/csharp/ql/test/library-tests/controlflow/guards/NullnessGuardedExpr.expected @@ -0,0 +1,3 @@ +| Guards.cs:114:14:114:38 | access to field Field | Guards.cs:113:21:113:45 | access to field Field | true | +| Guards.cs:116:27:116:51 | access to field Field | Guards.cs:115:17:115:41 | access to field Field | false | +| Guards.cs:125:29:125:30 | access to parameter s1 | Guards.cs:125:18:125:19 | access to parameter s1 | false | diff --git a/csharp/ql/test/library-tests/controlflow/guards/NullnessGuardedExpr.ql b/csharp/ql/test/library-tests/controlflow/guards/NullnessGuardedExpr.ql new file mode 100644 index 00000000000..102ae5c60f0 --- /dev/null +++ b/csharp/ql/test/library-tests/controlflow/guards/NullnessGuardedExpr.ql @@ -0,0 +1,6 @@ +import csharp +import semmle.code.csharp.controlflow.Guards + +from NullnessGuardedExpr nge, Expr e, boolean isNull +where nge.isGuardedBy(e, isNull) +select nge, e, isNull diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs index 06382cb47e6..5f72563406a 100644 --- a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs +++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs @@ -1,18 +1,66 @@ using System; -class Foo +class LocalTest { + // BAD: b is flagged. Object a, b, c; - void f() + void F() { - lock (a) lock (b) { } - lock (b) lock (a) { } - lock (b) lock (a) { } - lock (a) lock (b) { } - lock (b) lock (c) { } - lock (c) lock (b) { } - lock (a) lock (a) { } - lock (a) lock (a) { } + lock (a) lock (b) lock(c) { } + } + + void G() + { + lock (a) lock (c) lock(b) { } + } + + void H() + { + lock (a) lock(c) { } } } + +class GlobalTest +{ + // BAD: b is flagged. + static Object a, b, c; + + void F() + { + lock (a) G(); + } + + void G() + { + lock (b) H(); + lock (c) I(); + } + + void H() + { + lock (c) { } + } + + void I() + { + lock (b) { } + } +} + +class LambdaTest +{ + // BAD: a is flagged. + static Object a, b; + + void F() + { + Action lock_a = () => { lock(a) { } }; + Action lock_b = () => { lock(b) { } }; + + lock(a) lock_b(); + lock(b) lock_a(); + } +} + +// semmle-extractor-options: /r:System.Runtime.Extensions.dll /r:System.Threading.dll /r:System.Threading.Thread.dll diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected index 0fd430ed263..ff738999771 100644 --- a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected +++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected @@ -1,10 +1,4 @@ -| LockOrder.cs:9:18:9:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:10:18:10:29 | lock (...) {...} | here | -| LockOrder.cs:9:18:9:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:11:18:11:29 | lock (...) {...} | here | -| LockOrder.cs:10:18:10:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:9:18:9:29 | lock (...) {...} | here | -| LockOrder.cs:10:18:10:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:12:18:12:29 | lock (...) {...} | here | -| LockOrder.cs:11:18:11:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:9:18:9:29 | lock (...) {...} | here | -| LockOrder.cs:11:18:11:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:12:18:12:29 | lock (...) {...} | here | -| LockOrder.cs:12:18:12:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:10:18:10:29 | lock (...) {...} | here | -| LockOrder.cs:12:18:12:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:11:18:11:29 | lock (...) {...} | here | -| LockOrder.cs:13:18:13:29 | lock (...) {...} | Inconsistent lock sequence. The locks c and b are locked in a different sequence $@. | LockOrder.cs:14:18:14:29 | lock (...) {...} | here | -| LockOrder.cs:14:18:14:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and c are locked in a different sequence $@. | LockOrder.cs:13:18:13:29 | lock (...) {...} | here | +| LockOrder.cs:6:15:6:15 | b | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrder.cs:6:18:6:18 | c | c | LockOrder.cs:10:18:10:37 | lock (...) {...} | b | LockOrder.cs:10:27:10:37 | lock (...) {...} | c | LockOrder.cs:15:18:15:37 | lock (...) {...} | c | LockOrder.cs:15:27:15:37 | lock (...) {...} | b | +| LockOrder.cs:27:22:27:22 | b | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrder.cs:27:25:27:25 | c | c | LockOrder.cs:36:8:36:20 | lock (...) {...} | b | LockOrder.cs:42:9:42:20 | lock (...) {...} | c | LockOrder.cs:37:8:37:20 | lock (...) {...} | c | LockOrder.cs:47:9:47:20 | lock (...) {...} | b | +| LockOrder.cs:54:19:54:19 | a | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrder.cs:54:22:54:22 | b | b | LockOrder.cs:61:9:61:25 | lock (...) {...} | a | LockOrder.cs:59:33:59:43 | lock (...) {...} | b | LockOrder.cs:62:9:62:25 | lock (...) {...} | b | LockOrder.cs:58:33:58:43 | lock (...) {...} | a | +| LockOrderBad.cs:6:29:6:33 | lock1 | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrderBad.cs:7:29:7:33 | lock2 | lock2 | LockOrderBad.cs:11:9:19:9 | lock (...) {...} | lock1 | LockOrderBad.cs:16:13:18:13 | lock (...) {...} | lock2 | LockOrderBad.cs:24:9:32:9 | lock (...) {...} | lock2 | LockOrderBad.cs:29:13:31:13 | lock (...) {...} | lock1 | diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderBad.cs b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderBad.cs new file mode 100644 index 00000000000..a9dd05ac866 --- /dev/null +++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderBad.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading; + +class Deadlock +{ + private readonly Object lock1 = new Object(); + private readonly Object lock2 = new Object(); + + public void thread1() + { + lock (lock1) + { + Console.Out.WriteLine("Thread 1 acquired lock1"); + Thread.Sleep(10); + Console.Out.WriteLine("Thread 1 waiting on lock2"); + lock (lock2) // Deadlock here + { + } + } + } + + public void thread2() + { + lock (lock2) + { + Console.Out.WriteLine("Thread 2 acquired lock2"); + Thread.Sleep(10); + Console.Out.WriteLine("Thread 2 waiting on lock1"); + lock (lock1) // Deadlock here + { + } + } + } +} diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderGood.cs b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderGood.cs new file mode 100644 index 00000000000..1aaeef6a045 --- /dev/null +++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderGood.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading; + +class DeadlockFixed +{ + private readonly Object lock1 = new Object(); + private readonly Object lock2 = new Object(); + + public void thread1() + { + lock (lock1) + { + Console.Out.WriteLine("Thread 1 acquired lock1"); + Thread.Sleep(10); + Console.Out.WriteLine("Thread 1 waiting on lock2"); + lock (lock2) + { + } + } + } + + public void thread2() + { + lock (lock1) // Fixed + { + Console.Out.WriteLine("Thread 2 acquired lock1"); + Thread.Sleep(10); + Console.Out.WriteLine("Thread 2 waiting on lock2"); + lock (lock2) // Fixed + { + } + } + } +} diff --git a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql index 818ba676241..d6c1e205668 100644 --- a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql +++ b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql @@ -167,6 +167,15 @@ predicate overFlowTest(ComparisonExpr comp) { comp.getGreaterOperand().(IntegerLiteral).getIntValue() = 0 } +predicate concurrentModificationTest(BinaryExpr test) { + exists(IfStmt ifstmt, ThrowStmt throw, RefType exc | + ifstmt.getCondition() = test and + (ifstmt.getThen() = throw or ifstmt.getThen().(SingletonBlock).getStmt() = throw) and + throw.getExpr().(ClassInstanceExpr).getConstructedType() = exc and + exc.hasQualifiedName("java.util", "ConcurrentModificationException") + ) +} + /** * Holds if `test` and `guard` are equality tests of the same integral variable v with constants `c1` and `c2`. */ @@ -202,13 +211,13 @@ where ) else if constCondSimple(test, _) - then ( - constCondSimple(test, testIsTrue) and reason = "" and reasonElem = test // dummy reason element - ) else + then constCondSimple(test, testIsTrue) and reason = "" and reasonElem = test // dummy reason element + else exists(CondReason r | constCond(test, testIsTrue, r) and reason = ", because of $@" and reasonElem = r.getCond() ) ) and not overFlowTest(test) and + not concurrentModificationTest(test) and not exists(AssertStmt assert | assert.getExpr() = test.getParent*()) select test, "Test is always " + testIsTrue + reason + ".", reasonElem, "this condition" diff --git a/java/ql/src/filters/ImportAdditionalLibraries.ql b/java/ql/src/filters/ImportAdditionalLibraries.ql index 0774fe8894e..5d559fdd900 100644 --- a/java/ql/src/filters/ImportAdditionalLibraries.ql +++ b/java/ql/src/filters/ImportAdditionalLibraries.ql @@ -9,6 +9,7 @@ import java import semmle.code.java.dataflow.Guards +import semmle.code.java.dataflow.ParityAnalysis import semmle.code.java.security.DataFlow from File f, string tag diff --git a/java/ql/src/semmle/code/java/dataflow/Bound.qll b/java/ql/src/semmle/code/java/dataflow/Bound.qll new file mode 100644 index 00000000000..1742e1dc4fe --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/Bound.qll @@ -0,0 +1,68 @@ +import java +private import SSA +private import RangeUtils + +private newtype TBound = + TBoundZero() or + TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or + TBoundExpr(Expr e) { + e.(FieldRead).getField() instanceof ArrayLengthField and + not exists(SsaVariable v | e = v.getAUse()) + } + +/** + * A bound that may be inferred for an expression plus/minus an integer delta. + */ +abstract class Bound extends TBound { + abstract string toString(); + + /** Gets an expression that equals this bound plus `delta`. */ + abstract Expr getExpr(int delta); + + /** Gets an expression that equals this bound. */ + Expr getExpr() { result = getExpr(0) } + + predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0 + } +} + +/** + * The bound that corresponds to the integer 0. This is used to represent all + * integer bounds as bounds are always accompanied by an added integer delta. + */ +class ZeroBound extends Bound, TBoundZero { + override string toString() { result = "0" } + + override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta } +} + +/** + * A bound corresponding to the value of an SSA variable. + */ +class SsaBound extends Bound, TBoundSsa { + /** Gets the SSA variable that equals this bound. */ + SsaVariable getSsa() { this = TBoundSsa(result) } + + override string toString() { result = getSsa().toString() } + + override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 } + + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec) + } +} + +/** + * A bound that corresponds to the value of a specific expression that might be + * interesting, but isn't otherwise represented by the value of an SSA variable. + */ +class ExprBound extends Bound, TBoundExpr { + override string toString() { result = getExpr().toString() } + + override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 } + + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + getExpr().hasLocationInfo(path, sl, sc, el, ec) + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll new file mode 100644 index 00000000000..e0e3b7ea4ba --- /dev/null +++ b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll @@ -0,0 +1,352 @@ +/** + * Provides inferences of the form: `e` equals `b + v` modulo `m` where `e` is + * an expression, `b` is a `Bound` (typically zero or the value of an SSA + * variable), and `v` is an integer in the range `[0 .. m-1]`. + */ + +import java +private import SSA +private import RangeUtils +private import semmle.code.java.controlflow.Guards +import Bound + +/** + * Holds if `e + delta` equals `v` at `pos`. + */ +private predicate valueFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta) { + ssaUpdateStep(v, e, delta) and pos.hasReadOfVar(v) + or + exists(Guard guard, boolean testIsTrue | + pos.hasReadOfVar(v) and + guard = eqFlowCond(v, e, delta, true, testIsTrue) and + guardDirectlyControlsSsaRead(guard, pos, testIsTrue) + ) +} + +/** + * Holds if `add` is the addition of `larg` and `rarg`, neither of which are + * `ConstantIntegerExpr`s. + */ +private predicate nonConstAddition(Expr add, Expr larg, Expr rarg) { + ( + exists(AddExpr a | a = add | + larg = a.getLeftOperand() and + rarg = a.getRightOperand() + ) + or + exists(AssignAddExpr a | a = add | + larg = a.getDest() and + rarg = a.getRhs() + ) + ) and + not larg instanceof ConstantIntegerExpr and + not rarg instanceof ConstantIntegerExpr +} + +/** + * Holds if `sub` is the subtraction of `larg` and `rarg`, where `rarg` is not + * a `ConstantIntegerExpr`. + */ +private predicate nonConstSubtraction(Expr sub, Expr larg, Expr rarg) { + ( + exists(SubExpr s | s = sub | + larg = s.getLeftOperand() and + rarg = s.getRightOperand() + ) + or + exists(AssignSubExpr s | s = sub | + larg = s.getDest() and + rarg = s.getRhs() + ) + ) and + not rarg instanceof ConstantIntegerExpr +} + +/** Gets an expression that is the remainder modulo `mod` of `arg`. */ +private Expr modExpr(Expr arg, int mod) { + exists(RemExpr rem | + result = rem and + arg = rem.getLeftOperand() and + rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = mod and + mod >= 2 + ) + or + exists(CompileTimeConstantExpr c | + mod = 2.pow([1 .. 30]) and + c.getIntValue() = mod - 1 and + result.(AndBitwiseExpr).hasOperands(arg, c) + ) + or + result.(ParExpr).getExpr() = modExpr(arg, mod) +} + +/** + * Gets a guard that tests whether `v` is congruent with `val` modulo `mod` on + * its `testIsTrue` branch. + */ +private Guard moduloCheck(SsaVariable v, int val, int mod, boolean testIsTrue) { + exists(Expr rem, CompileTimeConstantExpr c, int r, boolean polarity | + result.isEquality(rem, c, polarity) and + c.getIntValue() = r and + rem = modExpr(v.getAUse(), mod) and + ( + testIsTrue = polarity and val = r + or + testIsTrue = polarity.booleanNot() and + mod = 2 and + val = 1 - r and + (r = 0 or r = 1) + ) + ) +} + +/** + * Holds if a guard ensures that `v` at `pos` is congruent with `val` modulo `mod`. + */ +private predicate moduloGuardedRead(SsaVariable v, SsaReadPosition pos, int val, int mod) { + exists(Guard guard, boolean testIsTrue | + pos.hasReadOfVar(v) and + guard = moduloCheck(v, val, mod, testIsTrue) and + guardControlsSsaRead(guard, pos, testIsTrue) + ) +} + +/** Holds if `factor` is a power of 2 that divides `mask`. */ +bindingset[mask] +private predicate andmaskFactor(int mask, int factor) { + mask % factor = 0 and + factor = 2.pow([1 .. 30]) +} + +/** Holds if `e` is evenly divisible by `factor`. */ +private predicate evenlyDivisibleExpr(Expr e, int factor) { + exists(ConstantIntegerExpr c, int k | k = c.getIntValue() | + e.(MulExpr).getAnOperand() = c and factor = k.abs() and factor >= 2 + or + e.(AssignMulExpr).getSource() = c and factor = k.abs() and factor >= 2 + or + e.(LShiftExpr).getRightOperand() = c and factor = 2.pow(k) and k > 0 + or + e.(AssignLShiftExpr).getRhs() = c and factor = 2.pow(k) and k > 0 + or + e.(AndBitwiseExpr).getAnOperand() = c and factor = max(int f | andmaskFactor(k, f)) + or + e.(AssignAndExpr).getSource() = c and factor = max(int f | andmaskFactor(k, f)) + ) +} + +private predicate id(BasicBlock x, BasicBlock y) { x = y } + +private predicate idOf(BasicBlock x, int y) = equivalenceRelation(id/2)(x, y) + +private int getId(BasicBlock bb) { idOf(bb, result) } + +/** + * Holds if `inp` is an input to `phi` along `edge` and this input has index `r` + * in an arbitrary 1-based numbering of the input edges to `phi`. + */ +private predicate rankedPhiInput( + SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int r +) { + edge.phiInput(phi, inp) and + edge = rank[r](SsaReadPositionPhiInputEdge e | + e.phiInput(phi, _) + | + e + order by + getId(e.getOrigBlock()) + ) +} + +/** + * Holds if `rix` is the number of input edges to `phi`. + */ +private predicate maxPhiInputRank(SsaPhiNode phi, int rix) { + rix = max(int r | rankedPhiInput(phi, _, _, r)) +} + +private int gcdLim() { result = 128 } + +/** + * Gets the greatest common divisor of `x` and `y`. This is restricted to small + * inputs and the case when `x` and `y` are not relatively prime. + */ +private int gcd(int x, int y) { + result != 1 and + result = x.abs() and + y = 0 and + x in [-gcdLim() .. gcdLim()] + or + result = gcd(y, x % y) and y != 0 and x in [-gcdLim() .. gcdLim()] +} + +/** + * Gets the remainder of `val` modulo `mod`. + * + * For `mod = 0` the result equals `val` and for `mod > 1` the result is within + * the range `[0 .. mod-1]`. + */ +bindingset[val, mod] +private int remainder(int val, int mod) { + mod = 0 and result = val + or + mod > 1 and result = ((val % mod) + mod) % mod +} + +/** + * Holds if `inp` is an input to `phi` and equals `phi` modulo `mod` along `edge`. + */ +private predicate phiSelfModulus( + SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int mod +) { + exists(SsaBound phibound, int v, int m | + edge.phiInput(phi, inp) and + phibound.getSsa() = phi and + ssaModulus(inp, edge, phibound, v, m) and + mod = gcd(m, v) and + mod != 1 + ) +} + +/** + * Holds if `b + val` modulo `mod` is a candidate congruence class for `phi`. + */ +private predicate phiModulusInit(SsaPhiNode phi, Bound b, int val, int mod) { + exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge | + edge.phiInput(phi, inp) and + ssaModulus(inp, edge, b, val, mod) + ) +} + +/** + * Holds if all inputs to `phi` numbered `1` to `rix` are equal to `b + val` modulo `mod`. + */ +private predicate phiModulusRankStep(SsaPhiNode phi, Bound b, int val, int mod, int rix) { + rix = 0 and + phiModulusInit(phi, b, val, mod) + or + exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge, int v1, int m1 | + mod != 1 and + val = remainder(v1, mod) + | + exists(int v2, int m2 | + rankedPhiInput(phi, inp, edge, rix) and + phiModulusRankStep(phi, b, v1, m1, rix - 1) and + ssaModulus(inp, edge, b, v2, m2) and + mod = gcd(gcd(m1, m2), v1 - v2) + ) + or + exists(int m2 | + rankedPhiInput(phi, inp, edge, rix) and + phiModulusRankStep(phi, b, v1, m1, rix - 1) and + phiSelfModulus(phi, inp, edge, m2) and + mod = gcd(m1, m2) + ) + ) +} + +/** + * Holds if `phi` is equal to `b + val` modulo `mod`. + */ +private predicate phiModulus(SsaPhiNode phi, Bound b, int val, int mod) { + exists(int r | + maxPhiInputRank(phi, r) and + phiModulusRankStep(phi, b, val, mod, r) + ) +} + +/** + * Holds if `v` at `pos` is equal to `b + val` modulo `mod`. + */ +private predicate ssaModulus(SsaVariable v, SsaReadPosition pos, Bound b, int val, int mod) { + phiModulus(v, b, val, mod) and pos.hasReadOfVar(v) + or + b.(SsaBound).getSsa() = v and pos.hasReadOfVar(v) and val = 0 and mod = 0 + or + exists(Expr e, int val0, int delta | + exprModulus(e, b, val0, mod) and + valueFlowStepSsa(v, pos, e, delta) and + val = remainder(val0 + delta, mod) + ) + or + moduloGuardedRead(v, pos, val, mod) and b instanceof ZeroBound +} + +/** + * Holds if `e` is equal to `b + val` modulo `mod`. + * + * There are two cases for the modulus: + * - `mod = 0`: The equality `e = b + val` is an ordinary equality. + * - `mod > 1`: `val` lies within the range `[0 .. mod-1]`. + */ +cached +predicate exprModulus(Expr e, Bound b, int val, int mod) { + e = b.getExpr(val) and mod = 0 + or + evenlyDivisibleExpr(e, mod) and val = 0 and b instanceof ZeroBound + or + exists(SsaVariable v, SsaReadPositionBlock bb | + ssaModulus(v, bb, b, val, mod) and + e = v.getAUse() and + bb.getBlock() = e.getBasicBlock() + ) + or + exists(Expr mid, int val0, int delta | + exprModulus(mid, b, val0, mod) and + valueFlowStep(e, mid, delta) and + val = remainder(val0 + delta, mod) + ) + or + exists(ConditionalExpr cond, int v1, int v2, int m1, int m2 | + cond = e and + condExprBranchModulus(cond, true, b, v1, m1) and + condExprBranchModulus(cond, false, b, v2, m2) and + mod = gcd(gcd(m1, m2), v1 - v2) and + mod != 1 and + val = remainder(v1, mod) + ) + or + exists(Bound b1, Bound b2, int v1, int v2, int m1, int m2 | + addModulus(e, true, b1, v1, m1) and + addModulus(e, false, b2, v2, m2) and + mod = gcd(m1, m2) and + mod != 1 and + val = remainder(v1 + v2, mod) + | + b = b1 and b2 instanceof ZeroBound + or + b = b2 and b1 instanceof ZeroBound + ) + or + exists(int v1, int v2, int m1, int m2 | + subModulus(e, true, b, v1, m1) and + subModulus(e, false, any(ZeroBound zb), v2, m2) and + mod = gcd(m1, m2) and + mod != 1 and + val = remainder(v1 - v2, mod) + ) +} + +private predicate condExprBranchModulus( + ConditionalExpr cond, boolean branch, Bound b, int val, int mod +) { + exprModulus(cond.getTrueExpr(), b, val, mod) and branch = true + or + exprModulus(cond.getFalseExpr(), b, val, mod) and branch = false +} + +private predicate addModulus(Expr add, boolean isLeft, Bound b, int val, int mod) { + exists(Expr larg, Expr rarg | nonConstAddition(add, larg, rarg) | + exprModulus(larg, b, val, mod) and isLeft = true + or + exprModulus(rarg, b, val, mod) and isLeft = false + ) +} + +private predicate subModulus(Expr sub, boolean isLeft, Bound b, int val, int mod) { + exists(Expr larg, Expr rarg | nonConstSubtraction(sub, larg, rarg) | + exprModulus(larg, b, val, mod) and isLeft = true + or + exprModulus(rarg, b, val, mod) and isLeft = false + ) +} diff --git a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll index 152f240914d..31b7766f0bd 100644 --- a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll +++ b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll @@ -1,9 +1,12 @@ /** + * DEPRECATED: Use semmle.code.java.dataflow.ModulusAnalysis instead. + * * Parity Analysis. * * The analysis is implemented as an abstract interpretation over the * two-valued domain `{even, odd}`. */ + import java private import SSA private import RangeUtils @@ -12,35 +15,35 @@ private import SignAnalysis private import semmle.code.java.Reflection /** Gets an expression that is the remainder modulo 2 of `arg`. */ -private Expr mod2(Expr arg) { +deprecated private Expr mod2(Expr arg) { exists(RemExpr rem | result = rem and arg = rem.getLeftOperand() and rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = 2 - ) or - result.(AndBitwiseExpr).hasOperands(arg, any(CompileTimeConstantExpr c | c.getIntValue() = 1)) or + ) + or + result.(AndBitwiseExpr).hasOperands(arg, any(CompileTimeConstantExpr c | c.getIntValue() = 1)) + or result.(ParExpr).getExpr() = mod2(arg) } /** An expression that calculates remainder modulo 2. */ -private class Mod2 extends Expr { - Mod2() { - this = mod2(_) - } +deprecated private class Mod2 extends Expr { + Mod2() { this = mod2(_) } /** Gets the argument of this remainder operation. */ - Expr getArg() { - this = mod2(result) - } + Expr getArg() { this = mod2(result) } } /** * Parity represented as booleans. Even corresponds to `false` and odd * corresponds to `true`. */ -class Parity extends boolean { +deprecated class Parity extends boolean { Parity() { this = true or this = false } + predicate isEven() { this = false } + predicate isOdd() { this = true } } @@ -48,7 +51,7 @@ class Parity extends boolean { * Gets a condition that performs a parity check on `v`, such that `v` has * the given parity if the condition evaluates to `testIsTrue`. */ -private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) { +deprecated private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) { exists(Mod2 rem, CompileTimeConstantExpr c, int r, boolean polarity | result.isEquality(rem, c, polarity) and c.getIntValue() = r and @@ -56,7 +59,8 @@ private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) { rem.getArg() = v.getAUse() and (testIsTrue = true or testIsTrue = false) and ( - r = 0 and parity = testIsTrue.booleanXor(polarity) or + r = 0 and parity = testIsTrue.booleanXor(polarity) + or r = 1 and parity = testIsTrue.booleanXor(polarity).booleanNot() ) ) @@ -65,25 +69,36 @@ private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) { /** * Gets the parity of `e` if it can be directly determined. */ -private Parity certainExprParity(Expr e) { +deprecated private Parity certainExprParity(Expr e) { exists(int i | e.(ConstantIntegerExpr).getIntValue() = i | if i % 2 = 0 then result.isEven() else result.isOdd() - ) or - e.(LongLiteral).getValue().regexpMatch(".*[02468]") and result.isEven() or - e.(LongLiteral).getValue().regexpMatch(".*[13579]") and result.isOdd() or + ) + or + e.(LongLiteral).getValue().regexpMatch(".*[02468]") and result.isEven() + or + e.(LongLiteral).getValue().regexpMatch(".*[13579]") and result.isOdd() + or not exists(e.(ConstantIntegerExpr).getIntValue()) and ( - result = certainExprParity(e.(ParExpr).getExpr()) or + result = certainExprParity(e.(ParExpr).getExpr()) + or exists(Guard guard, SsaVariable v, boolean testIsTrue | guard = parityCheck(v, result, testIsTrue) and e = v.getAUse() and guardControls_v2(guard, e.getBasicBlock(), testIsTrue) - ) or + ) + or exists(SsaVariable arr, int arrlen, FieldAccess len | e = len and len.getField() instanceof ArrayLengthField and len.getQualifier() = arr.getAUse() and - arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getFirstDimensionSize() = arrlen and + arr + .(SsaExplicitUpdate) + .getDefiningExpr() + .(VariableAssign) + .getSource() + .(ArrayCreationExpr) + .getFirstDimensionSize() = arrlen and if arrlen % 2 = 0 then result.isEven() else result.isOdd() ) ) @@ -92,114 +107,211 @@ private Parity certainExprParity(Expr e) { /** * Gets the expression that defines the array length that equals `len`, if any. */ -private Expr arrLenDef(FieldAccess len) { +deprecated private Expr arrLenDef(FieldAccess len) { exists(SsaVariable arr | len.getField() instanceof ArrayLengthField and len.getQualifier() = arr.getAUse() and - arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = result + arr + .(SsaExplicitUpdate) + .getDefiningExpr() + .(VariableAssign) + .getSource() + .(ArrayCreationExpr) + .getDimension(0) = result ) } /** Gets a possible parity for `v`. */ -private Parity ssaParity(SsaVariable v) { +deprecated private Parity ssaParity(SsaVariable v) { exists(VariableUpdate def | def = v.(SsaExplicitUpdate).getDefiningExpr() | - result = exprParity(def.(VariableAssign).getSource()) or - exists(EnhancedForStmt for | def = for.getVariable()) and (result = true or result = false) or - result = exprParity(def.(UnaryAssignExpr).getExpr()).booleanNot() or + result = exprParity(def.(VariableAssign).getSource()) + or + exists(EnhancedForStmt for | def = for.getVariable()) and + (result = true or result = false) + or + result = exprParity(def.(UnaryAssignExpr).getExpr()).booleanNot() + or exists(AssignOp a | a = def and result = exprParity(a)) - ) or - result = fieldParity(v.(SsaImplicitUpdate).getSourceVariable().getVariable()) or - result = fieldParity(v.(SsaImplicitInit).getSourceVariable().getVariable()) or - exists(Parameter p | v.(SsaImplicitInit).isParameterDefinition(p) and (result = true or result = false)) or + ) + or + result = fieldParity(v.(SsaImplicitUpdate).getSourceVariable().getVariable()) + or + result = fieldParity(v.(SsaImplicitInit).getSourceVariable().getVariable()) + or + exists(Parameter p | + v.(SsaImplicitInit).isParameterDefinition(p) and + (result = true or result = false) + ) + or result = ssaParity(v.(SsaPhiNode).getAPhiInput()) } /** Gets a possible parity for `f`. */ -private Parity fieldParity(Field f) { - result = exprParity(f.getAnAssignedValue()) or - exists(UnaryAssignExpr u | u.getExpr() = f.getAnAccess() and (result = true or result = false)) or - exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprParity(a)) or - exists(ReflectiveFieldAccess rfa | rfa.inferAccessedField() = f and (result = true or result = false)) +deprecated private Parity fieldParity(Field f) { + result = exprParity(f.getAnAssignedValue()) or - if f.fromSource() then - not exists(f.getInitializer()) and result.isEven() - else + exists(UnaryAssignExpr u | + u.getExpr() = f.getAnAccess() and (result = true or result = false) + ) + or + exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprParity(a)) + or + exists(ReflectiveFieldAccess rfa | + rfa.inferAccessedField() = f and + (result = true or result = false) + ) + or + if f.fromSource() + then not exists(f.getInitializer()) and result.isEven() + else ( + result = true or result = false + ) } /** Holds if the parity of `e` is too complicated to determine. */ -private predicate unknownParity(Expr e) { - e instanceof AssignDivExpr or - e instanceof AssignRShiftExpr or - e instanceof AssignURShiftExpr or - e instanceof DivExpr or - e instanceof RShiftExpr or - e instanceof URShiftExpr or - exists(Type fromtyp | e.(CastExpr).getExpr().getType() = fromtyp and not fromtyp instanceof IntegralType) or - e instanceof ArrayAccess and e.getType() instanceof IntegralType or - e instanceof MethodAccess and e.getType() instanceof IntegralType or - e instanceof ClassInstanceExpr and e.getType() instanceof IntegralType or - e.getType() instanceof FloatingPointType or +deprecated private predicate unknownParity(Expr e) { + e instanceof AssignDivExpr + or + e instanceof AssignRShiftExpr + or + e instanceof AssignURShiftExpr + or + e instanceof DivExpr + or + e instanceof RShiftExpr + or + e instanceof URShiftExpr + or + exists(Type fromtyp | + e.(CastExpr).getExpr().getType() = fromtyp and not fromtyp instanceof IntegralType + ) + or + e instanceof ArrayAccess and e.getType() instanceof IntegralType + or + e instanceof MethodAccess and e.getType() instanceof IntegralType + or + e instanceof ClassInstanceExpr and e.getType() instanceof IntegralType + or + e.getType() instanceof FloatingPointType + or e.getType() instanceof CharacterType } /** Gets a possible parity for `e`. */ -private Parity exprParity(Expr e) { - result = certainExprParity(e) or +deprecated private Parity exprParity(Expr e) { + result = certainExprParity(e) + or not exists(certainExprParity(e)) and ( - result = exprParity(e.(ParExpr).getExpr()) or - result = exprParity(arrLenDef(e)) or - exists(SsaVariable v | v.getAUse() = e | result = ssaParity(v)) and not exists(arrLenDef(e)) or + result = exprParity(e.(ParExpr).getExpr()) + or + result = exprParity(arrLenDef(e)) + or + exists(SsaVariable v | v.getAUse() = e | result = ssaParity(v)) and + not exists(arrLenDef(e)) + or exists(FieldAccess fa | fa = e | not exists(SsaVariable v | v.getAUse() = fa) and not exists(arrLenDef(e)) and result = fieldParity(fa.getField()) - ) or + ) + or exists(VarAccess va | va = e | not exists(SsaVariable v | v.getAUse() = va) and not va instanceof FieldAccess and (result = true or result = false) - ) or - result = exprParity(e.(AssignExpr).getSource()) or - result = exprParity(e.(PlusExpr).getExpr()) or - result = exprParity(e.(PostIncExpr).getExpr()) or - result = exprParity(e.(PostDecExpr).getExpr()) or - result = exprParity(e.(PreIncExpr).getExpr()).booleanNot() or - result = exprParity(e.(PreDecExpr).getExpr()).booleanNot() or - result = exprParity(e.(MinusExpr).getExpr()) or - result = exprParity(e.(BitNotExpr).getExpr()).booleanNot() or - unknownParity(e) and (result = true or result = false) or + ) + or + result = exprParity(e.(AssignExpr).getSource()) + or + result = exprParity(e.(PlusExpr).getExpr()) + or + result = exprParity(e.(PostIncExpr).getExpr()) + or + result = exprParity(e.(PostDecExpr).getExpr()) + or + result = exprParity(e.(PreIncExpr).getExpr()).booleanNot() + or + result = exprParity(e.(PreDecExpr).getExpr()).booleanNot() + or + result = exprParity(e.(MinusExpr).getExpr()) + or + result = exprParity(e.(BitNotExpr).getExpr()).booleanNot() + or + unknownParity(e) and + (result = true or result = false) + or exists(Parity p1, Parity p2, AssignOp a | a = e and p1 = exprParity(a.getDest()) and p2 = exprParity(a.getRhs()) - | - a instanceof AssignAddExpr and result = p1.booleanXor(p2) or - a instanceof AssignSubExpr and result = p1.booleanXor(p2) or - a instanceof AssignMulExpr and result = p1.booleanAnd(p2) or - a instanceof AssignRemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or - a instanceof AssignAndExpr and result = p1.booleanAnd(p2) or - a instanceof AssignOrExpr and result = p1.booleanOr(p2) or - a instanceof AssignXorExpr and result = p1.booleanXor(p2) or - a instanceof AssignLShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(a.getRhs())) - ) or + | + a instanceof AssignAddExpr and result = p1.booleanXor(p2) + or + a instanceof AssignSubExpr and result = p1.booleanXor(p2) + or + a instanceof AssignMulExpr and result = p1.booleanAnd(p2) + or + a instanceof AssignRemExpr and + ( + p2.isEven() and result = p1 + or + p2.isOdd() and + (result = true or result = false) + ) + or + a instanceof AssignAndExpr and result = p1.booleanAnd(p2) + or + a instanceof AssignOrExpr and result = p1.booleanOr(p2) + or + a instanceof AssignXorExpr and result = p1.booleanXor(p2) + or + a instanceof AssignLShiftExpr and + ( + result.isEven() + or + result = p1 and not strictlyPositive(a.getRhs()) + ) + ) + or exists(Parity p1, Parity p2, BinaryExpr bin | bin = e and p1 = exprParity(bin.getLeftOperand()) and p2 = exprParity(bin.getRightOperand()) - | - bin instanceof AddExpr and result = p1.booleanXor(p2) or - bin instanceof SubExpr and result = p1.booleanXor(p2) or - bin instanceof MulExpr and result = p1.booleanAnd(p2) or - bin instanceof RemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or - bin instanceof AndBitwiseExpr and result = p1.booleanAnd(p2) or - bin instanceof OrBitwiseExpr and result = p1.booleanOr(p2) or - bin instanceof XorBitwiseExpr and result = p1.booleanXor(p2) or - bin instanceof LShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(bin.getRightOperand())) - ) or - result = exprParity(e.(ConditionalExpr).getTrueExpr()) or - result = exprParity(e.(ConditionalExpr).getFalseExpr()) or + | + bin instanceof AddExpr and result = p1.booleanXor(p2) + or + bin instanceof SubExpr and result = p1.booleanXor(p2) + or + bin instanceof MulExpr and result = p1.booleanAnd(p2) + or + bin instanceof RemExpr and + ( + p2.isEven() and result = p1 + or + p2.isOdd() and + (result = true or result = false) + ) + or + bin instanceof AndBitwiseExpr and result = p1.booleanAnd(p2) + or + bin instanceof OrBitwiseExpr and result = p1.booleanOr(p2) + or + bin instanceof XorBitwiseExpr and result = p1.booleanXor(p2) + or + bin instanceof LShiftExpr and + ( + result.isEven() + or + result = p1 and not strictlyPositive(bin.getRightOperand()) + ) + ) + or + result = exprParity(e.(ConditionalExpr).getTrueExpr()) + or + result = exprParity(e.(ConditionalExpr).getFalseExpr()) + or result = exprParity(e.(CastExpr).getExpr()) ) } @@ -207,15 +319,15 @@ private Parity exprParity(Expr e) { /** * Gets the parity of `e` if it can be uniquely determined. */ -Parity getExprParity(Expr e) { - result = exprParity(e) and 1 = count(exprParity(e)) -} +deprecated Parity getExprParity(Expr e) { result = exprParity(e) and 1 = count(exprParity(e)) } /** + * DEPRECATED: Use semmle.code.java.dataflow.ModulusAnalysis instead. + * * Holds if the parity can be determined for both sides of `comp`. The boolean * `eqparity` indicates whether the two sides have equal or opposite parity. */ -predicate parityComparison(ComparisonExpr comp, boolean eqparity) { +deprecated predicate parityComparison(ComparisonExpr comp, boolean eqparity) { exists(Expr left, Expr right, boolean lpar, boolean rpar | comp.getLeftOperand() = left and comp.getRightOperand() = right and @@ -224,4 +336,3 @@ predicate parityComparison(ComparisonExpr comp, boolean eqparity) { eqparity = lpar.booleanXor(rpar).booleanNot() ) } - diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll index d6d396e8c6a..066bd0fa925 100644 --- a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll +++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll @@ -68,14 +68,16 @@ private import SSA private import RangeUtils private import semmle.code.java.controlflow.internal.GuardsLogic private import SignAnalysis -private import ParityAnalysis +private import ModulusAnalysis private import semmle.code.java.Reflection private import semmle.code.java.Collections private import semmle.code.java.Maps +import Bound -cached private module RangeAnalysisCache { - - cached module RangeAnalysisPublic { +cached +private module RangeAnalysisCache { + cached + module RangeAnalysisPublic { /** * Holds if `b + delta` is a valid bound for `e`. * - `upper = true` : `e <= b + delta` @@ -85,7 +87,8 @@ cached private module RangeAnalysisCache { * or `NoReason` if the bound was proven directly without the use of a bounding * condition. */ - cached predicate bounded(Expr e, Bound b, int delta, boolean upper, Reason reason) { + cached + predicate bounded(Expr e, Bound b, int delta, boolean upper, Reason reason) { bounded(e, b, delta, upper, _, _, reason) } } @@ -93,59 +96,116 @@ cached private module RangeAnalysisCache { /** * Holds if `guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)`. */ - cached predicate possibleReason(Guard guard) { guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _) } - + cached + predicate possibleReason(Guard guard) { + guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _) + } } private import RangeAnalysisCache import RangeAnalysisPublic -/** - * Gets a condition that tests whether `v` equals `e + delta`. - * - * If the condition evaluates to `testIsTrue`: - * - `isEq = true` : `v == e + delta` - * - `isEq = false` : `v != e + delta` - */ -private Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsTrue) { - exists(boolean eqpolarity | - result.isEquality(ssaRead(v, delta), e, eqpolarity) and - (testIsTrue = true or testIsTrue = false) and - eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq - ) - or - exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0)) -} - /** * Holds if `comp` corresponds to: * - `upper = true` : `v <= e + delta` or `v < e + delta` * - `upper = false` : `v >= e + delta` or `v > e + delta` */ -private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int delta, boolean upper) { +private predicate boundCondition( + ComparisonExpr comp, SsaVariable v, Expr e, int delta, boolean upper +) { comp.getLesserOperand() = ssaRead(v, delta) and e = comp.getGreaterOperand() and upper = true or comp.getGreaterOperand() = ssaRead(v, delta) and e = comp.getLesserOperand() and upper = false or exists(SubExpr sub, ConstantIntegerExpr c, int d | // (v - d) - e < c - comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and - sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and - upper = true and delta = d + c.getIntValue() + comp.getLesserOperand().getProperExpr() = sub and + comp.getGreaterOperand() = c and + sub.getLeftOperand() = ssaRead(v, d) and + sub.getRightOperand() = e and + upper = true and + delta = d + c.getIntValue() or // (v - d) - e > c - comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and - sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and - upper = false and delta = d + c.getIntValue() + comp.getGreaterOperand().getProperExpr() = sub and + comp.getLesserOperand() = c and + sub.getLeftOperand() = ssaRead(v, d) and + sub.getRightOperand() = e and + upper = false and + delta = d + c.getIntValue() or // e - (v - d) < c - comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and - sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and - upper = false and delta = d - c.getIntValue() + comp.getLesserOperand().getProperExpr() = sub and + comp.getGreaterOperand() = c and + sub.getLeftOperand() = e and + sub.getRightOperand() = ssaRead(v, d) and + upper = false and + delta = d - c.getIntValue() or // e - (v - d) > c - comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and - sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and - upper = true and delta = d - c.getIntValue() + comp.getGreaterOperand().getProperExpr() = sub and + comp.getLesserOperand() = c and + sub.getLeftOperand() = e and + sub.getRightOperand() = ssaRead(v, d) and + upper = true and + delta = d - c.getIntValue() + ) +} + +private predicate gcdInput(int x, int y) { + exists(ComparisonExpr comp, Bound b | + exprModulus(comp.getLesserOperand(), b, _, x) and + exprModulus(comp.getGreaterOperand(), b, _, y) + ) + or + exists(int x0, int y0 | + gcdInput(x0, y0) and + x = y0 and + y = x0 % y0 + ) +} + +private int gcd(int x, int y) { + result = x.abs() and y = 0 and gcdInput(x, y) + or + result = gcd(y, x % y) and y != 0 and gcdInput(x, y) +} + +/** + * Holds if `comp` is a comparison between `x` and `y` for which `y - x` has a + * fixed value modulo some `mod > 1`, such that the comparison can be + * strengthened by `strengthen` when evaluating to `testIsTrue`. + */ +private predicate modulusComparison(ComparisonExpr comp, boolean testIsTrue, int strengthen) { + exists( + Bound b, int v1, int v2, int mod1, int mod2, int mod, boolean resultIsStrict, int d, int k + | + // If `x <= y` and `x =(mod) b + v1` and `y =(mod) b + v2` then + // `0 <= y - x =(mod) v2 - v1`. By choosing `k =(mod) v2 - v1` with + // `0 <= k < mod` we get `k <= y - x`. If the resulting comparison is + // strict then the strengthening amount is instead `k - 1` modulo `mod`: + // `x < y` means `0 <= y - x - 1 =(mod) k - 1` so `k - 1 <= y - x - 1` and + // thus `k - 1 < y - x` with `0 <= k - 1 < mod`. + exprModulus(comp.getLesserOperand(), b, v1, mod1) and + exprModulus(comp.getGreaterOperand(), b, v2, mod2) and + mod = gcd(mod1, mod2) and + mod != 1 and + (testIsTrue = true or testIsTrue = false) and + ( + if comp.isStrict() + then resultIsStrict = testIsTrue + else resultIsStrict = testIsTrue.booleanNot() + ) and + ( + resultIsStrict = true and d = 1 + or + resultIsStrict = false and d = 0 + ) and + ( + testIsTrue = true and k = v2 - v1 + or + testIsTrue = false and k = v1 - v2 + ) and + strengthen = (((k - d) % mod) + mod) % mod ) } @@ -157,27 +217,47 @@ private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int * - `upper = false` : `v >= e + delta` */ private Guard boundFlowCond(SsaVariable v, Expr e, int delta, boolean upper, boolean testIsTrue) { - exists(ComparisonExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper, boolean resultIsStrict | + exists( + ComparisonExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper, + boolean resultIsStrict + | comp = result and boundCondition(comp, v, e, d1, compIsUpper) and (testIsTrue = true or testIsTrue = false) and upper = compIsUpper.booleanXor(testIsTrue.booleanNot()) and - (if comp.isStrict() then resultIsStrict = testIsTrue else resultIsStrict = testIsTrue.booleanNot()) and - (if v.getSourceVariable().getType() instanceof IntegralType then - (upper = true and strengthen = -1 or - upper = false and strengthen = 1) - else - strengthen = 0) and - // A non-strict inequality `x <= y` can be strengthened to `x <= y - 1` if - // `x` and `y` have opposite parities, and a strict inequality `x < y` can - // be similarly strengthened if `x` and `y` have equal parities. - (if parityComparison(comp, resultIsStrict) then d2 = strengthen else d2 = 0) and + ( + if comp.isStrict() + then resultIsStrict = testIsTrue + else resultIsStrict = testIsTrue.booleanNot() + ) and + ( + if v.getSourceVariable().getType() instanceof IntegralType + then ( + upper = true and strengthen = -1 + or + upper = false and strengthen = 1 + ) else strengthen = 0 + ) and + ( + exists(int k | modulusComparison(comp, testIsTrue, k) and d2 = strengthen * k) + or + not modulusComparison(comp, testIsTrue, _) and d2 = 0 + ) and // A strict inequality `x < y` can be strengthened to `x <= y - 1`. - (resultIsStrict = true and d3 = strengthen or resultIsStrict = false and d3 = 0) and + ( + resultIsStrict = true and d3 = strengthen + or + resultIsStrict = false and d3 = 0 + ) and delta = d1 + d2 + d3 - ) or - exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0)) or - result = eqFlowCond(v, e, delta, true, testIsTrue) and (upper = true or upper = false) + ) + or + exists(boolean testIsTrue0 | + implies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0) + ) + or + result = eqFlowCond(v, e, delta, true, testIsTrue) and + (upper = true or upper = false) } private newtype TReason = @@ -189,14 +269,13 @@ private newtype TReason = * is due to a specific condition, or `NoReason` if the bound is inferred * without going through a bounding condition. */ -abstract class Reason extends TReason { - abstract string toString(); -} -class NoReason extends Reason, TNoReason { - override string toString() { result = "NoReason" } -} +abstract class Reason extends TReason { abstract string toString(); } + +class NoReason extends Reason, TNoReason { override string toString() { result = "NoReason" } } + class CondReason extends Reason, TCondReason { Guard getCond() { this = TCondReason(result) } + override string toString() { result = getCond().toString() } } @@ -205,15 +284,14 @@ class CondReason extends Reason, TCondReason { * - `upper = true` : `v <= e + delta` * - `upper = false` : `v >= e + delta` */ -private predicate boundFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason) { - exists(SsaExplicitUpdate upd | v = upd and pos.hasReadOfVar(v) and reason = TNoReason() | - upd.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0 and (upper = true or upper = false) or - upd.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or - upd.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or - upd.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or - upd.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or - upd.getDefiningExpr().(AssignOp) = e and delta = 0 and (upper = true or upper = false) - ) or +private predicate boundFlowStepSsa( + SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason +) { + ssaUpdateStep(v, e, delta) and + pos.hasReadOfVar(v) and + (upper = true or upper = false) and + reason = TNoReason() + or exists(Guard guard, boolean testIsTrue | pos.hasReadOfVar(v) and guard = boundFlowCond(v, e, delta, upper, testIsTrue) and @@ -223,7 +301,9 @@ private predicate boundFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, i } /** Holds if `v != e + delta` at `pos`. */ -private predicate unequalFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, Reason reason) { +private predicate unequalFlowStepSsa( + SsaVariable v, SsaReadPosition pos, Expr e, int delta, Reason reason +) { exists(Guard guard, boolean testIsTrue | pos.hasReadOfVar(v) and guard = eqFlowCond(v, e, delta, false, testIsTrue) and @@ -238,16 +318,25 @@ private predicate unequalFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, */ private predicate safeCast(Type fromtyp, Type totyp) { exists(PrimitiveType pfrom, PrimitiveType pto | pfrom = fromtyp and pto = totyp | - pfrom = pto or - pfrom.hasName("char") and pto.getName().regexpMatch("int|long|float|double") or - pfrom.hasName("byte") and pto.getName().regexpMatch("short|int|long|float|double") or - pfrom.hasName("short") and pto.getName().regexpMatch("int|long|float|double") or - pfrom.hasName("int") and pto.getName().regexpMatch("long|float|double") or - pfrom.hasName("long") and pto.getName().regexpMatch("float|double") or - pfrom.hasName("float") and pto.hasName("double") or + pfrom = pto + or + pfrom.hasName("char") and pto.getName().regexpMatch("int|long|float|double") + or + pfrom.hasName("byte") and pto.getName().regexpMatch("short|int|long|float|double") + or + pfrom.hasName("short") and pto.getName().regexpMatch("int|long|float|double") + or + pfrom.hasName("int") and pto.getName().regexpMatch("long|float|double") + or + pfrom.hasName("long") and pto.getName().regexpMatch("float|double") + or + pfrom.hasName("float") and pto.hasName("double") + or pfrom.hasName("double") and pto.hasName("float") - ) or - safeCast(fromtyp.(BoxedType).getPrimitiveType(), totyp) or + ) + or + safeCast(fromtyp.(BoxedType).getPrimitiveType(), totyp) + or safeCast(fromtyp, totyp.(BoxedType).getPrimitiveType()) } @@ -255,18 +344,19 @@ private predicate safeCast(Type fromtyp, Type totyp) { * A cast that can be ignored for the purpose of range analysis. */ private class SafeCastExpr extends CastExpr { - SafeCastExpr() { - safeCast(getExpr().getType(), getType()) - } + SafeCastExpr() { safeCast(getExpr().getType(), getType()) } } /** * Holds if `typ` is a small integral type with the given lower and upper bounds. */ private predicate typeBound(Type typ, int lowerbound, int upperbound) { - typ.(PrimitiveType).hasName("byte") and lowerbound = -128 and upperbound = 127 or - typ.(PrimitiveType).hasName("short") and lowerbound = -32768 and upperbound = 32767 or - typ.(PrimitiveType).hasName("char") and lowerbound = 0 and upperbound = 65535 or + typ.(PrimitiveType).hasName("byte") and lowerbound = -128 and upperbound = 127 + or + typ.(PrimitiveType).hasName("short") and lowerbound = -32768 and upperbound = 32767 + or + typ.(PrimitiveType).hasName("char") and lowerbound = 0 and upperbound = 65535 + or typeBound(typ.(BoxedType).getPrimitiveType(), lowerbound, upperbound) } @@ -278,8 +368,10 @@ private class NarrowingCastExpr extends CastExpr { not this instanceof SafeCastExpr and typeBound(getType(), _, _) } + /** Gets the lower bound of the resulting type. */ int getLowerBound() { typeBound(getType(), result, _) } + /** Gets the upper bound of the resulting type. */ int getUpperBound() { typeBound(getType(), _, result) } } @@ -290,78 +382,84 @@ private class NarrowingCastExpr extends CastExpr { * - `upper = false` : `e2 >= e1 + delta` */ private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) { - e2.(ParExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or - e2.(AssignExpr).getSource() = e1 and delta = 0 and (upper = true or upper = false) or - e2.(PlusExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or - e2.(PostIncExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or - e2.(PostDecExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or - e2.(PreIncExpr).getExpr() = e1 and delta = 1 and (upper = true or upper = false) or - e2.(PreDecExpr).getExpr() = e1 and delta = -1 and (upper = true or upper = false) or - e2.(SafeCastExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or - exists(SsaExplicitUpdate v, FieldRead arrlen | - e2 = arrlen and - arrlen.getField() instanceof ArrayLengthField and - arrlen.getQualifier() = v.getAUse() and - v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = e1 and - delta = 0 and - (upper = true or upper = false) - ) or + valueFlowStep(e2, e1, delta) and + (upper = true or upper = false) + or + e2.(SafeCastExpr).getExpr() = e1 and + delta = 0 and + (upper = true or upper = false) + or exists(Expr x | - e2.(AddExpr).hasOperands(e1, x) or + e2.(AddExpr).hasOperands(e1, x) + or exists(AssignAddExpr add | add = e2 | - add.getDest() = e1 and add.getRhs() = x or + add.getDest() = e1 and add.getRhs() = x + or add.getDest() = x and add.getRhs() = e1 ) - | - x.(ConstantIntegerExpr).getIntValue() = delta and (upper = true or upper = false) - or + | + // `x instanceof ConstantIntegerExpr` is covered by valueFlowStep not x instanceof ConstantIntegerExpr and not e1 instanceof ConstantIntegerExpr and - if strictlyPositive(x) then - (upper = false and delta = 1) - else if positive(x) then - (upper = false and delta = 0) - else if strictlyNegative(x) then - (upper = true and delta = -1) - else if negative(x) then - (upper = true and delta = 0) - else - none() - ) or + if strictlyPositive(x) + then ( + upper = false and delta = 1 + ) else + if positive(x) + then ( + upper = false and delta = 0 + ) else + if strictlyNegative(x) + then ( + upper = true and delta = -1 + ) else if negative(x) then (upper = true and delta = 0) else none() + ) + or exists(Expr x | exists(SubExpr sub | e2 = sub and sub.getLeftOperand() = e1 and sub.getRightOperand() = x - ) or + ) + or exists(AssignSubExpr sub | e2 = sub and sub.getDest() = e1 and sub.getRhs() = x ) - | - x.(ConstantIntegerExpr).getIntValue() = -delta and (upper = true or upper = false) - or + | + // `x instanceof ConstantIntegerExpr` is covered by valueFlowStep not x instanceof ConstantIntegerExpr and - if strictlyPositive(x) then - (upper = true and delta = -1) - else if positive(x) then - (upper = true and delta = 0) - else if strictlyNegative(x) then - (upper = false and delta = 1) - else if negative(x) then - (upper = false and delta = 0) - else - none() - ) or - e2.(RemExpr).getRightOperand() = e1 and positive(e1) and delta = -1 and upper = true or - e2.(RemExpr).getLeftOperand() = e1 and positive(e1) and delta = 0 and upper = true or - e2.(AssignRemExpr).getRhs() = e1 and positive(e1) and delta = -1 and upper = true or - e2.(AssignRemExpr).getDest() = e1 and positive(e1) and delta = 0 and upper = true or - e2.(AndBitwiseExpr).getAnOperand() = e1 and positive(e1) and delta = 0 and upper = true or - e2.(AssignAndExpr).getSource() = e1 and positive(e1) and delta = 0 and upper = true or - e2.(OrBitwiseExpr).getAnOperand() = e1 and positive(e2) and delta = 0 and upper = false or - e2.(AssignOrExpr).getSource() = e1 and positive(e2) and delta = 0 and upper = false or + if strictlyPositive(x) + then ( + upper = true and delta = -1 + ) else + if positive(x) + then ( + upper = true and delta = 0 + ) else + if strictlyNegative(x) + then ( + upper = false and delta = 1 + ) else if negative(x) then (upper = false and delta = 0) else none() + ) + or + e2.(RemExpr).getRightOperand() = e1 and positive(e1) and delta = -1 and upper = true + or + e2.(RemExpr).getLeftOperand() = e1 and positive(e1) and delta = 0 and upper = true + or + e2.(AssignRemExpr).getRhs() = e1 and positive(e1) and delta = -1 and upper = true + or + e2.(AssignRemExpr).getDest() = e1 and positive(e1) and delta = 0 and upper = true + or + e2.(AndBitwiseExpr).getAnOperand() = e1 and positive(e1) and delta = 0 and upper = true + or + e2.(AssignAndExpr).getSource() = e1 and positive(e1) and delta = 0 and upper = true + or + e2.(OrBitwiseExpr).getAnOperand() = e1 and positive(e2) and delta = 0 and upper = false + or + e2.(AssignOrExpr).getSource() = e1 and positive(e2) and delta = 0 and upper = false + or exists(MethodAccess ma, Method m | e2 = ma and ma.getMethod() = m and @@ -370,11 +468,16 @@ private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) { e1 = ma.getAnArgument() and delta = -1 and upper = true - ) or + ) + or exists(MethodAccess ma, Method m | e2 = ma and ma.getMethod() = m and - (m.hasName("max") and upper = false or m.hasName("min") and upper = true) and + ( + m.hasName("max") and upper = false + or + m.hasName("min") and upper = true + ) and m.getDeclaringType().hasQualifiedName("java.lang", "Math") and e1 = ma.getAnArgument() and delta = 0 @@ -384,11 +487,19 @@ private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) { /** Holds if `e2 = e1 * factor` and `factor > 0`. */ private predicate boundFlowStepMul(Expr e2, Expr e1, int factor) { exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 | - e2.(MulExpr).hasOperands(e1, c) and factor = k or - exists(AssignMulExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or - exists(AssignMulExpr e | e = e2 and e.getDest() = c and e.getRhs() = e1 and factor = k) or - exists(LShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or - exists(AssignLShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)) + e2.(MulExpr).hasOperands(e1, c) and factor = k + or + exists(AssignMulExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) + or + exists(AssignMulExpr e | e = e2 and e.getDest() = c and e.getRhs() = e1 and factor = k) + or + exists(LShiftExpr e | + e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k) + ) + or + exists(AssignLShiftExpr e | + e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k) + ) ) } @@ -400,76 +511,39 @@ private predicate boundFlowStepMul(Expr e2, Expr e1, int factor) { */ private predicate boundFlowStepDiv(Expr e2, Expr e1, int factor) { exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 | - exists(DivExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k) or - exists(AssignDivExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or - exists(RShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or - exists(AssignRShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)) or - exists(URShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or - exists(AssignURShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)) + exists(DivExpr e | + e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k + ) + or + exists(AssignDivExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) + or + exists(RShiftExpr e | + e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k) + ) + or + exists(AssignRShiftExpr e | + e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k) + ) + or + exists(URShiftExpr e | + e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k) + ) + or + exists(AssignURShiftExpr e | + e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k) + ) ) } -private newtype TBound = - TBoundZero() or - TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or - TBoundExpr(Expr e) { e.(FieldRead).getField() instanceof ArrayLengthField and not exists(SsaVariable v | e = v.getAUse()) } - -/** - * A bound that may be inferred for an expression plus/minus an integer delta. - */ -abstract class Bound extends TBound { - abstract string toString(); - /** Gets an expression that equals this bound plus `delta`. */ - abstract Expr getExpr(int delta); - /** Gets an expression that equals this bound. */ - Expr getExpr() { - result = getExpr(0) - } - predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { - path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0 - } -} - -/** - * The bound that corresponds to the integer 0. This is used to represent all - * integer bounds as bounds are always accompanied by an added integer delta. - */ -class ZeroBound extends Bound, TBoundZero { - override string toString() { result = "0" } - override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta } -} - -/** - * A bound corresponding to the value of an SSA variable. - */ -class SsaBound extends Bound, TBoundSsa { - /** Gets the SSA variable that equals this bound. */ - SsaVariable getSsa() { this = TBoundSsa(result) } - override string toString() { result = getSsa().toString() } - override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 } - override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { - getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec) - } -} - -/** - * A bound that corresponds to the value of a specific expression that might be - * interesting, but isn't otherwise represented by the value of an SSA variable. - */ -class ExprBound extends Bound, TBoundExpr { - override string toString() { result = getExpr().toString() } - override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 } - override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { - getExpr().hasLocationInfo(path, sl, sc, el, ec) - } -} - /** * Holds if `b + delta` is a valid bound for `v` at `pos`. * - `upper = true` : `v <= b + delta` * - `upper = false` : `v >= b + delta` */ -private predicate boundedSsa(SsaVariable v, SsaReadPosition pos, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { +private predicate boundedSsa( + SsaVariable v, SsaReadPosition pos, Bound b, int delta, boolean upper, boolean fromBackEdge, + int origdelta, Reason reason +) { exists(Expr mid, int d1, int d2, Reason r1, Reason r2 | boundFlowStepSsa(v, pos, mid, d1, upper, r1) and bounded(mid, b, d2, upper, fromBackEdge, origdelta, r2) and @@ -477,14 +551,23 @@ private predicate boundedSsa(SsaVariable v, SsaReadPosition pos, Bound b, int de // upper = false: v >= mid + d1 >= b + d1 + d2 = b + delta delta = d1 + d2 and (if r1 instanceof NoReason then reason = r2 else reason = r1) - ) or + ) + or exists(int d, Reason r1, Reason r2 | boundedSsa(v, pos, b, d, upper, fromBackEdge, origdelta, r2) or boundedPhi(v, b, d, upper, fromBackEdge, origdelta, r2) - | + | unequalSsa(v, pos, b, d, r1) and - (upper = true and delta = d - 1 or upper = false and delta = d + 1) and - (reason = r1 or reason = r2 and not r2 instanceof NoReason) + ( + upper = true and delta = d - 1 + or + upper = false and delta = d + 1 + ) and + ( + reason = r1 + or + reason = r2 and not r2 instanceof NoReason + ) ) } @@ -506,14 +589,19 @@ private predicate unequalSsa(SsaVariable v, SsaReadPosition pos, Bound b, int de private predicate backEdge(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge) { edge.phiInput(phi, inp) and // Conservatively assume that every edge is a back edge if we don't have dominance information. - (phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or not hasDominanceInformation(edge.getOrigBlock())) + ( + phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or + not hasDominanceInformation(edge.getOrigBlock()) + ) } /** Weakens a delta to lie in the range `[-1..1]`. */ bindingset[delta, upper] private int weakenDelta(boolean upper, int delta) { - delta in [-1..1] and result = delta or - upper = true and result = -1 and delta < -1 or + delta in [-1 .. 1] and result = delta + or + upper = true and result = -1 and delta < -1 + or upper = false and result = 1 and delta > 1 } @@ -523,27 +611,43 @@ private int weakenDelta(boolean upper, int delta) { * - `upper = true` : `inp <= b + delta` * - `upper = false` : `inp >= b + delta` */ -private predicate boundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { +private predicate boundedPhiInp( + SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, Bound b, int delta, + boolean upper, boolean fromBackEdge, int origdelta, Reason reason +) { edge.phiInput(phi, inp) and exists(int d, boolean fromBackEdge0 | - boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason) or - boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason) or - b.(SsaBound).getSsa() = inp and d = 0 and (upper = true or upper = false) and fromBackEdge0 = false and origdelta = 0 and reason = TNoReason() - | - if backEdge(phi, inp, edge) then + boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason) + or + boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason) + or + b.(SsaBound).getSsa() = inp and + d = 0 and + (upper = true or upper = false) and + fromBackEdge0 = false and + origdelta = 0 and + reason = TNoReason() + | + if backEdge(phi, inp, edge) + then fromBackEdge = true and ( - fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta or + fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta + or fromBackEdge0 = false and delta = d ) - else - (delta = d and fromBackEdge = fromBackEdge0) + else ( + delta = d and fromBackEdge = fromBackEdge0 + ) ) } /** Holds if `boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)`. */ pragma[noinline] -private predicate boundedPhiInp1(SsaPhiNode phi, Bound b, boolean upper, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int delta) { +private predicate boundedPhiInp1( + SsaPhiNode phi, Bound b, boolean upper, SsaVariable inp, SsaReadPositionPhiInputEdge edge, + int delta +) { boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _) } @@ -553,11 +657,17 @@ private predicate boundedPhiInp1(SsaPhiNode phi, Bound b, boolean upper, SsaVari * - `upper = true` : `inp <= phi` * - `upper = false` : `inp >= phi` */ -private predicate selfBoundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, boolean upper) { +private predicate selfBoundedPhiInp( + SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, boolean upper +) { exists(int d, SsaBound phibound | phibound.getSsa() = phi and boundedPhiInp(phi, inp, edge, phibound, d, upper, _, _, _) and - (upper = true and d <= 0 or upper = false and d >= 0) + ( + upper = true and d <= 0 + or + upper = false and d >= 0 + ) ) } @@ -568,19 +678,29 @@ private predicate selfBoundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPosi * - `upper = false` : `inp >= b + delta` */ pragma[noinline] -private predicate boundedPhiCand(SsaPhiNode phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta, Reason reason) { - exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge | boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason)) +private predicate boundedPhiCand( + SsaPhiNode phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta, + Reason reason +) { + exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge | + boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason) + ) } /** * Holds if the candidate bound `b + delta` for `phi` is valid for the phi input * `inp` along `edge`. */ -private predicate boundedPhiCandValidForEdge(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason, SsaVariable inp, SsaReadPositionPhiInputEdge edge) { +private predicate boundedPhiCandValidForEdge( + SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, + Reason reason, SsaVariable inp, SsaReadPositionPhiInputEdge edge +) { boundedPhiCand(phi, upper, b, delta, fromBackEdge, origdelta, reason) and ( - exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta) or - exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta) or + exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta) + or + exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta) + or selfBoundedPhiInp(phi, inp, edge, upper) ) } @@ -590,7 +710,10 @@ private predicate boundedPhiCandValidForEdge(SsaPhiNode phi, Bound b, int delta, * - `upper = true` : `phi <= b + delta` * - `upper = false` : `phi >= b + delta` */ -private predicate boundedPhi(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { +private predicate boundedPhi( + SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, + Reason reason +) { forex(SsaVariable inp, SsaReadPositionPhiInputEdge edge | edge.phiInput(phi, inp) | boundedPhiCandValidForEdge(phi, b, delta, upper, fromBackEdge, origdelta, reason, inp, edge) ) @@ -612,14 +735,16 @@ private predicate lowerBoundZero(Expr e) { * (for `upper = false`) bound of `b`. */ private predicate baseBound(Expr e, int b, boolean upper) { - lowerBoundZero(e) and b = 0 and upper = false or + lowerBoundZero(e) and b = 0 and upper = false + or exists(Method read | e.(MethodAccess).getMethod().overrides*(read) and read.getDeclaringType().hasQualifiedName("java.io", "InputStream") and read.hasName("read") and read.getNumberOfParameters() = 0 - | - upper = true and b = 255 or + | + upper = true and b = 255 + or upper = false and b = -1 ) } @@ -631,16 +756,18 @@ private predicate baseBound(Expr e, int b, boolean upper) { * `upper = false` this means that the cast will not underflow. */ private predicate safeNarrowingCast(NarrowingCastExpr cast, boolean upper) { - exists(int bound | - bounded(cast.getExpr(), TBoundZero(), bound, upper, _, _, _) - | - upper = true and bound <= cast.getUpperBound() or + exists(int bound | bounded(cast.getExpr(), any(ZeroBound zb), bound, upper, _, _, _) | + upper = true and bound <= cast.getUpperBound() + or upper = false and bound >= cast.getLowerBound() ) } pragma[noinline] -private predicate boundedCastExpr(NarrowingCastExpr cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { +private predicate boundedCastExpr( + NarrowingCastExpr cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, + Reason reason +) { bounded(cast.getExpr(), b, delta, upper, fromBackEdge, origdelta, reason) } @@ -649,14 +776,27 @@ private predicate boundedCastExpr(NarrowingCastExpr cast, Bound b, int delta, bo * - `upper = true` : `e <= b + delta` * - `upper = false` : `e >= b + delta` */ -private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) { - e = b.getExpr(delta) and (upper = true or upper = false) and fromBackEdge = false and origdelta = delta and reason = TNoReason() or - baseBound(e, delta, upper) and b instanceof ZeroBound and fromBackEdge = false and origdelta = delta and reason = TNoReason() or +private predicate bounded( + Expr e, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason +) { + e = b.getExpr(delta) and + (upper = true or upper = false) and + fromBackEdge = false and + origdelta = delta and + reason = TNoReason() + or + baseBound(e, delta, upper) and + b instanceof ZeroBound and + fromBackEdge = false and + origdelta = delta and + reason = TNoReason() + or exists(SsaVariable v, SsaReadPositionBlock bb | boundedSsa(v, bb, b, delta, upper, fromBackEdge, origdelta, reason) and e = v.getAUse() and bb.getBlock() = e.getBasicBlock() - ) or + ) + or exists(Expr mid, int d1, int d2 | boundFlowStep(e, mid, d1, upper) and // Constants have easy, base-case bounds, so let's not infer any recursive bounds. @@ -665,18 +805,21 @@ private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fro // upper = true: e <= mid + d1 <= b + d1 + d2 = b + delta // upper = false: e >= mid + d1 >= b + d1 + d2 = b + delta delta = d1 + d2 - ) or + ) + or exists(SsaPhiNode phi | boundedPhi(phi, b, delta, upper, fromBackEdge, origdelta, reason) and e = phi.getAUse() - ) or + ) + or exists(Expr mid, int factor, int d | boundFlowStepMul(e, mid, factor) and not e instanceof ConstantIntegerExpr and bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and b instanceof ZeroBound and delta = d * factor - ) or + ) + or exists(Expr mid, int factor, int d | boundFlowStepDiv(e, mid, factor) and not e instanceof ConstantIntegerExpr and @@ -684,25 +827,38 @@ private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fro b instanceof ZeroBound and d >= 0 and delta = d / factor - ) or + ) + or exists(NarrowingCastExpr cast | cast = e and safeNarrowingCast(cast, upper.booleanNot()) and boundedCastExpr(cast, b, delta, upper, fromBackEdge, origdelta, reason) - ) or - exists(ConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2, Reason r1, Reason r2 | + ) + or + exists( + ConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2, Reason r1, + Reason r2 + | cond = e and boundedConditionalExpr(cond, b, upper, true, d1, fbe1, od1, r1) and boundedConditionalExpr(cond, b, upper, false, d2, fbe2, od2, r2) and - (delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1 or - delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2) - | - upper = true and delta = d1.maximum(d2) or + ( + delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1 + or + delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2 + ) + | + upper = true and delta = d1.maximum(d2) + or upper = false and delta = d1.minimum(d2) ) } -private predicate boundedConditionalExpr(ConditionalExpr cond, Bound b, boolean upper, boolean branch, int delta, boolean fromBackEdge, int origdelta, Reason reason) { - branch = true and bounded(cond.getTrueExpr(), b, delta, upper, fromBackEdge, origdelta, reason) or +private predicate boundedConditionalExpr( + ConditionalExpr cond, Bound b, boolean upper, boolean branch, int delta, boolean fromBackEdge, + int origdelta, Reason reason +) { + branch = true and bounded(cond.getTrueExpr(), b, delta, upper, fromBackEdge, origdelta, reason) + or branch = false and bounded(cond.getFalseExpr(), b, delta, upper, fromBackEdge, origdelta, reason) } diff --git a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll index 9b5e9a257a2..4cd953e573f 100644 --- a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll +++ b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll @@ -9,47 +9,60 @@ private import semmle.code.java.controlflow.internal.GuardsLogic /** An expression that always has the same integer value. */ pragma[nomagic] private predicate constantIntegerExpr(Expr e, int val) { - e.(CompileTimeConstantExpr).getIntValue() = val or + e.(CompileTimeConstantExpr).getIntValue() = val + or exists(SsaExplicitUpdate v, Expr src | e = v.getAUse() and src = v.getDefiningExpr().(VariableAssign).getSource() and constantIntegerExpr(src, val) ) + or + exists(SsaExplicitUpdate v, FieldRead arrlen | + e = arrlen and + arrlen.getField() instanceof ArrayLengthField and + arrlen.getQualifier() = v.getAUse() and + v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getFirstDimensionSize() = val + ) } /** An expression that always has the same integer value. */ class ConstantIntegerExpr extends Expr { - ConstantIntegerExpr() { - constantIntegerExpr(this, _) - } + ConstantIntegerExpr() { constantIntegerExpr(this, _) } /** Gets the integer value of this expression. */ - int getIntValue() { - constantIntegerExpr(this, result) - } + int getIntValue() { constantIntegerExpr(this, result) } } /** * Gets an expression that equals `v - d`. */ Expr ssaRead(SsaVariable v, int delta) { - result = v.getAUse() and delta = 0 or - result.(ParExpr).getExpr() = ssaRead(v, delta) or + result = v.getAUse() and delta = 0 + or + result.(ParExpr).getExpr() = ssaRead(v, delta) + or exists(int d1, ConstantIntegerExpr c | result.(AddExpr).hasOperands(ssaRead(v, d1), c) and delta = d1 - c.getIntValue() - ) or + ) + or exists(SubExpr sub, int d1, ConstantIntegerExpr c | result = sub and sub.getLeftOperand() = ssaRead(v, d1) and sub.getRightOperand() = c and delta = d1 + c.getIntValue() - ) or - v.(SsaExplicitUpdate).getDefiningExpr().(PreIncExpr) = result and delta = 0 or - v.(SsaExplicitUpdate).getDefiningExpr().(PreDecExpr) = result and delta = 0 or - v.(SsaExplicitUpdate).getDefiningExpr().(PostIncExpr) = result and delta = 1 or // x++ === ++x - 1 - v.(SsaExplicitUpdate).getDefiningExpr().(PostDecExpr) = result and delta = -1 or // x-- === --x + 1 - v.(SsaExplicitUpdate).getDefiningExpr().(Assignment) = result and delta = 0 or + ) + or + v.(SsaExplicitUpdate).getDefiningExpr().(PreIncExpr) = result and delta = 0 + or + v.(SsaExplicitUpdate).getDefiningExpr().(PreDecExpr) = result and delta = 0 + or + v.(SsaExplicitUpdate).getDefiningExpr().(PostIncExpr) = result and delta = 1 // x++ === ++x - 1 + or + v.(SsaExplicitUpdate).getDefiningExpr().(PostDecExpr) = result and delta = -1 // x-- === --x + 1 + or + v.(SsaExplicitUpdate).getDefiningExpr().(Assignment) = result and delta = 0 + or result.(AssignExpr).getSource() = ssaRead(v, delta) } @@ -114,7 +127,8 @@ class SsaReadPositionPhiInputEdge extends SsaReadPosition, TSsaReadPositionPhiIn * value `testIsTrue`. */ predicate guardDirectlyControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) { - guard.directlyControls(controlled.(SsaReadPositionBlock).getBlock(), testIsTrue) or + guard.directlyControls(controlled.(SsaReadPositionBlock).getBlock(), testIsTrue) + or exists(SsaReadPositionPhiInputEdge controlledEdge | controlledEdge = controlled | guard.directlyControls(controlledEdge.getOrigBlock(), testIsTrue) or guard.hasBranchEdge(controlledEdge.getOrigBlock(), controlledEdge.getPhiBlock(), testIsTrue) @@ -125,9 +139,101 @@ predicate guardDirectlyControlsSsaRead(Guard guard, SsaReadPosition controlled, * Holds if `guard` controls the position `controlled` with the value `testIsTrue`. */ predicate guardControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) { - guardDirectlyControlsSsaRead(guard, controlled, testIsTrue) or + guardDirectlyControlsSsaRead(guard, controlled, testIsTrue) + or exists(Guard guard0, boolean testIsTrue0 | implies_v2(guard0, testIsTrue0, guard, testIsTrue) and guardControlsSsaRead(guard0, controlled, testIsTrue0) ) } + +/** + * Gets a condition that tests whether `v` equals `e + delta`. + * + * If the condition evaluates to `testIsTrue`: + * - `isEq = true` : `v == e + delta` + * - `isEq = false` : `v != e + delta` + */ +Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsTrue) { + exists(boolean eqpolarity | + result.isEquality(ssaRead(v, delta), e, eqpolarity) and + (testIsTrue = true or testIsTrue = false) and + eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq + ) + or + exists(boolean testIsTrue0 | + implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0) + ) +} + +/** + * Holds if `v` is an `SsaExplicitUpdate` that equals `e + delta`. + */ +predicate ssaUpdateStep(SsaExplicitUpdate v, Expr e, int delta) { + v.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0 + or + v.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1 + or + v.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1 + or + v.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1 + or + v.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1 + or + v.getDefiningExpr().(AssignOp) = e and delta = 0 +} + +/** + * Holds if `e1 + delta` equals `e2`. + */ +predicate valueFlowStep(Expr e2, Expr e1, int delta) { + e2.(ParExpr).getExpr() = e1 and delta = 0 + or + e2.(AssignExpr).getSource() = e1 and delta = 0 + or + e2.(PlusExpr).getExpr() = e1 and delta = 0 + or + e2.(PostIncExpr).getExpr() = e1 and delta = 0 + or + e2.(PostDecExpr).getExpr() = e1 and delta = 0 + or + e2.(PreIncExpr).getExpr() = e1 and delta = 1 + or + e2.(PreDecExpr).getExpr() = e1 and delta = -1 + or + exists(SsaExplicitUpdate v, FieldRead arrlen | + e2 = arrlen and + arrlen.getField() instanceof ArrayLengthField and + arrlen.getQualifier() = v.getAUse() and + v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = e1 and + delta = 0 + ) + or + exists(Expr x | + e2.(AddExpr).hasOperands(e1, x) + or + exists(AssignAddExpr add | add = e2 | + add.getDest() = e1 and add.getRhs() = x + or + add.getDest() = x and add.getRhs() = e1 + ) + | + x.(ConstantIntegerExpr).getIntValue() = delta + ) + or + exists(Expr x | + exists(SubExpr sub | + e2 = sub and + sub.getLeftOperand() = e1 and + sub.getRightOperand() = x + ) + or + exists(AssignSubExpr sub | + e2 = sub and + sub.getDest() = e1 and + sub.getRhs() = x + ) + | + x.(ConstantIntegerExpr).getIntValue() = -delta + ) +} 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 57eade7058e..9d28e37cc8a 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 57eade7058e..9d28e37cc8a 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 57eade7058e..9d28e37cc8a 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 57eade7058e..9d28e37cc8a 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 57eade7058e..9d28e37cc8a 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 fc1d6e5053e..02c9919723b 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll @@ -118,7 +118,7 @@ private module ImplCommon { node1.(ArgumentNode).argumentOf(call, i1) and node2.getPreUpdateNode().(ArgumentNode).argumentOf(call, i2) and compatibleTypes(node1.getTypeBound(), f.getType()) and - compatibleTypes(node2.getTypeBound(), f.getDeclaringType()) + compatibleTypes(node2.getTypeBound(), f.getContainerType()) ) } @@ -149,7 +149,7 @@ private module ImplCommon { setterReturn(p, f) and arg.argumentOf(node2.asExpr(), _) and compatibleTypes(node1.getTypeBound(), f.getType()) and - compatibleTypes(node2.getTypeBound(), f.getDeclaringType()) + compatibleTypes(node2.getTypeBound(), f.getContainerType()) ) } @@ -174,7 +174,7 @@ private module ImplCommon { viableParamArg(p, arg) and getter(p, f) and arg.argumentOf(node2.asExpr(), _) and - compatibleTypes(node1.getTypeBound(), f.getDeclaringType()) and + compatibleTypes(node1.getTypeBound(), f.getContainerType()) and compatibleTypes(node2.getTypeBound(), f.getType()) ) } diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll index 57eade7058e..9d28e37cc8a 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll @@ -724,76 +724,27 @@ private predicate localFlowBigStep( localFlowExit(node2, config) } -/** - * Holds if `f` may contain an object of the same type, `t`, as the one - * that contains `f`, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ -private predicate selfRef(Content f, RefType t) { - t = f.getDeclaringType() and - f.isSelfRef() -} - -private newtype TFlowContainer = - TRegularContent(Content f) { not selfRef(f, _) } or - TSelfRefContent(RefType t) { selfRef(_, t) } - -/** - * A `Content` or a `Content` abstracted as its declaring type. - * - * Sequences of one or more `Content`s in the same declaring type for which - * `isSelfRef()` holds are represented as a single `FlowContainer` in an - * `AccessPath`. - */ -private class FlowContainer extends TFlowContainer { - string toString() { - exists(Content f | this = TRegularContent(f) and result = f.toString()) - or - exists(RefType t | this = TSelfRefContent(t) and result = t.toString()) - } - - predicate usesContent(Content f) { - this = TRegularContent(f) - or - exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t)) - } - - RefType getContainerType() { - exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType()) - or - this = TSelfRefContent(result) - } -} - private newtype TAccessPathFront = TFrontNil(Type t) or - TFrontHead(FlowContainer f) + TFrontHead(Content f) /** * The front of an `AccessPath`. This is either a head or a nil. */ private class AccessPathFront extends TAccessPathFront { string toString() { - exists(Type t | this = TFrontNil(t) | result = t.toString()) + exists(Type t | this = TFrontNil(t) | result = ppReprType(t)) or - exists(FlowContainer f | this = TFrontHead(f) | result = f.toString()) + exists(Content f | this = TFrontHead(f) | result = f.toString()) } Type getType() { this = TFrontNil(result) or - exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType()) + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) } - predicate headUsesContent(Content f) { - exists(FlowContainer fc | - fc.usesContent(f) and - this = TFrontHead(fc) - ) - } + predicate headUsesContent(Content f) { this = TFrontHead(f) } } private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } @@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config) private newtype TAccessPath = TNil(Type t) or - TCons(FlowContainer f, int len) { len in [1 .. 5] } + TCons(Content f, int len) { len in [1 .. 5] } /** * Conceptually a list of `Content`s followed by a `Type`, but only the first @@ -1016,7 +967,7 @@ private newtype TAccessPath = private class AccessPath extends TAccessPath { abstract string toString(); - FlowContainer getHead() { this = TCons(result, _) } + Content getHead() { this = TCons(result, _) } int len() { this = TNil(_) and result = 0 @@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath { Type getType() { this = TNil(result) or - exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType()) + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) } abstract AccessPathFront getFront(); } private class AccessPathNil extends AccessPath, TNil { - override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) } + override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) } override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) } } private class AccessPathCons extends AccessPath, TCons { override string toString() { - exists(FlowContainer f, int len | this = TCons(f, len) | + exists(Content f, int len | this = TCons(f, len) | result = f.toString() + ", ... (" + len.toString() + ")" ) } override AccessPathFront getFront() { - exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f)) + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) } } 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 a7c1c8a849f..62475df4d37 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -118,20 +118,10 @@ class Content extends TContent { } /** Gets the type of the object containing this content. */ - abstract RefType getDeclaringType(); + abstract RefType getContainerType(); /** Gets the type of this content. */ abstract Type getType(); - - /** - * Holds if this content may contain an object of the same type as the one - * that contains this content, and if this fact should be used to compress - * access paths. - * - * Examples include the tail pointer in a linked list or the left and right - * pointers in a binary tree. - */ - predicate isSelfRef() { none() } } private class FieldContent extends Content, TFieldContent { @@ -147,17 +137,15 @@ private class FieldContent extends Content, TFieldContent { f.getLocation().hasLocationInfo(path, sl, sc, el, ec) } - override RefType getDeclaringType() { result = f.getDeclaringType() } + override RefType getContainerType() { result = f.getDeclaringType() } override Type getType() { result = getFieldTypeBound(f) } - - override predicate isSelfRef() { compatibleTypes(getDeclaringType(), getType()) } } private class CollectionContent extends Content, TCollectionContent { override string toString() { result = "collection" } - override RefType getDeclaringType() { none() } + override RefType getContainerType() { none() } override Type getType() { none() } } @@ -165,7 +153,7 @@ private class CollectionContent extends Content, TCollectionContent { private class ArrayContent extends Content, TArrayContent { override string toString() { result = "array" } - override RefType getDeclaringType() { none() } + override RefType getContainerType() { none() } override Type getType() { none() } } @@ -212,6 +200,13 @@ RefType getErasedRepr(Type t) { ) } +/** Gets a string representation of a type returned by `getErasedRepr`. */ +string ppReprType(Type t) { + if t.(BoxedType).getPrimitiveType().getName() = "double" + then result = "Number" + else result = t.toString() +} + private predicate canContainBool(Type t) { t instanceof BooleanType or any(BooleanType b).(RefType).getASourceSupertype+() = t @@ -227,12 +222,9 @@ predicate compatibleTypes(Type t1, Type t2) { e1 = getErasedRepr(t1) and e2 = getErasedRepr(t2) | - /* - * Because of `getErasedRepr`, `erasedHaveIntersection` is a sufficient - * compatibility check, but `conContainBool` is kept as a dummy disjunct - * to get the proper join-order. - */ - + // Because of `getErasedRepr`, `erasedHaveIntersection` is a sufficient + // compatibility check, but `conContainBool` is kept as a dummy disjunct + // to get the proper join-order. erasedHaveIntersection(e1, e2) or canContainBool(e1) and canContainBool(e2) diff --git a/java/ql/test/query-tests/RangeAnalysis/A.java b/java/ql/test/query-tests/RangeAnalysis/A.java new file mode 100644 index 00000000000..1a3a3377286 --- /dev/null +++ b/java/ql/test/query-tests/RangeAnalysis/A.java @@ -0,0 +1,165 @@ +public class A { + private static final int[] arr1 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + private final int[] arr2; + private final int[] arr3; + + public A(int[] arr2, int n) { + if (arr2.length % 2 != 0) + throw new Exception(); + this.arr2 = arr2; + this.arr3 = new int[n << 1]; + } + + void m1(int[] a) { + int sum = 0; + for (int i = 0; i <= a.length; i++) { + sum += a[i]; // Out of bounds + } + } + + void m2(int[] a) { + int sum = 0; + for (int i = 0; i < a.length; i += 2) { + sum += a[i] + a[i + 1]; // Out of bounds (unless len%2==0) + } + } + + void m3(int[] a) { + if (a.length % 2 != 0) + return; + int sum = 0; + for (int i = 0; i < a.length; ) { + sum += a[i++]; // OK + sum += a[i++]; // OK + } + for (int i = 0; i < arr1.length; ) { + sum += arr1[i++]; // OK + sum += arr1[i++]; // OK - FP + i += 2; + } + for (int i = 0; i < arr2.length; ) { + sum += arr2[i++]; // OK + sum += arr2[i++]; // OK - FP + } + for (int i = 0; i < arr3.length; ) { + sum += arr3[i++]; // OK + sum += arr3[i++]; // OK - FP + } + int[] b; + if (sum > 3) + b = a; + else + b = arr1; + for (int i = 0; i < b.length; i++) { + sum += b[i]; // OK + sum += b[++i]; // OK - FP + } + } + + void m4(int[] a, int[] b) { + assert a.length % 2 == 0; + int sum = 0; + for (int i = 0; i < a.length; ) { + sum += a[i++]; // OK + sum += a[i++]; // OK - FP + } + int len = b.length; + if ((len & 1) != 0) + return; + for (int i = 0; i < len; ) { + sum += b[i++]; // OK + sum += b[i++]; // OK + } + } + + void m5(int n) { + int[] a = new int[3 * n]; + int sum = 0; + for (int i = 0; i < a.length; i += 3) { + sum += a[i] + a[i + 1] + a[i + 2]; // OK + } + } + + int m6(int[] a, int ix) { + if (ix < 0 || ix > a.length) + return 0; + return a[ix]; // Out of bounds + } + + void m7() { + int[] xs = new int[11]; + int sum = 0; + for (int i = 0; i < 11; i++) { + for (int j = 0; j < 11; j++) { + sum += xs[i]; // OK + sum += xs[j]; // OK + if (i < j) + sum += xs[i + 11 - j]; // OK - FP + else + sum += xs[i - j]; // OK + } + } + } + + void m8(int[] a) { + if ((a.length - 4) % 3 != 0) + return; + int sum = 0; + for (int i = 4; i < a.length; i += 3) { + sum += a[i]; // OK + sum += a[i + 1]; // OK - FP + sum += a[i + 2]; // OK - FP + } + } + + void m9() { + int[] a = new int[] { 1, 2, 3, 4, 5 }; + int sum = 0; + for (int i = 0; i < 10; i++) { + if (i < 5) + sum += a[i]; // OK + else + sum += a[9 - i]; // OK - FP + } + } + + void m10(int n, int m) { + int len = Math.min(n, m); + int[] a = new int[n]; + int sum = 0; + for (int i = n - 1; i >= 0; i--) { + sum += a[i]; // OK + for (int j = i + 1; j < len; j++) { + sum += a[j]; // OK + sum += a[i + 1]; // OK - FP + } + } + } + + void m11(int n) { + int len = n*2; + int[] a = new int[len]; + int sum = 0; + for (int i = 0; i < len; i = i + 2) { + sum += a[i+1]; // OK + for (int j = i; j < len - 2; j = j + 2) { + sum += a[j]; // OK + sum += a[j+1]; // OK + sum += a[j+2]; // OK + sum += a[j+3]; // OK + } + } + } + + void m12() { + int[] a = new int[] { 1, 2, 3, 4, 5, 6 }; + int sum = 0; + for (int i = 0; i < a.length; i += 2) { + sum += a[i] + a[i + 1]; // OK + } + int[] b = new int[8]; + for (int i = 2; i < 8; i += 2) { + sum += b[i] + b[i + 1]; // OK + } + } +} diff --git a/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected new file mode 100644 index 00000000000..23daa615a19 --- /dev/null +++ b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected @@ -0,0 +1,13 @@ +| A.java:16:14:16:17 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | +| A.java:23:21:23:28 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | +| A.java:37:14:37:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | +| A.java:42:14:42:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | +| A.java:46:14:46:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | +| A.java:55:14:55:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | +| A.java:64:14:64:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | +| A.java:86:12:86:16 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | +| A.java:97:18:97:31 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 8. | +| A.java:110:14:110:21 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | +| A.java:111:14:111:21 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 1. | +| A.java:122:16:122:23 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 3. | +| A.java:134:16:134:23 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. | diff --git a/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.qlref b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.qlref new file mode 100644 index 00000000000..439f2fd18de --- /dev/null +++ b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.qlref @@ -0,0 +1 @@ +Likely Bugs/Collections/ArrayIndexOutOfBounds.ql diff --git a/java/ql/test/query-tests/UselessComparisonTest/B.java b/java/ql/test/query-tests/UselessComparisonTest/B.java new file mode 100644 index 00000000000..c9c2b1cd69c --- /dev/null +++ b/java/ql/test/query-tests/UselessComparisonTest/B.java @@ -0,0 +1,22 @@ +import java.util.*; +import java.util.function.*; + +public class B { + int modcount = 0; + + int[] arr; + + public void modify(IntUnaryOperator func) { + int mc = modcount; + for (int i = 0; i < arr.length; i++) { + arr[i] = func.applyAsInt(arr[i]); + } + // Always false unless there is a call path from func.applyAsInt(..) to + // this method, but should not be reported, as checks guarding + // ConcurrentModificationExceptions are expected to always be false in the + // absence of API misuse. + if (mc != modcount) + throw new ConcurrentModificationException(); + modcount++; + } +} diff --git a/javascript/ql/src/Declarations/UnusedVariable.ql b/javascript/ql/src/Declarations/UnusedVariable.ql index 093de8b4bf7..c1d31e1b9d6 100644 --- a/javascript/ql/src/Declarations/UnusedVariable.ql +++ b/javascript/ql/src/Declarations/UnusedVariable.ql @@ -94,36 +94,95 @@ predicate isEnumMember(VarDecl decl) { } /** - * Gets a description of the declaration `vd`, which is either of the form "function f" if - * it is a function name, or "variable v" if it is not. + * Gets a description of the declaration `vd`, which is either of the form + * "function f", "variable v" or "class c". */ -string describe(VarDecl vd) { +string describeVarDecl(VarDecl vd) { if vd = any(Function f).getId() then result = "function " + vd.getName() else if vd = any(ClassDefinition c).getIdentifier() then result = "class " + vd.getName() - else if (vd = any(ImportSpecifier im).getLocal() or vd = any(ImportEqualsDeclaration im).getId()) then - result = "import " + vd.getName() else result = "variable " + vd.getName() } -from VarDecl vd, UnusedLocal v -where v = vd.getVariable() and - // exclude variables mentioned in JSDoc comments in externs - not mentionedInJSDocComment(v) and - // exclude variables used to filter out unwanted properties - not isPropertyFilter(v) and - // exclude imports of React that are implicitly referenced by JSX - not isReactImportForJSX(v) and - // exclude names that are used as types - not isUsedAsType(vd) and - // exclude names that are used as namespaces from inside a type - not isUsedAsNamespace(vd) and - // exclude decorated functions and classes - not isDecorated(vd) and - // exclude names of enum members; they also define property names - not isEnumMember(vd) and - // ignore ambient declarations - too noisy - not vd.isAmbient() -select vd, "Unused " + describe(vd) + "." +/** + * An import statement that provides variable declarations. + */ +class ImportVarDeclProvider extends Stmt { + + ImportVarDeclProvider() { + this instanceof ImportDeclaration or + this instanceof ImportEqualsDeclaration + } + + /** + * Gets a variable declaration of this import. + */ + VarDecl getAVarDecl() { + result = this.(ImportDeclaration).getASpecifier().getLocal() or + result = this.(ImportEqualsDeclaration).getId() + } + + /** + * Gets an unacceptable unused variable declared by this import. + */ + UnusedLocal getAnUnacceptableUnusedLocal() { + result = getAVarDecl().getVariable() and + not whitelisted(result) + } + +} + +/** + * Holds if it is acceptable that `v` is unused. + */ +predicate whitelisted(UnusedLocal v) { + // exclude variables mentioned in JSDoc comments in externs + mentionedInJSDocComment(v) or + // exclude variables used to filter out unwanted properties + isPropertyFilter(v) or + // exclude imports of React that are implicitly referenced by JSX + isReactImportForJSX(v) or + // exclude names that are used as types + exists (VarDecl vd | + v = vd.getVariable() | + isUsedAsType(vd) or + // exclude names that are used as namespaces from inside a type + isUsedAsNamespace(vd)or + // exclude decorated functions and classes + isDecorated(vd) or + // exclude names of enum members; they also define property names + isEnumMember(vd) or + // ignore ambient declarations - too noisy + vd.isAmbient() + ) +} + +/** + * Holds if `vd` declares an unused variable that does not come from an import statement, as explained by `msg`. + */ +predicate unusedNonImports(VarDecl vd, string msg) { + exists (UnusedLocal v | + v = vd.getVariable() and + msg = "Unused " + describeVarDecl(vd) + "." and + not vd = any(ImportVarDeclProvider p).getAVarDecl() and + not whitelisted(v) + ) +} + +/** + * Holds if `provider` declares one or more unused variables, as explained by `msg`. + */ +predicate unusedImports(ImportVarDeclProvider provider, string msg) { + exists (string imports, string names | + imports = pluralize("import", count(provider.getAnUnacceptableUnusedLocal())) and + names = strictconcat(provider.getAnUnacceptableUnusedLocal().getName(), ", ") and + msg = "Unused " + imports + " " + names + "." + ) +} + +from ASTNode sel, string msg +where unusedNonImports(sel, msg) or + unusedImports(sel, msg) +select sel, msg diff --git a/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql b/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql index 70c89715bf8..48ae4811244 100644 --- a/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql +++ b/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql @@ -45,5 +45,4 @@ class OmittedArrayElement extends ArrayExpr { } from OmittedArrayElement ae -where not ae.getFile().getFileType().isTypeScript() // ignore quirks in TypeScript tokenizer select ae, "Avoid omitted array elements." \ No newline at end of file diff --git a/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql b/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql index 2eef68fe5f4..fc52539be8d 100644 --- a/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql +++ b/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql @@ -36,8 +36,7 @@ where s.hasSemicolonInserted() and asi = strictcount(Stmt ss | asi(sc, ss, true)) and nstmt = strictcount(Stmt ss | asi(sc, ss, _)) and perc = ((1-asi/nstmt)*100).floor() and - perc >= 90 and - not s.getFile().getFileType().isTypeScript() // ignore some quirks in the TypeScript tokenizer + perc >= 90 select (LastLineOf)s, "Avoid automated semicolon insertion " + "(" + perc + "% of all statements in $@ have an explicit semicolon).", sc, "the enclosing " + sctype \ No newline at end of file diff --git a/javascript/ql/src/Security/CWE-089/SqlInjection.actual b/javascript/ql/src/Security/CWE-089/SqlInjection.actual new file mode 100644 index 00000000000..e69de29bb2d diff --git a/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql b/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql index 84e11138f7b..0629224ed50 100644 --- a/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql +++ b/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql @@ -39,7 +39,6 @@ where misleadingIndentationCandidate(ctrl, s1, s2) and f.hasIndentation(ctrlStartLine, indent, _) and f.hasIndentation(startLine1, indent, _) and f.hasIndentation(startLine2, indent, _) and - not s2 instanceof EmptyStmt and - not f.getFileType().isTypeScript() // ignore quirks in TypeScript tokenizer + not s2 instanceof EmptyStmt select (FirstLineOf)s2, "The indentation of this statement suggests that it is controlled by $@, while in fact it is not.", (FirstLineOf)ctrl, "this statement" \ No newline at end of file diff --git a/javascript/ql/src/semmle/javascript/Util.qll b/javascript/ql/src/semmle/javascript/Util.qll index caa1105191e..d669643be58 100644 --- a/javascript/ql/src/semmle/javascript/Util.qll +++ b/javascript/ql/src/semmle/javascript/Util.qll @@ -11,3 +11,16 @@ bindingset[s] string capitalize(string s) { result = s.charAt(0).toUpperCase() + s.suffix(1) } + + /** + * Gets the pluralization for `n` occurrences of `noun`. + * + * For example, the pluralization of `"function"` for `n = 2` is `"functions"`. + */ +bindingset[noun, n] +string pluralize(string noun, int n) { + if n = 1 then + result = noun + else + result = noun + "s" +} \ No newline at end of file diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 8fab8b9064f..e2c5f325b0d 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -93,7 +93,7 @@ abstract class Configuration extends string { } /** - * Holds if `source` is a source of flow labelled with `lbl` that is relevant + * Holds if `source` is a source of flow labeled with `lbl` that is relevant * for this configuration. */ predicate isSource(DataFlow::Node source, FlowLabel lbl) { @@ -108,7 +108,7 @@ abstract class Configuration extends string { } /** - * Holds if `sink` is a sink of flow labelled with `lbl` that is relevant + * Holds if `sink` is a sink of flow labeled with `lbl` that is relevant * for this configuration. */ predicate isSink(DataFlow::Node sink, FlowLabel lbl) { @@ -146,6 +146,7 @@ abstract class Configuration extends string { */ predicate isBarrier(DataFlow::Node node) { exists (BarrierGuardNode guard | + not guard instanceof LabeledBarrierGuardNode and isBarrierGuard(guard) and guard.blocks(node) ) @@ -161,6 +162,17 @@ abstract class Configuration extends string { */ predicate isBarrier(DataFlow::Node src, DataFlow::Node trg, FlowLabel lbl) { none() } + /** + * Holds if flow with label `lbl` cannot flow into `node`. + */ + predicate isLabeledBarrier(DataFlow::Node node, FlowLabel lbl) { + exists (LabeledBarrierGuardNode guard | + lbl = guard.getALabel() and + isBarrierGuard(guard) and + guard.blocks(node) + ) + } + /** * Holds if data flow node `guard` can act as a barrier when appearing * in a condition. @@ -297,7 +309,16 @@ abstract class BarrierGuardNode extends DataFlow::Node { * Holds if this node blocks expression `e` provided it evaluates to `outcome`. */ abstract predicate blocks(boolean outcome, Expr e); +} +/** + * A guard node that only blocks specific labels. + */ +abstract class LabeledBarrierGuardNode extends BarrierGuardNode { + /** + * Get a flow label blocked by this guard node. + */ + abstract FlowLabel getALabel(); } /** @@ -570,7 +591,8 @@ private predicate flowThroughCall(DataFlow::Node input, DataFlow::Node invk, ret.asExpr() = f.getAReturnedExpr() and calls(invk, f) and // Do not consider partial calls reachableFromInput(f, invk, input, ret, cfg, summary) and - not cfg.isBarrier(ret, invk) + not cfg.isBarrier(ret, invk) and + not cfg.isLabeledBarrier(invk, summary.getEndLabel()) ) } @@ -641,7 +663,8 @@ private predicate flowStep(DataFlow::Node pred, DataFlow::Configuration cfg, flowThroughProperty(pred, succ, cfg, summary) ) and not cfg.isBarrier(succ) and - not cfg.isBarrier(pred, succ) + not cfg.isBarrier(pred, succ) and + not cfg.isLabeledBarrier(succ, summary.getEndLabel()) } /** @@ -666,6 +689,7 @@ private predicate reachableFromSource(DataFlow::Node nd, DataFlow::Configuration exists (FlowLabel lbl | isSource(nd, cfg, lbl) and not cfg.isBarrier(nd) and + not cfg.isLabeledBarrier(nd, lbl) and summary = MkPathSummary(false, false, lbl, lbl) ) or @@ -684,7 +708,8 @@ private predicate onPath(DataFlow::Node nd, DataFlow::Configuration cfg, PathSummary summary) { reachableFromSource(nd, cfg, summary) and isSink(nd, cfg, summary.getEndLabel()) and - not cfg.isBarrier(nd) + not cfg.isBarrier(nd) and + not cfg.isLabeledBarrier(nd, summary.getEndLabel()) or exists (DataFlow::Node mid, PathSummary stepSummary | reachableFromSource(nd, cfg, summary) and diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll index 8a33ebb5c80..53cf8767f6d 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll @@ -130,7 +130,7 @@ module TaintTracking { * configurations it is used in. * * Note: For performance reasons, all subclasses of this class should be part - * of the standard library. Override `Configuration::isTaintSanitizerGuard` + * of the standard library. Override `Configuration::isSanitizer` * for analysis-specific taint steps. */ abstract class AdditionalSanitizerGuardNode extends SanitizerGuardNode { @@ -159,6 +159,12 @@ module TaintTracking { } + /** + * A sanitizer guard node that only blocks specific flow labels. + */ + abstract class LabeledSanitizerGuardNode extends SanitizerGuardNode, DataFlow::LabeledBarrierGuardNode { + } + /** * DEPRECATED: Override `Configuration::isAdditionalTaintStep` or use * `AdditionalTaintStep` instead. diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll index 69646cec816..95fb763f8cd 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll @@ -74,10 +74,12 @@ predicate localFlowStep(DataFlow::Node pred, DataFlow::Node succ, any(DataFlow::AdditionalFlowStep afs).step(pred, succ) and predlbl = succlbl or exists (boolean vp | configuration.isAdditionalFlowStep(pred, succ, vp) | - if vp = false and (predlbl = FlowLabel::data() or predlbl = FlowLabel::taint()) then - succlbl = FlowLabel::taint() - else - predlbl = succlbl + vp = true and + predlbl = succlbl + or + vp = false and + (predlbl = FlowLabel::data() or predlbl = FlowLabel::taint()) and + succlbl = FlowLabel::taint() ) or configuration.isAdditionalFlowStep(pred, succ, predlbl, succlbl) diff --git a/javascript/ql/src/semmle/javascript/frameworks/Express.qll b/javascript/ql/src/semmle/javascript/frameworks/Express.qll index 9c97daf932d..eb9467049bc 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Express.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Express.qll @@ -488,6 +488,31 @@ module Express { override string getKind() { result = kind } + + override predicate isUserControlledObject() { + kind = "body" and + exists (ExpressLibraries::BodyParser bodyParser, RouteHandlerExpr expr | + expr.getBody() = rh and + bodyParser.producesUserControlledObjects() and + bodyParser.flowsToExpr(expr.getAMatchingAncestor()) + ) + or + // If we can't find the middlewares for the route handler, + // but all known body parsers are deep, assume req.body is a deep object. + kind = "body" and + 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") + or + exists (DataFlow::PropRead base | + // `req.query.name` + base.accesses(request, "query") and + this = base.getAPropertyReference(_) + ) + ) + } } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll b/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll index e7538c54612..6f184880b16 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll @@ -230,4 +230,53 @@ module ExpressLibraries { } + /** + * An instance of the Express `body-parser` middleware. + */ + class BodyParser extends DataFlow::InvokeNode { + string kind; + + BodyParser() { + this = DataFlow::moduleImport("body-parser").getACall() and kind = "json" + or + exists (string moduleName | + (moduleName = "body-parser" or moduleName = "express") and + (kind = "json" or kind = "urlencoded") and + this = DataFlow::moduleMember(moduleName, kind).getACall() + ) + } + + /** + * Holds if this is a JSON body parser. + */ + predicate isJson() { + kind = "json" + } + + /** + * Holds if this is a URL-encoded body parser. + */ + predicate isUrlEncoded() { + kind = "urlencoded" + } + + /** + * Holds if this is an extended URL-encoded body parser. + * + * The extended URL-encoding allows for nested objects, like JSON. + */ + predicate isExtendedUrlEncoded() { + kind = "urlencoded" and + not getOptionArgument(0, "extended").mayHaveBooleanValue(false) + } + + /** + * Holds if this parses the input as JSON or extended URL-encoding, resulting + * in user-controlled objects (as opposed to user-controlled strings). + */ + predicate producesUserControlledObjects() { + isJson() or isExtendedUrlEncoded() + } + } + } diff --git a/javascript/ql/src/semmle/javascript/security/TaintedObject.qll b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll new file mode 100644 index 00000000000..efb5545b2c3 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll @@ -0,0 +1,119 @@ +/** + * Provides methods for reasoning about the flow of deeply tainted objects, such as JSON objects + * parsed from user-controlled data. + * + * Deeply tainted objects are arrays or objects with user-controlled property names, containing + * tainted values or deeply tainted objects in their properties. + * + * To track deeply tainted objects, a flow-tracking configuration should generally include the following: + * + * 1. One or more sinks associated with the label `TaintedObject::label()`. + * 2. The sources from `TaintedObject::isSource`. + * 3. The flow steps from `TaintedObject::step`. + * 4. The sanitizing guards `TaintedObject::SanitizerGuard`. + */ +import javascript + +module TaintedObject { + private import DataFlow + + private class TaintedObjectLabel extends FlowLabel { + TaintedObjectLabel() { this = "tainted-object" } + } + + /** + * Gets the flow label representing a deeply tainted object. + * + * A "tainted object" is an array or object whose property values are all assumed to be tainted as well. + * + * Note that the presence of the this label generally implies the presence of the `taint` label as well. + */ + FlowLabel label() { result instanceof TaintedObjectLabel } + + /** + * Holds for the flows steps that are relevant for tracking user-controlled JSON objects. + */ + predicate step(Node src, Node trg, FlowLabel inlbl, FlowLabel outlbl) { + // JSON parsers map tainted inputs to tainted JSON + (inlbl = FlowLabel::data() or inlbl = FlowLabel::taint()) and + outlbl = label() and + exists (JsonParserCall parse | + src = parse.getInput() and + trg = parse.getOutput()) + or + // Property reads preserve deep object taint. + inlbl = label() and + outlbl = label() and + trg.(PropRead).getBase() = src + or + // Property projection preserves deep object taint + inlbl = label() and + outlbl = label() and + trg.(PropertyProjection).getObject() = src + or + // Extending objects preserves deep object taint + inlbl = label() and + outlbl = label() and + exists (ExtendCall call | + src = call.getAnOperand() and + trg = call + or + src = call.getASourceOperand() and + trg = call.getDestinationOperand().getALocalSource()) + } + + /** + * Holds if `node` is a source of JSON taint and label is the JSON taint label. + */ + predicate isSource(Node source, FlowLabel label) { + source instanceof Source and label = label() + } + + /** + * A source of a user-controlled deep object. + */ + abstract class Source extends DataFlow::Node {} + + /** Request input accesses as a JSON source. */ + private class RequestInputAsSource extends Source { + RequestInputAsSource() { + this.(HTTP::RequestInputAccess).isUserControlledObject() + } + } + + /** + * Sanitizer guard that blocks deep object taint. + */ + abstract class SanitizerGuard extends TaintTracking::LabeledSanitizerGuardNode { + override FlowLabel getALabel() { + result = label() + } + } + + /** + * A test of form `typeof x === "something"`, preventing `x` from being an object in some cases. + */ + private class TypeTestGuard extends SanitizerGuard, ValueNode { + override EqualityTest astNode; + TypeofExpr typeof; + boolean polarity; + + TypeTestGuard() { + astNode.getAnOperand() = typeof and + ( + // typeof x === "object" sanitizes `x` when it evaluates to false + astNode.getAnOperand().getStringValue() = "object" and + polarity = astNode.getPolarity().booleanNot() + or + // typeof x === "string" sanitizes `x` when it evaluates to true + astNode.getAnOperand().getStringValue() != "object" and + polarity = astNode.getPolarity() + ) + } + + override predicate sanitizes(boolean outcome, Expr e) { + polarity = outcome and + e = typeof.getOperand() + } + } +} diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirect.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirect.qll index 35bb5fa950d..dc331b7d2a8 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirect.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirect.qll @@ -65,6 +65,11 @@ module ClientSideUrlRedirect { queryAccess(pred, succ) and f instanceof DocumentUrl and g = DataFlow::FlowLabel::taint() + or + // preserve document.url label in step from `location` to `location.href` + f instanceof DocumentUrl and + g instanceof DocumentUrl and + succ.(DataFlow::PropRead).accesses(pred, "href") } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll b/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll index 9c031728d7e..c338e6ced50 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll @@ -4,6 +4,7 @@ */ import javascript +import semmle.javascript.security.TaintedObject module NosqlInjection { /** @@ -14,7 +15,16 @@ module NosqlInjection { /** * A data flow sink for SQL-injection vulnerabilities. */ - abstract class Sink extends DataFlow::Node { } + abstract class Sink extends DataFlow::Node { + /** + * Gets a flow label relevant for this sink. + * + * Defaults to deeply tainted objects only. + */ + DataFlow::FlowLabel getAFlowLabel() { + result = TaintedObject::label() + } + } /** * A sanitizer for SQL-injection vulnerabilities. @@ -31,8 +41,12 @@ module NosqlInjection { source instanceof Source } - override predicate isSink(DataFlow::Node sink) { - sink instanceof Sink + override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) { + TaintedObject::isSource(source, label) + } + + override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) { + sink.(Sink).getAFlowLabel() = label } override predicate isSanitizer(DataFlow::Node node) { @@ -40,12 +54,20 @@ module NosqlInjection { node instanceof Sanitizer } - override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { + override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) { + guard instanceof TaintedObject::SanitizerGuard + } + + override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node trg, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl) { + TaintedObject::step(src, trg, inlbl, outlbl) + or // additional flow step to track taint through NoSQL query objects + inlbl = TaintedObject::label() and + outlbl = TaintedObject::label() and exists (NoSQL::Query query, DataFlow::SourceNode queryObj | queryObj.flowsToExpr(query) and - queryObj.flowsTo(succ) and - pred = queryObj.getAPropertyWrite().getRhs() + queryObj.flowsTo(trg) and + src = queryObj.getAPropertyWrite().getRhs() ) } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll index a1138a0eb5e..964f9e988ef 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll @@ -10,6 +10,11 @@ import semmle.javascript.security.dataflow.DOM abstract class RemoteFlowSource extends DataFlow::Node { /** Gets a string that describes the type of this remote flow source. */ abstract string getSourceType(); + + /** + * Holds if this can be a user-controlled object, such as a JSON object parsed from user-controlled data. + */ + predicate isUserControlledObject() { none() } } /** diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.expected b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.expected new file mode 100644 index 00000000000..dd96b0bcfb7 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.expected @@ -0,0 +1,7 @@ +| tst.ts:122:19:122:32 | import("type") | type | type | +| tst.ts:123:26:123:39 | import("type") | type | type | +| tst.ts:124:28:124:46 | import("namespace") | namespace | namespace | +| tst.ts:125:35:125:53 | import("namespace") | namespace | namespace | +| tst.ts:126:28:126:42 | import("value") | value | value | +| tst.ts:127:37:127:51 | import("value") | value | value | +| tst.ts:128:38:130:3 | import( ... ce'\\n ) | awkard-namespace | namespace | diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.ql b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.ql new file mode 100644 index 00000000000..9d61276462d --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.ql @@ -0,0 +1,13 @@ +import javascript + +string getKind(ImportTypeExpr imprt) { + if imprt.isTypeAccess() then + result = "type" + else if imprt.isNamespaceAccess() then + result = "namespace" + else + result = "value" +} + +from ImportTypeExpr imprt +select imprt, imprt.getPath(), getKind(imprt) diff --git a/javascript/ql/test/library-tests/Util/capitalize.expected b/javascript/ql/test/library-tests/Util/capitalize.expected new file mode 100644 index 00000000000..a92936e701a --- /dev/null +++ b/javascript/ql/test/library-tests/Util/capitalize.expected @@ -0,0 +1 @@ +| X | X | Xx | XX | Xx | XX | diff --git a/javascript/ql/test/library-tests/Util/capitalize.ql b/javascript/ql/test/library-tests/Util/capitalize.ql new file mode 100644 index 00000000000..0e90efece9b --- /dev/null +++ b/javascript/ql/test/library-tests/Util/capitalize.ql @@ -0,0 +1,3 @@ +import semmle.javascript.Util + +select capitalize("x"), capitalize("X"), capitalize("xx"), capitalize("XX"), capitalize("Xx"), capitalize("xX") diff --git a/javascript/ql/test/library-tests/Util/pluralize.expected b/javascript/ql/test/library-tests/Util/pluralize.expected new file mode 100644 index 00000000000..09cfc2d49bf --- /dev/null +++ b/javascript/ql/test/library-tests/Util/pluralize.expected @@ -0,0 +1 @@ +| xs | x | xs | xs | diff --git a/javascript/ql/test/library-tests/Util/pluralize.ql b/javascript/ql/test/library-tests/Util/pluralize.ql new file mode 100644 index 00000000000..7b7f0ed053d --- /dev/null +++ b/javascript/ql/test/library-tests/Util/pluralize.ql @@ -0,0 +1,3 @@ +import semmle.javascript.Util + +select pluralize("x", 0), pluralize("x", 1), pluralize("x", 2), pluralize("x", -1) diff --git a/javascript/ql/test/library-tests/Util/tst.js b/javascript/ql/test/library-tests/Util/tst.js new file mode 100644 index 00000000000..a9eba82d534 --- /dev/null +++ b/javascript/ql/test/library-tests/Util/tst.js @@ -0,0 +1 @@ +// used by qltest to identify the language diff --git a/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected b/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected index fc187550b36..7e1ae154cfe 100644 --- a/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected +++ b/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected @@ -1,6 +1,8 @@ -| decorated.ts:1:9:1:21 | actionHandler | Unused import actionHandler. | +| decorated.ts:1:1:1:126 | import ... where'; | Unused import actionHandler. | | decorated.ts:4:10:4:12 | fun | Unused function fun. | | externs.js:6:5:6:13 | iAmUnused | Unused variable iAmUnused. | +| multi-imports.js:1:1:1:29 | import ... om 'x'; | Unused imports a, b, d. | +| multi-imports.js:2:1:2:42 | import ... om 'x'; | Unused imports alphabetically, ordered. | | typeoftype.ts:9:7:9:7 | y | Unused variable y. | -| unusedShadowed.ts:1:8:1:8 | T | Unused import T. | -| unusedShadowed.ts:2:8:2:13 | object | Unused import object. | +| unusedShadowed.ts:1:1:1:26 | import ... where'; | Unused import T. | +| unusedShadowed.ts:2:1:2:31 | import ... where'; | Unused import object. | diff --git a/javascript/ql/test/query-tests/Declarations/UnusedVariable/multi-imports.js b/javascript/ql/test/query-tests/Declarations/UnusedVariable/multi-imports.js new file mode 100644 index 00000000000..b32fa341a83 --- /dev/null +++ b/javascript/ql/test/query-tests/Declarations/UnusedVariable/multi-imports.js @@ -0,0 +1,4 @@ +import {a, b, c, d} from 'x'; +import {ordered, alphabetically} from 'x'; + +c(); diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts b/javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts deleted file mode 100644 index 01ce984f8e3..00000000000 --- a/javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts +++ /dev/null @@ -1,12 +0,0 @@ -function foo(arg) { - console.log(arg); - console.log(arg); - console.log(arg); - console.log(arg); - console.log(arg); - console.log(arg); - console.log(arg); - console.log(arg); - console.log(arg); - console.log(`Unknown option '${arg}'.`); -} diff --git a/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected index 7a99482adb8..e6c84156dd0 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected @@ -1,5 +1,7 @@ | mongodb.js:18:16:18:20 | query | This query depends on $@. | mongodb.js:13:19:13:26 | req.body | a user-provided value | -| mongodb.js:39:16:39:20 | query | This query depends on $@. | mongodb.js:34:19:34:33 | req.query.title | a user-provided value | +| mongodb.js:32:18:32:45 | { title ... itle) } | This query depends on $@. | mongodb.js:26:19:26:26 | req.body | a user-provided value | +| mongodb.js:54:16:54:20 | query | This query depends on $@. | mongodb.js:49:19:49:33 | req.query.title | a user-provided value | +| mongodb_bodySafe.js:29:16:29:20 | query | This query depends on $@. | mongodb_bodySafe.js:24:19:24:33 | req.query.title | a user-provided value | | mongoose.js:27:20:27:24 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value | | mongoose.js:30:25:30:29 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value | | mongoose.js:33:24:33:28 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js b/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js index 900cad1cbba..00f3422ca40 100644 --- a/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js +++ b/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js @@ -16,6 +16,21 @@ app.post('/documents/find', (req, res) => { // NOT OK: query is tainted by user-provided object value doc.find(query); + + // OK: user-data is coerced to a string + doc.find({ title: '' + query.body.title }); + + // OK: throws unless user-data is a string + doc.find({ title: query.body.title.substr(1) }); + + let title = req.body.title; + if (typeof title === "string") { + // OK: input checked to be a string + doc.find({ title: title }); + + // NOT OK: input is parsed as JSON after string check + doc.find({ title: JSON.parse(title) }); + } }); }); diff --git a/javascript/ql/test/query-tests/Security/CWE-089/mongodb_bodySafe.js b/javascript/ql/test/query-tests/Security/CWE-089/mongodb_bodySafe.js new file mode 100644 index 00000000000..61a87bfaa5a --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/mongodb_bodySafe.js @@ -0,0 +1,31 @@ +const express = require('express'), + mongodb = require('mongodb'), + bodyParser = require('body-parser'); + +const MongoClient = mongodb.MongoClient; + +const app = express(); + +app.use(bodyParser.urlencoded({ extended: false })); + +app.post('/documents/find', (req, res) => { + const query = {}; + query.title = req.body.title; + MongoClient.connect('mongodb://localhost:27017/test', (err, db) => { + let doc = db.collection('doc'); + + // OK: req.body is safe + doc.find(query); + }); +}); + +app.post('/documents/find', (req, res) => { + const query = {}; + query.title = req.query.title; + MongoClient.connect('mongodb://localhost:27017/test', (err, db) => { + let doc = db.collection('doc'); + + // NOT OK: regardless of body parser, query value is still tainted + doc.find(query); + }); +});