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.
+
+
+
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++.
+
+
+
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:
+
+
+
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