mirror of
https://github.com/github/codeql.git
synced 2025-12-22 11:46:32 +01:00
Merge remote-tracking branch 'upstream/master' into ir-dataflow-toString
Solved conflicts in `*.expected` by re-running the tests.
This commit is contained in:
@@ -21,6 +21,7 @@ from Variable v
|
||||
where
|
||||
v.isStatic() and
|
||||
v.hasDefinition() and
|
||||
not v.isConstexpr() and
|
||||
not exists(VariableAccess a | a.getTarget() = v) and
|
||||
not v instanceof MemberVariable and
|
||||
not declarationHasSideEffects(v) and
|
||||
|
||||
@@ -13,7 +13,7 @@ import semmle.code.cpp.pointsto.PointsTo
|
||||
import Negativity
|
||||
|
||||
predicate closeCall(FunctionCall fc, Variable v) {
|
||||
fc.getTarget().hasGlobalName("close") and v.getAnAccess() = fc.getArgument(0)
|
||||
fc.getTarget().hasGlobalOrStdName("close") and v.getAnAccess() = fc.getArgument(0)
|
||||
or
|
||||
exists(FunctionCall midcall, Function mid, int arg |
|
||||
fc.getArgument(arg) = v.getAnAccess() and
|
||||
|
||||
@@ -13,7 +13,7 @@ import semmle.code.cpp.pointsto.PointsTo
|
||||
|
||||
predicate closed(Expr e) {
|
||||
exists(FunctionCall fc |
|
||||
fc.getTarget().hasGlobalName("close") and
|
||||
fc.getTarget().hasGlobalOrStdName("close") and
|
||||
fc.getArgument(0) = e
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ predicate allocCallOrIndirect(Expr e) {
|
||||
* can cause memory leaks.
|
||||
*/
|
||||
predicate verifiedRealloc(FunctionCall reallocCall, Variable v, ControlFlowNode verified) {
|
||||
reallocCall.getTarget().hasGlobalName("realloc") and
|
||||
reallocCall.getTarget().hasGlobalOrStdName("realloc") and
|
||||
reallocCall.getArgument(0) = v.getAnAccess() and
|
||||
(
|
||||
exists(Variable newV, ControlFlowNode node |
|
||||
@@ -79,7 +79,7 @@ predicate verifiedRealloc(FunctionCall reallocCall, Variable v, ControlFlowNode
|
||||
predicate freeCallOrIndirect(ControlFlowNode n, Variable v) {
|
||||
// direct free call
|
||||
freeCall(n, v.getAnAccess()) and
|
||||
not n.(FunctionCall).getTarget().hasGlobalName("realloc")
|
||||
not n.(FunctionCall).getTarget().hasGlobalOrStdName("realloc")
|
||||
or
|
||||
// verified realloc call
|
||||
verifiedRealloc(_, v, n)
|
||||
|
||||
@@ -13,10 +13,7 @@
|
||||
import cpp
|
||||
|
||||
class MallocCall extends FunctionCall {
|
||||
MallocCall() {
|
||||
this.getTarget().hasGlobalName("malloc") or
|
||||
this.getTarget().hasQualifiedName("std", "malloc")
|
||||
}
|
||||
MallocCall() { this.getTarget().hasGlobalOrStdName("malloc") }
|
||||
|
||||
Expr getAllocatedSize() {
|
||||
if this.getArgument(0) instanceof VariableAccess
|
||||
@@ -36,12 +33,12 @@ predicate spaceProblem(FunctionCall append, string msg) {
|
||||
malloc.getAllocatedSize() = add and
|
||||
buffer.getAnAccess() = strlen.getStringExpr() and
|
||||
(
|
||||
insert.getTarget().hasGlobalName("strcpy") or
|
||||
insert.getTarget().hasGlobalName("strncpy")
|
||||
insert.getTarget().hasGlobalOrStdName("strcpy") or
|
||||
insert.getTarget().hasGlobalOrStdName("strncpy")
|
||||
) and
|
||||
(
|
||||
append.getTarget().hasGlobalName("strcat") or
|
||||
append.getTarget().hasGlobalName("strncat")
|
||||
append.getTarget().hasGlobalOrStdName("strcat") or
|
||||
append.getTarget().hasGlobalOrStdName("strncat")
|
||||
) and
|
||||
malloc.getASuccessor+() = insert and
|
||||
insert.getArgument(1) = buffer.getAnAccess() and
|
||||
|
||||
@@ -25,7 +25,7 @@ import semmle.code.cpp.security.TaintTracking
|
||||
predicate sourceSized(FunctionCall fc, Expr src) {
|
||||
exists(string name |
|
||||
(name = "strncpy" or name = "strncat" or name = "memcpy" or name = "memmove") and
|
||||
fc.getTarget().hasGlobalName(name)
|
||||
fc.getTarget().hasGlobalOrStdName(name)
|
||||
) and
|
||||
exists(Expr dest, Expr size, Variable v |
|
||||
fc.getArgument(0) = dest and
|
||||
|
||||
@@ -60,19 +60,19 @@ predicate overflowOffsetInLoop(BufferAccess bufaccess, string msg) {
|
||||
predicate bufferAndSizeFunction(Function f, int buf, int size) {
|
||||
f.hasGlobalName("read") and buf = 1 and size = 2
|
||||
or
|
||||
f.hasGlobalName("fgets") and buf = 0 and size = 1
|
||||
f.hasGlobalOrStdName("fgets") and buf = 0 and size = 1
|
||||
or
|
||||
f.hasGlobalName("strncpy") and buf = 0 and size = 2
|
||||
f.hasGlobalOrStdName("strncpy") and buf = 0 and size = 2
|
||||
or
|
||||
f.hasGlobalName("strncat") and buf = 0 and size = 2
|
||||
f.hasGlobalOrStdName("strncat") and buf = 0 and size = 2
|
||||
or
|
||||
f.hasGlobalName("memcpy") and buf = 0 and size = 2
|
||||
f.hasGlobalOrStdName("memcpy") and buf = 0 and size = 2
|
||||
or
|
||||
f.hasGlobalName("memmove") and buf = 0 and size = 2
|
||||
f.hasGlobalOrStdName("memmove") and buf = 0 and size = 2
|
||||
or
|
||||
f.hasGlobalName("snprintf") and buf = 0 and size = 1
|
||||
f.hasGlobalOrStdName("snprintf") and buf = 0 and size = 1
|
||||
or
|
||||
f.hasGlobalName("vsnprintf") and buf = 0 and size = 1
|
||||
f.hasGlobalOrStdName("vsnprintf") and buf = 0 and size = 1
|
||||
}
|
||||
|
||||
class CallWithBufferSize extends FunctionCall {
|
||||
|
||||
@@ -17,12 +17,12 @@ import cpp
|
||||
class Allocation extends FunctionCall {
|
||||
Allocation() {
|
||||
exists(string name |
|
||||
this.getTarget().hasGlobalName(name) and
|
||||
this.getTarget().hasGlobalOrStdName(name) and
|
||||
(name = "malloc" or name = "calloc" or name = "realloc")
|
||||
)
|
||||
}
|
||||
|
||||
private string getName() { this.getTarget().hasGlobalName(result) }
|
||||
private string getName() { this.getTarget().hasGlobalOrStdName(result) }
|
||||
|
||||
int getSize() {
|
||||
this.getName() = "malloc" and
|
||||
|
||||
@@ -17,12 +17,12 @@ import cpp
|
||||
class Allocation extends FunctionCall {
|
||||
Allocation() {
|
||||
exists(string name |
|
||||
this.getTarget().hasGlobalName(name) and
|
||||
this.getTarget().hasGlobalOrStdName(name) and
|
||||
(name = "malloc" or name = "calloc" or name = "realloc")
|
||||
)
|
||||
}
|
||||
|
||||
private string getName() { this.getTarget().hasGlobalName(result) }
|
||||
private string getName() { this.getTarget().hasGlobalOrStdName(result) }
|
||||
|
||||
int getSize() {
|
||||
this.getName() = "malloc" and
|
||||
|
||||
@@ -16,7 +16,7 @@ import semmle.code.cpp.controlflow.LocalScopeVariableReachability
|
||||
predicate isFreeExpr(Expr e, LocalScopeVariable v) {
|
||||
exists(VariableAccess va | va.getTarget() = v |
|
||||
exists(FunctionCall fc | fc = e |
|
||||
fc.getTarget().hasGlobalName("free") and
|
||||
fc.getTarget().hasGlobalOrStdName("free") and
|
||||
va = fc.getArgument(0)
|
||||
)
|
||||
or
|
||||
|
||||
@@ -59,7 +59,7 @@ class Options extends string {
|
||||
predicate exits(Function f) {
|
||||
f.getAnAttribute().hasName("noreturn")
|
||||
or
|
||||
exists(string name | f.hasGlobalName(name) |
|
||||
exists(string name | f.hasGlobalOrStdName(name) |
|
||||
name = "exit" or
|
||||
name = "_exit" or
|
||||
name = "abort" or
|
||||
@@ -91,7 +91,7 @@ class Options extends string {
|
||||
* By default holds only for `fgets`.
|
||||
*/
|
||||
predicate alwaysCheckReturnValue(Function f) {
|
||||
f.hasGlobalName("fgets") or
|
||||
f.hasGlobalOrStdName("fgets") or
|
||||
CustomOptions::alwaysCheckReturnValue(f) // old Options.qll
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ where
|
||||
not bf.getType().hasName("BOOL") and
|
||||
// If this is true, then there cannot be unsigned sign extension or overflow.
|
||||
not bf.getDeclaredNumBits() = bf.getType().getSize() * 8 and
|
||||
not bf.isAnonymous()
|
||||
not bf.isAnonymous() and
|
||||
not bf.isFromUninstantiatedTemplate(_)
|
||||
select bf,
|
||||
"Bit field " + bf.getName() + " of type " + bf.getUnderlyingType().getName() +
|
||||
" should have explicitly unsigned integral, explicitly signed integral, or enumeration type."
|
||||
|
||||
@@ -2,36 +2,39 @@
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Checking for overflow of integer addition needs to be done with
|
||||
care, because automatic type promotion can prevent the check
|
||||
from working correctly.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Use an explicit cast to make sure that the result of the addition is
|
||||
not implicitly converted to a larger type.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="BadAdditionOverflowCheckExample1.cpp" />
|
||||
<p>
|
||||
On a typical architecture where <tt>short</tt> is 16 bits
|
||||
and <tt>int</tt> is 32 bits, the operands of the addition are
|
||||
automatically promoted to <tt>int</tt>, so it cannot overflow
|
||||
and the result of the comparison is always false.
|
||||
</p>
|
||||
<p>
|
||||
The code below implements the check correctly, by using an
|
||||
explicit cast to make sure that the result of the addition
|
||||
is <tt>unsigned short</tt>.
|
||||
</p>
|
||||
<sample src="BadAdditionOverflowCheckExample2.cpp" />
|
||||
</example>
|
||||
<references>
|
||||
<li><a href="http://c-faq.com/expr/preservingrules.html">Preserving Rules</a></li>
|
||||
<li><a href="https://www.securecoding.cert.org/confluence/plugins/servlet/mobile#content/view/20086942">Understand integer conversion rules</a></li>
|
||||
</references>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Checking for overflow of integer addition needs to be done with
|
||||
care, because automatic type promotion can prevent the check
|
||||
from working as intended, with the same value (<code>true</code>
|
||||
or <code>false</code>) always being returned.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Use an explicit cast to make sure that the result of the addition is
|
||||
not implicitly converted to a larger type.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="BadAdditionOverflowCheckExample1.cpp" />
|
||||
<p>
|
||||
On a typical architecture where <code>short</code> is 16 bits
|
||||
and <code>int</code> is 32 bits, the operands of the addition are
|
||||
automatically promoted to <code>int</code>, so it cannot overflow
|
||||
and the result of the comparison is always false.
|
||||
</p>
|
||||
<p>
|
||||
The code below implements the check correctly, by using an
|
||||
explicit cast to make sure that the result of the addition
|
||||
is <code>unsigned short</code> (which may overflow, in which case
|
||||
the comparison would evaluate to <code>true</code>).
|
||||
</p>
|
||||
<sample src="BadAdditionOverflowCheckExample2.cpp" />
|
||||
</example>
|
||||
<references>
|
||||
<li><a href="http://c-faq.com/expr/preservingrules.html">Preserving Rules</a></li>
|
||||
<li><a href="https://www.securecoding.cert.org/confluence/plugins/servlet/mobile#content/view/20086942">Understand integer conversion rules</a></li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
bool checkOverflow(unsigned short x, unsigned short y) {
|
||||
return (x + y < x); // BAD: x and y are automatically promoted to int.
|
||||
// BAD: comparison is always false due to type promotion
|
||||
return (x + y < x);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,16 @@ import cpp
|
||||
|
||||
from RelationalOperation e, BinaryBitwiseOperation lhs
|
||||
where
|
||||
lhs = e.getGreaterOperand() and
|
||||
lhs.getActualType().(IntegralType).isSigned() and
|
||||
forall(int op | op = lhs.(BitwiseAndExpr).getAnOperand().getValue().toInt() | op < 0) and
|
||||
// `lhs > 0` (or `0 < lhs`)
|
||||
// (note that `lhs < 0`, `lhs >= 0` or `lhs <= 0` all imply that the signedness of
|
||||
// `lhs` is understood, so should not be flagged).
|
||||
(e instanceof GTExpr or e instanceof LTExpr) and
|
||||
e.getGreaterOperand() = lhs and
|
||||
e.getLesserOperand().getValue() = "0" and
|
||||
// lhs is signed
|
||||
lhs.getActualType().(IntegralType).isSigned() and
|
||||
// if `lhs` has the form `x & c`, with constant `c`, `c` is negative
|
||||
forall(int op | op = lhs.(BitwiseAndExpr).getAnOperand().getValue().toInt() | op < 0) and
|
||||
// exception for cases involving macros
|
||||
not e.isAffectedByMacro()
|
||||
select e, "Potential unsafe sign check of a bitwise operation."
|
||||
|
||||
@@ -14,5 +14,8 @@
|
||||
import cpp
|
||||
|
||||
from ComparisonOperation co, ComparisonOperation chco
|
||||
where co.getAChild() = chco and not chco.isParenthesised()
|
||||
select co, "Check the comparison operator precedence."
|
||||
where
|
||||
co.getAChild() = chco and
|
||||
not chco.isParenthesised() and
|
||||
not co.isFromUninstantiatedTemplate(_)
|
||||
select co, "Comparison as an operand to another comparison."
|
||||
|
||||
@@ -13,17 +13,13 @@
|
||||
|
||||
import cpp
|
||||
import PointlessSelfComparison
|
||||
import semmle.code.cpp.commons.Exclusions
|
||||
|
||||
from ComparisonOperation cmp
|
||||
where
|
||||
pointlessSelfComparison(cmp) and
|
||||
not nanTest(cmp) and
|
||||
not overflowTest(cmp) and
|
||||
not exists(MacroInvocation mi |
|
||||
// cmp is in mi
|
||||
mi.getAnExpandedElement() = cmp and
|
||||
// and cmp was apparently not passed in as a macro parameter
|
||||
cmp.getLocation().getStartLine() = mi.getLocation().getStartLine() and
|
||||
cmp.getLocation().getStartColumn() = mi.getLocation().getStartColumn()
|
||||
)
|
||||
not cmp.isFromTemplateInstantiation(_) and
|
||||
not isFromMacroDefinition(cmp)
|
||||
select cmp, "Self comparison."
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
bool foo(int n1, unsigned short delta) {
|
||||
return n1 + delta < n1; // BAD
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
bool bar(unsigned short n1, unsigned short delta) {
|
||||
// NB: Comparison is always false
|
||||
return n1 + delta < n1; // GOOD (but misleading)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#include <limits.h>
|
||||
bool foo(int n1, unsigned short delta) {
|
||||
return n1 > INT_MAX - delta; // GOOD
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
bool bar(unsigned short n1, unsigned short delta) {
|
||||
return (unsigned short)(n1 + delta) < n1; // GOOD
|
||||
}
|
||||
115
cpp/ql/src/Likely Bugs/Arithmetic/SignedOverflowCheck.qhelp
Normal file
115
cpp/ql/src/Likely Bugs/Arithmetic/SignedOverflowCheck.qhelp
Normal file
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
When checking for integer overflow, you may often write tests like
|
||||
<code>a + b < a</code>. This works fine if <code>a</code> or
|
||||
<code>b</code> are unsigned integers, since any overflow in the addition
|
||||
will cause the value to simply "wrap around." However, using
|
||||
<i>signed</i> integers is problematic because signed overflow has undefined
|
||||
behavior according to the C and C++ standards. If the addition overflows
|
||||
and has an undefined result, the comparison will likewise be undefined;
|
||||
it may produce an unintended result, or may be deleted entirely by an
|
||||
optimizing compiler.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Solutions to this problem can be thought of as falling into one of two
|
||||
categories: (1) rewrite the signed expression so that overflow cannot occur
|
||||
but the signedness remains, or (2) rewrite (or cast) the signed expression
|
||||
into unsigned form.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Below we list examples of expressions where signed overflow may
|
||||
occur, along with proposed solutions. The list should not be
|
||||
considered exhaustive.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Given <code>unsigned short i, delta</code> and <code>i + delta < i</code>,
|
||||
it is possible to rewrite it as <code>(unsigned short)(i + delta) < i</code>.
|
||||
Note that <code>i + delta</code>does not actually overflow, due to <code>int</code> promotion
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Given <code>unsigned short i, delta</code> and <code>i + delta < i</code>,
|
||||
it is also possible to rewrite it as <code>USHORT_MAX - delta</code>. It must be true
|
||||
that <code>delta > 0</code> and the <code>limits.h</code> or <code>climits</code>
|
||||
header has been included.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Given <code>int i, delta</code> and <code>i + delta < i</code>,
|
||||
it is possible to rewrite it as <code>INT_MAX - delta</code>. It must be true
|
||||
that <code>delta > 0</code> and the <code>limits.h</code> or <code>climits</code>
|
||||
header has been included.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Given <code>int i, delta</code> and <code>i + delta < i</code>,
|
||||
it is also possible to rewrite it as <code>(unsigned)i + delta < i</code>.
|
||||
Note that program semantics are affected by this change.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Given <code>int i, delta</code> and <code>i + delta < i</code>,
|
||||
it is also possible to rewrite it as <code>unsigned int i, delta</code> and
|
||||
<code>i + delta < i</code>. Note that program semantics are
|
||||
affected by this change.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the following example, even though <code>delta</code> has been declared
|
||||
<code>unsigned short</code>, C/C++ type promotion rules require that its
|
||||
type is promoted to the larger type used in the addition and comparison,
|
||||
namely a <code>signed int</code>. Addition is performed on
|
||||
signed integers, and may have undefined behavior if an overflow occurs.
|
||||
As a result, the entire (comparison) expression may also have an undefined
|
||||
result.
|
||||
</p>
|
||||
<sample src="SignedOverflowCheck-bad1.cpp" />
|
||||
<p>
|
||||
The following example builds upon the previous one. Instead of
|
||||
performing an addition (which could overflow), we have re-framed the
|
||||
solution so that a subtraction is used instead. Since <code>delta</code>
|
||||
is promoted to a <code>signed int</code> and <code>INT_MAX</code> denotes
|
||||
the largest possible positive value for an <code>signed int</code>,
|
||||
the expression <code>INT_MAX - delta</code> can never be less than zero
|
||||
or more than <code>INT_MAX</code>. Hence, any overflow and underflow
|
||||
are avoided.
|
||||
</p>
|
||||
<sample src="SignedOverflowCheck-good1.cpp" />
|
||||
<p>
|
||||
In the following example, even though both <code>n</code> and <code>delta</code>
|
||||
have been declared <code>unsigned short</code>, both are promoted to
|
||||
<code>signed int</code> prior to addition. Because we started out with the
|
||||
narrower <code>short</code> type, the addition is guaranteed not to overflow
|
||||
and is therefore defined. But the fact that <code>n1 + delta</code> never
|
||||
overflows means that the condition <code>n1 + delta < n1</code> will never
|
||||
hold true, which likely is not what the programmer intended. (see also the
|
||||
<code>cpp/bad-addition-overflow-check</code> query).
|
||||
</p>
|
||||
<sample src="SignedOverflowCheck-bad2.cpp" />
|
||||
<p>
|
||||
The next example provides a solution to the previous one. Even though
|
||||
<code>i + delta</code> does not overflow, casting it to an
|
||||
<code>unsigned short</code> truncates the addition modulo 2^16,
|
||||
so that <code>unsigned short</code> "wrap around" may now be observed.
|
||||
Furthermore, since the left-hand side is now of type <code>unsigned short</code>,
|
||||
the right-hand side does not need to be promoted to a <code>signed int</code>.
|
||||
</p>
|
||||
|
||||
<sample src="SignedOverflowCheck-good2.cpp" />
|
||||
</example>
|
||||
<references>
|
||||
<li><a href="http://c-faq.com/expr/preservingrules.html">comp.lang.c FAQ list · Question 3.19 (Preserving rules)</a></li>
|
||||
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/INT31-C.+Ensure+that+integer+conversions+do+not+result+in+lost+or+misinterpreted+data">INT31-C. Ensure that integer conversions do not result in lost or misinterpreted data</a></li>
|
||||
<li>W. Dietz, P. Li, J. Regehr, V. Adve. <a href="https://www.cs.utah.edu/~regehr/papers/overflow12.pdf">Understanding Integer Overflow in C/C++</a></li>
|
||||
</references>
|
||||
</qhelp>
|
||||
31
cpp/ql/src/Likely Bugs/Arithmetic/SignedOverflowCheck.ql
Normal file
31
cpp/ql/src/Likely Bugs/Arithmetic/SignedOverflowCheck.ql
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @name Signed overflow check
|
||||
* @description Testing for overflow by adding a value to a variable
|
||||
* to see if it "wraps around" works only for
|
||||
* unsigned integer values.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id cpp/signed-overflow-check
|
||||
* @tags correctness
|
||||
* security
|
||||
*/
|
||||
|
||||
import cpp
|
||||
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
private import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
|
||||
|
||||
from RelationalOperation ro, AddExpr add, Expr expr1, Expr expr2
|
||||
where
|
||||
ro.getAnOperand() = add and
|
||||
add.getAnOperand() = expr1 and
|
||||
ro.getAnOperand() = expr2 and
|
||||
globalValueNumber(expr1) = globalValueNumber(expr2) and
|
||||
add.getUnspecifiedType().(IntegralType).isSigned() and
|
||||
not exists(MacroInvocation mi | mi.getAnAffectedElement() = add) and
|
||||
exprMightOverflowPositively(add) and
|
||||
exists(Compilation c | c.getAFileCompiled() = ro.getFile() |
|
||||
not c.getAnArgument() = "-fwrapv" and
|
||||
not c.getAnArgument() = "-fno-strict-overflow"
|
||||
)
|
||||
select ro, "Testing for signed overflow may produce undefined results."
|
||||
@@ -157,7 +157,8 @@ where
|
||||
formatOtherArgType(ffc, n, expected, arg, actual) and
|
||||
not actual.getUnspecifiedType().(IntegralType).getSize() = sizeof_IntType()
|
||||
) and
|
||||
not arg.isAffectedByMacro()
|
||||
not arg.isAffectedByMacro() and
|
||||
not arg.isFromUninstantiatedTemplate(_)
|
||||
select arg,
|
||||
"This argument should be of type '" + expected.getName() + "' but is of type '" +
|
||||
actual.getUnspecifiedType().getName() + "'"
|
||||
|
||||
@@ -30,7 +30,7 @@ private predicate additionalLogicalCheck(Expr e, string operation, int valueToCh
|
||||
/**
|
||||
* An `Operation` that seems to be checking for leap year.
|
||||
*/
|
||||
class CheckForLeapYearOperation extends Operation {
|
||||
class CheckForLeapYearOperation extends Expr {
|
||||
CheckForLeapYearOperation() {
|
||||
exists(BinaryArithmeticOperation bo | bo = this |
|
||||
bo.getAnOperand().getValue().toInt() = 4 and
|
||||
@@ -39,8 +39,6 @@ class CheckForLeapYearOperation extends Operation {
|
||||
additionalLogicalCheck(this.getEnclosingElement(), "%", 400)
|
||||
)
|
||||
}
|
||||
|
||||
override string getOperator() { result = "LeapYearCheck" }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
int get_number_from_network();
|
||||
|
||||
int process_network(int[] buff, int buffSize) {
|
||||
int i = ntohl(get_number_from_network());
|
||||
return buff[i];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
uint32_t get_number_from_network();
|
||||
|
||||
int process_network(int[] buff, uint32_t buffSize) {
|
||||
uint32_t i = ntohl(get_number_from_network());
|
||||
if (i < buffSize) {
|
||||
return buff[i];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Data received over a network connection may be received in a different byte order than
|
||||
the byte order used by the local host, making the data difficult to process. To address this,
|
||||
data received over the wire is usually converted to host byte order by a call to a network-to-host
|
||||
byte order function, such as <code>ntohl</code>.
|
||||
</p>
|
||||
<p>
|
||||
The use of a network-to-host byte order function is therefore a good indicator that the returned
|
||||
value is unvalidated data retrieved from the network, and should not be used without further
|
||||
validation. In particular, the returned value should not be used as an array index or array length
|
||||
value without validation, as this could result in a buffer overflow vulnerability.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Validate data returned by network-to-host byte order functions before use and especially before
|
||||
using the value as an array index or bound.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>In the example below, network data is retrieved and passed to <code>ntohl</code> to convert
|
||||
it to host byte order. The data is then used as an index in an array access expression. However,
|
||||
there is no validation that the data returned by <code>ntohl</code> is within the bounds of the array,
|
||||
which could lead to reading outside the bounds of the buffer.
|
||||
</p>
|
||||
<sample src="NtohlArrayNoBound-bad.cpp" />
|
||||
<p>In the corrected example, the returned data is validated against the known size of the buffer,
|
||||
before being used as an array index.</p>
|
||||
<sample src="NtohlArrayNoBound-good.cpp" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-ntohl">
|
||||
ntohl - winsock reference
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://linux.die.net/man/3/ntohl">
|
||||
ntohl - Linux man page
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @id cpp/network-to-host-function-as-array-bound
|
||||
* @name Untrusted network-to-host usage
|
||||
* @description Using the result of a network-to-host byte order function, such as ntohl, as an
|
||||
* array bound or length value without checking it may result in buffer overflows or
|
||||
* other vulnerabilties.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import NtohlArrayNoBound
|
||||
import semmle.code.cpp.dataflow.DataFlow
|
||||
|
||||
from NetworkToBufferSizeConfiguration bufConfig, DataFlow::Node source, DataFlow::Node sink
|
||||
where bufConfig.hasFlow(source, sink)
|
||||
select sink, "Unchecked use of data from network function $@", source, source.toString()
|
||||
154
cpp/ql/src/Likely Bugs/Memory Management/NtohlArrayNoBound.qll
Normal file
154
cpp/ql/src/Likely Bugs/Memory Management/NtohlArrayNoBound.qll
Normal file
@@ -0,0 +1,154 @@
|
||||
import cpp
|
||||
import semmle.code.cpp.dataflow.DataFlow
|
||||
import semmle.code.cpp.controlflow.Guards
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
|
||||
/**
|
||||
* An access (read or write) to a buffer, provided as a pair of
|
||||
* a pointer to the buffer and the length of data to be read or written.
|
||||
* Extend this class to support different kinds of buffer access.
|
||||
*/
|
||||
abstract class BufferAccess extends Locatable {
|
||||
/** Gets the pointer to the buffer being accessed. */
|
||||
abstract Expr getPointer();
|
||||
|
||||
/** Gets the length of the data being read or written by this buffer access. */
|
||||
abstract Expr getAccessedLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* A buffer access through an array expression.
|
||||
*/
|
||||
class ArrayBufferAccess extends BufferAccess, ArrayExpr {
|
||||
override Expr getPointer() { result = this.getArrayBase() }
|
||||
|
||||
override Expr getAccessedLength() { result = this.getArrayOffset() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A buffer access through an overloaded array expression.
|
||||
*/
|
||||
class OverloadedArrayBufferAccess extends BufferAccess, OverloadedArrayExpr {
|
||||
override Expr getPointer() { result = this.getQualifier() }
|
||||
|
||||
override Expr getAccessedLength() { result = this.getAnArgument() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A buffer access through pointer arithmetic.
|
||||
*/
|
||||
class PointerArithmeticAccess extends BufferAccess, Expr {
|
||||
PointerArithmeticOperation p;
|
||||
|
||||
PointerArithmeticAccess() {
|
||||
this = p and
|
||||
p.getAnOperand().getType().getUnspecifiedType() instanceof IntegralType and
|
||||
not p.getParent() instanceof ComparisonOperation
|
||||
}
|
||||
|
||||
override Expr getPointer() {
|
||||
result = p.getAnOperand() and
|
||||
result.getType().getUnspecifiedType() instanceof PointerType
|
||||
}
|
||||
|
||||
override Expr getAccessedLength() {
|
||||
result = p.getAnOperand() and
|
||||
result.getType().getUnspecifiedType() instanceof IntegralType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of buffer accesses through a call to memcpy.
|
||||
*/
|
||||
class MemCpy extends BufferAccess, FunctionCall {
|
||||
MemCpy() { getTarget().hasName("memcpy") }
|
||||
|
||||
override Expr getPointer() {
|
||||
result = getArgument(0) or
|
||||
result = getArgument(1)
|
||||
}
|
||||
|
||||
override Expr getAccessedLength() { result = getArgument(2) }
|
||||
}
|
||||
|
||||
class StrncpySizeExpr extends BufferAccess, FunctionCall {
|
||||
StrncpySizeExpr() { getTarget().hasName("strncpy") }
|
||||
|
||||
override Expr getPointer() {
|
||||
result = getArgument(0) or
|
||||
result = getArgument(1)
|
||||
}
|
||||
|
||||
override Expr getAccessedLength() { result = getArgument(2) }
|
||||
}
|
||||
|
||||
class RecvSizeExpr extends BufferAccess, FunctionCall {
|
||||
RecvSizeExpr() { getTarget().hasName("recv") }
|
||||
|
||||
override Expr getPointer() { result = getArgument(1) }
|
||||
|
||||
override Expr getAccessedLength() { result = getArgument(2) }
|
||||
}
|
||||
|
||||
class SendSizeExpr extends BufferAccess, FunctionCall {
|
||||
SendSizeExpr() { getTarget().hasName("send") }
|
||||
|
||||
override Expr getPointer() { result = getArgument(1) }
|
||||
|
||||
override Expr getAccessedLength() { result = getArgument(2) }
|
||||
}
|
||||
|
||||
class SnprintfSizeExpr extends BufferAccess, FunctionCall {
|
||||
SnprintfSizeExpr() { getTarget().hasName("snprintf") }
|
||||
|
||||
override Expr getPointer() { result = getArgument(0) }
|
||||
|
||||
override Expr getAccessedLength() { result = getArgument(1) }
|
||||
}
|
||||
|
||||
class MemcmpSizeExpr extends BufferAccess, FunctionCall {
|
||||
MemcmpSizeExpr() { getTarget().hasName("Memcmp") }
|
||||
|
||||
override Expr getPointer() {
|
||||
result = getArgument(0) or
|
||||
result = getArgument(1)
|
||||
}
|
||||
|
||||
override Expr getAccessedLength() { result = getArgument(2) }
|
||||
}
|
||||
|
||||
class MallocSizeExpr extends BufferAccess, FunctionCall {
|
||||
MallocSizeExpr() { getTarget().hasName("malloc") }
|
||||
|
||||
override Expr getPointer() { none() }
|
||||
|
||||
override Expr getAccessedLength() { result = getArgument(1) }
|
||||
}
|
||||
|
||||
class NetworkFunctionCall extends FunctionCall {
|
||||
NetworkFunctionCall() {
|
||||
getTarget().hasName("ntohd") or
|
||||
getTarget().hasName("ntohf") or
|
||||
getTarget().hasName("ntohl") or
|
||||
getTarget().hasName("ntohll") or
|
||||
getTarget().hasName("ntohs")
|
||||
}
|
||||
}
|
||||
|
||||
class NetworkToBufferSizeConfiguration extends DataFlow::Configuration {
|
||||
NetworkToBufferSizeConfiguration() { this = "NetworkToBufferSizeConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof NetworkFunctionCall }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
node.asExpr() = any(BufferAccess ba).getAccessedLength()
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) {
|
||||
exists(GuardCondition gc, GVN gvn |
|
||||
gc.getAChild*() = gvn.getAnExpr() and
|
||||
globalValueNumber(node.asExpr()) = gvn and
|
||||
gc.controls(node.asExpr().getBasicBlock(), _)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
bool not_in_range(T *ptr, T *ptr_end, size_t i) {
|
||||
return ptr + i >= ptr_end || ptr + i < ptr; // BAD
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
bool not_in_range(T *ptr, T *ptr_end, size_t i) {
|
||||
return i >= ptr_end - ptr; // GOOD
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
When checking for integer overflow, you may often write tests like
|
||||
<code>p + i < p</code>. This works fine if <code>p</code> and
|
||||
<code>i</code> are unsigned integers, since any overflow in the addition
|
||||
will cause the value to simply "wrap around." However, using this pattern when
|
||||
<code>p</code> is a pointer is problematic because pointer overflow has
|
||||
undefined behavior according to the C and C++ standards. If the addition
|
||||
overflows and has an undefined result, the comparison will likewise be
|
||||
undefined; it may produce an unintended result, or may be deleted entirely by an
|
||||
optimizing compiler.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
To check whether an index <code>i</code> is less than the length of an array,
|
||||
simply compare these two numbers as unsigned integers: <code>i < ARRAY_LENGTH</code>.
|
||||
If the length of the array is defined as the difference between two pointers
|
||||
<code>ptr</code> and <code>p_end</code>, write <code>i < p_end - ptr</code>.
|
||||
If <code>i</code> is signed, cast it to unsigned
|
||||
in order to guard against negative <code>i</code>. For example, write
|
||||
<code>(size_t)i < p_end - ptr</code>.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
An invalid check for pointer overflow is most often seen as part of checking
|
||||
whether a number <code>a</code> is too large by checking first if adding the
|
||||
number to <code>ptr</code> goes past the end of an allocation and then
|
||||
checking if adding it to <code>ptr</code> creates a pointer so large that it
|
||||
overflows and wraps around.
|
||||
</p>
|
||||
|
||||
<sample src="PointerOverflow-bad.cpp" />
|
||||
|
||||
<p>
|
||||
In both of these checks, the operations are performed in the wrong order.
|
||||
First, an expression that may cause undefined behavior is evaluated
|
||||
(<code>ptr + i</code>), and then the result is checked for being in range.
|
||||
But once undefined behavior has happened in the pointer addition, it cannot
|
||||
be recovered from: it's too late to perform the range check after a possible
|
||||
pointer overflow.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
While it's not the subject of this query, the expression <code>ptr + i <
|
||||
ptr_end</code> is also an invalid range check. It's undefined behavor in
|
||||
C/C++ to create a pointer that points more than one past the end of an
|
||||
allocation.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The next example shows how to portably check whether an unsigned number is outside the
|
||||
range of an allocation between <code>ptr</code> and <code>ptr_end</code>.
|
||||
</p>
|
||||
<sample src="PointerOverflow-good.cpp" />
|
||||
</example>
|
||||
<references>
|
||||
<li>Embedded in Academia: <a href="https://blog.regehr.org/archives/1395">Pointer Overflow Checking</a>.</li>
|
||||
<li>LWN: <a href="https://lwn.net/Articles/278137/">GCC and pointer overflows</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
31
cpp/ql/src/Likely Bugs/Memory Management/PointerOverflow.ql
Normal file
31
cpp/ql/src/Likely Bugs/Memory Management/PointerOverflow.ql
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @name Pointer overflow check
|
||||
* @description Adding a value to a pointer to check if it overflows relies
|
||||
* on undefined behavior and may lead to memory corruption.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id cpp/pointer-overflow-check
|
||||
* @tags reliability
|
||||
* security
|
||||
*/
|
||||
|
||||
import cpp
|
||||
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
private import semmle.code.cpp.commons.Exclusions
|
||||
|
||||
from RelationalOperation ro, PointerAddExpr add, Expr expr1, Expr expr2
|
||||
where
|
||||
ro.getAnOperand() = add and
|
||||
add.getAnOperand() = expr1 and
|
||||
ro.getAnOperand() = expr2 and
|
||||
globalValueNumber(expr1) = globalValueNumber(expr2) and
|
||||
// Exclude macros but not their arguments
|
||||
not isFromMacroDefinition(ro) and
|
||||
// There must be a compilation of this file without a flag that makes pointer
|
||||
// overflow well defined.
|
||||
exists(Compilation c | c.getAFileCompiled() = ro.getFile() |
|
||||
not c.getAnArgument() = "-fwrapv-pointer" and
|
||||
not c.getAnArgument() = "-fno-strict-overflow"
|
||||
)
|
||||
select ro, "Range check relying on pointer overflow."
|
||||
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Using the TLS or SSLv23 protocol from the boost::asio library, but not disabling deprecated protocols may expose the software to known vulnerabilities or permit weak encryption algorithms to be used. Disabling the minimum-recommended protocols is also flagged.</p>
|
||||
</overview>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
<a href="https://www.boost.org/doc/libs/1_71_0/doc/html/boost_asio.html">Boost.Asio documentation</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @name Boost_asio TLS Settings Misconfiguration
|
||||
* @description Using the TLS or SSLv23 protocol from the boost::asio library, but not disabling deprecated protocols, or disabling minimum-recommended protocols.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @id cpp/boost/tls_settings_misconfiguration
|
||||
* @tags security
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.security.boostorg.asio.protocols
|
||||
|
||||
class ExistsAnyFlowConfig extends DataFlow::Configuration {
|
||||
ExistsAnyFlowConfig() { this = "ExistsAnyFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
exists(BoostorgAsio::SslContextClass c | c.getAContructorCall() = source.asExpr())
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(BoostorgAsio::SslSetOptionsFunction f, FunctionCall fcSetOptions |
|
||||
f.getACallToThisFunction() = fcSetOptions and
|
||||
fcSetOptions.getQualifier() = sink.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
bindingset[flag]
|
||||
predicate isOptionSet(ConstructorCall cc, int flag, FunctionCall fcSetOptions) {
|
||||
exists(ExistsAnyFlowConfig anyFlowConfig, VariableAccess contextSetOptions |
|
||||
anyFlowConfig.hasFlow(DataFlow::exprNode(cc), DataFlow::exprNode(contextSetOptions)) and
|
||||
exists(BoostorgAsio::SslSetOptionsFunction f | f.getACallToThisFunction() = fcSetOptions |
|
||||
contextSetOptions = fcSetOptions.getQualifier() and
|
||||
forall(
|
||||
Expr optionArgument, BoostorgAsio::SslOptionConfig optionArgConfig,
|
||||
Expr optionArgumentSource
|
||||
|
|
||||
optionArgument = fcSetOptions.getArgument(0) and
|
||||
optionArgConfig
|
||||
.hasFlow(DataFlow::exprNode(optionArgumentSource), DataFlow::exprNode(optionArgument))
|
||||
|
|
||||
optionArgument.getValue().toInt().bitShiftRight(16).bitAnd(flag) = flag
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[flag]
|
||||
predicate isOptionNotSet(ConstructorCall cc, int flag) {
|
||||
not exists(FunctionCall fcSetOptions | isOptionSet(cc, flag, fcSetOptions))
|
||||
}
|
||||
|
||||
from
|
||||
BoostorgAsio::SslContextCallTlsProtocolConfig configConstructor, Expr protocolSource,
|
||||
Expr protocolSink, ConstructorCall cc, Expr e, string msg
|
||||
where
|
||||
configConstructor.hasFlow(DataFlow::exprNode(protocolSource), DataFlow::exprNode(protocolSink)) and
|
||||
cc.getArgument(0) = protocolSink and
|
||||
(
|
||||
BoostorgAsio::isExprSslV23BoostProtocol(protocolSource) and
|
||||
not (
|
||||
isOptionSet(cc, BoostorgAsio::getShiftedSslOptionsNoSsl3(), _) and
|
||||
isOptionSet(cc, BoostorgAsio::getShiftedSslOptionsNoTls1(), _) and
|
||||
isOptionSet(cc, BoostorgAsio::getShiftedSslOptionsNoTls1_1(), _) and
|
||||
isOptionNotSet(cc, BoostorgAsio::getShiftedSslOptionsNoTls1_2())
|
||||
)
|
||||
or
|
||||
BoostorgAsio::isExprTlsBoostProtocol(protocolSource) and
|
||||
not BoostorgAsio::isExprSslV23BoostProtocol(protocolSource) and
|
||||
not (
|
||||
isOptionSet(cc, BoostorgAsio::getShiftedSslOptionsNoTls1(), _) and
|
||||
isOptionSet(cc, BoostorgAsio::getShiftedSslOptionsNoTls1_1(), _) and
|
||||
isOptionNotSet(cc, BoostorgAsio::getShiftedSslOptionsNoTls1_2())
|
||||
)
|
||||
) and
|
||||
(
|
||||
BoostorgAsio::isExprSslV23BoostProtocol(protocolSource) and
|
||||
isOptionNotSet(cc, BoostorgAsio::getShiftedSslOptionsNoSsl3()) and
|
||||
e = cc and
|
||||
msg = "no_sslv3 has not been set"
|
||||
or
|
||||
isOptionNotSet(cc, BoostorgAsio::getShiftedSslOptionsNoTls1()) and
|
||||
e = cc and
|
||||
msg = "no_tlsv1 has not been set"
|
||||
or
|
||||
isOptionNotSet(cc, BoostorgAsio::getShiftedSslOptionsNoTls1_1()) and
|
||||
e = cc and
|
||||
msg = "no_tlsv1_1 has not been set"
|
||||
or
|
||||
isOptionSet(cc, BoostorgAsio::getShiftedSslOptionsNoTls1_2(), e) and
|
||||
msg = "no_tlsv1_2 was set"
|
||||
)
|
||||
select cc, "Usage of $@ with protocol $@ is not configured correctly: The option $@.", cc,
|
||||
"boost::asio::ssl::context::context", protocolSource, protocolSource.toString(), e, msg
|
||||
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Using boost::asio library but specifying a deprecated hardcoded protocol.</p>
|
||||
<p>Using a deprecated hardcoded protocol instead of negotiting would lock your application to a protocol that has known vulnerabilities or weaknesses.</p>
|
||||
</overview>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
<a href="https://www.boost.org/doc/libs/1_71_0/doc/html/boost_asio.html">Boost.Asio documentation</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @name boost::asio Use of deprecated hardcoded Protocol
|
||||
* @description Using a deprecated hard-coded protocol using the boost::asio library.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @id cpp/boost/use-of-deprecated-hardcoded-security-protocol
|
||||
* @tags security
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.security.boostorg.asio.protocols
|
||||
|
||||
from
|
||||
BoostorgAsio::SslContextCallConfig config, Expr protocolSource, Expr protocolSink,
|
||||
ConstructorCall cc
|
||||
where
|
||||
config.hasFlow(DataFlow::exprNode(protocolSource), DataFlow::exprNode(protocolSink)) and
|
||||
not exists(BoostorgAsio::SslContextCallTlsProtocolConfig tlsConfig |
|
||||
tlsConfig.hasFlow(DataFlow::exprNode(protocolSource), DataFlow::exprNode(protocolSink))
|
||||
) and
|
||||
cc.getArgument(0) = protocolSink and
|
||||
exists(BoostorgAsio::SslContextCallBannedProtocolConfig bannedConfig |
|
||||
bannedConfig.hasFlow(DataFlow::exprNode(protocolSource), DataFlow::exprNode(protocolSink))
|
||||
)
|
||||
select protocolSink, "Usage of $@ specifying a deprecated hardcoded protocol $@ in function $@.",
|
||||
cc, "boost::asio::ssl::context::context", protocolSource, protocolSource.toString(),
|
||||
cc.getEnclosingFunction(), cc.getEnclosingFunction().toString()
|
||||
@@ -7,6 +7,10 @@
|
||||
* @id cpp/ignore-return-value-sal
|
||||
* @problem.severity warning
|
||||
* @tags reliability
|
||||
* external/cwe/cwe-573
|
||||
* external/cwe/cwe-252
|
||||
* @opaque-id SM02344
|
||||
* @microsoft.severity Important
|
||||
*/
|
||||
|
||||
import SAL
|
||||
|
||||
@@ -2,29 +2,45 @@ import cpp
|
||||
|
||||
class SALMacro extends Macro {
|
||||
SALMacro() {
|
||||
this.getFile().getBaseName() = "sal.h" or
|
||||
this.getFile().getBaseName() = "specstrings_strict.h" or
|
||||
this.getFile().getBaseName() = "specstrings.h"
|
||||
exists(string filename | filename = this.getFile().getBaseName() |
|
||||
filename = "sal.h" or
|
||||
filename = "specstrings_strict.h" or
|
||||
filename = "specstrings.h" or
|
||||
filename = "w32p.h" or
|
||||
filename = "minwindef.h"
|
||||
) and
|
||||
(
|
||||
// Dialect for Windows 8 and above
|
||||
this.getName().matches("\\_%\\_")
|
||||
or
|
||||
// Dialect for Windows 7
|
||||
this.getName().matches("\\_\\_%")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
predicate isTopLevelMacroAccess(MacroAccess ma) { not exists(ma.getParentInvocation()) }
|
||||
|
||||
class SALAnnotation extends MacroInvocation {
|
||||
SALAnnotation() {
|
||||
this.getMacro() instanceof SALMacro and
|
||||
not exists(this.getParentInvocation())
|
||||
isTopLevelMacroAccess(this)
|
||||
}
|
||||
|
||||
/** Returns the `Declaration` annotated by `this`. */
|
||||
Declaration getDeclaration() { annotatesAt(this, result.getADeclarationEntry(), _, _) }
|
||||
Declaration getDeclaration() {
|
||||
annotatesAt(this, result.getADeclarationEntry(), _, _) and
|
||||
not result instanceof Type // exclude typedefs
|
||||
}
|
||||
|
||||
/** Returns the `DeclarationEntry` annotated by `this`. */
|
||||
DeclarationEntry getDeclarationEntry() { annotatesAt(this, result, _, _) }
|
||||
DeclarationEntry getDeclarationEntry() {
|
||||
annotatesAt(this, result, _, _) and
|
||||
not result instanceof TypeDeclarationEntry // exclude typedefs
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Particular SAL annotations of interest
|
||||
*/
|
||||
|
||||
class SALCheckReturn extends SALAnnotation {
|
||||
SALCheckReturn() {
|
||||
exists(SALMacro m | m = this.getMacro() |
|
||||
@@ -39,8 +55,8 @@ class SALNotNull extends SALAnnotation {
|
||||
exists(SALMacro m | m = this.getMacro() |
|
||||
not m.getName().matches("%\\_opt\\_%") and
|
||||
(
|
||||
m.getName().matches("\\_In%") or
|
||||
m.getName().matches("\\_Out%") or
|
||||
m.getName().matches("_In%") or
|
||||
m.getName().matches("_Out%") or
|
||||
m.getName() = "_Ret_notnull_"
|
||||
)
|
||||
) and
|
||||
@@ -63,42 +79,124 @@ class SALMaybeNull extends SALAnnotation {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation details
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation details
|
||||
/**
|
||||
* Holds if `a` annotates the declaration entry `d` and
|
||||
* its start position is the `idx`th position in `file` that holds a SAL element.
|
||||
*/
|
||||
|
||||
private predicate annotatesAt(SALAnnotation a, DeclarationEntry e, File file, int idx) {
|
||||
a = salElementAt(file, idx) and
|
||||
(
|
||||
// Base case: `a` right before `e`
|
||||
e = salElementAt(file, idx + 1)
|
||||
or
|
||||
// Recursive case: `a` right before some annotation on `e`
|
||||
annotatesAt(_, e, file, idx + 1)
|
||||
)
|
||||
}
|
||||
|
||||
library class SALElement extends Element {
|
||||
SALElement() {
|
||||
this instanceof DeclarationEntry or
|
||||
this instanceof SALAnnotation
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the `idx`th `SALElement` in `file`. */
|
||||
private SALElement salElementAt(File file, int idx) {
|
||||
interestingLoc(file, result, interestingStartPos(file, idx))
|
||||
predicate annotatesAt(SALAnnotation a, DeclarationEntry d, File file, int idx) {
|
||||
annotatesAtPosition(a.(SALElement).getStartPosition(), d, file, idx)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an SALElement element at character `result` comes at
|
||||
* position `idx` in `file`.
|
||||
* Holds if `pos` is the `idx`th position in `file` that holds a SAL element,
|
||||
* which annotates the declaration entry `d` (by occurring before it without
|
||||
* any other declaration entries in between).
|
||||
*/
|
||||
private int interestingStartPos(File file, int idx) {
|
||||
result = rank[idx](int otherStart | interestingLoc(file, _, otherStart))
|
||||
// For performance reasons, do not mention the annotation itself here,
|
||||
// but compute with positions instead. This performs better on databases
|
||||
// with many annotations at the same position.
|
||||
private predicate annotatesAtPosition(SALPosition pos, DeclarationEntry d, File file, int idx) {
|
||||
pos = salRelevantPositionAt(file, idx) and
|
||||
salAnnotationPos(pos) and
|
||||
(
|
||||
// Base case: `pos` right before `d`
|
||||
d.(SALElement).getStartPosition() = salRelevantPositionAt(file, idx + 1)
|
||||
or
|
||||
// Recursive case: `pos` right before some annotation on `d`
|
||||
annotatesAtPosition(_, d, file, idx + 1)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `element` in `file` is at character `startPos`. */
|
||||
private predicate interestingLoc(File file, SALElement element, int startPos) {
|
||||
element.getLocation().charLoc(file, startPos, _)
|
||||
/**
|
||||
* A parameter annotated by one or more SAL annotations.
|
||||
*/
|
||||
class SALParameter extends Parameter {
|
||||
/** One of this parameter's annotations. */
|
||||
SALAnnotation a;
|
||||
|
||||
SALParameter() { annotatesAt(a, this.getADeclarationEntry(), _, _) }
|
||||
|
||||
predicate isIn() { a.getMacroName().toLowerCase().matches("%\\_in%") }
|
||||
|
||||
predicate isOut() { a.getMacroName().toLowerCase().matches("%\\_out%") }
|
||||
|
||||
predicate isInOut() { a.getMacroName().toLowerCase().matches("%\\_inout%") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A SAL element, that is, a SAL annotation or a declaration entry
|
||||
* that may have SAL annotations.
|
||||
*/
|
||||
library class SALElement extends Element {
|
||||
SALElement() {
|
||||
containsSALAnnotation(this.(DeclarationEntry).getFile()) or
|
||||
this instanceof SALAnnotation
|
||||
}
|
||||
|
||||
predicate hasStartPosition(File file, int line, int col) {
|
||||
exists(Location loc | loc = this.getLocation() |
|
||||
file = loc.getFile() and
|
||||
line = loc.getStartLine() and
|
||||
col = loc.getStartColumn()
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasEndPosition(File file, int line, int col) {
|
||||
exists(Location loc |
|
||||
loc = this.(FunctionDeclarationEntry).getBlock().getLocation()
|
||||
or
|
||||
this = any(VariableDeclarationEntry vde |
|
||||
vde.isDefinition() and
|
||||
loc = vde.getVariable().getInitializer().getLocation()
|
||||
)
|
||||
|
|
||||
file = loc.getFile() and
|
||||
line = loc.getEndLine() and
|
||||
col = loc.getEndColumn()
|
||||
)
|
||||
}
|
||||
|
||||
SALPosition getStartPosition() {
|
||||
exists(File file, int line, int col |
|
||||
this.hasStartPosition(file, line, col) and
|
||||
result = MkSALPosition(file, line, col)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `file` contains a SAL annotation. */
|
||||
pragma[noinline]
|
||||
private predicate containsSALAnnotation(File file) { any(SALAnnotation a).getFile() = file }
|
||||
|
||||
/**
|
||||
* A source-file position of a `SALElement`. Unlike location, this denotes a
|
||||
* point in the file rather than a range.
|
||||
*/
|
||||
private newtype SALPosition =
|
||||
MkSALPosition(File file, int line, int col) {
|
||||
exists(SALElement e |
|
||||
e.hasStartPosition(file, line, col)
|
||||
or
|
||||
e.hasEndPosition(file, line, col)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `pos` is the start position of a SAL annotation. */
|
||||
pragma[noinline]
|
||||
private predicate salAnnotationPos(SALPosition pos) {
|
||||
any(SALAnnotation a).(SALElement).getStartPosition() = pos
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `idx`th position in `file` that holds a SAL element,
|
||||
* ordering positions lexicographically by their start line and start column.
|
||||
*/
|
||||
private SALPosition salRelevantPositionAt(File file, int idx) {
|
||||
result = rank[idx](SALPosition pos, int line, int col |
|
||||
pos = MkSALPosition(file, line, col)
|
||||
|
|
||||
pos order by line, col
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,8 +34,10 @@ class FileFunction extends FunctionWithWrappers {
|
||||
nme.matches("CreateFile%")
|
||||
)
|
||||
or
|
||||
this.hasQualifiedName("std", "fopen")
|
||||
or
|
||||
// on any of the fstream classes, or filebuf
|
||||
exists(string nme | this.getDeclaringType().getSimpleName() = nme |
|
||||
exists(string nme | this.getDeclaringType().hasQualifiedName("std", nme) |
|
||||
nme = "basic_fstream" or
|
||||
nme = "basic_ifstream" or
|
||||
nme = "basic_ofstream" or
|
||||
|
||||
@@ -34,7 +34,7 @@ characters before writing to the HTML page.</p>
|
||||
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet">XSS
|
||||
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html">XSS
|
||||
(Cross Site Scripting) Prevention Cheat Sheet</a>.
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -17,8 +17,8 @@ import semmle.code.cpp.security.TaintTracking
|
||||
/** A call that prints its arguments to `stdout`. */
|
||||
class PrintStdoutCall extends FunctionCall {
|
||||
PrintStdoutCall() {
|
||||
getTarget().hasGlobalName("puts") or
|
||||
getTarget().hasGlobalName("printf")
|
||||
getTarget().hasGlobalOrStdName("puts") or
|
||||
getTarget().hasGlobalOrStdName("printf")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,7 @@ import semmle.code.cpp.dataflow.DataFlow
|
||||
import semmle.code.cpp.models.implementations.Memcpy
|
||||
|
||||
class MallocCall extends FunctionCall {
|
||||
MallocCall() {
|
||||
this.getTarget().hasGlobalName("malloc") or
|
||||
this.getTarget().hasQualifiedName("std", "malloc")
|
||||
}
|
||||
MallocCall() { this.getTarget().hasGlobalOrStdName("malloc") }
|
||||
|
||||
Expr getAllocatedSize() {
|
||||
if this.getArgument(0) instanceof VariableAccess
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @id cpp/comparison-with-wider-type
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @precision high
|
||||
* @tags reliability
|
||||
* security
|
||||
* external/cwe/cwe-190
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>A common pattern is to initialize a local variable by calling another function (an
|
||||
"initialization" function) with the address of the local variable as a pointer argument. That
|
||||
function is then responsible for writing to the memory location referenced by the pointer.</p>
|
||||
|
||||
<p>In some cases, the called function may not always write to the memory pointed to by the
|
||||
pointer argument. In such cases, the function will typically return a "status" code, informing the
|
||||
caller as to whether the initialization succeeded or not. If the caller does not check the status
|
||||
code before reading the local variable, it may read unitialized memory, which can result in
|
||||
unexpected behavior.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>When using a initialization function that does not guarantee to initialize the memory pointed to
|
||||
by the passed pointer, and returns a status code to indicate whether such initialization occurred,
|
||||
the status code should be checked before reading from the local variable.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>In this hypothetical example we have code for managing a series of devices. The code
|
||||
includes a <code>DeviceConfig</code> struct that can represent properties about each device.
|
||||
The <code>initDeviceConfig</code> function can be called to initialize one of these structures, by
|
||||
providing a "device number", which can be used to look up the appropriate properties in some data
|
||||
store. If an invalid device number is provided, the function returns a status code of
|
||||
<code>-1</code>, and does not initialize the provided pointer.</p>
|
||||
|
||||
<p>In the first code sample below, the <code>notify</code> function calls the
|
||||
<code>initDeviceConfig</code> function with a pointer to the local variable <code>config</code>,
|
||||
which is then subsequently accessed to fetch properties of the device. However, the code does not
|
||||
check the return value from the function call to <code>initDeviceConfig</code>. If the
|
||||
device number passed to the <code>notify</code> function was invalid, the
|
||||
<code>initDeviceConfig</code> function will leave the <code>config</code> variable uninitialized,
|
||||
which will result in the <code>notify</code> function accessing uninitialized memory.</p>
|
||||
|
||||
<sample src="ConditionallyUninitializedVariableBad.c" />
|
||||
|
||||
<p>To fix this, the code needs to check that the return value of the call to
|
||||
<code>initDeviceConfig</code> is zero. If that is true, then the calling code can safely assume
|
||||
that the local variable has been initialized.</p>
|
||||
|
||||
<sample src="ConditionallyUninitializedVariableGood.c" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
Wikipedia:
|
||||
<a href="https://en.wikipedia.org/wiki/Uninitialized_variable">Uninitialized variable</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @name Conditionally uninitialized variable
|
||||
* @description When an initialization function is used to initialize a local variable, but the
|
||||
* returned status code is not checked, the variable may be left in an uninitialized
|
||||
* state, and reading the variable may result in undefined behavior.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @opaque-id SM02313
|
||||
* @id cpp/conditionally-uninitialized-variable
|
||||
* @tags security
|
||||
* external/cwe/cwe-457
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.controlflow.SSA
|
||||
private import UninitializedVariables
|
||||
|
||||
from
|
||||
ConditionallyInitializedVariable v, ConditionalInitializationFunction f,
|
||||
ConditionalInitializationCall call, string defined, Evidence e
|
||||
where
|
||||
exists(v.getARiskyAccess(f, call, e)) and
|
||||
(
|
||||
if e = DefinitionInSnapshot()
|
||||
then defined = ""
|
||||
else
|
||||
if e = SuggestiveSALAnnotation()
|
||||
then defined = "externally defined (SAL) "
|
||||
else defined = "externally defined (CSV) "
|
||||
)
|
||||
select call,
|
||||
"The status of this call to " + defined +
|
||||
"$@ is not checked, potentially leaving $@ uninitialized.", f, f.getName(), v, v.getName()
|
||||
@@ -0,0 +1,25 @@
|
||||
struct DeviceConfig {
|
||||
bool isEnabled;
|
||||
int channel;
|
||||
};
|
||||
|
||||
int initDeviceConfig(DeviceConfig *ref, int deviceNumber) {
|
||||
if (deviceNumber >= getMaxDevices()) {
|
||||
// No device with that number, return -1 to indicate failure
|
||||
return -1;
|
||||
}
|
||||
// Device with that number, fetch parameters and initialize struct
|
||||
ref->isEnabled = fetchIsDeviceEnabled(deviceNumber);
|
||||
ref->channel = fetchDeviceChannel(deviceNumber);
|
||||
// Return 0 to indicate success
|
||||
return 0;
|
||||
}
|
||||
|
||||
int notify(int deviceNumber) {
|
||||
DeviceConfig config;
|
||||
initDeviceConfig(&config, deviceNumber);
|
||||
// BAD: Using config without checking the status code that is returned
|
||||
if (config.isEnabled) {
|
||||
notifyChannel(config.channel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
struct DeviceConfig {
|
||||
bool isEnabled;
|
||||
int channel;
|
||||
};
|
||||
|
||||
int initDeviceConfig(DeviceConfig *ref, int deviceNumber) {
|
||||
if (deviceNumber >= getMaxDevices()) {
|
||||
// No device with that number, return -1 to indicate failure
|
||||
return -1;
|
||||
}
|
||||
// Device with that number, fetch parameters and initialize struct
|
||||
ref->isEnabled = fetchIsDeviceEnabled(deviceNumber);
|
||||
ref->channel = fetchDeviceChannel(deviceNumber);
|
||||
// Return 0 to indicate success
|
||||
return 0;
|
||||
}
|
||||
|
||||
void notify(int deviceNumber) {
|
||||
DeviceConfig config;
|
||||
int statusCode = initDeviceConfig(&config, deviceNumber);
|
||||
if (statusCode == 0) {
|
||||
// GOOD: Status code returned by initialization function is checked, so this is safe
|
||||
if (config.isEnabled) {
|
||||
notifyChannel(config.channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
705
cpp/ql/src/Security/CWE/CWE-457/InitializationFunctions.qll
Normal file
705
cpp/ql/src/Security/CWE/CWE-457/InitializationFunctions.qll
Normal file
@@ -0,0 +1,705 @@
|
||||
/**
|
||||
* Provides classes and predicates for identifying functions that initialize their arguments.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import external.ExternalArtifact
|
||||
private import semmle.code.cpp.dispatch.VirtualDispatchPrototype
|
||||
import semmle.code.cpp.NestedFields
|
||||
import Microsoft.SAL
|
||||
import semmle.code.cpp.controlflow.Guards
|
||||
|
||||
/** A context under which a function may be called. */
|
||||
private newtype TContext =
|
||||
/** No specific call context. */
|
||||
NoContext() or
|
||||
/**
|
||||
* The call context is that the given other parameter is null.
|
||||
*
|
||||
* This context is created for all parameters that are null checked in the body of the function.
|
||||
*/
|
||||
ParamNull(Parameter p) { p = any(ParameterNullCheck pnc).getParameter() } or
|
||||
/**
|
||||
* The call context is that the given other parameter is not null.
|
||||
*
|
||||
* This context is created for all parameters that are null checked in the body of the function.
|
||||
*/
|
||||
ParamNotNull(Parameter p) { p = any(ParameterNullCheck pnc).getParameter() }
|
||||
|
||||
/**
|
||||
* A context under which a function may be called.
|
||||
*
|
||||
* Some functions may conditionally initialize a parameter depending on the value of another
|
||||
* parameter. Consider:
|
||||
* ```
|
||||
* int MyInitFunction(int* paramToBeInitialized, int* paramToCheck) {
|
||||
* if (!paramToCheck) {
|
||||
* // fail!
|
||||
* return -1;
|
||||
* }
|
||||
* paramToBeInitialized = 0;
|
||||
* }
|
||||
* ```
|
||||
* In this case, whether `paramToBeInitialized` is initialized when this function call completes
|
||||
* depends on whether `paramToCheck` is or is not null. A call-context insensitive analysis will
|
||||
* determine that any call to this function may leave the parameter uninitialized, even if the
|
||||
* argument to paramToCheck is guaranteed to be non-null (`&foo`, for example).
|
||||
*
|
||||
* This class models call contexts that can be considered when calculating whether a given parameter
|
||||
* initializes or not. The supported contexts are:
|
||||
* - `ParamNull(otherParam)` - the given `otherParam` is considered to be null. Applies when
|
||||
* exactly one parameter other than this one is null checked.
|
||||
* - `ParamNotNull(otherParam)` - the given `otherParam` is considered to be not null. Applies when
|
||||
* exactly one parameter other than this one is null checked.
|
||||
* - `NoContext()` - applies in all other circumstances.
|
||||
*/
|
||||
class Context extends TContext {
|
||||
string toString() {
|
||||
this = NoContext() and result = "NoContext"
|
||||
or
|
||||
this = ParamNull(any(Parameter p | result = "ParamNull(" + p.getName() + ")"))
|
||||
or
|
||||
this = ParamNotNull(any(Parameter p | result = "ParamNotNull(" + p.getName() + ")"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A check against a parameter.
|
||||
*/
|
||||
abstract class ParameterCheck extends Expr {
|
||||
/**
|
||||
* Gets a successor of this check that should be ignored for the given context.
|
||||
*/
|
||||
abstract ControlFlowNode getIgnoredSuccessorForContext(Context c);
|
||||
}
|
||||
|
||||
/** A null-check expression on a parameter. */
|
||||
class ParameterNullCheck extends ParameterCheck {
|
||||
Parameter p;
|
||||
ControlFlowNode nullSuccessor;
|
||||
ControlFlowNode notNullSuccessor;
|
||||
|
||||
ParameterNullCheck() {
|
||||
this.isCondition() and
|
||||
p.getFunction() instanceof InitializationFunction and
|
||||
p.getType().getUnspecifiedType() instanceof PointerType and
|
||||
exists(VariableAccess va | va = p.getAnAccess() |
|
||||
nullSuccessor = getATrueSuccessor() and
|
||||
notNullSuccessor = getAFalseSuccessor() and
|
||||
(
|
||||
va = this.(NotExpr).getOperand() or
|
||||
va = any(EQExpr eq | eq = this and eq.getAnOperand().getValue() = "0").getAnOperand() or
|
||||
va = getCheckedFalseCondition(this) or
|
||||
va = any(NEExpr eq |
|
||||
eq = getCheckedFalseCondition(this) and eq.getAnOperand().getValue() = "0"
|
||||
).getAnOperand()
|
||||
)
|
||||
or
|
||||
nullSuccessor = getAFalseSuccessor() and
|
||||
notNullSuccessor = getATrueSuccessor() and
|
||||
(
|
||||
va = this or
|
||||
va = any(NEExpr eq | eq = this and eq.getAnOperand().getValue() = "0").getAnOperand() or
|
||||
va = any(EQExpr eq |
|
||||
eq = getCheckedFalseCondition(this) and eq.getAnOperand().getValue() = "0"
|
||||
).getAnOperand()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** The parameter being null-checked. */
|
||||
Parameter getParameter() { result = p }
|
||||
|
||||
override ControlFlowNode getIgnoredSuccessorForContext(Context c) {
|
||||
c = ParamNull(p) and result = notNullSuccessor
|
||||
or
|
||||
c = ParamNotNull(p) and result = nullSuccessor
|
||||
}
|
||||
|
||||
/** The successor at which the parameter is confirmed to be null. */
|
||||
ControlFlowNode getNullSuccessor() { result = nullSuccessor }
|
||||
|
||||
/** The successor at which the parameter is confirmed to be not-null. */
|
||||
ControlFlowNode getNotNullSuccessor() { result = notNullSuccessor }
|
||||
}
|
||||
|
||||
/**
|
||||
* An entry in a CSV file in cond-init that contains externally defined functions that are
|
||||
* conditional initializers. These files are typically produced by running the
|
||||
* ConditionallyInitializedFunction companion query.
|
||||
*/
|
||||
class ValidatedExternalCondInitFunction extends ExternalData {
|
||||
ValidatedExternalCondInitFunction() { this.getDataPath().matches("%cond-init%.csv") }
|
||||
|
||||
predicate isExternallyVerified(Function f, int param) {
|
||||
functionSignature(f, getField(1), getField(2)) and param = getFieldAsInt(3)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of evidence used to determine whether a function initializes a parameter.
|
||||
*/
|
||||
newtype Evidence =
|
||||
/**
|
||||
* The function is defined in the snapshot, and the CFG has been analyzed to determine that the
|
||||
* parameter is not initialized on at least one path to the exit.
|
||||
*/
|
||||
DefinitionInSnapshot() or
|
||||
/**
|
||||
* The function is externally defined, but the parameter has an `_out` SAL annotation which
|
||||
* suggests that it is initialized in the function.
|
||||
*/
|
||||
SuggestiveSALAnnotation() or
|
||||
/**
|
||||
* We have been given a CSV file which indicates this parameter is conditionally initialized.
|
||||
*/
|
||||
ExternalEvidence()
|
||||
|
||||
/**
|
||||
* A call to an function which initializes one or more of its parameters.
|
||||
*/
|
||||
class InitializationFunctionCall extends FunctionCall {
|
||||
Expr initializedArgument;
|
||||
|
||||
InitializationFunctionCall() { initializedArgument = getAnInitializedArgument(this) }
|
||||
|
||||
/** Gets a parameter that is initialized by this call. */
|
||||
Parameter getAnInitParameter() { result.getAnAccess() = initializedArgument }
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable access which is dereferenced then assigned to.
|
||||
*/
|
||||
private predicate isPointerDereferenceAssignmentTarget(VariableAccess target) {
|
||||
target.getParent().(PointerDereferenceExpr) = any(Assignment e).getLValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* A function which initializes one or more of its parameters.
|
||||
*/
|
||||
class InitializationFunction extends Function {
|
||||
int i;
|
||||
Evidence evidence;
|
||||
|
||||
InitializationFunction() {
|
||||
evidence = DefinitionInSnapshot() and
|
||||
(
|
||||
// Assignment by pointer dereferencing the parameter
|
||||
isPointerDereferenceAssignmentTarget(this.getParameter(i).getAnAccess()) or
|
||||
// Field wise assignment to the parameter
|
||||
any(Assignment e).getLValue() = getAFieldAccess(this.getParameter(i)) or
|
||||
i = this
|
||||
.(MemberFunction)
|
||||
.getAnOverridingFunction+()
|
||||
.(InitializationFunction)
|
||||
.initializedParameter() or
|
||||
getParameter(i) = any(InitializationFunctionCall c).getAnInitParameter()
|
||||
)
|
||||
or
|
||||
// If we have no definition, we look at SAL annotations
|
||||
not this.isDefined() and
|
||||
this.getParameter(i).(SALParameter).isOut() and
|
||||
evidence = SuggestiveSALAnnotation()
|
||||
or
|
||||
// We have some external information that this function conditionally initializes
|
||||
not this.isDefined() and
|
||||
any(ValidatedExternalCondInitFunction vc).isExternallyVerified(this, i) and
|
||||
evidence = ExternalEvidence()
|
||||
}
|
||||
|
||||
/** Gets a parameter index which is initialized by this function. */
|
||||
int initializedParameter() { result = i }
|
||||
|
||||
/** Gets a `ControlFlowNode` which assigns a new value to the parameter with the given index. */
|
||||
ControlFlowNode paramReassignment(int index) {
|
||||
index = i and
|
||||
(
|
||||
result = this.getParameter(i).getAnAccess() and
|
||||
(
|
||||
result = any(Assignment a).getLValue().(PointerDereferenceExpr).getOperand()
|
||||
or
|
||||
// Field wise assignment to the parameter
|
||||
result = any(Assignment a).getLValue().(FieldAccess).getQualifier()
|
||||
or
|
||||
// Assignment to a nested field of the parameter
|
||||
result = any(Assignment a).getLValue().(NestedFieldAccess).getUltimateQualifier()
|
||||
or
|
||||
result = getAnInitializedArgument(any(Call c))
|
||||
or
|
||||
exists(IfStmt check | result = check.getCondition().getAChild*() |
|
||||
paramReassignmentCondition(check)
|
||||
)
|
||||
)
|
||||
or
|
||||
result = any(AssumeExpr e |
|
||||
e.getEnclosingFunction() = this and e.getAChild().(Literal).getValue() = "0"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate: holds if the `if` statement `check` contains a
|
||||
* reassignment to the `i`th parameter within its `then` statement.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate paramReassignmentCondition(IfStmt check) {
|
||||
this.paramReassignment(i).getEnclosingStmt().getParentStmt*() = check.getThen()
|
||||
}
|
||||
|
||||
/** Holds if `n` can be reached without the parameter at `index` being reassigned. */
|
||||
predicate paramNotReassignedAt(ControlFlowNode n, int index, Context c) {
|
||||
c = getAContext(index) and
|
||||
(
|
||||
not exists(this.getEntryPoint()) and index = i and n = this
|
||||
or
|
||||
n = this.getEntryPoint() and index = i
|
||||
or
|
||||
exists(ControlFlowNode mid | paramNotReassignedAt(mid, index, c) |
|
||||
n = mid.getASuccessor() and
|
||||
not n = paramReassignment(index) and
|
||||
/*
|
||||
* Ignore successor edges where the parameter is null, because it is then confirmed to be
|
||||
* initialized.
|
||||
*/
|
||||
|
||||
not exists(ParameterNullCheck nullCheck |
|
||||
nullCheck = mid and
|
||||
nullCheck = getANullCheck(index) and
|
||||
n = nullCheck.getNullSuccessor()
|
||||
) and
|
||||
/*
|
||||
* Ignore successor edges which are excluded by the given context
|
||||
*/
|
||||
|
||||
not exists(ParameterCheck paramCheck | paramCheck = mid |
|
||||
n = paramCheck.getIgnoredSuccessorForContext(c)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a null-check on the parameter at `index`. */
|
||||
private ParameterNullCheck getANullCheck(int index) {
|
||||
getParameter(index) = result.getParameter()
|
||||
}
|
||||
|
||||
/** Gets a parameter which is not at the given index. */
|
||||
private Parameter getOtherParameter(int index) {
|
||||
index = i and
|
||||
result = getAParameter() and
|
||||
not result.getIndex() = index
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call `Context` that is applicable when considering whether parameter at the `index` can
|
||||
* be conditionally initialized.
|
||||
*/
|
||||
Context getAContext(int index) {
|
||||
index = i and
|
||||
/*
|
||||
* If there is one and only one other parameter which is null checked in the body of the method,
|
||||
* then we have two contexts to consider - that the other param is null, or that the other param
|
||||
* is not null.
|
||||
*/
|
||||
|
||||
if
|
||||
strictcount(Parameter p |
|
||||
exists(Context c | c = ParamNull(p) or c = ParamNotNull(p)) and
|
||||
p = getOtherParameter(index)
|
||||
) = 1
|
||||
then
|
||||
exists(Parameter p | p = getOtherParameter(index) |
|
||||
result = ParamNull(p) or result = ParamNotNull(p)
|
||||
)
|
||||
else
|
||||
// Otherwise, only consider NoContext.
|
||||
result = NoContext()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this function should be whitelisted - that is, not considered as conditionally
|
||||
* initializing its parameters.
|
||||
*/
|
||||
predicate whitelisted() {
|
||||
exists(string name | this.hasName(name) |
|
||||
// Return value is not a success code but the output functions never fail.
|
||||
name.matches("_Interlocked%")
|
||||
or
|
||||
// Functions that never fail, according to MSDN.
|
||||
name = "QueryPerformanceCounter"
|
||||
or
|
||||
name = "QueryPerformanceFrequency"
|
||||
or
|
||||
// Functions that never fail post-Vista, according to MSDN.
|
||||
name = "InitializeCriticalSectionAndSpinCount"
|
||||
or
|
||||
// `rand_s` writes 0 to a non-null argument if it fails, according to MSDN.
|
||||
name = "rand_s"
|
||||
or
|
||||
// IntersectRect initializes the argument regardless of whether the input intersects
|
||||
name = "IntersectRect"
|
||||
or
|
||||
name = "SetRect"
|
||||
or
|
||||
name = "UnionRect"
|
||||
or
|
||||
// These functions appears to have an incorrect CFG, which leads to false positives
|
||||
name = "PhysicalToLogicalDPIPoint"
|
||||
or
|
||||
name = "LogicalToPhysicalDPIPoint"
|
||||
or
|
||||
// Sets NtProductType to default on error
|
||||
name = "RtlGetNtProductType"
|
||||
or
|
||||
// Our CFG is not sophisticated enough to detect that the argument is always initialized
|
||||
name = "StringCchLengthA"
|
||||
or
|
||||
// All paths init the argument, and always returns SUCCESS.
|
||||
name = "RtlUnicodeToMultiByteSize"
|
||||
or
|
||||
// All paths init the argument, and always returns SUCCESS.
|
||||
name = "RtlMultiByteToUnicodeSize"
|
||||
or
|
||||
// All paths init the argument, and always returns SUCCESS.
|
||||
name = "RtlUnicodeToMultiByteN"
|
||||
or
|
||||
// Always initializes argument
|
||||
name = "RtlGetFirstRange"
|
||||
or
|
||||
// Destination range is zeroed out on failure, assuming first two parameters are valid
|
||||
name = "memcpy_s"
|
||||
or
|
||||
// This zeroes the memory unconditionally
|
||||
name = "SeCreateAccessState"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function which initializes one or more of its parameters, but not on all paths.
|
||||
*/
|
||||
class ConditionalInitializationFunction extends InitializationFunction {
|
||||
Context c;
|
||||
|
||||
ConditionalInitializationFunction() {
|
||||
c = this.getAContext(i) and
|
||||
not this.whitelisted() and
|
||||
exists(Type status | status = this.getType().getUnspecifiedType() |
|
||||
status instanceof IntegralType or
|
||||
status instanceof Enum
|
||||
) and
|
||||
not this.getType().getName().toLowerCase() = "size_t" and
|
||||
(
|
||||
/*
|
||||
* If there is no definition, consider this to be conditionally initializing (based on either
|
||||
* SAL or external data).
|
||||
*/
|
||||
|
||||
not evidence = DefinitionInSnapshot()
|
||||
or
|
||||
/*
|
||||
* If this function is defined in this snapshot, then it conditionally initializes if there
|
||||
* is at least one path through the function which doesn't initialize the parameter.
|
||||
*
|
||||
* Explicitly ignore pure virtual functions.
|
||||
*/
|
||||
|
||||
this.isDefined() and
|
||||
this.paramNotReassignedAt(this, i, c) and
|
||||
not this instanceof PureVirtualFunction
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the evidence associated with the given parameter. */
|
||||
Evidence getEvidence(int param) {
|
||||
/*
|
||||
* Note: due to the way the predicate dispatch interacts with fields, this needs to be
|
||||
* implemented on this class, not `InitializationFunction`. If implemented on the latter it
|
||||
* can return evidence that does not result in conditional initialization.
|
||||
*/
|
||||
|
||||
param = i and evidence = result
|
||||
}
|
||||
|
||||
/** Gets the index of a parameter which is conditionally initialized. */
|
||||
int conditionallyInitializedParameter(Context context) { result = i and context = c }
|
||||
}
|
||||
|
||||
/**
|
||||
* More elaborate tracking, flagging cases where the status is checked after
|
||||
* the potentially uninitialized variable has been used, and ignoring cases
|
||||
* where the status is not checked but there is no use of the potentially
|
||||
* uninitialized variable, may be obtained via `getARiskyAccess`.
|
||||
*/
|
||||
class ConditionalInitializationCall extends FunctionCall {
|
||||
ConditionalInitializationFunction target;
|
||||
|
||||
ConditionalInitializationCall() { target = getTarget(this) }
|
||||
|
||||
/** Gets the argument passed for the given parameter to this call. */
|
||||
Expr getArgumentForParameter(Parameter p) {
|
||||
p = getTarget().getAParameter() and
|
||||
result = getArgument(p.getIndex())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an argument conditionally initialized by this call.
|
||||
*/
|
||||
Expr getAConditionallyInitializedArgument(ConditionalInitializationFunction condTarget, Evidence e) {
|
||||
condTarget = target and
|
||||
exists(Context context |
|
||||
result = getAConditionallyInitializedArgument(this, condTarget, context, e)
|
||||
|
|
||||
context = NoContext()
|
||||
or
|
||||
exists(Parameter otherP, Expr otherArg |
|
||||
context = ParamNotNull(otherP) or
|
||||
context = ParamNull(otherP)
|
||||
|
|
||||
otherArg = getArgumentForParameter(otherP) and
|
||||
(otherArg instanceof AddressOfExpr implies context = ParamNotNull(otherP)) and
|
||||
(otherArg.getType() instanceof ArrayType implies context = ParamNotNull(otherP)) and
|
||||
(otherArg.getValue() = "0" implies context = ParamNull(otherP))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
VariableAccess getAConditionallyInitializedVariable() {
|
||||
not result.getTarget().getAnAssignedValue().getASuccessor+() = result and
|
||||
// Should not be assigned field-wise prior to the call.
|
||||
not exists(Assignment a, FieldAccess fa |
|
||||
fa.getQualifier() = result.getTarget().getAnAccess() and
|
||||
a.getLValue() = fa and
|
||||
fa.getASuccessor+() = result
|
||||
) and
|
||||
result = this
|
||||
.getArgument(getTarget(this)
|
||||
.(ConditionalInitializationFunction)
|
||||
.conditionallyInitializedParameter(_))
|
||||
.(AddressOfExpr)
|
||||
.getOperand()
|
||||
}
|
||||
|
||||
Variable getStatusVariable() {
|
||||
exists(AssignExpr a | a.getLValue() = result.getAnAccess() | a.getRValue() = this)
|
||||
or
|
||||
result.getInitializer().getExpr() = this
|
||||
}
|
||||
|
||||
Expr getSuccessCheck() {
|
||||
exists(this.getAFalseSuccessor()) and result = this
|
||||
or
|
||||
result = this.getParent() and
|
||||
(
|
||||
result instanceof NotExpr or
|
||||
result.(EQExpr).getAnOperand().getValue() = "0" or
|
||||
result.(GEExpr).getLesserOperand().getValue() = "0"
|
||||
)
|
||||
}
|
||||
|
||||
Expr getFailureCheck() {
|
||||
result = this.getParent() and
|
||||
(
|
||||
result instanceof NotExpr or
|
||||
result.(NEExpr).getAnOperand().getValue() = "0" or
|
||||
result.(LTExpr).getLesserOperand().getValue() = "0"
|
||||
)
|
||||
}
|
||||
|
||||
private predicate inCheckedContext() {
|
||||
exists(Call parent | this = parent.getAnArgument() |
|
||||
parent.getTarget() instanceof Operator or
|
||||
parent.getTarget().hasName("VerifyOkCatastrophic")
|
||||
)
|
||||
}
|
||||
|
||||
ControlFlowNode uncheckedReaches(LocalVariable var) {
|
||||
(
|
||||
not exists(var.getInitializer()) and
|
||||
var = this.getAConditionallyInitializedVariable().getTarget() and
|
||||
if exists(this.getFailureCheck())
|
||||
then result = this.getFailureCheck().getATrueSuccessor()
|
||||
else
|
||||
if exists(this.getSuccessCheck())
|
||||
then result = this.getSuccessCheck().getAFalseSuccessor()
|
||||
else (
|
||||
result = this.getASuccessor() and not this.inCheckedContext()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(ControlFlowNode mid | mid = uncheckedReaches(var) |
|
||||
not mid = getStatusVariable().getAnAccess() and
|
||||
not mid = var.getAnAccess() and
|
||||
not exists(VariableAccess write | result = write and write = var.getAnAccess() |
|
||||
write = any(AssignExpr a).getLValue() or
|
||||
write = any(AddressOfExpr a).getOperand()
|
||||
) and
|
||||
result = mid.getASuccessor()
|
||||
)
|
||||
}
|
||||
|
||||
VariableAccess getARiskyRead(Function f) {
|
||||
f = this.getTarget() and
|
||||
exists(this.getFile().getRelativePath()) and
|
||||
result = this.uncheckedReaches(result.getTarget()) and
|
||||
not this.(GuardCondition).controls(result.getBasicBlock(), _)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of an argument to the call which is initialized by the call.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
int initializedArgument(Call call) {
|
||||
exists(InitializationFunction target |
|
||||
target = getTarget(call) and
|
||||
result = target.initializedParameter()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an argument which is initialized by the call.
|
||||
*/
|
||||
Expr getAnInitializedArgument(Call call) { result = call.getArgument(initializedArgument(call)) }
|
||||
|
||||
/**
|
||||
* Gets the position of an argument to the call to the target which is conditionally initialized by
|
||||
* the call, under the given context and evidence.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private int conditionallyInitializedArgument(
|
||||
Call call, ConditionalInitializationFunction target, Context c, Evidence e
|
||||
) {
|
||||
target = getTarget(call) and
|
||||
c = target.getAContext(result) and
|
||||
e = target.getEvidence(result) and
|
||||
result = target.conditionallyInitializedParameter(c)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an argument which is conditionally initialized by the call to the given target under the given context and evidence.
|
||||
*/
|
||||
Expr getAConditionallyInitializedArgument(
|
||||
Call call, ConditionalInitializationFunction target, Context c, Evidence e
|
||||
) {
|
||||
result = call.getArgument(conditionallyInitializedArgument(call, target, c, e))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type signature for the functions parameters.
|
||||
*/
|
||||
private string typeSig(Function f) {
|
||||
result = concat(int i, Type pt |
|
||||
pt = f.getParameter(i).getType()
|
||||
|
|
||||
pt.getUnspecifiedType().toString(), "," order by i
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds where qualifiedName and typeSig make up the signature for the function.
|
||||
*/
|
||||
private predicate functionSignature(Function f, string qualifiedName, string typeSig) {
|
||||
qualifiedName = f.getQualifiedName() and
|
||||
typeSig = typeSig(f)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a possible definition for the undefined function by matching the undefined function name
|
||||
* and parameter arity with a defined function.
|
||||
*
|
||||
* This is useful for identifying call to target dependencies across libraries, where the libraries
|
||||
* are never statically linked together.
|
||||
*/
|
||||
private Function getAPossibleDefinition(Function undefinedFunction) {
|
||||
not undefinedFunction.isDefined() and
|
||||
exists(string qn, string typeSig |
|
||||
functionSignature(undefinedFunction, qn, typeSig) and functionSignature(result, qn, typeSig)
|
||||
) and
|
||||
result.isDefined()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for `getTarget`, that computes possible targets of a `Call`.
|
||||
*
|
||||
* If there is at least one defined target after performing some simple virtual dispatch
|
||||
* resolution, then the result is all the defined targets.
|
||||
*/
|
||||
private Function getTarget1(Call c) {
|
||||
result = VirtualDispatch::getAViableTarget(c) and
|
||||
result.isDefined()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for `getTarget`, that computes possible targets of a `Call`.
|
||||
*
|
||||
* If we can use the heuristic matching of functions to find definitions for some of the viable
|
||||
* targets, return those.
|
||||
*/
|
||||
private Function getTarget2(Call c) {
|
||||
not exists(getTarget1(c)) and
|
||||
result = getAPossibleDefinition(VirtualDispatch::getAViableTarget(c))
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for `getTarget`, that computes possible targets of a `Call`.
|
||||
*
|
||||
* Otherwise, the result is the undefined `Function` instances.
|
||||
*/
|
||||
private Function getTarget3(Call c) {
|
||||
not exists(getTarget1(c)) and
|
||||
not exists(getTarget2(c)) and
|
||||
result = VirtualDispatch::getAViableTarget(c)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a possible target for the `Call`, using the name and parameter matching if we did not associate
|
||||
* this call with a specific definition at link or compile time, and performing simple virtual
|
||||
* dispatch resolution.
|
||||
*/
|
||||
Function getTarget(Call c) {
|
||||
result = getTarget1(c) or
|
||||
result = getTarget2(c) or
|
||||
result = getTarget3(c)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an access of a field on `Variable` v.
|
||||
*/
|
||||
FieldAccess getAFieldAccess(Variable v) {
|
||||
exists(VariableAccess va, Expr qualifierExpr |
|
||||
// Find an access of the variable, or an AddressOfExpr that has the access
|
||||
va = v.getAnAccess() and
|
||||
(
|
||||
qualifierExpr = va or
|
||||
qualifierExpr.(AddressOfExpr).getOperand() = va
|
||||
)
|
||||
|
|
||||
// Direct field access
|
||||
qualifierExpr = result.getQualifier()
|
||||
or
|
||||
// Nested field access
|
||||
qualifierExpr = result.(NestedFieldAccess).getUltimateQualifier()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a condition which is checked to be false by the given `ne` expression, according to this pattern:
|
||||
* ```
|
||||
* int a = !!result;
|
||||
* if (!a) { // <- ne
|
||||
* ....
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private Expr getCheckedFalseCondition(NotExpr ne) {
|
||||
exists(LocalVariable v |
|
||||
result = v.getInitializer().getExpr().(NotExpr).getOperand().(NotExpr).getOperand() and
|
||||
ne.getOperand() = v.getAnAccess() and
|
||||
nonAssignedVariable(v)
|
||||
// and not passed by val?
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nonAssignedVariable(Variable v) { not exists(v.getAnAssignment()) }
|
||||
190
cpp/ql/src/Security/CWE/CWE-457/UninitializedVariables.qll
Normal file
190
cpp/ql/src/Security/CWE/CWE-457/UninitializedVariables.qll
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* A module for identifying conditionally initialized variables.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import InitializationFunctions
|
||||
|
||||
// Optimised reachability predicates
|
||||
private predicate reaches(ControlFlowNode a, ControlFlowNode b) = fastTC(successor/2)(a, b)
|
||||
|
||||
private predicate successor(ControlFlowNode a, ControlFlowNode b) { b = a.getASuccessor() }
|
||||
|
||||
class WhitelistedCallsConfig extends string {
|
||||
WhitelistedCallsConfig() { this = "config" }
|
||||
|
||||
abstract predicate isWhitelisted(Call c);
|
||||
}
|
||||
|
||||
abstract class WhitelistedCall extends Call {
|
||||
override Function getTarget() { none() }
|
||||
}
|
||||
|
||||
private predicate hasConditionalInitialization(
|
||||
ConditionalInitializationFunction f, ConditionalInitializationCall call, LocalVariable v,
|
||||
VariableAccess initAccess, Evidence e
|
||||
) {
|
||||
// Ignore whitelisted calls
|
||||
not call instanceof WhitelistedCall and
|
||||
f = getTarget(call) and
|
||||
initAccess = v.getAnAccess() and
|
||||
initAccess = call.getAConditionallyInitializedArgument(f, e).(AddressOfExpr).getOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable that can be conditionally initialized by a call.
|
||||
*/
|
||||
class ConditionallyInitializedVariable extends LocalVariable {
|
||||
ConditionalInitializationCall call;
|
||||
ConditionalInitializationFunction f;
|
||||
VariableAccess initAccess;
|
||||
Evidence e;
|
||||
|
||||
ConditionallyInitializedVariable() {
|
||||
// Find a call that conditionally initializes this variable
|
||||
hasConditionalInitialization(f, call, this, initAccess, e) and
|
||||
// Ignore cases where the variable is assigned prior to the call
|
||||
not reaches(getAnAssignedValue(), initAccess) and
|
||||
// Ignore cases where the variable is assigned field-wise prior to the call.
|
||||
not exists(FieldAccess fa |
|
||||
exists(Assignment a |
|
||||
fa = getAFieldAccess(this) and
|
||||
a.getLValue() = fa
|
||||
)
|
||||
|
|
||||
reaches(fa, initAccess)
|
||||
) and
|
||||
// Ignore cases where the variable is assigned by a prior call to an initialization function
|
||||
not exists(Call c |
|
||||
getAnAccess() = getAnInitializedArgument(c).(AddressOfExpr).getOperand() and
|
||||
reaches(c, initAccess)
|
||||
) and
|
||||
/*
|
||||
* Static local variables with constant initializers do not have the initializer expr as part of
|
||||
* the CFG, but should always be considered as initialized, so exclude them.
|
||||
*/
|
||||
|
||||
not exists(getInitializer().getExpr())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an access of the variable `v` which is not used as an lvalue, and not used as an argument
|
||||
* to an initialization function.
|
||||
*/
|
||||
private VariableAccess getAReadAccess() {
|
||||
result = this.getAnAccess() and
|
||||
// Not used as an lvalue
|
||||
not result = any(AssignExpr a).getLValue() and
|
||||
// Not passed to another initialization function
|
||||
not exists(Call c, int j | j = c.getTarget().(InitializationFunction).initializedParameter() |
|
||||
result = c.getArgument(j).(AddressOfExpr).getOperand()
|
||||
) and
|
||||
// Not a pointless read
|
||||
not result = any(ExprStmt es).getExpr()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a read access of variable `v` that occurs after the `initializingCall`.
|
||||
*/
|
||||
private VariableAccess getAReadAccessAfterCall(ConditionalInitializationCall initializingCall) {
|
||||
// Variable associated with this particular call
|
||||
call = initializingCall and
|
||||
// Access is a meaningful read access
|
||||
result = getAReadAccess() and
|
||||
// Which occurs after the call
|
||||
reaches(call, result) and
|
||||
/*
|
||||
* Ignore risky accesses which are arguments to calls which also include another parameter to
|
||||
* the original call. This is an attempt to eliminate results where the "status" can be checked
|
||||
* through another parameter that assigned as part of the original call.
|
||||
*/
|
||||
|
||||
not exists(Call c |
|
||||
c.getAnArgument() = result or
|
||||
c.getAnArgument().(AddressOfExpr).getOperand() = result
|
||||
|
|
||||
exists(LocalVariable lv |
|
||||
call.getAnArgument().(AddressOfExpr).getOperand() = lv.getAnAccess() and
|
||||
not lv = this
|
||||
|
|
||||
c.getAnArgument() = lv.getAnAccess()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an access to the variable that is risky because the variable may not be initialized after
|
||||
* the `call`, and the status of the call is never checked.
|
||||
*/
|
||||
VariableAccess getARiskyAccessWithNoStatusCheck(
|
||||
ConditionalInitializationFunction initializingFunction,
|
||||
ConditionalInitializationCall initializingCall, Evidence evidence
|
||||
) {
|
||||
// Variable associated with this particular call
|
||||
call = initializingCall and
|
||||
initializingFunction = f and
|
||||
e = evidence and
|
||||
result = getAReadAccessAfterCall(initializingCall) and
|
||||
(
|
||||
// Access is risky because status return code ignored completely
|
||||
call instanceof ExprInVoidContext
|
||||
or
|
||||
// Access is risky because status return code ignored completely
|
||||
exists(LocalVariable status | call = status.getAnAssignedValue() |
|
||||
not exists(status.getAnAccess())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an access to the variable that is risky because the variable may not be initialized after
|
||||
* the `call`, and the status of the call is only checked after the risky access.
|
||||
*/
|
||||
VariableAccess getARiskyAccessBeforeStatusCheck(
|
||||
ConditionalInitializationFunction initializingFunction,
|
||||
ConditionalInitializationCall initializingCall, Evidence evidence
|
||||
) {
|
||||
// Variable associated with this particular call
|
||||
call = initializingCall and
|
||||
initializingFunction = f and
|
||||
e = evidence and
|
||||
result = getAReadAccessAfterCall(initializingCall) and
|
||||
exists(LocalVariable status, Assignment a |
|
||||
a.getRValue() = call and
|
||||
call = status.getAnAssignedValue() and
|
||||
// There exists a check of the status code
|
||||
definitionUsePair(status, a, _) and
|
||||
// And the check of the status code does not occur before the risky access
|
||||
not exists(VariableAccess statusAccess |
|
||||
definitionUsePair(status, a, statusAccess) and
|
||||
reaches(statusAccess, result)
|
||||
) and
|
||||
// Ignore cases where the assignment to the status code is used directly
|
||||
a instanceof ExprInVoidContext and
|
||||
/*
|
||||
* Ignore risky accesses which are arguments to calls which also include the status code.
|
||||
* If both the risky value and status code are passed to a different function, that
|
||||
* function is responsible for checking the status code.
|
||||
*/
|
||||
|
||||
not exists(Call c |
|
||||
c.getAnArgument() = result or
|
||||
c.getAnArgument().(AddressOfExpr).getOperand() = result
|
||||
|
|
||||
definitionUsePair(status, a, c.getAnArgument())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an access to the variable that is risky because the variable may not be initialized after
|
||||
* the `call`.
|
||||
*/
|
||||
VariableAccess getARiskyAccess(
|
||||
ConditionalInitializationFunction initializingFunction,
|
||||
ConditionalInitializationCall initializingCall, Evidence evidence
|
||||
) {
|
||||
result = getARiskyAccessBeforeStatusCheck(initializingFunction, initializingCall, evidence) or
|
||||
result = getARiskyAccessWithNoStatusCheck(initializingFunction, initializingCall, evidence)
|
||||
}
|
||||
}
|
||||
@@ -190,11 +190,11 @@ private predicate windowsSystemInfo(FunctionCall source, Element use) {
|
||||
// void WINAPI GetSystemInfo(_Out_ LPSYSTEM_INFO lpSystemInfo);
|
||||
// void WINAPI GetNativeSystemInfo(_Out_ LPSYSTEM_INFO lpSystemInfo);
|
||||
(
|
||||
source.getTarget().hasName("GetVersionEx") or
|
||||
source.getTarget().hasName("GetVersionExA") or
|
||||
source.getTarget().hasName("GetVersionExW") or
|
||||
source.getTarget().hasName("GetSystemInfo") or
|
||||
source.getTarget().hasName("GetNativeSystemInfo")
|
||||
source.getTarget().hasGlobalName("GetVersionEx") or
|
||||
source.getTarget().hasGlobalName("GetVersionExA") or
|
||||
source.getTarget().hasGlobalName("GetVersionExW") or
|
||||
source.getTarget().hasGlobalName("GetSystemInfo") or
|
||||
source.getTarget().hasGlobalName("GetNativeSystemInfo")
|
||||
) and
|
||||
use = source.getArgument(0)
|
||||
}
|
||||
@@ -216,9 +216,9 @@ private predicate windowsFolderPath(FunctionCall source, Element use) {
|
||||
// _In_ BOOL fCreate
|
||||
// );
|
||||
(
|
||||
source.getTarget().hasName("SHGetSpecialFolderPath") or
|
||||
source.getTarget().hasName("SHGetSpecialFolderPathA") or
|
||||
source.getTarget().hasName("SHGetSpecialFolderPathW")
|
||||
source.getTarget().hasGlobalName("SHGetSpecialFolderPath") or
|
||||
source.getTarget().hasGlobalName("SHGetSpecialFolderPathA") or
|
||||
source.getTarget().hasGlobalName("SHGetSpecialFolderPathW")
|
||||
) and
|
||||
use = source.getArgument(1)
|
||||
or
|
||||
@@ -228,7 +228,7 @@ private predicate windowsFolderPath(FunctionCall source, Element use) {
|
||||
// _In_opt_ HANDLE hToken,
|
||||
// _Out_ PWSTR *ppszPath
|
||||
// );
|
||||
source.getTarget().hasName("SHGetKnownFolderPath") and
|
||||
source.getTarget().hasGlobalName("SHGetKnownFolderPath") and
|
||||
use = source.getArgument(3)
|
||||
or
|
||||
// HRESULT SHGetFolderPath(
|
||||
@@ -239,9 +239,9 @@ private predicate windowsFolderPath(FunctionCall source, Element use) {
|
||||
// _Out_ LPTSTR pszPath
|
||||
// );
|
||||
(
|
||||
source.getTarget().hasName("SHGetFolderPath") or
|
||||
source.getTarget().hasName("SHGetFolderPathA") or
|
||||
source.getTarget().hasName("SHGetFolderPathW")
|
||||
source.getTarget().hasGlobalName("SHGetFolderPath") or
|
||||
source.getTarget().hasGlobalName("SHGetFolderPathA") or
|
||||
source.getTarget().hasGlobalName("SHGetFolderPathW")
|
||||
) and
|
||||
use = source.getArgument(4)
|
||||
or
|
||||
@@ -254,9 +254,9 @@ private predicate windowsFolderPath(FunctionCall source, Element use) {
|
||||
// _Out_ LPTSTR pszPath
|
||||
// );
|
||||
(
|
||||
source.getTarget().hasName("SHGetFolderPathAndSubDir") or
|
||||
source.getTarget().hasName("SHGetFolderPathAndSubDirA") or
|
||||
source.getTarget().hasName("SHGetFolderPathAndSubDirW")
|
||||
source.getTarget().hasGlobalName("SHGetFolderPathAndSubDir") or
|
||||
source.getTarget().hasGlobalName("SHGetFolderPathAndSubDirA") or
|
||||
source.getTarget().hasGlobalName("SHGetFolderPathAndSubDirW")
|
||||
) and
|
||||
use = source.getArgument(5)
|
||||
}
|
||||
@@ -273,9 +273,9 @@ class WindowsFolderPath extends SystemData {
|
||||
|
||||
private predicate logonUser(FunctionCall source, VariableAccess use) {
|
||||
(
|
||||
source.getTarget().hasName("LogonUser") or
|
||||
source.getTarget().hasName("LogonUserW") or
|
||||
source.getTarget().hasName("LogonUserA")
|
||||
source.getTarget().hasGlobalName("LogonUser") or
|
||||
source.getTarget().hasGlobalName("LogonUserW") or
|
||||
source.getTarget().hasGlobalName("LogonUserA")
|
||||
) and
|
||||
use = source.getAnArgument()
|
||||
}
|
||||
@@ -297,9 +297,9 @@ private predicate regQuery(FunctionCall source, VariableAccess use) {
|
||||
// _Inout_opt_ PLONG lpcbValue
|
||||
// );
|
||||
(
|
||||
source.getTarget().hasName("RegQueryValue") or
|
||||
source.getTarget().hasName("RegQueryValueA") or
|
||||
source.getTarget().hasName("RegQueryValueW")
|
||||
source.getTarget().hasGlobalName("RegQueryValue") or
|
||||
source.getTarget().hasGlobalName("RegQueryValueA") or
|
||||
source.getTarget().hasGlobalName("RegQueryValueW")
|
||||
) and
|
||||
use = source.getArgument(2)
|
||||
or
|
||||
@@ -311,9 +311,9 @@ private predicate regQuery(FunctionCall source, VariableAccess use) {
|
||||
// _Inout_opt_ LPDWORD ldwTotsize
|
||||
// );
|
||||
(
|
||||
source.getTarget().hasName("RegQueryMultipleValues") or
|
||||
source.getTarget().hasName("RegQueryMultipleValuesA") or
|
||||
source.getTarget().hasName("RegQueryMultipleValuesW")
|
||||
source.getTarget().hasGlobalName("RegQueryMultipleValues") or
|
||||
source.getTarget().hasGlobalName("RegQueryMultipleValuesA") or
|
||||
source.getTarget().hasGlobalName("RegQueryMultipleValuesW")
|
||||
) and
|
||||
use = source.getArgument(3)
|
||||
or
|
||||
@@ -326,9 +326,9 @@ private predicate regQuery(FunctionCall source, VariableAccess use) {
|
||||
// _Inout_opt_ LPDWORD lpcbData
|
||||
// );
|
||||
(
|
||||
source.getTarget().hasName("RegQueryValueEx") or
|
||||
source.getTarget().hasName("RegQueryValueExA") or
|
||||
source.getTarget().hasName("RegQueryValueExW")
|
||||
source.getTarget().hasGlobalName("RegQueryValueEx") or
|
||||
source.getTarget().hasGlobalName("RegQueryValueExA") or
|
||||
source.getTarget().hasGlobalName("RegQueryValueExW")
|
||||
) and
|
||||
use = source.getArgument(4)
|
||||
or
|
||||
@@ -342,9 +342,9 @@ private predicate regQuery(FunctionCall source, VariableAccess use) {
|
||||
// _Inout_opt_ LPDWORD pcbData
|
||||
// );
|
||||
(
|
||||
source.getTarget().hasName("RegGetValue") or
|
||||
source.getTarget().hasName("RegGetValueA") or
|
||||
source.getTarget().hasName("RegGetValueW")
|
||||
source.getTarget().hasGlobalName("RegGetValue") or
|
||||
source.getTarget().hasGlobalName("RegGetValueA") or
|
||||
source.getTarget().hasGlobalName("RegGetValueW")
|
||||
) and
|
||||
use = source.getArgument(5)
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@ import cpp
|
||||
from FunctionCall call, Function target
|
||||
where
|
||||
call.getTarget() = target and
|
||||
target.hasGlobalName("gets")
|
||||
target.hasGlobalOrStdName("gets")
|
||||
select call, "gets does not guard against buffer overflow"
|
||||
|
||||
14
cpp/ql/src/codeql-suites/cpp-lgtm-full.qls
Normal file
14
cpp/ql/src/codeql-suites/cpp-lgtm-full.qls
Normal file
@@ -0,0 +1,14 @@
|
||||
- description: Standard LGTM queries for C/C++, including ones not displayed by default
|
||||
- qlpack: codeql-cpp
|
||||
- apply: lgtm-selectors.yml
|
||||
from: codeql-suite-helpers
|
||||
# These queries are infeasible to compute on large projects:
|
||||
- exclude:
|
||||
query path:
|
||||
- Security/CWE/CWE-497/ExposedSystemData.ql
|
||||
- Critical/DescriptorMayNotBeClosed.ql
|
||||
- Critical/DescriptorNeverClosed.ql
|
||||
- Critical/FileMayNotBeClosed.ql
|
||||
- Critical/FileNeverClosed.ql
|
||||
- Critical/MemoryMayNotBeFreed.ql
|
||||
- Critical/MemoryNeverFreed.ql
|
||||
4
cpp/ql/src/codeql-suites/cpp-lgtm.qls
Normal file
4
cpp/ql/src/codeql-suites/cpp-lgtm.qls
Normal file
@@ -0,0 +1,4 @@
|
||||
- description: Standard LGTM queries for C/C++
|
||||
- apply: codeql-suites/cpp-lgtm-full.qls
|
||||
- apply: lgtm-displayed-only.yml
|
||||
from: codeql-suite-helpers
|
||||
@@ -22,7 +22,7 @@ predicate acquireExpr(Expr acquire, string kind) {
|
||||
exists(FunctionCall fc, Function f, string name |
|
||||
fc = acquire and
|
||||
f = fc.getTarget() and
|
||||
f.hasGlobalName(name) and
|
||||
f.hasGlobalOrStdName(name) and
|
||||
(
|
||||
name = "fopen" and
|
||||
kind = "file"
|
||||
@@ -46,7 +46,7 @@ predicate releaseExpr(Expr release, Expr resource, string kind) {
|
||||
exists(FunctionCall fc, Function f, string name |
|
||||
fc = release and
|
||||
f = fc.getTarget() and
|
||||
f.hasGlobalName(name) and
|
||||
f.hasGlobalOrStdName(name) and
|
||||
(
|
||||
name = "fclose" and
|
||||
resource = fc.getArgument(0) and
|
||||
|
||||
@@ -51,7 +51,7 @@ class ReferenceCopyAssignmentOperator extends MemberFunction {
|
||||
|
||||
/**
|
||||
* A call to a function called swap. Note: could be a member,
|
||||
* std::swap or a function overloading std::swap (not in std::)
|
||||
* `std::swap` or a function overloading `std::swap` (not in `std::`)
|
||||
* so keep it simple
|
||||
*/
|
||||
FunctionCall getASwapCall() {
|
||||
|
||||
@@ -22,8 +22,8 @@ predicate containsArray(Type t) {
|
||||
or
|
||||
containsArray(t.getUnderlyingType()) and
|
||||
not exists(TypedefType allowed | allowed = t |
|
||||
allowed.hasGlobalName("jmp_buf") or
|
||||
allowed.hasGlobalName("va_list")
|
||||
allowed.hasGlobalOrStdName("jmp_buf") or
|
||||
allowed.hasGlobalOrStdName("va_list")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
/*
|
||||
* See More Effective C++ item 7.
|
||||
* Note: Meyers allows unary & to be overloaded but not comma
|
||||
* Note: Meyers allows unary `&` to be overloaded but not comma.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
@@ -15,11 +15,11 @@ import cpp
|
||||
/*
|
||||
* Interpretation and deviations:
|
||||
* - if the higher operator has precedence > arithmetic then it is fine
|
||||
* RATIONALE: exprs like f(), *x, &x are easily understood to bind tightly
|
||||
* RATIONALE: exprs like `f()`, `*x`, `&x` are easily understood to bind tightly
|
||||
* - if the higher operator is the RHS of an assign then it is fine
|
||||
* RATIONALE: cf. MISRA, too many cases excluded otherwise
|
||||
* - comparison operators can be mixed with arithmetic
|
||||
* RATIONALE: x==y+z is common and unambiguous
|
||||
* RATIONALE: `x==y+z` is common and unambiguous
|
||||
*/
|
||||
|
||||
predicate arithmeticPrecedence(int p) { p = 12 or p = 13 }
|
||||
|
||||
4
cpp/ql/src/qlpack.yml
Normal file
4
cpp/ql/src/qlpack.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
name: codeql-cpp
|
||||
version: 0.0.0
|
||||
dbscheme: semmlecode.cpp.dbscheme
|
||||
suites: codeql-suites
|
||||
@@ -605,15 +605,6 @@ class Class extends UserType {
|
||||
class_instantiation(underlyingElement(this), unresolveElement(c))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th template argument used to instantiate this class from a
|
||||
* class template. When called on a class template, this will return the
|
||||
* `i`th template parameter.
|
||||
*/
|
||||
override Type getTemplateArgument(int i) {
|
||||
class_template_argument(underlyingElement(this), i, unresolveElement(result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this class/struct is polymorphic (has a virtual function, or
|
||||
* inherits one).
|
||||
@@ -623,7 +614,7 @@ class Class extends UserType {
|
||||
}
|
||||
|
||||
override predicate involvesTemplateParameter() {
|
||||
getATemplateArgument().involvesTemplateParameter()
|
||||
getATemplateArgument().(Type).involvesTemplateParameter()
|
||||
}
|
||||
|
||||
/** Holds if this class, struct or union was declared 'final'. */
|
||||
|
||||
@@ -14,8 +14,12 @@ private import semmle.code.cpp.internal.QualifiedName as Q
|
||||
* ```
|
||||
* extern int myglobal;
|
||||
* ```
|
||||
* Each of these declarations is given its own distinct `DeclarationEntry`,
|
||||
* but they all share the same `Declaration`.
|
||||
* and defined in one:
|
||||
* ```
|
||||
* int myglobal;
|
||||
* ```
|
||||
* Each of these declarations (including the definition) is given its own
|
||||
* distinct `DeclarationEntry`, but they all share the same `Declaration`.
|
||||
*
|
||||
* Some derived class of `Declaration` do not have a corresponding
|
||||
* `DeclarationEntry`, because they always have a unique source location.
|
||||
@@ -119,6 +123,13 @@ abstract class Declaration extends Locatable, @declaration {
|
||||
/** Holds if this declaration has the given name in the global namespace. */
|
||||
predicate hasGlobalName(string name) { this.hasQualifiedName("", "", name) }
|
||||
|
||||
/** Holds if this declaration has the given name in the global namespace or the `std` namespace. */
|
||||
predicate hasGlobalOrStdName(string name) {
|
||||
this.hasGlobalName(name)
|
||||
or
|
||||
this.hasQualifiedName("std", "", name)
|
||||
}
|
||||
|
||||
/** Gets a specifier of this declaration. */
|
||||
abstract Specifier getASpecifier();
|
||||
|
||||
@@ -189,26 +200,99 @@ abstract class Declaration extends Locatable, @declaration {
|
||||
|
||||
/**
|
||||
* Gets a template argument used to instantiate this declaration from a template.
|
||||
* When called on a template, this will return a template parameter.
|
||||
* When called on a template, this will return a template parameter type for
|
||||
* both typed and non-typed parameters.
|
||||
*/
|
||||
final Type getATemplateArgument() { result = getTemplateArgument(_) }
|
||||
final Locatable getATemplateArgument() { result = getTemplateArgument(_) }
|
||||
|
||||
/**
|
||||
* Gets a template argument used to instantiate this declaration from a template.
|
||||
* When called on a template, this will return a non-typed template
|
||||
* parameter value.
|
||||
*/
|
||||
final Locatable getATemplateArgumentKind() { result = getTemplateArgumentKind(_) }
|
||||
|
||||
/**
|
||||
* Gets the `i`th template argument used to instantiate this declaration from a
|
||||
* template. When called on a template, this will return the `i`th template parameter.
|
||||
* template.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* `template<typename T, T X> class Foo;`
|
||||
*
|
||||
* Will have `getTemplateArgument(0)` return `T`, and
|
||||
* `getTemplateArgument(1)` return `X`.
|
||||
*
|
||||
* `Foo<int, 1> bar;`
|
||||
*
|
||||
* Will have `getTemplateArgument())` return `int`, and
|
||||
* `getTemplateArgument(1)` return `1`.
|
||||
*/
|
||||
Type getTemplateArgument(int index) { none() }
|
||||
final Locatable getTemplateArgument(int index) {
|
||||
if exists(getTemplateArgumentValue(index))
|
||||
then result = getTemplateArgumentValue(index)
|
||||
else result = getTemplateArgumentType(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th template argument value used to instantiate this declaration
|
||||
* from a template. When called on a template, this will return the `i`th template
|
||||
* parameter value if it exists.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* `template<typename T, T X> class Foo;`
|
||||
*
|
||||
* Will have `getTemplateArgumentKind(1)` return `T`, and no result for
|
||||
* `getTemplateArgumentKind(0)`.
|
||||
*
|
||||
* `Foo<int, 10> bar;
|
||||
*
|
||||
* Will have `getTemplateArgumentKind(1)` return `int`, and no result for
|
||||
* `getTemplateArgumentKind(0)`.
|
||||
*/
|
||||
final Locatable getTemplateArgumentKind(int index) {
|
||||
if exists(getTemplateArgumentValue(index))
|
||||
then result = getTemplateArgumentType(index)
|
||||
else none()
|
||||
}
|
||||
|
||||
/** Gets the number of template arguments for this declaration. */
|
||||
final int getNumberOfTemplateArguments() {
|
||||
result = count(int i | exists(getTemplateArgument(i)))
|
||||
}
|
||||
|
||||
private Type getTemplateArgumentType(int index) {
|
||||
class_template_argument(underlyingElement(this), index, unresolveElement(result))
|
||||
or
|
||||
function_template_argument(underlyingElement(this), index, unresolveElement(result))
|
||||
or
|
||||
variable_template_argument(underlyingElement(this), index, unresolveElement(result))
|
||||
}
|
||||
|
||||
private Expr getTemplateArgumentValue(int index) {
|
||||
class_template_argument_value(underlyingElement(this), index, unresolveElement(result))
|
||||
or
|
||||
function_template_argument_value(underlyingElement(this), index, unresolveElement(result))
|
||||
or
|
||||
variable_template_argument_value(underlyingElement(this), index, unresolveElement(result))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ declaration entry. See the comment above `Declaration` for an
|
||||
* explanation of the relationship between `Declaration` and
|
||||
* `DeclarationEntry`.
|
||||
* A C/C++ declaration entry. For example the following code contains five
|
||||
* declaration entries:
|
||||
* ```
|
||||
* extern int myGlobal;
|
||||
* int myVariable;
|
||||
* typedef char MyChar;
|
||||
* void myFunction();
|
||||
* void myFunction() {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* See the comment above `Declaration` for an explanation of the relationship
|
||||
* between `Declaration` and `DeclarationEntry`.
|
||||
*/
|
||||
abstract class DeclarationEntry extends Locatable {
|
||||
/** Gets a specifier associated with this declaration entry. */
|
||||
@@ -281,8 +365,19 @@ abstract class DeclarationEntry extends Locatable {
|
||||
* A declaration that can potentially have more C++ access rights than its
|
||||
* enclosing element. This comprises `Class` (they have access to their own
|
||||
* private members) along with other `UserType`s and `Function` (they can be
|
||||
* the target of `friend` declarations).
|
||||
* the target of `friend` declarations). For example `MyClass` and
|
||||
* `myFunction` in the following code:
|
||||
* ```
|
||||
* class MyClass
|
||||
* {
|
||||
* public:
|
||||
* ...
|
||||
* };
|
||||
*
|
||||
* void myFunction() {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* In the C++ standard (N4140 11.2), rules for access control revolve around
|
||||
* the informal phrase "_R_ occurs in a member or friend of class C", where
|
||||
* `AccessHolder` corresponds to this _R_.
|
||||
@@ -416,8 +511,19 @@ abstract class AccessHolder extends Declaration {
|
||||
/**
|
||||
* A declaration that very likely has more C++ access rights than its
|
||||
* enclosing element. This comprises `Class` (they have access to their own
|
||||
* private members) along with any target of a `friend` declaration.
|
||||
* private members) along with any target of a `friend` declaration. For
|
||||
* example `MyClass` and `friendFunction` in the following code:
|
||||
* ```
|
||||
* class MyClass
|
||||
* {
|
||||
* public:
|
||||
* friend void friendFunction();
|
||||
* };
|
||||
*
|
||||
* void friendFunction() {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* Most access rights are computed for `DirectAccessHolder` instead of
|
||||
* `AccessHolder` -- that's more efficient because there are fewer
|
||||
* `DirectAccessHolder`s. If a `DirectAccessHolder` contains an `AccessHolder`,
|
||||
|
||||
@@ -343,15 +343,6 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
|
||||
function_instantiation(underlyingElement(this), unresolveElement(f))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th template argument used to instantiate this function from a
|
||||
* function template. When called on a function template, this will return the
|
||||
* `i`th template parameter.
|
||||
*/
|
||||
override Type getTemplateArgument(int index) {
|
||||
function_template_argument(underlyingElement(this), index, unresolveElement(result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this function is defined in several files. This is illegal in
|
||||
* C (though possible in some C++ compilers), and likely indicates that
|
||||
@@ -434,7 +425,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function {
|
||||
// ... and likewise for destructors.
|
||||
this.(Destructor).getADestruction().mayBeGloballyImpure()
|
||||
else
|
||||
not exists(string name | this.hasGlobalName(name) |
|
||||
not exists(string name | this.hasGlobalOrStdName(name) |
|
||||
// Unless it's a function that we know is side-effect-free, it may
|
||||
// have side-effects.
|
||||
name = "strcmp" or
|
||||
|
||||
40
cpp/ql/src/semmle/code/cpp/NestedFields.qll
Normal file
40
cpp/ql/src/semmle/code/cpp/NestedFields.qll
Normal file
@@ -0,0 +1,40 @@
|
||||
import cpp
|
||||
|
||||
/**
|
||||
* Gets a `Field` that is within the given `Struct`, either directly or nested
|
||||
* inside one or more levels of member structs.
|
||||
*/
|
||||
private Field getANestedField(Struct s) {
|
||||
result = s.getAField()
|
||||
or
|
||||
exists(NestedStruct ns |
|
||||
s = ns.getDeclaringType() and
|
||||
result = getANestedField(ns)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps a series of field accesses to determine the outer-most qualifier.
|
||||
*/
|
||||
private Expr getUltimateQualifier(FieldAccess fa) {
|
||||
exists(Expr qualifier | qualifier = fa.getQualifier() |
|
||||
result = getUltimateQualifier(qualifier)
|
||||
or
|
||||
not qualifier instanceof FieldAccess and result = qualifier
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Accesses to nested fields.
|
||||
*/
|
||||
class NestedFieldAccess extends FieldAccess {
|
||||
Expr ultimateQualifier;
|
||||
|
||||
NestedFieldAccess() {
|
||||
ultimateQualifier = getUltimateQualifier(this) and
|
||||
getTarget() = getANestedField(ultimateQualifier.getType().stripType())
|
||||
}
|
||||
|
||||
/** Gets the ultimate qualifier of this nested field access. */
|
||||
Expr getUltimateQualifier() { result = ultimateQualifier }
|
||||
}
|
||||
@@ -158,3 +158,10 @@ class Parameter extends LocalScopeVariable, @parameter {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `int` that is a parameter index for some function. This is needed for binding in certain cases.
|
||||
*/
|
||||
class ParameterIndex extends int {
|
||||
ParameterIndex() { exists(Parameter p | this = p.getIndex()) }
|
||||
}
|
||||
|
||||
@@ -35,6 +35,14 @@ private string getParameterTypeString(Type parameterType) {
|
||||
else result = parameterType.(DumpType).getTypeIdentityString()
|
||||
}
|
||||
|
||||
private string getTemplateArgumentString(Declaration d, int i) {
|
||||
if exists(d.getTemplateArgumentKind(i))
|
||||
then
|
||||
result = d.getTemplateArgumentKind(i).(DumpType).getTypeIdentityString() + " " +
|
||||
d.getTemplateArgument(i)
|
||||
else result = d.getTemplateArgument(i).(DumpType).getTypeIdentityString()
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Declaration` extended to add methods for generating strings useful only for dumps and debugging.
|
||||
*/
|
||||
@@ -56,7 +64,7 @@ abstract private class DumpDeclaration extends Declaration {
|
||||
strictconcat(int i |
|
||||
exists(this.getTemplateArgument(i))
|
||||
|
|
||||
this.getTemplateArgument(i).(DumpType).getTypeIdentityString(), ", " order by i
|
||||
getTemplateArgumentString(this, i), ", " order by i
|
||||
) + ">"
|
||||
else result = ""
|
||||
}
|
||||
|
||||
@@ -7,3 +7,15 @@
|
||||
|
||||
import cpp
|
||||
import PrintAST
|
||||
|
||||
/**
|
||||
* Temporarily tweak this class or make a copy to control which functions are
|
||||
* printed.
|
||||
*/
|
||||
class Cfg extends PrintASTConfiguration {
|
||||
/**
|
||||
* TWEAK THIS PREDICATE AS NEEDED.
|
||||
* Holds if the AST for `func` should be printed.
|
||||
*/
|
||||
override predicate shouldPrintFunction(Function func) { any() }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ private import semmle.code.cpp.internal.ResolveClass
|
||||
|
||||
/**
|
||||
* A C/C++ type.
|
||||
*
|
||||
* This QL class represents the root of the C/C++ type hierarchy.
|
||||
*/
|
||||
class Type extends Locatable, @type {
|
||||
Type() { isType(underlyingElement(this)) }
|
||||
@@ -210,7 +212,7 @@ class Type extends Locatable, @type {
|
||||
// A function call that provides an explicit template argument that refers to T uses T.
|
||||
// We exclude calls within instantiations, since they do not appear directly in the source.
|
||||
exists(FunctionCall c |
|
||||
c.getAnExplicitTemplateArgument().refersTo(this) and
|
||||
c.getAnExplicitTemplateArgument().(Type).refersTo(this) and
|
||||
result = c and
|
||||
not c.getEnclosingFunction().isConstructedFrom(_)
|
||||
)
|
||||
@@ -289,6 +291,13 @@ class Type extends Locatable, @type {
|
||||
|
||||
/**
|
||||
* A C/C++ built-in primitive type (int, float, void, and so on). See 4.1.1.
|
||||
* In the following example, `unsigned int` and `double` denote primitive
|
||||
* built-in types:
|
||||
* ```
|
||||
* double a;
|
||||
* unsigned int ua[40];
|
||||
* typedef double LargeFloat;
|
||||
* ```
|
||||
*/
|
||||
class BuiltInType extends Type, @builtintype {
|
||||
override string toString() { result = this.getName() }
|
||||
@@ -301,7 +310,14 @@ class BuiltInType extends Type, @builtintype {
|
||||
}
|
||||
|
||||
/**
|
||||
* An erroneous type.
|
||||
* An erroneous type. This type has no corresponding C/C++ syntax.
|
||||
*
|
||||
* `ErroneousType` is the type of `ErrorExpr`, which in turn refers to an illegal
|
||||
* language construct. In the example below, a temporary (`0`) cannot be bound
|
||||
* to an lvalue reference (`int &`):
|
||||
* ```
|
||||
* int &intref = 0;
|
||||
* ```
|
||||
*/
|
||||
class ErroneousType extends BuiltInType {
|
||||
ErroneousType() { builtintypes(underlyingElement(this), _, 1, _, _, _) }
|
||||
@@ -310,7 +326,18 @@ class ErroneousType extends BuiltInType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The unknown type.
|
||||
* The unknown type. This type has no corresponding C/C++ syntax.
|
||||
*
|
||||
* Unknown types usually occur inside _uninstantiated_ template functions.
|
||||
* In the example below, the expressions `x.a` and `x.b` have unknown type
|
||||
* in the _uninstantiated_ template.
|
||||
* ```
|
||||
* template<typename T>
|
||||
* bool check(T x) {
|
||||
* if (x.a == x.b)
|
||||
* abort();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class UnknownType extends BuiltInType {
|
||||
UnknownType() { builtintypes(underlyingElement(this), _, 2, _, _, _) }
|
||||
@@ -326,6 +353,10 @@ private predicate isArithmeticType(@builtintype type, int kind) {
|
||||
|
||||
/**
|
||||
* The C/C++ arithmetic types. See 4.1.1.
|
||||
*
|
||||
* This includes primitive types on which arithmetic, bitwise or logical
|
||||
* operations may be performed. Examples of arithmetic types include
|
||||
* `char`, `int`, `float`, and `bool`.
|
||||
*/
|
||||
class ArithmeticType extends BuiltInType {
|
||||
ArithmeticType() { isArithmeticType(underlyingElement(this), _) }
|
||||
@@ -349,11 +380,20 @@ private predicate isIntegralType(@builtintype type, int kind) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ integral or enum type.
|
||||
* The definition of "integral type" in the C++ Standard excludes enum types,
|
||||
* but because an enum type holds a value of its underlying integral type,
|
||||
* A C/C++ integral or `enum` type.
|
||||
*
|
||||
* The definition of "integral type" in the C++ standard excludes `enum` types,
|
||||
* but because an `enum` type holds a value of its underlying integral type,
|
||||
* it is often useful to have a common category that includes both integral
|
||||
* and enum types.
|
||||
* and `enum` types.
|
||||
*
|
||||
* In the following example, `a`, `b` and `c` are all declared with an
|
||||
* integral or `enum` type:
|
||||
* ```
|
||||
* unsigned long a;
|
||||
* enum e1 { val1, val2 } b;
|
||||
* enum class e2: short { val3, val4 } c;
|
||||
* ```
|
||||
*/
|
||||
class IntegralOrEnumType extends Type {
|
||||
IntegralOrEnumType() {
|
||||
@@ -426,7 +466,17 @@ private predicate integralTypeMapping(int original, int canonical, int unsigned,
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ integral types. See 4.1.1.
|
||||
* The C/C++ integral types. See 4.1.1. These are types that are represented
|
||||
* as integers of varying sizes. Both `enum` types and floating-point types
|
||||
* are excluded.
|
||||
*
|
||||
* In the following examples, `a`, `b` and `c` are declared using integral
|
||||
* types:
|
||||
* ```
|
||||
* unsigned int a;
|
||||
* long long b;
|
||||
* char c;
|
||||
* ```
|
||||
*/
|
||||
class IntegralType extends ArithmeticType, IntegralOrEnumType {
|
||||
int kind;
|
||||
@@ -497,7 +547,12 @@ class IntegralType extends ArithmeticType, IntegralOrEnumType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ boolean type. See 4.2.
|
||||
* The C/C++ boolean type. See 4.2. This is the C `_Bool` type
|
||||
* or the C++ `bool` type. For example:
|
||||
* ```
|
||||
* extern bool a, b; // C++
|
||||
* _Bool c, d; // C
|
||||
* ```
|
||||
*/
|
||||
class BoolType extends IntegralType {
|
||||
BoolType() { builtintypes(underlyingElement(this), _, 4, _, _, _) }
|
||||
@@ -506,12 +561,23 @@ class BoolType extends IntegralType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ character types. See 4.3.
|
||||
* The C/C++ character types. See 4.3. This includes the `char`,
|
||||
* `signed char` and `unsigned char` types, all of which are
|
||||
* distinct from one another. For example:
|
||||
* ```
|
||||
* char a, b;
|
||||
* signed char c, d;
|
||||
* unsigned char e, f;
|
||||
* ```
|
||||
*/
|
||||
abstract class CharType extends IntegralType { }
|
||||
|
||||
/**
|
||||
* The C/C++ char type (which is different to signed char and unsigned char).
|
||||
* The C/C++ `char` type (which is distinct from `signed char` and
|
||||
* `unsigned char`). For example:
|
||||
* ```
|
||||
* char a, b;
|
||||
* ```
|
||||
*/
|
||||
class PlainCharType extends CharType {
|
||||
PlainCharType() { builtintypes(underlyingElement(this), _, 5, _, _, _) }
|
||||
@@ -520,7 +586,11 @@ class PlainCharType extends CharType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ unsigned char type (which is different to plain char, even when chars are unsigned by default).
|
||||
* The C/C++ `unsigned char` type (which is distinct from plain `char`
|
||||
* even when `char` is `unsigned` by default).
|
||||
* ```
|
||||
* unsigned char e, f;
|
||||
* ```
|
||||
*/
|
||||
class UnsignedCharType extends CharType {
|
||||
UnsignedCharType() { builtintypes(underlyingElement(this), _, 6, _, _, _) }
|
||||
@@ -529,7 +599,11 @@ class UnsignedCharType extends CharType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ signed char type (which is different to plain char, even when chars are signed by default).
|
||||
* The C/C++ `signed char` type (which is distinct from plain `char`
|
||||
* even when `char` is `signed` by default).
|
||||
* ```
|
||||
* signed char c, d;
|
||||
* ```
|
||||
*/
|
||||
class SignedCharType extends CharType {
|
||||
SignedCharType() { builtintypes(underlyingElement(this), _, 7, _, _, _) }
|
||||
@@ -538,7 +612,11 @@ class SignedCharType extends CharType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ short types. See 4.3.
|
||||
* The C/C++ short types. See 4.3. This includes `short`, `signed short`
|
||||
* and `unsigned short`.
|
||||
* ```
|
||||
* signed short ss;
|
||||
* ```
|
||||
*/
|
||||
class ShortType extends IntegralType {
|
||||
ShortType() {
|
||||
@@ -551,7 +629,11 @@ class ShortType extends IntegralType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ integer types. See 4.4.
|
||||
* The C/C++ integer types. See 4.4. This includes `int`, `signed int`
|
||||
* and `unsigned int`.
|
||||
* ```
|
||||
* unsigned int ui;
|
||||
* ```
|
||||
*/
|
||||
class IntType extends IntegralType {
|
||||
IntType() {
|
||||
@@ -564,7 +646,11 @@ class IntType extends IntegralType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ long types. See 4.4.
|
||||
* The C/C++ long types. See 4.4. This includes `long`, `signed long`
|
||||
* and `unsigned long`.
|
||||
* ```
|
||||
* long l;
|
||||
* ```
|
||||
*/
|
||||
class LongType extends IntegralType {
|
||||
LongType() {
|
||||
@@ -577,7 +663,11 @@ class LongType extends IntegralType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ long long types. See 4.4.
|
||||
* The C/C++ long long types. See 4.4. This includes `long long`, `signed long long`
|
||||
* and `unsigned long long`.
|
||||
* ```
|
||||
* signed long long sll;
|
||||
* ```
|
||||
*/
|
||||
class LongLongType extends IntegralType {
|
||||
LongLongType() {
|
||||
@@ -590,7 +680,12 @@ class LongLongType extends IntegralType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The GNU C __int128 types.
|
||||
* The GNU C __int128 primitive types. They are not part of standard C/C++.
|
||||
*
|
||||
* This includes `__int128`, `signed __int128` and `unsigned __int128`.
|
||||
* ```
|
||||
* unsigned __int128 ui128;
|
||||
* ```
|
||||
*/
|
||||
class Int128Type extends IntegralType {
|
||||
Int128Type() {
|
||||
@@ -598,10 +693,18 @@ class Int128Type extends IntegralType {
|
||||
builtintypes(underlyingElement(this), _, 36, _, _, _) or
|
||||
builtintypes(underlyingElement(this), _, 37, _, _, _)
|
||||
}
|
||||
|
||||
override string getCanonicalQLClass() { result = "Int128Type" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ floating point types. See 4.5.
|
||||
* The C/C++ floating point types. See 4.5. This includes `float`,
|
||||
* `double` and `long double` types.
|
||||
* ```
|
||||
* float f;
|
||||
* double d;
|
||||
* long double ld;
|
||||
* ```
|
||||
*/
|
||||
class FloatingPointType extends ArithmeticType {
|
||||
FloatingPointType() {
|
||||
@@ -610,14 +713,19 @@ class FloatingPointType extends ArithmeticType {
|
||||
(
|
||||
kind >= 24 and kind <= 32
|
||||
or
|
||||
kind = 38
|
||||
kind >= 38 and kind <= 42
|
||||
or
|
||||
kind >= 45 and kind <= 50
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ float type.
|
||||
* The C/C++ `float` type.
|
||||
* ```
|
||||
* float f;
|
||||
* ```
|
||||
*/
|
||||
class FloatType extends FloatingPointType {
|
||||
FloatType() { builtintypes(underlyingElement(this), _, 24, _, _, _) }
|
||||
@@ -626,7 +734,10 @@ class FloatType extends FloatingPointType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ double type.
|
||||
* The C/C++ `double` type.
|
||||
* ```
|
||||
* double d;
|
||||
* ```
|
||||
*/
|
||||
class DoubleType extends FloatingPointType {
|
||||
DoubleType() { builtintypes(underlyingElement(this), _, 25, _, _, _) }
|
||||
@@ -635,7 +746,10 @@ class DoubleType extends FloatingPointType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ long double type.
|
||||
* The C/C++ `long double` type.
|
||||
* ```
|
||||
* long double ld;
|
||||
* ```
|
||||
*/
|
||||
class LongDoubleType extends FloatingPointType {
|
||||
LongDoubleType() { builtintypes(underlyingElement(this), _, 26, _, _, _) }
|
||||
@@ -644,35 +758,58 @@ class LongDoubleType extends FloatingPointType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The GNU C __float128 type.
|
||||
* The GNU C `__float128` primitive type. This is not standard C/C++.
|
||||
* ```
|
||||
* __float128 f128;
|
||||
* ```
|
||||
*/
|
||||
class Float128Type extends FloatingPointType {
|
||||
Float128Type() { builtintypes(underlyingElement(this), _, 38, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Float128Type" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The GNU C _Decimal32 type.
|
||||
* The GNU C `_Decimal32` primitive type. This is not standard C/C++.
|
||||
* ```
|
||||
* _Decimal32 d32;
|
||||
* ```
|
||||
*/
|
||||
class Decimal32Type extends FloatingPointType {
|
||||
Decimal32Type() { builtintypes(underlyingElement(this), _, 40, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Decimal32Type" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The GNU C _Decimal64 type.
|
||||
* The GNU C `_Decimal64` primitive type. This is not standard C/C++.
|
||||
* ```
|
||||
* _Decimal64 d64;
|
||||
* ```
|
||||
*/
|
||||
class Decimal64Type extends FloatingPointType {
|
||||
Decimal64Type() { builtintypes(underlyingElement(this), _, 41, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Decimal64Type" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The GNU C _Decimal128 type.
|
||||
* The GNU C `_Decimal128` primitive type. This is not standard C/C++.
|
||||
* ```
|
||||
* _Decimal128 d128;
|
||||
* ```
|
||||
*/
|
||||
class Decimal128Type extends FloatingPointType {
|
||||
Decimal128Type() { builtintypes(underlyingElement(this), _, 42, _, _, _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Decimal128Type" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ void type. See 4.7.
|
||||
* The C/C++ `void` type. See 4.7.
|
||||
* ```
|
||||
* void foo();
|
||||
* ```
|
||||
*/
|
||||
class VoidType extends BuiltInType {
|
||||
VoidType() { builtintypes(underlyingElement(this), _, 3, _, _, _) }
|
||||
@@ -686,6 +823,9 @@ class VoidType extends BuiltInType {
|
||||
* Note that on some platforms `wchar_t` doesn't exist as a built-in
|
||||
* type but a typedef is provided. Consider using the `Wchar_t` QL
|
||||
* class to include these types.
|
||||
* ```
|
||||
* wchar_t wc;
|
||||
* ```
|
||||
*/
|
||||
class WideCharType extends IntegralType {
|
||||
WideCharType() { builtintypes(underlyingElement(this), _, 33, _, _, _) }
|
||||
@@ -694,7 +834,10 @@ class WideCharType extends IntegralType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ `char16_t` type.
|
||||
* The C/C++ `char16_t` type. This is available starting with C11 and C++11.
|
||||
* ```
|
||||
* char16_t c16;
|
||||
* ```
|
||||
*/
|
||||
class Char16Type extends IntegralType {
|
||||
Char16Type() { builtintypes(underlyingElement(this), _, 43, _, _, _) }
|
||||
@@ -703,7 +846,10 @@ class Char16Type extends IntegralType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The C/C++ `char32_t` type.
|
||||
* The C/C++ `char32_t` type. This is available starting with C11 and C++11.
|
||||
* ```
|
||||
* char32_t c32;
|
||||
* ```
|
||||
*/
|
||||
class Char32Type extends IntegralType {
|
||||
Char32Type() { builtintypes(underlyingElement(this), _, 44, _, _, _) }
|
||||
@@ -712,13 +858,13 @@ class Char32Type extends IntegralType {
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the C++11 nullptr constant.
|
||||
*
|
||||
* Note that this is not `nullptr_t`, as `nullptr_t` is defined as:
|
||||
* The (primitive) type of the C++11 `nullptr` constant. It is a
|
||||
* distinct type, denoted by `decltype(nullptr)`, that is not itself a pointer
|
||||
* type or a pointer to member type. The `<cstddef>` header usually defines
|
||||
* the `std::nullptr_t` type as follows:
|
||||
* ```
|
||||
* typedef decltype(nullptr) nullptr_t;
|
||||
* typedef decltype(nullptr) nullptr_t;
|
||||
* ```
|
||||
* Instead, this is the unspeakable type given by `decltype(nullptr)`.
|
||||
*/
|
||||
class NullPointerType extends BuiltInType {
|
||||
NullPointerType() { builtintypes(underlyingElement(this), _, 34, _, _, _) }
|
||||
@@ -729,8 +875,13 @@ class NullPointerType extends BuiltInType {
|
||||
/**
|
||||
* A C/C++ derived type.
|
||||
*
|
||||
* These are pointer and reference types, array and vector types, and const and volatile types.
|
||||
* In all cases, the type is formed from a single base type.
|
||||
* These are pointer and reference types, array and GNU vector types, and `const` and `volatile` types.
|
||||
* In all cases, the type is formed from a single base type. For example:
|
||||
* ```
|
||||
* int *pi;
|
||||
* int &ri = *pi;
|
||||
* const float fa[40];
|
||||
* ```
|
||||
*/
|
||||
class DerivedType extends Type, @derivedtype {
|
||||
override string toString() { result = this.getName() }
|
||||
@@ -775,9 +926,15 @@ class DerivedType extends Type, @derivedtype {
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of the C++11 decltype operator.
|
||||
* An instance of the C++11 `decltype` operator. For example:
|
||||
* ```
|
||||
* int a;
|
||||
* decltype(a) b;
|
||||
* ```
|
||||
*/
|
||||
class Decltype extends Type, @decltype {
|
||||
override string getCanonicalQLClass() { result = "Decltype" }
|
||||
|
||||
/**
|
||||
* The expression whose type is being obtained by this decltype.
|
||||
*/
|
||||
@@ -788,17 +945,17 @@ class Decltype extends Type, @decltype {
|
||||
*/
|
||||
Type getBaseType() { decltypes(underlyingElement(this), _, unresolveElement(result), _) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "Decltype" }
|
||||
|
||||
/**
|
||||
* Whether an extra pair of parentheses around the expression would change the semantics of this decltype.
|
||||
*
|
||||
* The following example shows the effect of an extra pair of parentheses:
|
||||
* struct A { double x; };
|
||||
* const A* a = new A();
|
||||
* decltype( a->x ); // type is double
|
||||
* decltype((a->x)); // type is const double&
|
||||
* Consult the C++11 standard for more details.
|
||||
* ```
|
||||
* struct A { double x; };
|
||||
* const A* a = new A();
|
||||
* decltype( a->x ); // type is double
|
||||
* decltype((a->x)); // type is const double&
|
||||
* ```
|
||||
* Please consult the C++11 standard for more details.
|
||||
*/
|
||||
predicate parenthesesWouldChangeMeaning() { decltypes(underlyingElement(this), _, _, true) }
|
||||
|
||||
@@ -841,6 +998,10 @@ class Decltype extends Type, @decltype {
|
||||
|
||||
/**
|
||||
* A C/C++ pointer type. See 4.9.1.
|
||||
* ```
|
||||
* void *ptr;
|
||||
* void **ptr2 = &ptr;
|
||||
* ```
|
||||
*/
|
||||
class PointerType extends DerivedType {
|
||||
PointerType() { derivedtypes(underlyingElement(this), _, 1, _) }
|
||||
@@ -863,8 +1024,8 @@ class PointerType extends DerivedType {
|
||||
/**
|
||||
* A C++ reference type. See 4.9.1.
|
||||
*
|
||||
* For C++11 code bases, this includes both lvalue references (&) and rvalue references (&&).
|
||||
* To distinguish between them, use the LValueReferenceType and RValueReferenceType classes.
|
||||
* For C++11 code bases, this includes both _lvalue_ references (`&`) and _rvalue_ references (`&&`).
|
||||
* To distinguish between them, use the LValueReferenceType and RValueReferenceType QL classes.
|
||||
*/
|
||||
class ReferenceType extends DerivedType {
|
||||
ReferenceType() {
|
||||
@@ -889,7 +1050,11 @@ class ReferenceType extends DerivedType {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C++11 lvalue reference type (e.g. int&).
|
||||
* A C++11 lvalue reference type (e.g. `int &`).
|
||||
* ```
|
||||
* int a;
|
||||
* int& b = a;
|
||||
* ```
|
||||
*/
|
||||
class LValueReferenceType extends ReferenceType {
|
||||
LValueReferenceType() { derivedtypes(underlyingElement(this), _, 2, _) }
|
||||
@@ -898,7 +1063,14 @@ class LValueReferenceType extends ReferenceType {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C++11 rvalue reference type (e.g. int&&).
|
||||
* A C++11 rvalue reference type (e.g., `int &&`). It is used to
|
||||
* implement "move" semantics for object construction and assignment.
|
||||
* ```
|
||||
* class C {
|
||||
* E e;
|
||||
* C(C&& from): e(std::move(from.e)) { }
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
class RValueReferenceType extends ReferenceType {
|
||||
RValueReferenceType() { derivedtypes(underlyingElement(this), _, 8, _) }
|
||||
@@ -910,6 +1082,10 @@ class RValueReferenceType extends ReferenceType {
|
||||
|
||||
/**
|
||||
* A type with specifiers.
|
||||
* ```
|
||||
* const int a;
|
||||
* volatile char v;
|
||||
* ```
|
||||
*/
|
||||
class SpecifiedType extends DerivedType {
|
||||
SpecifiedType() { derivedtypes(underlyingElement(this), _, 3, _) }
|
||||
@@ -955,6 +1131,9 @@ class SpecifiedType extends DerivedType {
|
||||
|
||||
/**
|
||||
* A C/C++ array type. See 4.9.1.
|
||||
* ```
|
||||
* char table[32];
|
||||
* ```
|
||||
*/
|
||||
class ArrayType extends DerivedType {
|
||||
ArrayType() { derivedtypes(underlyingElement(this), _, 4, _) }
|
||||
@@ -1001,10 +1180,16 @@ class ArrayType extends DerivedType {
|
||||
* A GNU/Clang vector type.
|
||||
*
|
||||
* In both Clang and GNU compilers, vector types can be introduced using the
|
||||
* __attribute__((vector_size(byte_size))) syntax. The Clang compiler also
|
||||
* allows vector types to be introduced using the ext_vector_type,
|
||||
* neon_vector_type, and neon_polyvector_type attributes (all of which take
|
||||
* an element type rather than a byte size).
|
||||
* `__attribute__((vector_size(byte_size)))` syntax. The Clang compiler also
|
||||
* allows vector types to be introduced using the `ext_vector_type`,
|
||||
* `neon_vector_type`, and `neon_polyvector_type` attributes (all of which take
|
||||
* an element count rather than a byte size).
|
||||
*
|
||||
* In the example below, both `v4si` and `float4` are GNU vector types:
|
||||
* ```
|
||||
* typedef int v4si __attribute__ (( vector_size(4*sizeof(int)) ));
|
||||
* typedef float float4 __attribute__((ext_vector_type(4)));
|
||||
* ```
|
||||
*/
|
||||
class GNUVectorType extends DerivedType {
|
||||
GNUVectorType() { derivedtypes(underlyingElement(this), _, 5, _) }
|
||||
@@ -1043,7 +1228,10 @@ class GNUVectorType extends DerivedType {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ pointer to function. See 7.7.
|
||||
* A C/C++ pointer to a function. See 7.7.
|
||||
* ```
|
||||
* int(* pointer)(const void *element1, const void *element2);
|
||||
* ```
|
||||
*/
|
||||
class FunctionPointerType extends FunctionPointerIshType {
|
||||
FunctionPointerType() { derivedtypes(underlyingElement(this), _, 6, _) }
|
||||
@@ -1058,7 +1246,10 @@ class FunctionPointerType extends FunctionPointerIshType {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ reference to function.
|
||||
* A C++ reference to a function.
|
||||
* ```
|
||||
* int(& reference)(const void *element1, const void *element2);
|
||||
* ```
|
||||
*/
|
||||
class FunctionReferenceType extends FunctionPointerIshType {
|
||||
FunctionReferenceType() { derivedtypes(underlyingElement(this), _, 7, _) }
|
||||
@@ -1073,10 +1264,14 @@ class FunctionReferenceType extends FunctionPointerIshType {
|
||||
}
|
||||
|
||||
/**
|
||||
* A block type, for example int(^)(char, float).
|
||||
* A block type, for example, `int(^)(char, float)`.
|
||||
*
|
||||
* Block types (along with blocks themselves) are a language extension
|
||||
* supported by Clang, and by Apple's branch of GCC.
|
||||
* ```
|
||||
* int(^ block)(const char *element1, const char *element2)
|
||||
* = ^int (const char *element1, const char *element2) { return element1 - element 2; }
|
||||
* ```
|
||||
*/
|
||||
class BlockType extends FunctionPointerIshType {
|
||||
BlockType() { derivedtypes(underlyingElement(this), _, 10, _) }
|
||||
@@ -1089,7 +1284,9 @@ class BlockType extends FunctionPointerIshType {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ pointer to function, or a block.
|
||||
* A C/C++ pointer to a function, a C++ function reference, or a clang/Apple block.
|
||||
*
|
||||
* See `FunctionPointerType`, `FunctionReferenceType` and `BlockType` for more information.
|
||||
*/
|
||||
class FunctionPointerIshType extends DerivedType {
|
||||
FunctionPointerIshType() {
|
||||
@@ -1134,7 +1331,13 @@ class FunctionPointerIshType extends DerivedType {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C++ pointer to member. See 15.5.
|
||||
* A C++ pointer to data member. See 15.5.
|
||||
* ```
|
||||
* class C { int m; };
|
||||
* int C::* p = &C::m; // pointer to data member m of class C
|
||||
* class C *;
|
||||
* int val = c.*p; // access data member
|
||||
* ```
|
||||
*/
|
||||
class PointerToMemberType extends Type, @ptrtomember {
|
||||
/** a printable representation of this named element */
|
||||
@@ -1171,7 +1374,14 @@ class PointerToMemberType extends Type, @ptrtomember {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ routine type. This is what results from stripping away the pointer from a function pointer type.
|
||||
* A C/C++ routine type. Conceptually, this is what results from stripping
|
||||
* away the pointer from a function pointer type. It can also occur in C++
|
||||
* code, for example the base type of `myRoutineType` in the following code:
|
||||
* ```
|
||||
* using myRoutineType = int(int);
|
||||
*
|
||||
* myRoutineType *fp = 0;
|
||||
* ```
|
||||
*/
|
||||
class RoutineType extends Type, @routinetype {
|
||||
/** a printable representation of this named element */
|
||||
@@ -1231,7 +1441,13 @@ class RoutineType extends Type, @routinetype {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C++ typename template parameter.
|
||||
* A C++ `typename` (or `class`) template parameter.
|
||||
*
|
||||
* In the example below, `T` is a template parameter:
|
||||
* ```
|
||||
* template <class T>
|
||||
* class C { };
|
||||
* ```
|
||||
*/
|
||||
class TemplateParameter extends UserType {
|
||||
TemplateParameter() {
|
||||
@@ -1243,7 +1459,16 @@ class TemplateParameter extends UserType {
|
||||
override predicate involvesTemplateParameter() { any() }
|
||||
}
|
||||
|
||||
/** A C++ template template parameter, e.g. template <template <typename,typename> class T>. */
|
||||
/**
|
||||
* A C++ template template parameter.
|
||||
*
|
||||
* In the example below, `T` is a template template parameter (although its name
|
||||
* may be omitted):
|
||||
* ```
|
||||
* template <template <typename T> class Container, class Elem>
|
||||
* void foo(const Container<Elem> &value) { }
|
||||
* ```
|
||||
*/
|
||||
class TemplateTemplateParameter extends TemplateParameter {
|
||||
TemplateTemplateParameter() { usertypes(underlyingElement(this), _, 8) }
|
||||
|
||||
@@ -1251,7 +1476,10 @@ class TemplateTemplateParameter extends TemplateParameter {
|
||||
}
|
||||
|
||||
/**
|
||||
* A type representing the use of the C++11 auto keyword.
|
||||
* A type representing the use of the C++11 `auto` keyword.
|
||||
* ```
|
||||
* auto val = some_typed_expr();
|
||||
* ```
|
||||
*/
|
||||
class AutoType extends TemplateParameter {
|
||||
AutoType() { usertypes(underlyingElement(this), "auto", 7) }
|
||||
|
||||
@@ -2,12 +2,17 @@ import semmle.code.cpp.Type
|
||||
private import semmle.code.cpp.internal.ResolveClass
|
||||
|
||||
/**
|
||||
* A C/C++ typedef type. See 4.9.1.
|
||||
* A C/C++ typedef type. See 4.9.1. For example the types declared on each line of the following code:
|
||||
* ```
|
||||
* typedef int my_int;
|
||||
* using my_int2 = int;
|
||||
* ```
|
||||
*/
|
||||
class TypedefType extends UserType {
|
||||
TypedefType() { usertypes(underlyingElement(this), _, 5) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "TypedefType" }
|
||||
TypedefType() {
|
||||
usertypes(underlyingElement(this), _, 5) or
|
||||
usertypes(underlyingElement(this), _, 14)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base type of this typedef type.
|
||||
@@ -26,10 +31,6 @@ class TypedefType extends UserType {
|
||||
result = this.getBaseType().getPointerIndirectionLevel()
|
||||
}
|
||||
|
||||
override string explain() {
|
||||
result = "typedef {" + this.getBaseType().explain() + "} as \"" + this.getName() + "\""
|
||||
}
|
||||
|
||||
override predicate isDeeplyConst() { this.getBaseType().isDeeplyConst() } // Just an alias
|
||||
|
||||
override predicate isDeeplyConstBelow() { this.getBaseType().isDeeplyConstBelow() } // Just an alias
|
||||
@@ -46,7 +47,43 @@ class TypedefType extends UserType {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C++ typedef type that is directly enclosed by a function.
|
||||
* A traditional C/C++ typedef type. See 4.9.1. For example the type declared in the following code:
|
||||
* ```
|
||||
* typedef int my_int;
|
||||
* ```
|
||||
*/
|
||||
class CTypedefType extends TypedefType {
|
||||
CTypedefType() { usertypes(underlyingElement(this), _, 5) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "CTypedefType" }
|
||||
|
||||
override string explain() {
|
||||
result = "typedef {" + this.getBaseType().explain() + "} as \"" + this.getName() + "\""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A using alias C++ typedef type. For example the type declared in the following code:
|
||||
* ```
|
||||
* using my_int2 = int;
|
||||
* ```
|
||||
*/
|
||||
class UsingAliasTypedefType extends TypedefType {
|
||||
UsingAliasTypedefType() { usertypes(underlyingElement(this), _, 14) }
|
||||
|
||||
override string getCanonicalQLClass() { result = "UsingAliasTypedefType" }
|
||||
|
||||
override string explain() {
|
||||
result = "using {" + this.getBaseType().explain() + "} as \"" + this.getName() + "\""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A C++ `typedef` type that is directly enclosed by a function. For example the type declared inside the function `foo` in
|
||||
* the following code:
|
||||
* ```
|
||||
* int foo(void) { typedef int local; }
|
||||
* ```
|
||||
*/
|
||||
class LocalTypedefType extends TypedefType {
|
||||
LocalTypedefType() { isLocal() }
|
||||
@@ -55,7 +92,11 @@ class LocalTypedefType extends TypedefType {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C++ typedef type that is directly enclosed by a class, struct or union.
|
||||
* A C++ `typedef` type that is directly enclosed by a `class`, `struct` or `union`. For example the type declared inside
|
||||
* the class `C` in the following code:
|
||||
* ```
|
||||
* class C { typedef int nested; };
|
||||
* ```
|
||||
*/
|
||||
class NestedTypedefType extends TypedefType {
|
||||
NestedTypedefType() { this.isMember() }
|
||||
|
||||
@@ -5,8 +5,14 @@ import semmle.code.cpp.Function
|
||||
private import semmle.code.cpp.internal.ResolveClass
|
||||
|
||||
/**
|
||||
* A C/C++ user-defined type. Examples include `Class`, `Struct`, `Union`,
|
||||
* `Enum`, and `TypedefType`.
|
||||
* A C/C++ user-defined type. Examples include `class`, `struct`, `union`,
|
||||
* `enum` and `typedef` types.
|
||||
* ```
|
||||
* enum e1 { val1, val2 } b;
|
||||
* enum class e2: short { val3, val4 } c;
|
||||
* typedef int my_int;
|
||||
* class C { int a, b; };
|
||||
* ```
|
||||
*/
|
||||
class UserType extends Type, Declaration, NameQualifyingElement, AccessHolder, @usertype {
|
||||
/**
|
||||
@@ -88,6 +94,10 @@ class UserType extends Type, Declaration, NameQualifyingElement, AccessHolder, @
|
||||
|
||||
/**
|
||||
* A particular definition or forward declaration of a C/C++ user-defined type.
|
||||
* ```
|
||||
* class C;
|
||||
* typedef int ti;
|
||||
* ```
|
||||
*/
|
||||
class TypeDeclarationEntry extends DeclarationEntry, @type_decl {
|
||||
override UserType getDeclaration() { result = getType() }
|
||||
|
||||
@@ -155,15 +155,6 @@ class Variable extends Declaration, @variable {
|
||||
variable_instantiation(underlyingElement(this), unresolveElement(v))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th template argument used to instantiate this variable from a
|
||||
* variable template. When called on a variable template, this will return the
|
||||
* `i`th template parameter.
|
||||
*/
|
||||
override Type getTemplateArgument(int index) {
|
||||
variable_template_argument(underlyingElement(this), index, unresolveElement(result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is a compiler-generated variable. For example, a
|
||||
* [range-based for loop](http://en.cppreference.com/w/cpp/language/range-for)
|
||||
@@ -315,7 +306,7 @@ class ParameterDeclarationEntry extends VariableDeclarationEntry {
|
||||
* static int c;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Local variables can be static; use the `isStatic` member predicate to
|
||||
* detect those.
|
||||
*/
|
||||
@@ -343,7 +334,7 @@ deprecated class StackVariable extends Variable {
|
||||
* static int c;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Local variables can be static; use the `isStatic` member predicate to detect
|
||||
* those.
|
||||
*
|
||||
@@ -512,9 +503,9 @@ class TemplateVariable extends Variable {
|
||||
* void myTemplateFunction() {
|
||||
* T b;
|
||||
* }
|
||||
*
|
||||
*
|
||||
* ...
|
||||
*
|
||||
*
|
||||
* myTemplateFunction<int>();
|
||||
* ```
|
||||
*/
|
||||
|
||||
@@ -5,13 +5,17 @@ import cpp
|
||||
*/
|
||||
predicate allocationFunction(Function f) {
|
||||
exists(string name |
|
||||
f.hasGlobalName(name) and
|
||||
f.hasGlobalOrStdName(name) and
|
||||
(
|
||||
name = "malloc" or
|
||||
name = "calloc" or
|
||||
name = "realloc" or
|
||||
name = "strdup" or
|
||||
name = "wcsdup" or
|
||||
name = "wcsdup"
|
||||
)
|
||||
or
|
||||
f.hasGlobalName(name) and
|
||||
(
|
||||
name = "_strdup" or
|
||||
name = "_wcsdup" or
|
||||
name = "_mbsdup" or
|
||||
@@ -59,7 +63,7 @@ predicate allocationCall(FunctionCall fc) {
|
||||
allocationFunction(fc.getTarget()) and
|
||||
(
|
||||
// realloc(ptr, 0) only frees the pointer
|
||||
fc.getTarget().hasGlobalName("realloc") implies not fc.getArgument(1).getValue() = "0"
|
||||
fc.getTarget().hasGlobalOrStdName("realloc") implies not fc.getArgument(1).getValue() = "0"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,7 +77,10 @@ predicate freeFunction(Function f, int argNum) {
|
||||
name = "free" and argNum = 0
|
||||
or
|
||||
name = "realloc" and argNum = 0
|
||||
or
|
||||
)
|
||||
or
|
||||
f.hasGlobalOrStdName(name) and
|
||||
(
|
||||
name = "ExFreePoolWithTag" and argNum = 0
|
||||
or
|
||||
name = "ExFreeToLookasideListEx" and argNum = 1
|
||||
|
||||
@@ -44,7 +44,7 @@ predicate memberMayBeVarSize(Class c, MemberVariable v) {
|
||||
aoe.getAddressable() = v
|
||||
)
|
||||
or
|
||||
exists(BuiltInOperationOffsetOf oo |
|
||||
exists(BuiltInOperationBuiltInOffsetOf oo |
|
||||
// `offsetof(c, v)` using a builtin
|
||||
oo.getAChild().(VariableAccess).getTarget() = v
|
||||
)
|
||||
|
||||
@@ -174,9 +174,7 @@ class MicrosoftInt64Type extends IntegralType {
|
||||
* `__builtin_va_copy` and `__builtin_va_arg` expressions.
|
||||
*/
|
||||
class BuiltInVarArgsList extends Type {
|
||||
BuiltInVarArgsList() {
|
||||
this.hasName("__builtin_va_list")
|
||||
}
|
||||
BuiltInVarArgsList() { this.hasName("__builtin_va_list") }
|
||||
|
||||
override string getCanonicalQLClass() { result = "BuiltInVarArgsList" }
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class EnvironmentRead extends Expr {
|
||||
private predicate readsEnvironment(Expr read, string sourceDescription) {
|
||||
exists(FunctionCall call, string name |
|
||||
read = call and
|
||||
call.getTarget().hasGlobalName(name) and
|
||||
call.getTarget().hasGlobalOrStdName(name) and
|
||||
(name = "getenv" or name = "secure_getenv" or name = "_wgetenv") and
|
||||
sourceDescription = name
|
||||
)
|
||||
|
||||
@@ -79,3 +79,26 @@ predicate functionContainsPreprocCode(Function f) {
|
||||
pbdStartLine >= fBlockStartLine
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is completely or partially from a macro definition, as opposed
|
||||
* to being passed in as an argument.
|
||||
*
|
||||
* In the following example, the call to `f` is from a macro definition,
|
||||
* while `y`, `+`, `1`, and `;` are not. This assumes that no identifier apart
|
||||
* from `M` refers to a macro.
|
||||
* ```
|
||||
* #define M(x) f(x)
|
||||
* ...
|
||||
* M(y + 1);
|
||||
* ```
|
||||
*/
|
||||
predicate isFromMacroDefinition(Element e) {
|
||||
exists(MacroInvocation mi |
|
||||
// e is in mi
|
||||
mi.getAnExpandedElement() = e and
|
||||
// and e was apparently not passed in as a macro parameter
|
||||
e.getLocation().getStartLine() = mi.getLocation().getStartLine() and
|
||||
e.getLocation().getStartColumn() = mi.getLocation().getStartColumn()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import cpp
|
||||
*/
|
||||
predicate fopenCall(FunctionCall fc) {
|
||||
exists(Function f | f = fc.getTarget() |
|
||||
f.hasGlobalName("fopen") or
|
||||
f.hasGlobalOrStdName("fopen") or
|
||||
f.hasGlobalName("open") or
|
||||
f.hasGlobalName("_open") or
|
||||
f.hasGlobalName("_wopen") or
|
||||
@@ -23,7 +23,7 @@ predicate fopenCall(FunctionCall fc) {
|
||||
*/
|
||||
predicate fcloseCall(FunctionCall fc, Expr closed) {
|
||||
exists(Function f | f = fc.getTarget() |
|
||||
f.hasGlobalName("fclose") and
|
||||
f.hasGlobalOrStdName("fclose") and
|
||||
closed = fc.getArgument(0)
|
||||
or
|
||||
f.hasGlobalName("close") and
|
||||
@@ -32,7 +32,7 @@ predicate fcloseCall(FunctionCall fc, Expr closed) {
|
||||
f.hasGlobalName("_close") and
|
||||
closed = fc.getArgument(0)
|
||||
or
|
||||
f.hasGlobalName("CloseHandle") and
|
||||
f.hasGlobalOrStdName("CloseHandle") and
|
||||
closed = fc.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,25 +8,32 @@ import semmle.code.cpp.commons.StringAnalysis
|
||||
import semmle.code.cpp.models.interfaces.FormattingFunction
|
||||
import semmle.code.cpp.models.implementations.Printf
|
||||
|
||||
class PrintfFormatAttribute extends FormatAttribute {
|
||||
PrintfFormatAttribute() {
|
||||
getArchetype() = "printf" or
|
||||
getArchetype() = "__printf__"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that can be identified as a `printf` style formatting
|
||||
* function by its use of the GNU `format` attribute.
|
||||
*/
|
||||
class AttributeFormattingFunction extends FormattingFunction {
|
||||
FormatAttribute printf_attrib;
|
||||
|
||||
override string getCanonicalQLClass() { result = "AttributeFormattingFunction" }
|
||||
|
||||
AttributeFormattingFunction() {
|
||||
printf_attrib = getAnAttribute() and
|
||||
(
|
||||
printf_attrib.getArchetype() = "printf" or
|
||||
printf_attrib.getArchetype() = "__printf__"
|
||||
) and
|
||||
exists(printf_attrib.getFirstFormatArgIndex()) // exclude `vprintf` style format functions
|
||||
exists(PrintfFormatAttribute printf_attrib |
|
||||
printf_attrib = getAnAttribute() and
|
||||
exists(printf_attrib.getFirstFormatArgIndex()) // exclude `vprintf` style format functions
|
||||
)
|
||||
}
|
||||
|
||||
override int getFormatParameterIndex() { result = printf_attrib.getFormatIndex() }
|
||||
override int getFormatParameterIndex() {
|
||||
forex(PrintfFormatAttribute printf_attrib | printf_attrib = getAnAttribute() |
|
||||
result = printf_attrib.getFormatIndex()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,15 +131,17 @@ class FormattingFunctionCall extends Expr {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the argument corresponding to the nth conversion specifier
|
||||
* Gets the argument corresponding to the nth conversion specifier.
|
||||
*/
|
||||
Expr getConversionArgument(int n) {
|
||||
exists(FormatLiteral fl, int b, int o |
|
||||
exists(FormatLiteral fl |
|
||||
fl = this.getFormat() and
|
||||
b = sum(int i, int toSum | i < n and toSum = fl.getNumArgNeeded(i) | toSum) and
|
||||
o = fl.getNumArgNeeded(n) and
|
||||
o > 0 and
|
||||
result = this.getFormatArgument(b + o - 1)
|
||||
(
|
||||
result = this.getFormatArgument(fl.getParameterFieldValue(n))
|
||||
or
|
||||
result = this.getFormatArgument(fl.getFormatArgumentIndexFor(n, 2)) and
|
||||
not exists(fl.getParameterFieldValue(n))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -142,11 +151,14 @@ class FormattingFunctionCall extends Expr {
|
||||
* an explicit minimum field width).
|
||||
*/
|
||||
Expr getMinFieldWidthArgument(int n) {
|
||||
exists(FormatLiteral fl, int b |
|
||||
exists(FormatLiteral fl |
|
||||
fl = this.getFormat() and
|
||||
b = sum(int i, int toSum | i < n and toSum = fl.getNumArgNeeded(i) | toSum) and
|
||||
fl.hasImplicitMinFieldWidth(n) and
|
||||
result = this.getFormatArgument(b)
|
||||
(
|
||||
result = this.getFormatArgument(fl.getMinFieldWidthParameterFieldValue(n))
|
||||
or
|
||||
result = this.getFormatArgument(fl.getFormatArgumentIndexFor(n, 0)) and
|
||||
not exists(fl.getMinFieldWidthParameterFieldValue(n))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -156,12 +168,14 @@ class FormattingFunctionCall extends Expr {
|
||||
* precision).
|
||||
*/
|
||||
Expr getPrecisionArgument(int n) {
|
||||
exists(FormatLiteral fl, int b, int o |
|
||||
exists(FormatLiteral fl |
|
||||
fl = this.getFormat() and
|
||||
b = sum(int i, int toSum | i < n and toSum = fl.getNumArgNeeded(i) | toSum) and
|
||||
(if fl.hasImplicitMinFieldWidth(n) then o = 1 else o = 0) and
|
||||
fl.hasImplicitPrecision(n) and
|
||||
result = this.getFormatArgument(b + o)
|
||||
(
|
||||
result = this.getFormatArgument(fl.getPrecisionParameterFieldValue(n))
|
||||
or
|
||||
result = this.getFormatArgument(fl.getFormatArgumentIndexFor(n, 1)) and
|
||||
not exists(fl.getPrecisionParameterFieldValue(n))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -169,7 +183,11 @@ class FormattingFunctionCall extends Expr {
|
||||
* Gets the number of arguments to this call that are parameters to the
|
||||
* format string.
|
||||
*/
|
||||
int getNumFormatArgument() { result = count(this.getFormatArgument(_)) }
|
||||
int getNumFormatArgument() {
|
||||
result = count(this.getFormatArgument(_)) and
|
||||
// format arguments must be known
|
||||
exists(getTarget().(FormattingFunction).getFirstFormatArgumentIndex())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,6 +375,14 @@ class FormatLiteral extends Literal {
|
||||
*/
|
||||
string getParameterField(int n) { this.parseConvSpec(n, _, result, _, _, _, _, _) }
|
||||
|
||||
/**
|
||||
* Gets the parameter field of the nth conversion specifier (if it has one) as a
|
||||
* zero-based number.
|
||||
*/
|
||||
int getParameterFieldValue(int n) {
|
||||
result = this.getParameterField(n).regexpCapture("([0-9]*)\\$", 1).toInt() - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flags of the nth conversion specifier.
|
||||
*/
|
||||
@@ -426,6 +452,14 @@ class FormatLiteral extends Literal {
|
||||
*/
|
||||
int getMinFieldWidth(int n) { result = this.getMinFieldWidthOpt(n).toInt() }
|
||||
|
||||
/**
|
||||
* Gets the zero-based parameter number of the minimum field width of the nth
|
||||
* conversion specifier, if it is implicit and uses a parameter field (such as `*1$`).
|
||||
*/
|
||||
int getMinFieldWidthParameterFieldValue(int n) {
|
||||
result = this.getMinFieldWidthOpt(n).regexpCapture("\\*([0-9]*)\\$", 1).toInt() - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the precision of the nth conversion specifier (empty string if none is given).
|
||||
*/
|
||||
@@ -456,6 +490,14 @@ class FormatLiteral extends Literal {
|
||||
else result = this.getPrecisionOpt(n).regexpCapture("\\.([0-9]*)", 1).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the zero-based parameter number of the precision of the nth conversion
|
||||
* specifier, if it is implicit and uses a parameter field (such as `*1$`).
|
||||
*/
|
||||
int getPrecisionParameterFieldValue(int n) {
|
||||
result = this.getPrecisionOpt(n).regexpCapture("\\.\\*([0-9]*)\\$", 1).toInt() - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the length flag of the nth conversion specifier.
|
||||
*/
|
||||
@@ -773,19 +815,49 @@ class FormatLiteral extends Literal {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the nth conversion specifier of this format string (if `mode = 2`), it's
|
||||
* minimum field width (if `mode = 0`) or it's precision (if `mode = 1`) requires a
|
||||
* format argument.
|
||||
*
|
||||
* Most conversion specifiers require a format argument, whereas minimum field width
|
||||
* and precision only require a format argument if they are present and a `*` was
|
||||
* used for it's value in the format string.
|
||||
*/
|
||||
private predicate hasFormatArgumentIndexFor(int n, int mode) {
|
||||
mode = 0 and
|
||||
this.hasImplicitMinFieldWidth(n)
|
||||
or
|
||||
mode = 1 and
|
||||
this.hasImplicitPrecision(n)
|
||||
or
|
||||
mode = 2 and
|
||||
exists(this.getConvSpecOffset(n)) and
|
||||
not this.getConversionChar(n) = "m"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the computed format argument index for the nth conversion specifier of this
|
||||
* format string (if `mode = 2`), it's minimum field width (if `mode = 0`) or it's
|
||||
* precision (if `mode = 1`). Has no result if that element is not present. Does
|
||||
* not account for positional arguments (`$`).
|
||||
*/
|
||||
int getFormatArgumentIndexFor(int n, int mode) {
|
||||
hasFormatArgumentIndexFor(n, mode) and
|
||||
(3 * n) + mode = rank[result + 1](int n2, int mode2 |
|
||||
hasFormatArgumentIndexFor(n2, mode2)
|
||||
|
|
||||
(3 * n2) + mode2
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of arguments required by the nth conversion specifier
|
||||
* of this format string.
|
||||
*/
|
||||
int getNumArgNeeded(int n) {
|
||||
exists(this.getConvSpecOffset(n)) and
|
||||
not this.getConversionChar(n) = "%" and
|
||||
exists(int n1, int n2, int n3 |
|
||||
(if this.hasImplicitMinFieldWidth(n) then n1 = 1 else n1 = 0) and
|
||||
(if this.hasImplicitPrecision(n) then n2 = 1 else n2 = 0) and
|
||||
(if this.getConversionChar(n) = "m" then n3 = 0 else n3 = 1) and
|
||||
result = n1 + n2 + n3
|
||||
)
|
||||
result = count(int mode | hasFormatArgumentIndexFor(n, mode))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -797,7 +869,7 @@ class FormatLiteral extends Literal {
|
||||
// At least one conversion specifier has a parameter field, in which case,
|
||||
// they all should have.
|
||||
result = max(string s | this.getParameterField(_) = s + "$" | s.toInt())
|
||||
else result = sum(int n, int toSum | toSum = this.getNumArgNeeded(n) | toSum)
|
||||
else result = count(int n, int mode | hasFormatArgumentIndexFor(n, mode))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,8 +53,8 @@ class AnalysedString extends Expr {
|
||||
*/
|
||||
class StrlenCall extends FunctionCall {
|
||||
StrlenCall() {
|
||||
this.getTarget().hasGlobalName("strlen") or
|
||||
this.getTarget().hasGlobalName("wcslen") or
|
||||
this.getTarget().hasGlobalOrStdName("strlen") or
|
||||
this.getTarget().hasGlobalOrStdName("wcslen") or
|
||||
this.getTarget().hasGlobalName("_mbslen") or
|
||||
this.getTarget().hasGlobalName("_mbslen_l") or
|
||||
this.getTarget().hasGlobalName("_mbstrlen") or
|
||||
|
||||
@@ -35,7 +35,8 @@ private predicate exprInVoidContext(Expr e) {
|
||||
exists(CommaExpr c | c.getLeftOperand() = e)
|
||||
or
|
||||
exists(CommaExpr c | c.getRightOperand() = e and c instanceof ExprInVoidContext)
|
||||
or
|
||||
exists(ForStmt for | for.getUpdate() = e)
|
||||
) and
|
||||
not e instanceof Qualifier and
|
||||
not e.getActualType() instanceof VoidType
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import cpp
|
||||
import BasicBlocks
|
||||
private import semmle.code.cpp.controlflow.internal.ConstantExprs
|
||||
private import semmle.code.cpp.controlflow.internal.CFG
|
||||
|
||||
/**
|
||||
* A control-flow node is either a statement or an expression; in addition,
|
||||
@@ -86,11 +87,11 @@ import ControlFlowGraphPublic
|
||||
class ControlFlowNodeBase extends ElementBase, @cfgnode { }
|
||||
|
||||
predicate truecond_base(ControlFlowNodeBase n1, ControlFlowNodeBase n2) {
|
||||
truecond(unresolveElement(n1), unresolveElement(n2))
|
||||
qlCFGTrueSuccessor(n1, n2)
|
||||
}
|
||||
|
||||
predicate falsecond_base(ControlFlowNodeBase n1, ControlFlowNodeBase n2) {
|
||||
falsecond(unresolveElement(n1), unresolveElement(n2))
|
||||
qlCFGFalseSuccessor(n1, n2)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +121,7 @@ abstract class AdditionalControlFlowEdge extends ControlFlowNodeBase {
|
||||
* `AdditionalControlFlowEdge`. Use this relation instead of `successors`.
|
||||
*/
|
||||
predicate successors_extended(ControlFlowNodeBase source, ControlFlowNodeBase target) {
|
||||
successors(unresolveElement(source), unresolveElement(target))
|
||||
qlCFGSuccessor(source, target)
|
||||
or
|
||||
source.(AdditionalControlFlowEdge).getAnEdgeTarget() = target
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import Nullness
|
||||
*/
|
||||
predicate callDereferences(FunctionCall fc, int i) {
|
||||
exists(string name |
|
||||
fc.getTarget().hasGlobalName(name) and
|
||||
fc.getTarget().hasGlobalOrStdName(name) and
|
||||
(
|
||||
name = "bcopy" and i in [0 .. 1]
|
||||
or
|
||||
|
||||
@@ -156,7 +156,7 @@ private predicate compares_eq(
|
||||
|
||||
/**
|
||||
* If `test => part` and `part => left == right + k` then `test => left == right + k`.
|
||||
* Similarly for the case where `test` is false.
|
||||
* Similarly for the case where `test` is false.
|
||||
*/
|
||||
private predicate logical_comparison_eq(
|
||||
BinaryLogicalOperation test, Expr left, Expr right, int k, boolean areEqual, boolean testIsTrue
|
||||
@@ -275,7 +275,7 @@ private predicate compares_ge(
|
||||
|
||||
/**
|
||||
* If `test => part` and `part => left < right + k` then `test => left < right + k`.
|
||||
* Similarly for the case where `test` evaluates false.
|
||||
* Similarly for the case where `test` evaluates false.
|
||||
*/
|
||||
private predicate logical_comparison_lt(
|
||||
BinaryLogicalOperation test, Expr left, Expr right, int k, boolean isLt, boolean testIsTrue
|
||||
@@ -362,7 +362,7 @@ private predicate add_lt(
|
||||
)
|
||||
}
|
||||
|
||||
/** The int value of integer constant expression. */
|
||||
/** The `int` value of integer constant expression. */
|
||||
private int int_value(Expr e) {
|
||||
e.getUnderlyingType() instanceof IntegralType and
|
||||
result = e.getValue().toInt()
|
||||
|
||||
@@ -264,9 +264,9 @@ predicate callMayReturnNull(Call call) {
|
||||
* Holds if `f` may, directly or indirectly, return a null literal.
|
||||
*/
|
||||
predicate mayReturnNull(Function f) {
|
||||
f.hasGlobalName("malloc")
|
||||
f.hasGlobalOrStdName("malloc")
|
||||
or
|
||||
f.hasGlobalName("calloc")
|
||||
f.hasGlobalOrStdName("calloc")
|
||||
or
|
||||
// f.hasGlobalName("strchr")
|
||||
// or
|
||||
|
||||
@@ -29,11 +29,11 @@ class SsaDefinition extends ControlFlowNodeBase {
|
||||
|
||||
/**
|
||||
* Gets a string representation of the SSA variable represented by the pair
|
||||
* (this, v).
|
||||
* `(this, v)`.
|
||||
*/
|
||||
string toString(LocalScopeVariable v) { exists(StandardSSA x | result = x.toString(this, v)) }
|
||||
|
||||
/** Gets a use of the SSA variable represented by the pair (this, v). */
|
||||
/** Gets a use of the SSA variable represented by the pair `(this, v)`. */
|
||||
VariableAccess getAUse(LocalScopeVariable v) {
|
||||
exists(StandardSSA x | result = x.getAUse(this, v))
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ private predicate excludeNodeAndNodesBelow(Expr e) {
|
||||
* control flow in them.
|
||||
*/
|
||||
private predicate excludeNodesStrictlyBelow(Node n) {
|
||||
n instanceof BuiltInOperationOffsetOf
|
||||
n instanceof BuiltInOperationBuiltInOffsetOf
|
||||
or
|
||||
n instanceof BuiltInIntAddr
|
||||
or
|
||||
@@ -173,49 +173,36 @@ predicate excludeNode(Node n) {
|
||||
excludeNode(n.getParentNode())
|
||||
}
|
||||
|
||||
private newtype TPos =
|
||||
PosBefore() or
|
||||
PosAt() or
|
||||
PosAfter() or
|
||||
PosBeforeDestructors() or
|
||||
PosAfterDestructors()
|
||||
|
||||
/** A `Pos` without a `bindingset` requirement on the constructor. */
|
||||
private class AnyPos extends TPos {
|
||||
string toString() { result = "Pos" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A constant that indicates the type of sub-node in a pair of `(Node, Pos)`.
|
||||
* See the comment block at the top of this file.
|
||||
*/
|
||||
private class Pos extends AnyPos {
|
||||
// This is to make sure we get compile errors in code that forgets to restrict a `Pos`.
|
||||
private class Pos extends int {
|
||||
bindingset[this]
|
||||
Pos() { any() }
|
||||
|
||||
/** Holds if this is the position just _before_ the associated `Node`. */
|
||||
predicate isBefore() { this = PosBefore() }
|
||||
predicate isBefore() { this = 0 }
|
||||
|
||||
/** Holds if `(n, this)` is the sub-node that represents `n` itself. */
|
||||
predicate isAt() { this = PosAt() }
|
||||
predicate isAt() { this = 1 }
|
||||
|
||||
/** Holds if this is the position just _after_ the associated `Node`. */
|
||||
predicate isAfter() { this = PosAfter() }
|
||||
predicate isAfter() { this = 2 }
|
||||
|
||||
/**
|
||||
* Holds if `(n, this)` is the virtual sub-node that comes just _before_ any
|
||||
* implicit destructor calls following `n`. The node `n` will be some node
|
||||
* that may be followed by local variables going out of scope.
|
||||
*/
|
||||
predicate isBeforeDestructors() { this = PosBeforeDestructors() }
|
||||
predicate isBeforeDestructors() { this = 3 }
|
||||
|
||||
/**
|
||||
* Holds if `(n, this)` is the virtual sub-node that comes just _after_ any
|
||||
* implicit destructor calls following `n`. The node `n` will be some node
|
||||
* that may be followed by local variables going out of scope.
|
||||
*/
|
||||
predicate isAfterDestructors() { this = PosAfterDestructors() }
|
||||
predicate isAfterDestructors() { this = 4 }
|
||||
|
||||
pragma[inline]
|
||||
predicate nodeBefore(Node n, Node nEq) { this.isBefore() and n = nEq }
|
||||
@@ -489,17 +476,6 @@ private Node getLastControlOrderChild(Node n) {
|
||||
result = getControlOrderChildDense(n, max(int i | exists(getControlOrderChildDense(n, i))))
|
||||
}
|
||||
|
||||
private newtype TSpec =
|
||||
SpecPos(AnyPos p) or
|
||||
SpecAround() or
|
||||
SpecAroundDestructors() or
|
||||
SpecBarrier()
|
||||
|
||||
/** A `Spec` without a `bindingset` requirement on the constructor. */
|
||||
private class AnySpec extends TSpec {
|
||||
string toString() { result = "Spec" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A constant that represents two positions: one position for when it's used as
|
||||
* the _source_ of a sub-edge, and another position for when it's used as the
|
||||
@@ -507,25 +483,10 @@ private class AnySpec extends TSpec {
|
||||
* themselves as both source and target, as well as two _around_ values and a
|
||||
* _barrier_ value.
|
||||
*/
|
||||
private class Spec extends AnySpec {
|
||||
private class Spec extends Pos {
|
||||
bindingset[this]
|
||||
Spec() { any() }
|
||||
|
||||
/** See Pos.isBefore. */
|
||||
predicate isBefore() { this = SpecPos(PosBefore()) }
|
||||
|
||||
/** See Pos.isAt. */
|
||||
predicate isAt() { this = SpecPos(PosAt()) }
|
||||
|
||||
/** See Pos.isAfter. */
|
||||
predicate isAfter() { this = SpecPos(PosAfter()) }
|
||||
|
||||
/** See Pos.isBeforeDestructors. */
|
||||
predicate isBeforeDestructors() { this = SpecPos(PosBeforeDestructors()) }
|
||||
|
||||
/** See Pos.isAfterDestructors. */
|
||||
predicate isAfterDestructors() { this = SpecPos(PosAfterDestructors()) }
|
||||
|
||||
/**
|
||||
* Holds if this spec, when used on a node `n` between `(n1, p1)` and
|
||||
* `(n2, p2)`, should add the following sub-edges.
|
||||
@@ -533,7 +494,7 @@ private class Spec extends AnySpec {
|
||||
* (n1, p1) ----> before(n)
|
||||
* after(n) ----> (n2, p2)
|
||||
*/
|
||||
predicate isAround() { this = SpecAround() }
|
||||
predicate isAround() { this = 5 }
|
||||
|
||||
/**
|
||||
* Holds if this spec, when used on a node `n` between `(n1, p1)` and
|
||||
@@ -542,16 +503,17 @@ private class Spec extends AnySpec {
|
||||
* (n1, p1) ----> beforeDestructors(n)
|
||||
* afterDestructors(n) ----> (n2, p2)
|
||||
*/
|
||||
predicate isAroundDestructors() { this = SpecAroundDestructors() }
|
||||
predicate isAroundDestructors() { this = 6 }
|
||||
|
||||
/**
|
||||
* Holds if this node is a _barrier_. A barrier resolves to no positions and
|
||||
* can be inserted between nodes that should have no sub-edges between them.
|
||||
*/
|
||||
predicate isBarrier() { this = SpecBarrier() }
|
||||
predicate isBarrier() { this = 7 }
|
||||
|
||||
Pos getSourcePos() {
|
||||
this = SpecPos(result)
|
||||
this = [0 .. 4] and
|
||||
result = this
|
||||
or
|
||||
this.isAround() and
|
||||
result.isAfter()
|
||||
@@ -561,7 +523,8 @@ private class Spec extends AnySpec {
|
||||
}
|
||||
|
||||
Pos getTargetPos() {
|
||||
this = SpecPos(result)
|
||||
this = [0 .. 4] and
|
||||
result = this
|
||||
or
|
||||
this.isAround() and
|
||||
result.isBefore()
|
||||
@@ -888,6 +851,21 @@ private predicate subEdge(Pos p1, Node n1, Node n2, Pos p2) {
|
||||
p2.nodeAfter(n2, s)
|
||||
)
|
||||
or
|
||||
// ConstexprIfStmt -> condition ; { then, else } -> // same as IfStmt
|
||||
exists(ConstexprIfStmt s |
|
||||
p1.nodeAt(n1, s) and
|
||||
p2.nodeBefore(n2, s.getCondition())
|
||||
or
|
||||
p1.nodeAfter(n1, s.getThen()) and
|
||||
p2.nodeBeforeDestructors(n2, s)
|
||||
or
|
||||
p1.nodeAfter(n1, s.getElse()) and
|
||||
p2.nodeBeforeDestructors(n2, s)
|
||||
or
|
||||
p1.nodeAfterDestructors(n1, s) and
|
||||
p2.nodeAfter(n2, s)
|
||||
)
|
||||
or
|
||||
// WhileStmt -> condition ; body -> condition ; after dtors -> after
|
||||
exists(WhileStmt s |
|
||||
p1.nodeAt(n1, s) and
|
||||
@@ -1175,9 +1153,8 @@ private class ExceptionSource extends Node {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `test` is the test of a control-flow construct that will always
|
||||
* have true/false sub-edges out of it, where the `truth`-sub-edge goes to
|
||||
* `(n2, p2)`.
|
||||
* Holds if `test` is the test of a control-flow construct where the `truth`
|
||||
* sub-edge goes to `(n2, p2)`.
|
||||
*/
|
||||
private predicate conditionJumpsTop(Expr test, boolean truth, Node n2, Pos p2) {
|
||||
exists(IfStmt s | test = s.getCondition() |
|
||||
@@ -1192,6 +1169,24 @@ private predicate conditionJumpsTop(Expr test, boolean truth, Node n2, Pos p2) {
|
||||
p2.nodeBeforeDestructors(n2, s)
|
||||
)
|
||||
or
|
||||
exists(ConstexprIfStmt s, string cond |
|
||||
test = s.getCondition() and
|
||||
cond = test.getFullyConverted().getValue()
|
||||
|
|
||||
truth = true and
|
||||
cond != "0" and
|
||||
p2.nodeBefore(n2, s.getThen())
|
||||
or
|
||||
truth = false and
|
||||
cond = "0" and
|
||||
p2.nodeBefore(n2, s.getElse())
|
||||
or
|
||||
not exists(s.getElse()) and
|
||||
truth = false and
|
||||
cond = "0" and
|
||||
p2.nodeBeforeDestructors(n2, s)
|
||||
)
|
||||
or
|
||||
exists(Loop l |
|
||||
(
|
||||
l instanceof WhileStmt
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* on each other without introducing mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplCommon::Public
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
|
||||
@@ -258,8 +258,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKind kind) {
|
||||
viableImpl(call) = result.getCallable() and
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
@@ -313,22 +313,23 @@ private predicate nodeCandFwd1(Node node, Configuration config) {
|
||||
viableParamArg(_, node, arg)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
nodeCandFwd1(mid, config) and
|
||||
parameterValueFlowsToUpdate(p, mid) and
|
||||
viableParamArg(_, p, node.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnNode ret, ReturnKind kind |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = viableReturnPos(call, kind) and
|
||||
node = getAnOutNode(call, kind)
|
||||
exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind |
|
||||
nodeCandFwd1ReturnPosition(pos, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
node = kind.getAnOutNode(call)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(ReturnNodeExt ret |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = pos
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
|
||||
exists(Node mid |
|
||||
@@ -403,22 +404,23 @@ private predicate nodeCand1(Node node, Configuration config) {
|
||||
nodeCand1(param, config)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node) and
|
||||
viableParamArg(_, p, mid.getPreUpdateNode()) and
|
||||
nodeCand1(mid, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind, OutNode out |
|
||||
nodeCand1(out, config) and
|
||||
getReturnPosition(node) = viableReturnPos(call, kind) and
|
||||
out = getAnOutNode(call, kind)
|
||||
exists(ReturnPosition pos |
|
||||
nodeCand1ReturnPosition(pos, config) and
|
||||
getReturnPosition(node) = pos
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind, Node out |
|
||||
nodeCand1(out, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
|
||||
*/
|
||||
@@ -565,28 +567,24 @@ private predicate additionalLocalFlowStepOrFlowThroughCallable(
|
||||
simpleArgumentFlowsThrough(node1, node2, _, config)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition getReturnPosition1(Node node, Configuration config) {
|
||||
result = getReturnPosition(node) and
|
||||
nodeCand1(node, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow out of a callable from `node1` to `node2`, either
|
||||
* through a `ReturnNode` or through an argument that has been mutated, and
|
||||
* that this step is part of a path from a source to a sink.
|
||||
*/
|
||||
private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) {
|
||||
nodeCand1(node1, unbind(config)) and
|
||||
nodeCand1(node2, config) and
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
(
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
)
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition1(node1, unbind(config)) = viableReturnPos(call, kind) and
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -905,30 +903,35 @@ private predicate localFlowExit(Node node, Configuration config) {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate localFlowStepPlus(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc
|
||||
) {
|
||||
localFlowEntry(node1, config) and
|
||||
not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
localFlowEntry(node1, config) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
cc.relevantFor(node1.getEnclosingCallable()) and
|
||||
not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config, cc) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config, cc) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -936,11 +939,11 @@ private predicate localFlowStepPlus(
|
||||
* Holds if `node1` can step to `node2` in one or more local steps and this
|
||||
* path can occur as a maximal subsequence of local steps in a dataflow path.
|
||||
*/
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate localFlowBigStep(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext
|
||||
) {
|
||||
localFlowStepPlus(node1, node2, preservesValue, config) and
|
||||
localFlowStepPlus(node1, node2, preservesValue, config, callContext) and
|
||||
localFlowExit(node2, config)
|
||||
}
|
||||
|
||||
@@ -1000,7 +1003,7 @@ private class AccessPathFrontNilNode extends Node {
|
||||
(
|
||||
any(Configuration c).isSource(this)
|
||||
or
|
||||
localFlowBigStep(_, this, false, _)
|
||||
localFlowBigStep(_, this, false, _, _)
|
||||
or
|
||||
additionalJumpStep(_, this, _)
|
||||
)
|
||||
@@ -1023,12 +1026,12 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
(
|
||||
exists(Node mid |
|
||||
flowCandFwd(mid, fromArg, apf, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(mid, fromArg, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
apf = node.(AccessPathFrontNilNode).getApf()
|
||||
)
|
||||
or
|
||||
@@ -1075,6 +1078,7 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
flowCandFwd(mid, fromArg, _, config) and
|
||||
store(mid, f, node) and
|
||||
nodeCand(node, unbind(config)) and
|
||||
readStoreCand(f, unbind(config)) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
or
|
||||
@@ -1121,13 +1125,13 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
apf instanceof AccessPathFrontNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flowCand(mid, toReturn, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(node, _, apf, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flowCand(mid, toReturn, nil, config) and
|
||||
apf instanceof AccessPathFrontNil
|
||||
)
|
||||
@@ -1175,12 +1179,12 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandStore(node, f, toReturn, apf0, config) and
|
||||
apf0.headUsesContent(f) and
|
||||
consCand(f, apf, unbind(config))
|
||||
consCand(f, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandRead(node, f, toReturn, apf0, config) and
|
||||
consCandFwd(f, apf0, unbind(config)) and
|
||||
consCandFwd(f, apf0, config) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
}
|
||||
@@ -1221,8 +1225,8 @@ private newtype TAccessPath =
|
||||
TConsCons(Content f1, Content f2, int len) { consCand(f1, TFrontHead(f2), _) and len in [2 .. 5] }
|
||||
|
||||
/**
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first
|
||||
* element of the list and its length are tracked. If data flows from a source to
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first two
|
||||
* elements of the list and its length are tracked. If data flows from a source to
|
||||
* a given node with a given `AccessPath`, this indicates the sequence of
|
||||
* dereference operations needed to get from the value in the node to the
|
||||
* tracked object. The final type indicates the type of the tracked object.
|
||||
@@ -1260,7 +1264,7 @@ abstract private class AccessPath extends TAccessPath {
|
||||
|
||||
private class AccessPathNil extends AccessPath, TNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -1276,7 +1280,7 @@ private class AccessPathConsNil extends AccessPathCons, TConsNil {
|
||||
override string toString() {
|
||||
exists(Content f, DataFlowType t | this = TConsNil(f, t) |
|
||||
// The `concat` becomes "" if `ppReprType` has no result.
|
||||
result = f.toString() + concat(" : " + ppReprType(t))
|
||||
result = "[" + f.toString() + "]" + concat(" : " + ppReprType(t))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1293,8 +1297,8 @@ private class AccessPathConsCons extends AccessPathCons, TConsCons {
|
||||
override string toString() {
|
||||
exists(Content f1, Content f2, int len | this = TConsCons(f1, f2, len) |
|
||||
if len = 2
|
||||
then result = f1.toString() + ", " + f2.toString()
|
||||
else result = f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f1.toString() + ", " + f2.toString() + "]"
|
||||
else result = "[" + f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1362,12 +1366,12 @@ private predicate flowFwd0(
|
||||
(
|
||||
exists(Node mid |
|
||||
flowFwd(mid, fromArg, apf, ap, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(mid, fromArg, _, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
ap = node.(AccessPathNilNode).getAp() and
|
||||
apf = ap.(AccessPathNil).getFront()
|
||||
)
|
||||
@@ -1471,13 +1475,13 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio
|
||||
ap instanceof AccessPathNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flow(mid, toReturn, ap, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(node, _, _, ap, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flow(mid, toReturn, nil, config) and
|
||||
ap instanceof AccessPathNil
|
||||
)
|
||||
@@ -1618,11 +1622,14 @@ abstract class PathNode extends TPathNode {
|
||||
/** Gets a successor of this node, if any. */
|
||||
abstract PathNode getASuccessor();
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
abstract predicate isSource();
|
||||
|
||||
private string ppAp() {
|
||||
this instanceof PathNodeSink and result = ""
|
||||
or
|
||||
exists(string s | s = this.(PathNodeMid).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1683,12 +1690,6 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = getSuccMid()
|
||||
or
|
||||
// a final step to a sink via one or more local steps
|
||||
localFlowStepPlus(node, result.getNode(), _, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid |
|
||||
mid = getSuccMid() and
|
||||
@@ -1697,23 +1698,12 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(mid.getConfiguration())
|
||||
)
|
||||
or
|
||||
// a direct step from a source to a sink if a node is both
|
||||
this instanceof PathNodeSource and
|
||||
result instanceof PathNodeSink and
|
||||
this.getNode() = result.getNode() and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow graph node corresponding to a source.
|
||||
*/
|
||||
private class PathNodeSource extends PathNodeMid {
|
||||
PathNodeSource() {
|
||||
getConfiguration().isSource(getNode()) and
|
||||
getCallContext() instanceof CallContextAny and
|
||||
getAp() instanceof AccessPathNil
|
||||
override predicate isSource() {
|
||||
config.isSource(node) and
|
||||
cc instanceof CallContextAny and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1733,6 +1723,8 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessor() { none() }
|
||||
|
||||
override predicate isSource() { config.isSource(node) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1740,14 +1732,20 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
* a callable is recorded by `cc`.
|
||||
*/
|
||||
private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPath ap) {
|
||||
localFlowBigStep(mid.getNode(), node, true, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp()
|
||||
or
|
||||
localFlowBigStep(mid.getNode(), node, false, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf |
|
||||
midnode = mid.getNode() and
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, conf, localCC) and
|
||||
ap = ap0
|
||||
or
|
||||
localFlowBigStep(midnode, node, false, conf, localCC) and
|
||||
ap0 instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, mid.getConfiguration()) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -1762,8 +1760,6 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
|
||||
or
|
||||
exists(Content f, AccessPath ap0 | contentStoreStep(mid, node, ap0, f, cc) and push(ap0, f, ap))
|
||||
or
|
||||
pathOutOfArgument(mid, node, cc) and ap = mid.getAp()
|
||||
or
|
||||
pathIntoCallable(mid, node, _, cc, _) and ap = mid.getAp()
|
||||
or
|
||||
pathOutOfCallable(mid, node, cc) and ap = mid.getAp()
|
||||
@@ -1797,9 +1793,9 @@ private predicate pathOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallCo
|
||||
not innercc instanceof CallContextCall
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathOutOfCallable1(
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKind kind, CallContext cc
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
pathOutOfCallable0(mid, pos, innercc) and
|
||||
@@ -1816,29 +1812,9 @@ private predicate pathOutOfCallable1(
|
||||
* is a return from a callable and is recorded by `cc`, if needed.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, OutNode out, CallContext cc) {
|
||||
exists(ReturnKind kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate pathOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
flow(node, unbind(mid.getConfiguration()))
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, Node out, CallContext cc) {
|
||||
exists(ReturnKindExt kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1891,7 +1867,7 @@ private predicate pathIntoCallable(
|
||||
pathIntoCallable0(mid, callable, i, outercc, call, emptyAp) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -1900,9 +1876,9 @@ private predicate pathIntoCallable(
|
||||
/** Holds if data may flow from `p` to a return of kind `kind`. */
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThrough(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(PathNodeMid mid, ReturnNode ret |
|
||||
exists(PathNodeMid mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -1917,14 +1893,14 @@ private predicate paramFlowsThrough(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathThroughCallable0(
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKind kind, CallContext cc, AccessPathNil apnil
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc |
|
||||
pathIntoCallable(mid, p, cc, innercc, call) and
|
||||
paramFlowsThrough(p, kind, innercc, apnil, unbind(mid.getConfiguration())) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc) and
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc) and
|
||||
mid.getAp() instanceof AccessPathNil
|
||||
)
|
||||
}
|
||||
@@ -1934,12 +1910,10 @@ private predicate pathThroughCallable0(
|
||||
* The context `cc` is restored to its value prior to entering the callable.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathThroughCallable(
|
||||
PathNodeMid mid, OutNode out, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
private predicate pathThroughCallable(PathNodeMid mid, Node out, CallContext cc, AccessPathNil apnil) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
pathThroughCallable0(call, mid, kind, cc, apnil) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1967,12 +1941,12 @@ private predicate valuePathThroughCallable(PathNodeMid mid, OutNode out, CallCon
|
||||
* sinks.
|
||||
*/
|
||||
private predicate flowsTo(
|
||||
PathNodeSource flowsource, PathNodeSink flowsink, Node source, Node sink,
|
||||
Configuration configuration
|
||||
PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration
|
||||
) {
|
||||
flowsource.isSource() and
|
||||
flowsource.getConfiguration() = configuration and
|
||||
flowsource.getNode() = source and
|
||||
pathSuccPlus(flowsource, flowsink) and
|
||||
(flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
|
||||
flowsink.getNode() = sink
|
||||
}
|
||||
|
||||
@@ -1996,16 +1970,10 @@ private module FlowExploration {
|
||||
// flow into callable
|
||||
viableParamArg(_, node2, node1)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
|
|
||||
c1 = node1.getEnclosingCallable() and
|
||||
@@ -2082,7 +2050,7 @@ private module FlowExploration {
|
||||
|
||||
private class PartialAccessPathNil extends PartialAccessPath, TPartialNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -2094,8 +2062,8 @@ private module FlowExploration {
|
||||
override string toString() {
|
||||
exists(Content f, int len | this = TPartialCons(f, len) |
|
||||
if len = 1
|
||||
then result = f.toString()
|
||||
else result = f.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f.toString() + "]"
|
||||
else result = "[" + f.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2172,7 +2140,7 @@ private module FlowExploration {
|
||||
|
||||
private string ppAp() {
|
||||
exists(string s | s = this.(PartialPathNodePriv).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2216,16 +2184,19 @@ private module FlowExploration {
|
||||
private predicate partialPathStep(
|
||||
PartialPathNodePriv mid, Node node, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -2247,8 +2218,6 @@ private module FlowExploration {
|
||||
apConsFwd(ap, f, ap0, config)
|
||||
)
|
||||
or
|
||||
partialPathOutOfArgument(mid, node, cc, ap, config)
|
||||
or
|
||||
partialPathIntoCallable(mid, node, _, cc, _, ap, config)
|
||||
or
|
||||
partialPathOutOfCallable(mid, node, cc, ap, config)
|
||||
@@ -2307,7 +2276,7 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathOutOfCallable1(
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKind kind, CallContext cc,
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
@@ -2321,36 +2290,12 @@ private module FlowExploration {
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnKind kind, DataFlowCall call |
|
||||
exists(ReturnKindExt kind, DataFlowCall call |
|
||||
partialPathOutOfCallable1(mid, call, kind, cc, ap, config)
|
||||
|
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfArgument(
|
||||
PartialPathNodePriv mid, PostUpdateNode node, CallContext cc, PartialAccessPath ap,
|
||||
Configuration config
|
||||
) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2389,7 +2334,7 @@ private module FlowExploration {
|
||||
partialPathIntoCallable0(mid, callable, i, outercc, call, emptyAp, ap, config) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -2397,10 +2342,10 @@ private module FlowExploration {
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThroughInPartialPath(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(PartialPathNodePriv mid, ReturnNode ret |
|
||||
exists(PartialPathNodePriv mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -2417,23 +2362,23 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathThroughCallable0(
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKind kind, CallContext cc,
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc, PartialAccessPathNil midapnil |
|
||||
partialPathIntoCallable(mid, p, cc, innercc, call, midapnil, config) and
|
||||
paramFlowsThroughInPartialPath(p, kind, innercc, apnil, config) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc)
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathThroughCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPathNil apnil,
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
partialPathThroughCallable0(call, mid, kind, cc, apnil, config) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* on each other without introducing mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplCommon::Public
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
|
||||
@@ -258,8 +258,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKind kind) {
|
||||
viableImpl(call) = result.getCallable() and
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
@@ -313,22 +313,23 @@ private predicate nodeCandFwd1(Node node, Configuration config) {
|
||||
viableParamArg(_, node, arg)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
nodeCandFwd1(mid, config) and
|
||||
parameterValueFlowsToUpdate(p, mid) and
|
||||
viableParamArg(_, p, node.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnNode ret, ReturnKind kind |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = viableReturnPos(call, kind) and
|
||||
node = getAnOutNode(call, kind)
|
||||
exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind |
|
||||
nodeCandFwd1ReturnPosition(pos, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
node = kind.getAnOutNode(call)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(ReturnNodeExt ret |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = pos
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
|
||||
exists(Node mid |
|
||||
@@ -403,22 +404,23 @@ private predicate nodeCand1(Node node, Configuration config) {
|
||||
nodeCand1(param, config)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node) and
|
||||
viableParamArg(_, p, mid.getPreUpdateNode()) and
|
||||
nodeCand1(mid, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind, OutNode out |
|
||||
nodeCand1(out, config) and
|
||||
getReturnPosition(node) = viableReturnPos(call, kind) and
|
||||
out = getAnOutNode(call, kind)
|
||||
exists(ReturnPosition pos |
|
||||
nodeCand1ReturnPosition(pos, config) and
|
||||
getReturnPosition(node) = pos
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind, Node out |
|
||||
nodeCand1(out, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
|
||||
*/
|
||||
@@ -565,28 +567,24 @@ private predicate additionalLocalFlowStepOrFlowThroughCallable(
|
||||
simpleArgumentFlowsThrough(node1, node2, _, config)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition getReturnPosition1(Node node, Configuration config) {
|
||||
result = getReturnPosition(node) and
|
||||
nodeCand1(node, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow out of a callable from `node1` to `node2`, either
|
||||
* through a `ReturnNode` or through an argument that has been mutated, and
|
||||
* that this step is part of a path from a source to a sink.
|
||||
*/
|
||||
private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) {
|
||||
nodeCand1(node1, unbind(config)) and
|
||||
nodeCand1(node2, config) and
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
(
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
)
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition1(node1, unbind(config)) = viableReturnPos(call, kind) and
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -905,30 +903,35 @@ private predicate localFlowExit(Node node, Configuration config) {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate localFlowStepPlus(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc
|
||||
) {
|
||||
localFlowEntry(node1, config) and
|
||||
not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
localFlowEntry(node1, config) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
cc.relevantFor(node1.getEnclosingCallable()) and
|
||||
not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config, cc) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config, cc) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -936,11 +939,11 @@ private predicate localFlowStepPlus(
|
||||
* Holds if `node1` can step to `node2` in one or more local steps and this
|
||||
* path can occur as a maximal subsequence of local steps in a dataflow path.
|
||||
*/
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate localFlowBigStep(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext
|
||||
) {
|
||||
localFlowStepPlus(node1, node2, preservesValue, config) and
|
||||
localFlowStepPlus(node1, node2, preservesValue, config, callContext) and
|
||||
localFlowExit(node2, config)
|
||||
}
|
||||
|
||||
@@ -1000,7 +1003,7 @@ private class AccessPathFrontNilNode extends Node {
|
||||
(
|
||||
any(Configuration c).isSource(this)
|
||||
or
|
||||
localFlowBigStep(_, this, false, _)
|
||||
localFlowBigStep(_, this, false, _, _)
|
||||
or
|
||||
additionalJumpStep(_, this, _)
|
||||
)
|
||||
@@ -1023,12 +1026,12 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
(
|
||||
exists(Node mid |
|
||||
flowCandFwd(mid, fromArg, apf, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(mid, fromArg, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
apf = node.(AccessPathFrontNilNode).getApf()
|
||||
)
|
||||
or
|
||||
@@ -1075,6 +1078,7 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
flowCandFwd(mid, fromArg, _, config) and
|
||||
store(mid, f, node) and
|
||||
nodeCand(node, unbind(config)) and
|
||||
readStoreCand(f, unbind(config)) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
or
|
||||
@@ -1121,13 +1125,13 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
apf instanceof AccessPathFrontNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flowCand(mid, toReturn, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(node, _, apf, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flowCand(mid, toReturn, nil, config) and
|
||||
apf instanceof AccessPathFrontNil
|
||||
)
|
||||
@@ -1175,12 +1179,12 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandStore(node, f, toReturn, apf0, config) and
|
||||
apf0.headUsesContent(f) and
|
||||
consCand(f, apf, unbind(config))
|
||||
consCand(f, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandRead(node, f, toReturn, apf0, config) and
|
||||
consCandFwd(f, apf0, unbind(config)) and
|
||||
consCandFwd(f, apf0, config) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
}
|
||||
@@ -1221,8 +1225,8 @@ private newtype TAccessPath =
|
||||
TConsCons(Content f1, Content f2, int len) { consCand(f1, TFrontHead(f2), _) and len in [2 .. 5] }
|
||||
|
||||
/**
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first
|
||||
* element of the list and its length are tracked. If data flows from a source to
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first two
|
||||
* elements of the list and its length are tracked. If data flows from a source to
|
||||
* a given node with a given `AccessPath`, this indicates the sequence of
|
||||
* dereference operations needed to get from the value in the node to the
|
||||
* tracked object. The final type indicates the type of the tracked object.
|
||||
@@ -1260,7 +1264,7 @@ abstract private class AccessPath extends TAccessPath {
|
||||
|
||||
private class AccessPathNil extends AccessPath, TNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -1276,7 +1280,7 @@ private class AccessPathConsNil extends AccessPathCons, TConsNil {
|
||||
override string toString() {
|
||||
exists(Content f, DataFlowType t | this = TConsNil(f, t) |
|
||||
// The `concat` becomes "" if `ppReprType` has no result.
|
||||
result = f.toString() + concat(" : " + ppReprType(t))
|
||||
result = "[" + f.toString() + "]" + concat(" : " + ppReprType(t))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1293,8 +1297,8 @@ private class AccessPathConsCons extends AccessPathCons, TConsCons {
|
||||
override string toString() {
|
||||
exists(Content f1, Content f2, int len | this = TConsCons(f1, f2, len) |
|
||||
if len = 2
|
||||
then result = f1.toString() + ", " + f2.toString()
|
||||
else result = f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f1.toString() + ", " + f2.toString() + "]"
|
||||
else result = "[" + f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1362,12 +1366,12 @@ private predicate flowFwd0(
|
||||
(
|
||||
exists(Node mid |
|
||||
flowFwd(mid, fromArg, apf, ap, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(mid, fromArg, _, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
ap = node.(AccessPathNilNode).getAp() and
|
||||
apf = ap.(AccessPathNil).getFront()
|
||||
)
|
||||
@@ -1471,13 +1475,13 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio
|
||||
ap instanceof AccessPathNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flow(mid, toReturn, ap, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(node, _, _, ap, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flow(mid, toReturn, nil, config) and
|
||||
ap instanceof AccessPathNil
|
||||
)
|
||||
@@ -1618,11 +1622,14 @@ abstract class PathNode extends TPathNode {
|
||||
/** Gets a successor of this node, if any. */
|
||||
abstract PathNode getASuccessor();
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
abstract predicate isSource();
|
||||
|
||||
private string ppAp() {
|
||||
this instanceof PathNodeSink and result = ""
|
||||
or
|
||||
exists(string s | s = this.(PathNodeMid).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1683,12 +1690,6 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = getSuccMid()
|
||||
or
|
||||
// a final step to a sink via one or more local steps
|
||||
localFlowStepPlus(node, result.getNode(), _, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid |
|
||||
mid = getSuccMid() and
|
||||
@@ -1697,23 +1698,12 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(mid.getConfiguration())
|
||||
)
|
||||
or
|
||||
// a direct step from a source to a sink if a node is both
|
||||
this instanceof PathNodeSource and
|
||||
result instanceof PathNodeSink and
|
||||
this.getNode() = result.getNode() and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow graph node corresponding to a source.
|
||||
*/
|
||||
private class PathNodeSource extends PathNodeMid {
|
||||
PathNodeSource() {
|
||||
getConfiguration().isSource(getNode()) and
|
||||
getCallContext() instanceof CallContextAny and
|
||||
getAp() instanceof AccessPathNil
|
||||
override predicate isSource() {
|
||||
config.isSource(node) and
|
||||
cc instanceof CallContextAny and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1733,6 +1723,8 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessor() { none() }
|
||||
|
||||
override predicate isSource() { config.isSource(node) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1740,14 +1732,20 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
* a callable is recorded by `cc`.
|
||||
*/
|
||||
private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPath ap) {
|
||||
localFlowBigStep(mid.getNode(), node, true, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp()
|
||||
or
|
||||
localFlowBigStep(mid.getNode(), node, false, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf |
|
||||
midnode = mid.getNode() and
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, conf, localCC) and
|
||||
ap = ap0
|
||||
or
|
||||
localFlowBigStep(midnode, node, false, conf, localCC) and
|
||||
ap0 instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, mid.getConfiguration()) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -1762,8 +1760,6 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
|
||||
or
|
||||
exists(Content f, AccessPath ap0 | contentStoreStep(mid, node, ap0, f, cc) and push(ap0, f, ap))
|
||||
or
|
||||
pathOutOfArgument(mid, node, cc) and ap = mid.getAp()
|
||||
or
|
||||
pathIntoCallable(mid, node, _, cc, _) and ap = mid.getAp()
|
||||
or
|
||||
pathOutOfCallable(mid, node, cc) and ap = mid.getAp()
|
||||
@@ -1797,9 +1793,9 @@ private predicate pathOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallCo
|
||||
not innercc instanceof CallContextCall
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathOutOfCallable1(
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKind kind, CallContext cc
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
pathOutOfCallable0(mid, pos, innercc) and
|
||||
@@ -1816,29 +1812,9 @@ private predicate pathOutOfCallable1(
|
||||
* is a return from a callable and is recorded by `cc`, if needed.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, OutNode out, CallContext cc) {
|
||||
exists(ReturnKind kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate pathOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
flow(node, unbind(mid.getConfiguration()))
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, Node out, CallContext cc) {
|
||||
exists(ReturnKindExt kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1891,7 +1867,7 @@ private predicate pathIntoCallable(
|
||||
pathIntoCallable0(mid, callable, i, outercc, call, emptyAp) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -1900,9 +1876,9 @@ private predicate pathIntoCallable(
|
||||
/** Holds if data may flow from `p` to a return of kind `kind`. */
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThrough(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(PathNodeMid mid, ReturnNode ret |
|
||||
exists(PathNodeMid mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -1917,14 +1893,14 @@ private predicate paramFlowsThrough(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathThroughCallable0(
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKind kind, CallContext cc, AccessPathNil apnil
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc |
|
||||
pathIntoCallable(mid, p, cc, innercc, call) and
|
||||
paramFlowsThrough(p, kind, innercc, apnil, unbind(mid.getConfiguration())) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc) and
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc) and
|
||||
mid.getAp() instanceof AccessPathNil
|
||||
)
|
||||
}
|
||||
@@ -1934,12 +1910,10 @@ private predicate pathThroughCallable0(
|
||||
* The context `cc` is restored to its value prior to entering the callable.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathThroughCallable(
|
||||
PathNodeMid mid, OutNode out, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
private predicate pathThroughCallable(PathNodeMid mid, Node out, CallContext cc, AccessPathNil apnil) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
pathThroughCallable0(call, mid, kind, cc, apnil) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1967,12 +1941,12 @@ private predicate valuePathThroughCallable(PathNodeMid mid, OutNode out, CallCon
|
||||
* sinks.
|
||||
*/
|
||||
private predicate flowsTo(
|
||||
PathNodeSource flowsource, PathNodeSink flowsink, Node source, Node sink,
|
||||
Configuration configuration
|
||||
PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration
|
||||
) {
|
||||
flowsource.isSource() and
|
||||
flowsource.getConfiguration() = configuration and
|
||||
flowsource.getNode() = source and
|
||||
pathSuccPlus(flowsource, flowsink) and
|
||||
(flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
|
||||
flowsink.getNode() = sink
|
||||
}
|
||||
|
||||
@@ -1996,16 +1970,10 @@ private module FlowExploration {
|
||||
// flow into callable
|
||||
viableParamArg(_, node2, node1)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
|
|
||||
c1 = node1.getEnclosingCallable() and
|
||||
@@ -2082,7 +2050,7 @@ private module FlowExploration {
|
||||
|
||||
private class PartialAccessPathNil extends PartialAccessPath, TPartialNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -2094,8 +2062,8 @@ private module FlowExploration {
|
||||
override string toString() {
|
||||
exists(Content f, int len | this = TPartialCons(f, len) |
|
||||
if len = 1
|
||||
then result = f.toString()
|
||||
else result = f.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f.toString() + "]"
|
||||
else result = "[" + f.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2172,7 +2140,7 @@ private module FlowExploration {
|
||||
|
||||
private string ppAp() {
|
||||
exists(string s | s = this.(PartialPathNodePriv).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2216,16 +2184,19 @@ private module FlowExploration {
|
||||
private predicate partialPathStep(
|
||||
PartialPathNodePriv mid, Node node, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -2247,8 +2218,6 @@ private module FlowExploration {
|
||||
apConsFwd(ap, f, ap0, config)
|
||||
)
|
||||
or
|
||||
partialPathOutOfArgument(mid, node, cc, ap, config)
|
||||
or
|
||||
partialPathIntoCallable(mid, node, _, cc, _, ap, config)
|
||||
or
|
||||
partialPathOutOfCallable(mid, node, cc, ap, config)
|
||||
@@ -2307,7 +2276,7 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathOutOfCallable1(
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKind kind, CallContext cc,
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
@@ -2321,36 +2290,12 @@ private module FlowExploration {
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnKind kind, DataFlowCall call |
|
||||
exists(ReturnKindExt kind, DataFlowCall call |
|
||||
partialPathOutOfCallable1(mid, call, kind, cc, ap, config)
|
||||
|
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfArgument(
|
||||
PartialPathNodePriv mid, PostUpdateNode node, CallContext cc, PartialAccessPath ap,
|
||||
Configuration config
|
||||
) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2389,7 +2334,7 @@ private module FlowExploration {
|
||||
partialPathIntoCallable0(mid, callable, i, outercc, call, emptyAp, ap, config) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -2397,10 +2342,10 @@ private module FlowExploration {
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThroughInPartialPath(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(PartialPathNodePriv mid, ReturnNode ret |
|
||||
exists(PartialPathNodePriv mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -2417,23 +2362,23 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathThroughCallable0(
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKind kind, CallContext cc,
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc, PartialAccessPathNil midapnil |
|
||||
partialPathIntoCallable(mid, p, cc, innercc, call, midapnil, config) and
|
||||
paramFlowsThroughInPartialPath(p, kind, innercc, apnil, config) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc)
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathThroughCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPathNil apnil,
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
partialPathThroughCallable0(call, mid, kind, cc, apnil, config) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* on each other without introducing mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplCommon::Public
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
|
||||
@@ -258,8 +258,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKind kind) {
|
||||
viableImpl(call) = result.getCallable() and
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
@@ -313,22 +313,23 @@ private predicate nodeCandFwd1(Node node, Configuration config) {
|
||||
viableParamArg(_, node, arg)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
nodeCandFwd1(mid, config) and
|
||||
parameterValueFlowsToUpdate(p, mid) and
|
||||
viableParamArg(_, p, node.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnNode ret, ReturnKind kind |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = viableReturnPos(call, kind) and
|
||||
node = getAnOutNode(call, kind)
|
||||
exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind |
|
||||
nodeCandFwd1ReturnPosition(pos, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
node = kind.getAnOutNode(call)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(ReturnNodeExt ret |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = pos
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
|
||||
exists(Node mid |
|
||||
@@ -403,22 +404,23 @@ private predicate nodeCand1(Node node, Configuration config) {
|
||||
nodeCand1(param, config)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node) and
|
||||
viableParamArg(_, p, mid.getPreUpdateNode()) and
|
||||
nodeCand1(mid, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind, OutNode out |
|
||||
nodeCand1(out, config) and
|
||||
getReturnPosition(node) = viableReturnPos(call, kind) and
|
||||
out = getAnOutNode(call, kind)
|
||||
exists(ReturnPosition pos |
|
||||
nodeCand1ReturnPosition(pos, config) and
|
||||
getReturnPosition(node) = pos
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind, Node out |
|
||||
nodeCand1(out, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
|
||||
*/
|
||||
@@ -565,28 +567,24 @@ private predicate additionalLocalFlowStepOrFlowThroughCallable(
|
||||
simpleArgumentFlowsThrough(node1, node2, _, config)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition getReturnPosition1(Node node, Configuration config) {
|
||||
result = getReturnPosition(node) and
|
||||
nodeCand1(node, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow out of a callable from `node1` to `node2`, either
|
||||
* through a `ReturnNode` or through an argument that has been mutated, and
|
||||
* that this step is part of a path from a source to a sink.
|
||||
*/
|
||||
private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) {
|
||||
nodeCand1(node1, unbind(config)) and
|
||||
nodeCand1(node2, config) and
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
(
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
)
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition1(node1, unbind(config)) = viableReturnPos(call, kind) and
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -905,30 +903,35 @@ private predicate localFlowExit(Node node, Configuration config) {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate localFlowStepPlus(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc
|
||||
) {
|
||||
localFlowEntry(node1, config) and
|
||||
not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
localFlowEntry(node1, config) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
cc.relevantFor(node1.getEnclosingCallable()) and
|
||||
not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config, cc) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config, cc) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -936,11 +939,11 @@ private predicate localFlowStepPlus(
|
||||
* Holds if `node1` can step to `node2` in one or more local steps and this
|
||||
* path can occur as a maximal subsequence of local steps in a dataflow path.
|
||||
*/
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate localFlowBigStep(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext
|
||||
) {
|
||||
localFlowStepPlus(node1, node2, preservesValue, config) and
|
||||
localFlowStepPlus(node1, node2, preservesValue, config, callContext) and
|
||||
localFlowExit(node2, config)
|
||||
}
|
||||
|
||||
@@ -1000,7 +1003,7 @@ private class AccessPathFrontNilNode extends Node {
|
||||
(
|
||||
any(Configuration c).isSource(this)
|
||||
or
|
||||
localFlowBigStep(_, this, false, _)
|
||||
localFlowBigStep(_, this, false, _, _)
|
||||
or
|
||||
additionalJumpStep(_, this, _)
|
||||
)
|
||||
@@ -1023,12 +1026,12 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
(
|
||||
exists(Node mid |
|
||||
flowCandFwd(mid, fromArg, apf, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(mid, fromArg, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
apf = node.(AccessPathFrontNilNode).getApf()
|
||||
)
|
||||
or
|
||||
@@ -1075,6 +1078,7 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
flowCandFwd(mid, fromArg, _, config) and
|
||||
store(mid, f, node) and
|
||||
nodeCand(node, unbind(config)) and
|
||||
readStoreCand(f, unbind(config)) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
or
|
||||
@@ -1121,13 +1125,13 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
apf instanceof AccessPathFrontNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flowCand(mid, toReturn, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(node, _, apf, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flowCand(mid, toReturn, nil, config) and
|
||||
apf instanceof AccessPathFrontNil
|
||||
)
|
||||
@@ -1175,12 +1179,12 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandStore(node, f, toReturn, apf0, config) and
|
||||
apf0.headUsesContent(f) and
|
||||
consCand(f, apf, unbind(config))
|
||||
consCand(f, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandRead(node, f, toReturn, apf0, config) and
|
||||
consCandFwd(f, apf0, unbind(config)) and
|
||||
consCandFwd(f, apf0, config) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
}
|
||||
@@ -1221,8 +1225,8 @@ private newtype TAccessPath =
|
||||
TConsCons(Content f1, Content f2, int len) { consCand(f1, TFrontHead(f2), _) and len in [2 .. 5] }
|
||||
|
||||
/**
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first
|
||||
* element of the list and its length are tracked. If data flows from a source to
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first two
|
||||
* elements of the list and its length are tracked. If data flows from a source to
|
||||
* a given node with a given `AccessPath`, this indicates the sequence of
|
||||
* dereference operations needed to get from the value in the node to the
|
||||
* tracked object. The final type indicates the type of the tracked object.
|
||||
@@ -1260,7 +1264,7 @@ abstract private class AccessPath extends TAccessPath {
|
||||
|
||||
private class AccessPathNil extends AccessPath, TNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -1276,7 +1280,7 @@ private class AccessPathConsNil extends AccessPathCons, TConsNil {
|
||||
override string toString() {
|
||||
exists(Content f, DataFlowType t | this = TConsNil(f, t) |
|
||||
// The `concat` becomes "" if `ppReprType` has no result.
|
||||
result = f.toString() + concat(" : " + ppReprType(t))
|
||||
result = "[" + f.toString() + "]" + concat(" : " + ppReprType(t))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1293,8 +1297,8 @@ private class AccessPathConsCons extends AccessPathCons, TConsCons {
|
||||
override string toString() {
|
||||
exists(Content f1, Content f2, int len | this = TConsCons(f1, f2, len) |
|
||||
if len = 2
|
||||
then result = f1.toString() + ", " + f2.toString()
|
||||
else result = f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f1.toString() + ", " + f2.toString() + "]"
|
||||
else result = "[" + f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1362,12 +1366,12 @@ private predicate flowFwd0(
|
||||
(
|
||||
exists(Node mid |
|
||||
flowFwd(mid, fromArg, apf, ap, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(mid, fromArg, _, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
ap = node.(AccessPathNilNode).getAp() and
|
||||
apf = ap.(AccessPathNil).getFront()
|
||||
)
|
||||
@@ -1471,13 +1475,13 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio
|
||||
ap instanceof AccessPathNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flow(mid, toReturn, ap, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(node, _, _, ap, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flow(mid, toReturn, nil, config) and
|
||||
ap instanceof AccessPathNil
|
||||
)
|
||||
@@ -1618,11 +1622,14 @@ abstract class PathNode extends TPathNode {
|
||||
/** Gets a successor of this node, if any. */
|
||||
abstract PathNode getASuccessor();
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
abstract predicate isSource();
|
||||
|
||||
private string ppAp() {
|
||||
this instanceof PathNodeSink and result = ""
|
||||
or
|
||||
exists(string s | s = this.(PathNodeMid).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1683,12 +1690,6 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = getSuccMid()
|
||||
or
|
||||
// a final step to a sink via one or more local steps
|
||||
localFlowStepPlus(node, result.getNode(), _, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid |
|
||||
mid = getSuccMid() and
|
||||
@@ -1697,23 +1698,12 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(mid.getConfiguration())
|
||||
)
|
||||
or
|
||||
// a direct step from a source to a sink if a node is both
|
||||
this instanceof PathNodeSource and
|
||||
result instanceof PathNodeSink and
|
||||
this.getNode() = result.getNode() and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow graph node corresponding to a source.
|
||||
*/
|
||||
private class PathNodeSource extends PathNodeMid {
|
||||
PathNodeSource() {
|
||||
getConfiguration().isSource(getNode()) and
|
||||
getCallContext() instanceof CallContextAny and
|
||||
getAp() instanceof AccessPathNil
|
||||
override predicate isSource() {
|
||||
config.isSource(node) and
|
||||
cc instanceof CallContextAny and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1733,6 +1723,8 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessor() { none() }
|
||||
|
||||
override predicate isSource() { config.isSource(node) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1740,14 +1732,20 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
* a callable is recorded by `cc`.
|
||||
*/
|
||||
private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPath ap) {
|
||||
localFlowBigStep(mid.getNode(), node, true, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp()
|
||||
or
|
||||
localFlowBigStep(mid.getNode(), node, false, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf |
|
||||
midnode = mid.getNode() and
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, conf, localCC) and
|
||||
ap = ap0
|
||||
or
|
||||
localFlowBigStep(midnode, node, false, conf, localCC) and
|
||||
ap0 instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, mid.getConfiguration()) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -1762,8 +1760,6 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
|
||||
or
|
||||
exists(Content f, AccessPath ap0 | contentStoreStep(mid, node, ap0, f, cc) and push(ap0, f, ap))
|
||||
or
|
||||
pathOutOfArgument(mid, node, cc) and ap = mid.getAp()
|
||||
or
|
||||
pathIntoCallable(mid, node, _, cc, _) and ap = mid.getAp()
|
||||
or
|
||||
pathOutOfCallable(mid, node, cc) and ap = mid.getAp()
|
||||
@@ -1797,9 +1793,9 @@ private predicate pathOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallCo
|
||||
not innercc instanceof CallContextCall
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathOutOfCallable1(
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKind kind, CallContext cc
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
pathOutOfCallable0(mid, pos, innercc) and
|
||||
@@ -1816,29 +1812,9 @@ private predicate pathOutOfCallable1(
|
||||
* is a return from a callable and is recorded by `cc`, if needed.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, OutNode out, CallContext cc) {
|
||||
exists(ReturnKind kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate pathOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
flow(node, unbind(mid.getConfiguration()))
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, Node out, CallContext cc) {
|
||||
exists(ReturnKindExt kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1891,7 +1867,7 @@ private predicate pathIntoCallable(
|
||||
pathIntoCallable0(mid, callable, i, outercc, call, emptyAp) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -1900,9 +1876,9 @@ private predicate pathIntoCallable(
|
||||
/** Holds if data may flow from `p` to a return of kind `kind`. */
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThrough(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(PathNodeMid mid, ReturnNode ret |
|
||||
exists(PathNodeMid mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -1917,14 +1893,14 @@ private predicate paramFlowsThrough(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathThroughCallable0(
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKind kind, CallContext cc, AccessPathNil apnil
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc |
|
||||
pathIntoCallable(mid, p, cc, innercc, call) and
|
||||
paramFlowsThrough(p, kind, innercc, apnil, unbind(mid.getConfiguration())) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc) and
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc) and
|
||||
mid.getAp() instanceof AccessPathNil
|
||||
)
|
||||
}
|
||||
@@ -1934,12 +1910,10 @@ private predicate pathThroughCallable0(
|
||||
* The context `cc` is restored to its value prior to entering the callable.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathThroughCallable(
|
||||
PathNodeMid mid, OutNode out, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
private predicate pathThroughCallable(PathNodeMid mid, Node out, CallContext cc, AccessPathNil apnil) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
pathThroughCallable0(call, mid, kind, cc, apnil) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1967,12 +1941,12 @@ private predicate valuePathThroughCallable(PathNodeMid mid, OutNode out, CallCon
|
||||
* sinks.
|
||||
*/
|
||||
private predicate flowsTo(
|
||||
PathNodeSource flowsource, PathNodeSink flowsink, Node source, Node sink,
|
||||
Configuration configuration
|
||||
PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration
|
||||
) {
|
||||
flowsource.isSource() and
|
||||
flowsource.getConfiguration() = configuration and
|
||||
flowsource.getNode() = source and
|
||||
pathSuccPlus(flowsource, flowsink) and
|
||||
(flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
|
||||
flowsink.getNode() = sink
|
||||
}
|
||||
|
||||
@@ -1996,16 +1970,10 @@ private module FlowExploration {
|
||||
// flow into callable
|
||||
viableParamArg(_, node2, node1)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
|
|
||||
c1 = node1.getEnclosingCallable() and
|
||||
@@ -2082,7 +2050,7 @@ private module FlowExploration {
|
||||
|
||||
private class PartialAccessPathNil extends PartialAccessPath, TPartialNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -2094,8 +2062,8 @@ private module FlowExploration {
|
||||
override string toString() {
|
||||
exists(Content f, int len | this = TPartialCons(f, len) |
|
||||
if len = 1
|
||||
then result = f.toString()
|
||||
else result = f.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f.toString() + "]"
|
||||
else result = "[" + f.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2172,7 +2140,7 @@ private module FlowExploration {
|
||||
|
||||
private string ppAp() {
|
||||
exists(string s | s = this.(PartialPathNodePriv).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2216,16 +2184,19 @@ private module FlowExploration {
|
||||
private predicate partialPathStep(
|
||||
PartialPathNodePriv mid, Node node, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -2247,8 +2218,6 @@ private module FlowExploration {
|
||||
apConsFwd(ap, f, ap0, config)
|
||||
)
|
||||
or
|
||||
partialPathOutOfArgument(mid, node, cc, ap, config)
|
||||
or
|
||||
partialPathIntoCallable(mid, node, _, cc, _, ap, config)
|
||||
or
|
||||
partialPathOutOfCallable(mid, node, cc, ap, config)
|
||||
@@ -2307,7 +2276,7 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathOutOfCallable1(
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKind kind, CallContext cc,
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
@@ -2321,36 +2290,12 @@ private module FlowExploration {
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnKind kind, DataFlowCall call |
|
||||
exists(ReturnKindExt kind, DataFlowCall call |
|
||||
partialPathOutOfCallable1(mid, call, kind, cc, ap, config)
|
||||
|
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfArgument(
|
||||
PartialPathNodePriv mid, PostUpdateNode node, CallContext cc, PartialAccessPath ap,
|
||||
Configuration config
|
||||
) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2389,7 +2334,7 @@ private module FlowExploration {
|
||||
partialPathIntoCallable0(mid, callable, i, outercc, call, emptyAp, ap, config) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -2397,10 +2342,10 @@ private module FlowExploration {
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThroughInPartialPath(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(PartialPathNodePriv mid, ReturnNode ret |
|
||||
exists(PartialPathNodePriv mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -2417,23 +2362,23 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathThroughCallable0(
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKind kind, CallContext cc,
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc, PartialAccessPathNil midapnil |
|
||||
partialPathIntoCallable(mid, p, cc, innercc, call, midapnil, config) and
|
||||
paramFlowsThroughInPartialPath(p, kind, innercc, apnil, config) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc)
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathThroughCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPathNil apnil,
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
partialPathThroughCallable0(call, mid, kind, cc, apnil, config) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* on each other without introducing mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplCommon::Public
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
|
||||
@@ -258,8 +258,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKind kind) {
|
||||
viableImpl(call) = result.getCallable() and
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
@@ -313,22 +313,23 @@ private predicate nodeCandFwd1(Node node, Configuration config) {
|
||||
viableParamArg(_, node, arg)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
nodeCandFwd1(mid, config) and
|
||||
parameterValueFlowsToUpdate(p, mid) and
|
||||
viableParamArg(_, p, node.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnNode ret, ReturnKind kind |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = viableReturnPos(call, kind) and
|
||||
node = getAnOutNode(call, kind)
|
||||
exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind |
|
||||
nodeCandFwd1ReturnPosition(pos, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
node = kind.getAnOutNode(call)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(ReturnNodeExt ret |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = pos
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
|
||||
exists(Node mid |
|
||||
@@ -403,22 +404,23 @@ private predicate nodeCand1(Node node, Configuration config) {
|
||||
nodeCand1(param, config)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node) and
|
||||
viableParamArg(_, p, mid.getPreUpdateNode()) and
|
||||
nodeCand1(mid, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind, OutNode out |
|
||||
nodeCand1(out, config) and
|
||||
getReturnPosition(node) = viableReturnPos(call, kind) and
|
||||
out = getAnOutNode(call, kind)
|
||||
exists(ReturnPosition pos |
|
||||
nodeCand1ReturnPosition(pos, config) and
|
||||
getReturnPosition(node) = pos
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind, Node out |
|
||||
nodeCand1(out, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
|
||||
*/
|
||||
@@ -565,28 +567,24 @@ private predicate additionalLocalFlowStepOrFlowThroughCallable(
|
||||
simpleArgumentFlowsThrough(node1, node2, _, config)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition getReturnPosition1(Node node, Configuration config) {
|
||||
result = getReturnPosition(node) and
|
||||
nodeCand1(node, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow out of a callable from `node1` to `node2`, either
|
||||
* through a `ReturnNode` or through an argument that has been mutated, and
|
||||
* that this step is part of a path from a source to a sink.
|
||||
*/
|
||||
private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) {
|
||||
nodeCand1(node1, unbind(config)) and
|
||||
nodeCand1(node2, config) and
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
(
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
)
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition1(node1, unbind(config)) = viableReturnPos(call, kind) and
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -905,30 +903,35 @@ private predicate localFlowExit(Node node, Configuration config) {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate localFlowStepPlus(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc
|
||||
) {
|
||||
localFlowEntry(node1, config) and
|
||||
not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
localFlowEntry(node1, config) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
cc.relevantFor(node1.getEnclosingCallable()) and
|
||||
not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config, cc) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config, cc) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -936,11 +939,11 @@ private predicate localFlowStepPlus(
|
||||
* Holds if `node1` can step to `node2` in one or more local steps and this
|
||||
* path can occur as a maximal subsequence of local steps in a dataflow path.
|
||||
*/
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate localFlowBigStep(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext
|
||||
) {
|
||||
localFlowStepPlus(node1, node2, preservesValue, config) and
|
||||
localFlowStepPlus(node1, node2, preservesValue, config, callContext) and
|
||||
localFlowExit(node2, config)
|
||||
}
|
||||
|
||||
@@ -1000,7 +1003,7 @@ private class AccessPathFrontNilNode extends Node {
|
||||
(
|
||||
any(Configuration c).isSource(this)
|
||||
or
|
||||
localFlowBigStep(_, this, false, _)
|
||||
localFlowBigStep(_, this, false, _, _)
|
||||
or
|
||||
additionalJumpStep(_, this, _)
|
||||
)
|
||||
@@ -1023,12 +1026,12 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
(
|
||||
exists(Node mid |
|
||||
flowCandFwd(mid, fromArg, apf, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(mid, fromArg, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
apf = node.(AccessPathFrontNilNode).getApf()
|
||||
)
|
||||
or
|
||||
@@ -1075,6 +1078,7 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
flowCandFwd(mid, fromArg, _, config) and
|
||||
store(mid, f, node) and
|
||||
nodeCand(node, unbind(config)) and
|
||||
readStoreCand(f, unbind(config)) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
or
|
||||
@@ -1121,13 +1125,13 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
apf instanceof AccessPathFrontNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flowCand(mid, toReturn, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(node, _, apf, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flowCand(mid, toReturn, nil, config) and
|
||||
apf instanceof AccessPathFrontNil
|
||||
)
|
||||
@@ -1175,12 +1179,12 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandStore(node, f, toReturn, apf0, config) and
|
||||
apf0.headUsesContent(f) and
|
||||
consCand(f, apf, unbind(config))
|
||||
consCand(f, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandRead(node, f, toReturn, apf0, config) and
|
||||
consCandFwd(f, apf0, unbind(config)) and
|
||||
consCandFwd(f, apf0, config) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
}
|
||||
@@ -1221,8 +1225,8 @@ private newtype TAccessPath =
|
||||
TConsCons(Content f1, Content f2, int len) { consCand(f1, TFrontHead(f2), _) and len in [2 .. 5] }
|
||||
|
||||
/**
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first
|
||||
* element of the list and its length are tracked. If data flows from a source to
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first two
|
||||
* elements of the list and its length are tracked. If data flows from a source to
|
||||
* a given node with a given `AccessPath`, this indicates the sequence of
|
||||
* dereference operations needed to get from the value in the node to the
|
||||
* tracked object. The final type indicates the type of the tracked object.
|
||||
@@ -1260,7 +1264,7 @@ abstract private class AccessPath extends TAccessPath {
|
||||
|
||||
private class AccessPathNil extends AccessPath, TNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -1276,7 +1280,7 @@ private class AccessPathConsNil extends AccessPathCons, TConsNil {
|
||||
override string toString() {
|
||||
exists(Content f, DataFlowType t | this = TConsNil(f, t) |
|
||||
// The `concat` becomes "" if `ppReprType` has no result.
|
||||
result = f.toString() + concat(" : " + ppReprType(t))
|
||||
result = "[" + f.toString() + "]" + concat(" : " + ppReprType(t))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1293,8 +1297,8 @@ private class AccessPathConsCons extends AccessPathCons, TConsCons {
|
||||
override string toString() {
|
||||
exists(Content f1, Content f2, int len | this = TConsCons(f1, f2, len) |
|
||||
if len = 2
|
||||
then result = f1.toString() + ", " + f2.toString()
|
||||
else result = f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f1.toString() + ", " + f2.toString() + "]"
|
||||
else result = "[" + f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1362,12 +1366,12 @@ private predicate flowFwd0(
|
||||
(
|
||||
exists(Node mid |
|
||||
flowFwd(mid, fromArg, apf, ap, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(mid, fromArg, _, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
ap = node.(AccessPathNilNode).getAp() and
|
||||
apf = ap.(AccessPathNil).getFront()
|
||||
)
|
||||
@@ -1471,13 +1475,13 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio
|
||||
ap instanceof AccessPathNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flow(mid, toReturn, ap, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(node, _, _, ap, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flow(mid, toReturn, nil, config) and
|
||||
ap instanceof AccessPathNil
|
||||
)
|
||||
@@ -1618,11 +1622,14 @@ abstract class PathNode extends TPathNode {
|
||||
/** Gets a successor of this node, if any. */
|
||||
abstract PathNode getASuccessor();
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
abstract predicate isSource();
|
||||
|
||||
private string ppAp() {
|
||||
this instanceof PathNodeSink and result = ""
|
||||
or
|
||||
exists(string s | s = this.(PathNodeMid).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1683,12 +1690,6 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = getSuccMid()
|
||||
or
|
||||
// a final step to a sink via one or more local steps
|
||||
localFlowStepPlus(node, result.getNode(), _, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid |
|
||||
mid = getSuccMid() and
|
||||
@@ -1697,23 +1698,12 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(mid.getConfiguration())
|
||||
)
|
||||
or
|
||||
// a direct step from a source to a sink if a node is both
|
||||
this instanceof PathNodeSource and
|
||||
result instanceof PathNodeSink and
|
||||
this.getNode() = result.getNode() and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow graph node corresponding to a source.
|
||||
*/
|
||||
private class PathNodeSource extends PathNodeMid {
|
||||
PathNodeSource() {
|
||||
getConfiguration().isSource(getNode()) and
|
||||
getCallContext() instanceof CallContextAny and
|
||||
getAp() instanceof AccessPathNil
|
||||
override predicate isSource() {
|
||||
config.isSource(node) and
|
||||
cc instanceof CallContextAny and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1733,6 +1723,8 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessor() { none() }
|
||||
|
||||
override predicate isSource() { config.isSource(node) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1740,14 +1732,20 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
* a callable is recorded by `cc`.
|
||||
*/
|
||||
private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPath ap) {
|
||||
localFlowBigStep(mid.getNode(), node, true, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp()
|
||||
or
|
||||
localFlowBigStep(mid.getNode(), node, false, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf |
|
||||
midnode = mid.getNode() and
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, conf, localCC) and
|
||||
ap = ap0
|
||||
or
|
||||
localFlowBigStep(midnode, node, false, conf, localCC) and
|
||||
ap0 instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, mid.getConfiguration()) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -1762,8 +1760,6 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
|
||||
or
|
||||
exists(Content f, AccessPath ap0 | contentStoreStep(mid, node, ap0, f, cc) and push(ap0, f, ap))
|
||||
or
|
||||
pathOutOfArgument(mid, node, cc) and ap = mid.getAp()
|
||||
or
|
||||
pathIntoCallable(mid, node, _, cc, _) and ap = mid.getAp()
|
||||
or
|
||||
pathOutOfCallable(mid, node, cc) and ap = mid.getAp()
|
||||
@@ -1797,9 +1793,9 @@ private predicate pathOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallCo
|
||||
not innercc instanceof CallContextCall
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathOutOfCallable1(
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKind kind, CallContext cc
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
pathOutOfCallable0(mid, pos, innercc) and
|
||||
@@ -1816,29 +1812,9 @@ private predicate pathOutOfCallable1(
|
||||
* is a return from a callable and is recorded by `cc`, if needed.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, OutNode out, CallContext cc) {
|
||||
exists(ReturnKind kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate pathOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
flow(node, unbind(mid.getConfiguration()))
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, Node out, CallContext cc) {
|
||||
exists(ReturnKindExt kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1891,7 +1867,7 @@ private predicate pathIntoCallable(
|
||||
pathIntoCallable0(mid, callable, i, outercc, call, emptyAp) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -1900,9 +1876,9 @@ private predicate pathIntoCallable(
|
||||
/** Holds if data may flow from `p` to a return of kind `kind`. */
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThrough(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(PathNodeMid mid, ReturnNode ret |
|
||||
exists(PathNodeMid mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -1917,14 +1893,14 @@ private predicate paramFlowsThrough(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathThroughCallable0(
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKind kind, CallContext cc, AccessPathNil apnil
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc |
|
||||
pathIntoCallable(mid, p, cc, innercc, call) and
|
||||
paramFlowsThrough(p, kind, innercc, apnil, unbind(mid.getConfiguration())) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc) and
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc) and
|
||||
mid.getAp() instanceof AccessPathNil
|
||||
)
|
||||
}
|
||||
@@ -1934,12 +1910,10 @@ private predicate pathThroughCallable0(
|
||||
* The context `cc` is restored to its value prior to entering the callable.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathThroughCallable(
|
||||
PathNodeMid mid, OutNode out, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
private predicate pathThroughCallable(PathNodeMid mid, Node out, CallContext cc, AccessPathNil apnil) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
pathThroughCallable0(call, mid, kind, cc, apnil) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1967,12 +1941,12 @@ private predicate valuePathThroughCallable(PathNodeMid mid, OutNode out, CallCon
|
||||
* sinks.
|
||||
*/
|
||||
private predicate flowsTo(
|
||||
PathNodeSource flowsource, PathNodeSink flowsink, Node source, Node sink,
|
||||
Configuration configuration
|
||||
PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration
|
||||
) {
|
||||
flowsource.isSource() and
|
||||
flowsource.getConfiguration() = configuration and
|
||||
flowsource.getNode() = source and
|
||||
pathSuccPlus(flowsource, flowsink) and
|
||||
(flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
|
||||
flowsink.getNode() = sink
|
||||
}
|
||||
|
||||
@@ -1996,16 +1970,10 @@ private module FlowExploration {
|
||||
// flow into callable
|
||||
viableParamArg(_, node2, node1)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
|
|
||||
c1 = node1.getEnclosingCallable() and
|
||||
@@ -2082,7 +2050,7 @@ private module FlowExploration {
|
||||
|
||||
private class PartialAccessPathNil extends PartialAccessPath, TPartialNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -2094,8 +2062,8 @@ private module FlowExploration {
|
||||
override string toString() {
|
||||
exists(Content f, int len | this = TPartialCons(f, len) |
|
||||
if len = 1
|
||||
then result = f.toString()
|
||||
else result = f.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f.toString() + "]"
|
||||
else result = "[" + f.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2172,7 +2140,7 @@ private module FlowExploration {
|
||||
|
||||
private string ppAp() {
|
||||
exists(string s | s = this.(PartialPathNodePriv).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2216,16 +2184,19 @@ private module FlowExploration {
|
||||
private predicate partialPathStep(
|
||||
PartialPathNodePriv mid, Node node, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -2247,8 +2218,6 @@ private module FlowExploration {
|
||||
apConsFwd(ap, f, ap0, config)
|
||||
)
|
||||
or
|
||||
partialPathOutOfArgument(mid, node, cc, ap, config)
|
||||
or
|
||||
partialPathIntoCallable(mid, node, _, cc, _, ap, config)
|
||||
or
|
||||
partialPathOutOfCallable(mid, node, cc, ap, config)
|
||||
@@ -2307,7 +2276,7 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathOutOfCallable1(
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKind kind, CallContext cc,
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
@@ -2321,36 +2290,12 @@ private module FlowExploration {
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnKind kind, DataFlowCall call |
|
||||
exists(ReturnKindExt kind, DataFlowCall call |
|
||||
partialPathOutOfCallable1(mid, call, kind, cc, ap, config)
|
||||
|
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfArgument(
|
||||
PartialPathNodePriv mid, PostUpdateNode node, CallContext cc, PartialAccessPath ap,
|
||||
Configuration config
|
||||
) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2389,7 +2334,7 @@ private module FlowExploration {
|
||||
partialPathIntoCallable0(mid, callable, i, outercc, call, emptyAp, ap, config) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -2397,10 +2342,10 @@ private module FlowExploration {
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThroughInPartialPath(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(PartialPathNodePriv mid, ReturnNode ret |
|
||||
exists(PartialPathNodePriv mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -2417,23 +2362,23 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathThroughCallable0(
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKind kind, CallContext cc,
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc, PartialAccessPathNil midapnil |
|
||||
partialPathIntoCallable(mid, p, cc, innercc, call, midapnil, config) and
|
||||
paramFlowsThroughInPartialPath(p, kind, innercc, apnil, config) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc)
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathThroughCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPathNil apnil,
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
partialPathThroughCallable0(call, mid, kind, cc, apnil, config) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
* on each other without introducing mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplCommon::Public
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
|
||||
@@ -258,8 +258,8 @@ private predicate additionalJumpStep(Node node1, Node node2, Configuration confi
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKind kind) {
|
||||
viableImpl(call) = result.getCallable() and
|
||||
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
|
||||
viableCallable(call) = result.getCallable() and
|
||||
kind = result.getKind()
|
||||
}
|
||||
|
||||
@@ -313,22 +313,23 @@ private predicate nodeCandFwd1(Node node, Configuration config) {
|
||||
viableParamArg(_, node, arg)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
nodeCandFwd1(mid, config) and
|
||||
parameterValueFlowsToUpdate(p, mid) and
|
||||
viableParamArg(_, p, node.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnNode ret, ReturnKind kind |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = viableReturnPos(call, kind) and
|
||||
node = getAnOutNode(call, kind)
|
||||
exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind |
|
||||
nodeCandFwd1ReturnPosition(pos, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
node = kind.getAnOutNode(call)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(ReturnNodeExt ret |
|
||||
nodeCandFwd1(ret, config) and
|
||||
getReturnPosition(ret) = pos
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) {
|
||||
exists(Node mid |
|
||||
@@ -403,22 +404,23 @@ private predicate nodeCand1(Node node, Configuration config) {
|
||||
nodeCand1(param, config)
|
||||
)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(PostUpdateNode mid, ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node) and
|
||||
viableParamArg(_, p, mid.getPreUpdateNode()) and
|
||||
nodeCand1(mid, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind, OutNode out |
|
||||
nodeCand1(out, config) and
|
||||
getReturnPosition(node) = viableReturnPos(call, kind) and
|
||||
out = getAnOutNode(call, kind)
|
||||
exists(ReturnPosition pos |
|
||||
nodeCand1ReturnPosition(pos, config) and
|
||||
getReturnPosition(node) = pos
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind, Node out |
|
||||
nodeCand1(out, config) and
|
||||
pos = viableReturnPos(call, kind) and
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` is the target of a read in the flow covered by `nodeCand1`.
|
||||
*/
|
||||
@@ -565,28 +567,24 @@ private predicate additionalLocalFlowStepOrFlowThroughCallable(
|
||||
simpleArgumentFlowsThrough(node1, node2, _, config)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private ReturnPosition getReturnPosition1(Node node, Configuration config) {
|
||||
result = getReturnPosition(node) and
|
||||
nodeCand1(node, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow out of a callable from `node1` to `node2`, either
|
||||
* through a `ReturnNode` or through an argument that has been mutated, and
|
||||
* that this step is part of a path from a source to a sink.
|
||||
*/
|
||||
private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) {
|
||||
nodeCand1(node1, unbind(config)) and
|
||||
nodeCand1(node2, config) and
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
(
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
)
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition1(node1, unbind(config)) = viableReturnPos(call, kind) and
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -905,30 +903,35 @@ private predicate localFlowExit(Node node, Configuration config) {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate localFlowStepPlus(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc
|
||||
) {
|
||||
localFlowEntry(node1, config) and
|
||||
not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
localFlowEntry(node1, config) and
|
||||
(
|
||||
localFlowStep(node1, node2, config) and preservesValue = true
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
cc.relevantFor(node1.getEnclosingCallable()) and
|
||||
not isUnreachableInCall(node1, cc.(LocalCallContextSpecificCall).getCall()) and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
additionalLocalFlowStep(node1, node2, config) and preservesValue = false
|
||||
) and
|
||||
node1 != node2 and
|
||||
nodeCand(node2, unbind(config))
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, preservesValue, config, cc) and
|
||||
localFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowStepPlus(node1, mid, _, config, cc) and
|
||||
additionalLocalFlowStep(mid, node2, config) and
|
||||
not mid instanceof CastNode and
|
||||
preservesValue = false and
|
||||
nodeCand(node2, unbind(config))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -936,11 +939,11 @@ private predicate localFlowStepPlus(
|
||||
* Holds if `node1` can step to `node2` in one or more local steps and this
|
||||
* path can occur as a maximal subsequence of local steps in a dataflow path.
|
||||
*/
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate localFlowBigStep(
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config
|
||||
Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext
|
||||
) {
|
||||
localFlowStepPlus(node1, node2, preservesValue, config) and
|
||||
localFlowStepPlus(node1, node2, preservesValue, config, callContext) and
|
||||
localFlowExit(node2, config)
|
||||
}
|
||||
|
||||
@@ -1000,7 +1003,7 @@ private class AccessPathFrontNilNode extends Node {
|
||||
(
|
||||
any(Configuration c).isSource(this)
|
||||
or
|
||||
localFlowBigStep(_, this, false, _)
|
||||
localFlowBigStep(_, this, false, _, _)
|
||||
or
|
||||
additionalJumpStep(_, this, _)
|
||||
)
|
||||
@@ -1023,12 +1026,12 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
(
|
||||
exists(Node mid |
|
||||
flowCandFwd(mid, fromArg, apf, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(mid, fromArg, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
apf = node.(AccessPathFrontNilNode).getApf()
|
||||
)
|
||||
or
|
||||
@@ -1075,6 +1078,7 @@ private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf,
|
||||
flowCandFwd(mid, fromArg, _, config) and
|
||||
store(mid, f, node) and
|
||||
nodeCand(node, unbind(config)) and
|
||||
readStoreCand(f, unbind(config)) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
or
|
||||
@@ -1121,13 +1125,13 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
apf instanceof AccessPathFrontNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flowCand(mid, toReturn, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathFrontNil nil |
|
||||
flowCandFwd(node, _, apf, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flowCand(mid, toReturn, nil, config) and
|
||||
apf instanceof AccessPathFrontNil
|
||||
)
|
||||
@@ -1175,12 +1179,12 @@ private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Co
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandStore(node, f, toReturn, apf0, config) and
|
||||
apf0.headUsesContent(f) and
|
||||
consCand(f, apf, unbind(config))
|
||||
consCand(f, apf, config)
|
||||
)
|
||||
or
|
||||
exists(Content f, AccessPathFront apf0 |
|
||||
flowCandRead(node, f, toReturn, apf0, config) and
|
||||
consCandFwd(f, apf0, unbind(config)) and
|
||||
consCandFwd(f, apf0, config) and
|
||||
apf.headUsesContent(f)
|
||||
)
|
||||
}
|
||||
@@ -1221,8 +1225,8 @@ private newtype TAccessPath =
|
||||
TConsCons(Content f1, Content f2, int len) { consCand(f1, TFrontHead(f2), _) and len in [2 .. 5] }
|
||||
|
||||
/**
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first
|
||||
* element of the list and its length are tracked. If data flows from a source to
|
||||
* Conceptually a list of `Content`s followed by a `Type`, but only the first two
|
||||
* elements of the list and its length are tracked. If data flows from a source to
|
||||
* a given node with a given `AccessPath`, this indicates the sequence of
|
||||
* dereference operations needed to get from the value in the node to the
|
||||
* tracked object. The final type indicates the type of the tracked object.
|
||||
@@ -1260,7 +1264,7 @@ abstract private class AccessPath extends TAccessPath {
|
||||
|
||||
private class AccessPathNil extends AccessPath, TNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -1276,7 +1280,7 @@ private class AccessPathConsNil extends AccessPathCons, TConsNil {
|
||||
override string toString() {
|
||||
exists(Content f, DataFlowType t | this = TConsNil(f, t) |
|
||||
// The `concat` becomes "" if `ppReprType` has no result.
|
||||
result = f.toString() + concat(" : " + ppReprType(t))
|
||||
result = "[" + f.toString() + "]" + concat(" : " + ppReprType(t))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1293,8 +1297,8 @@ private class AccessPathConsCons extends AccessPathCons, TConsCons {
|
||||
override string toString() {
|
||||
exists(Content f1, Content f2, int len | this = TConsCons(f1, f2, len) |
|
||||
if len = 2
|
||||
then result = f1.toString() + ", " + f2.toString()
|
||||
else result = f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f1.toString() + ", " + f2.toString() + "]"
|
||||
else result = "[" + f1.toString() + ", " + f2.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1362,12 +1366,12 @@ private predicate flowFwd0(
|
||||
(
|
||||
exists(Node mid |
|
||||
flowFwd(mid, fromArg, apf, ap, config) and
|
||||
localFlowBigStep(mid, node, true, config)
|
||||
localFlowBigStep(mid, node, true, config, _)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(mid, fromArg, _, nil, config) and
|
||||
localFlowBigStep(mid, node, false, config) and
|
||||
localFlowBigStep(mid, node, false, config, _) and
|
||||
ap = node.(AccessPathNilNode).getAp() and
|
||||
apf = ap.(AccessPathNil).getFront()
|
||||
)
|
||||
@@ -1471,13 +1475,13 @@ private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuratio
|
||||
ap instanceof AccessPathNil
|
||||
or
|
||||
exists(Node mid |
|
||||
localFlowBigStep(node, mid, true, config) and
|
||||
localFlowBigStep(node, mid, true, config, _) and
|
||||
flow(mid, toReturn, ap, config)
|
||||
)
|
||||
or
|
||||
exists(Node mid, AccessPathNil nil |
|
||||
flowFwd(node, _, _, ap, config) and
|
||||
localFlowBigStep(node, mid, false, config) and
|
||||
localFlowBigStep(node, mid, false, config, _) and
|
||||
flow(mid, toReturn, nil, config) and
|
||||
ap instanceof AccessPathNil
|
||||
)
|
||||
@@ -1618,11 +1622,14 @@ abstract class PathNode extends TPathNode {
|
||||
/** Gets a successor of this node, if any. */
|
||||
abstract PathNode getASuccessor();
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
abstract predicate isSource();
|
||||
|
||||
private string ppAp() {
|
||||
this instanceof PathNodeSink and result = ""
|
||||
or
|
||||
exists(string s | s = this.(PathNodeMid).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1683,12 +1690,6 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = getSuccMid()
|
||||
or
|
||||
// a final step to a sink via one or more local steps
|
||||
localFlowStepPlus(node, result.getNode(), _, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid |
|
||||
mid = getSuccMid() and
|
||||
@@ -1697,23 +1698,12 @@ private class PathNodeMid extends PathNode, TPathNodeMid {
|
||||
result instanceof PathNodeSink and
|
||||
result.getConfiguration() = unbind(mid.getConfiguration())
|
||||
)
|
||||
or
|
||||
// a direct step from a source to a sink if a node is both
|
||||
this instanceof PathNodeSource and
|
||||
result instanceof PathNodeSink and
|
||||
this.getNode() = result.getNode() and
|
||||
result.getConfiguration() = unbind(this.getConfiguration())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow graph node corresponding to a source.
|
||||
*/
|
||||
private class PathNodeSource extends PathNodeMid {
|
||||
PathNodeSource() {
|
||||
getConfiguration().isSource(getNode()) and
|
||||
getCallContext() instanceof CallContextAny and
|
||||
getAp() instanceof AccessPathNil
|
||||
override predicate isSource() {
|
||||
config.isSource(node) and
|
||||
cc instanceof CallContextAny and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1733,6 +1723,8 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessor() { none() }
|
||||
|
||||
override predicate isSource() { config.isSource(node) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1740,14 +1732,20 @@ private class PathNodeSink extends PathNode, TPathNodeSink {
|
||||
* a callable is recorded by `cc`.
|
||||
*/
|
||||
private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPath ap) {
|
||||
localFlowBigStep(mid.getNode(), node, true, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp()
|
||||
or
|
||||
localFlowBigStep(mid.getNode(), node, false, mid.getConfiguration()) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf |
|
||||
midnode = mid.getNode() and
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, conf, localCC) and
|
||||
ap = ap0
|
||||
or
|
||||
localFlowBigStep(midnode, node, false, conf, localCC) and
|
||||
ap0 instanceof AccessPathNil and
|
||||
ap = node.(AccessPathNilNode).getAp()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, mid.getConfiguration()) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -1762,8 +1760,6 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPat
|
||||
or
|
||||
exists(Content f, AccessPath ap0 | contentStoreStep(mid, node, ap0, f, cc) and push(ap0, f, ap))
|
||||
or
|
||||
pathOutOfArgument(mid, node, cc) and ap = mid.getAp()
|
||||
or
|
||||
pathIntoCallable(mid, node, _, cc, _) and ap = mid.getAp()
|
||||
or
|
||||
pathOutOfCallable(mid, node, cc) and ap = mid.getAp()
|
||||
@@ -1797,9 +1793,9 @@ private predicate pathOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallCo
|
||||
not innercc instanceof CallContextCall
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathOutOfCallable1(
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKind kind, CallContext cc
|
||||
PathNodeMid mid, DataFlowCall call, ReturnKindExt kind, CallContext cc
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
pathOutOfCallable0(mid, pos, innercc) and
|
||||
@@ -1816,29 +1812,9 @@ private predicate pathOutOfCallable1(
|
||||
* is a return from a callable and is recorded by `cc`, if needed.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, OutNode out, CallContext cc) {
|
||||
exists(ReturnKind kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate pathOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
flow(node, unbind(mid.getConfiguration()))
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
private predicate pathOutOfCallable(PathNodeMid mid, Node out, CallContext cc) {
|
||||
exists(ReturnKindExt kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) |
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1891,7 +1867,7 @@ private predicate pathIntoCallable(
|
||||
pathIntoCallable0(mid, callable, i, outercc, call, emptyAp) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -1900,9 +1876,9 @@ private predicate pathIntoCallable(
|
||||
/** Holds if data may flow from `p` to a return of kind `kind`. */
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThrough(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, AccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(PathNodeMid mid, ReturnNode ret |
|
||||
exists(PathNodeMid mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -1917,14 +1893,14 @@ private predicate paramFlowsThrough(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate pathThroughCallable0(
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKind kind, CallContext cc, AccessPathNil apnil
|
||||
DataFlowCall call, PathNodeMid mid, ReturnKindExt kind, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc |
|
||||
pathIntoCallable(mid, p, cc, innercc, call) and
|
||||
paramFlowsThrough(p, kind, innercc, apnil, unbind(mid.getConfiguration())) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc) and
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc) and
|
||||
mid.getAp() instanceof AccessPathNil
|
||||
)
|
||||
}
|
||||
@@ -1934,12 +1910,10 @@ private predicate pathThroughCallable0(
|
||||
* The context `cc` is restored to its value prior to entering the callable.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate pathThroughCallable(
|
||||
PathNodeMid mid, OutNode out, CallContext cc, AccessPathNil apnil
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
private predicate pathThroughCallable(PathNodeMid mid, Node out, CallContext cc, AccessPathNil apnil) {
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
pathThroughCallable0(call, mid, kind, cc, apnil) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1967,12 +1941,12 @@ private predicate valuePathThroughCallable(PathNodeMid mid, OutNode out, CallCon
|
||||
* sinks.
|
||||
*/
|
||||
private predicate flowsTo(
|
||||
PathNodeSource flowsource, PathNodeSink flowsink, Node source, Node sink,
|
||||
Configuration configuration
|
||||
PathNode flowsource, PathNodeSink flowsink, Node source, Node sink, Configuration configuration
|
||||
) {
|
||||
flowsource.isSource() and
|
||||
flowsource.getConfiguration() = configuration and
|
||||
flowsource.getNode() = source and
|
||||
pathSuccPlus(flowsource, flowsink) and
|
||||
(flowsource = flowsink or pathSuccPlus(flowsource, flowsink)) and
|
||||
flowsink.getNode() = sink
|
||||
}
|
||||
|
||||
@@ -1996,16 +1970,10 @@ private module FlowExploration {
|
||||
// flow into callable
|
||||
viableParamArg(_, node2, node1)
|
||||
or
|
||||
// flow out of an argument
|
||||
exists(ParameterNode p |
|
||||
parameterValueFlowsToUpdate(p, node1) and
|
||||
viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode())
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
getReturnPosition(node1) = viableReturnPos(call, kind) and
|
||||
node2 = getAnOutNode(call, kind)
|
||||
node2 = kind.getAnOutNode(call)
|
||||
)
|
||||
|
|
||||
c1 = node1.getEnclosingCallable() and
|
||||
@@ -2082,7 +2050,7 @@ private module FlowExploration {
|
||||
|
||||
private class PartialAccessPathNil extends PartialAccessPath, TPartialNil {
|
||||
override string toString() {
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(" : " + ppReprType(t)))
|
||||
exists(DataFlowType t | this = TPartialNil(t) | result = concat(": " + ppReprType(t)))
|
||||
}
|
||||
|
||||
override AccessPathFront getFront() {
|
||||
@@ -2094,8 +2062,8 @@ private module FlowExploration {
|
||||
override string toString() {
|
||||
exists(Content f, int len | this = TPartialCons(f, len) |
|
||||
if len = 1
|
||||
then result = f.toString()
|
||||
else result = f.toString() + ", ... (" + len.toString() + ")"
|
||||
then result = "[" + f.toString() + "]"
|
||||
else result = "[" + f.toString() + ", ... (" + len.toString() + ")]"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2172,7 +2140,7 @@ private module FlowExploration {
|
||||
|
||||
private string ppAp() {
|
||||
exists(string s | s = this.(PartialPathNodePriv).getAp().toString() |
|
||||
if s = "" then result = "" else result = " [" + s + "]"
|
||||
if s = "" then result = "" else result = " " + s
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2216,16 +2184,19 @@ private module FlowExploration {
|
||||
private predicate partialPathStep(
|
||||
PartialPathNodePriv mid, Node node, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
not isUnreachableInCall(node, cc.(CallContextSpecificCall).getCall()) and
|
||||
(
|
||||
localFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
or
|
||||
additionalLocalFlowStep(mid.getNode(), node, config) and
|
||||
cc = mid.getCallContext() and
|
||||
mid.getAp() instanceof PartialAccessPathNil and
|
||||
ap = TPartialNil(getErasedRepr(node.getType())) and
|
||||
config = mid.getConfiguration()
|
||||
)
|
||||
or
|
||||
jumpStep(mid.getNode(), node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
@@ -2247,8 +2218,6 @@ private module FlowExploration {
|
||||
apConsFwd(ap, f, ap0, config)
|
||||
)
|
||||
or
|
||||
partialPathOutOfArgument(mid, node, cc, ap, config)
|
||||
or
|
||||
partialPathIntoCallable(mid, node, _, cc, _, ap, config)
|
||||
or
|
||||
partialPathOutOfCallable(mid, node, cc, ap, config)
|
||||
@@ -2307,7 +2276,7 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathOutOfCallable1(
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKind kind, CallContext cc,
|
||||
PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc |
|
||||
@@ -2321,36 +2290,12 @@ private module FlowExploration {
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPath ap, Configuration config
|
||||
) {
|
||||
exists(ReturnKind kind, DataFlowCall call |
|
||||
exists(ReturnKindExt kind, DataFlowCall call |
|
||||
partialPathOutOfCallable1(mid, call, kind, cc, ap, config)
|
||||
|
|
||||
out = getAnOutNode(call, kind)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathOutOfArgument(
|
||||
PartialPathNodePriv mid, PostUpdateNode node, CallContext cc, PartialAccessPath ap,
|
||||
Configuration config
|
||||
) {
|
||||
exists(
|
||||
PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i,
|
||||
DataFlowCall call, ArgumentNode arg
|
||||
|
|
||||
mid.getNode() = n and
|
||||
parameterValueFlowsToUpdate(p, n) and
|
||||
innercc = mid.getCallContext() and
|
||||
p.isParameterOf(callable, i) and
|
||||
resolveReturn(innercc, callable, call) and
|
||||
node.getPreUpdateNode() = arg and
|
||||
arg.argumentOf(call, i) and
|
||||
ap = mid.getAp() and
|
||||
config = mid.getConfiguration()
|
||||
|
|
||||
if reducedViableImplInReturn(callable, call)
|
||||
then cc = TReturn(callable, call)
|
||||
else cc = TAnyCallContext()
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2389,7 +2334,7 @@ private module FlowExploration {
|
||||
partialPathIntoCallable0(mid, callable, i, outercc, call, emptyAp, ap, config) and
|
||||
p.isParameterOf(callable, i)
|
||||
|
|
||||
if reducedViableImplInCallContext(_, callable, call)
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
then innercc = TSpecificCall(call, i, emptyAp)
|
||||
else innercc = TSomeCall(p, emptyAp)
|
||||
)
|
||||
@@ -2397,10 +2342,10 @@ private module FlowExploration {
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate paramFlowsThroughInPartialPath(
|
||||
ParameterNode p, ReturnKind kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
ParameterNode p, ReturnKindExt kind, CallContextCall cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(PartialPathNodePriv mid, ReturnNode ret |
|
||||
exists(PartialPathNodePriv mid, ReturnNodeExt ret |
|
||||
mid.getNode() = ret and
|
||||
kind = ret.getKind() and
|
||||
cc = mid.getCallContext() and
|
||||
@@ -2417,23 +2362,23 @@ private module FlowExploration {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate partialPathThroughCallable0(
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKind kind, CallContext cc,
|
||||
DataFlowCall call, PartialPathNodePriv mid, ReturnKindExt kind, CallContext cc,
|
||||
PartialAccessPathNil apnil, Configuration config
|
||||
) {
|
||||
exists(ParameterNode p, CallContext innercc, PartialAccessPathNil midapnil |
|
||||
partialPathIntoCallable(mid, p, cc, innercc, call, midapnil, config) and
|
||||
paramFlowsThroughInPartialPath(p, kind, innercc, apnil, config) and
|
||||
not parameterValueFlowsThrough(p, kind, innercc)
|
||||
not parameterValueFlowsThrough(p, kind.(ValueReturnKind).getKind(), innercc)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate partialPathThroughCallable(
|
||||
PartialPathNodePriv mid, OutNode out, CallContext cc, PartialAccessPathNil apnil,
|
||||
PartialPathNodePriv mid, Node out, CallContext cc, PartialAccessPathNil apnil,
|
||||
Configuration config
|
||||
) {
|
||||
exists(DataFlowCall call, ReturnKind kind |
|
||||
exists(DataFlowCall call, ReturnKindExt kind |
|
||||
partialPathThroughCallable0(call, mid, kind, cc, apnil, config) and
|
||||
out = getAnOutNode(call, kind)
|
||||
out = kind.getAnOutNode(call)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,9 @@ class ArgumentNode extends Node {
|
||||
DataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
}
|
||||
|
||||
private newtype TReturnKind = TNormalReturnKind()
|
||||
private newtype TReturnKind =
|
||||
TNormalReturnKind() or
|
||||
TRefReturnKind(int i) { exists(Parameter parameter | i = parameter.getIndex()) }
|
||||
|
||||
/**
|
||||
* A return kind. A return kind describes how a value can be returned
|
||||
@@ -59,23 +61,54 @@ private newtype TReturnKind = TNormalReturnKind()
|
||||
*/
|
||||
class ReturnKind extends TReturnKind {
|
||||
/** Gets a textual representation of this return kind. */
|
||||
string toString() { result = "return" }
|
||||
string toString() {
|
||||
this instanceof TNormalReturnKind and
|
||||
result = "return"
|
||||
or
|
||||
this instanceof TRefReturnKind and
|
||||
result = "ref"
|
||||
}
|
||||
}
|
||||
|
||||
/** A data flow node that occurs as the result of a `ReturnStmt`. */
|
||||
class ReturnNode extends ExprNode {
|
||||
ReturnNode() { exists(ReturnStmt ret | this.getExpr() = ret.getExpr()) }
|
||||
/** A data flow node that represents a returned value in the called function. */
|
||||
abstract class ReturnNode extends Node {
|
||||
/** Gets the kind of this returned value. */
|
||||
abstract ReturnKind getKind();
|
||||
}
|
||||
|
||||
/** A `ReturnNode` that occurs as the result of a `ReturnStmt`. */
|
||||
private class NormalReturnNode extends ReturnNode, ExprNode {
|
||||
NormalReturnNode() { exists(ReturnStmt ret | this.getExpr() = ret.getExpr()) }
|
||||
|
||||
/** Gets the kind of this returned value. */
|
||||
ReturnKind getKind() { result = TNormalReturnKind() }
|
||||
override ReturnKind getKind() { result = TNormalReturnKind() }
|
||||
}
|
||||
|
||||
/** A data flow node that represents the output of a call. */
|
||||
class OutNode extends ExprNode {
|
||||
OutNode() { this.getExpr() instanceof Call }
|
||||
/**
|
||||
* A `ReturnNode` that occurs as a result of a definition of a reference
|
||||
* parameter reaching the end of a function body.
|
||||
*/
|
||||
private class RefReturnNode extends ReturnNode, RefParameterFinalValueNode {
|
||||
/** Gets the kind of this returned value. */
|
||||
override ReturnKind getKind() { result = TRefReturnKind(this.getParameter().getIndex()) }
|
||||
}
|
||||
|
||||
/** A data flow node that represents the output of a call at the call site. */
|
||||
abstract class OutNode extends Node {
|
||||
/** Gets the underlying call. */
|
||||
abstract DataFlowCall getCall();
|
||||
}
|
||||
|
||||
private class ExprOutNode extends OutNode, ExprNode {
|
||||
ExprOutNode() { this.getExpr() instanceof Call }
|
||||
|
||||
/** Gets the underlying call. */
|
||||
DataFlowCall getCall() { result = this.getExpr() }
|
||||
override DataFlowCall getCall() { result = this.getExpr() }
|
||||
}
|
||||
|
||||
private class RefOutNode extends OutNode, DefinitionByReferenceNode {
|
||||
/** Gets the underlying call. */
|
||||
override DataFlowCall getCall() { result = this.getArgument().getParent() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,6 +118,11 @@ class OutNode extends ExprNode {
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
|
||||
result = call.getNode() and
|
||||
kind = TNormalReturnKind()
|
||||
or
|
||||
exists(int i |
|
||||
result.asDefiningArgument() = call.getArgument(i) and
|
||||
kind = TRefReturnKind(i)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,3 +302,5 @@ class DataFlowCall extends Expr {
|
||||
/** Gets the enclosing callable of this call. */
|
||||
Function getEnclosingCallable() { result = this.getEnclosingFunction() }
|
||||
}
|
||||
|
||||
predicate isUnreachableInCall(Node n, DataFlowCall call) { none() } // stub implementation
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
private import cpp
|
||||
private import semmle.code.cpp.dataflow.internal.FlowVar
|
||||
private import semmle.code.cpp.models.interfaces.DataFlow
|
||||
private import semmle.code.cpp.controlflow.Guards
|
||||
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
|
||||
cached
|
||||
private newtype TNode =
|
||||
@@ -25,7 +27,8 @@ private newtype TNode =
|
||||
not c.getTarget().getParameter(i).getUnderlyingType().(PointerType).getBaseType().isConst()
|
||||
)
|
||||
} or
|
||||
TUninitializedNode(LocalVariable v) { not v.hasInitializer() }
|
||||
TUninitializedNode(LocalVariable v) { not v.hasInitializer() } or
|
||||
TRefParameterFinalValueNode(Parameter p) { exists(FlowVar var | var.reachesRefParameter(p)) }
|
||||
|
||||
/**
|
||||
* A node in a data flow graph.
|
||||
@@ -248,6 +251,23 @@ class UninitializedNode extends Node, TUninitializedNode {
|
||||
LocalVariable getLocalVariable() { result = v }
|
||||
}
|
||||
|
||||
/** INTERNAL: do not use. The final value of a non-const ref parameter. */
|
||||
class RefParameterFinalValueNode extends Node, TRefParameterFinalValueNode {
|
||||
Parameter p;
|
||||
|
||||
RefParameterFinalValueNode() { this = TRefParameterFinalValueNode(p) }
|
||||
|
||||
override Function getFunction() { result = p.getFunction() }
|
||||
|
||||
override Type getType() { result = p.getType() }
|
||||
|
||||
override string toString() { result = p.toString() }
|
||||
|
||||
override Location getLocation() { result = p.getLocation() }
|
||||
|
||||
Parameter getParameter() { result = p }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node associated with an object after an operation that might have
|
||||
* changed its state.
|
||||
@@ -490,7 +510,7 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
or
|
||||
var.definedPartiallyAt(nodeFrom.asPartialDefinition())
|
||||
) and
|
||||
varToExprStep(var, nodeTo.asExpr())
|
||||
varToNodeStep(var, nodeTo)
|
||||
)
|
||||
or
|
||||
// Expr -> DefinitionByReferenceNode
|
||||
@@ -533,9 +553,13 @@ private predicate exprToVarStep(Expr assignedExpr, FlowVar var) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the expression `e` is an access of the variable `var`.
|
||||
* Holds if the node `n` is an access of the variable `var`.
|
||||
*/
|
||||
private predicate varToExprStep(FlowVar var, Expr e) { e = var.getAnAccess() }
|
||||
private predicate varToNodeStep(FlowVar var, Node n) {
|
||||
n.asExpr() = var.getAnAccess()
|
||||
or
|
||||
var.reachesRefParameter(n.(RefParameterFinalValueNode).getParameter())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flows from `fromExpr` to `toExpr` directly, in the case
|
||||
@@ -553,6 +577,10 @@ private predicate exprToExprStep_nocfg(Expr fromExpr, Expr toExpr) {
|
||||
or
|
||||
toExpr = any(StmtExpr stmtExpr | fromExpr = stmtExpr.getResultExpr())
|
||||
or
|
||||
toExpr.(AddressOfExpr).getOperand() = fromExpr
|
||||
or
|
||||
toExpr.(BuiltInOperationBuiltInAddressOf).getOperand() = fromExpr
|
||||
or
|
||||
// The following case is needed to track the qualifier object for flow
|
||||
// through fields. It gives flow from `T(x)` to `new T(x)`. That's not
|
||||
// strictly _data_ flow but _taint_ flow because the type of `fromExpr` is
|
||||
@@ -574,8 +602,8 @@ private predicate exprToExprStep_nocfg(Expr fromExpr, Expr toExpr) {
|
||||
exists(DataFlowFunction f, FunctionInput inModel, FunctionOutput outModel, int iIn |
|
||||
call.getTarget() = f and
|
||||
f.hasDataFlow(inModel, outModel) and
|
||||
outModel.isOutReturnValue() and
|
||||
inModel.isInParameter(iIn) and
|
||||
outModel.isReturnValue() and
|
||||
inModel.isParameter(iIn) and
|
||||
fromExpr = call.getArgument(iIn)
|
||||
)
|
||||
)
|
||||
@@ -585,12 +613,12 @@ private predicate exprToDefinitionByReferenceStep(Expr exprIn, Expr argOut) {
|
||||
exists(DataFlowFunction f, Call call, FunctionOutput outModel, int argOutIndex |
|
||||
call.getTarget() = f and
|
||||
argOut = call.getArgument(argOutIndex) and
|
||||
outModel.isOutParameterPointer(argOutIndex) and
|
||||
outModel.isParameterDeref(argOutIndex) and
|
||||
exists(int argInIndex, FunctionInput inModel | f.hasDataFlow(inModel, outModel) |
|
||||
inModel.isInParameterPointer(argInIndex) and
|
||||
inModel.isParameterDeref(argInIndex) and
|
||||
call.passesByReference(argInIndex, exprIn)
|
||||
or
|
||||
inModel.isInParameter(argInIndex) and
|
||||
inModel.isParameter(argInIndex) and
|
||||
exprIn = call.getArgument(argInIndex)
|
||||
)
|
||||
)
|
||||
@@ -654,12 +682,16 @@ VariableAccess getAnAccessToAssignedVariable(Expr assign) {
|
||||
*
|
||||
* It is important that all extending classes in scope are disjoint.
|
||||
*/
|
||||
class BarrierGuard extends Expr {
|
||||
/** NOT YET SUPPORTED. Holds if this guard validates `e` upon evaluating to `branch`. */
|
||||
abstract deprecated predicate checks(Expr e, boolean branch);
|
||||
class BarrierGuard extends GuardCondition {
|
||||
/** Override this predicate to hold if this guard validates `e` upon evaluating to `b`. */
|
||||
abstract predicate checks(Expr e, boolean b);
|
||||
|
||||
/** Gets a node guarded by this guard. */
|
||||
final Node getAGuardedNode() {
|
||||
none() // stub
|
||||
final ExprNode getAGuardedNode() {
|
||||
exists(GVN value, boolean branch |
|
||||
result.getExpr() = value.getAnExpr() and
|
||||
this.checks(value.getAnExpr(), branch) and
|
||||
this.controls(result.getExpr().getBasicBlock(), branch)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +62,20 @@ class FlowVar extends TFlowVar {
|
||||
cached
|
||||
abstract predicate definedByReference(Expr arg);
|
||||
|
||||
/**
|
||||
* Holds if this `FlowVar` is a `PartialDefinition` whose defined expression
|
||||
* is `e`.
|
||||
*/
|
||||
cached
|
||||
abstract predicate definedPartiallyAt(Expr e);
|
||||
|
||||
/**
|
||||
* Holds if this `FlowVar` is a definition of a reference parameter `p` that
|
||||
* persists until the function returns.
|
||||
*/
|
||||
cached
|
||||
abstract predicate reachesRefParameter(Parameter p);
|
||||
|
||||
/**
|
||||
* Holds if this `FlowVar` corresponds to the initial value of `v`. The following
|
||||
* is an exhaustive list of cases where this may happen.
|
||||
@@ -338,6 +349,9 @@ module FlowVar_internal {
|
||||
param = v
|
||||
}
|
||||
|
||||
// `fullySupportedSsaVariable` excludes reference types
|
||||
override predicate reachesRefParameter(Parameter p) { none() }
|
||||
|
||||
/**
|
||||
* Holds if this `SsaVar` corresponds to a non-phi definition. Users of this
|
||||
* library will never directly use an `SsaVar` that comes from a phi node,
|
||||
@@ -387,6 +401,13 @@ module FlowVar_internal {
|
||||
sbb = v.(Parameter).getFunction().getEntryPoint()
|
||||
}
|
||||
|
||||
override predicate reachesRefParameter(Parameter p) {
|
||||
parameterIsNonConstReference(p) and
|
||||
p = v and
|
||||
// This definition reaches the exit node of the function CFG
|
||||
getAReachedBlockVarSBB(this).getANode() = p.getFunction()
|
||||
}
|
||||
|
||||
override predicate definedByInitialValue(LocalScopeVariable lsv) {
|
||||
blockVarDefinedByVariable(sbb, lsv) and
|
||||
lsv = v
|
||||
@@ -490,7 +511,7 @@ module FlowVar_internal {
|
||||
exists(VariableAccess va |
|
||||
va.getTarget() = result and
|
||||
readAccess(va) and
|
||||
bbNotInLoop(va.getBasicBlock())
|
||||
exists(BasicBlock bb | bb = va.getBasicBlock() | not this.bbInLoop(bb))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -513,10 +534,8 @@ module FlowVar_internal {
|
||||
bbInLoopCondition(bb)
|
||||
}
|
||||
|
||||
predicate bbNotInLoop(BasicBlock bb) {
|
||||
not this.bbInLoop(bb) and
|
||||
bb.getEnclosingFunction() = this.getEnclosingFunction()
|
||||
}
|
||||
/** Holds if `sbb` is inside this loop. */
|
||||
predicate sbbInLoop(SubBasicBlock sbb) { this.bbInLoop(sbb.getBasicBlock()) }
|
||||
|
||||
/**
|
||||
* Holds if `bb` is a basic block inside this loop where `v` has not been
|
||||
@@ -537,22 +556,19 @@ module FlowVar_internal {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if some loop always assigns to `v` before leaving through an edge
|
||||
* from `bbInside` in its condition to `bbOutside` outside the loop, where
|
||||
* (`sbbDef`, `v`) is a `BlockVar` defined outside the loop. Also, `v` must
|
||||
* be used outside the loop.
|
||||
* Holds if `loop` always assigns to `v` before leaving through an edge
|
||||
* from `bbInside` in its condition to `bbOutside` outside the loop. Also,
|
||||
* `v` must be used outside the loop.
|
||||
*/
|
||||
predicate skipLoop(
|
||||
SubBasicBlock sbbInside, SubBasicBlock sbbOutside, SubBasicBlock sbbDef, Variable v
|
||||
SubBasicBlock sbbInside, SubBasicBlock sbbOutside, Variable v, AlwaysTrueUponEntryLoop loop
|
||||
) {
|
||||
exists(AlwaysTrueUponEntryLoop loop, BasicBlock bbInside, BasicBlock bbOutside |
|
||||
exists(BasicBlock bbInside, BasicBlock bbOutside |
|
||||
loop.alwaysAssignsBeforeLeavingCondition(bbInside, bbOutside, v) and
|
||||
bbInside = sbbInside.getBasicBlock() and
|
||||
bbOutside = sbbOutside.getBasicBlock() and
|
||||
sbbInside.lastInBB() and
|
||||
sbbOutside.firstInBB() and
|
||||
loop.bbNotInLoop(sbbDef.getBasicBlock()) and
|
||||
exists(TBlockVar(sbbDef, v))
|
||||
sbbOutside.firstInBB()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -571,7 +587,7 @@ module FlowVar_internal {
|
||||
start = TBlockVar(sbbDef, v) and
|
||||
result = mid.getASuccessor() and
|
||||
variableLiveInSBB(result, v) and
|
||||
not skipLoop(mid, result, sbbDef, v) and
|
||||
forall(AlwaysTrueUponEntryLoop loop | skipLoop(mid, result, v, loop) | loop.sbbInLoop(sbbDef)) and
|
||||
not assignmentLikeOperation(result, v, _, _)
|
||||
)
|
||||
}
|
||||
@@ -593,12 +609,23 @@ module FlowVar_internal {
|
||||
private predicate variableLiveInSBB(SubBasicBlock sbb, Variable v) {
|
||||
variableAccessInSBB(v, sbb, _)
|
||||
or
|
||||
// Non-const reference parameters are live at the end of the function
|
||||
parameterIsNonConstReference(v) and
|
||||
sbb.contains(v.(Parameter).getFunction())
|
||||
or
|
||||
exists(SubBasicBlock succ | succ = sbb.getASuccessor() |
|
||||
variableLiveInSBB(succ, v) and
|
||||
not variableNotLiveBefore(succ, v)
|
||||
)
|
||||
}
|
||||
|
||||
predicate parameterIsNonConstReference(Parameter p) {
|
||||
exists(ReferenceType refType |
|
||||
refType = p.getUnderlyingType() and
|
||||
not refType.getBaseType().isConst()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if liveness of `v` should stop propagating backwards from `sbb`.
|
||||
*/
|
||||
@@ -679,10 +706,11 @@ module FlowVar_internal {
|
||||
predicate dominatedByOverwrite(UninitializedLocalVariable v, VariableAccess va) {
|
||||
exists(BasicBlock bb, int vaIndex |
|
||||
va = bb.getNode(vaIndex) and
|
||||
va.getTarget() = v
|
||||
|
|
||||
va.getTarget() = v and
|
||||
vaIndex > indexOfFirstOverwriteInBB(v, bb)
|
||||
or
|
||||
va = bb.getNode(vaIndex) and
|
||||
va.getTarget() = v and
|
||||
bbStrictlyDominates(getAnOverwritingBB(v), bb)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -122,11 +122,11 @@ private predicate exprToDefinitionByReferenceStep(Expr exprIn, Expr argOut) {
|
||||
exists(DataFlowFunction f, Call call, FunctionOutput outModel, int argOutIndex |
|
||||
call.getTarget() = f and
|
||||
argOut = call.getArgument(argOutIndex) and
|
||||
outModel.isOutParameterPointer(argOutIndex) and
|
||||
outModel.isParameterDeref(argOutIndex) and
|
||||
exists(int argInIndex, FunctionInput inModel | f.hasDataFlow(inModel, outModel) |
|
||||
// Taint flows from a pointer to a dereference, which DataFlow does not handle
|
||||
// memcpy(&dest_var, tainted_ptr, len)
|
||||
inModel.isInParameterPointer(argInIndex) and
|
||||
inModel.isParameterDeref(argInIndex) and
|
||||
exprIn = call.getArgument(argInIndex)
|
||||
)
|
||||
)
|
||||
@@ -134,15 +134,15 @@ private predicate exprToDefinitionByReferenceStep(Expr exprIn, Expr argOut) {
|
||||
exists(TaintFunction f, Call call, FunctionOutput outModel, int argOutIndex |
|
||||
call.getTarget() = f and
|
||||
argOut = call.getArgument(argOutIndex) and
|
||||
outModel.isOutParameterPointer(argOutIndex) and
|
||||
outModel.isParameterDeref(argOutIndex) and
|
||||
exists(int argInIndex, FunctionInput inModel | f.hasTaintFlow(inModel, outModel) |
|
||||
inModel.isInParameterPointer(argInIndex) and
|
||||
inModel.isParameterDeref(argInIndex) and
|
||||
exprIn = call.getArgument(argInIndex)
|
||||
or
|
||||
inModel.isInParameterPointer(argInIndex) and
|
||||
inModel.isParameterDeref(argInIndex) and
|
||||
call.passesByReference(argInIndex, exprIn)
|
||||
or
|
||||
inModel.isInParameter(argInIndex) and
|
||||
inModel.isParameter(argInIndex) and
|
||||
exprIn = call.getArgument(argInIndex)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import cpp
|
||||
|
||||
/**
|
||||
* A module for performing simple virtual dispatch analysis.
|
||||
*/
|
||||
module VirtualDispatch {
|
||||
/**
|
||||
* Gets a possible implementation target when the given function is the static target of a virtual call.
|
||||
*/
|
||||
private MemberFunction getAPossibleImplementation(MemberFunction staticTarget) {
|
||||
/*
|
||||
* `IUnknown` is a COM interface with many sub-types, and many overrides (tens of thousands on
|
||||
* some databases), so we ignore any member functions defined within that interface.
|
||||
*/
|
||||
|
||||
not staticTarget.getDeclaringType().hasName("IUnknown") and
|
||||
result = staticTarget.getAnOverridingFunction*()
|
||||
}
|
||||
|
||||
/** Gets the static type of the qualifier expression for the given call. */
|
||||
private Class getCallQualifierType(FunctionCall c) {
|
||||
result = c.getQualifier().getType().stripType() and
|
||||
/*
|
||||
* `IUnknown` is a COM interface with many sub-types (tens of thousands on some databases), so
|
||||
* we ignore any cases where the qualifier type is that interface.
|
||||
*/
|
||||
|
||||
not result.hasName("IUnknown")
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for `getAViableTarget`, which computes the viable targets for
|
||||
* virtual calls based on the qualifier type.
|
||||
*/
|
||||
private Function getAViableVirtualCallTarget(Class qualifierType, MemberFunction staticTarget) {
|
||||
exists(Class qualifierSubType |
|
||||
result = getAPossibleImplementation(staticTarget) and
|
||||
qualifierType = qualifierSubType.getABaseClass*() and
|
||||
mayInherit(qualifierSubType, result) and
|
||||
not cannotInherit(qualifierSubType, result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a viable target for the given function call.
|
||||
*
|
||||
* If `c` is a virtual call, then we will perform a simple virtual dispatch analysis to return
|
||||
* the `Function` instances which might be a viable target, based on an analysis of the declared
|
||||
* type of the qualifier expression.
|
||||
*
|
||||
* (This analysis is imprecise: it looks for subtypes of the declared type of the qualifier expression
|
||||
* and the possible implementations of `c.getTarget()` that are declared or inherited by those subtypes.
|
||||
* This does not account for virtual inheritance and the ways this affects dispatch.)
|
||||
*
|
||||
* If `c` is not a virtual call, the result will be `c.getTarget()`.
|
||||
*/
|
||||
Function getAViableTarget(Call c) {
|
||||
if c.(FunctionCall).isVirtual() and c.getTarget() instanceof MemberFunction
|
||||
then result = getAViableVirtualCallTarget(getCallQualifierType(c), c.getTarget())
|
||||
else result = c.getTarget()
|
||||
}
|
||||
|
||||
/** Holds if `f` is declared in `c` or a transitive base class of `c`. */
|
||||
private predicate mayInherit(Class c, MemberFunction f) {
|
||||
f.getDeclaringType() = c.getABaseClass*()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `c` cannot inherit the member function `f`,
|
||||
* that is, `c` or one of its supertypes overrides `f`.
|
||||
*/
|
||||
private predicate cannotInherit(Class c, MemberFunction f) {
|
||||
exists(Class overridingType, MemberFunction override |
|
||||
cannotInheritHelper(c, f, overridingType, override) and
|
||||
override.overrides+(f)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate cannotInheritHelper(
|
||||
Class c, MemberFunction f, Class overridingType, MemberFunction override
|
||||
) {
|
||||
c.getABaseClass*() = overridingType and
|
||||
override.getDeclaringType() = overridingType and
|
||||
overridingType.getABaseClass+() = f.getDeclaringType()
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,18 @@ abstract class Access extends Expr, NameQualifiableElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ enum constant access expression.
|
||||
* A C/C++ `enum` constant access expression. For example the access to
|
||||
* `MYENUMCONST1` in `myFunction` in the following code:
|
||||
* ```
|
||||
* enum MyEnum {
|
||||
* MYENUMCONST1,
|
||||
* MYENUMCONST2
|
||||
* };
|
||||
*
|
||||
* void myFunction() {
|
||||
* MyEnum v = MYENUMCONST1;
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
class EnumConstantAccess extends Access, @varaccess {
|
||||
override string getCanonicalQLClass() { result = "EnumConstantAccess" }
|
||||
@@ -27,15 +38,23 @@ class EnumConstantAccess extends Access, @varaccess {
|
||||
exists(EnumConstant c | varbind(underlyingElement(this), unresolveElement(c)))
|
||||
}
|
||||
|
||||
/** Gets the accessed enum constant. */
|
||||
/** Gets the accessed `enum` constant. */
|
||||
override EnumConstant getTarget() { varbind(underlyingElement(this), unresolveElement(result)) }
|
||||
|
||||
/** Gets a textual representation of this enum constant access. */
|
||||
/** Gets a textual representation of this `enum` constant access. */
|
||||
override string toString() { result = this.getTarget().getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ variable access expression.
|
||||
* A C/C++ variable access expression. For example the accesses to
|
||||
* `x` and `y` in `myFunction` in the following code:
|
||||
* ```
|
||||
* int x;
|
||||
*
|
||||
* void myFunction(int y) {
|
||||
* x = y;
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
class VariableAccess extends Access, @varaccess {
|
||||
override string getCanonicalQLClass() { result = "VariableAccess" }
|
||||
@@ -129,7 +148,18 @@ class VariableAccess extends Access, @varaccess {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ field access expression.
|
||||
* A C/C++ field access expression. For example the accesses to
|
||||
* `x` and `y` in `myMethod` in the following code:
|
||||
* ```
|
||||
* class MyClass {
|
||||
* public:
|
||||
* void myMethod(MyClass &other) {
|
||||
* x = other.y;
|
||||
* }
|
||||
*
|
||||
* int x, y;
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
class FieldAccess extends VariableAccess {
|
||||
override string getCanonicalQLClass() { result = "FieldAccess" }
|
||||
@@ -141,8 +171,23 @@ class FieldAccess extends VariableAccess {
|
||||
}
|
||||
|
||||
/**
|
||||
* A field access of the form `obj->field`. The type of `obj` is a pointer,
|
||||
* so this is equivalent to `(*obj).field`.
|
||||
* A field access whose qualifier is a pointer to a class, struct or union.
|
||||
* These typically take the form `obj->field`. Another case is a field access
|
||||
* with an implicit `this->` qualifier, which is often a `PointerFieldAccess`
|
||||
* (but see also `ImplicitThisFieldAccess`).
|
||||
*
|
||||
* For example the accesses to `x` and `y` in `myMethod` in the following code
|
||||
* are each a `PointerFieldAccess`:
|
||||
* ```
|
||||
* class MyClass {
|
||||
* public:
|
||||
* void myMethod(MyClass *other) {
|
||||
* other->x = y;
|
||||
* }
|
||||
*
|
||||
* int x, y;
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
class PointerFieldAccess extends FieldAccess {
|
||||
override string getCanonicalQLClass() { result = "PointerFieldAccess" }
|
||||
@@ -169,7 +214,18 @@ class DotFieldAccess extends FieldAccess {
|
||||
|
||||
/**
|
||||
* A field access of the form `obj.field`, where the type of `obj` is a
|
||||
* reference to a class/struct/union.
|
||||
* reference to a class/struct/union. For example the accesses to `y` in
|
||||
* `myMethod` in the following code:
|
||||
* ```
|
||||
* class MyClass {
|
||||
* public:
|
||||
* void myMethod(MyClass a, MyClass &b) {
|
||||
* a.x = b.y;
|
||||
* }
|
||||
*
|
||||
* int x, y;
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
class ReferenceFieldAccess extends DotFieldAccess {
|
||||
override string getCanonicalQLClass() { result = "ReferenceFieldAccess" }
|
||||
@@ -179,7 +235,18 @@ class ReferenceFieldAccess extends DotFieldAccess {
|
||||
|
||||
/**
|
||||
* A field access of the form `obj.field`, where the type of `obj` is a
|
||||
* class/struct/union (and not a reference).
|
||||
* class/struct/union (and not a reference). For example the accesses to `x`
|
||||
* in `myMethod` in the following code:
|
||||
* ```
|
||||
* class MyClass {
|
||||
* public:
|
||||
* void myMethod(MyClass a, MyClass &b) {
|
||||
* a.x = b.y;
|
||||
* }
|
||||
*
|
||||
* int x, y;
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
class ValueFieldAccess extends DotFieldAccess {
|
||||
override string getCanonicalQLClass() { result = "ValueFieldAccess" }
|
||||
@@ -198,25 +265,40 @@ private predicate referenceConversion(Conversion c) {
|
||||
/**
|
||||
* Holds if `e` is a reference expression (that is, it has a type of the
|
||||
* form `T&`), which is converted to a value. For example:
|
||||
*
|
||||
* ```
|
||||
* int myfcn(MyStruct &x) {
|
||||
* return x.field;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* In this example, the type of `x` is `MyStruct&`, but it gets implicitly
|
||||
* converted to `MyStruct` in the expression `x.field`.
|
||||
*/
|
||||
private predicate exprHasReferenceConversion(Expr e) { referenceConversion(e.getConversion+()) }
|
||||
|
||||
/**
|
||||
* A field access of a field of `this`. The access has no qualifier because
|
||||
* the use of `this` is implicit. For example, `field` is equivalent to
|
||||
* `this->field` if `field` is a member of `this`.
|
||||
* A field access of a field of `this` which has no qualifier because
|
||||
* the use of `this` is implicit. For example, in the following code the
|
||||
* implicit call to the destructor of `A` has no qualifier because the
|
||||
* use of `this` is implicit:
|
||||
* ```
|
||||
* class A {
|
||||
* public:
|
||||
* ~A() {
|
||||
* // ...
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* class B {
|
||||
* public:
|
||||
* A a;
|
||||
*
|
||||
* ~B() {
|
||||
* // Implicit call to the destructor of `A`.
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
* Note: the C++ front-end often automatically desugars `field` to
|
||||
* `this->field`, so most implicit accesses of `this->field` are instances
|
||||
* `this->field`, so most accesses of `this->field` are instances
|
||||
* of `PointerFieldAccess` (with `ThisExpr` as the qualifier), not
|
||||
* `ImplicitThisFieldAccess`.
|
||||
*/
|
||||
@@ -227,7 +309,38 @@ class ImplicitThisFieldAccess extends FieldAccess {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ function access expression.
|
||||
* A C++ _pointer to non-static data member_ literal. For example, `&C::x` is
|
||||
* an expression that refers to field `x` of class `C`. If the type of that
|
||||
* field is `int`, then `&C::x` ought to have type `int C::*`. It is currently
|
||||
* modeled in QL as having type `int`.
|
||||
*
|
||||
* See [dcl.mptr] in the C++17 standard or see
|
||||
* https://en.cppreference.com/w/cpp/language/pointer#Pointers_to_data_members.
|
||||
*/
|
||||
class PointerToFieldLiteral extends ImplicitThisFieldAccess {
|
||||
PointerToFieldLiteral() {
|
||||
// The extractor currently emits a pointer-to-field literal as a field
|
||||
// access without a qualifier. The only other unqualified field accesses it
|
||||
// emits are for compiler-generated constructors and destructors. When we
|
||||
// filter those out, there are only pointer-to-field literals left.
|
||||
not this.isCompilerGenerated()
|
||||
}
|
||||
|
||||
override predicate isConstant() { any() }
|
||||
|
||||
override string getCanonicalQLClass() { result = "PointerToFieldLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ function access expression. For example the access to
|
||||
* `myFunctionTarget` in `myFunction` in the following code:
|
||||
* ```
|
||||
* int myFunctionTarget(int);
|
||||
*
|
||||
* void myFunction() {
|
||||
* int (*myFunctionPointer)(int) = &myTarget;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class FunctionAccess extends Access, @routineexpr {
|
||||
FunctionAccess() { not iscall(underlyingElement(this), _) }
|
||||
@@ -246,7 +359,7 @@ class FunctionAccess extends Access, @routineexpr {
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a parameter of a function signature for the purposes of a decltype.
|
||||
* An access to a parameter of a function signature for the purposes of a `decltype`.
|
||||
*
|
||||
* For example, given the following code:
|
||||
* ```
|
||||
@@ -256,7 +369,7 @@ class FunctionAccess extends Access, @routineexpr {
|
||||
* }
|
||||
* ```
|
||||
* The return type of the function is a decltype, the expression of which contains
|
||||
* an add expression, which in turn has two ParamAccessForType children.
|
||||
* an add expression, which in turn has two `ParamAccessForType` children.
|
||||
*/
|
||||
class ParamAccessForType extends Expr, @param_ref {
|
||||
override string toString() { result = "param access" }
|
||||
@@ -264,7 +377,22 @@ class ParamAccessForType extends Expr, @param_ref {
|
||||
|
||||
/**
|
||||
* An access to a type. This occurs in certain contexts where a built-in
|
||||
* works on types directly rather than variables, expressions etc.
|
||||
* works on types directly rather than variables, expressions etc. For
|
||||
* example the reference to `MyClass` in `__is_pod` in the following code:
|
||||
* ```
|
||||
* class MyClass {
|
||||
* ...
|
||||
* };
|
||||
*
|
||||
* void myFunction() {
|
||||
* if (__is_pod(MyClass))
|
||||
* {
|
||||
* ...
|
||||
* } else {
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class TypeName extends Expr, @type_operand {
|
||||
override string getCanonicalQLClass() { result = "TypeName" }
|
||||
@@ -273,9 +401,17 @@ class TypeName extends Expr, @type_operand {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ array access expression.
|
||||
* A C/C++ array access expression. For example, the access to `as` in
|
||||
* `myFunction` in the following code:
|
||||
* ```
|
||||
* int as[10];
|
||||
*
|
||||
* For calls to operator[], which look syntactically identical, see OverloadedArrayExpr.
|
||||
* void myFunction() {
|
||||
* as[0]++;
|
||||
* }
|
||||
* ```
|
||||
* For calls to `operator[]`, which look syntactically identical, see
|
||||
* `OverloadedArrayExpr`.
|
||||
*/
|
||||
class ArrayExpr extends Expr, @subscriptexpr {
|
||||
override string getCanonicalQLClass() { result = "ArrayExpr" }
|
||||
@@ -283,14 +419,14 @@ class ArrayExpr extends Expr, @subscriptexpr {
|
||||
/**
|
||||
* Gets the array or pointer expression being subscripted.
|
||||
*
|
||||
* This is arr in both arr[0] and 0[arr].
|
||||
* This is `arr` in both `arr[0]` and `0[arr]`.
|
||||
*/
|
||||
Expr getArrayBase() { result = this.getChild(0) }
|
||||
|
||||
/**
|
||||
* Gets the expression giving the index into the array.
|
||||
*
|
||||
* This is 0 in both arr[0] and 0[arr].
|
||||
* This is `0` in both `arr[0]` and `0[arr]`.
|
||||
*/
|
||||
Expr getArrayOffset() { result = this.getChild(1) }
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import semmle.code.cpp.exprs.Expr
|
||||
|
||||
/**
|
||||
* A C/C++ arithmetic operation.
|
||||
* A C/C++ unary arithmetic operation.
|
||||
*
|
||||
* This is an abstract base QL class.
|
||||
*/
|
||||
abstract class UnaryArithmeticOperation extends UnaryOperation { }
|
||||
|
||||
/**
|
||||
* A C/C++ unary minus expression.
|
||||
* ```
|
||||
* b = - a;
|
||||
* ```
|
||||
*/
|
||||
class UnaryMinusExpr extends UnaryArithmeticOperation, @arithnegexpr {
|
||||
override string getOperator() { result = "-" }
|
||||
@@ -18,6 +23,9 @@ class UnaryMinusExpr extends UnaryArithmeticOperation, @arithnegexpr {
|
||||
|
||||
/**
|
||||
* A C/C++ unary plus expression.
|
||||
* ```
|
||||
* b = + a;
|
||||
* ```
|
||||
*/
|
||||
class UnaryPlusExpr extends UnaryArithmeticOperation, @unaryplusexpr {
|
||||
override string getOperator() { result = "+" }
|
||||
@@ -28,16 +36,26 @@ class UnaryPlusExpr extends UnaryArithmeticOperation, @unaryplusexpr {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ GNU conjugation expression.
|
||||
* A C/C++ GNU conjugation expression. It operates on `_Complex` or
|
||||
* `__complex__ `numbers, and is similar to the C99 `conj`, `conjf` and `conjl`
|
||||
* functions.
|
||||
* ```
|
||||
* _Complex double a = ( 1.0, 2.0 );
|
||||
* _Complex double b = ~ a; // ( 1.0, - 2.0 )
|
||||
* ```
|
||||
*/
|
||||
class ConjugationExpr extends UnaryArithmeticOperation, @conjugation {
|
||||
override string getOperator() { result = "~" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "ConjugationExpr" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ `++` or `--` expression (either prefix or postfix).
|
||||
*
|
||||
* Note that this doesn't include calls to user-defined `operator++`
|
||||
* This is the abstract base QL class for increment and decrement operations.
|
||||
*
|
||||
* Note that this does not include calls to user-defined `operator++`
|
||||
* or `operator--`.
|
||||
*/
|
||||
abstract class CrementOperation extends UnaryArithmeticOperation {
|
||||
@@ -56,35 +74,38 @@ abstract class CrementOperation extends UnaryArithmeticOperation {
|
||||
/**
|
||||
* A C/C++ `++` expression (either prefix or postfix).
|
||||
*
|
||||
* Note that this doesn't include calls to user-defined `operator++`.
|
||||
* Note that this does not include calls to user-defined `operator++`.
|
||||
*/
|
||||
abstract class IncrementOperation extends CrementOperation { }
|
||||
|
||||
/**
|
||||
* A C/C++ `--` expression (either prefix or postfix).
|
||||
*
|
||||
* Note that this doesn't include calls to user-defined `operator--`.
|
||||
* Note that this does not include calls to user-defined `operator--`.
|
||||
*/
|
||||
abstract class DecrementOperation extends CrementOperation { }
|
||||
|
||||
/**
|
||||
* A C/C++ `++` or `--` prefix expression.
|
||||
*
|
||||
* Note that this doesn't include calls to user-defined operators.
|
||||
* Note that this does not include calls to user-defined operators.
|
||||
*/
|
||||
abstract class PrefixCrementOperation extends CrementOperation { }
|
||||
|
||||
/**
|
||||
* A C/C++ `++` or `--` postfix expression.
|
||||
*
|
||||
* Note that this doesn't include calls to user-defined operators.
|
||||
* Note that this does not include calls to user-defined operators.
|
||||
*/
|
||||
abstract class PostfixCrementOperation extends CrementOperation { }
|
||||
|
||||
/**
|
||||
* A C/C++ prefix increment expression, as in `++x`.
|
||||
*
|
||||
* Note that this doesn't include calls to user-defined `operator++`.
|
||||
* Note that this does not include calls to user-defined `operator++`.
|
||||
* ```
|
||||
* b = ++a;
|
||||
* ```
|
||||
*/
|
||||
class PrefixIncrExpr extends IncrementOperation, PrefixCrementOperation, @preincrexpr {
|
||||
override string getOperator() { result = "++" }
|
||||
@@ -97,7 +118,10 @@ class PrefixIncrExpr extends IncrementOperation, PrefixCrementOperation, @preinc
|
||||
/**
|
||||
* A C/C++ prefix decrement expression, as in `--x`.
|
||||
*
|
||||
* Note that this doesn't include calls to user-defined `operator--`.
|
||||
* Note that this does not include calls to user-defined `operator--`.
|
||||
* ```
|
||||
* b = --a;
|
||||
* ```
|
||||
*/
|
||||
class PrefixDecrExpr extends DecrementOperation, PrefixCrementOperation, @predecrexpr {
|
||||
override string getOperator() { result = "--" }
|
||||
@@ -110,7 +134,10 @@ class PrefixDecrExpr extends DecrementOperation, PrefixCrementOperation, @predec
|
||||
/**
|
||||
* A C/C++ postfix increment expression, as in `x++`.
|
||||
*
|
||||
* Note that this doesn't include calls to user-defined `operator++`.
|
||||
* Note that this does not include calls to user-defined `operator++`.
|
||||
* ```
|
||||
* b = a++;
|
||||
* ```
|
||||
*/
|
||||
class PostfixIncrExpr extends IncrementOperation, PostfixCrementOperation, @postincrexpr {
|
||||
override string getOperator() { result = "++" }
|
||||
@@ -125,7 +152,10 @@ class PostfixIncrExpr extends IncrementOperation, PostfixCrementOperation, @post
|
||||
/**
|
||||
* A C/C++ postfix decrement expression, as in `x--`.
|
||||
*
|
||||
* Note that this doesn't include calls to user-defined `operator--`.
|
||||
* Note that this does not include calls to user-defined `operator--`.
|
||||
* ```
|
||||
* b = a--;
|
||||
* ```
|
||||
*/
|
||||
class PostfixDecrExpr extends DecrementOperation, PostfixCrementOperation, @postdecrexpr {
|
||||
override string getOperator() { result = "--" }
|
||||
@@ -138,26 +168,45 @@ class PostfixDecrExpr extends DecrementOperation, PostfixCrementOperation, @post
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ GNU real part expression.
|
||||
* A C/C++ GNU real part expression. It operates on `_Complex` or
|
||||
* `__complex__` numbers.
|
||||
* ```
|
||||
* _Complex double f = { 2.0, 3.0 };
|
||||
* double d = __real(f); // 2.0
|
||||
* ```
|
||||
*/
|
||||
class RealPartExpr extends UnaryArithmeticOperation, @realpartexpr {
|
||||
override string getOperator() { result = "__real" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "RealPartExpr" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ GNU imaginary part expression.
|
||||
* A C/C++ GNU imaginary part expression. It operates on `_Complex` or
|
||||
* `__complex__` numbers.
|
||||
* ```
|
||||
* _Complex double f = { 2.0, 3.0 };
|
||||
* double d = __imag(f); // 3.0
|
||||
* ```
|
||||
*/
|
||||
class ImaginaryPartExpr extends UnaryArithmeticOperation, @imagpartexpr {
|
||||
override string getOperator() { result = "__imag" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "ImaginaryPartExpr" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ binary arithmetic operation.
|
||||
*
|
||||
* This is an abstract base QL class for all binary arithmetic operations.
|
||||
*/
|
||||
abstract class BinaryArithmeticOperation extends BinaryOperation { }
|
||||
|
||||
/**
|
||||
* A C/C++ add expression.
|
||||
* ```
|
||||
* c = a + b;
|
||||
* ```
|
||||
*/
|
||||
class AddExpr extends BinaryArithmeticOperation, @addexpr {
|
||||
override string getOperator() { result = "+" }
|
||||
@@ -169,6 +218,9 @@ class AddExpr extends BinaryArithmeticOperation, @addexpr {
|
||||
|
||||
/**
|
||||
* A C/C++ subtract expression.
|
||||
* ```
|
||||
* c = a - b;
|
||||
* ```
|
||||
*/
|
||||
class SubExpr extends BinaryArithmeticOperation, @subexpr {
|
||||
override string getOperator() { result = "-" }
|
||||
@@ -180,6 +232,9 @@ class SubExpr extends BinaryArithmeticOperation, @subexpr {
|
||||
|
||||
/**
|
||||
* A C/C++ multiply expression.
|
||||
* ```
|
||||
* c = a * b;
|
||||
* ```
|
||||
*/
|
||||
class MulExpr extends BinaryArithmeticOperation, @mulexpr {
|
||||
override string getOperator() { result = "*" }
|
||||
@@ -191,6 +246,9 @@ class MulExpr extends BinaryArithmeticOperation, @mulexpr {
|
||||
|
||||
/**
|
||||
* A C/C++ divide expression.
|
||||
* ```
|
||||
* c = a / b;
|
||||
* ```
|
||||
*/
|
||||
class DivExpr extends BinaryArithmeticOperation, @divexpr {
|
||||
override string getOperator() { result = "/" }
|
||||
@@ -202,6 +260,9 @@ class DivExpr extends BinaryArithmeticOperation, @divexpr {
|
||||
|
||||
/**
|
||||
* A C/C++ remainder expression.
|
||||
* ```
|
||||
* c = a % b;
|
||||
* ```
|
||||
*/
|
||||
class RemExpr extends BinaryArithmeticOperation, @remexpr {
|
||||
override string getOperator() { result = "%" }
|
||||
@@ -212,71 +273,133 @@ class RemExpr extends BinaryArithmeticOperation, @remexpr {
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ multiply expression with an imaginary number.
|
||||
* A C/C++ multiply expression with an imaginary number. This is specific to
|
||||
* C99 and later.
|
||||
* ```
|
||||
* double z;
|
||||
* _Imaginary double x, y;
|
||||
* z = x * y;
|
||||
* ```
|
||||
*/
|
||||
class ImaginaryMulExpr extends BinaryArithmeticOperation, @jmulexpr {
|
||||
override string getOperator() { result = "*" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "ImaginaryMulExpr" }
|
||||
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ divide expression with an imaginary number.
|
||||
* A C/C++ divide expression with an imaginary number. This is specific to
|
||||
* C99 and later.
|
||||
* ```
|
||||
* double z;
|
||||
* _Imaginary double y;
|
||||
* z = z / y;
|
||||
* ```
|
||||
*/
|
||||
class ImaginaryDivExpr extends BinaryArithmeticOperation, @jdivexpr {
|
||||
override string getOperator() { result = "/" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "ImaginaryDivExpr" }
|
||||
|
||||
override int getPrecedence() { result = 13 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ add expression with a real term and an imaginary term.
|
||||
* A C/C++ add expression with a real term and an imaginary term. This is
|
||||
* specific to C99 and later.
|
||||
* ```
|
||||
* double z;
|
||||
* _Imaginary double x;
|
||||
* _Complex double w;
|
||||
* w = z + x;
|
||||
* ```
|
||||
*/
|
||||
class RealImaginaryAddExpr extends BinaryArithmeticOperation, @fjaddexpr {
|
||||
override string getOperator() { result = "+" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "RealImaginaryAddExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ add expression with an imaginary term and a real term.
|
||||
* A C/C++ add expression with an imaginary term and a real term. This is
|
||||
* specific to C99 and later.
|
||||
* ```
|
||||
* double z;
|
||||
* _Imaginary double x;
|
||||
* _Complex double w;
|
||||
* w = x + z;
|
||||
* ```
|
||||
*/
|
||||
class ImaginaryRealAddExpr extends BinaryArithmeticOperation, @jfaddexpr {
|
||||
override string getOperator() { result = "+" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "ImaginaryRealAddExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ subtract expression with a real term and an imaginary term.
|
||||
* A C/C++ subtract expression with a real term and an imaginary term. This is
|
||||
* specific to C99 and later.
|
||||
* ```
|
||||
* double z;
|
||||
* _Imaginary double x;
|
||||
* _Complex double w;
|
||||
* w = z - x;
|
||||
* ```
|
||||
*/
|
||||
class RealImaginarySubExpr extends BinaryArithmeticOperation, @fjsubexpr {
|
||||
override string getOperator() { result = "-" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "RealImaginarySubExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ subtract expression with an imaginary term and a real term.
|
||||
* A C/C++ subtract expression with an imaginary term and a real term. This is
|
||||
* specific to C99 and later.
|
||||
* ```
|
||||
* double z;
|
||||
* _Imaginary double x;
|
||||
* _Complex double w;
|
||||
* w = x - z;
|
||||
* ```
|
||||
*/
|
||||
class ImaginaryRealSubExpr extends BinaryArithmeticOperation, @jfsubexpr {
|
||||
override string getOperator() { result = "-" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "ImaginaryRealSubExpr" }
|
||||
|
||||
override int getPrecedence() { result = 12 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ GNU min expression.
|
||||
* ```
|
||||
* c = a <? b;
|
||||
* ```
|
||||
*/
|
||||
class MinExpr extends BinaryArithmeticOperation, @minexpr {
|
||||
override string getOperator() { result = "<?" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "MinExpr" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A C/C++ GNU max expression.
|
||||
* ```
|
||||
* c = a >? b;
|
||||
* ```
|
||||
*/
|
||||
class MaxExpr extends BinaryArithmeticOperation, @maxexpr {
|
||||
override string getOperator() { result = ">?" }
|
||||
|
||||
override string getCanonicalQLClass() { result = "MaxExpr" }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,6 +409,10 @@ abstract class PointerArithmeticOperation extends BinaryArithmeticOperation { }
|
||||
|
||||
/**
|
||||
* A C/C++ pointer add expression.
|
||||
* ```
|
||||
* foo *ptr = &f[0];
|
||||
* ptr = ptr + 2;
|
||||
* ```
|
||||
*/
|
||||
class PointerAddExpr extends PointerArithmeticOperation, @paddexpr {
|
||||
override string getOperator() { result = "+" }
|
||||
@@ -297,6 +424,10 @@ class PointerAddExpr extends PointerArithmeticOperation, @paddexpr {
|
||||
|
||||
/**
|
||||
* A C/C++ pointer subtract expression.
|
||||
* ```
|
||||
* foo *ptr = &f[3];
|
||||
* ptr = ptr - 2;
|
||||
* ```
|
||||
*/
|
||||
class PointerSubExpr extends PointerArithmeticOperation, @psubexpr {
|
||||
override string getOperator() { result = "-" }
|
||||
@@ -308,6 +439,10 @@ class PointerSubExpr extends PointerArithmeticOperation, @psubexpr {
|
||||
|
||||
/**
|
||||
* A C/C++ pointer difference expression.
|
||||
* ```
|
||||
* foo *start = &f[0], *end = &f[4];
|
||||
* int size = end - size;
|
||||
* ```
|
||||
*/
|
||||
class PointerDiffExpr extends PointerArithmeticOperation, @pdiffexpr {
|
||||
override string getOperator() { result = "-" }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user