mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge remote-tracking branch 'upstream/main' into side-effects
This commit is contained in:
11
.github/workflows/codeql-analysis.yml
vendored
11
.github/workflows/codeql-analysis.yml
vendored
@@ -19,13 +19,18 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
pull-requests: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@main
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
with:
|
||||
languages: csharp
|
||||
@@ -34,7 +39,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@main
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -48,4 +53,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@main
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll",
|
||||
"java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll",
|
||||
"java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll",
|
||||
"java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl6.qll",
|
||||
"cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll",
|
||||
"cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll",
|
||||
"cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* The 'Unsigned difference expression compared to zero' (cpp/unsigned-difference-expression-compared-zero) query has been improved to produce fewer false positive results.
|
||||
@@ -0,0 +1,2 @@
|
||||
codescanning
|
||||
* The 'Pointer to stack object used as return value' (cpp/return-stack-allocated-object) query has been deprecated, and any uses should be replaced with `Returning stack-allocated memory` (cpp/return-stack-allocated-memory).
|
||||
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* The `exprMightOverflowPositively` and `exprMightOverflowNegatively` predicates from the `SimpleRangeAnalysis` library now recognize more expressions that might overflow.
|
||||
@@ -39,7 +39,7 @@ then replace all the relevant occurrences in the code.</p>
|
||||
</li>
|
||||
<li>
|
||||
Mats Henricson and Erik Nyquist, <i>Industrial Strength C++</i>, published by Prentice Hall PTR (1997).
|
||||
Chapter 5: Object Life Cycle, Rec 5.4 (<a href="http://mongers.org/industrial-c++/">PDF</a>).
|
||||
Chapter 5: Object Life Cycle, Rec 5.4 (<a href="https://web.archive.org/web/20190919025638/https://mongers.org/industrial-c++/">PDF</a>).
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.securecoding.cert.org/confluence/display/c/DCL06-C.+Use+meaningful+symbolic+constants+to+represent+literal+values">DCL06-C. Use meaningful symbolic constants to represent literal values</a>
|
||||
|
||||
@@ -38,7 +38,7 @@ constant.</p>
|
||||
</li>
|
||||
<li>
|
||||
Mats Henricson and Erik Nyquist, <i>Industrial Strength C++</i>, published by Prentice Hall PTR (1997).
|
||||
Chapter 5: Object Life Cycle, Rec 5.4 (<a href="http://mongers.org/industrial-c++/">PDF</a>).
|
||||
Chapter 5: Object Life Cycle, Rec 5.4 (<a href="https://web.archive.org/web/20190919025638/https://mongers.org/industrial-c++/">PDF</a>).
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.securecoding.cert.org/confluence/display/c/DCL06-C.+Use+meaningful+symbolic+constants+to+represent+literal+values">DCL06-C. Use meaningful symbolic constants to represent literal values</a>
|
||||
|
||||
@@ -21,7 +21,7 @@ Review the purpose of the each global variable flagged by this rule and update e
|
||||
|
||||
<li>
|
||||
Mats Henricson and Erik Nyquist, <i>Industrial Strength C++</i>, published by Prentice Hall PTR (1997).
|
||||
Chapter 1: Naming, Rec 1.1 (<a href="http://mongers.org/industrial-c++/">PDF</a>).
|
||||
Chapter 1: Naming, Rec 1.1 (<a href="https://web.archive.org/web/20190919025638/https://mongers.org/industrial-c++/">PDF</a>).
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.learncpp.com/cpp-tutorial/42-global-variables/">Global variables</a>.
|
||||
|
||||
@@ -45,7 +45,7 @@ this rule.
|
||||
</li>
|
||||
<li>
|
||||
Mats Henricson and Erik Nyquist, <i>Industrial Strength C++</i>, Rule 4.6. Prentice Hall PTR, 1997.
|
||||
(<a href="http://mongers.org/industrial-c++/">PDF</a>).
|
||||
(<a href="https://web.archive.org/web/20190919025638/https://mongers.org/industrial-c++/">PDF</a>).
|
||||
</li>
|
||||
<li>
|
||||
cplusplus.com: <a href="http://www.cplusplus.com/doc/tutorial/control/">Control Structures</a>.
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* @tags reliability
|
||||
* security
|
||||
* external/cwe/cwe-562
|
||||
* @deprecated This query is not suitable for production use and has been deprecated. Use
|
||||
* cpp/return-stack-allocated-memory instead.
|
||||
*/
|
||||
|
||||
import semmle.code.cpp.pointsto.PointsTo
|
||||
|
||||
@@ -32,7 +32,7 @@ Check the return value of functions that return status information.
|
||||
<references>
|
||||
|
||||
<li>
|
||||
M. Henricson and E. Nyquist, <i>Industrial Strength C++</i>, Chapter 12: Error handling. Prentice Hall PTR, 1997 (<a href="http://mongers.org/industrial-c++/">available online</a>).
|
||||
M. Henricson and E. Nyquist, <i>Industrial Strength C++</i>, Chapter 12: Error handling. Prentice Hall PTR, 1997 (<a href="https://web.archive.org/web/20190919025638/https://mongers.org/industrial-c++/">available online</a>).
|
||||
</li>
|
||||
<li>
|
||||
The CERT C Secure Coding Standard: <a href="https://www.securecoding.cert.org/confluence/display/perl/EXP32-PL.+Do+not+ignore+function+return+values">EXP32-PL. Do not ignore function return values</a>.
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
* @id cpp/signed-overflow-check
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-128
|
||||
* external/cwe/cwe-190
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id cpp/upcast-array-pointer-arithmetic
|
||||
* @tags correctness
|
||||
* reliability
|
||||
* security
|
||||
* external/cwe/cwe-119
|
||||
* external/cwe/cwe-843
|
||||
* @id cpp/upcast-array-pointer-arithmetic
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* security
|
||||
* external/cwe/cwe-190
|
||||
* external/cwe/cwe-253
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* security
|
||||
* external/cwe/cwe-234
|
||||
* external/cwe/cwe-685
|
||||
*/
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ indication that there may be cases unhandled by the <code>switch</code> statemen
|
||||
MSDN Library: <a href="https://docs.microsoft.com/en-us/cpp/cpp/switch-statement-cpp">switch statement (C++)</a>
|
||||
</li>
|
||||
<li>
|
||||
M. Henricson and E. Nyquist, <i>Industrial Strength C++</i>, Chapter 4: Control Flow, Rec 4.5. Prentice Hall PTR, 1997 (<a href="http://mongers.org/industrial-c++/">available online</a>).
|
||||
M. Henricson and E. Nyquist, <i>Industrial Strength C++</i>, Chapter 4: Control Flow, Rec 4.5. Prentice Hall PTR, 1997 (<a href="https://web.archive.org/web/20190919025638/https://mongers.org/industrial-c++/">available online</a>).
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* @id cpp/pointer-overflow-check
|
||||
* @tags reliability
|
||||
* security
|
||||
* external/cwe/cwe-758
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* @tags correctness
|
||||
* language-features
|
||||
* security
|
||||
* external/cwe/cwe-670
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
* @tags correctness
|
||||
* maintainability
|
||||
* security
|
||||
* external/cwe/cwe-234
|
||||
* external/cwe/cwe-685
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
@@ -29,7 +29,7 @@ build time: the more included files, the longer the compilation time.</p>
|
||||
<a href="http://www.drdobbs.com/cpp/decoupling-c-header-files/212701130">Decoupling C Header Files</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://wiki.hsr.ch/Prog3/files/overload72-FINAL_DesigningHeaderFiles.pdf">C++ Best Practice -
|
||||
<a href="https://accu.org/journals/overload/14/72/griffiths_1995/">C++ Best Practice -
|
||||
Designing Header Files</a>
|
||||
</li>
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ they are contributing to unnecessarily long build times and creating artificial
|
||||
<a href="http://www.drdobbs.com/cpp/decoupling-c-header-files/212701130">Decoupling C Header Files</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://wiki.hsr.ch/Prog3/files/overload72-FINAL_DesigningHeaderFiles.pdf">C++ Best Practice -
|
||||
<a href="https://accu.org/journals/overload/14/72/griffiths_1995/">C++ Best Practice -
|
||||
Designing Header Files</a>
|
||||
</li>
|
||||
</references>
|
||||
|
||||
@@ -28,6 +28,7 @@ predicate outOfBoundsExpr(Expr expr, string kind) {
|
||||
|
||||
from Expr use, Expr origin, string kind
|
||||
where
|
||||
not use.getUnspecifiedType() instanceof PointerType and
|
||||
outOfBoundsExpr(use, kind) and
|
||||
tainted(origin, use) and
|
||||
origin != use and
|
||||
|
||||
@@ -12,29 +12,60 @@
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.commons.Exclusions
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
|
||||
import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
|
||||
import semmle.code.cpp.controlflow.Guards
|
||||
import semmle.code.cpp.dataflow.DataFlow
|
||||
|
||||
/** Holds if `sub` is guarded by a condition which ensures that `left >= right`. */
|
||||
/**
|
||||
* Holds if `sub` is guarded by a condition which ensures that
|
||||
* `left >= right`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate isGuarded(SubExpr sub, Expr left, Expr right) {
|
||||
exists(GuardCondition guard |
|
||||
guard.controls(sub.getBasicBlock(), true) and
|
||||
guard.ensuresLt(left, right, 0, sub.getBasicBlock(), false)
|
||||
exists(GuardCondition guard, int k |
|
||||
guard.controls(sub.getBasicBlock(), _) and
|
||||
guard.ensuresLt(left, right, k, sub.getBasicBlock(), false) and
|
||||
k >= 0
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `sub` will never be negative. */
|
||||
predicate nonNegative(SubExpr sub) {
|
||||
not exprMightOverflowNegatively(sub.getFullyConverted())
|
||||
/**
|
||||
* Holds if `n` is known or suspected to be less than or equal to
|
||||
* `sub.getLeftOperand()`.
|
||||
*/
|
||||
predicate exprIsSubLeftOrLess(SubExpr sub, DataFlow::Node n) {
|
||||
n.asExpr() = sub.getLeftOperand()
|
||||
or
|
||||
// The subtraction is guarded by a check of the form `left >= right`.
|
||||
exists(GVN left, GVN right |
|
||||
// This is basically a poor man's version of a directional unbind operator.
|
||||
strictcount([left, globalValueNumber(sub.getLeftOperand())]) = 1 and
|
||||
strictcount([right, globalValueNumber(sub.getRightOperand())]) = 1 and
|
||||
isGuarded(sub, left.getAnExpr(), right.getAnExpr())
|
||||
exists(DataFlow::Node other |
|
||||
// dataflow
|
||||
exprIsSubLeftOrLess(sub, other) and
|
||||
(
|
||||
DataFlow::localFlowStep(n, other) or
|
||||
DataFlow::localFlowStep(other, n)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node other |
|
||||
// guard constraining `sub`
|
||||
exprIsSubLeftOrLess(sub, other) and
|
||||
isGuarded(sub, other.asExpr(), n.asExpr()) // other >= n
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node other, float p, float q |
|
||||
// linear access of `other`
|
||||
exprIsSubLeftOrLess(sub, other) and
|
||||
linearAccess(n.asExpr(), other.asExpr(), p, q) and // n = p * other + q
|
||||
p <= 1 and
|
||||
q <= 0
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node other, float p, float q |
|
||||
// linear access of `n`
|
||||
exprIsSubLeftOrLess(sub, other) and
|
||||
linearAccess(other.asExpr(), n.asExpr(), p, q) and // other = p * n + q
|
||||
p >= 1 and
|
||||
q >= 0
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,5 +76,6 @@ where
|
||||
ro.getLesserOperand().getValue().toInt() = 0 and
|
||||
ro.getGreaterOperand() = sub and
|
||||
sub.getFullyConverted().getUnspecifiedType().(IntegralType).isUnsigned() and
|
||||
not nonNegative(sub)
|
||||
exprMightOverflowNegatively(sub.getFullyConverted()) and // generally catches false positives involving constants
|
||||
not exprIsSubLeftOrLess(sub, DataFlow::exprNode(sub.getRightOperand())) // generally catches false positives where there's a relation between the left and right operands
|
||||
select ro, "Unsigned subtraction can never be negative."
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// BAD: the allocation will throw an unhandled exception
|
||||
// instead of returning a null pointer.
|
||||
void bad1(std::size_t length) noexcept {
|
||||
int* dest = new int[length];
|
||||
if(!dest) {
|
||||
return;
|
||||
}
|
||||
std::memset(dest, 0, length);
|
||||
// ...
|
||||
}
|
||||
|
||||
// BAD: the allocation won't throw an exception, but
|
||||
// instead return a null pointer.
|
||||
void bad2(std::size_t length) noexcept {
|
||||
try {
|
||||
int* dest = new(std::nothrow) int[length];
|
||||
std::memset(dest, 0, length);
|
||||
// ...
|
||||
} catch(std::bad_alloc&) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// GOOD: the allocation failure is handled appropiately.
|
||||
void good1(std::size_t length) noexcept {
|
||||
try {
|
||||
int* dest = new int[length];
|
||||
std::memset(dest, 0, length);
|
||||
// ...
|
||||
} catch(std::bad_alloc&) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// GOOD: the allocation failure is handled appropiately.
|
||||
void good2(std::size_t length) noexcept {
|
||||
int* dest = new int[length];
|
||||
if(!dest) {
|
||||
return;
|
||||
}
|
||||
std::memset(dest, 0, length);
|
||||
// ...
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Different overloads of the <code>new</code> operator handle allocation failures in different ways.
|
||||
If <code>new T</code> fails for some type <code>T</code>, it throws a <code>std::bad_alloc</code> exception,
|
||||
but <code>new(std::nothrow) T</code> returns a null pointer. If the programmer does not use the corresponding
|
||||
method of error handling, allocation failure may go unhandled and could cause the program to behave in
|
||||
unexpected ways.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Make sure that exceptions are handled appropriately if <code>new T</code> is used. On the other hand,
|
||||
make sure to handle the possibility of null pointers if <code>new(std::nothrow) T</code> is used.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="IncorrectAllocationErrorHandling.cpp" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
CERT C++ Coding Standard:
|
||||
<a href="https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM52-CPP.+Detect+and+handle+memory+allocation+errors">MEM52-CPP. Detect and handle memory allocation errors</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* @name Incorrect allocation-error handling
|
||||
* @description `operator new` throws an exception on allocation failures, while `operator new(std::nothrow)` returns a null pointer. Mixing up these two failure conditions can result in unexpected behavior.
|
||||
* @kind problem
|
||||
* @id cpp/incorrect-allocation-error-handling
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-570
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
import semmle.code.cpp.controlflow.Guards
|
||||
|
||||
/**
|
||||
* A C++ `delete` or `delete[]` expression.
|
||||
*/
|
||||
class DeleteOrDeleteArrayExpr extends Expr {
|
||||
DeleteOrDeleteArrayExpr() { this instanceof DeleteExpr or this instanceof DeleteArrayExpr }
|
||||
|
||||
DeallocationFunction getDeallocator() {
|
||||
result = [this.(DeleteExpr).getDeallocator(), this.(DeleteArrayExpr).getDeallocator()]
|
||||
}
|
||||
|
||||
Destructor getDestructor() {
|
||||
result = [this.(DeleteExpr).getDestructor(), this.(DeleteArrayExpr).getDestructor()]
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the `Constructor` invoked when `newExpr` allocates memory. */
|
||||
Constructor getConstructorForAllocation(NewOrNewArrayExpr newExpr) {
|
||||
result.getACallToThisFunction() = newExpr.getInitializer()
|
||||
}
|
||||
|
||||
/** Gets the `Destructor` invoked when `deleteExpr` deallocates memory. */
|
||||
Destructor getDestructorForDeallocation(DeleteOrDeleteArrayExpr deleteExpr) {
|
||||
result = deleteExpr.getDestructor()
|
||||
}
|
||||
|
||||
/** Holds if the evaluation of `newExpr` may throw an exception. */
|
||||
predicate newMayThrow(NewOrNewArrayExpr newExpr) {
|
||||
functionMayThrow(newExpr.getAllocator()) or
|
||||
functionMayThrow(getConstructorForAllocation(newExpr))
|
||||
}
|
||||
|
||||
/** Holds if the evaluation of `deleteExpr` may throw an exception. */
|
||||
predicate deleteMayThrow(DeleteOrDeleteArrayExpr deleteExpr) {
|
||||
functionMayThrow(deleteExpr.getDeallocator()) or
|
||||
functionMayThrow(getDestructorForDeallocation(deleteExpr))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the function may throw an exception when called. That is, if the body of the function looks
|
||||
* like it might throw an exception, and the function does not have a `noexcept` or `throw()` specifier.
|
||||
*/
|
||||
predicate functionMayThrow(Function f) {
|
||||
(not exists(f.getBlock()) or stmtMayThrow(f.getBlock())) and
|
||||
not f.isNoExcept() and
|
||||
not f.isNoThrow()
|
||||
}
|
||||
|
||||
/** Holds if the evaluation of `stmt` may throw an exception. */
|
||||
predicate stmtMayThrow(Stmt stmt) {
|
||||
stmtMayThrow(stmt.(BlockStmt).getAStmt())
|
||||
or
|
||||
convertedExprMayThrow(stmt.(ExprStmt).getExpr())
|
||||
or
|
||||
convertedExprMayThrow(stmt.(DeclStmt).getADeclaration().(Variable).getInitializer().getExpr())
|
||||
or
|
||||
exists(IfStmt ifStmt | ifStmt = stmt |
|
||||
convertedExprMayThrow(ifStmt.getCondition()) or
|
||||
stmtMayThrow([ifStmt.getThen(), ifStmt.getElse()])
|
||||
)
|
||||
or
|
||||
exists(ConstexprIfStmt constIfStmt | constIfStmt = stmt |
|
||||
stmtMayThrow([constIfStmt.getThen(), constIfStmt.getElse()])
|
||||
)
|
||||
or
|
||||
exists(Loop loop | loop = stmt |
|
||||
convertedExprMayThrow(loop.getCondition()) or
|
||||
stmtMayThrow(loop.getStmt())
|
||||
)
|
||||
or
|
||||
// The case for `Loop` already checked the condition and the statement.
|
||||
convertedExprMayThrow(stmt.(RangeBasedForStmt).getUpdate())
|
||||
or
|
||||
// The case for `Loop` already checked the condition and the statement.
|
||||
exists(ForStmt forStmt | forStmt = stmt |
|
||||
stmtMayThrow(forStmt.getInitialization())
|
||||
or
|
||||
convertedExprMayThrow(forStmt.getUpdate())
|
||||
)
|
||||
or
|
||||
exists(SwitchStmt switchStmt | switchStmt = stmt |
|
||||
convertedExprMayThrow(switchStmt.getExpr()) or
|
||||
stmtMayThrow(switchStmt.getStmt())
|
||||
)
|
||||
or
|
||||
// NOTE: We don't include `TryStmt` as those exceptions are not "observable" outside the function.
|
||||
stmtMayThrow(stmt.(Handler).getBlock())
|
||||
or
|
||||
convertedExprMayThrow(stmt.(CoReturnStmt).getExpr())
|
||||
or
|
||||
convertedExprMayThrow(stmt.(ReturnStmt).getExpr())
|
||||
}
|
||||
|
||||
/** Holds if the evaluation of `e` (including conversions) may throw an exception. */
|
||||
predicate convertedExprMayThrow(Expr e) {
|
||||
exprMayThrow(e)
|
||||
or
|
||||
convertedExprMayThrow(e.getConversion())
|
||||
}
|
||||
|
||||
/** Holds if the evaluation of `e` may throw an exception. */
|
||||
predicate exprMayThrow(Expr e) {
|
||||
e instanceof DynamicCast
|
||||
or
|
||||
e instanceof TypeidOperator
|
||||
or
|
||||
e instanceof ThrowExpr
|
||||
or
|
||||
newMayThrow(e)
|
||||
or
|
||||
deleteMayThrow(e)
|
||||
or
|
||||
convertedExprMayThrow(e.(UnaryOperation).getOperand())
|
||||
or
|
||||
exists(BinaryOperation binOp | binOp = e |
|
||||
convertedExprMayThrow([binOp.getLeftOperand(), binOp.getRightOperand()])
|
||||
)
|
||||
or
|
||||
exists(Assignment assign | assign = e |
|
||||
convertedExprMayThrow([assign.getLValue(), assign.getRValue()])
|
||||
)
|
||||
or
|
||||
exists(CommaExpr comma | comma = e |
|
||||
convertedExprMayThrow([comma.getLeftOperand(), comma.getRightOperand()])
|
||||
)
|
||||
or
|
||||
exists(StmtExpr stmtExpr | stmtExpr = e |
|
||||
convertedExprMayThrow(stmtExpr.getResultExpr()) or
|
||||
stmtMayThrow(stmtExpr.getStmt())
|
||||
)
|
||||
or
|
||||
convertedExprMayThrow(e.(Conversion).getExpr())
|
||||
or
|
||||
exists(FunctionCall fc | fc = e |
|
||||
not exists(fc.getTarget()) or
|
||||
functionMayThrow(fc.getTarget()) or
|
||||
convertedExprMayThrow(fc.getAnArgument())
|
||||
)
|
||||
}
|
||||
|
||||
/** An allocator that might throw an exception. */
|
||||
class ThrowingAllocator extends Function {
|
||||
ThrowingAllocator() {
|
||||
exists(NewOrNewArrayExpr newExpr |
|
||||
newExpr.getAllocator() = this and
|
||||
functionMayThrow(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** The `std::bad_alloc` exception and its `bsl` variant. */
|
||||
class BadAllocType extends Class {
|
||||
BadAllocType() { this.hasGlobalOrStdOrBslName("bad_alloc") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A catch block that catches a `std::bad_alloc` (or any of its superclasses), or a catch
|
||||
* block that catches every exception (i.e., `catch(...)`).
|
||||
*/
|
||||
class BadAllocCatchBlock extends CatchBlock {
|
||||
BadAllocCatchBlock() {
|
||||
this.getParameter().getUnspecifiedType().stripType() =
|
||||
any(BadAllocType badAlloc).getABaseClass*()
|
||||
or
|
||||
not exists(this.getParameter())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `newExpr` is embedded in a `try` statement with a catch block `catchBlock` that
|
||||
* catches a `std::bad_alloc` exception, but nothing in the `try` block (including the `newExpr`)
|
||||
* will throw that exception.
|
||||
*/
|
||||
predicate noThrowInTryBlock(NewOrNewArrayExpr newExpr, BadAllocCatchBlock catchBlock) {
|
||||
exists(TryStmt try |
|
||||
not stmtMayThrow(try.getStmt()) and
|
||||
try.getACatchClause() = catchBlock and
|
||||
newExpr.getEnclosingBlock().getEnclosingBlock*() = try.getStmt()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `newExpr` is handles allocation failures by throwing an exception, yet
|
||||
* the guard condition `guard` compares the result of `newExpr` to a null value.
|
||||
*/
|
||||
predicate nullCheckInThrowingNew(NewOrNewArrayExpr newExpr, GuardCondition guard) {
|
||||
newExpr.getAllocator() instanceof ThrowingAllocator and
|
||||
(
|
||||
// Handles null comparisons.
|
||||
guard.ensuresEq(globalValueNumber(newExpr).getAnExpr(), any(NullValue null), _, _, _)
|
||||
or
|
||||
// Handles `if(ptr)` and `if(!ptr)` cases.
|
||||
guard = globalValueNumber(newExpr).getAnExpr()
|
||||
)
|
||||
}
|
||||
|
||||
from NewOrNewArrayExpr newExpr, Element element, string msg, string elementString
|
||||
where
|
||||
not newExpr.isFromUninstantiatedTemplate(_) and
|
||||
(
|
||||
noThrowInTryBlock(newExpr, element) and
|
||||
msg = "This allocation cannot throw. $@ is unnecessary." and
|
||||
elementString = "This catch block"
|
||||
or
|
||||
nullCheckInThrowingNew(newExpr, element) and
|
||||
msg = "This allocation cannot return null. $@ is unnecessary." and
|
||||
elementString = "This check"
|
||||
)
|
||||
select newExpr, msg, element, elementString
|
||||
@@ -1,35 +0,0 @@
|
||||
// BAD: on memory allocation error, the program terminates.
|
||||
void badFunction(const int *source, std::size_t length) noexcept {
|
||||
int * dest = new int[length];
|
||||
std::memset(dest, 0, length);
|
||||
// ..
|
||||
}
|
||||
// GOOD: memory allocation error will be handled.
|
||||
void goodFunction(const int *source, std::size_t length) noexcept {
|
||||
try {
|
||||
int * dest = new int[length];
|
||||
} catch(std::bad_alloc) {
|
||||
// ...
|
||||
}
|
||||
std::memset(dest, 0, length);
|
||||
// ..
|
||||
}
|
||||
// BAD: memory allocation error will not be handled.
|
||||
void badFunction(const int *source, std::size_t length) noexcept {
|
||||
try {
|
||||
int * dest = new (std::nothrow) int[length];
|
||||
} catch(std::bad_alloc) {
|
||||
// ...
|
||||
}
|
||||
std::memset(dest, 0, length);
|
||||
// ..
|
||||
}
|
||||
// GOOD: memory allocation error will be handled.
|
||||
void goodFunction(const int *source, std::size_t length) noexcept {
|
||||
int * dest = new (std::nothrow) int[length];
|
||||
if (!dest) {
|
||||
return;
|
||||
}
|
||||
std::memset(dest, 0, length);
|
||||
// ..
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>When using the <code>new</code> operator to allocate memory, you need to pay attention to the different ways of detecting errors. <code>::operator new(std::size_t)</code> throws an exception on error, whereas <code>::operator new(std::size_t, const std::nothrow_t &)</code> returns zero on error. The programmer can get confused and check the error that occurs when allocating memory incorrectly. That can lead to an unhandled program termination or to a violation of the program logic.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Use the correct error detection method corresponding with the memory allocation.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The following example demonstrates various approaches to detecting memory allocation errors using the <code>new</code> operator.</p>
|
||||
<sample src="WrongInDetectingAndHandlingMemoryAllocationErrors.cpp" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
CERT C++ Coding Standard:
|
||||
<a href="https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM52-CPP.+Detect+and+handle+memory+allocation+errors">MEM52-CPP. Detect and handle memory allocation errors</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* @name Detect And Handle Memory Allocation Errors
|
||||
* @description --::operator new(std::size_t) throws an exception on error, and ::operator new(std::size_t, const std::nothrow_t &) returns zero on error.
|
||||
* --the programmer can get confused when check the error that occurs when allocating memory incorrectly.
|
||||
* @kind problem
|
||||
* @id cpp/detect-and-handle-memory-allocation-errors
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-570
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
/**
|
||||
* Lookup if condition compare with 0
|
||||
*/
|
||||
class IfCompareWithZero extends IfStmt {
|
||||
IfCompareWithZero() {
|
||||
this.getCondition().(EQExpr).getAChild().getValue() = "0"
|
||||
or
|
||||
this.getCondition().(NEExpr).getAChild().getValue() = "0" and
|
||||
this.hasElse()
|
||||
or
|
||||
this.getCondition().(NEExpr).getAChild().getValue() = "0" and
|
||||
this.getThen().getAChild*() instanceof ReturnStmt
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lookup for calls to `operator new`, with incorrect error handling.
|
||||
*/
|
||||
class WrongCheckErrorOperatorNew extends FunctionCall {
|
||||
Expr exp;
|
||||
|
||||
WrongCheckErrorOperatorNew() {
|
||||
this = exp.(NewOrNewArrayExpr).getAChild().(FunctionCall) and
|
||||
(
|
||||
this.getTarget().hasGlobalOrStdName("operator new")
|
||||
or
|
||||
this.getTarget().hasGlobalOrStdName("operator new[]")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if handler `try ... catch` exists.
|
||||
*/
|
||||
predicate isExistsTryCatchBlock() {
|
||||
exists(TryStmt ts | this.getEnclosingStmt() = ts.getStmt().getAChild*())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if results call `operator new` check in `operator if`.
|
||||
*/
|
||||
predicate isExistsIfCondition() {
|
||||
exists(IfCompareWithZero ifc, AssignExpr aex, Initializer it |
|
||||
// call `operator new` directly from the condition of `operator if`.
|
||||
this = ifc.getCondition().getAChild*()
|
||||
or
|
||||
// check results call `operator new` with variable appropriation
|
||||
postDominates(ifc, this) and
|
||||
aex.getAChild() = exp and
|
||||
ifc.getCondition().getAChild().(VariableAccess).getTarget() =
|
||||
aex.getLValue().(VariableAccess).getTarget()
|
||||
or
|
||||
// check results call `operator new` with declaration variable
|
||||
postDominates(ifc, this) and
|
||||
exp = it.getExpr() and
|
||||
it.getDeclaration() = ifc.getCondition().getAChild().(VariableAccess).getTarget()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(std::nothrow)` or `(std::noexcept)` exists in call `operator new`.
|
||||
*/
|
||||
predicate isExistsNothrow() { getTarget().isNoExcept() or getTarget().isNoThrow() }
|
||||
}
|
||||
|
||||
from WrongCheckErrorOperatorNew op
|
||||
where
|
||||
// use call `operator new` with `(std::nothrow)` and checking error using `try ... catch` block and not `operator if`
|
||||
op.isExistsNothrow() and not op.isExistsIfCondition() and op.isExistsTryCatchBlock()
|
||||
or
|
||||
// use call `operator new` without `(std::nothrow)` and checking error using `operator if` and not `try ... catch` block
|
||||
not op.isExistsNothrow() and not op.isExistsTryCatchBlock() and op.isExistsIfCondition()
|
||||
select op, "memory allocation error check is incorrect or missing"
|
||||
@@ -0,0 +1,17 @@
|
||||
while(flagsLoop)
|
||||
{
|
||||
...
|
||||
if(flagsIf) break;
|
||||
...
|
||||
}while(flagsLoop); // BAD: when exiting through `break`, it is possible to get into an eternal loop.
|
||||
...
|
||||
while(flagsLoop)
|
||||
{
|
||||
...
|
||||
if(flagsIf) break;
|
||||
...
|
||||
} // GOOD: correct cycle
|
||||
...
|
||||
if(intA+intB) return 1; // BAD: possibly no comparison
|
||||
...
|
||||
if(intA+intB>intC) return 1; // GOOD: correct comparison
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>In some situations, after code refactoring, parts of the old constructs may remain. They are correctly accepted by the compiler, but can critically affect program execution. For example, if you switch from `do {...} while ();` to `while () {...}` forgetting to remove the old construct completely, you get `while(){...}while();` which may be vulnerable. These code snippets look suspicious and require the developer's attention.</p>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>We recommend that you use more explicit code transformations.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>The following example demonstrates the erroneous and corrected sections of the code.</p>
|
||||
<sample src="InsufficientControlFlowManagementAfterRefactoringTheCode.c" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
CWE Common Weakness Enumeration:
|
||||
<a href="https://cwe.mitre.org/data/definitions/691.html"> CWE-691: Insufficient Control Flow Management</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @name Errors After Refactoring
|
||||
* @description --In some situations, after code refactoring, parts of the old constructs may remain.
|
||||
* --They are correctly accepted by the compiler, but can critically affect program execution.
|
||||
* --For example, if you switch from `do {...} while ();` to `while () {...}` with errors, you run the risk of running out of resources.
|
||||
* --These code snippets look suspicious and require the developer's attention.
|
||||
* @kind problem
|
||||
* @id cpp/errors-after-refactoring
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-691
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.valuenumbering.HashCons
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
|
||||
/**
|
||||
* Using `while` directly after the body of another` while`.
|
||||
*/
|
||||
class UsingWhileAfterWhile extends WhileStmt {
|
||||
/**
|
||||
* Using a loop call after another loop has finished running can result in an eternal loop.
|
||||
* For example, perhaps as a result of refactoring, the `do ... while ()` loop was incorrectly corrected.
|
||||
* Even in the case of deliberate use of such an expression, it is better to correct it.
|
||||
*/
|
||||
UsingWhileAfterWhile() {
|
||||
exists(WhileStmt wh1 |
|
||||
wh1.getStmt().getAChild*().(BreakStmt).(ControlFlowNode).getASuccessor().getASuccessor() =
|
||||
this and
|
||||
hashCons(wh1.getCondition()) = hashCons(this.getCondition()) and
|
||||
this.getStmt() instanceof EmptyStmt
|
||||
)
|
||||
or
|
||||
exists(ForStmt fr1 |
|
||||
fr1.getStmt().getAChild*().(BreakStmt).(ControlFlowNode).getASuccessor().getASuccessor() =
|
||||
this and
|
||||
hashCons(fr1.getCondition()) = hashCons(this.getCondition()) and
|
||||
this.getStmt() instanceof EmptyStmt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Using arithmetic in a condition.
|
||||
*/
|
||||
class UsingArithmeticInComparison extends BinaryArithmeticOperation {
|
||||
/**
|
||||
* Using arithmetic operations in a comparison operation can be dangerous.
|
||||
* For example, part of the comparison may have been lost as a result of refactoring.
|
||||
* Even if you deliberately use such an expression, it is better to add an explicit comparison.
|
||||
*/
|
||||
UsingArithmeticInComparison() {
|
||||
this.getParent*() instanceof IfStmt and
|
||||
not this.getAChild*().isConstant() and
|
||||
not this.getParent*() instanceof Call and
|
||||
not this.getParent*() instanceof AssignExpr and
|
||||
not this.getParent*() instanceof ArrayExpr and
|
||||
not this.getParent*() instanceof RemExpr and
|
||||
not this.getParent*() instanceof AssignBitwiseOperation and
|
||||
not this.getParent*() instanceof AssignArithmeticOperation and
|
||||
not this.getParent*() instanceof EqualityOperation and
|
||||
not this.getParent*() instanceof RelationalOperation
|
||||
}
|
||||
|
||||
/** Holds when the expression is inside the loop body. */
|
||||
predicate insideTheLoop() { exists(Loop lp | lp.getStmt().getAChild*() = this.getParent*()) }
|
||||
|
||||
/** Holds when the expression is used in binary operations. */
|
||||
predicate workingWithValue() {
|
||||
this.getParent*() instanceof BinaryBitwiseOperation or
|
||||
this.getParent*() instanceof NotExpr
|
||||
}
|
||||
|
||||
/** Holds when the expression contains a pointer. */
|
||||
predicate workingWithPointer() {
|
||||
this.getAChild*().getFullyConverted().getType() instanceof DerivedType
|
||||
}
|
||||
|
||||
/** Holds when a null comparison expression exists. */
|
||||
predicate compareWithZero() {
|
||||
exists(Expr exp |
|
||||
exp instanceof ComparisonOperation and
|
||||
(
|
||||
globalValueNumber(exp.getAChild*()) = globalValueNumber(this) or
|
||||
hashCons(exp.getAChild*()) = hashCons(this)
|
||||
) and
|
||||
(
|
||||
exp.(ComparisonOperation).getLeftOperand().getValue() = "0" or
|
||||
exp.(ComparisonOperation).getRightOperand().getValue() = "0"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds when a comparison expression exists. */
|
||||
predicate compareWithOutZero() {
|
||||
exists(Expr exp |
|
||||
exp instanceof ComparisonOperation and
|
||||
(
|
||||
globalValueNumber(exp.getAChild*()) = globalValueNumber(this) or
|
||||
hashCons(exp.getAChild*()) = hashCons(this)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from Expr exp
|
||||
where
|
||||
exp instanceof UsingArithmeticInComparison and
|
||||
not exp.(UsingArithmeticInComparison).workingWithValue() and
|
||||
not exp.(UsingArithmeticInComparison).workingWithPointer() and
|
||||
not exp.(UsingArithmeticInComparison).insideTheLoop() and
|
||||
not exp.(UsingArithmeticInComparison).compareWithZero() and
|
||||
exp.(UsingArithmeticInComparison).compareWithOutZero()
|
||||
or
|
||||
exists(WhileStmt wst | wst instanceof UsingWhileAfterWhile and exp = wst.getCondition())
|
||||
select exp, "this expression needs your attention"
|
||||
@@ -33,7 +33,7 @@ the break statement only exits from one level of the loop.</p>
|
||||
</li>
|
||||
<li>
|
||||
Mats Henricson and Erik Nyquist, <i>Industrial Strength C++</i>, published by Prentice Hall PTR (1997).
|
||||
Chapter 4: Control Flow, Rule 4.6 (<a href="http://mongers.org/industrial-c++/">PDF</a>).
|
||||
Chapter 4: Control Flow, Rule 4.6 (<a href="https://web.archive.org/web/20190919025638/https://mongers.org/industrial-c++/">PDF</a>).
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.cplusplus.com/doc/tutorial/control/">www.cplusplus.com Control Structures</a>
|
||||
|
||||
@@ -39,7 +39,7 @@ loop if the loop requires more complicated variable iteration.
|
||||
</li>
|
||||
<li>
|
||||
Mats Henricson and Erik Nyquist, <i>Industrial Strength C++</i>, published by Prentice Hall PTR (1997).
|
||||
Chapter 4: Control Flow, Rule 4.1 (<a href="http://mongers.org/industrial-c++/">PDF</a>).
|
||||
Chapter 4: Control Flow, Rule 4.1 (<a href="https://web.archive.org/web/20190919025638/https://mongers.org/industrial-c++/">PDF</a>).
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ class Type extends Locatable, @type {
|
||||
*
|
||||
* For example, starting with `const i64* const` in the context of `typedef long long i64;`, this predicate will return `long long*`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
Type getUnspecifiedType() { unspecifiedtype(underlyingElement(this), unresolveElement(result)) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ import cpp
|
||||
* In rare cases, the same node is used in multiple control-flow scopes. This
|
||||
* confuses the dominance analysis, so this predicate is used to exclude them.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate hasMultiScopeNode(Function f) {
|
||||
exists(ControlFlowNode node |
|
||||
node.getControlFlowScope() = f and
|
||||
|
||||
@@ -1307,7 +1307,8 @@ private predicate conditionJumps(Expr test, boolean truth, Node n2, Pos p2) {
|
||||
)
|
||||
}
|
||||
|
||||
// Factored out for performance. See QL-796.
|
||||
// Pulled out for performance. See
|
||||
// https://github.com/github/codeql-coreql-team/issues/1044.
|
||||
private predicate normalGroupMemberBaseCase(Node memberNode, Pos memberPos, Node atNode) {
|
||||
memberNode = atNode and
|
||||
memberPos.isAt() and
|
||||
|
||||
@@ -104,9 +104,43 @@ private predicate loopConditionAlwaysUponEntry(ControlFlowNode loop, Expr condit
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This relation is the same as the `el instanceof Function`, only obfuscated
|
||||
* so the optimizer will not understand that any `FunctionCall.getTarget()`
|
||||
* should be in this relation.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate isFunction(Element el) {
|
||||
el instanceof Function
|
||||
or
|
||||
el.(Expr).getParent() = el
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fc` is a `FunctionCall` with no return value for `getTarget`. This
|
||||
* can happen in case of rare database inconsistencies.
|
||||
*/
|
||||
pragma[noopt]
|
||||
private predicate callHasNoTarget(@funbindexpr fc) {
|
||||
exists(Function f |
|
||||
funbind(fc, f) and
|
||||
not isFunction(f)
|
||||
)
|
||||
}
|
||||
|
||||
// Pulled out for performance. See
|
||||
// https://github.com/github/codeql-coreql-team/issues/1044.
|
||||
private predicate potentiallyReturningFunctionCall_base(FunctionCall fc) {
|
||||
fc.isVirtual()
|
||||
or
|
||||
callHasNoTarget(fc)
|
||||
}
|
||||
|
||||
/** A function call that *may* return; if in doubt, we assume it may. */
|
||||
private predicate potentiallyReturningFunctionCall(FunctionCall fc) {
|
||||
potentiallyReturningFunction(fc.getTarget()) or fc.isVirtual()
|
||||
potentiallyReturningFunctionCall_base(fc)
|
||||
or
|
||||
potentiallyReturningFunction(fc.getTarget())
|
||||
}
|
||||
|
||||
/** A function that *may* return; if in doubt, we assume it may. */
|
||||
|
||||
@@ -31,7 +31,7 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) {
|
||||
* currently excludes read-steps, store-steps, and flow-through.
|
||||
*
|
||||
* The analysis uses non-linear recursion: When computing a flow path in or out
|
||||
* of a call, we use the results of the analysis recursively to resolve lamba
|
||||
* of a call, we use the results of the analysis recursively to resolve lambda
|
||||
* calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
|
||||
*/
|
||||
private module LambdaFlow {
|
||||
|
||||
@@ -321,5 +321,5 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
|
||||
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
|
||||
|
||||
/** Extra data-flow steps needed for lamba flow analysis. */
|
||||
/** Extra data-flow steps needed for lambda flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
|
||||
|
||||
@@ -850,6 +850,24 @@ class NewOrNewArrayExpr extends Expr, @any_new_expr {
|
||||
this.getAllocatorCall()
|
||||
.getArgument(this.getAllocator().(OperatorNewAllocationFunction).getPlacementArgument())
|
||||
}
|
||||
|
||||
/**
|
||||
* For `operator new`, this gets the call or expression that initializes the allocated object, if any.
|
||||
*
|
||||
* As examples, for `new int(4)`, this will be `4`, and for `new std::vector(4)`, this will
|
||||
* be a call to the constructor `std::vector::vector(size_t)` with `4` as an argument.
|
||||
*
|
||||
* For `operator new[]`, this gets the call or expression that initializes the first element of the
|
||||
* array, if any.
|
||||
*
|
||||
* This will either be a call to the default constructor for the array's element type (as
|
||||
* in `new std::string[10]`), or a literal zero for arrays of scalars which are zero-initialized
|
||||
* due to extra parentheses (as in `new int[10]()`).
|
||||
*
|
||||
* At runtime, the constructor will be called once for each element in the array, but the
|
||||
* constructor call only exists once in the AST.
|
||||
*/
|
||||
final Expr getInitializer() { result = this.getChild(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -871,14 +889,6 @@ class NewExpr extends NewOrNewArrayExpr, @new_expr {
|
||||
override Type getAllocatedType() {
|
||||
new_allocated_type(underlyingElement(this), unresolveElement(result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the call or expression that initializes the allocated object, if any.
|
||||
*
|
||||
* As examples, for `new int(4)`, this will be `4`, and for `new std::vector(4)`, this will
|
||||
* be a call to the constructor `std::vector::vector(size_t)` with `4` as an argument.
|
||||
*/
|
||||
Expr getInitializer() { result = this.getChild(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -909,18 +919,6 @@ class NewArrayExpr extends NewOrNewArrayExpr, @new_array_expr {
|
||||
result = getType().getUnderlyingType().(PointerType).getBaseType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the call or expression that initializes the first element of the array, if any.
|
||||
*
|
||||
* This will either be a call to the default constructor for the array's element type (as
|
||||
* in `new std::string[10]`), or a literal zero for arrays of scalars which are zero-initialized
|
||||
* due to extra parentheses (as in `new int[10]()`).
|
||||
*
|
||||
* At runtime, the constructor will be called once for each element in the array, but the
|
||||
* constructor call only exists once in the AST.
|
||||
*/
|
||||
Expr getInitializer() { result = this.getChild(1) }
|
||||
|
||||
/**
|
||||
* Gets the extent of the non-constant array dimension, if any.
|
||||
*
|
||||
@@ -1271,7 +1269,8 @@ private predicate convparents(Expr child, int idx, Element parent) {
|
||||
)
|
||||
}
|
||||
|
||||
// Pulled out for performance. See QL-796.
|
||||
// Pulled out for performance. See
|
||||
// https://github.com/github/codeql-coreql-team/issues/1044.
|
||||
private predicate hasNoConversions(Expr e) { not e.hasConversion() }
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,7 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) {
|
||||
* currently excludes read-steps, store-steps, and flow-through.
|
||||
*
|
||||
* The analysis uses non-linear recursion: When computing a flow path in or out
|
||||
* of a call, we use the results of the analysis recursively to resolve lamba
|
||||
* of a call, we use the results of the analysis recursively to resolve lambda
|
||||
* calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
|
||||
*/
|
||||
private module LambdaFlow {
|
||||
|
||||
@@ -557,5 +557,5 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
|
||||
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
|
||||
|
||||
/** Extra data-flow steps needed for lamba flow analysis. */
|
||||
/** Extra data-flow steps needed for lambda flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
|
||||
|
||||
@@ -297,7 +297,8 @@ class Instruction extends Construction::TStageInstruction {
|
||||
/**
|
||||
* Gets the opcode that specifies the operation performed by this instruction.
|
||||
*/
|
||||
final Opcode getOpcode() { result = Construction::getInstructionOpcode(this) }
|
||||
pragma[inline]
|
||||
final Opcode getOpcode() { Construction::getInstructionOpcode(result, this) }
|
||||
|
||||
/**
|
||||
* Gets all direct uses of the result of this instruction. The result can be
|
||||
|
||||
@@ -338,15 +338,21 @@ private module Cached {
|
||||
instr = unreachedInstruction(_) and result = Language::getVoidType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `opcode` is the opcode that specifies the operation performed by `instr`.
|
||||
*
|
||||
* The parameters are ordered such that they produce a clean join (with no need for reordering)
|
||||
* in the characteristic predicates of the `Instruction` subclasses.
|
||||
*/
|
||||
cached
|
||||
Opcode getInstructionOpcode(Instruction instr) {
|
||||
result = getOldInstruction(instr).getOpcode()
|
||||
predicate getInstructionOpcode(Opcode opcode, Instruction instr) {
|
||||
opcode = getOldInstruction(instr).getOpcode()
|
||||
or
|
||||
instr = phiInstruction(_, _) and result instanceof Opcode::Phi
|
||||
instr = phiInstruction(_, _) and opcode instanceof Opcode::Phi
|
||||
or
|
||||
instr = chiInstruction(_) and result instanceof Opcode::Chi
|
||||
instr = chiInstruction(_) and opcode instanceof Opcode::Chi
|
||||
or
|
||||
instr = unreachedInstruction(_) and result instanceof Opcode::Unreached
|
||||
instr = unreachedInstruction(_) and opcode instanceof Opcode::Unreached
|
||||
}
|
||||
|
||||
cached
|
||||
|
||||
@@ -297,7 +297,8 @@ class Instruction extends Construction::TStageInstruction {
|
||||
/**
|
||||
* Gets the opcode that specifies the operation performed by this instruction.
|
||||
*/
|
||||
final Opcode getOpcode() { result = Construction::getInstructionOpcode(this) }
|
||||
pragma[inline]
|
||||
final Opcode getOpcode() { Construction::getInstructionOpcode(result, this) }
|
||||
|
||||
/**
|
||||
* Gets all direct uses of the result of this instruction. The result can be
|
||||
|
||||
@@ -360,8 +360,8 @@ CppType getInstructionResultType(TStageInstruction instr) {
|
||||
getInstructionTranslatedElement(instr).hasInstruction(_, getInstructionTag(instr), result)
|
||||
}
|
||||
|
||||
Opcode getInstructionOpcode(TStageInstruction instr) {
|
||||
getInstructionTranslatedElement(instr).hasInstruction(result, getInstructionTag(instr), _)
|
||||
predicate getInstructionOpcode(Opcode opcode, TStageInstruction instr) {
|
||||
getInstructionTranslatedElement(instr).hasInstruction(opcode, getInstructionTag(instr), _)
|
||||
}
|
||||
|
||||
IRFunctionBase getInstructionEnclosingIRFunction(TStageInstruction instr) {
|
||||
|
||||
@@ -42,7 +42,8 @@ IRTempVariable getIRTempVariable(Locatable ast, TempVariableTag tag) {
|
||||
*/
|
||||
predicate isIRConstant(Expr expr) { exists(expr.getValue()) }
|
||||
|
||||
// Pulled out to work around QL-796
|
||||
// Pulled out for performance. See
|
||||
// https://github.com/github/codeql-coreql-team/issues/1044.
|
||||
private predicate isOrphan(Expr expr) { not exists(getRealParent(expr)) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -297,7 +297,8 @@ class Instruction extends Construction::TStageInstruction {
|
||||
/**
|
||||
* Gets the opcode that specifies the operation performed by this instruction.
|
||||
*/
|
||||
final Opcode getOpcode() { result = Construction::getInstructionOpcode(this) }
|
||||
pragma[inline]
|
||||
final Opcode getOpcode() { Construction::getInstructionOpcode(result, this) }
|
||||
|
||||
/**
|
||||
* Gets all direct uses of the result of this instruction. The result can be
|
||||
|
||||
@@ -338,15 +338,21 @@ private module Cached {
|
||||
instr = unreachedInstruction(_) and result = Language::getVoidType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `opcode` is the opcode that specifies the operation performed by `instr`.
|
||||
*
|
||||
* The parameters are ordered such that they produce a clean join (with no need for reordering)
|
||||
* in the characteristic predicates of the `Instruction` subclasses.
|
||||
*/
|
||||
cached
|
||||
Opcode getInstructionOpcode(Instruction instr) {
|
||||
result = getOldInstruction(instr).getOpcode()
|
||||
predicate getInstructionOpcode(Opcode opcode, Instruction instr) {
|
||||
opcode = getOldInstruction(instr).getOpcode()
|
||||
or
|
||||
instr = phiInstruction(_, _) and result instanceof Opcode::Phi
|
||||
instr = phiInstruction(_, _) and opcode instanceof Opcode::Phi
|
||||
or
|
||||
instr = chiInstruction(_) and result instanceof Opcode::Chi
|
||||
instr = chiInstruction(_) and opcode instanceof Opcode::Chi
|
||||
or
|
||||
instr = unreachedInstruction(_) and result instanceof Opcode::Unreached
|
||||
instr = unreachedInstruction(_) and opcode instanceof Opcode::Unreached
|
||||
}
|
||||
|
||||
cached
|
||||
|
||||
@@ -1617,6 +1617,20 @@ private module SimpleRangeAnalysisCached {
|
||||
defMightOverflowPositively(def, v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is an expression where the concept of overflow makes sense.
|
||||
* This predicate is used to filter out some of the unanalyzable expressions
|
||||
* from `exprMightOverflowPositively` and `exprMightOverflowNegatively`.
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate exprThatCanOverflow(Expr e) {
|
||||
e instanceof UnaryArithmeticOperation or
|
||||
e instanceof BinaryArithmeticOperation or
|
||||
e instanceof AssignArithmeticOperation or
|
||||
e instanceof LShiftExpr or
|
||||
e instanceof AssignLShiftExpr
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the expression might overflow negatively. This predicate
|
||||
* does not consider the possibility that the expression might overflow
|
||||
@@ -1630,6 +1644,11 @@ private module SimpleRangeAnalysisCached {
|
||||
// bound of `x`, so the standard logic (above) does not work for
|
||||
// detecting whether it might overflow.
|
||||
getLowerBoundsImpl(expr.(PostfixDecrExpr)) = exprMinVal(expr)
|
||||
or
|
||||
// We can't conclude that any unanalyzable expression might overflow. This
|
||||
// is because there are many expressions that the range analysis doesn't
|
||||
// handle, but where the concept of overflow doesn't make sense.
|
||||
exprThatCanOverflow(expr) and not analyzableExpr(expr)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1657,6 +1676,11 @@ private module SimpleRangeAnalysisCached {
|
||||
// bound of `x`, so the standard logic (above) does not work for
|
||||
// detecting whether it might overflow.
|
||||
getUpperBoundsImpl(expr.(PostfixIncrExpr)) = exprMaxVal(expr)
|
||||
or
|
||||
// We can't conclude that any unanalyzable expression might overflow. This
|
||||
// is because there are many expressions that the range analysis doesn't
|
||||
// handle, but where the concept of overflow doesn't make sense.
|
||||
exprThatCanOverflow(expr) and not analyzableExpr(expr)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -99,12 +99,11 @@ VariableAccess varUse(LocalScopeVariable v) { result = v.getAnAccess() }
|
||||
* Holds if `e` potentially overflows and `use` is an operand of `e` that is not guarded.
|
||||
*/
|
||||
predicate missingGuardAgainstOverflow(Operation e, VariableAccess use) {
|
||||
(
|
||||
convertedExprMightOverflowPositively(e)
|
||||
or
|
||||
// Ensure that the predicate holds when range analysis cannot determine an upper bound
|
||||
upperBound(e.getFullyConverted()) = exprMaxVal(e.getFullyConverted())
|
||||
) and
|
||||
// Since `e` is guarenteed to be a `BinaryArithmeticOperation`, a `UnaryArithmeticOperation` or
|
||||
// an `AssignArithmeticOperation` by the other constraints in this predicate, we know that
|
||||
// `convertedExprMightOverflowPositively` will have a result even when `e` is not analyzable
|
||||
// by `SimpleRangeAnalysis`.
|
||||
convertedExprMightOverflowPositively(e) and
|
||||
use = e.getAnOperand() and
|
||||
exists(LocalScopeVariable v | use.getTarget() = v |
|
||||
// overflow possible if large
|
||||
@@ -126,12 +125,11 @@ predicate missingGuardAgainstOverflow(Operation e, VariableAccess use) {
|
||||
* Holds if `e` potentially underflows and `use` is an operand of `e` that is not guarded.
|
||||
*/
|
||||
predicate missingGuardAgainstUnderflow(Operation e, VariableAccess use) {
|
||||
(
|
||||
convertedExprMightOverflowNegatively(e)
|
||||
or
|
||||
// Ensure that the predicate holds when range analysis cannot determine a lower bound
|
||||
lowerBound(e.getFullyConverted()) = exprMinVal(e.getFullyConverted())
|
||||
) and
|
||||
// Since `e` is guarenteed to be a `BinaryArithmeticOperation`, a `UnaryArithmeticOperation` or
|
||||
// an `AssignArithmeticOperation` by the other constraints in this predicate, we know that
|
||||
// `convertedExprMightOverflowNegatively` will have a result even when `e` is not analyzable
|
||||
// by `SimpleRangeAnalysis`.
|
||||
convertedExprMightOverflowNegatively(e) and
|
||||
use = e.getAnOperand() and
|
||||
exists(LocalScopeVariable v | use.getTarget() = v |
|
||||
// underflow possible if use is left operand and small
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
| test.cpp:21:9:21:15 | new | This allocation cannot return null. $@ is unnecessary. | test.cpp:21:9:21:15 | new | This check |
|
||||
| test.cpp:29:13:29:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:30:7:30:13 | ... == ... | This check |
|
||||
| test.cpp:33:13:33:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:34:8:34:9 | p2 | This check |
|
||||
| test.cpp:37:13:37:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:38:7:38:16 | ... == ... | This check |
|
||||
| test.cpp:41:13:41:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:42:7:42:19 | ... == ... | This check |
|
||||
| test.cpp:45:13:45:24 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:46:7:46:8 | p5 | This check |
|
||||
| test.cpp:49:8:49:19 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:50:7:50:13 | ... == ... | This check |
|
||||
| test.cpp:53:8:53:19 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:54:8:54:9 | p7 | This check |
|
||||
| test.cpp:58:8:58:19 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:59:7:59:16 | ... == ... | This check |
|
||||
| test.cpp:63:8:63:19 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:64:7:64:19 | ... != ... | This check |
|
||||
| test.cpp:69:9:69:20 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:70:7:70:14 | ... != ... | This check |
|
||||
| test.cpp:75:11:75:22 | new[] | This allocation cannot return null. $@ is unnecessary. | test.cpp:76:13:76:15 | p11 | This check |
|
||||
| test.cpp:92:5:92:31 | new[] | This allocation cannot throw. $@ is unnecessary. | test.cpp:97:36:98:3 | { ... } | This catch block |
|
||||
| test.cpp:93:15:93:41 | new[] | This allocation cannot throw. $@ is unnecessary. | test.cpp:97:36:98:3 | { ... } | This catch block |
|
||||
| test.cpp:96:10:96:36 | new[] | This allocation cannot throw. $@ is unnecessary. | test.cpp:97:36:98:3 | { ... } | This catch block |
|
||||
| test.cpp:151:9:151:24 | new | This allocation cannot throw. $@ is unnecessary. | test.cpp:152:15:152:18 | { ... } | This catch block |
|
||||
| test.cpp:199:15:199:35 | new | This allocation cannot throw. $@ is unnecessary. | test.cpp:201:16:201:19 | { ... } | This catch block |
|
||||
| test.cpp:212:14:212:34 | new | This allocation cannot throw. $@ is unnecessary. | test.cpp:213:34:213:36 | { ... } | This catch block |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-570/IncorrectAllocationErrorHandling.ql
|
||||
@@ -1,5 +0,0 @@
|
||||
| test.cpp:30:15:30:26 | call to operator new[] | memory allocation error check is incorrect or missing |
|
||||
| test.cpp:38:9:38:20 | call to operator new[] | memory allocation error check is incorrect or missing |
|
||||
| test.cpp:50:13:50:38 | call to operator new[] | memory allocation error check is incorrect or missing |
|
||||
| test.cpp:51:22:51:47 | call to operator new[] | memory allocation error check is incorrect or missing |
|
||||
| test.cpp:53:18:53:43 | call to operator new[] | memory allocation error check is incorrect or missing |
|
||||
@@ -1 +0,0 @@
|
||||
experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
|
||||
@@ -1,97 +1,227 @@
|
||||
#define NULL ((void*)0)
|
||||
class exception {};
|
||||
#define NULL ((void *)0)
|
||||
|
||||
namespace std{
|
||||
struct nothrow_t {};
|
||||
typedef unsigned long size_t;
|
||||
class bad_alloc{
|
||||
const char* what() const throw();
|
||||
};
|
||||
extern const std::nothrow_t nothrow;
|
||||
}
|
||||
namespace std {
|
||||
struct nothrow_t {};
|
||||
typedef unsigned long size_t;
|
||||
|
||||
class exception {};
|
||||
class bad_alloc : public exception {};
|
||||
|
||||
extern const std::nothrow_t nothrow;
|
||||
} // namespace std
|
||||
|
||||
using namespace std;
|
||||
|
||||
void* operator new(std::size_t _Size);
|
||||
void* operator new[](std::size_t _Size);
|
||||
void* operator new( std::size_t count, const std::nothrow_t& tag ) noexcept;
|
||||
void* operator new[]( std::size_t count, const std::nothrow_t& tag ) noexcept;
|
||||
void *operator new(std::size_t);
|
||||
void *operator new[](std::size_t);
|
||||
void *operator new(std::size_t, const std::nothrow_t &) noexcept;
|
||||
void *operator new[](std::size_t, const std::nothrow_t &) noexcept;
|
||||
|
||||
void badNew_0_0()
|
||||
{
|
||||
while (true) {
|
||||
new int[100]; // BAD [NOT DETECTED]
|
||||
if(!(new int[100])) // BAD [NOT DETECTED]
|
||||
return;
|
||||
}
|
||||
}
|
||||
void badNew_0_1()
|
||||
{
|
||||
int * i = new int[100]; // BAD
|
||||
if(i == 0)
|
||||
return;
|
||||
if(!i)
|
||||
return;
|
||||
if(i == NULL)
|
||||
return;
|
||||
int * j;
|
||||
j = new int[100]; // BAD
|
||||
if(j == 0)
|
||||
return;
|
||||
if(!j)
|
||||
return;
|
||||
if(j == NULL)
|
||||
return;
|
||||
}
|
||||
void badNew_1_0()
|
||||
{
|
||||
try {
|
||||
while (true) {
|
||||
new(std::nothrow) int[100]; // BAD
|
||||
int* p = new(std::nothrow) int[100]; // BAD
|
||||
int* p1;
|
||||
p1 = new(std::nothrow) int[100]; // BAD
|
||||
}
|
||||
} catch (const exception &){//const std::bad_alloc& e) {
|
||||
// std::cout << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
void badNew_1_1()
|
||||
{
|
||||
while (true) {
|
||||
int* p = new(std::nothrow) int[100]; // BAD [NOT DETECTED]
|
||||
new(std::nothrow) int[100]; // BAD [NOT DETECTED]
|
||||
}
|
||||
void bad_new_in_condition() {
|
||||
if (!(new int)) { // BAD
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void goodNew_0_0()
|
||||
{
|
||||
try {
|
||||
while (true) {
|
||||
new int[100]; // GOOD
|
||||
}
|
||||
} catch (const exception &){//const std::bad_alloc& e) {
|
||||
// std::cout << e.what() << '\n';
|
||||
}
|
||||
void foo(int**);
|
||||
|
||||
void bad_new_missing_exception_handling() {
|
||||
int *p1 = new int[100]; // BAD
|
||||
if (p1 == 0)
|
||||
return;
|
||||
|
||||
int *p2 = new int[100]; // BAD
|
||||
if (!p2)
|
||||
return;
|
||||
|
||||
int *p3 = new int[100]; // BAD
|
||||
if (p3 == NULL)
|
||||
return;
|
||||
|
||||
int *p4 = new int[100]; // BAD
|
||||
if (p4 == nullptr)
|
||||
return;
|
||||
|
||||
int *p5 = new int[100]; // BAD
|
||||
if (p5) {} else return;
|
||||
|
||||
int *p6;
|
||||
p6 = new int[100]; // BAD
|
||||
if (p6 == 0) return;
|
||||
|
||||
int *p7;
|
||||
p7 = new int[100]; // BAD
|
||||
if (!p7)
|
||||
return;
|
||||
|
||||
int *p8;
|
||||
p8 = new int[100]; // BAD
|
||||
if (p8 == NULL)
|
||||
return;
|
||||
|
||||
int *p9;
|
||||
p9 = new int[100]; // BAD
|
||||
if (p9 != nullptr) {
|
||||
} else
|
||||
return;
|
||||
|
||||
int *p10;
|
||||
p10 = new int[100]; // BAD
|
||||
if (p10 != 0) {
|
||||
}
|
||||
|
||||
int *p11;
|
||||
do {
|
||||
p11 = new int[100]; // BAD
|
||||
} while (!p11);
|
||||
|
||||
int* p12 = new int[100];
|
||||
foo(&p12);
|
||||
if(p12) {} else return; // GOOD: p12 is probably modified in foo, so it's
|
||||
// not the return value of the new that's checked.
|
||||
|
||||
int* p13 = new int[100];
|
||||
foo(&p13);
|
||||
if(!p13) {
|
||||
return;
|
||||
} else { }; // GOOD: same as above.
|
||||
}
|
||||
|
||||
void goodNew_1_0()
|
||||
{
|
||||
while (true) {
|
||||
int* p = new(std::nothrow) int[100]; // GOOD
|
||||
if (p == nullptr) {
|
||||
// std::cout << "Allocation returned nullptr\n";
|
||||
break;
|
||||
}
|
||||
int* p1;
|
||||
p1 = new(std::nothrow) int[100]; // GOOD
|
||||
if (p1 == nullptr) {
|
||||
// std::cout << "Allocation returned nullptr\n";
|
||||
break;
|
||||
}
|
||||
if (new(std::nothrow) int[100] == nullptr) { // GOOD
|
||||
// std::cout << "Allocation returned nullptr\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
void bad_new_nothrow_in_exception_body() {
|
||||
try {
|
||||
new (std::nothrow) int[100]; // BAD
|
||||
int *p1 = new (std::nothrow) int[100]; // BAD
|
||||
|
||||
int *p2;
|
||||
p2 = new (std::nothrow) int[100]; // BAD
|
||||
} catch (const std::bad_alloc &) {
|
||||
}
|
||||
}
|
||||
|
||||
void good_new_has_exception_handling() {
|
||||
try {
|
||||
int *p1 = new int[100]; // GOOD
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void good_new_handles_nullptr() {
|
||||
int *p1 = new (std::nothrow) int[100]; // GOOD
|
||||
if (p1 == nullptr)
|
||||
return;
|
||||
|
||||
int *p2;
|
||||
p2 = new (std::nothrow) int[100]; // GOOD
|
||||
if (p2 == nullptr)
|
||||
return;
|
||||
|
||||
int *p3;
|
||||
p3 = new (std::nothrow) int[100]; // GOOD
|
||||
if (p3 != nullptr) {
|
||||
}
|
||||
|
||||
int *p4;
|
||||
p4 = new (std::nothrow) int[100]; // GOOD
|
||||
if (p4) {
|
||||
} else
|
||||
return;
|
||||
|
||||
int *p5;
|
||||
p5 = new (std::nothrow) int[100]; // GOOD
|
||||
if (p5 != nullptr) {
|
||||
} else
|
||||
return;
|
||||
|
||||
if (new (std::nothrow) int[100] == nullptr)
|
||||
return; // GOOD
|
||||
}
|
||||
|
||||
void* operator new(std::size_t count, void*) noexcept;
|
||||
void* operator new[](std::size_t count, void*) noexcept;
|
||||
|
||||
struct Foo {
|
||||
Foo() noexcept;
|
||||
Foo(int);
|
||||
|
||||
operator bool();
|
||||
};
|
||||
|
||||
void bad_placement_new_with_exception_handling() {
|
||||
char buffer[1024];
|
||||
try { new (buffer) Foo; } // BAD
|
||||
catch (...) { }
|
||||
}
|
||||
|
||||
void good_placement_new_with_exception_handling() {
|
||||
char buffer[1024];
|
||||
try { new (buffer) Foo(42); } // GOOD: Foo constructor might throw
|
||||
catch (...) { }
|
||||
}
|
||||
|
||||
int unknown_value_without_exceptions() noexcept;
|
||||
|
||||
void may_throw() {
|
||||
if(unknown_value_without_exceptions()) {
|
||||
throw "bad luck exception!";
|
||||
}
|
||||
}
|
||||
|
||||
void unknown_code_that_may_throw(int*);
|
||||
void unknown_code_that_will_not_throw(int*) noexcept;
|
||||
|
||||
void calls_throwing_code(int* p) {
|
||||
if(unknown_value_without_exceptions()) unknown_code_that_may_throw(p);
|
||||
}
|
||||
|
||||
void calls_non_throwing(int* p) {
|
||||
if (unknown_value_without_exceptions()) unknown_code_that_will_not_throw(p);
|
||||
}
|
||||
|
||||
void good_new_with_throwing_call() {
|
||||
try {
|
||||
int* p1 = new(std::nothrow) int; // GOOD
|
||||
may_throw();
|
||||
} catch(...) { }
|
||||
|
||||
try {
|
||||
int* p2 = new(std::nothrow) int; // GOOD
|
||||
Foo f(10);
|
||||
} catch(...) { }
|
||||
|
||||
try {
|
||||
int* p3 = new(std::nothrow) int; // GOOD
|
||||
calls_throwing_code(p3);
|
||||
} catch(...) { }
|
||||
}
|
||||
|
||||
void bad_new_with_nonthrowing_call() {
|
||||
try {
|
||||
int* p1 = new(std::nothrow) int; // BAD
|
||||
calls_non_throwing(p1);
|
||||
} catch(...) { }
|
||||
|
||||
try {
|
||||
int* p2 = new(std::nothrow) int; // GOOD: boolean conversion constructor might throw
|
||||
Foo f;
|
||||
if(f) { }
|
||||
} catch(...) { }
|
||||
}
|
||||
|
||||
void bad_new_catch_baseclass_of_bad_alloc() {
|
||||
try {
|
||||
int* p = new(std::nothrow) int; // BAD
|
||||
} catch(const std::exception&) { }
|
||||
}
|
||||
|
||||
void good_new_catch_exception_in_assignment() {
|
||||
int* p;
|
||||
try {
|
||||
p = new int; // GOOD
|
||||
} catch(const std::bad_alloc&) { }
|
||||
}
|
||||
|
||||
void good_new_catch_exception_in_conversion() {
|
||||
try {
|
||||
long* p = (long*) new int; // GOOD
|
||||
} catch(const std::bad_alloc&) { }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
| test.c:15:6:15:16 | ... + ... | this expression needs your attention |
|
||||
| test.c:17:17:17:27 | ... + ... | this expression needs your attention |
|
||||
| test.c:22:10:22:15 | ... > ... | this expression needs your attention |
|
||||
| test.c:26:10:26:15 | ... > ... | this expression needs your attention |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-691/InsufficientControlFlowManagementAfterRefactoringTheCode.ql
|
||||
@@ -12,14 +12,18 @@ void workFunction_0(char *s) {
|
||||
void workFunction_1(char *s) {
|
||||
int intA,intB;
|
||||
|
||||
if(intA + intB) return; // BAD [NOT DETECTED]
|
||||
if(intA + intB) return; // BAD
|
||||
if(intA + intB>4) return; // GOOD
|
||||
if(intA>0 && (intA + intB)) return; // BAD [NOT DETECTED]
|
||||
if(intA>0 && (intA + intB)) return; // BAD
|
||||
while(intA>0)
|
||||
{
|
||||
if(intB - intA<10) break;
|
||||
intA--;
|
||||
}while(intA>0); // BAD [NOT DETECTED]
|
||||
}while(intA>0); // BAD
|
||||
for(intA=100; intA>0; intA--)
|
||||
{
|
||||
if(intB - intA<10) break;
|
||||
}while(intA>0); // BAD
|
||||
while(intA>0)
|
||||
{
|
||||
if(intB - intA<10) break;
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
| test.c:50:3:50:5 | sc3 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:49:9:49:16 | 127 | Extreme value |
|
||||
| test.c:59:3:59:5 | sc6 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:58:9:58:16 | 127 | Extreme value |
|
||||
| test.c:63:3:63:5 | sc8 | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test.c:62:9:62:16 | - ... | Extreme value |
|
||||
| test.c:75:3:75:5 | sc1 | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:74:9:74:16 | 127 | Extreme value |
|
||||
| test.c:124:9:124:9 | x | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:118:17:118:23 | 2147483647 | Extreme value |
|
||||
|
||||
@@ -72,7 +72,7 @@ void test_negatives() {
|
||||
signed char sc1, sc2, sc3, sc4, sc5, sc6, sc7, sc8;
|
||||
|
||||
sc1 = CHAR_MAX;
|
||||
sc1 += 0; // GOOD [FALSE POSITIVE]
|
||||
sc1 += 0; // GOOD
|
||||
sc1 += -1; // GOOD
|
||||
sc2 = CHAR_MIN;
|
||||
sc2 += -1; // BAD [NOT DETECTED]
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
| test.cpp:6:5:6:13 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:10:8:10:24 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:15:9:15:25 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:32:12:32:20 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:39:12:39:20 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:47:5:47:13 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:55:5:55:13 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:62:5:62:13 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:69:5:69:13 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:75:8:75:16 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:101:6:101:14 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:128:6:128:14 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:137:6:137:14 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:146:7:146:15 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:152:7:152:15 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:182:6:182:14 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:208:6:208:14 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:252:10:252:18 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:266:10:266:24 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:276:11:276:19 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
| test.cpp:288:10:288:18 | ... > ... | Unsigned subtraction can never be negative. |
|
||||
|
||||
@@ -12,7 +12,7 @@ void test(unsigned x, unsigned y, bool unknown) {
|
||||
}
|
||||
|
||||
if(total <= limit) {
|
||||
while(limit - total > 0) { // GOOD [FALSE POSITIVE]
|
||||
while(limit - total > 0) { // GOOD
|
||||
total += getAnInt();
|
||||
if(total > limit) break;
|
||||
}
|
||||
@@ -29,14 +29,14 @@ void test(unsigned x, unsigned y, bool unknown) {
|
||||
} else {
|
||||
y = x;
|
||||
}
|
||||
bool b1 = x - y > 0; // GOOD [FALSE POSITIVE]
|
||||
bool b1 = x - y > 0; // GOOD
|
||||
|
||||
x = getAnInt();
|
||||
y = getAnInt();
|
||||
if(y > x) {
|
||||
y = x - 1;
|
||||
}
|
||||
bool b2 = x - y > 0; // GOOD [FALSE POSITIVE]
|
||||
bool b2 = x - y > 0; // GOOD
|
||||
|
||||
int N = getAnInt();
|
||||
y = x;
|
||||
@@ -44,7 +44,7 @@ void test(unsigned x, unsigned y, bool unknown) {
|
||||
if(unknown) { y--; }
|
||||
}
|
||||
|
||||
if(x - y > 0) { } // GOOD [FALSE POSITIVE]
|
||||
if(x - y > 0) { } // GOOD
|
||||
|
||||
x = y;
|
||||
while(cond()) {
|
||||
@@ -52,7 +52,7 @@ void test(unsigned x, unsigned y, bool unknown) {
|
||||
y--;
|
||||
}
|
||||
|
||||
if(x - y > 0) { } // GOOD [FALSE POSITIVE]
|
||||
if(x - y > 0) { } // GOOD
|
||||
|
||||
y = 0;
|
||||
for(int i = 0; i < x; ++i) {
|
||||
@@ -66,7 +66,7 @@ void test(unsigned x, unsigned y, bool unknown) {
|
||||
if(unknown) { x++; }
|
||||
}
|
||||
|
||||
if(x - y > 0) { } // GOOD [FALSE POSITIVE]
|
||||
if(x - y > 0) { } // GOOD
|
||||
|
||||
int n = getAnInt();
|
||||
if (n > x - y) { n = x - y; }
|
||||
@@ -74,4 +74,227 @@ void test(unsigned x, unsigned y, bool unknown) {
|
||||
y += n; // NOTE: `n` is at most `x - y` at this point.
|
||||
if (x - y > 0) {} // GOOD [FALSE POSITIVE]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test2() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = a;
|
||||
|
||||
if (a - b > 0) { // GOOD (as a = b)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test3() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = a - 1;
|
||||
|
||||
if (a - b > 0) { // GOOD (as a >= b)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test4() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = a + 1;
|
||||
|
||||
if (a - b > 0) { // BAD
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test5() {
|
||||
unsigned int b = getAnInt();
|
||||
unsigned int a = b;
|
||||
|
||||
if (a - b > 0) { // GOOD (as a = b)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test6() {
|
||||
unsigned int b = getAnInt();
|
||||
unsigned int a = b + 1;
|
||||
|
||||
if (a - b > 0) { // GOOD (as a >= b)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test7() {
|
||||
unsigned int b = getAnInt();
|
||||
unsigned int a = b - 1;
|
||||
|
||||
if (a - b > 0) { // BAD
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test8() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
|
||||
if (a - b > 0) { // BAD
|
||||
// ...
|
||||
}
|
||||
|
||||
if (a >= b) { // GOOD
|
||||
if (a - b > 0) { // GOOD (as a >= b)
|
||||
// ...
|
||||
}
|
||||
} else {
|
||||
if (a - b > 0) { // BAD
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
if (b >= a) { // GOOD
|
||||
if (a - b > 0) { // BAD
|
||||
// ...
|
||||
}
|
||||
} else {
|
||||
if (a - b > 0) { // GOOD (as a > b)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
while (a >= b) { // GOOD
|
||||
if (a - b > 0) { // GOOD (as a >= b)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
if (a < b) return;
|
||||
|
||||
if (a - b > 0) { // GOOD (as a >= b)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test9() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
|
||||
if (a < b) {
|
||||
b = 0;
|
||||
}
|
||||
|
||||
if (a - b > 0) { // GOOD (as a >= b) [FALSE POSITIVE]
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test10() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
|
||||
if (a < b) {
|
||||
a = b;
|
||||
}
|
||||
|
||||
if (a - b > 0) { // GOOD (as a >= b)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test11() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
|
||||
if (a < b) return;
|
||||
|
||||
b = getAnInt();
|
||||
|
||||
if (a - b > 0) { // BAD
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void test12() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
unsigned int c;
|
||||
|
||||
if ((b <= c) && (c <= a)) {
|
||||
if (a - b > 0) { // GOOD (as b <= a)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
if (b <= c) {
|
||||
if (c <= a) {
|
||||
if (a - b > 0) { // GOOD (as b <= a)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int test13() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
|
||||
if (b != 0) {
|
||||
return 0;
|
||||
} // b = 0
|
||||
|
||||
return (a - b > 0); // GOOD (as b = 0)
|
||||
}
|
||||
|
||||
int test14() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
|
||||
if (!b) {
|
||||
return 0;
|
||||
} // b != 0
|
||||
|
||||
return (a - b > 0); // BAD
|
||||
}
|
||||
|
||||
struct Numbers
|
||||
{
|
||||
unsigned int a, b;
|
||||
};
|
||||
|
||||
int test15(Numbers *n) {
|
||||
|
||||
if (!n) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (n->a - n->b > 0); // BAD
|
||||
}
|
||||
|
||||
int test16() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
|
||||
if (!b) {
|
||||
return 0;
|
||||
} else {
|
||||
return (a - b > 0); // BAD
|
||||
}
|
||||
}
|
||||
|
||||
int test17() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
|
||||
if (b == 0) {
|
||||
return 0;
|
||||
} // b != 0
|
||||
|
||||
return (a - b > 0); // BAD
|
||||
}
|
||||
|
||||
int test18() {
|
||||
unsigned int a = getAnInt();
|
||||
unsigned int b = getAnInt();
|
||||
|
||||
if (b) {
|
||||
return 0;
|
||||
} // b == 0
|
||||
|
||||
return (a - b > 0); // GOOD (as b = 0)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* `System.Console.Read` methods have been added as data flow sources of local user input.
|
||||
2
csharp/change-notes/2021-04-23-model-error-extraction.md
Normal file
2
csharp/change-notes/2021-04-23-model-error-extraction.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* Program model errors are now extracted in standalone extraction.
|
||||
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* Data-flow modelling of `StringBuilder` objects has been improved.
|
||||
14
csharp/change-notes/2021-05-03-implicit-constructor-init.md
Normal file
14
csharp/change-notes/2021-05-03-implicit-constructor-init.md
Normal file
@@ -0,0 +1,14 @@
|
||||
lgtm,codescanning
|
||||
* Implicit base constructor calls are now extracted. For example, in
|
||||
```csharp
|
||||
class Base
|
||||
{
|
||||
public Base() { }
|
||||
}
|
||||
|
||||
class Sub : Base
|
||||
{
|
||||
public Sub() { }
|
||||
}
|
||||
```
|
||||
there is an implicit call to the `Base` constructor from the `Sub` constructor.
|
||||
@@ -7,6 +7,8 @@ namespace Semmle.Extraction.CSharp
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Extractor.SetInvariantCulture();
|
||||
|
||||
return (int)Extractor.Run(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Extractor.SetInvariantCulture();
|
||||
|
||||
var options = Options.Create(args);
|
||||
// options.CIL = true; // To do: Enable this
|
||||
using var output = new ConsoleLogger(options.Verbosity);
|
||||
|
||||
@@ -41,7 +41,38 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
var initializer = syntax?.Initializer;
|
||||
|
||||
if (initializer is null)
|
||||
{
|
||||
if (Symbol.MethodKind is MethodKind.Constructor)
|
||||
{
|
||||
var baseType = Symbol.ContainingType.BaseType;
|
||||
if (baseType is null)
|
||||
{
|
||||
Context.ModelError(Symbol, "Unable to resolve base type in implicit constructor initializer");
|
||||
return;
|
||||
}
|
||||
|
||||
var baseConstructor = baseType.InstanceConstructors.FirstOrDefault(c => c.Arity is 0);
|
||||
|
||||
if (baseConstructor is null)
|
||||
{
|
||||
Context.ModelError(Symbol, "Unable to resolve implicit constructor initializer call");
|
||||
return;
|
||||
}
|
||||
|
||||
var baseConstructorTarget = Create(Context, baseConstructor);
|
||||
var info = new ExpressionInfo(Context,
|
||||
AnnotatedTypeSymbol.CreateNotAnnotated(baseType),
|
||||
Location,
|
||||
Kinds.ExprKind.CONSTRUCTOR_INIT,
|
||||
this,
|
||||
-1,
|
||||
isCompilerGenerated: true,
|
||||
null);
|
||||
|
||||
trapFile.expr_call(new Expression(info), baseConstructorTarget);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ITypeSymbol initializerType;
|
||||
var symbolInfo = Context.GetSymbolInfo(initializer);
|
||||
|
||||
@@ -193,6 +193,16 @@ namespace Semmle.Extraction.CSharp.Entities
|
||||
return Default.CreateGenerated(cx, parent, childIndex, location, parameter.Type.IsReferenceType ? ValueAsString(null) : null);
|
||||
}
|
||||
|
||||
if (parameter.Type.SpecialType == SpecialType.System_Object)
|
||||
{
|
||||
// this can happen in VB.NET
|
||||
cx.ExtractionError($"Extracting default argument value 'object {parameter.Name} = default' instead of 'object {parameter.Name} = {defaultValue}'. The latter is not supported in C#.",
|
||||
null, null, severity: Util.Logging.Severity.Warning);
|
||||
|
||||
// we're generating a default expression:
|
||||
return Default.CreateGenerated(cx, parent, childIndex, location, ValueAsString(null));
|
||||
}
|
||||
|
||||
// const literal:
|
||||
return Literal.CreateGenerated(cx, parent, childIndex, parameter.Type, defaultValue, location);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Semmle.Util.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
|
||||
namespace Semmle.Extraction.CSharp
|
||||
{
|
||||
@@ -52,6 +54,21 @@ namespace Semmle.Extraction.CSharp
|
||||
public void MissingType(string type) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the application culture to the invariant culture.
|
||||
///
|
||||
/// This is required among others to ensure that the invariant culture is used for value formatting during TRAP
|
||||
/// file writing.
|
||||
/// </summary>
|
||||
public static void SetInvariantCulture()
|
||||
{
|
||||
var culture = CultureInfo.InvariantCulture;
|
||||
CultureInfo.DefaultThreadCurrentCulture = culture;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = culture;
|
||||
Thread.CurrentThread.CurrentCulture = culture;
|
||||
Thread.CurrentThread.CurrentUICulture = culture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command-line driver for the extractor.
|
||||
/// </summary>
|
||||
|
||||
@@ -376,6 +376,19 @@ namespace Semmle.Extraction
|
||||
Extractor.Message(msg);
|
||||
}
|
||||
|
||||
private void ExtractionError(InternalError error)
|
||||
{
|
||||
ExtractionError(new Message(error.Message, error.EntityText, CreateLocation(error.Location), error.StackTrace, Severity.Error));
|
||||
}
|
||||
|
||||
private void ReportError(InternalError error)
|
||||
{
|
||||
if (!Extractor.Standalone)
|
||||
throw error;
|
||||
|
||||
ExtractionError(error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal an error in the program model.
|
||||
/// </summary>
|
||||
@@ -383,8 +396,7 @@ namespace Semmle.Extraction
|
||||
/// <param name="msg">The error message.</param>
|
||||
public void ModelError(SyntaxNode node, string msg)
|
||||
{
|
||||
if (!Extractor.Standalone)
|
||||
throw new InternalError(node, msg);
|
||||
ReportError(new InternalError(node, msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -394,8 +406,7 @@ namespace Semmle.Extraction
|
||||
/// <param name="msg">The error message.</param>
|
||||
public void ModelError(ISymbol symbol, string msg)
|
||||
{
|
||||
if (!Extractor.Standalone)
|
||||
throw new InternalError(symbol, msg);
|
||||
ReportError(new InternalError(symbol, msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -404,8 +415,7 @@ namespace Semmle.Extraction
|
||||
/// <param name="msg">The error message.</param>
|
||||
public void ModelError(string msg)
|
||||
{
|
||||
if (!Extractor.Standalone)
|
||||
throw new InternalError(msg);
|
||||
ReportError(new InternalError(msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
63
csharp/ql/src/Diagnostics/DiagnosticExtractionErrors.ql
Normal file
63
csharp/ql/src/Diagnostics/DiagnosticExtractionErrors.ql
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @name Extraction errors
|
||||
* @description List all errors reported by the extractor or the compiler. Extractor errors are
|
||||
* limited to those files where there are no compilation errors.
|
||||
* @kind diagnostic
|
||||
* @id cs/diagnostics/extraction-errors
|
||||
*/
|
||||
|
||||
import csharp
|
||||
import semmle.code.csharp.commons.Diagnostics
|
||||
|
||||
private newtype TDiagnosticError =
|
||||
TCompilerError(CompilerError c) or
|
||||
TExtractorError(ExtractorError e)
|
||||
|
||||
abstract private class DiagnosticError extends TDiagnosticError {
|
||||
abstract string getMessage();
|
||||
|
||||
abstract string toString();
|
||||
|
||||
abstract Location getLocation();
|
||||
|
||||
string getLocationMessage() {
|
||||
if getLocation().getFile().fromSource()
|
||||
then result = " in " + getLocation().getFile()
|
||||
else result = ""
|
||||
}
|
||||
}
|
||||
|
||||
private class DiagnosticCompilerError extends DiagnosticError {
|
||||
CompilerError c;
|
||||
|
||||
DiagnosticCompilerError() { this = TCompilerError(c) }
|
||||
|
||||
override string getMessage() {
|
||||
result = "Compiler error" + this.getLocationMessage() + ": " + c.getMessage()
|
||||
}
|
||||
|
||||
override string toString() { result = c.toString() }
|
||||
|
||||
override Location getLocation() { result = c.getLocation() }
|
||||
}
|
||||
|
||||
private class DiagnosticExtractorError extends DiagnosticError {
|
||||
ExtractorError e;
|
||||
|
||||
DiagnosticExtractorError() {
|
||||
this = TExtractorError(e) and
|
||||
not exists(CompilerError ce | ce.getLocation().getFile() = e.getLocation().getFile())
|
||||
}
|
||||
|
||||
override string getMessage() {
|
||||
result =
|
||||
"Unexpected " + e.getOrigin() + " error" + this.getLocationMessage() + ": " + e.getText()
|
||||
}
|
||||
|
||||
override string toString() { result = e.toString() }
|
||||
|
||||
override Location getLocation() { result = e.getLocation() }
|
||||
}
|
||||
|
||||
from DiagnosticError error
|
||||
select error.getMessage(), 2
|
||||
17
csharp/ql/src/Diagnostics/DiagnosticNoExtractionErrors.ql
Normal file
17
csharp/ql/src/Diagnostics/DiagnosticNoExtractionErrors.ql
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @name Successfully extracted files
|
||||
* @description A list of all files in the source code directory that were extracted
|
||||
* without encountering an extraction or compiler error in the file.
|
||||
* @kind diagnostic
|
||||
* @id cs/diagnostics/successfully-extracted-files
|
||||
*/
|
||||
|
||||
import csharp
|
||||
import semmle.code.csharp.commons.Diagnostics
|
||||
|
||||
from File file
|
||||
where
|
||||
file.fromSource() and
|
||||
not exists(ExtractorError e | e.getLocation().getFile() = file) and
|
||||
not exists(CompilerError e | e.getLocation().getFile() = file)
|
||||
select file, ""
|
||||
@@ -29,7 +29,7 @@ However on older compilers, the actual output is <code>10 10 10 10 10 10 10 10 1
|
||||
|
||||
<references>
|
||||
|
||||
<li>Eric Lippert's Blog: <a href="http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx">Closing over the loop variable considered harmful</a>.</li>
|
||||
<li>Eric Lippert's Blog: <a href="https://docs.microsoft.com/en-gb/archive/blogs/ericlippert/closing-over-the-loop-variable-considered-harmful">Closing over the loop variable considered harmful</a>.</li>
|
||||
|
||||
</references>
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<a href="https://blogs.msdn.microsoft.com/shawnste/2018/04/12/the-japanese-calendars-y2k-moment/">The Japanese Calendar's Y2K Moment</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://docs.microsoft.com/en-us/windows/desktop/Intl/era-handling-for-the-japanese-calendar/">Era Handling for the Japanese Calendar</a>.
|
||||
<a href="https://docs.microsoft.com/en-us/windows/win32/intl/era-handling-for-the-japanese-calendar">Era Handling for the Japanese Calendar</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://simple.wikipedia.org/wiki/List_of_Japanese_eras">List of Japanese Eras (Wikipedia)</a>
|
||||
|
||||
@@ -25,7 +25,7 @@ really be compared using <code>i == j</code>.</p>
|
||||
<references>
|
||||
|
||||
<li>MSDN: <a href="http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx">Object.ReferenceEquals Method</a>.</li>
|
||||
<li>The Way I See It: <a href="http://blogs.msdn.com/b/vijaysk/archive/2008/03/19/object-referenceequals-valuevar-valuevar-will-always-return-false.aspx">Object.ReferenceEquals(ValueVar, ValueVar) will always return false.</a></li>
|
||||
<li>The Way I See It: <a href="https://docs.microsoft.com/en-us/archive/blogs/vijaysk/object-referenceequalsvaluevar-valuevar-will-always-return-false">Object.ReferenceEquals(ValueVar, ValueVar) will always return false.</a></li>
|
||||
|
||||
|
||||
</references>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
Microsoft Visual Studio Unit Testing Framework: documentation at <a href="https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.aspx">MSDN</a>.
|
||||
</li>
|
||||
<li>
|
||||
xUnit.net: official website at <a href="https://xunit.github.io/">https://xunit.github.io/</a>.
|
||||
xUnit.net: official website at <a href="https://xunit.net/">https://xunit.net/</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
|
||||
11
csharp/ql/src/Metrics/Summaries/LinesOfCode.ql
Normal file
11
csharp/ql/src/Metrics/Summaries/LinesOfCode.ql
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @id cs/summary/lines-of-code
|
||||
* @name Total lines of code in the database
|
||||
* @description The total number of lines of code across all files. This is a useful metric of the size of a database. For all files that were seen during the build, this query counts the lines of code, excluding whitespace or comments.
|
||||
* @kind metric
|
||||
* @tags summary
|
||||
*/
|
||||
|
||||
import csharp
|
||||
|
||||
select sum(File f | f.fromSource() | f.getNumberOfLinesOfCode())
|
||||
@@ -42,7 +42,7 @@ To fix this problem, the 'debug' flag should be set to <code>false</code>, or re
|
||||
|
||||
<li>
|
||||
MSDN:
|
||||
<a href="https://blogs.msdn.microsoft.com/prashant_upadhyay/2011/07/14/why-debugfalse-in-asp-net-applications-in-production-environment/">Why debug=false in ASP.NET applications in production environment</a>.
|
||||
<a href="https://web.archive.org/web/20190919105353/https://blogs.msdn.microsoft.com/prashant_upadhyay/2011/07/14/why-debugfalse-in-asp-net-applications-in-production-environment/">Why debug=false in ASP.NET applications in production environment</a>.
|
||||
</li>
|
||||
<li>
|
||||
MSDN:
|
||||
|
||||
@@ -36,7 +36,4 @@ use the error log, but remote users will not see the information.</p>
|
||||
<sample src="ExceptionInformationExposure.cs" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://www.owasp.org/index.php/Information_Leak_(information_disclosure)">Information Leak</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -297,7 +297,8 @@ class Instruction extends Construction::TStageInstruction {
|
||||
/**
|
||||
* Gets the opcode that specifies the operation performed by this instruction.
|
||||
*/
|
||||
final Opcode getOpcode() { result = Construction::getInstructionOpcode(this) }
|
||||
pragma[inline]
|
||||
final Opcode getOpcode() { Construction::getInstructionOpcode(result, this) }
|
||||
|
||||
/**
|
||||
* Gets all direct uses of the result of this instruction. The result can be
|
||||
|
||||
@@ -165,10 +165,10 @@ import Cached
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
Opcode getInstructionOpcode(TRawInstruction instr) {
|
||||
predicate getInstructionOpcode(Opcode opcode, TRawInstruction instr) {
|
||||
exists(TranslatedElement element, InstructionTag tag |
|
||||
instructionOrigin(instr, element, tag) and
|
||||
element.hasInstruction(result, tag, _)
|
||||
element.hasInstruction(opcode, tag, _)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@ private Element getRealParent(Expr expr) { result = expr.getParent() }
|
||||
*/
|
||||
predicate isIRConstant(Expr expr) { exists(expr.getValue()) }
|
||||
|
||||
// Pulled out to work around QL-796
|
||||
// Pulled out for performance. See
|
||||
// https://github.com/github/codeql-coreql-team/issues/1044.
|
||||
private predicate isOrphan(Expr expr) { not exists(getRealParent(expr)) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -297,7 +297,8 @@ class Instruction extends Construction::TStageInstruction {
|
||||
/**
|
||||
* Gets the opcode that specifies the operation performed by this instruction.
|
||||
*/
|
||||
final Opcode getOpcode() { result = Construction::getInstructionOpcode(this) }
|
||||
pragma[inline]
|
||||
final Opcode getOpcode() { Construction::getInstructionOpcode(result, this) }
|
||||
|
||||
/**
|
||||
* Gets all direct uses of the result of this instruction. The result can be
|
||||
|
||||
@@ -338,15 +338,21 @@ private module Cached {
|
||||
instr = unreachedInstruction(_) and result = Language::getVoidType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `opcode` is the opcode that specifies the operation performed by `instr`.
|
||||
*
|
||||
* The parameters are ordered such that they produce a clean join (with no need for reordering)
|
||||
* in the characteristic predicates of the `Instruction` subclasses.
|
||||
*/
|
||||
cached
|
||||
Opcode getInstructionOpcode(Instruction instr) {
|
||||
result = getOldInstruction(instr).getOpcode()
|
||||
predicate getInstructionOpcode(Opcode opcode, Instruction instr) {
|
||||
opcode = getOldInstruction(instr).getOpcode()
|
||||
or
|
||||
instr = phiInstruction(_, _) and result instanceof Opcode::Phi
|
||||
instr = phiInstruction(_, _) and opcode instanceof Opcode::Phi
|
||||
or
|
||||
instr = chiInstruction(_) and result instanceof Opcode::Chi
|
||||
instr = chiInstruction(_) and opcode instanceof Opcode::Chi
|
||||
or
|
||||
instr = unreachedInstruction(_) and result instanceof Opcode::Unreached
|
||||
instr = unreachedInstruction(_) and opcode instanceof Opcode::Unreached
|
||||
}
|
||||
|
||||
cached
|
||||
|
||||
@@ -316,6 +316,15 @@ private module SsaDefReaches {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the reference to `def` at index `i` in basic block `bb` is the
|
||||
* last reference to `v` inside `bb`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
|
||||
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
|
||||
}
|
||||
|
||||
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
|
||||
exists(ssaDefRank(def, v, bb, _, _))
|
||||
}
|
||||
@@ -351,8 +360,7 @@ private module SsaDefReaches {
|
||||
*/
|
||||
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
|
||||
varBlockReaches(def, bb1, bb2) and
|
||||
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and
|
||||
variableRead(bb2, i2, _, _)
|
||||
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,15 +442,22 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
|
||||
bb2 = bb1
|
||||
)
|
||||
or
|
||||
exists(SourceVariable v | ssaDefRank(def, v, bb1, i1, _) = maxSsaRefRank(bb1, v)) and
|
||||
lastSsaRef(def, _, bb1, i1) and
|
||||
defAdjacentRead(def, bb1, bb2, i2)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate adjacentDefRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
v = def.getSourceVariable()
|
||||
}
|
||||
|
||||
private predicate adjacentDefReachesRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
exists(SourceVariable v | v = def.getSourceVariable() |
|
||||
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
|
||||
ssaRef(bb1, i1, v, SsaDef())
|
||||
or
|
||||
variableRead(bb1, i1, v, true)
|
||||
@@ -475,17 +490,19 @@ predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, Ba
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
|
||||
exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) |
|
||||
exists(SourceVariable v |
|
||||
// Next reference to `v` inside `bb` is a write
|
||||
next.definesAt(v, bb, j) and
|
||||
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
|
||||
exists(int rnk, int j |
|
||||
rnk = ssaDefRank(def, v, bb, i, _) and
|
||||
next.definesAt(v, bb, j) and
|
||||
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
|
||||
)
|
||||
or
|
||||
// Can reach a write using one or more steps
|
||||
rnk = maxSsaRefRank(bb, v) and
|
||||
lastSsaRef(def, v, bb, i) and
|
||||
exists(BasicBlock bb2 |
|
||||
varBlockReaches(def, bb, bb2) and
|
||||
next.definesAt(v, bb2, j) and
|
||||
1 = ssaRefRank(bb2, j, v, SsaDef())
|
||||
1 = ssaDefRank(next, v, bb2, _, SsaDef())
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -539,7 +556,8 @@ pragma[nomagic]
|
||||
predicate lastRef(Definition def, BasicBlock bb, int i) {
|
||||
lastRefRedef(def, bb, i, _)
|
||||
or
|
||||
exists(SourceVariable v | ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) |
|
||||
lastSsaRef(def, _, bb, i) and
|
||||
(
|
||||
// Can reach exit directly
|
||||
bb instanceof ExitBasicBlock
|
||||
or
|
||||
|
||||
@@ -49,6 +49,7 @@ module Stages {
|
||||
|
||||
cached
|
||||
module DataFlowStage {
|
||||
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch
|
||||
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
|
||||
private import semmle.code.csharp.dataflow.internal.DataFlowImplCommon
|
||||
private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate
|
||||
@@ -78,6 +79,8 @@ module Stages {
|
||||
or
|
||||
exists(CallContext cc)
|
||||
or
|
||||
exists(any(DataFlowCall c).getEnclosingCallable())
|
||||
or
|
||||
forceCachingInSameStageRev()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ class File extends Container, @file {
|
||||
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
|
||||
|
||||
/** Holds if this file contains source code. */
|
||||
predicate fromSource() { this.getNumberOfLinesOfCode() > 0 }
|
||||
predicate fromSource() { files(this, _, _, "cs", _) }
|
||||
|
||||
/** Holds if this file is a library. */
|
||||
predicate fromLibrary() {
|
||||
|
||||
@@ -36,7 +36,7 @@ class Guard extends Expr {
|
||||
* Holds if basic block `bb` is guarded by this expression having value `v`.
|
||||
*/
|
||||
predicate controlsBasicBlock(BasicBlock bb, AbstractValue v) {
|
||||
Internal::guardControls(this, _, bb, v)
|
||||
Internal::guardControls(this, bb, v)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,6 +50,12 @@ class Guard extends Expr {
|
||||
polarity = v.getValue()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a valid value for this guard. For example, if this guard is a test, then
|
||||
* it can have Boolean values `true` and `false`.
|
||||
*/
|
||||
AbstractValue getAValue() { isGuard(this, result) }
|
||||
}
|
||||
|
||||
/** An abstract value. */
|
||||
@@ -967,13 +973,6 @@ module Internal {
|
||||
e = any(BinaryArithmeticOperation bao | result = bao.getAnOperand())
|
||||
}
|
||||
|
||||
/** Same as `this.getAChildExpr*()`, but avoids `fastTC`. */
|
||||
private Expr getAChildExprStar(Guard g) {
|
||||
result = g
|
||||
or
|
||||
result = getAChildExprStar(g).getAChildExpr()
|
||||
}
|
||||
|
||||
private Expr stripConditionalExpr(Expr e) {
|
||||
e =
|
||||
any(ConditionalExpr ce |
|
||||
@@ -1009,8 +1008,11 @@ module Internal {
|
||||
|
||||
/** Holds if pre-basic-block `bb` only is reached when guard `g` has abstract value `v`. */
|
||||
predicate preControls(Guard g, PreBasicBlocks::PreBasicBlock bb, AbstractValue v) {
|
||||
exists(AbstractValue v0, Guard g0 | preControlsDirect(g0, bb, v0) |
|
||||
preImpliesSteps(g0, v0, g, v)
|
||||
preControlsDirect(g, bb, v)
|
||||
or
|
||||
exists(AbstractValue v0, Guard g0 |
|
||||
preControls(g0, bb, v0) and
|
||||
preImpliesStep(g0, v0, g, v)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1038,6 +1040,23 @@ module Internal {
|
||||
}
|
||||
}
|
||||
|
||||
private predicate canReturnBool(Callable c, Expr ret) {
|
||||
canReturn(c, ret) and
|
||||
c.getReturnType() instanceof BoolType
|
||||
}
|
||||
|
||||
private predicate boolReturnImplies(Expr ret, BooleanValue retVal, Guard g, AbstractValue v) {
|
||||
canReturnBool(_, ret) and
|
||||
isGuard(ret, retVal) and
|
||||
g = ret and
|
||||
v = retVal
|
||||
or
|
||||
exists(Guard g0, AbstractValue v0 |
|
||||
boolReturnImplies(ret, retVal, g0, v0) and
|
||||
preImpliesStep(g0, v0, g, v)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ret` is an expression returned by the callable to which parameter
|
||||
* `p` belongs, and `ret` having Boolean value `retVal` allows the conclusion
|
||||
@@ -1046,14 +1065,14 @@ module Internal {
|
||||
private predicate validReturnInCustomNullCheck(
|
||||
Expr ret, Parameter p, BooleanValue retVal, boolean isNull
|
||||
) {
|
||||
exists(Callable c | canReturn(c, ret) |
|
||||
p.getCallable() = c and
|
||||
c.getReturnType() instanceof BoolType
|
||||
exists(Callable c |
|
||||
canReturnBool(c, ret) and
|
||||
p.getCallable() = c
|
||||
) and
|
||||
exists(PreSsaImplicitParameterDefinition def | p = def.getParameter() |
|
||||
def.nullGuardedReturn(ret, isNull)
|
||||
or
|
||||
exists(NullValue nv | preImpliesSteps(ret, retVal, def.getARead(), nv) |
|
||||
exists(NullValue nv | boolReturnImplies(ret, retVal, def.getARead(), nv) |
|
||||
if nv.isNull() then isNull = true else isNull = false
|
||||
)
|
||||
)
|
||||
@@ -1691,6 +1710,79 @@ module Internal {
|
||||
|
||||
import PreCFG
|
||||
|
||||
private predicate interestingDescendantCandidate(Expr e) {
|
||||
guardControls(e, _, _)
|
||||
or
|
||||
e instanceof AccessOrCallExpr
|
||||
}
|
||||
|
||||
/**
|
||||
* An (interesting) descendant of a guard that controls some basic block.
|
||||
*
|
||||
* This class exists purely for performance reasons: It allows us to big-step
|
||||
* through the child hierarchy in `guardControlsSub()` instead of using
|
||||
* `getAChildExpr()`.
|
||||
*/
|
||||
private class ControlGuardDescendant extends Expr {
|
||||
ControlGuardDescendant() {
|
||||
guardControls(this, _, _)
|
||||
or
|
||||
any(ControlGuardDescendant other).interestingDescendant(this)
|
||||
}
|
||||
|
||||
private predicate descendant(Expr e) {
|
||||
e = this.getAChildExpr()
|
||||
or
|
||||
exists(Expr mid |
|
||||
descendant(mid) and
|
||||
not interestingDescendantCandidate(mid) and
|
||||
e = mid.getAChildExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `e` is an interesting descendant of this descendant. */
|
||||
predicate interestingDescendant(Expr e) {
|
||||
descendant(e) and
|
||||
interestingDescendantCandidate(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `g` controls basic block `bb`, and `sub` is some (interesting)
|
||||
* sub expression of `g`.
|
||||
*
|
||||
* Sub expressions inside nested logical operations that themselve control `bb`
|
||||
* are not included, since these will be sub expressions of their immediately
|
||||
* enclosing logical operation. (This restriction avoids a quadratic blow-up.)
|
||||
*
|
||||
* For example, in
|
||||
*
|
||||
* ```csharp
|
||||
* if (a && (b && c))
|
||||
* BLOCK
|
||||
* ```
|
||||
*
|
||||
* `a` is included as a sub expression of `a && (b && c)` (which controls `BLOCK`),
|
||||
* while `b` and `c` are only included as sub expressions of `b && c` (which also
|
||||
* controls `BLOCK`).
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate guardControlsSub(Guard g, BasicBlock bb, ControlGuardDescendant sub) {
|
||||
guardControls(g, bb, _) and
|
||||
sub = g
|
||||
or
|
||||
exists(ControlGuardDescendant mid |
|
||||
guardControlsSub(g, bb, mid) and
|
||||
mid.interestingDescendant(sub)
|
||||
|
|
||||
not guardControls(sub, bb, _)
|
||||
or
|
||||
not mid instanceof UnaryLogicalOperation and
|
||||
not mid instanceof BinaryLogicalOperation and
|
||||
not mid instanceof BitwiseOperation
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class for calculating structurally equal access/call expressions.
|
||||
*/
|
||||
@@ -1710,13 +1802,13 @@ module Internal {
|
||||
|
||||
/**
|
||||
* Holds if access/call expression `e` (targeting declaration `target`)
|
||||
* is a sub expression of a condition that controls whether basic block
|
||||
* is a sub expression of a guard that controls whether basic block
|
||||
* `bb` is reached.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate candidateAux(AccessOrCallExpr e, Declaration target, BasicBlock bb) {
|
||||
target = e.getTarget() and
|
||||
exists(Guard g | e = getAChildExprStar(g) | guardControls(g, _, bb, _))
|
||||
guardControlsSub(_, bb, e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1727,49 +1819,67 @@ module Internal {
|
||||
|
||||
/**
|
||||
* Holds if basic block `bb` only is reached when guard `g` has abstract value `v`.
|
||||
*
|
||||
* `cb` records all of the possible condition blocks for `g` that a path from the
|
||||
* callable entry point to `bb` may go through.
|
||||
*/
|
||||
cached
|
||||
predicate guardControls(Guard g, ConditionBlock cb, BasicBlock bb, AbstractValue v) {
|
||||
predicate guardControls(Guard g, BasicBlock bb, AbstractValue v) {
|
||||
exists(ControlFlowElement cfe, ConditionalSuccessor cs |
|
||||
v.branch(cfe, cs, g) and cfe.controlsBlock(bb, cs, _)
|
||||
)
|
||||
or
|
||||
exists(AbstractValue v0, Guard g0 |
|
||||
impliesSteps(g0, v0, g, v) and
|
||||
exists(ControlFlowElement cfe, ConditionalSuccessor cs |
|
||||
v0.branch(cfe, cs, g0) and cfe.controlsBlock(bb, cs, cb)
|
||||
)
|
||||
guardControls(g0, bb, v0) and
|
||||
impliesStep(g0, v0, g, v)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate guardControlsSubSame(Guard g, BasicBlock bb, ControlGuardDescendant sub) {
|
||||
guardControlsSub(g, bb, sub) and
|
||||
any(ConditionOnExprComparisonConfig c).same(sub, _)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate nodeIsGuardedBySameSubExpr0(
|
||||
ControlFlow::Node guardedCfn, AccessOrCallExpr guarded, Guard g, ConditionBlock cb,
|
||||
ControlFlow::Node guardedCfn, BasicBlock guardedBB, AccessOrCallExpr guarded, Guard g,
|
||||
AccessOrCallExpr sub, AbstractValue v
|
||||
) {
|
||||
Stages::GuardsStage::forceCachingInSameStage() and
|
||||
guardedCfn = guarded.getAControlFlowNode() and
|
||||
guardControls(g, cb, guardedCfn.getBasicBlock(), v) and
|
||||
exists(ConditionOnExprComparisonConfig c | c.same(sub, guarded))
|
||||
guardedBB = guardedCfn.getBasicBlock() and
|
||||
guardControls(g, guardedBB, v) and
|
||||
guardControlsSubSame(g, guardedBB, sub) and
|
||||
any(ConditionOnExprComparisonConfig c).same(sub, guarded)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate nodeIsGuardedBySameSubExpr(
|
||||
ControlFlow::Node guardedCfn, AccessOrCallExpr guarded, Guard g, ConditionBlock cb,
|
||||
ControlFlow::Node guardedCfn, BasicBlock guardedBB, AccessOrCallExpr guarded, Guard g,
|
||||
AccessOrCallExpr sub, AbstractValue v
|
||||
) {
|
||||
nodeIsGuardedBySameSubExpr0(guardedCfn, guarded, g, cb, sub, v) and
|
||||
sub = getAChildExprStar(g)
|
||||
nodeIsGuardedBySameSubExpr0(guardedCfn, guardedBB, guarded, g, sub, v) and
|
||||
guardControlsSub(g, guardedBB, sub)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate nodeIsGuardedBySameSubExprSsaDef0(
|
||||
ControlFlow::Node cfn, BasicBlock guardedBB, AccessOrCallExpr guarded, Guard g,
|
||||
ControlFlow::Node subCfn, BasicBlock subCfnBB, AccessOrCallExpr sub, AbstractValue v,
|
||||
Ssa::Definition def
|
||||
) {
|
||||
nodeIsGuardedBySameSubExpr(cfn, guardedBB, guarded, g, sub, v) and
|
||||
def = sub.getAnSsaQualifier(subCfn) and
|
||||
subCfnBB = subCfn.getBasicBlock()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate nodeIsGuardedBySameSubExprSsaDef(
|
||||
ControlFlow::Node cfn, AccessOrCallExpr guarded, Guard g, ControlFlow::Node subCfn,
|
||||
ControlFlow::Node guardedCfn, AccessOrCallExpr guarded, Guard g, ControlFlow::Node subCfn,
|
||||
AccessOrCallExpr sub, AbstractValue v, Ssa::Definition def
|
||||
) {
|
||||
exists(ConditionBlock cb |
|
||||
nodeIsGuardedBySameSubExpr(cfn, guarded, g, cb, sub, v) and
|
||||
subCfn.getBasicBlock().dominates(cb) and
|
||||
def = sub.getAnSsaQualifier(subCfn)
|
||||
exists(BasicBlock guardedBB, BasicBlock subCfnBB |
|
||||
nodeIsGuardedBySameSubExprSsaDef0(guardedCfn, guardedBB, guarded, g, subCfn, subCfnBB, sub,
|
||||
v, def) and
|
||||
subCfnBB.getASuccessor*() = guardedBB
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1789,7 +1899,7 @@ module Internal {
|
||||
AccessOrCallExpr guarded, Guard g, AccessOrCallExpr sub, AbstractValue v
|
||||
) {
|
||||
forex(ControlFlow::Node cfn | cfn = guarded.getAControlFlowNode() |
|
||||
nodeIsGuardedBySameSubExpr(cfn, guarded, g, _, sub, v)
|
||||
nodeIsGuardedBySameSubExpr(cfn, _, guarded, g, sub, v)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1814,7 +1924,7 @@ module Internal {
|
||||
predicate isGuardedByNode(
|
||||
ControlFlow::Nodes::ElementNode guarded, Guard g, AccessOrCallExpr sub, AbstractValue v
|
||||
) {
|
||||
nodeIsGuardedBySameSubExpr(guarded, _, g, _, sub, v) and
|
||||
nodeIsGuardedBySameSubExpr(guarded, _, _, g, sub, v) and
|
||||
forall(ControlFlow::Node subCfn, Ssa::Definition def |
|
||||
nodeIsGuardedBySameSubExprSsaDef(guarded, _, g, subCfn, sub, v, def)
|
||||
|
|
||||
@@ -1860,40 +1970,6 @@ module Internal {
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
/**
|
||||
* Holds if the assumption that `g1` has abstract value `v1` implies that
|
||||
* `g2` has abstract value `v2`, using zero or more steps of reasoning. That is,
|
||||
* the evaluation of `g2` to `v2` dominates the evaluation of `g1` to `v1`.
|
||||
*
|
||||
* This predicate does not rely on the control flow graph.
|
||||
*/
|
||||
predicate preImpliesSteps(Guard g1, AbstractValue v1, Guard g2, AbstractValue v2) {
|
||||
g1 = g2 and
|
||||
v1 = v2 and
|
||||
isGuard(g1, v1)
|
||||
or
|
||||
exists(Expr mid, AbstractValue vMid | preImpliesSteps(g1, v1, mid, vMid) |
|
||||
preImpliesStep(mid, vMid, g2, v2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the assumption that `g1` has abstract value `v1` implies that
|
||||
* `g2` has abstract value `v2`, using zero or more steps of reasoning. That is,
|
||||
* the evaluation of `g2` to `v2` dominates the evaluation of `g1` to `v1`.
|
||||
*
|
||||
* This predicate relies on the control flow graph.
|
||||
*/
|
||||
predicate impliesSteps(Guard g1, AbstractValue v1, Guard g2, AbstractValue v2) {
|
||||
g1 = g2 and
|
||||
v1 = v2 and
|
||||
isGuard(g1, v1)
|
||||
or
|
||||
exists(Expr mid, AbstractValue vMid | impliesSteps(g1, v1, mid, vMid) |
|
||||
impliesStep(mid, vMid, g2, v2)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private import Internal
|
||||
|
||||
@@ -244,18 +244,10 @@ module InitializerSplitting {
|
||||
* Holds if `c` is a non-static constructor that performs the initialization
|
||||
* of member `m`.
|
||||
*/
|
||||
predicate constructorInitializes(Constructor c, InitializedInstanceMember m) {
|
||||
predicate constructorInitializes(InstanceConstructor c, InitializedInstanceMember m) {
|
||||
c.isUnboundDeclaration() and
|
||||
not c.isStatic() and
|
||||
c.getDeclaringType().hasMember(m) and
|
||||
(
|
||||
not c.hasInitializer()
|
||||
or
|
||||
// Members belonging to the base class are initialized via calls to the
|
||||
// base constructor
|
||||
c.getInitializer().isBase() and
|
||||
m.getDeclaringType() = c.getDeclaringType()
|
||||
)
|
||||
c.getDeclaringType().getAMember() = m and
|
||||
not c.getInitializer().isThis()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -316,6 +316,15 @@ private module SsaDefReaches {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the reference to `def` at index `i` in basic block `bb` is the
|
||||
* last reference to `v` inside `bb`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
|
||||
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
|
||||
}
|
||||
|
||||
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
|
||||
exists(ssaDefRank(def, v, bb, _, _))
|
||||
}
|
||||
@@ -351,8 +360,7 @@ private module SsaDefReaches {
|
||||
*/
|
||||
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
|
||||
varBlockReaches(def, bb1, bb2) and
|
||||
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and
|
||||
variableRead(bb2, i2, _, _)
|
||||
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,15 +442,22 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
|
||||
bb2 = bb1
|
||||
)
|
||||
or
|
||||
exists(SourceVariable v | ssaDefRank(def, v, bb1, i1, _) = maxSsaRefRank(bb1, v)) and
|
||||
lastSsaRef(def, _, bb1, i1) and
|
||||
defAdjacentRead(def, bb1, bb2, i2)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate adjacentDefRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
v = def.getSourceVariable()
|
||||
}
|
||||
|
||||
private predicate adjacentDefReachesRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
exists(SourceVariable v | v = def.getSourceVariable() |
|
||||
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
|
||||
ssaRef(bb1, i1, v, SsaDef())
|
||||
or
|
||||
variableRead(bb1, i1, v, true)
|
||||
@@ -475,17 +490,19 @@ predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, Ba
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
|
||||
exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) |
|
||||
exists(SourceVariable v |
|
||||
// Next reference to `v` inside `bb` is a write
|
||||
next.definesAt(v, bb, j) and
|
||||
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
|
||||
exists(int rnk, int j |
|
||||
rnk = ssaDefRank(def, v, bb, i, _) and
|
||||
next.definesAt(v, bb, j) and
|
||||
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
|
||||
)
|
||||
or
|
||||
// Can reach a write using one or more steps
|
||||
rnk = maxSsaRefRank(bb, v) and
|
||||
lastSsaRef(def, v, bb, i) and
|
||||
exists(BasicBlock bb2 |
|
||||
varBlockReaches(def, bb, bb2) and
|
||||
next.definesAt(v, bb2, j) and
|
||||
1 = ssaRefRank(bb2, j, v, SsaDef())
|
||||
1 = ssaDefRank(next, v, bb2, _, SsaDef())
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -539,7 +556,8 @@ pragma[nomagic]
|
||||
predicate lastRef(Definition def, BasicBlock bb, int i) {
|
||||
lastRefRedef(def, bb, i, _)
|
||||
or
|
||||
exists(SourceVariable v | ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) |
|
||||
lastSsaRef(def, _, bb, i) and
|
||||
(
|
||||
// Can reach exit directly
|
||||
bb instanceof ExitBasicBlock
|
||||
or
|
||||
|
||||
@@ -807,17 +807,29 @@ class SystemTextStringBuilderFlow extends LibraryTypeDataFlow, SystemTextStringB
|
||||
sinkAp = AccessPath::empty() and
|
||||
preservesValue = false
|
||||
or
|
||||
exists(int i, Type t |
|
||||
name.regexpMatch("Append(Format|Line)?") and
|
||||
t = m.getParameter(i).getType() and
|
||||
source = TCallableFlowSourceArg(i) and
|
||||
name.regexpMatch("Append(Format|Line|Join)?") and
|
||||
preservesValue = true and
|
||||
(
|
||||
exists(int i, Type t |
|
||||
t = m.getParameter(i).getType() and
|
||||
source = TCallableFlowSourceArg(i) and
|
||||
sink = TCallableFlowSinkQualifier() and
|
||||
sinkAp = AccessPath::element()
|
||||
|
|
||||
(
|
||||
t instanceof StringType or
|
||||
t instanceof ObjectType
|
||||
) and
|
||||
sourceAp = AccessPath::empty()
|
||||
or
|
||||
isCollectionType(t) and
|
||||
sourceAp = AccessPath::element()
|
||||
)
|
||||
or
|
||||
source = TCallableFlowSourceQualifier() and
|
||||
sourceAp = AccessPath::empty() and
|
||||
sink = [TCallableFlowSinkQualifier().(TCallableFlowSink), TCallableFlowSinkReturn()] and
|
||||
sinkAp = AccessPath::element() and
|
||||
preservesValue = true
|
||||
|
|
||||
t instanceof StringType or
|
||||
t instanceof ObjectType
|
||||
sink = TCallableFlowSinkReturn() and
|
||||
sinkAp = AccessPath::empty()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -128,12 +128,19 @@ private predicate dereferenceAt(BasicBlock bb, int i, Ssa::Definition def, Deref
|
||||
* has abstract value `vDef`.
|
||||
*/
|
||||
private predicate exprImpliesSsaDef(
|
||||
Expr e, G::AbstractValue vExpr, Ssa::Definition def, G::AbstractValue vDef
|
||||
G::Guard e, G::AbstractValue vExpr, Ssa::Definition def, G::AbstractValue vDef
|
||||
) {
|
||||
exists(G::Guard g | G::Internal::impliesSteps(e, vExpr, g, vDef) |
|
||||
g = def.getARead()
|
||||
vExpr = e.getAValue() and
|
||||
vExpr = vDef and
|
||||
(
|
||||
e = def.getARead()
|
||||
or
|
||||
g = def.(Ssa::ExplicitDefinition).getADefinition().getTargetAccess()
|
||||
e = def.(Ssa::ExplicitDefinition).getADefinition().getTargetAccess()
|
||||
)
|
||||
or
|
||||
exists(Expr e0, G::AbstractValue vExpr0 |
|
||||
exprImpliesSsaDef(e0, vExpr0, def, vDef) and
|
||||
G::Internal::impliesStep(e, vExpr, e0, vExpr0)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ private import dotnet
|
||||
private import DataFlowPublic
|
||||
private import DataFlowPrivate
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
private import semmle.code.csharp.Caching
|
||||
private import semmle.code.csharp.dataflow.FlowSummary
|
||||
private import semmle.code.csharp.dispatch.Dispatch
|
||||
private import semmle.code.csharp.frameworks.system.Collections
|
||||
@@ -69,8 +70,6 @@ private predicate transitiveCapturedCallTarget(ControlFlow::Nodes::ElementNode c
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
private import semmle.code.csharp.Caching
|
||||
|
||||
cached
|
||||
newtype TReturnKind =
|
||||
TNormalReturnKind() { Stages::DataFlowStage::forceCachingInSameStage() } or
|
||||
@@ -247,6 +246,7 @@ abstract class DataFlowCall extends TDataFlowCall {
|
||||
abstract DataFlow::Node getNode();
|
||||
|
||||
/** Gets the enclosing callable of this call. */
|
||||
cached
|
||||
abstract DataFlowCallable getEnclosingCallable();
|
||||
|
||||
/** Gets the underlying expression, if any. */
|
||||
@@ -280,7 +280,10 @@ class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
|
||||
|
||||
override DataFlow::ExprNode getNode() { result.getControlFlowNode() = cfn }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result = cfn.getEnclosingCallable() }
|
||||
override DataFlowCallable getEnclosingCallable() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and
|
||||
result = cfn.getEnclosingCallable()
|
||||
}
|
||||
|
||||
override string toString() { result = cfn.toString() }
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) {
|
||||
* currently excludes read-steps, store-steps, and flow-through.
|
||||
*
|
||||
* The analysis uses non-linear recursion: When computing a flow path in or out
|
||||
* of a call, we use the results of the analysis recursively to resolve lamba
|
||||
* of a call, we use the results of the analysis recursively to resolve lambda
|
||||
* calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
|
||||
*/
|
||||
private module LambdaFlow {
|
||||
|
||||
@@ -21,9 +21,11 @@ private import semmle.code.csharp.frameworks.system.threading.Tasks
|
||||
|
||||
abstract class NodeImpl extends Node {
|
||||
/** Do not call: use `getEnclosingCallable()` instead. */
|
||||
cached
|
||||
abstract DataFlowCallable getEnclosingCallableImpl();
|
||||
|
||||
/** Do not call: use `getType()` instead. */
|
||||
cached
|
||||
abstract DotNet::Type getTypeImpl();
|
||||
|
||||
/** Gets the type of this node used for type pruning. */
|
||||
@@ -39,27 +41,39 @@ abstract class NodeImpl extends Node {
|
||||
}
|
||||
|
||||
/** Do not call: use `getControlFlowNode()` instead. */
|
||||
cached
|
||||
abstract ControlFlow::Node getControlFlowNodeImpl();
|
||||
|
||||
/** Do not call: use `getLocation()` instead. */
|
||||
cached
|
||||
abstract Location getLocationImpl();
|
||||
|
||||
/** Do not call: use `toString()` instead. */
|
||||
cached
|
||||
abstract string toStringImpl();
|
||||
}
|
||||
|
||||
private class ExprNodeImpl extends ExprNode, NodeImpl {
|
||||
override DataFlowCallable getEnclosingCallableImpl() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and
|
||||
result = this.getExpr().getEnclosingCallable()
|
||||
}
|
||||
|
||||
override DotNet::Type getTypeImpl() { result = this.getExpr().getType() }
|
||||
override DotNet::Type getTypeImpl() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and
|
||||
result = this.getExpr().getType()
|
||||
}
|
||||
|
||||
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() { this = TExprNode(result) }
|
||||
override ControlFlow::Nodes::ElementNode getControlFlowNodeImpl() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and this = TExprNode(result)
|
||||
}
|
||||
|
||||
override Location getLocationImpl() { result = this.getExpr().getLocation() }
|
||||
override Location getLocationImpl() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and result = this.getExpr().getLocation()
|
||||
}
|
||||
|
||||
override string toStringImpl() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and
|
||||
result = this.getControlFlowNode().toString()
|
||||
or
|
||||
exists(CIL::Expr e |
|
||||
@@ -967,6 +981,16 @@ private module Cached {
|
||||
or
|
||||
n.asExpr() = any(WithExpr we).getInitializer()
|
||||
}
|
||||
|
||||
cached
|
||||
predicate parameterNode(Node n, DataFlowCallable c, int i) {
|
||||
n.(ParameterNodeImpl).isParameterOf(c, i)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate argumentNode(Node n, DataFlowCall call, int pos) {
|
||||
n.(ArgumentNodeImpl).argumentOf(call, pos)
|
||||
}
|
||||
}
|
||||
|
||||
import Cached
|
||||
@@ -992,8 +1016,6 @@ class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode {
|
||||
}
|
||||
|
||||
abstract class ParameterNodeImpl extends NodeImpl {
|
||||
abstract DotNet::Parameter getParameter();
|
||||
|
||||
abstract predicate isParameterOf(DataFlowCallable c, int i);
|
||||
}
|
||||
|
||||
@@ -1010,11 +1032,9 @@ private module ParameterNodes {
|
||||
/** Gets the SSA definition corresponding to this parameter, if any. */
|
||||
Ssa::ExplicitDefinition getSsaDefinition() {
|
||||
result.getADefinition().(AssignableDefinitions::ImplicitParameterDefinition).getParameter() =
|
||||
this.getParameter()
|
||||
parameter
|
||||
}
|
||||
|
||||
override DotNet::Parameter getParameter() { result = parameter }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, int i) { c.getParameter(i) = parameter }
|
||||
|
||||
override DataFlowCallable getEnclosingCallableImpl() { result = parameter.getCallable() }
|
||||
@@ -1037,8 +1057,6 @@ private module ParameterNodes {
|
||||
/** Gets the callable containing this implicit instance parameter. */
|
||||
Callable getCallable() { result = callable }
|
||||
|
||||
override DotNet::Parameter getParameter() { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, int pos) { callable = c and pos = -1 }
|
||||
|
||||
override DataFlowCallable getEnclosingCallableImpl() { result = callable }
|
||||
@@ -1113,8 +1131,6 @@ private module ParameterNodes {
|
||||
/** Gets the captured variable that this implicit parameter models. */
|
||||
LocalScopeVariable getVariable() { result = def.getVariable() }
|
||||
|
||||
override DotNet::Parameter getParameter() { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, int i) {
|
||||
i = getParameterPosition(def) and
|
||||
c = this.getEnclosingCallable()
|
||||
@@ -1125,13 +1141,15 @@ private module ParameterNodes {
|
||||
import ParameterNodes
|
||||
|
||||
/** A data-flow node that represents a call argument. */
|
||||
abstract class ArgumentNode extends Node {
|
||||
/** Holds if this argument occurs at the given position in the given call. */
|
||||
cached
|
||||
abstract predicate argumentOf(DataFlowCall call, int pos);
|
||||
class ArgumentNode extends Node {
|
||||
ArgumentNode() { argumentNode(this, _, _) }
|
||||
|
||||
/** Gets the call in which this node is an argument. */
|
||||
final DataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
/** Holds if this argument occurs at the given position in the given call. */
|
||||
final predicate argumentOf(DataFlowCall call, int pos) { argumentNode(this, call, pos) }
|
||||
}
|
||||
|
||||
abstract private class ArgumentNodeImpl extends Node {
|
||||
abstract predicate argumentOf(DataFlowCall call, int pos);
|
||||
}
|
||||
|
||||
private module ArgumentNodes {
|
||||
@@ -1149,7 +1167,7 @@ private module ArgumentNodes {
|
||||
}
|
||||
|
||||
/** A data-flow node that represents an explicit call argument. */
|
||||
class ExplicitArgumentNode extends ArgumentNode {
|
||||
class ExplicitArgumentNode extends ArgumentNodeImpl {
|
||||
ExplicitArgumentNode() {
|
||||
this.asExpr() instanceof Argument
|
||||
or
|
||||
@@ -1157,7 +1175,6 @@ private module ArgumentNodes {
|
||||
}
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, int pos) {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and
|
||||
exists(ArgumentConfiguration x, Expr c, Argument arg |
|
||||
arg = this.asExpr() and
|
||||
c = call.getExpr() and
|
||||
@@ -1189,7 +1206,8 @@ private module ArgumentNodes {
|
||||
* } }
|
||||
* ```
|
||||
*/
|
||||
class ImplicitCapturedArgumentNode extends ArgumentNode, NodeImpl, TImplicitCapturedArgumentNode {
|
||||
class ImplicitCapturedArgumentNode extends ArgumentNodeImpl, NodeImpl,
|
||||
TImplicitCapturedArgumentNode {
|
||||
private LocalScopeVariable v;
|
||||
private ControlFlow::Nodes::ElementNode cfn;
|
||||
|
||||
@@ -1231,7 +1249,7 @@ private module ArgumentNodes {
|
||||
* A node that corresponds to the value of an object creation (`new C()`) before
|
||||
* the constructor has run.
|
||||
*/
|
||||
class MallocNode extends ArgumentNode, NodeImpl, TMallocNode {
|
||||
class MallocNode extends ArgumentNodeImpl, NodeImpl, TMallocNode {
|
||||
private ControlFlow::Nodes::ElementNode cfn;
|
||||
|
||||
MallocNode() { this = TMallocNode(cfn) }
|
||||
@@ -1266,7 +1284,7 @@ private module ArgumentNodes {
|
||||
* and that argument is itself a compatible array, for example
|
||||
* `Foo(new[] { "a", "b", "c" })`.
|
||||
*/
|
||||
class ParamsArgumentNode extends ArgumentNode, NodeImpl, TParamsArgumentNode {
|
||||
class ParamsArgumentNode extends ArgumentNodeImpl, NodeImpl, TParamsArgumentNode {
|
||||
private ControlFlow::Node callCfn;
|
||||
|
||||
ParamsArgumentNode() { this = TParamsArgumentNode(callCfn) }
|
||||
@@ -1291,7 +1309,7 @@ private module ArgumentNodes {
|
||||
override string toStringImpl() { result = "[implicit array creation] " + callCfn }
|
||||
}
|
||||
|
||||
private class SummaryArgumentNode extends SummaryNode, ArgumentNode {
|
||||
private class SummaryArgumentNode extends SummaryNode, ArgumentNodeImpl {
|
||||
private DataFlowCall c;
|
||||
private int i;
|
||||
|
||||
@@ -1324,10 +1342,7 @@ private module ReturnNodes {
|
||||
)
|
||||
}
|
||||
|
||||
override NormalReturnKind getKind() {
|
||||
any(DotNet::Callable c).canReturn(this.getExpr()) and
|
||||
exists(result)
|
||||
}
|
||||
override NormalReturnKind getKind() { exists(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1744,7 +1759,10 @@ class DataFlowType extends Gvn::GvnType {
|
||||
}
|
||||
|
||||
/** Gets the type of `n` used for type pruning. */
|
||||
DataFlowType getNodeType(NodeImpl n) { result = n.getDataFlowType() }
|
||||
pragma[inline]
|
||||
Gvn::GvnType getNodeType(NodeImpl n) {
|
||||
pragma[only_bind_into](result) = pragma[only_bind_out](n).getDataFlowType()
|
||||
}
|
||||
|
||||
/** Gets a string representation of a `DataFlowType`. */
|
||||
string ppReprType(DataFlowType t) { result = t.toString() }
|
||||
@@ -1819,7 +1837,8 @@ private module PostUpdateNodes {
|
||||
* Such a node acts as both a post-update node for the `MallocNode`, as well as
|
||||
* a pre-update node for the `ObjectCreationNode`.
|
||||
*/
|
||||
class ObjectInitializerNode extends PostUpdateNode, NodeImpl, ArgumentNode, TObjectInitializerNode {
|
||||
class ObjectInitializerNode extends PostUpdateNode, NodeImpl, ArgumentNodeImpl,
|
||||
TObjectInitializerNode {
|
||||
private ObjectCreation oc;
|
||||
private ControlFlow::Nodes::ElementNode cfn;
|
||||
|
||||
@@ -1980,7 +1999,7 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
||||
kind = TMkUnit()
|
||||
}
|
||||
|
||||
/** Extra data-flow steps needed for lamba flow analysis. */
|
||||
/** Extra data-flow steps needed for lambda flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) {
|
||||
exists(Ssa::Definition def |
|
||||
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo) and
|
||||
|
||||
@@ -3,7 +3,6 @@ private import cil
|
||||
private import dotnet
|
||||
private import DataFlowDispatch
|
||||
private import DataFlowPrivate
|
||||
private import semmle.code.csharp.Caching
|
||||
private import semmle.code.csharp.controlflow.Guards
|
||||
private import semmle.code.csharp.Unification
|
||||
|
||||
@@ -38,38 +37,21 @@ class Node extends TNode {
|
||||
}
|
||||
|
||||
/** Gets the type of this node. */
|
||||
cached
|
||||
final DotNet::Type getType() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and result = this.(NodeImpl).getTypeImpl()
|
||||
}
|
||||
final DotNet::Type getType() { result = this.(NodeImpl).getTypeImpl() }
|
||||
|
||||
/** Gets the enclosing callable of this node. */
|
||||
cached
|
||||
final DataFlowCallable getEnclosingCallable() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and
|
||||
result = this.(NodeImpl).getEnclosingCallableImpl()
|
||||
}
|
||||
|
||||
/** Gets the control flow node corresponding to this node, if any. */
|
||||
cached
|
||||
final ControlFlow::Node getControlFlowNode() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and
|
||||
result = this.(NodeImpl).getControlFlowNodeImpl()
|
||||
}
|
||||
final ControlFlow::Node getControlFlowNode() { result = this.(NodeImpl).getControlFlowNodeImpl() }
|
||||
|
||||
/** Gets a textual representation of this node. */
|
||||
cached
|
||||
final string toString() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and
|
||||
result = this.(NodeImpl).toStringImpl()
|
||||
}
|
||||
final string toString() { result = this.(NodeImpl).toStringImpl() }
|
||||
|
||||
/** Gets the location of this node. */
|
||||
cached
|
||||
final Location getLocation() {
|
||||
Stages::DataFlowStage::forceCachingInSameStage() and
|
||||
result = this.(NodeImpl).getLocationImpl()
|
||||
}
|
||||
final Location getLocation() { result = this.(NodeImpl).getLocationImpl() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
@@ -81,7 +63,7 @@ class Node extends TNode {
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,18 +99,18 @@ class ExprNode extends Node {
|
||||
* flow graph.
|
||||
*/
|
||||
class ParameterNode extends Node {
|
||||
private ParameterNodeImpl p;
|
||||
|
||||
ParameterNode() { this = p }
|
||||
ParameterNode() { parameterNode(this, _, _) }
|
||||
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
DotNet::Parameter getParameter() { result = p.getParameter() }
|
||||
DotNet::Parameter getParameter() {
|
||||
exists(DataFlowCallable c, int i | this.isParameterOf(c, i) and result = c.getParameter(i))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this node is the parameter of callable `c` at the specified
|
||||
* (zero-based) position.
|
||||
*/
|
||||
predicate isParameterOf(DataFlowCallable c, int i) { p.isParameterOf(c, i) }
|
||||
predicate isParameterOf(DataFlowCallable c, int i) { parameterNode(this, c, i) }
|
||||
}
|
||||
|
||||
/** A definition, viewed as a node in a data flow graph. */
|
||||
@@ -166,6 +148,7 @@ predicate localFlowStep = localFlowStepImpl/2;
|
||||
* Holds if data flows from `source` to `sink` in zero or more local
|
||||
* (intra-procedural) steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user