Skip to content

Commit ff5cbe9

Browse files
Implement package expressions2
1 parent 0eb6feb commit ff5cbe9

36 files changed

+2501
-26
lines changed

c/misra/src/rules/RULE-18-9/ArrayToPointerConversionOfTemporaryObject.ql

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import cpp
1717
import codingstandards.c.misra
1818
import codingstandards.c.Objects
19+
import codingstandards.cpp.Expr
1920

2021
/**
2122
* Holds if the value of an expression is used or stored.
@@ -37,32 +38,14 @@ predicate isUsedOrStored(Expr e) {
3738
e = any(ClassAggregateLiteral l).getAFieldExpr(_)
3839
}
3940

40-
/**
41-
* Find expressions that defer their value directly to an inner expression
42-
* value.
43-
*
44-
* When an array is on the rhs of a comma expr, or in the then/else branch of a
45-
* ternary expr, and the result us used as a pointer, then the ArrayToPointer
46-
* conversion is marked inside comma expr/ternary expr, on the operands. These
47-
* conversions are only non-compliant if they flow into an operation or store.
48-
*
49-
* Full flow analysis with localFlowStep should not be necessary, and may cast a
50-
* wider net than needed for some queries, potentially resulting in false
51-
* positives.
52-
*/
53-
Expr temporaryObjectFlowStep(Expr e) {
54-
e = result.(CommaExpr).getRightOperand()
55-
or
56-
e = result.(ConditionalExpr).getThen()
57-
or
58-
e = result.(ConditionalExpr).getElse()
59-
}
60-
6141
from FieldAccess fa, TemporaryObjectIdentity temporary, ArrayToPointerConversion conversion
6242
where
6343
not isExcluded(conversion, InvalidMemory3Package::arrayToPointerConversionOfTemporaryObjectQuery()) and
6444
fa = temporary.getASubobjectAccess() and
6545
conversion.getExpr() = fa and
66-
isUsedOrStored(temporaryObjectFlowStep*(conversion.getExpr()))
46+
exists(Expr useOrStore |
47+
temporaryObjectFlowStep*(conversion.getExpr(), useOrStore) and
48+
isUsedOrStored(useOrStore)
49+
)
6750
select conversion, "Array to pointer conversion of array $@ from temporary object $@.",
6851
fa.getTarget(), fa.getTarget().getName(), temporary, temporary.toString()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- All queries related to integer suffixes:
2+
- No visible changes expected: the regex for parsing integer suffixes, and how they are treated after lexing, has been refactored.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- `RULE-18-9` - `ArraytoPointerConversionOfTemporaryObject.ql`
2+
- The behavior for finding flow steps of temporary objects (for example, from ternary branches to the ternary expr result) has been extracted for reuse in other rules, no visible changes expected.

