Skip to content

Commit ab1f96f

Browse files
Merge pull request #770 from jbj/cfg-static-init-pr
C++: Add addresses to `Expr.isConstant`
2 parents b0b2fc8 + a383a1d commit ab1f96f

File tree

11 files changed

+314
-33
lines changed

11 files changed

+314
-33
lines changed

change-notes/1.20/analysis-cpp.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@
2626

2727
## Changes to QL libraries
2828

29-
There is a new `Namespace.isInline()` predicate, which holds if the namespace was declared as `inline namespace`.
29+
* There is a new `Namespace.isInline()` predicate, which holds if the namespace was declared as `inline namespace`.
30+
* The `Expr.isConstant()` predicate now also holds for _address constant expressions_, which are addresses that will be constant after the program has been linked. These address constants do not have a result for `Expr.getValue()`.

cpp/ql/src/Likely Bugs/Likely Typos/LogicalExprCouldBeSimplified.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ string comparisonOnLiterals(ComparisonOperation op) {
4343
simple(op.getLeftOperand()) and
4444
simple(op.getRightOperand()) and
4545
not op.getAnOperand().isInMacroExpansion() and
46-
if op.isConstant()
46+
if exists(op.getValue())
4747
then result = "This comparison involves two literals and is always " + op.getValue() + "."
4848
else result = "This comparison involves two literals and should be simplified."
4949
}

