Merge master into next.

`master` up to and including cfe0b8803a.
This commit is contained in:
Aditya Sharad
2018-10-25 15:32:47 +01:00
122 changed files with 4155 additions and 1297 deletions

17
.lgtm.yml Executable file
View File

@@ -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

View File

@@ -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. |

View File

@@ -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* |

View File

@@ -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.

View File

@@ -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. |

View File

@@ -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)

View File

@@ -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"

View File

@@ -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;

View File

@@ -0,0 +1,28 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Code immediately following a <code>goto</code> or <code>break</code> 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.
</p>
</overview>
<recommendation>
<p>
If the unreachable code is necessary, move the <code>goto</code> or <code>break</code> statement to
after the code. Otherwise, delete the unreachable code.
</p>
</recommendation>
<example><sample src="DeadCodeGoto.cpp" />
</example>
<references>
<li>
The CERT C Secure Coding Standard: <a href="https://wiki.sei.cmu.edu/confluence/display/c/MSC12-C.+Detect+and+remove+code+that+has+no+effect+or+is+never+executed">MSC12-C. Detect and remove code that has no effect or is never executed</a>.
</li>
</references>
</qhelp>

View File

@@ -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()

View File

@@ -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));
}

View File

@@ -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."

View File

@@ -0,0 +1,7 @@
void f()
{
for (signed char i = 0; i < 100; i--)
{
// code ...
}
}

View File

@@ -0,0 +1,27 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>A <code>for-loop</code> iteration expression goes backwards with respect of the initialization statement and condition expression.</p>
<p>This warning indicates that a <code>for-loop</code> might not function as intended.</p>
</overview>
<recommendation>
<p>To fix this issue, check that the loop condition is correct and change the iteration expression to match.</p>
</recommendation>
<example>
<p>In the following example, the initialization statement (<code>i = 0</code>) and the condition expression (<code>i &lt; 100</code>) indicate that the intended iteration expression should have been incrementing, but instead a postfix decrement operator is used (<code>i--</code>).</p>
<sample src="inconsistentLoopDirection.c" />
<p>To fix this issue, change the iteration expression to match the direction of the initialization statement and the condition expression: <code>i++</code>.</p>
</example>
<references>
<li><a href="https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2012/58teb7hd(v=vs.110)">warning C6293: Ill-defined for-loop: counts down from minimum</a>
</li>
</references>
</qhelp>

View File

@@ -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

View File

@@ -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);
// ...

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>This query indicates that there is a call to a function of the <code>CreateProcess*</code> family of functions, which introduces a security vulnerability.</p>
</overview>
<recommendation>
<p>Do not use <code>NULL</code> for the <code>lpApplicationName</code> argument to the <code>CreateProcess*</code> function.</p>
<p>If you pass <code>NULL</code> for <code>lpApplicationName</code>, use quotation marks around the executable path in <code>lpCommandLine</code>.</p>
</recommendation>
<example>
<p>In the following example, <code>CreateProcessW</code> is called with a <code>NULL</code> value for <code>lpApplicationName</code>,
and the value for <code>lpCommandLine</code> that represent the application path is not quoted and has spaces in it.</p>
<p>If an attacker has access to the file system, they can elevate privileges by creating a file such as <code>C:\Program.exe</code> that will be executed instead of the intended application.</p>
<sample src="UnsafeCreateProcessCall.cpp" />
<p>To fix this issue, specify a valid string for <code>lpApplicationName</code>, or quote the path for <code>lpCommandLine</code>. For example:</p>
<p><code>(LPWSTR)L"\"C:\\Program Files\\MyApp\"", // lpCommandLine</code></p>
</example>
<references>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa">CreateProcessA function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessw">CreateProcessW function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessasusera">CreateProcessAsUserA function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessasuserw">CreateProcessAsUserW function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createprocesswithlogonw">CreateProcessWithLogonW function (Microsoft documentation).</a>
</li>
<li>
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createprocesswithtokenw">CreateProcessWithTokenW function (Microsoft documentation).</a>
</li>
</references>
</qhelp>

View File

@@ -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

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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

View File

@@ -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))
}
}

View File

@@ -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))
}
}

View File

@@ -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))
}
}

View File

@@ -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))
}
}

View File

@@ -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())
)
}

View File