cpp/common/src/codingstandards/cpp/Cpp14Literal.qll

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,33 @@ module Cpp14Literal {
1212
/** Convenience for implementing class `UnrecognizedNumericLiteral` */
1313
abstract private class RecognizedNumericLiteral extends StandardLibrary::Literal { }
1414

15+
/** An integer literal suffix (e.g. 'u', 'll', etc, or even an empty string). */
16+
class IntegerLiteralSuffix extends string {
17+
string uPart;
18+
string lPart;
19+
20+
IntegerLiteralSuffix() {
21+
uPart = ["", "u", "U"] and
22+
lPart = ["", "l", "L"] + ["", "l", "L"] and
23+
this = uPart + lPart
24+
}
25+
26+
predicate isSigned() { uPart = "" }
27+
28+
predicate isUnsigned() { uPart != "" }
29+
30+
int getLCount() { result = lPart.length() }
31+
}
32+
1533
/** An integer literal. */
1634
abstract class IntegerLiteral extends NumericLiteral {
1735
predicate isSigned() { not isUnsigned() }
1836

19-
predicate isUnsigned() { getValueText().regexpMatch(".*[uU][lL]?$") }
37+
predicate isUnsigned() { getSuffix().isUnsigned() }
38+
39+
IntegerLiteralSuffix getSuffix() {
40+
result = getValueText().regexpCapture("[^uUlL]*([uU]?[lL]*)\\s*$", 1)
41+
}
2042
}
2143

2244
/**

cpp/common/src/codingstandards/cpp/Expr.qll

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,28 @@ predicate isCompileTimeEvaluatedExpression(Expr expression) {
242242
)
243243
}
244244

245+
/**
246+
* Find expressions that defer their value directly to an inner expression
247+
* value -- how temporary object can flow without being copied or having their
248+
* address taken.
249+
*
250+
* Full flow analysis with localFlowStep is often not necessary for certain
251+
* rules related to temporary objects, and may cast a wider net than needed for
252+
* those queries.
253+
*
254+
* When an array is on the rhs of a comma expr, or in the then/else branch of a
255+
* ternary expr, and the result us used as a pointer, then the ArrayToPointer
256+
* conversion is marked inside comma expr/ternary expr, on the operands. These
257+
* conversions are only non-compliant if they flow into an operation or store.
258+
*/
259+
predicate temporaryObjectFlowStep(Expr source, Expr sink) {
260+
source = sink.(CommaExpr).getRightOperand()
261+
or
262+
source = sink.(ConditionalExpr).getThen()
263+
or
264+
source = sink.(ConditionalExpr).getElse()
265+
}
266+
245267
predicate isDirectCompileTimeEvaluatedExpression(Expr expression) {
246268
expression instanceof Literal
247269
or
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import codingstandards.cpp.alertreporting.CustomPathStateProblem
2+
import codingstandards.cpp.Expr
3+
import codingstandards.cpp.Scope
4+
5+
signature class LambdaSig extends LambdaExpression;
6+
7+
class ImplicitThisCapturingLambdaExpr extends LambdaExpression {
8+
ImplicitThisCapturingLambdaExpr() {
9+
exists(LambdaCapture capture |
10+
capture = getACapture() and
11+
capture.getField().getName() = "(captured this)" and
12+
capture.isImplicit()
13+
)
14+
}
15+
}
16+
17+
class ImplicitCaptureLambdaExpr extends LambdaExpression {
18+
LambdaCapture capture;
19+
20+
ImplicitCaptureLambdaExpr() { capture = getCapture(_) and capture.isImplicit() }
21+
22+
LambdaCapture getImplicitCapture() { result = capture }
23+
}
24+
25+
/**
26+
* A module to find lambda expressions that are eventually copied or moved.
27+
*
28+
* Unfortunately, CodeQL does not capture all lambda flow or all lambda copies/moves. However,
29+
* since lambdas can only be used in an extremely limited number of ways, we can easily roll our
30+
* own dataflow-like analysis as a custom path problem, to match lambdas to stores.
31+
*/
32+
module TransientLambda<LambdaSig Source> {
33+
final class FinalLocatable = Locatable;
34+
35+
/**
36+
* Create a custom path problem, which inherently performs a graph search to find paths from start
37+
* nodes (in this case, lambda expressions) to end nodes (in this case, copies and moves of
38+
* lambdas).
39+
*/
40+
private module TransientLambdaConfig implements CustomPathStateProblemConfigSig {
41+
class Node extends FinalLocatable {
42+
Node() {
43+
this instanceof Source
44+
or
45+
this instanceof Variable
46+
or
47+
this.(VariableAccess).getTarget() instanceof Parameter
48+
or
49+
this instanceof Expr
50+
or
51+
this instanceof Function
52+
or
53+
this instanceof NewExpr
54+
}
55+
}
56+
57+
class State = TranslationUnit;
58+
59+
// Do not search past the first copy or move of a lambda.
60+
predicate searchPastEnd() { none() }
61+
62+
predicate start(Node n, TranslationUnit state) { n instanceof Source and state = n.getFile() }
63+
64+
bindingset[state]
65+
pragma[inline_late]
66+
predicate end(Node n, TranslationUnit state) {
67+
n instanceof Variable and not n instanceof Parameter
68+
or
69+
n instanceof Function and
70+
not functionDefinedInTranslationUnit(n, state)
71+
or
72+
exists(NewExpr alloc |
73+
alloc = n and
74+
alloc.getAllocatedType().stripTopLevelSpecifiers() instanceof Closure
75+
)
76+
}
77+
78+
predicate edge(Node a, TranslationUnit s1, Node b, TranslationUnit s2) {
79+
s2 = s1 and
80+
(
81+
a = b.(Variable).getInitializer().getExpr()
82+
or
83+
exists(Call fc, int i |
84+
a = fc.getArgument(i) and
85+
(
86+
b = fc.getTarget().getParameter(i)
87+
or
88+
b = fc.getTarget()
89+
)
90+
)
91+
or
92+
exists(Call fc, Function f, ReturnStmt ret |
93+
f = fc.getTarget() and
94+
ret.getEnclosingFunction() = f and
95+
a = ret.getExpr() and
96+
b = fc
97+
)
98+
or
99+
b = a.(Parameter).getAnAccess()
100+
or
101+
temporaryObjectFlowStep(a, b)
102+
or
103+
a = b.(NewExpr).getInitializer()
104+
)
105+
}
106+
}
107+
108+
import CustomPathStateProblem<TransientLambdaConfig> as TransientFlow
109+
110+
module PathProblem {
111+
import TransientFlow
112+
}
113+
114+
predicate isStored(Source lambda, Element store, string reason) {
115+
TransientFlow::problem(lambda, store) and
116+
(
117+
if store instanceof Function and not functionDefinedInTranslationUnit(store, lambda.getFile())
118+
then reason = "passed to a different translation unit"
119+
else reason = "copied or moved"
120+
)
121+
}
122+
}
123+
124+
/**
125+
* An alterate module for detecting transient lambdas which uses the standard CodeQL dataflow
126+
* library.
127+
*
128+
* Ideally, this module eventually replaces `TransientLambda`, however, current CodeQL support for
129+
* flow of lambdas is unreliable and incomplete/inconsistent. This implementation does not detect
130+
* all cases correctly, but it is a starting place to revisit at a later time.
131+
*
132+
* In the current dataflow library, there are many missing nodes and edges, making this currently
133+
* difficult or impossible to implement correctly.
134+
*/
135+
module TransientLambdaDataFlow<LambdaSig Source> {
136+
import semmle.code.cpp.dataflow.new.DataFlow as DataFlow
137+
import DataFlow::DataFlow as NewDataFlow
138+
139+
final class FinalLocatable = Locatable;
140+
141+
/**
142+
* Create a custom path problem, which inherently performs a graph search to find paths from start
143+
* nodes (in this case, lambda expressions) to end nodes (in this case, copies and moves of
144+
* lambdas).
145+
*/
146+
module TransientLambdaConfig implements NewDataFlow::StateConfigSig {
147+
class FlowState = TranslationUnit;
148+
149+
predicate isSource(NewDataFlow::Node n, TranslationUnit state) {
150+
n.asExpr() instanceof Source and state = n.asExpr().getFile()
151+
}
152+
153+
predicate isSink(NewDataFlow::Node n) {
154+
n.asOperand().getDef().getAst() instanceof VariableDeclarationEntry
155+
or
156+
exists(n.asVariable()) and not exists(n.asParameter())
157+
or
158+
exists(NewExpr alloc |
159+
alloc.getAllocatedType() instanceof Closure and
160+
alloc.getInitializer() = n.asExpr()
161+
)
162+
or
163+
// Detect casting to std::function, which results in a copy of the lambda.
164+
exists(Conversion conv | conv.getExpr() = n.asExpr())
165+
or
166+
// Detect all function calls, in case the definition is in a different translation unit.
167+
// We cannot detect this with stateful dataflow, for performance reasons.
168+
exists(FunctionCall fc | fc.getAnArgument() = n.asExpr())
169+
}
170+
171+
predicate isSink(NewDataFlow::Node n, TranslationUnit state) {
172+
// Ideally, we would be able to check here for calls to functions defined outside of the
173+
// translation unit, but in the current stateful dataflow library, this will result in a
174+
// cartesian product of all nodes with all translation units. This limitation doesn't exist
175+
// in the alternate `TransientLambda` module which uses `CustomPathStateProblem`.
176+
//
177+
// Since this predicate holds for none(), it may seem that we don't need to use stateful flow.
178+
// However, stateful flow is still a good idea so that we can add isBarrier() to prevent flow
179+
// out of the translation unit. That should be possible to do without introducing a
180+
// cartesian product.
181+
//
182+
// To work around the cartesian product, this predicate holds for none() and `isSink(n)`
183+
// should hold for all function calls. After flow has found lambda/function call pairs, we
184+
// can filter out those pairs where the function is defined in a different translation unit.
185+
//
186+
// This isn't quite implemented yet.
187+
none()
188+
}
189+
190+
predicate isBarrierOut(NewDataFlow::Node n, TranslationUnit state) {
191+
// TODO: Implement a barrier to prevent flow out of the translation unit.
192+
none()
193+
}
194+
195+
predicate isAdditionalFlowStep(
196+
NewDataFlow::Node a, TranslationUnit s1, NewDataFlow::Node b, TranslationUnit s2
197+
) {
198+
// Add additional flow steps to handle:
199+
//
200+
// auto x = []() { ... };
201+
//
202+
// Which isn't represented in the dataflow graph otherwise.
203+
pragma[only_bind_out](s2) = s1 and
204+
(
205+
pragma[only_bind_out](a.asExpr()) =
206+
b.asOperand()
207+
.getDef()
208+
.getAst()
209+
.(VariableDeclarationEntry)
210+
.getVariable()
211+
.getInitializer()
212+
.getExpr()
213+
or
214+
a.asExpr().(Conversion).getExpr() = b.asExpr()
215+
)
216+
}
217+
}
218+
219+
import NewDataFlow::GlobalWithState<TransientLambdaConfig> as TransientFlow
220+
221+
module PathProblem {
222+
import TransientFlow::PathGraph
223+
}
224+
225+
predicate isStored(Source lambda, Element store) {
226+
exists(NewDataFlow::Node sink |
227+
TransientFlow::flow(NewDataFlow::exprNode(lambda), sink) and
228+
store = sink.asOperand().getDef().getAst() and
229+
not exists(FunctionCall fc |
230+
fc.getAnArgument() = sink.asExpr() and
231+
exists(FunctionDeclarationEntry funcDef |
232+
funcDef = fc.getTarget().getDefinition() and
233+
funcDef.getFile() = lambda.getFile().(TranslationUnit).getATransitivelyIncludedFile()
234+
)
235+
)
236+
)
237+
}
238+
}

cpp/common/src/codingstandards/cpp/Locations.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,14 @@ bindingset[filepath, lineNumber]
2828
int getLastColumnNumber(string filepath, int lineNumber) {
2929
result = max(Location l | l.hasLocationInfo(filepath, _, _, lineNumber, _) | l.getEndColumn())
3030
}
31+
32+
bindingset[a, b]
33+
predicate shareEnding(Locatable a, Locatable b) {
34+
exists(Location la, Location lb |
35+
la = a.getLocation() and
36+
lb = b.getLocation() and
37+
la.getFile() = lb.getFile() and
38+
la.getEndLine() = lb.getEndLine() and
39+
la.getEndColumn() = lb.getEndColumn()
40+
)
41+
}

cpp/common/src/codingstandards/cpp/Scope.qll

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,15 @@ predicate inSameTranslationUnitLate(File f1, File f2) {
307307
)
308308
}
309309

310+
bindingset[f, tu]
311+
pragma[inline_late]
312+
predicate functionDefinedInTranslationUnit(Function f, TranslationUnit tu) {
313+
exists(FunctionDeclarationEntry fde | fde = f.getDefinition() |
314+
fde = f.getDefinition() and
315+
fde.getFile() = tu.getATransitivelyIncludedFile()
316+
)
317+
}
318+
310319
/** A file that is a C/C++ source file */
311320
class SourceFile extends File {
312321
SourceFile() {

0 commit comments

Comments
 (0)