cpp/ql/src/semmle/code/cpp/controlflow/internal/CFG.qll

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -461,38 +461,16 @@ private predicate skipInitializer(Initializer init) {
461461
*/
462462
private predicate runtimeExprInStaticInitializer(Expr e) {
463463
inStaticInitializer(e) and
464-
if
465-
e instanceof AggregateLiteral
466-
or
467-
e instanceof PointerArithmeticOperation
468-
or
469-
// Extractor doesn't populate this specifier at the time of writing, so
470-
// this case has not been tested. See CPP-314.
471-
e.(FunctionCall).getTarget().hasSpecifier("constexpr")
464+
if e instanceof AggregateLiteral
472465
then runtimeExprInStaticInitializer(e.getAChild())
473-
else (
474-
// Not constant
475-
not e.isConstant() and
476-
// Not a function address
477-
not e instanceof FunctionAccess and
478-
// Not a function address-of (same as above)
479-
not e.(AddressOfExpr).getOperand() instanceof FunctionAccess and
480-
// Not the address of a global variable
481-
not exists(Variable v |
482-
v.isStatic()
483-
or
484-
v instanceof GlobalOrNamespaceVariable
485-
|
486-
e.(AddressOfExpr).getOperand() = v.getAnAccess()
487-
)
488-
)
466+
else not e.getFullyConverted().isConstant()
489467
}
490468

491469
/** Holds if `e` is part of the initializer of a local static variable. */
492470
private predicate inStaticInitializer(Expr e) {
493471
exists(LocalVariable local |
494472
local.isStatic() and
495-
e.(Node).getParentNode*() = local.getInitializer()
473+
e.getParent+() = local.getInitializer()
496474
)
497475
}
498476

cpp/ql/src/semmle/code/cpp/exprs/Expr.qll

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import semmle.code.cpp.Element
33
private import semmle.code.cpp.Enclosing
44
private import semmle.code.cpp.internal.ResolveClass
5+
private import semmle.code.cpp.internal.AddressConstantExpression
56

67
/**
78
* A C/C++ expression.
@@ -84,7 +85,12 @@ class Expr extends StmtParent, @expr {
8485
string getValueText() { exists(@value v | values(v,_,result) and valuebind(v,underlyingElement(this))) }
8586

8687
/** Holds if this expression has a value that can be determined at compile time. */
87-
predicate isConstant() { valuebind(_,underlyingElement(this)) }
88+
cached
89+
predicate isConstant() {
90+
valuebind(_,underlyingElement(this))
91+
or
92+
addressConstantExpression(this)
93+
}
8894

8995
/**
9096
* Holds if this expression is side-effect free (conservative
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
private import cpp
2+
private import semmle.code.cpp.dataflow.EscapesTree
3+
4+
predicate addressConstantExpression(Expr e) {
5+
constantAddressPointer(e)
6+
or
7+
constantAddressReference(e)
8+
or
9+
// Special case for function pointers, where `fp == *fp`.
10+
constantAddressLValue(e) and
11+
e.getType() instanceof FunctionPointerType
12+
}
13+
14+
/** Holds if `v` is a constexpr variable initialized to a constant address. */
15+
private predicate addressConstantVariable(Variable v) {
16+
addressConstantExpression(v.getInitializer().getExpr().getFullyConverted()) and
17+
// Here we should also require that `v` is constexpr, but we don't have that
18+
// information in the db. See CPP-314. Instead, we require that the variable
19+
// is never defined except in its initializer.
20+
forall(Expr def | definition(v, def) | def = any(Initializer init).getExpr())
21+
}
22+
23+
/**
24+
* Holds if `lvalue` is an lvalue whose address is an _address constant
25+
* expression_.
26+
*/
27+
private predicate constantAddressLValue(Expr lvalue) {
28+
lvalue.(VariableAccess).getTarget() = any(Variable v |
29+
v.(Variable).isStatic()
30+
or
31+
v instanceof GlobalOrNamespaceVariable
32+
)
33+
or
34+
// There is no `Conversion` for the implicit conversion from a function type
35+
// to a function _pointer_ type. Instead, the type of a `FunctionAccess`
36+
// tells us how it's going to be used.
37+
lvalue.(FunctionAccess).getType() instanceof RoutineType
38+
or
39+
// String literals have array types and undergo array-to-pointer conversion.
40+
lvalue instanceof StringLiteral
41+
or
42+
// lvalue -> lvalue
43+
exists(Expr prev |
44+
constantAddressLValue(prev) and
45+
lvalueToLvalueStep(prev, lvalue)
46+
)
47+
or
48+
// pointer -> lvalue
49+
exists(Expr prev |
50+
constantAddressPointer(prev) and
51+
pointerToLvalueStep(prev, lvalue)
52+
)
53+
or
54+
// reference -> lvalue
55+
exists(Expr prev |
56+
constantAddressReference(prev) and
57+
referenceToLvalueStep(prev, lvalue)
58+
)
59+
}
60+
61+
/** Holds if `pointer` is an _address constant expression_ of pointer type. */
62+
private predicate constantAddressPointer(Expr pointer) {
63+
// There is no `Conversion` for the implicit conversion from a function type
64+
// to a function _pointer_ type. Instead, the type of a `FunctionAccess`
65+
// tells us how it's going to be used.
66+
pointer.(FunctionAccess).getType() instanceof FunctionPointerType
67+
or
68+
addressConstantVariable(pointer.(VariableAccess).getTarget()) and
69+
pointer.getType().getUnderlyingType() instanceof PointerType
70+
or
71+
// pointer -> pointer
72+
exists(Expr prev |
73+
constantAddressPointer(prev) and
74+
pointerToPointerStep(prev, pointer)
75+
)
76+
or
77+
// lvalue -> pointer
78+
exists(Expr prev |
79+
constantAddressLValue(prev) and
80+
lvalueToPointerStep(prev, pointer)
81+
)
82+
}
83+
84+
/** Holds if `reference` is an _address constant expression_ of reference type. */
85+
private predicate constantAddressReference(Expr reference) {
86+
addressConstantVariable(reference.(VariableAccess).getTarget()) and
87+
reference.getType().getUnderlyingType() instanceof ReferenceType
88+
or
89+
addressConstantVariable(reference.(VariableAccess).getTarget()) and
90+
reference.getType().getUnderlyingType() instanceof FunctionReferenceType // not a ReferenceType
91+
or
92+
// reference -> reference
93+
exists(Expr prev |
94+
constantAddressReference(prev) and
95+
referenceToReferenceStep(prev, reference)
96+
)
97+
or
98+
// lvalue -> reference
99+
exists(Expr prev |
100+
constantAddressLValue(prev) and
101+
lvalueToReferenceStep(prev, reference)
102+
)
103+
}
104+
105+
private predicate lvalueToLvalueStep(Expr lvalueIn, Expr lvalueOut) {
106+
lvalueIn = lvalueOut.(DotFieldAccess).getQualifier().getFullyConverted()
107+
or
108+
lvalueIn.getConversion() = lvalueOut.(ParenthesisExpr)
109+
or
110+
// Special case for function pointers, where `fp == *fp`.
111+
lvalueIn = lvalueOut.(PointerDereferenceExpr).getOperand().getFullyConverted() and
112+
lvalueIn.getType() instanceof FunctionPointerType
113+
}
114+
115+
private predicate pointerToLvalueStep(Expr pointerIn, Expr lvalueOut) {
116+
lvalueOut = any(ArrayExpr ae |
117+
pointerIn = ae.getArrayBase().getFullyConverted() and
118+
hasConstantValue(ae.getArrayOffset().getFullyConverted())
119+
)
120+
or
121+
pointerIn = lvalueOut.(PointerDereferenceExpr).getOperand().getFullyConverted()
122+
or
123+
pointerIn = lvalueOut.(PointerFieldAccess).getQualifier().getFullyConverted()
124+
}
125+
126+
private predicate lvalueToPointerStep(Expr lvalueIn, Expr pointerOut) {
127+
lvalueIn.getConversion() = pointerOut.(ArrayToPointerConversion)
128+
or
129+
lvalueIn = pointerOut.(AddressOfExpr).getOperand().getFullyConverted()
130+
}
131+
132+
private predicate pointerToPointerStep(Expr pointerIn, Expr pointerOut) {
133+
(
134+
pointerOut instanceof PointerAddExpr
135+
or
136+
pointerOut instanceof PointerSubExpr
137+
) and
138+
pointerIn = pointerOut.getAChild().getFullyConverted() and
139+
pointerIn.getType().getUnspecifiedType() instanceof PointerType and
140+
// The pointer arg won't be constant in the sense of `hasConstantValue`, so
141+
// this will have to match the integer argument.
142+
hasConstantValue(pointerOut.getAChild().getFullyConverted())
143+
or
144+
pointerIn = pointerOut.(UnaryPlusExpr).getOperand().getFullyConverted()
145+
or
146+
pointerIn.getConversion() = pointerOut.(Cast)
147+
or
148+
pointerIn.getConversion() = pointerOut.(ParenthesisExpr)
149+
or
150+
pointerOut = any(ConditionalExpr cond |
151+
cond.getCondition().getFullyConverted().getValue().toInt() != 0 and
152+
pointerIn = cond.getThen().getFullyConverted()
153+
or
154+
cond.getCondition().getFullyConverted().getValue().toInt() = 0 and
155+
pointerIn = cond.getElse().getFullyConverted()
156+
)
157+
or
158+
// The comma operator is allowed by C++17 but disallowed by C99. This
159+
// disjunct is a compromise that's chosen for being easy to implement.
160+
pointerOut = any(CommaExpr comma |
161+
hasConstantValue(comma.getLeftOperand()) and
162+
pointerIn = comma.getRightOperand().getFullyConverted()
163+
)
164+
}
165+
166+
private predicate lvalueToReferenceStep(Expr lvalueIn, Expr referenceOut) {
167+
lvalueIn.getConversion() = referenceOut.(ReferenceToExpr)
168+
}
169+
170+
private predicate referenceToLvalueStep(Expr referenceIn, Expr lvalueOut) {
171+
// This probably cannot happen. It would require an expression to be
172+
// converted to a reference and back again without an intermediate variable
173+
// assignment.
174+
referenceIn.getConversion() = lvalueOut.(ReferenceDereferenceExpr)
175+
}
176+
177+
private predicate referenceToReferenceStep(Expr referenceIn, Expr referenceOut) {
178+
referenceIn.getConversion() = referenceOut.(Cast)
179+
or
180+
referenceIn.getConversion() = referenceOut.(ParenthesisExpr)
181+
}
182+
183+
/** Holds if `e` is constant according to the database. */
184+
private predicate hasConstantValue(Expr e) { valuebind(_, underlyingElement(e)) }

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ private Element getRealParent(Expr expr) {
2828
result.(Destructor).getADestruction() = expr
2929
}
3030

31+
/**
32+
* Holds if `expr` is a constant of a type that can be replaced directly with
33+
* its value in the IR. This does not include address constants as we have no
34+
* means to express those as QL values.
35+
*/
36+
predicate isIRConstant(Expr expr) { exists(expr.getValue()) }
37+
3138
/**
3239
* Holds if `expr` and all of its descendants should be ignored for the purposes
3340
* of IR generation due to some property of `expr` itself. Unlike
@@ -42,7 +49,7 @@ private predicate ignoreExprAndDescendants(Expr expr) {
4249
getRealParent(expr) instanceof SwitchCase or
4350
// Ignore descendants of constant expressions, since we'll just substitute the
4451
// constant value.
45-
getRealParent(expr).(Expr).isConstant() or
52+
isIRConstant(getRealParent(expr)) or
4653
// The `DestructorCall` node for a `DestructorFieldDestruction` has a `FieldAccess`
4754
// node as its qualifier, but that `FieldAccess` does not have a child of its own.
4855
// We'll ignore that `FieldAccess`, and supply the receiver as part of the calling
@@ -125,7 +132,7 @@ private predicate translateStmt(Stmt stmt) {
125132
*/
126133
private predicate isNativeCondition(Expr expr) {
127134
expr instanceof BinaryLogicalOperation and
128-
not expr.isConstant()
135+
not isIRConstant(expr)
129136
}
130137

131138
/**
@@ -138,7 +145,7 @@ private predicate isFlexibleCondition(Expr expr) {
138145
expr instanceof NotExpr
139146
) and
140147
usedAsCondition(expr) and
141-
not expr.isConstant()
148+
not isIRConstant(expr)
142149
}
143150

144151
/**

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,7 @@ class TranslatedFunctionAccess extends TranslatedNonConstantExpr {
962962
abstract class TranslatedNonConstantExpr extends TranslatedCoreExpr, TTranslatedValueExpr {
963963
TranslatedNonConstantExpr() {
964964
this = TTranslatedValueExpr(expr) and
965-
not expr.isConstant()
965+
not isIRConstant(expr)
966966
}
967967
}
968968

@@ -974,7 +974,7 @@ abstract class TranslatedNonConstantExpr extends TranslatedCoreExpr, TTranslated
974974
abstract class TranslatedConstantExpr extends TranslatedCoreExpr, TTranslatedValueExpr {
975975
TranslatedConstantExpr() {
976976
this = TTranslatedValueExpr(expr) and
977-
expr.isConstant()
977+
isIRConstant(expr)
978978
}
979979

980980
override final Instruction getFirstInstruction() {

0 commit comments

Comments
 (0)