@@ -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`.

View File

@@ -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) {

View File

@@ -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 ... |

View File

@@ -0,0 +1 @@
Critical/DeadCodeGoto.ql

View File

@@ -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:
}

View File

@@ -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 ...
}
}
}

View File

@@ -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
}

View File

@@ -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). |

View File

@@ -0,0 +1 @@
Likely Bugs/Likely Typos/inconsistentLoopDirection.ql

View File

@@ -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"<fake_password!>",
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"<fake_password!>",
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"<fake_password!>",
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"<fake_password!>",
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);
}

View File

@@ -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. |

View File

@@ -0,0 +1 @@
Security/CWE/CWE-428/UnsafeCreateProcessCall.ql

View File

@@ -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. |

View File

@@ -66,3 +66,25 @@ public:
n = new MyNumber(200); // GOOD: deleted in base class
}
};
template<class T>
class TemplateWithDestructor
{
public:
TemplateWithDestructor(int len) {
ptr = new char[len]; // GOOD
}
~TemplateWithDestructor()
{
delete [] ptr;
}
private:
char *ptr;
};
void test() {
TemplateWithDestructor<int *> *t_ptr = new TemplateWithDestructor<int *>(10);
//delete t_ptr; --- destructor never used
}

View File

@@ -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;
};

View File

@@ -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 |

View File

@@ -50,3 +50,45 @@ MyValue g7(bool c)
DONOTHING
// BAD [the alert here is unfortunately placed]
}
typedef void MYVOID;
MYVOID g8()
{
// GOOD
}
template<class T, class U>
class TypePair
{
public:
typedef T first;
typedef U second;
};
TypePair<void, int>::first g9()
{
// GOOD (the return type amounts to void)
}
TypePair<void, int>::second g10()
{
// BAD (the return type amounts to int)
}
template<class T>
typename TypePair<void, T>::first g11()
{
// GOOD (the return type amounts to void)
}
template<class T>
typename TypePair<void, T>::second g12()
{
// BAD (the return type amounts to T / int)
}
void instantiate()
{
g11<int>();
g12<int>();
}

View File

@@ -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:

View File

@@ -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

View File

@@ -46,10 +46,12 @@ namespace Semmle.Extraction.CSharp
/// <param name="commandLineArguments">Arguments passed to csc.</param>
/// <param name="compilationIn">The Roslyn compilation.</param>
/// <param name="options">Extractor options.</param>
/// <param name="roslynArgs">The arguments passed to Roslyn.</param>
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.
/// </summary>
public void LogDiagnostics()
/// <param name="roslynArgs">The arguments passed to Roslyn.</param>
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);

View File

@@ -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<string, string>
{
{ "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 ?? "<null>"}");
return;
}
}
ArgsWithResponse = AddDefaultResponse(CscRsp, options.CompilerArguments).ToArray();
}
void SkipExtractionBecause(string reason)
@@ -87,7 +99,7 @@ namespace Semmle.Extraction.CSharp
/// <summary>
/// The file csc.rsp.
/// </summary>
public string CscRsp => Path.Combine(FrameworkPath, csc_rsp);
string CscRsp => Path.Combine(FrameworkPath, csc_rsp);
/// <summary>
/// Should we skip extraction?
@@ -103,5 +115,25 @@ namespace Semmle.Extraction.CSharp
/// Gets additional reference directories - the compiler directory.
/// </summary>
public string AdditionalReferenceDirectories => SpecifiedCompiler != null ? Path.GetDirectoryName(SpecifiedCompiler) : null;
/// <summary>
/// Adds @csc.rsp to the argument list to mimic csc.exe.
/// </summary>
/// <param name="responseFile">The full pathname of csc.rsp.</param>
/// <param name="args">The other command line arguments.</param>
/// <returns>Modified list of arguments.</returns>
static IEnumerable<string> AddDefaultResponse(string responseFile, IEnumerable<string> args)
{
return SuppressDefaultResponseFile(args) && File.Exists(responseFile) ?
args :
new[] { "@" + responseFile }.Concat(args);
}
static bool SuppressDefaultResponseFile(IEnumerable<string> args)
{
return args.Any(arg => new[] { "/noconfig", "-noconfig" }.Contains(arg.ToLowerInvariant()));
}
public readonly string[] ArgsWithResponse;
}
}

View File

@@ -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<string> args)
{
return args.Any(arg => new[] { "/noconfig", "-noconfig" }.Contains(arg.ToLowerInvariant()));
}
/// <summary>
/// Adds @csc.rsp to the argument list to mimic csc.exe.
/// </summary>
/// <param name="responseFile">The full pathname of csc.rsp.</param>
/// <param name="args">The other command line arguments.</param>
/// <returns>Modified list of arguments.</returns>
static IEnumerable<string> AddDefaultResponse(string responseFile, IEnumerable<string> args)
{
return SuppressDefaultResponseFile(args) && File.Exists(responseFile) ?
args :
new[] { "@" + responseFile }.Concat(args);
}
/// <summary>
/// Gets the complete list of locations to locate references.
/// </summary>

View File

@@ -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" });

View File

@@ -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
/// <summary>
/// Computes the hash of <paramref name="filePath"/>.
/// </summary>
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;

View File

@@ -1,5 +0,0 @@
{
"sdk": {
"version": "2.1.103"
}
}

View File

@@ -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
)

View File

@@ -17,10 +17,10 @@ variables in the same sequence.</p>
<p>The following example shows a program running two threads, which deadlocks because <code>thread1</code> holds <code>lock1</code> and
is waiting to acquire <code>lock2</code>, whilst <code>thread2</code> holds <code>lock2</code> and is waiting to acquire <code>lock1</code>.</p>
<sample src="LockOrder.cs" />
<sample src="LockOrderBad.cs" />
<p>This problem is resolved by reordering the <code>lock</code> variables as shown below.</p>
<sample src="LockOrderFix.cs" />
<sample src="LockOrderGood.cs" />
</example>
<references>

View File

@@ -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()

View File

@@ -1,3 +1,6 @@
using System;
using System.Threading;
class Deadlock
{
private readonly Object lock1 = new Object();

View File

@@ -1,3 +1,6 @@
using System;
using System.Threading;
class DeadlockFixed
{
private readonly Object lock1 = new Object();

View File

@@ -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

View File

@@ -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)

View File

@@ -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)
}
/**

View File

@@ -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"

View File

@@ -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, _)
)
}
}
}

View File

@@ -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()
)
}

View File

@@ -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 |

View File

@@ -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

View File

@@ -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 |

View File

@@ -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<object> _:
return o.ToString(); // null guarded
case Action<string> a:
return a.ToString(); // not null (but not a guard)
case "":
return o.ToString(); // null guarded
case null:
return o.ToString(); // not null guarded
default:
return o.ToString(); // null guarded
}
}
}

View File

@@ -0,0 +1,12 @@
| Guards.cs:154:24:154:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:153:13:153:34 | case Action<Object>: | true |
| Guards.cs:158:24:158:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:153:13:153:34 | case Action<Object>: | false |
| Guards.cs:158:24:158:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:155:13:155:34 | case Action<String> a: | false |
| Guards.cs:158:24:158:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:157:13:157:20 | case ...: | true |
| Guards.cs:160:24:160:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:153:13:153:34 | case Action<Object>: | false |
| Guards.cs:160:24:160:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:155:13:155:34 | case Action<String> a: | false |
| Guards.cs:160:24:160:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:157:13:157:20 | case ...: | false |
| Guards.cs:160:24:160:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:159:13:159:22 | case ...: | true |
| Guards.cs:162:24:162:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:153:13:153:34 | case Action<Object>: | false |
| Guards.cs:162:24:162:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:155:13:155:34 | case Action<String> a: | false |
| Guards.cs:162:24:162:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:157:13:157:20 | case ...: | false |
| Guards.cs:162:24:162:24 | access to parameter o | Guards.cs:151:17:151:17 | access to parameter o | Guards.cs:159:13:159:22 | case ...: | false |

View File

@@ -0,0 +1,6 @@
import csharp
import semmle.code.csharp.controlflow.Guards
from MatchingGuardedExpr mge, Expr e, CaseStmt cs, boolean isMatch
where mge.isGuardedBy(e, cs, isMatch)
select mge, e, cs, isMatch

View File

@@ -23,3 +23,8 @@
| Guards.cs:97:31:97:31 | access to parameter s |
| Guards.cs:116:27:116:51 | access to field Field |
| Guards.cs:125:29:125:30 | access to parameter s1 |
| Guards.cs:132:16:132:16 | access to parameter s |
| Guards.cs:138:20:138:20 | access to parameter s |
| Guards.cs:154:24:154:24 | access to parameter o |
| Guards.cs:158:24:158:24 | access to parameter o |
| Guards.cs:162:24:162:24 | access to parameter o |

View File

@@ -0,0 +1,3 @@
| Guards.cs:114:14:114:38 | access to field Field | Guards.cs:113:21:113:45 | access to field Field | true |
| Guards.cs:116:27:116:51 | access to field Field | Guards.cs:115:17:115:41 | access to field Field | false |
| Guards.cs:125:29:125:30 | access to parameter s1 | Guards.cs:125:18:125:19 | access to parameter s1 | false |

View File

@@ -0,0 +1,6 @@
import csharp
import semmle.code.csharp.controlflow.Guards
from NullnessGuardedExpr nge, Expr e, boolean isNull
where nge.isGuardedBy(e, isNull)
select nge, e, isNull

View File

@@ -1,18 +1,66 @@
using System;
class Foo
class LocalTest
{
// BAD: b is flagged.
Object a, b, c;
void f()
void F()
{
lock (a) lock (b) { }
lock (b) lock (a) { }
lock (b) lock (a) { }
lock (a) lock (b) { }
lock (b) lock (c) { }
lock (c) lock (b) { }
lock (a) lock (a) { }
lock (a) lock (a) { }
lock (a) lock (b) lock(c) { }
}
void G()
{
lock (a) lock (c) lock(b) { }
}
void H()
{
lock (a) lock(c) { }
}
}
class GlobalTest
{
// BAD: b is flagged.
static Object a, b, c;
void F()
{
lock (a) G();
}
void G()
{
lock (b) H();
lock (c) I();
}
void H()
{
lock (c) { }
}
void I()
{
lock (b) { }
}
}
class LambdaTest
{
// BAD: a is flagged.
static Object a, b;
void F()
{
Action lock_a = () => { lock(a) { } };
Action lock_b = () => { lock(b) { } };
lock(a) lock_b();
lock(b) lock_a();
}
}
// semmle-extractor-options: /r:System.Runtime.Extensions.dll /r:System.Threading.dll /r:System.Threading.Thread.dll

View File

@@ -1,10 +1,4 @@
| LockOrder.cs:9:18:9:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:10:18:10:29 | lock (...) {...} | here |
| LockOrder.cs:9:18:9:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:11:18:11:29 | lock (...) {...} | here |
| LockOrder.cs:10:18:10:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:9:18:9:29 | lock (...) {...} | here |
| LockOrder.cs:10:18:10:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:12:18:12:29 | lock (...) {...} | here |
| LockOrder.cs:11:18:11:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:9:18:9:29 | lock (...) {...} | here |
| LockOrder.cs:11:18:11:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:12:18:12:29 | lock (...) {...} | here |
| LockOrder.cs:12:18:12:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:10:18:10:29 | lock (...) {...} | here |
| LockOrder.cs:12:18:12:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:11:18:11:29 | lock (...) {...} | here |
| LockOrder.cs:13:18:13:29 | lock (...) {...} | Inconsistent lock sequence. The locks c and b are locked in a different sequence $@. | LockOrder.cs:14:18:14:29 | lock (...) {...} | here |
| LockOrder.cs:14:18:14:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and c are locked in a different sequence $@. | LockOrder.cs:13:18:13:29 | lock (...) {...} | here |
| LockOrder.cs:6:15:6:15 | b | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrder.cs:6:18:6:18 | c | c | LockOrder.cs:10:18:10:37 | lock (...) {...} | b | LockOrder.cs:10:27:10:37 | lock (...) {...} | c | LockOrder.cs:15:18:15:37 | lock (...) {...} | c | LockOrder.cs:15:27:15:37 | lock (...) {...} | b |
| LockOrder.cs:27:22:27:22 | b | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrder.cs:27:25:27:25 | c | c | LockOrder.cs:36:8:36:20 | lock (...) {...} | b | LockOrder.cs:42:9:42:20 | lock (...) {...} | c | LockOrder.cs:37:8:37:20 | lock (...) {...} | c | LockOrder.cs:47:9:47:20 | lock (...) {...} | b |
| LockOrder.cs:54:19:54:19 | a | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrder.cs:54:22:54:22 | b | b | LockOrder.cs:61:9:61:25 | lock (...) {...} | a | LockOrder.cs:59:33:59:43 | lock (...) {...} | b | LockOrder.cs:62:9:62:25 | lock (...) {...} | b | LockOrder.cs:58:33:58:43 | lock (...) {...} | a |
| LockOrderBad.cs:6:29:6:33 | lock1 | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrderBad.cs:7:29:7:33 | lock2 | lock2 | LockOrderBad.cs:11:9:19:9 | lock (...) {...} | lock1 | LockOrderBad.cs:16:13:18:13 | lock (...) {...} | lock2 | LockOrderBad.cs:24:9:32:9 | lock (...) {...} | lock2 | LockOrderBad.cs:29:13:31:13 | lock (...) {...} | lock1 |

View File

@@ -0,0 +1,34 @@
using System;
using System.Threading;
class Deadlock
{
private readonly Object lock1 = new Object();
private readonly Object lock2 = new Object();
public void thread1()
{
lock (lock1)
{
Console.Out.WriteLine("Thread 1 acquired lock1");
Thread.Sleep(10);
Console.Out.WriteLine("Thread 1 waiting on lock2");
lock (lock2) // Deadlock here
{
}
}
}
public void thread2()
{
lock (lock2)
{
Console.Out.WriteLine("Thread 2 acquired lock2");
Thread.Sleep(10);
Console.Out.WriteLine("Thread 2 waiting on lock1");
lock (lock1) // Deadlock here
{
}
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Threading;
class DeadlockFixed
{
private readonly Object lock1 = new Object();
private readonly Object lock2 = new Object();
public void thread1()
{
lock (lock1)
{
Console.Out.WriteLine("Thread 1 acquired lock1");
Thread.Sleep(10);
Console.Out.WriteLine("Thread 1 waiting on lock2");
lock (lock2)
{
}
}
}
public void thread2()
{
lock (lock1) // Fixed
{
Console.Out.WriteLine("Thread 2 acquired lock1");
Thread.Sleep(10);
Console.Out.WriteLine("Thread 2 waiting on lock2");
lock (lock2) // Fixed
{
}
}
}
}

View File

@@ -167,6 +167,15 @@ predicate overFlowTest(ComparisonExpr comp) {
comp.getGreaterOperand().(IntegerLiteral).getIntValue() = 0
}
predicate concurrentModificationTest(BinaryExpr test) {
exists(IfStmt ifstmt, ThrowStmt throw, RefType exc |
ifstmt.getCondition() = test and
(ifstmt.getThen() = throw or ifstmt.getThen().(SingletonBlock).getStmt() = throw) and
throw.getExpr().(ClassInstanceExpr).getConstructedType() = exc and
exc.hasQualifiedName("java.util", "ConcurrentModificationException")
)
}
/**
* Holds if `test` and `guard` are equality tests of the same integral variable v with constants `c1` and `c2`.
*/
@@ -202,13 +211,13 @@ where
)
else
if constCondSimple(test, _)
then (
constCondSimple(test, testIsTrue) and reason = "" and reasonElem = test // dummy reason element
) else
then constCondSimple(test, testIsTrue) and reason = "" and reasonElem = test // dummy reason element
else
exists(CondReason r |
constCond(test, testIsTrue, r) and reason = ", because of $@" and reasonElem = r.getCond()
)
) and
not overFlowTest(test) and
not concurrentModificationTest(test) and
not exists(AssertStmt assert | assert.getExpr() = test.getParent*())
select test, "Test is always " + testIsTrue + reason + ".", reasonElem, "this condition"

View File

@@ -9,6 +9,7 @@
import java
import semmle.code.java.dataflow.Guards
import semmle.code.java.dataflow.ParityAnalysis
import semmle.code.java.security.DataFlow
from File f, string tag

View File

@@ -0,0 +1,68 @@
import java
private import SSA
private import RangeUtils
private newtype TBound =
TBoundZero() or
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
TBoundExpr(Expr e) {
e.(FieldRead).getField() instanceof ArrayLengthField and
not exists(SsaVariable v | e = v.getAUse())
}
/**
* A bound that may be inferred for an expression plus/minus an integer delta.
*/
abstract class Bound extends TBound {
abstract string toString();
/** Gets an expression that equals this bound plus `delta`. */
abstract Expr getExpr(int delta);
/** Gets an expression that equals this bound. */
Expr getExpr() { result = getExpr(0) }
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
}
}
/**
* The bound that corresponds to the integer 0. This is used to represent all
* integer bounds as bounds are always accompanied by an added integer delta.
*/
class ZeroBound extends Bound, TBoundZero {
override string toString() { result = "0" }
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
}
/**
* A bound corresponding to the value of an SSA variable.
*/
class SsaBound extends Bound, TBoundSsa {
/** Gets the SSA variable that equals this bound. */
SsaVariable getSsa() { this = TBoundSsa(result) }
override string toString() { result = getSsa().toString() }
override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 }
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
}
/**
* A bound that corresponds to the value of a specific expression that might be
* interesting, but isn't otherwise represented by the value of an SSA variable.
*/
class ExprBound extends Bound, TBoundExpr {
override string toString() { result = getExpr().toString() }
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
getExpr().hasLocationInfo(path, sl, sc, el, ec)
}
}

View File

@@ -0,0 +1,352 @@
/**
* Provides inferences of the form: `e` equals `b + v` modulo `m` where `e` is
* an expression, `b` is a `Bound` (typically zero or the value of an SSA
* variable), and `v` is an integer in the range `[0 .. m-1]`.
*/
import java
private import SSA
private import RangeUtils
private import semmle.code.java.controlflow.Guards
import Bound
/**
* Holds if `e + delta` equals `v` at `pos`.
*/
private predicate valueFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta) {
ssaUpdateStep(v, e, delta) and pos.hasReadOfVar(v)
or
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = eqFlowCond(v, e, delta, true, testIsTrue) and
guardDirectlyControlsSsaRead(guard, pos, testIsTrue)
)
}
/**
* Holds if `add` is the addition of `larg` and `rarg`, neither of which are
* `ConstantIntegerExpr`s.
*/
private predicate nonConstAddition(Expr add, Expr larg, Expr rarg) {
(
exists(AddExpr a | a = add |
larg = a.getLeftOperand() and
rarg = a.getRightOperand()
)
or
exists(AssignAddExpr a | a = add |
larg = a.getDest() and
rarg = a.getRhs()
)
) and
not larg instanceof ConstantIntegerExpr and
not rarg instanceof ConstantIntegerExpr
}
/**
* Holds if `sub` is the subtraction of `larg` and `rarg`, where `rarg` is not
* a `ConstantIntegerExpr`.
*/
private predicate nonConstSubtraction(Expr sub, Expr larg, Expr rarg) {
(
exists(SubExpr s | s = sub |
larg = s.getLeftOperand() and
rarg = s.getRightOperand()
)
or
exists(AssignSubExpr s | s = sub |
larg = s.getDest() and
rarg = s.getRhs()
)
) and
not rarg instanceof ConstantIntegerExpr
}
/** Gets an expression that is the remainder modulo `mod` of `arg`. */
private Expr modExpr(Expr arg, int mod) {
exists(RemExpr rem |
result = rem and
arg = rem.getLeftOperand() and
rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = mod and
mod >= 2
)
or
exists(CompileTimeConstantExpr c |
mod = 2.pow([1 .. 30]) and
c.getIntValue() = mod - 1 and
result.(AndBitwiseExpr).hasOperands(arg, c)
)
or
result.(ParExpr).getExpr() = modExpr(arg, mod)
}
/**
* Gets a guard that tests whether `v` is congruent with `val` modulo `mod` on
* its `testIsTrue` branch.
*/
private Guard moduloCheck(SsaVariable v, int val, int mod, boolean testIsTrue) {
exists(Expr rem, CompileTimeConstantExpr c, int r, boolean polarity |
result.isEquality(rem, c, polarity) and
c.getIntValue() = r and
rem = modExpr(v.getAUse(), mod) and
(
testIsTrue = polarity and val = r
or
testIsTrue = polarity.booleanNot() and
mod = 2 and
val = 1 - r and
(r = 0 or r = 1)
)
)
}
/**
* Holds if a guard ensures that `v` at `pos` is congruent with `val` modulo `mod`.
*/
private predicate moduloGuardedRead(SsaVariable v, SsaReadPosition pos, int val, int mod) {
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = moduloCheck(v, val, mod, testIsTrue) and
guardControlsSsaRead(guard, pos, testIsTrue)
)
}
/** Holds if `factor` is a power of 2 that divides `mask`. */
bindingset[mask]
private predicate andmaskFactor(int mask, int factor) {
mask % factor = 0 and
factor = 2.pow([1 .. 30])
}
/** Holds if `e` is evenly divisible by `factor`. */
private predicate evenlyDivisibleExpr(Expr e, int factor) {
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() |
e.(MulExpr).getAnOperand() = c and factor = k.abs() and factor >= 2
or
e.(AssignMulExpr).getSource() = c and factor = k.abs() and factor >= 2
or
e.(LShiftExpr).getRightOperand() = c and factor = 2.pow(k) and k > 0
or
e.(AssignLShiftExpr).getRhs() = c and factor = 2.pow(k) and k > 0
or
e.(AndBitwiseExpr).getAnOperand() = c and factor = max(int f | andmaskFactor(k, f))
or
e.(AssignAndExpr).getSource() = c and factor = max(int f | andmaskFactor(k, f))
)
}
private predicate id(BasicBlock x, BasicBlock y) { x = y }
private predicate idOf(BasicBlock x, int y) = equivalenceRelation(id/2)(x, y)
private int getId(BasicBlock bb) { idOf(bb, result) }
/**
* Holds if `inp` is an input to `phi` along `edge` and this input has index `r`
* in an arbitrary 1-based numbering of the input edges to `phi`.
*/
private predicate rankedPhiInput(
SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int r
) {
edge.phiInput(phi, inp) and
edge = rank[r](SsaReadPositionPhiInputEdge e |
e.phiInput(phi, _)
|
e
order by
getId(e.getOrigBlock())
)
}
/**
* Holds if `rix` is the number of input edges to `phi`.
*/
private predicate maxPhiInputRank(SsaPhiNode phi, int rix) {
rix = max(int r | rankedPhiInput(phi, _, _, r))
}
private int gcdLim() { result = 128 }
/**
* Gets the greatest common divisor of `x` and `y`. This is restricted to small
* inputs and the case when `x` and `y` are not relatively prime.
*/
private int gcd(int x, int y) {
result != 1 and
result = x.abs() and
y = 0 and
x in [-gcdLim() .. gcdLim()]
or
result = gcd(y, x % y) and y != 0 and x in [-gcdLim() .. gcdLim()]
}
/**
* Gets the remainder of `val` modulo `mod`.
*
* For `mod = 0` the result equals `val` and for `mod > 1` the result is within
* the range `[0 .. mod-1]`.
*/
bindingset[val, mod]
private int remainder(int val, int mod) {
mod = 0 and result = val
or
mod > 1 and result = ((val % mod) + mod) % mod
}
/**
* Holds if `inp` is an input to `phi` and equals `phi` modulo `mod` along `edge`.
*/
private predicate phiSelfModulus(
SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int mod
) {
exists(SsaBound phibound, int v, int m |
edge.phiInput(phi, inp) and
phibound.getSsa() = phi and
ssaModulus(inp, edge, phibound, v, m) and
mod = gcd(m, v) and
mod != 1
)
}
/**
* Holds if `b + val` modulo `mod` is a candidate congruence class for `phi`.
*/
private predicate phiModulusInit(SsaPhiNode phi, Bound b, int val, int mod) {
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge |
edge.phiInput(phi, inp) and
ssaModulus(inp, edge, b, val, mod)
)
}
/**
* Holds if all inputs to `phi` numbered `1` to `rix` are equal to `b + val` modulo `mod`.
*/
private predicate phiModulusRankStep(SsaPhiNode phi, Bound b, int val, int mod, int rix) {
rix = 0 and
phiModulusInit(phi, b, val, mod)
or
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge, int v1, int m1 |
mod != 1 and
val = remainder(v1, mod)
|
exists(int v2, int m2 |
rankedPhiInput(phi, inp, edge, rix) and
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
ssaModulus(inp, edge, b, v2, m2) and
mod = gcd(gcd(m1, m2), v1 - v2)
)
or
exists(int m2 |
rankedPhiInput(phi, inp, edge, rix) and
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
phiSelfModulus(phi, inp, edge, m2) and
mod = gcd(m1, m2)
)
)
}
/**
* Holds if `phi` is equal to `b + val` modulo `mod`.
*/
private predicate phiModulus(SsaPhiNode phi, Bound b, int val, int mod) {
exists(int r |
maxPhiInputRank(phi, r) and
phiModulusRankStep(phi, b, val, mod, r)
)
}
/**
* Holds if `v` at `pos` is equal to `b + val` modulo `mod`.
*/
private predicate ssaModulus(SsaVariable v, SsaReadPosition pos, Bound b, int val, int mod) {
phiModulus(v, b, val, mod) and pos.hasReadOfVar(v)
or
b.(SsaBound).getSsa() = v and pos.hasReadOfVar(v) and val = 0 and mod = 0
or
exists(Expr e, int val0, int delta |
exprModulus(e, b, val0, mod) and
valueFlowStepSsa(v, pos, e, delta) and
val = remainder(val0 + delta, mod)
)
or
moduloGuardedRead(v, pos, val, mod) and b instanceof ZeroBound
}
/**
* Holds if `e` is equal to `b + val` modulo `mod`.
*
* There are two cases for the modulus:
* - `mod = 0`: The equality `e = b + val` is an ordinary equality.
* - `mod > 1`: `val` lies within the range `[0 .. mod-1]`.
*/
cached
predicate exprModulus(Expr e, Bound b, int val, int mod) {
e = b.getExpr(val) and mod = 0
or
evenlyDivisibleExpr(e, mod) and val = 0 and b instanceof ZeroBound
or
exists(SsaVariable v, SsaReadPositionBlock bb |
ssaModulus(v, bb, b, val, mod) and
e = v.getAUse() and
bb.getBlock() = e.getBasicBlock()
)
or
exists(Expr mid, int val0, int delta |
exprModulus(mid, b, val0, mod) and
valueFlowStep(e, mid, delta) and
val = remainder(val0 + delta, mod)
)
or
exists(ConditionalExpr cond, int v1, int v2, int m1, int m2 |
cond = e and
condExprBranchModulus(cond, true, b, v1, m1) and
condExprBranchModulus(cond, false, b, v2, m2) and
mod = gcd(gcd(m1, m2), v1 - v2) and
mod != 1 and
val = remainder(v1, mod)
)
or
exists(Bound b1, Bound b2, int v1, int v2, int m1, int m2 |
addModulus(e, true, b1, v1, m1) and
addModulus(e, false, b2, v2, m2) and
mod = gcd(m1, m2) and
mod != 1 and
val = remainder(v1 + v2, mod)
|
b = b1 and b2 instanceof ZeroBound
or
b = b2 and b1 instanceof ZeroBound
)
or
exists(int v1, int v2, int m1, int m2 |
subModulus(e, true, b, v1, m1) and
subModulus(e, false, any(ZeroBound zb), v2, m2) and
mod = gcd(m1, m2) and
mod != 1 and
val = remainder(v1 - v2, mod)
)
}
private predicate condExprBranchModulus(
ConditionalExpr cond, boolean branch, Bound b, int val, int mod
) {
exprModulus(cond.getTrueExpr(), b, val, mod) and branch = true
or
exprModulus(cond.getFalseExpr(), b, val, mod) and branch = false
}
private predicate addModulus(Expr add, boolean isLeft, Bound b, int val, int mod) {
exists(Expr larg, Expr rarg | nonConstAddition(add, larg, rarg) |
exprModulus(larg, b, val, mod) and isLeft = true
or
exprModulus(rarg, b, val, mod) and isLeft = false
)
}
private predicate subModulus(Expr sub, boolean isLeft, Bound b, int val, int mod) {
exists(Expr larg, Expr rarg | nonConstSubtraction(sub, larg, rarg) |
exprModulus(larg, b, val, mod) and isLeft = true
or
exprModulus(rarg, b, val, mod) and isLeft = false
)
}

View File

@@ -1,9 +1,12 @@
/**
* DEPRECATED: Use semmle.code.java.dataflow.ModulusAnalysis instead.
*
* Parity Analysis.
*
* The analysis is implemented as an abstract interpretation over the
* two-valued domain `{even, odd}`.
*/
import java
private import SSA
private import RangeUtils
@@ -12,35 +15,35 @@ private import SignAnalysis
private import semmle.code.java.Reflection
/** Gets an expression that is the remainder modulo 2 of `arg`. */
private Expr mod2(Expr arg) {
deprecated private Expr mod2(Expr arg) {
exists(RemExpr rem |
result = rem and
arg = rem.getLeftOperand() and
rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = 2
) or
result.(AndBitwiseExpr).hasOperands(arg, any(CompileTimeConstantExpr c | c.getIntValue() = 1)) or
)
or
result.(AndBitwiseExpr).hasOperands(arg, any(CompileTimeConstantExpr c | c.getIntValue() = 1))
or
result.(ParExpr).getExpr() = mod2(arg)
}
/** An expression that calculates remainder modulo 2. */
private class Mod2 extends Expr {
Mod2() {
this = mod2(_)
}
deprecated private class Mod2 extends Expr {
Mod2() { this = mod2(_) }
/** Gets the argument of this remainder operation. */
Expr getArg() {
this = mod2(result)
}
Expr getArg() { this = mod2(result) }
}
/**
* Parity represented as booleans. Even corresponds to `false` and odd
* corresponds to `true`.
*/
class Parity extends boolean {
deprecated class Parity extends boolean {
Parity() { this = true or this = false }
predicate isEven() { this = false }
predicate isOdd() { this = true }
}
@@ -48,7 +51,7 @@ class Parity extends boolean {
* Gets a condition that performs a parity check on `v`, such that `v` has
* the given parity if the condition evaluates to `testIsTrue`.
*/
private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
deprecated private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
exists(Mod2 rem, CompileTimeConstantExpr c, int r, boolean polarity |
result.isEquality(rem, c, polarity) and
c.getIntValue() = r and
@@ -56,7 +59,8 @@ private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
rem.getArg() = v.getAUse() and
(testIsTrue = true or testIsTrue = false) and
(
r = 0 and parity = testIsTrue.booleanXor(polarity) or
r = 0 and parity = testIsTrue.booleanXor(polarity)
or
r = 1 and parity = testIsTrue.booleanXor(polarity).booleanNot()
)
)
@@ -65,25 +69,36 @@ private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
/**
* Gets the parity of `e` if it can be directly determined.
*/
private Parity certainExprParity(Expr e) {
deprecated private Parity certainExprParity(Expr e) {
exists(int i | e.(ConstantIntegerExpr).getIntValue() = i |
if i % 2 = 0 then result.isEven() else result.isOdd()
) or
e.(LongLiteral).getValue().regexpMatch(".*[02468]") and result.isEven() or
e.(LongLiteral).getValue().regexpMatch(".*[13579]") and result.isOdd() or
)
or
e.(LongLiteral).getValue().regexpMatch(".*[02468]") and result.isEven()
or
e.(LongLiteral).getValue().regexpMatch(".*[13579]") and result.isOdd()
or
not exists(e.(ConstantIntegerExpr).getIntValue()) and
(
result = certainExprParity(e.(ParExpr).getExpr()) or
result = certainExprParity(e.(ParExpr).getExpr())
or
exists(Guard guard, SsaVariable v, boolean testIsTrue |
guard = parityCheck(v, result, testIsTrue) and
e = v.getAUse() and
guardControls_v2(guard, e.getBasicBlock(), testIsTrue)
) or
)
or
exists(SsaVariable arr, int arrlen, FieldAccess len |
e = len and
len.getField() instanceof ArrayLengthField and
len.getQualifier() = arr.getAUse() and
arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getFirstDimensionSize() = arrlen and
arr
.(SsaExplicitUpdate)
.getDefiningExpr()
.(VariableAssign)
.getSource()
.(ArrayCreationExpr)
.getFirstDimensionSize() = arrlen and
if arrlen % 2 = 0 then result.isEven() else result.isOdd()
)
)
@@ -92,114 +107,211 @@ private Parity certainExprParity(Expr e) {
/**
* Gets the expression that defines the array length that equals `len`, if any.
*/
private Expr arrLenDef(FieldAccess len) {
deprecated private Expr arrLenDef(FieldAccess len) {
exists(SsaVariable arr |
len.getField() instanceof ArrayLengthField and
len.getQualifier() = arr.getAUse() and
arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = result
arr
.(SsaExplicitUpdate)
.getDefiningExpr()
.(VariableAssign)
.getSource()
.(ArrayCreationExpr)
.getDimension(0) = result
)
}
/** Gets a possible parity for `v`. */
private Parity ssaParity(SsaVariable v) {
deprecated private Parity ssaParity(SsaVariable v) {
exists(VariableUpdate def | def = v.(SsaExplicitUpdate).getDefiningExpr() |
result = exprParity(def.(VariableAssign).getSource()) or
exists(EnhancedForStmt for | def = for.getVariable()) and (result = true or result = false) or
result = exprParity(def.(UnaryAssignExpr).getExpr()).booleanNot() or
result = exprParity(def.(VariableAssign).getSource())
or
exists(EnhancedForStmt for | def = for.getVariable()) and
(result = true or result = false)
or
result = exprParity(def.(UnaryAssignExpr).getExpr()).booleanNot()
or
exists(AssignOp a | a = def and result = exprParity(a))
) or
result = fieldParity(v.(SsaImplicitUpdate).getSourceVariable().getVariable()) or
result = fieldParity(v.(SsaImplicitInit).getSourceVariable().getVariable()) or
exists(Parameter p | v.(SsaImplicitInit).isParameterDefinition(p) and (result = true or result = false)) or
)
or
result = fieldParity(v.(SsaImplicitUpdate).getSourceVariable().getVariable())
or
result = fieldParity(v.(SsaImplicitInit).getSourceVariable().getVariable())
or
exists(Parameter p |
v.(SsaImplicitInit).isParameterDefinition(p) and
(result = true or result = false)
)
or
result = ssaParity(v.(SsaPhiNode).getAPhiInput())
}
/** Gets a possible parity for `f`. */
private Parity fieldParity(Field f) {
result = exprParity(f.getAnAssignedValue()) or
exists(UnaryAssignExpr u | u.getExpr() = f.getAnAccess() and (result = true or result = false)) or
exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprParity(a)) or
exists(ReflectiveFieldAccess rfa | rfa.inferAccessedField() = f and (result = true or result = false))
deprecated private Parity fieldParity(Field f) {
result = exprParity(f.getAnAssignedValue())
or
if f.fromSource() then
not exists(f.getInitializer()) and result.isEven()
else
exists(UnaryAssignExpr u |
u.getExpr() = f.getAnAccess() and
(result = true or result = false)
)
or
exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprParity(a))
or
exists(ReflectiveFieldAccess rfa |
rfa.inferAccessedField() = f and
(result = true or result = false)
)
or
if f.fromSource()
then not exists(f.getInitializer()) and result.isEven()
else (
result = true or result = false
)
}
/** Holds if the parity of `e` is too complicated to determine. */
private predicate unknownParity(Expr e) {
e instanceof AssignDivExpr or
e instanceof AssignRShiftExpr or
e instanceof AssignURShiftExpr or
e instanceof DivExpr or
e instanceof RShiftExpr or
e instanceof URShiftExpr or
exists(Type fromtyp | e.(CastExpr).getExpr().getType() = fromtyp and not fromtyp instanceof IntegralType) or
e instanceof ArrayAccess and e.getType() instanceof IntegralType or
e instanceof MethodAccess and e.getType() instanceof IntegralType or
e instanceof ClassInstanceExpr and e.getType() instanceof IntegralType or
e.getType() instanceof FloatingPointType or
deprecated private predicate unknownParity(Expr e) {
e instanceof AssignDivExpr
or
e instanceof AssignRShiftExpr
or
e instanceof AssignURShiftExpr
or
e instanceof DivExpr
or
e instanceof RShiftExpr
or
e instanceof URShiftExpr
or
exists(Type fromtyp |
e.(CastExpr).getExpr().getType() = fromtyp and not fromtyp instanceof IntegralType
)
or
e instanceof ArrayAccess and e.getType() instanceof IntegralType
or
e instanceof MethodAccess and e.getType() instanceof IntegralType
or
e instanceof ClassInstanceExpr and e.getType() instanceof IntegralType
or
e.getType() instanceof FloatingPointType
or
e.getType() instanceof CharacterType
}
/** Gets a possible parity for `e`. */
private Parity exprParity(Expr e) {
result = certainExprParity(e) or
deprecated private Parity exprParity(Expr e) {
result = certainExprParity(e)
or
not exists(certainExprParity(e)) and
(
result = exprParity(e.(ParExpr).getExpr()) or
result = exprParity(arrLenDef(e)) or
exists(SsaVariable v | v.getAUse() = e | result = ssaParity(v)) and not exists(arrLenDef(e)) or
result = exprParity(e.(ParExpr).getExpr())
or
result = exprParity(arrLenDef(e))
or
exists(SsaVariable v | v.getAUse() = e | result = ssaParity(v)) and
not exists(arrLenDef(e))
or
exists(FieldAccess fa | fa = e |
not exists(SsaVariable v | v.getAUse() = fa) and
not exists(arrLenDef(e)) and
result = fieldParity(fa.getField())
) or
)
or
exists(VarAccess va | va = e |
not exists(SsaVariable v | v.getAUse() = va) and
not va instanceof FieldAccess and
(result = true or result = false)
) or
result = exprParity(e.(AssignExpr).getSource()) or
result = exprParity(e.(PlusExpr).getExpr()) or
result = exprParity(e.(PostIncExpr).getExpr()) or
result = exprParity(e.(PostDecExpr).getExpr()) or
result = exprParity(e.(PreIncExpr).getExpr()).booleanNot() or
result = exprParity(e.(PreDecExpr).getExpr()).booleanNot() or
result = exprParity(e.(MinusExpr).getExpr()) or
result = exprParity(e.(BitNotExpr).getExpr()).booleanNot() or
unknownParity(e) and (result = true or result = false) or
)
or
result = exprParity(e.(AssignExpr).getSource())
or
result = exprParity(e.(PlusExpr).getExpr())
or
result = exprParity(e.(PostIncExpr).getExpr())
or
result = exprParity(e.(PostDecExpr).getExpr())
or
result = exprParity(e.(PreIncExpr).getExpr()).booleanNot()
or
result = exprParity(e.(PreDecExpr).getExpr()).booleanNot()
or
result = exprParity(e.(MinusExpr).getExpr())
or
result = exprParity(e.(BitNotExpr).getExpr()).booleanNot()
or
unknownParity(e) and
(result = true or result = false)
or
exists(Parity p1, Parity p2, AssignOp a |
a = e and
p1 = exprParity(a.getDest()) and
p2 = exprParity(a.getRhs())
|
a instanceof AssignAddExpr and result = p1.booleanXor(p2) or
a instanceof AssignSubExpr and result = p1.booleanXor(p2) or
a instanceof AssignMulExpr and result = p1.booleanAnd(p2) or
a instanceof AssignRemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or
a instanceof AssignAndExpr and result = p1.booleanAnd(p2) or
a instanceof AssignOrExpr and result = p1.booleanOr(p2) or
a instanceof AssignXorExpr and result = p1.booleanXor(p2) or
a instanceof AssignLShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(a.getRhs()))
) or
|
a instanceof AssignAddExpr and result = p1.booleanXor(p2)
or
a instanceof AssignSubExpr and result = p1.booleanXor(p2)
or
a instanceof AssignMulExpr and result = p1.booleanAnd(p2)
or
a instanceof AssignRemExpr and
(
p2.isEven() and result = p1
or
p2.isOdd() and
(result = true or result = false)
)
or
a instanceof AssignAndExpr and result = p1.booleanAnd(p2)
or
a instanceof AssignOrExpr and result = p1.booleanOr(p2)
or
a instanceof AssignXorExpr and result = p1.booleanXor(p2)
or
a instanceof AssignLShiftExpr and
(
result.isEven()
or
result = p1 and not strictlyPositive(a.getRhs())
)
)
or
exists(Parity p1, Parity p2, BinaryExpr bin |
bin = e and
p1 = exprParity(bin.getLeftOperand()) and
p2 = exprParity(bin.getRightOperand())
|
bin instanceof AddExpr and result = p1.booleanXor(p2) or
bin instanceof SubExpr and result = p1.booleanXor(p2) or
bin instanceof MulExpr and result = p1.booleanAnd(p2) or
bin instanceof RemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or
bin instanceof AndBitwiseExpr and result = p1.booleanAnd(p2) or
bin instanceof OrBitwiseExpr and result = p1.booleanOr(p2) or
bin instanceof XorBitwiseExpr and result = p1.booleanXor(p2) or
bin instanceof LShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(bin.getRightOperand()))
) or
result = exprParity(e.(ConditionalExpr).getTrueExpr()) or
result = exprParity(e.(ConditionalExpr).getFalseExpr()) or
|
bin instanceof AddExpr and result = p1.booleanXor(p2)
or
bin instanceof SubExpr and result = p1.booleanXor(p2)
or
bin instanceof MulExpr and result = p1.booleanAnd(p2)
or
bin instanceof RemExpr and
(
p2.isEven() and result = p1
or
p2.isOdd() and
(result = true or result = false)
)
or
bin instanceof AndBitwiseExpr and result = p1.booleanAnd(p2)
or
bin instanceof OrBitwiseExpr and result = p1.booleanOr(p2)
or
bin instanceof XorBitwiseExpr and result = p1.booleanXor(p2)
or
bin instanceof LShiftExpr and
(
result.isEven()
or
result = p1 and not strictlyPositive(bin.getRightOperand())
)
)
or
result = exprParity(e.(ConditionalExpr).getTrueExpr())
or
result = exprParity(e.(ConditionalExpr).getFalseExpr())
or
result = exprParity(e.(CastExpr).getExpr())
)
}
@@ -207,15 +319,15 @@ private Parity exprParity(Expr e) {
/**
* Gets the parity of `e` if it can be uniquely determined.
*/
Parity getExprParity(Expr e) {
result = exprParity(e) and 1 = count(exprParity(e))
}
deprecated Parity getExprParity(Expr e) { result = exprParity(e) and 1 = count(exprParity(e)) }
/**
* DEPRECATED: Use semmle.code.java.dataflow.ModulusAnalysis instead.
*
* Holds if the parity can be determined for both sides of `comp`. The boolean
* `eqparity` indicates whether the two sides have equal or opposite parity.
*/
predicate parityComparison(ComparisonExpr comp, boolean eqparity) {
deprecated predicate parityComparison(ComparisonExpr comp, boolean eqparity) {
exists(Expr left, Expr right, boolean lpar, boolean rpar |
comp.getLeftOperand() = left and
comp.getRightOperand() = right and
@@ -224,4 +336,3 @@ predicate parityComparison(ComparisonExpr comp, boolean eqparity) {
eqparity = lpar.booleanXor(rpar).booleanNot()
)
}

View File

@@ -68,14 +68,16 @@ private import SSA
private import RangeUtils
private import semmle.code.java.controlflow.internal.GuardsLogic
private import SignAnalysis
private import ParityAnalysis
private import ModulusAnalysis
private import semmle.code.java.Reflection
private import semmle.code.java.Collections
private import semmle.code.java.Maps
import Bound
cached private module RangeAnalysisCache {
cached module RangeAnalysisPublic {
cached
private module RangeAnalysisCache {
cached
module RangeAnalysisPublic {
/**
* Holds if `b + delta` is a valid bound for `e`.
* - `upper = true` : `e <= b + delta`
@@ -85,7 +87,8 @@ cached private module RangeAnalysisCache {
* or `NoReason` if the bound was proven directly without the use of a bounding
* condition.
*/
cached predicate bounded(Expr e, Bound b, int delta, boolean upper, Reason reason) {
cached
predicate bounded(Expr e, Bound b, int delta, boolean upper, Reason reason) {
bounded(e, b, delta, upper, _, _, reason)
}
}
@@ -93,59 +96,116 @@ cached private module RangeAnalysisCache {
/**
* Holds if `guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)`.
*/
cached predicate possibleReason(Guard guard) { guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _) }
cached
predicate possibleReason(Guard guard) {
guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)
}
}
private import RangeAnalysisCache
import RangeAnalysisPublic
/**
* Gets a condition that tests whether `v` equals `e + delta`.
*
* If the condition evaluates to `testIsTrue`:
* - `isEq = true` : `v == e + delta`
* - `isEq = false` : `v != e + delta`
*/
private Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsTrue) {
exists(boolean eqpolarity |
result.isEquality(ssaRead(v, delta), e, eqpolarity) and
(testIsTrue = true or testIsTrue = false) and
eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq
)
or
exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0))
}
/**
* Holds if `comp` corresponds to:
* - `upper = true` : `v <= e + delta` or `v < e + delta`
* - `upper = false` : `v >= e + delta` or `v > e + delta`
*/
private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int delta, boolean upper) {
private predicate boundCondition(
ComparisonExpr comp, SsaVariable v, Expr e, int delta, boolean upper
) {
comp.getLesserOperand() = ssaRead(v, delta) and e = comp.getGreaterOperand() and upper = true
or
comp.getGreaterOperand() = ssaRead(v, delta) and e = comp.getLesserOperand() and upper = false
or
exists(SubExpr sub, ConstantIntegerExpr c, int d |
// (v - d) - e < c
comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and
sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and
upper = true and delta = d + c.getIntValue()
comp.getLesserOperand().getProperExpr() = sub and
comp.getGreaterOperand() = c and
sub.getLeftOperand() = ssaRead(v, d) and
sub.getRightOperand() = e and
upper = true and
delta = d + c.getIntValue()
or
// (v - d) - e > c
comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and
sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and
upper = false and delta = d + c.getIntValue()
comp.getGreaterOperand().getProperExpr() = sub and
comp.getLesserOperand() = c and
sub.getLeftOperand() = ssaRead(v, d) and
sub.getRightOperand() = e and
upper = false and
delta = d + c.getIntValue()
or
// e - (v - d) < c
comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and
sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and
upper = false and delta = d - c.getIntValue()
comp.getLesserOperand().getProperExpr() = sub and
comp.getGreaterOperand() = c and
sub.getLeftOperand() = e and
sub.getRightOperand() = ssaRead(v, d) and
upper = false and
delta = d - c.getIntValue()
or
// e - (v - d) > c
comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and
sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and
upper = true and delta = d - c.getIntValue()
comp.getGreaterOperand().getProperExpr() = sub and
comp.getLesserOperand() = c and
sub.getLeftOperand() = e and
sub.getRightOperand() = ssaRead(v, d) and
upper = true and
delta = d - c.getIntValue()
)
}
private predicate gcdInput(int x, int y) {
exists(ComparisonExpr comp, Bound b |
exprModulus(comp.getLesserOperand(), b, _, x) and
exprModulus(comp.getGreaterOperand(), b, _, y)
)
or
exists(int x0, int y0 |
gcdInput(x0, y0) and
x = y0 and
y = x0 % y0
)
}
private int gcd(int x, int y) {
result = x.abs() and y = 0 and gcdInput(x, y)
or
result = gcd(y, x % y) and y != 0 and gcdInput(x, y)
}
/**
* Holds if `comp` is a comparison between `x` and `y` for which `y - x` has a
* fixed value modulo some `mod > 1`, such that the comparison can be
* strengthened by `strengthen` when evaluating to `testIsTrue`.
*/
private predicate modulusComparison(ComparisonExpr comp, boolean testIsTrue, int strengthen) {
exists(
Bound b, int v1, int v2, int mod1, int mod2, int mod, boolean resultIsStrict, int d, int k
|
// If `x <= y` and `x =(mod) b + v1` and `y =(mod) b + v2` then
// `0 <= y - x =(mod) v2 - v1`. By choosing `k =(mod) v2 - v1` with
// `0 <= k < mod` we get `k <= y - x`. If the resulting comparison is
// strict then the strengthening amount is instead `k - 1` modulo `mod`:
// `x < y` means `0 <= y - x - 1 =(mod) k - 1` so `k - 1 <= y - x - 1` and
// thus `k - 1 < y - x` with `0 <= k - 1 < mod`.
exprModulus(comp.getLesserOperand(), b, v1, mod1) and
exprModulus(comp.getGreaterOperand(), b, v2, mod2) and
mod = gcd(mod1, mod2) and
mod != 1 and
(testIsTrue = true or testIsTrue = false) and
(
if comp.isStrict()
then resultIsStrict = testIsTrue
else resultIsStrict = testIsTrue.booleanNot()
) and
(
resultIsStrict = true and d = 1
or
resultIsStrict = false and d = 0
) and
(
testIsTrue = true and k = v2 - v1
or
testIsTrue = false and k = v1 - v2
) and
strengthen = (((k - d) % mod) + mod) % mod
)
}
@@ -157,27 +217,47 @@ private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int
* - `upper = false` : `v >= e + delta`
*/
private Guard boundFlowCond(SsaVariable v, Expr e, int delta, boolean upper, boolean testIsTrue) {
exists(ComparisonExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper, boolean resultIsStrict |
exists(
ComparisonExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper,
boolean resultIsStrict
|
comp = result and
boundCondition(comp, v, e, d1, compIsUpper) and
(testIsTrue = true or testIsTrue = false) and
upper = compIsUpper.booleanXor(testIsTrue.booleanNot()) and
(if comp.isStrict() then resultIsStrict = testIsTrue else resultIsStrict = testIsTrue.booleanNot()) and
(if v.getSourceVariable().getType() instanceof IntegralType then
(upper = true and strengthen = -1 or
upper = false and strengthen = 1)
else
strengthen = 0) and
// A non-strict inequality `x <= y` can be strengthened to `x <= y - 1` if
// `x` and `y` have opposite parities, and a strict inequality `x < y` can
// be similarly strengthened if `x` and `y` have equal parities.
(if parityComparison(comp, resultIsStrict) then d2 = strengthen else d2 = 0) and
(
if comp.isStrict()
then resultIsStrict = testIsTrue
else resultIsStrict = testIsTrue.booleanNot()
) and
(
if v.getSourceVariable().getType() instanceof IntegralType
then (
upper = true and strengthen = -1
or
upper = false and strengthen = 1
) else strengthen = 0
) and
(
exists(int k | modulusComparison(comp, testIsTrue, k) and d2 = strengthen * k)
or
not modulusComparison(comp, testIsTrue, _) and d2 = 0
) and
// A strict inequality `x < y` can be strengthened to `x <= y - 1`.
(resultIsStrict = true and d3 = strengthen or resultIsStrict = false and d3 = 0) and
(
resultIsStrict = true and d3 = strengthen
or
resultIsStrict = false and d3 = 0
) and
delta = d1 + d2 + d3
) or
exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0)) or
result = eqFlowCond(v, e, delta, true, testIsTrue) and (upper = true or upper = false)
)
or
exists(boolean testIsTrue0 |
implies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0)
)
or
result = eqFlowCond(v, e, delta, true, testIsTrue) and
(upper = true or upper = false)
}
private newtype TReason =
@@ -189,14 +269,13 @@ private newtype TReason =
* is due to a specific condition, or `NoReason` if the bound is inferred
* without going through a bounding condition.
*/
abstract class Reason extends TReason {
abstract string toString();
}
class NoReason extends Reason, TNoReason {
override string toString() { result = "NoReason" }
}
abstract class Reason extends TReason { abstract string toString(); }
class NoReason extends Reason, TNoReason { override string toString() { result = "NoReason" } }
class CondReason extends Reason, TCondReason {
Guard getCond() { this = TCondReason(result) }
override string toString() { result = getCond().toString() }
}
@@ -205,15 +284,14 @@ class CondReason extends Reason, TCondReason {
* - `upper = true` : `v <= e + delta`
* - `upper = false` : `v >= e + delta`
*/
private predicate boundFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason) {
exists(SsaExplicitUpdate upd | v = upd and pos.hasReadOfVar(v) and reason = TNoReason() |
upd.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0 and (upper = true or upper = false) or
upd.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or
upd.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or
upd.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or
upd.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or
upd.getDefiningExpr().(AssignOp) = e and delta = 0 and (upper = true or upper = false)
) or
private predicate boundFlowStepSsa(
SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason
) {
ssaUpdateStep(v, e, delta) and
pos.hasReadOfVar(v) and
(upper = true or upper = false) and
reason = TNoReason()
or
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = boundFlowCond(v, e, delta, upper, testIsTrue) and
@@ -223,7 +301,9 @@ private predicate boundFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, i
}
/** Holds if `v != e + delta` at `pos`. */
private predicate unequalFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, Reason reason) {
private predicate unequalFlowStepSsa(
SsaVariable v, SsaReadPosition pos, Expr e, int delta, Reason reason
) {
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = eqFlowCond(v, e, delta, false, testIsTrue) and
@@ -238,16 +318,25 @@ private predicate unequalFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e,
*/
private predicate safeCast(Type fromtyp, Type totyp) {
exists(PrimitiveType pfrom, PrimitiveType pto | pfrom = fromtyp and pto = totyp |
pfrom = pto or
pfrom.hasName("char") and pto.getName().regexpMatch("int|long|float|double") or
pfrom.hasName("byte") and pto.getName().regexpMatch("short|int|long|float|double") or
pfrom.hasName("short") and pto.getName().regexpMatch("int|long|float|double") or
pfrom.hasName("int") and pto.getName().regexpMatch("long|float|double") or
pfrom.hasName("long") and pto.getName().regexpMatch("float|double") or
pfrom.hasName("float") and pto.hasName("double") or
pfrom = pto
or
pfrom.hasName("char") and pto.getName().regexpMatch("int|long|float|double")
or
pfrom.hasName("byte") and pto.getName().regexpMatch("short|int|long|float|double")
or
pfrom.hasName("short") and pto.getName().regexpMatch("int|long|float|double")
or
pfrom.hasName("int") and pto.getName().regexpMatch("long|float|double")
or
pfrom.hasName("long") and pto.getName().regexpMatch("float|double")
or
pfrom.hasName("float") and pto.hasName("double")
or
pfrom.hasName("double") and pto.hasName("float")
) or
safeCast(fromtyp.(BoxedType).getPrimitiveType(), totyp) or
)
or
safeCast(fromtyp.(BoxedType).getPrimitiveType(), totyp)
or
safeCast(fromtyp, totyp.(BoxedType).getPrimitiveType())
}
@@ -255,18 +344,19 @@ private predicate safeCast(Type fromtyp, Type totyp) {
* A cast that can be ignored for the purpose of range analysis.
*/
private class SafeCastExpr extends CastExpr {
SafeCastExpr() {
safeCast(getExpr().getType(), getType())
}
SafeCastExpr() { safeCast(getExpr().getType(), getType()) }
}
/**
* Holds if `typ` is a small integral type with the given lower and upper bounds.
*/
private predicate typeBound(Type typ, int lowerbound, int upperbound) {
typ.(PrimitiveType).hasName("byte") and lowerbound = -128 and upperbound = 127 or
typ.(PrimitiveType).hasName("short") and lowerbound = -32768 and upperbound = 32767 or
typ.(PrimitiveType).hasName("char") and lowerbound = 0 and upperbound = 65535 or
typ.(PrimitiveType).hasName("byte") and lowerbound = -128 and upperbound = 127
or
typ.(PrimitiveType).hasName("short") and lowerbound = -32768 and upperbound = 32767
or
typ.(PrimitiveType).hasName("char") and lowerbound = 0 and upperbound = 65535
or
typeBound(typ.(BoxedType).getPrimitiveType(), lowerbound, upperbound)
}
@@ -278,8 +368,10 @@ private class NarrowingCastExpr extends CastExpr {
not this instanceof SafeCastExpr and
typeBound(getType(), _, _)
}
/** Gets the lower bound of the resulting type. */
int getLowerBound() { typeBound(getType(), result, _) }
/** Gets the upper bound of the resulting type. */
int getUpperBound() { typeBound(getType(), _, result) }
}
@@ -290,78 +382,84 @@ private class NarrowingCastExpr extends CastExpr {
* - `upper = false` : `e2 >= e1 + delta`
*/
private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
e2.(ParExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
e2.(AssignExpr).getSource() = e1 and delta = 0 and (upper = true or upper = false) or
e2.(PlusExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
e2.(PostIncExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
e2.(PostDecExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
e2.(PreIncExpr).getExpr() = e1 and delta = 1 and (upper = true or upper = false) or
e2.(PreDecExpr).getExpr() = e1 and delta = -1 and (upper = true or upper = false) or
e2.(SafeCastExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
exists(SsaExplicitUpdate v, FieldRead arrlen |
e2 = arrlen and
arrlen.getField() instanceof ArrayLengthField and
arrlen.getQualifier() = v.getAUse() and
v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = e1 and
delta = 0 and
(upper = true or upper = false)
) or
valueFlowStep(e2, e1, delta) and
(upper = true or upper = false)
or
e2.(SafeCastExpr).getExpr() = e1 and
delta = 0 and
(upper = true or upper = false)
or
exists(Expr x |
e2.(AddExpr).hasOperands(e1, x) or
e2.(AddExpr).hasOperands(e1, x)
or
exists(AssignAddExpr add | add = e2 |
add.getDest() = e1 and add.getRhs() = x or
add.getDest() = e1 and add.getRhs() = x
or
add.getDest() = x and add.getRhs() = e1
)
|
x.(ConstantIntegerExpr).getIntValue() = delta and (upper = true or upper = false)
or
|
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
not x instanceof ConstantIntegerExpr and
not e1 instanceof ConstantIntegerExpr and
if strictlyPositive(x) then
(upper = false and delta = 1)
else if positive(x) then
(upper = false and delta = 0)
else if strictlyNegative(x) then
(upper = true and delta = -1)
else if negative(x) then
(upper = true and delta = 0)
else
none()
) or
if strictlyPositive(x)
then (
upper = false and delta = 1
) else
if positive(x)
then (
upper = false and delta = 0
) else
if strictlyNegative(x)
then (
upper = true and delta = -1
) else if negative(x) then (upper = true and delta = 0) else none()
)
or
exists(Expr x |
exists(SubExpr sub |
e2 = sub and
sub.getLeftOperand() = e1 and
sub.getRightOperand() = x
) or
)
or
exists(AssignSubExpr sub |
e2 = sub and
sub.getDest() = e1 and
sub.getRhs() = x
)
|
x.(ConstantIntegerExpr).getIntValue() = -delta and (upper = true or upper = false)
or
|
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
not x instanceof ConstantIntegerExpr and
if strictlyPositive(x) then
(upper = true and delta = -1)
else if positive(x) then
(upper = true and delta = 0)
else if strictlyNegative(x) then
(upper = false and delta = 1)
else if negative(x) then
(upper = false and delta = 0)
else
none()
) or
e2.(RemExpr).getRightOperand() = e1 and positive(e1) and delta = -1 and upper = true or
e2.(RemExpr).getLeftOperand() = e1 and positive(e1) and delta = 0 and upper = true or
e2.(AssignRemExpr).getRhs() = e1 and positive(e1) and delta = -1 and upper = true or
e2.(AssignRemExpr).getDest() = e1 and positive(e1) and delta = 0 and upper = true or
e2.(AndBitwiseExpr).getAnOperand() = e1 and positive(e1) and delta = 0 and upper = true or
e2.(AssignAndExpr).getSource() = e1 and positive(e1) and delta = 0 and upper = true or
e2.(OrBitwiseExpr).getAnOperand() = e1 and positive(e2) and delta = 0 and upper = false or
e2.(AssignOrExpr).getSource() = e1 and positive(e2) and delta = 0 and upper = false or
if strictlyPositive(x)
then (
upper = true and delta = -1
) else
if positive(x)
then (
upper = true and delta = 0
) else
if strictlyNegative(x)
then (
upper = false and delta = 1
) else if negative(x) then (upper = false and delta = 0) else none()
)
or
e2.(RemExpr).getRightOperand() = e1 and positive(e1) and delta = -1 and upper = true
or
e2.(RemExpr).getLeftOperand() = e1 and positive(e1) and delta = 0 and upper = true
or
e2.(AssignRemExpr).getRhs() = e1 and positive(e1) and delta = -1 and upper = true
or
e2.(AssignRemExpr).getDest() = e1 and positive(e1) and delta = 0 and upper = true
or
e2.(AndBitwiseExpr).getAnOperand() = e1 and positive(e1) and delta = 0 and upper = true
or
e2.(AssignAndExpr).getSource() = e1 and positive(e1) and delta = 0 and upper = true
or
e2.(OrBitwiseExpr).getAnOperand() = e1 and positive(e2) and delta = 0 and upper = false
or
e2.(AssignOrExpr).getSource() = e1 and positive(e2) and delta = 0 and upper = false
or
exists(MethodAccess ma, Method m |
e2 = ma and
ma.getMethod() = m and
@@ -370,11 +468,16 @@ private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
e1 = ma.getAnArgument() and
delta = -1 and
upper = true
) or
)
or
exists(MethodAccess ma, Method m |
e2 = ma and
ma.getMethod() = m and
(m.hasName("max") and upper = false or m.hasName("min") and upper = true) and
(
m.hasName("max") and upper = false
or
m.hasName("min") and upper = true
) and
m.getDeclaringType().hasQualifiedName("java.lang", "Math") and
e1 = ma.getAnArgument() and
delta = 0
@@ -384,11 +487,19 @@ private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
/** Holds if `e2 = e1 * factor` and `factor > 0`. */
private predicate boundFlowStepMul(Expr e2, Expr e1, int factor) {
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
e2.(MulExpr).hasOperands(e1, c) and factor = k or
exists(AssignMulExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or
exists(AssignMulExpr e | e = e2 and e.getDest() = c and e.getRhs() = e1 and factor = k) or
exists(LShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or
exists(AssignLShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k))
e2.(MulExpr).hasOperands(e1, c) and factor = k
or
exists(AssignMulExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k)
or
exists(AssignMulExpr e | e = e2 and e.getDest() = c and e.getRhs() = e1 and factor = k)
or
exists(LShiftExpr e |
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
)
or
exists(AssignLShiftExpr e |
e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)
)
)
}
@@ -400,76 +511,39 @@ private predicate boundFlowStepMul(Expr e2, Expr e1, int factor) {
*/
private predicate boundFlowStepDiv(Expr e2, Expr e1, int factor) {
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
exists(DivExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k) or
exists(AssignDivExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or
exists(RShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or
exists(AssignRShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)) or
exists(URShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or
exists(AssignURShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k))
exists(DivExpr e |
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k
)
or
exists(AssignDivExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k)
or
exists(RShiftExpr e |
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
)
or
exists(AssignRShiftExpr e |
e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)
)
or
exists(URShiftExpr e |
e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
)
or
exists(AssignURShiftExpr e |
e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)
)
)
}
private newtype TBound =
TBoundZero() or
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
TBoundExpr(Expr e) { e.(FieldRead).getField() instanceof ArrayLengthField and not exists(SsaVariable v | e = v.getAUse()) }
/**
* A bound that may be inferred for an expression plus/minus an integer delta.
*/
abstract class Bound extends TBound {
abstract string toString();
/** Gets an expression that equals this bound plus `delta`. */
abstract Expr getExpr(int delta);
/** Gets an expression that equals this bound. */
Expr getExpr() {
result = getExpr(0)
}
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
}
}
/**
* The bound that corresponds to the integer 0. This is used to represent all
* integer bounds as bounds are always accompanied by an added integer delta.
*/
class ZeroBound extends Bound, TBoundZero {
override string toString() { result = "0" }
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
}
/**
* A bound corresponding to the value of an SSA variable.
*/
class SsaBound extends Bound, TBoundSsa {
/** Gets the SSA variable that equals this bound. */
SsaVariable getSsa() { this = TBoundSsa(result) }
override string toString() { result = getSsa().toString() }
override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 }
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
}
/**
* A bound that corresponds to the value of a specific expression that might be
* interesting, but isn't otherwise represented by the value of an SSA variable.
*/
class ExprBound extends Bound, TBoundExpr {
override string toString() { result = getExpr().toString() }
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
getExpr().hasLocationInfo(path, sl, sc, el, ec)
}
}
/**
* Holds if `b + delta` is a valid bound for `v` at `pos`.
* - `upper = true` : `v <= b + delta`
* - `upper = false` : `v >= b + delta`
*/
private predicate boundedSsa(SsaVariable v, SsaReadPosition pos, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
private predicate boundedSsa(
SsaVariable v, SsaReadPosition pos, Bound b, int delta, boolean upper, boolean fromBackEdge,
int origdelta, Reason reason
) {
exists(Expr mid, int d1, int d2, Reason r1, Reason r2 |
boundFlowStepSsa(v, pos, mid, d1, upper, r1) and
bounded(mid, b, d2, upper, fromBackEdge, origdelta, r2) and
@@ -477,14 +551,23 @@ private predicate boundedSsa(SsaVariable v, SsaReadPosition pos, Bound b, int de
// upper = false: v >= mid + d1 >= b + d1 + d2 = b + delta
delta = d1 + d2 and
(if r1 instanceof NoReason then reason = r2 else reason = r1)
) or
)
or
exists(int d, Reason r1, Reason r2 |
boundedSsa(v, pos, b, d, upper, fromBackEdge, origdelta, r2) or
boundedPhi(v, b, d, upper, fromBackEdge, origdelta, r2)
|
|
unequalSsa(v, pos, b, d, r1) and
(upper = true and delta = d - 1 or upper = false and delta = d + 1) and
(reason = r1 or reason = r2 and not r2 instanceof NoReason)
(
upper = true and delta = d - 1
or
upper = false and delta = d + 1
) and
(
reason = r1
or
reason = r2 and not r2 instanceof NoReason
)
)
}
@@ -506,14 +589,19 @@ private predicate unequalSsa(SsaVariable v, SsaReadPosition pos, Bound b, int de
private predicate backEdge(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge) {
edge.phiInput(phi, inp) and
// Conservatively assume that every edge is a back edge if we don't have dominance information.
(phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or not hasDominanceInformation(edge.getOrigBlock()))
(
phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or
not hasDominanceInformation(edge.getOrigBlock())
)
}
/** Weakens a delta to lie in the range `[-1..1]`. */
bindingset[delta, upper]
private int weakenDelta(boolean upper, int delta) {
delta in [-1..1] and result = delta or
upper = true and result = -1 and delta < -1 or
delta in [-1 .. 1] and result = delta
or
upper = true and result = -1 and delta < -1
or
upper = false and result = 1 and delta > 1
}
@@ -523,27 +611,43 @@ private int weakenDelta(boolean upper, int delta) {
* - `upper = true` : `inp <= b + delta`
* - `upper = false` : `inp >= b + delta`
*/
private predicate boundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
private predicate boundedPhiInp(
SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, Bound b, int delta,
boolean upper, boolean fromBackEdge, int origdelta, Reason reason
) {
edge.phiInput(phi, inp) and
exists(int d, boolean fromBackEdge0 |
boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason) or
boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason) or
b.(SsaBound).getSsa() = inp and d = 0 and (upper = true or upper = false) and fromBackEdge0 = false and origdelta = 0 and reason = TNoReason()
|
if backEdge(phi, inp, edge) then
boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason)
or
boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason)
or
b.(SsaBound).getSsa() = inp and
d = 0 and
(upper = true or upper = false) and
fromBackEdge0 = false and
origdelta = 0 and
reason = TNoReason()
|
if backEdge(phi, inp, edge)
then
fromBackEdge = true and
(
fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta or
fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta
or
fromBackEdge0 = false and delta = d
)
else
(delta = d and fromBackEdge = fromBackEdge0)
else (
delta = d and fromBackEdge = fromBackEdge0
)
)
}
/** Holds if `boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)`. */
pragma[noinline]
private predicate boundedPhiInp1(SsaPhiNode phi, Bound b, boolean upper, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int delta) {
private predicate boundedPhiInp1(
SsaPhiNode phi, Bound b, boolean upper, SsaVariable inp, SsaReadPositionPhiInputEdge edge,
int delta
) {
boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)
}
@@ -553,11 +657,17 @@ private predicate boundedPhiInp1(SsaPhiNode phi, Bound b, boolean upper, SsaVari
* - `upper = true` : `inp <= phi`
* - `upper = false` : `inp >= phi`
*/
private predicate selfBoundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, boolean upper) {
private predicate selfBoundedPhiInp(
SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, boolean upper
) {
exists(int d, SsaBound phibound |
phibound.getSsa() = phi and
boundedPhiInp(phi, inp, edge, phibound, d, upper, _, _, _) and
(upper = true and d <= 0 or upper = false and d >= 0)
(
upper = true and d <= 0
or
upper = false and d >= 0
)
)
}
@@ -568,19 +678,29 @@ private predicate selfBoundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPosi
* - `upper = false` : `inp >= b + delta`
*/
pragma[noinline]
private predicate boundedPhiCand(SsaPhiNode phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta, Reason reason) {
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge | boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason))
private predicate boundedPhiCand(
SsaPhiNode phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta,
Reason reason
) {
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge |
boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason)
)
}
/**
* Holds if the candidate bound `b + delta` for `phi` is valid for the phi input
* `inp` along `edge`.
*/
private predicate boundedPhiCandValidForEdge(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason, SsaVariable inp, SsaReadPositionPhiInputEdge edge) {
private predicate boundedPhiCandValidForEdge(
SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
Reason reason, SsaVariable inp, SsaReadPositionPhiInputEdge edge
) {
boundedPhiCand(phi, upper, b, delta, fromBackEdge, origdelta, reason) and
(
exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta) or
exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta) or
exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta)
or
exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta)
or
selfBoundedPhiInp(phi, inp, edge, upper)
)
}
@@ -590,7 +710,10 @@ private predicate boundedPhiCandValidForEdge(SsaPhiNode phi, Bound b, int delta,
* - `upper = true` : `phi <= b + delta`
* - `upper = false` : `phi >= b + delta`
*/
private predicate boundedPhi(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
private predicate boundedPhi(
SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
Reason reason
) {
forex(SsaVariable inp, SsaReadPositionPhiInputEdge edge | edge.phiInput(phi, inp) |
boundedPhiCandValidForEdge(phi, b, delta, upper, fromBackEdge, origdelta, reason, inp, edge)
)
@@ -612,14 +735,16 @@ private predicate lowerBoundZero(Expr e) {
* (for `upper = false`) bound of `b`.
*/
private predicate baseBound(Expr e, int b, boolean upper) {
lowerBoundZero(e) and b = 0 and upper = false or
lowerBoundZero(e) and b = 0 and upper = false
or
exists(Method read |
e.(MethodAccess).getMethod().overrides*(read) and
read.getDeclaringType().hasQualifiedName("java.io", "InputStream") and
read.hasName("read") and
read.getNumberOfParameters() = 0
|
upper = true and b = 255 or
|
upper = true and b = 255
or
upper = false and b = -1
)
}
@@ -631,16 +756,18 @@ private predicate baseBound(Expr e, int b, boolean upper) {
* `upper = false` this means that the cast will not underflow.
*/
private predicate safeNarrowingCast(NarrowingCastExpr cast, boolean upper) {
exists(int bound |
bounded(cast.getExpr(), TBoundZero(), bound, upper, _, _, _)
|
upper = true and bound <= cast.getUpperBound() or
exists(int bound | bounded(cast.getExpr(), any(ZeroBound zb), bound, upper, _, _, _) |
upper = true and bound <= cast.getUpperBound()
or
upper = false and bound >= cast.getLowerBound()
)
}
pragma[noinline]
private predicate boundedCastExpr(NarrowingCastExpr cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
private predicate boundedCastExpr(
NarrowingCastExpr cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
Reason reason
) {
bounded(cast.getExpr(), b, delta, upper, fromBackEdge, origdelta, reason)
}
@@ -649,14 +776,27 @@ private predicate boundedCastExpr(NarrowingCastExpr cast, Bound b, int delta, bo
* - `upper = true` : `e <= b + delta`
* - `upper = false` : `e >= b + delta`
*/
private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
e = b.getExpr(delta) and (upper = true or upper = false) and fromBackEdge = false and origdelta = delta and reason = TNoReason() or
baseBound(e, delta, upper) and b instanceof ZeroBound and fromBackEdge = false and origdelta = delta and reason = TNoReason() or
private predicate bounded(
Expr e, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason
) {
e = b.getExpr(delta) and
(upper = true or upper = false) and
fromBackEdge = false and
origdelta = delta and
reason = TNoReason()
or
baseBound(e, delta, upper) and
b instanceof ZeroBound and
fromBackEdge = false and
origdelta = delta and
reason = TNoReason()
or
exists(SsaVariable v, SsaReadPositionBlock bb |
boundedSsa(v, bb, b, delta, upper, fromBackEdge, origdelta, reason) and
e = v.getAUse() and
bb.getBlock() = e.getBasicBlock()
) or
)
or
exists(Expr mid, int d1, int d2 |
boundFlowStep(e, mid, d1, upper) and
// Constants have easy, base-case bounds, so let's not infer any recursive bounds.
@@ -665,18 +805,21 @@ private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fro
// upper = true: e <= mid + d1 <= b + d1 + d2 = b + delta
// upper = false: e >= mid + d1 >= b + d1 + d2 = b + delta
delta = d1 + d2
) or
)
or
exists(SsaPhiNode phi |
boundedPhi(phi, b, delta, upper, fromBackEdge, origdelta, reason) and
e = phi.getAUse()
) or
)
or
exists(Expr mid, int factor, int d |
boundFlowStepMul(e, mid, factor) and
not e instanceof ConstantIntegerExpr and
bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
b instanceof ZeroBound and
delta = d * factor
) or
)
or
exists(Expr mid, int factor, int d |
boundFlowStepDiv(e, mid, factor) and
not e instanceof ConstantIntegerExpr and
@@ -684,25 +827,38 @@ private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fro
b instanceof ZeroBound and
d >= 0 and
delta = d / factor
) or
)
or
exists(NarrowingCastExpr cast |
cast = e and
safeNarrowingCast(cast, upper.booleanNot()) and
boundedCastExpr(cast, b, delta, upper, fromBackEdge, origdelta, reason)
) or
exists(ConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2, Reason r1, Reason r2 |
)
or
exists(
ConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2, Reason r1,
Reason r2
|
cond = e and
boundedConditionalExpr(cond, b, upper, true, d1, fbe1, od1, r1) and
boundedConditionalExpr(cond, b, upper, false, d2, fbe2, od2, r2) and
(delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1 or
delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2)
|
upper = true and delta = d1.maximum(d2) or
(
delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1
or
delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2
)
|
upper = true and delta = d1.maximum(d2)
or
upper = false and delta = d1.minimum(d2)
)
}
private predicate boundedConditionalExpr(ConditionalExpr cond, Bound b, boolean upper, boolean branch, int delta, boolean fromBackEdge, int origdelta, Reason reason) {
branch = true and bounded(cond.getTrueExpr(), b, delta, upper, fromBackEdge, origdelta, reason) or
private predicate boundedConditionalExpr(
ConditionalExpr cond, Bound b, boolean upper, boolean branch, int delta, boolean fromBackEdge,
int origdelta, Reason reason
) {
branch = true and bounded(cond.getTrueExpr(), b, delta, upper, fromBackEdge, origdelta, reason)
or
branch = false and bounded(cond.getFalseExpr(), b, delta, upper, fromBackEdge, origdelta, reason)
}

View File

@@ -9,47 +9,60 @@ private import semmle.code.java.controlflow.internal.GuardsLogic
/** An expression that always has the same integer value. */
pragma[nomagic]
private predicate constantIntegerExpr(Expr e, int val) {
e.(CompileTimeConstantExpr).getIntValue() = val or
e.(CompileTimeConstantExpr).getIntValue() = val
or
exists(SsaExplicitUpdate v, Expr src |
e = v.getAUse() and
src = v.getDefiningExpr().(VariableAssign).getSource() and
constantIntegerExpr(src, val)
)
or
exists(SsaExplicitUpdate v, FieldRead arrlen |
e = arrlen and
arrlen.getField() instanceof ArrayLengthField and
arrlen.getQualifier() = v.getAUse() and
v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getFirstDimensionSize() = val
)
}
/** An expression that always has the same integer value. */
class ConstantIntegerExpr extends Expr {
ConstantIntegerExpr() {
constantIntegerExpr(this, _)
}
ConstantIntegerExpr() { constantIntegerExpr(this, _) }
/** Gets the integer value of this expression. */
int getIntValue() {
constantIntegerExpr(this, result)
}
int getIntValue() { constantIntegerExpr(this, result) }
}
/**
* Gets an expression that equals `v - d`.
*/
Expr ssaRead(SsaVariable v, int delta) {
result = v.getAUse() and delta = 0 or
result.(ParExpr).getExpr() = ssaRead(v, delta) or
result = v.getAUse() and delta = 0
or
result.(ParExpr).getExpr() = ssaRead(v, delta)
or
exists(int d1, ConstantIntegerExpr c |
result.(AddExpr).hasOperands(ssaRead(v, d1), c) and
delta = d1 - c.getIntValue()
) or
)
or
exists(SubExpr sub, int d1, ConstantIntegerExpr c |
result = sub and
sub.getLeftOperand() = ssaRead(v, d1) and
sub.getRightOperand() = c and
delta = d1 + c.getIntValue()
) or
v.(SsaExplicitUpdate).getDefiningExpr().(PreIncExpr) = result and delta = 0 or
v.(SsaExplicitUpdate).getDefiningExpr().(PreDecExpr) = result and delta = 0 or
v.(SsaExplicitUpdate).getDefiningExpr().(PostIncExpr) = result and delta = 1 or // x++ === ++x - 1
v.(SsaExplicitUpdate).getDefiningExpr().(PostDecExpr) = result and delta = -1 or // x-- === --x + 1
v.(SsaExplicitUpdate).getDefiningExpr().(Assignment) = result and delta = 0 or
)
or
v.(SsaExplicitUpdate).getDefiningExpr().(PreIncExpr) = result and delta = 0
or
v.(SsaExplicitUpdate).getDefiningExpr().(PreDecExpr) = result and delta = 0
or
v.(SsaExplicitUpdate).getDefiningExpr().(PostIncExpr) = result and delta = 1 // x++ === ++x - 1
or
v.(SsaExplicitUpdate).getDefiningExpr().(PostDecExpr) = result and delta = -1 // x-- === --x + 1
or
v.(SsaExplicitUpdate).getDefiningExpr().(Assignment) = result and delta = 0
or
result.(AssignExpr).getSource() = ssaRead(v, delta)
}
@@ -114,7 +127,8 @@ class SsaReadPositionPhiInputEdge extends SsaReadPosition, TSsaReadPositionPhiIn
* value `testIsTrue`.
*/
predicate guardDirectlyControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) {
guard.directlyControls(controlled.(SsaReadPositionBlock).getBlock(), testIsTrue) or
guard.directlyControls(controlled.(SsaReadPositionBlock).getBlock(), testIsTrue)
or
exists(SsaReadPositionPhiInputEdge controlledEdge | controlledEdge = controlled |
guard.directlyControls(controlledEdge.getOrigBlock(), testIsTrue) or
guard.hasBranchEdge(controlledEdge.getOrigBlock(), controlledEdge.getPhiBlock(), testIsTrue)
@@ -125,9 +139,101 @@ predicate guardDirectlyControlsSsaRead(Guard guard, SsaReadPosition controlled,
* Holds if `guard` controls the position `controlled` with the value `testIsTrue`.
*/
predicate guardControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) {
guardDirectlyControlsSsaRead(guard, controlled, testIsTrue) or
guardDirectlyControlsSsaRead(guard, controlled, testIsTrue)
or
exists(Guard guard0, boolean testIsTrue0 |
implies_v2(guard0, testIsTrue0, guard, testIsTrue) and
guardControlsSsaRead(guard0, controlled, testIsTrue0)
)
}
/**
* Gets a condition that tests whether `v` equals `e + delta`.
*
* If the condition evaluates to `testIsTrue`:
* - `isEq = true` : `v == e + delta`
* - `isEq = false` : `v != e + delta`
*/
Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsTrue) {
exists(boolean eqpolarity |
result.isEquality(ssaRead(v, delta), e, eqpolarity) and
(testIsTrue = true or testIsTrue = false) and
eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq
)
or
exists(boolean testIsTrue0 |
implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0)
)
}
/**
* Holds if `v` is an `SsaExplicitUpdate` that equals `e + delta`.
*/
predicate ssaUpdateStep(SsaExplicitUpdate v, Expr e, int delta) {
v.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0
or
v.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1
or
v.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1
or
v.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1
or
v.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1
or
v.getDefiningExpr().(AssignOp) = e and delta = 0
}
/**
* Holds if `e1 + delta` equals `e2`.
*/
predicate valueFlowStep(Expr e2, Expr e1, int delta) {
e2.(ParExpr).getExpr() = e1 and delta = 0
or
e2.(AssignExpr).getSource() = e1 and delta = 0
or
e2.(PlusExpr).getExpr() = e1 and delta = 0
or
e2.(PostIncExpr).getExpr() = e1 and delta = 0
or
e2.(PostDecExpr).getExpr() = e1 and delta = 0
or
e2.(PreIncExpr).getExpr() = e1 and delta = 1
or
e2.(PreDecExpr).getExpr() = e1 and delta = -1
or
exists(SsaExplicitUpdate v, FieldRead arrlen |
e2 = arrlen and
arrlen.getField() instanceof ArrayLengthField and
arrlen.getQualifier() = v.getAUse() and
v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = e1 and
delta = 0
)
or
exists(Expr x |
e2.(AddExpr).hasOperands(e1, x)
or
exists(AssignAddExpr add | add = e2 |
add.getDest() = e1 and add.getRhs() = x
or
add.getDest() = x and add.getRhs() = e1
)
|
x.(ConstantIntegerExpr).getIntValue() = delta
)
or
exists(Expr x |
exists(SubExpr sub |
e2 = sub and
sub.getLeftOperand() = e1 and
sub.getRightOperand() = x
)
or
exists(AssignSubExpr sub |
e2 = sub and
sub.getDest() = e1 and
sub.getRhs() = x
)
|
x.(ConstantIntegerExpr).getIntValue() = -delta
)
}

View File

@@ -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))
}
}

View File

@@ -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))
}
}

View File

@@ -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))
}
}

View File

@@ -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))
}
}

View File

@@ -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))
}
}

View File

@@ -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())
)
}

View File

@@ -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))
}
}

View File

@@ -118,20 +118,10 @@ class Content extends TContent {
}
/** Gets the type of the object containing this content. */
abstract RefType getDeclaringType();
abstract RefType getContainerType();
/** Gets the type of this content. */
abstract Type getType();
/**
* Holds if this content may contain an object of the same type as the one
* that contains this content, and if this fact should be used to compress
* access paths.
*
* Examples include the tail pointer in a linked list or the left and right
* pointers in a binary tree.
*/
predicate isSelfRef() { none() }
}
private class FieldContent extends Content, TFieldContent {
@@ -147,17 +137,15 @@ private class FieldContent extends Content, TFieldContent {
f.getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
override RefType getDeclaringType() { result = f.getDeclaringType() }
override RefType getContainerType() { result = f.getDeclaringType() }
override Type getType() { result = getFieldTypeBound(f) }
override predicate isSelfRef() { compatibleTypes(getDeclaringType(), getType()) }
}
private class CollectionContent extends Content, TCollectionContent {
override string toString() { result = "collection" }
override RefType getDeclaringType() { none() }
override RefType getContainerType() { none() }
override Type getType() { none() }
}
@@ -165,7 +153,7 @@ private class CollectionContent extends Content, TCollectionContent {
private class ArrayContent extends Content, TArrayContent {
override string toString() { result = "array" }
override RefType getDeclaringType() { none() }
override RefType getContainerType() { none() }
override Type getType() { none() }
}
@@ -212,6 +200,13 @@ RefType getErasedRepr(Type t) {
)
}
/** Gets a string representation of a type returned by `getErasedRepr`. */
string ppReprType(Type t) {
if t.(BoxedType).getPrimitiveType().getName() = "double"
then result = "Number"
else result = t.toString()
}
private predicate canContainBool(Type t) {
t instanceof BooleanType or
any(BooleanType b).(RefType).getASourceSupertype+() = t
@@ -227,12 +222,9 @@ predicate compatibleTypes(Type t1, Type t2) {
e1 = getErasedRepr(t1) and
e2 = getErasedRepr(t2)
|
/*
* Because of `getErasedRepr`, `erasedHaveIntersection` is a sufficient
* compatibility check, but `conContainBool` is kept as a dummy disjunct
* to get the proper join-order.
*/
// Because of `getErasedRepr`, `erasedHaveIntersection` is a sufficient
// compatibility check, but `conContainBool` is kept as a dummy disjunct
// to get the proper join-order.
erasedHaveIntersection(e1, e2)
or
canContainBool(e1) and canContainBool(e2)

View File

@@ -0,0 +1,165 @@
public class A {
private static final int[] arr1 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
private final int[] arr2;
private final int[] arr3;
public A(int[] arr2, int n) {
if (arr2.length % 2 != 0)
throw new Exception();
this.arr2 = arr2;
this.arr3 = new int[n << 1];
}
void m1(int[] a) {
int sum = 0;
for (int i = 0; i <= a.length; i++) {
sum += a[i]; // Out of bounds
}
}
void m2(int[] a) {
int sum = 0;
for (int i = 0; i < a.length; i += 2) {
sum += a[i] + a[i + 1]; // Out of bounds (unless len%2==0)
}
}
void m3(int[] a) {
if (a.length % 2 != 0)
return;
int sum = 0;
for (int i = 0; i < a.length; ) {
sum += a[i++]; // OK
sum += a[i++]; // OK
}
for (int i = 0; i < arr1.length; ) {
sum += arr1[i++]; // OK
sum += arr1[i++]; // OK - FP
i += 2;
}
for (int i = 0; i < arr2.length; ) {
sum += arr2[i++]; // OK
sum += arr2[i++]; // OK - FP
}
for (int i = 0; i < arr3.length; ) {
sum += arr3[i++]; // OK
sum += arr3[i++]; // OK - FP
}
int[] b;
if (sum > 3)
b = a;
else
b = arr1;
for (int i = 0; i < b.length; i++) {
sum += b[i]; // OK
sum += b[++i]; // OK - FP
}
}
void m4(int[] a, int[] b) {
assert a.length % 2 == 0;
int sum = 0;
for (int i = 0; i < a.length; ) {
sum += a[i++]; // OK
sum += a[i++]; // OK - FP
}
int len = b.length;
if ((len & 1) != 0)
return;
for (int i = 0; i < len; ) {
sum += b[i++]; // OK
sum += b[i++]; // OK
}
}
void m5(int n) {
int[] a = new int[3 * n];
int sum = 0;
for (int i = 0; i < a.length; i += 3) {
sum += a[i] + a[i + 1] + a[i + 2]; // OK
}
}
int m6(int[] a, int ix) {
if (ix < 0 || ix > a.length)
return 0;
return a[ix]; // Out of bounds
}
void m7() {
int[] xs = new int[11];
int sum = 0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
sum += xs[i]; // OK
sum += xs[j]; // OK
if (i < j)
sum += xs[i + 11 - j]; // OK - FP
else
sum += xs[i - j]; // OK
}
}
}
void m8(int[] a) {
if ((a.length - 4) % 3 != 0)
return;
int sum = 0;
for (int i = 4; i < a.length; i += 3) {
sum += a[i]; // OK
sum += a[i + 1]; // OK - FP
sum += a[i + 2]; // OK - FP
}
}
void m9() {
int[] a = new int[] { 1, 2, 3, 4, 5 };
int sum = 0;
for (int i = 0; i < 10; i++) {
if (i < 5)
sum += a[i]; // OK
else
sum += a[9 - i]; // OK - FP
}
}
void m10(int n, int m) {
int len = Math.min(n, m);
int[] a = new int[n];
int sum = 0;
for (int i = n - 1; i >= 0; i--) {
sum += a[i]; // OK
for (int j = i + 1; j < len; j++) {
sum += a[j]; // OK
sum += a[i + 1]; // OK - FP
}
}
}
void m11(int n) {
int len = n*2;
int[] a = new int[len];
int sum = 0;
for (int i = 0; i < len; i = i + 2) {
sum += a[i+1]; // OK
for (int j = i; j < len - 2; j = j + 2) {
sum += a[j]; // OK
sum += a[j+1]; // OK
sum += a[j+2]; // OK
sum += a[j+3]; // OK
}
}
}
void m12() {
int[] a = new int[] { 1, 2, 3, 4, 5, 6 };
int sum = 0;
for (int i = 0; i < a.length; i += 2) {
sum += a[i] + a[i + 1]; // OK
}
int[] b = new int[8];
for (int i = 2; i < 8; i += 2) {
sum += b[i] + b[i + 1]; // OK
}
}
}

View File

@@ -0,0 +1,13 @@
| A.java:16:14:16:17 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:23:21:23:28 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:37:14:37:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:42:14:42:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:46:14:46:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:55:14:55:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:64:14:64:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:86:12:86:16 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:97:18:97:31 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 8. |
| A.java:110:14:110:21 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:111:14:111:21 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 1. |
| A.java:122:16:122:23 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 3. |
| A.java:134:16:134:23 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |

View File

@@ -0,0 +1 @@
Likely Bugs/Collections/ArrayIndexOutOfBounds.ql

View File

@@ -0,0 +1,22 @@
import java.util.*;
import java.util.function.*;
public class B {
int modcount = 0;
int[] arr;
public void modify(IntUnaryOperator func) {
int mc = modcount;
for (int i = 0; i < arr.length; i++) {
arr[i] = func.applyAsInt(arr[i]);
}
// Always false unless there is a call path from func.applyAsInt(..) to
// this method, but should not be reported, as checks guarding
// ConcurrentModificationExceptions are expected to always be false in the
// absence of API misuse.
if (mc != modcount)
throw new ConcurrentModificationException();
modcount++;
}
}

View File

@@ -94,36 +94,95 @@ predicate isEnumMember(VarDecl decl) {
}
/**
* Gets a description of the declaration `vd`, which is either of the form "function f" if
* it is a function name, or "variable v" if it is not.
* Gets a description of the declaration `vd`, which is either of the form
* "function f", "variable v" or "class c".
*/
string describe(VarDecl vd) {
string describeVarDecl(VarDecl vd) {
if vd = any(Function f).getId() then
result = "function " + vd.getName()
else if vd = any(ClassDefinition c).getIdentifier() then
result = "class " + vd.getName()
else if (vd = any(ImportSpecifier im).getLocal() or vd = any(ImportEqualsDeclaration im).getId()) then
result = "import " + vd.getName()
else
result = "variable " + vd.getName()
}
from VarDecl vd, UnusedLocal v
where v = vd.getVariable() and
// exclude variables mentioned in JSDoc comments in externs
not mentionedInJSDocComment(v) and
// exclude variables used to filter out unwanted properties
not isPropertyFilter(v) and
// exclude imports of React that are implicitly referenced by JSX
not isReactImportForJSX(v) and
// exclude names that are used as types
not isUsedAsType(vd) and
// exclude names that are used as namespaces from inside a type
not isUsedAsNamespace(vd) and
// exclude decorated functions and classes
not isDecorated(vd) and
// exclude names of enum members; they also define property names
not isEnumMember(vd) and
// ignore ambient declarations - too noisy
not vd.isAmbient()
select vd, "Unused " + describe(vd) + "."
/**
* An import statement that provides variable declarations.
*/
class ImportVarDeclProvider extends Stmt {
ImportVarDeclProvider() {
this instanceof ImportDeclaration or
this instanceof ImportEqualsDeclaration
}
/**
* Gets a variable declaration of this import.
*/
VarDecl getAVarDecl() {
result = this.(ImportDeclaration).getASpecifier().getLocal() or
result = this.(ImportEqualsDeclaration).getId()
}
/**
* Gets an unacceptable unused variable declared by this import.
*/
UnusedLocal getAnUnacceptableUnusedLocal() {
result = getAVarDecl().getVariable() and
not whitelisted(result)
}
}
/**
* Holds if it is acceptable that `v` is unused.
*/
predicate whitelisted(UnusedLocal v) {
// exclude variables mentioned in JSDoc comments in externs
mentionedInJSDocComment(v) or
// exclude variables used to filter out unwanted properties
isPropertyFilter(v) or
// exclude imports of React that are implicitly referenced by JSX
isReactImportForJSX(v) or
// exclude names that are used as types
exists (VarDecl vd |
v = vd.getVariable() |
isUsedAsType(vd) or
// exclude names that are used as namespaces from inside a type
isUsedAsNamespace(vd)or
// exclude decorated functions and classes
isDecorated(vd) or
// exclude names of enum members; they also define property names
isEnumMember(vd) or
// ignore ambient declarations - too noisy
vd.isAmbient()
)
}
/**
* Holds if `vd` declares an unused variable that does not come from an import statement, as explained by `msg`.
*/
predicate unusedNonImports(VarDecl vd, string msg) {
exists (UnusedLocal v |
v = vd.getVariable() and
msg = "Unused " + describeVarDecl(vd) + "." and
not vd = any(ImportVarDeclProvider p).getAVarDecl() and
not whitelisted(v)
)
}
/**
* Holds if `provider` declares one or more unused variables, as explained by `msg`.
*/
predicate unusedImports(ImportVarDeclProvider provider, string msg) {
exists (string imports, string names |
imports = pluralize("import", count(provider.getAnUnacceptableUnusedLocal())) and
names = strictconcat(provider.getAnUnacceptableUnusedLocal().getName(), ", ") and
msg = "Unused " + imports + " " + names + "."
)
}
from ASTNode sel, string msg
where unusedNonImports(sel, msg) or
unusedImports(sel, msg)
select sel, msg

View File

@@ -45,5 +45,4 @@ class OmittedArrayElement extends ArrayExpr {
}
from OmittedArrayElement ae
where not ae.getFile().getFileType().isTypeScript() // ignore quirks in TypeScript tokenizer
select ae, "Avoid omitted array elements."

View File

@@ -36,8 +36,7 @@ where s.hasSemicolonInserted() and
asi = strictcount(Stmt ss | asi(sc, ss, true)) and
nstmt = strictcount(Stmt ss | asi(sc, ss, _)) and
perc = ((1-asi/nstmt)*100).floor() and
perc >= 90 and
not s.getFile().getFileType().isTypeScript() // ignore some quirks in the TypeScript tokenizer
perc >= 90
select (LastLineOf)s, "Avoid automated semicolon insertion " +
"(" + perc + "% of all statements in $@ have an explicit semicolon).",
sc, "the enclosing " + sctype

View File

@@ -39,7 +39,6 @@ where misleadingIndentationCandidate(ctrl, s1, s2) and
f.hasIndentation(ctrlStartLine, indent, _) and
f.hasIndentation(startLine1, indent, _) and
f.hasIndentation(startLine2, indent, _) and
not s2 instanceof EmptyStmt and
not f.getFileType().isTypeScript() // ignore quirks in TypeScript tokenizer
not s2 instanceof EmptyStmt
select (FirstLineOf)s2, "The indentation of this statement suggests that it is controlled by $@, while in fact it is not.",
(FirstLineOf)ctrl, "this statement"

View File

@@ -11,3 +11,16 @@ bindingset[s]
string capitalize(string s) {
result = s.charAt(0).toUpperCase() + s.suffix(1)
}
/**
* Gets the pluralization for `n` occurrences of `noun`.
*
* For example, the pluralization of `"function"` for `n = 2` is `"functions"`.
*/
bindingset[noun, n]
string pluralize(string noun, int n) {
if n = 1 then
result = noun
else
result = noun + "s"
}

Some files were not shown because too many files have changed in this diff Show More