Skip to content

Commit f58c7cc

Browse files
authored
Merge pull request #1446 from hvitved/csharp/cached-stages
Approved by calumgrant
2 parents 1a9f362 + 51d093a commit f58c7cc

28 files changed

+1022
-777
lines changed

csharp/ql/src/semmle/code/cil/BasicBlock.qll

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ private import CIL
88
* A basic block, that is, a maximal straight-line sequence of control flow nodes
99
* without branches or joins.
1010
*/
11-
class BasicBlock extends Internal::TBasicBlockStart {
11+
class BasicBlock extends Cached::TBasicBlockStart {
1212
/** Gets an immediate successor of this basic block, if any. */
1313
BasicBlock getASuccessor() { result.getFirstNode() = getLastNode().getASuccessor() }
1414

@@ -52,13 +52,13 @@ class BasicBlock extends Internal::TBasicBlockStart {
5252
BasicBlock getAFalseSuccessor() { result.getFirstNode() = getLastNode().getFalseSuccessor() }
5353

5454
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
55-
ControlFlowNode getNode(int pos) { Internal::bbIndex(getFirstNode(), result, pos) }
55+
ControlFlowNode getNode(int pos) { Cached::bbIndex(getFirstNode(), result, pos) }
5656

5757
/** Gets a control flow node in this basic block. */
5858
ControlFlowNode getANode() { result = getNode(_) }
5959

6060
/** Gets the first control flow node in this basic block. */
61-
ControlFlowNode getFirstNode() { this = Internal::TBasicBlockStart(result) }
61+
ControlFlowNode getFirstNode() { this = Cached::TBasicBlockStart(result) }
6262

6363
/** Gets the last control flow node in this basic block. */
6464
ControlFlowNode getLastNode() { result = getNode(length() - 1) }
@@ -246,7 +246,7 @@ class BasicBlock extends Internal::TBasicBlockStart {
246246
* Internal implementation details.
247247
*/
248248
cached
249-
private module Internal {
249+
private module Cached {
250250
/** Internal representation of basic blocks. */
251251
cached
252252
newtype TBasicBlock = TBasicBlockStart(ControlFlowNode cfn) { startsBB(cfn) }

csharp/ql/src/semmle/code/cil/CallableReturns.qll

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ private predicate alwaysNullExpr(Expr expr) {
4141
or
4242
alwaysNullMethod(expr.(StaticCall).getTarget())
4343
or
44-
forex(VariableUpdate vu | DefUse::variableUpdateUse(_, vu, expr) |
45-
alwaysNullVariableUpdate(vu)
46-
)
44+
forex(VariableUpdate vu | DefUse::variableUpdateUse(_, vu, expr) | alwaysNullVariableUpdate(vu))
4745
}
4846

4947
pragma[noinline]

csharp/ql/src/semmle/code/csharp/Assignable.qll

Lines changed: 131 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,96 @@ class AssignableWrite extends AssignableAccess {
142142
AssignableWrite() { exists(AssignableDefinition def | def.getTargetAccess() = this) }
143143
}
144144

145+
/**
146+
* A `ref` argument in a call.
147+
*
148+
* All predicates in this class deliberately do not use the `Call` class, or any
149+
* subclass thereof, as that results in too conservative negative recursion
150+
* compilation errors.
151+
*/
152+
private class RefArg extends AssignableAccess {
153+
private Expr call;
154+
155+
private int position;
156+
157+
RefArg() {
158+
this.isRefArgument() and
159+
this = call.getChildExpr(position) and
160+
(
161+
call instanceof @method_invocation_expr
162+
or
163+
call instanceof @delegate_invocation_expr
164+
or
165+
call instanceof @local_function_invocation_expr
166+
or
167+
call instanceof @object_creation_expr
168+
)
169+
}
170+
171+
pragma[noinline]
172+
Parameter getAParameter(string name) {
173+
exists(Callable callable | result = callable.getAParameter() |
174+
expr_call(call, callable) and
175+
result.getName() = name
176+
)
177+
}
178+
179+
/** Gets the parameter that this argument corresponds to. */
180+
private Parameter getParameter() {
181+
exists(string name | result = this.getAParameter(name) |
182+
// Appears in the positional part of the call
183+
result.getPosition() = position and
184+
not exists(this.getExplicitArgumentName())
185+
or
186+
// Appears in the named part of the call
187+
name = this.getExplicitArgumentName()
188+
)
189+
}
190+
191+
private Callable getSourceDeclarationTarget(Parameter p) {
192+
p = this.getParameter().getSourceDeclaration() and
193+
result.getAParameter() = p
194+
}
195+
196+
/**
197+
* Holds if the assignment to this `ref` argument via parameter `p` is
198+
* analyzable. That is, the target callable is non-overridable and from
199+
* source.
200+
*/
201+
predicate isAnalyzable(Parameter p) {
202+
exists(Callable callable | callable = this.getSourceDeclarationTarget(p) |
203+
not callable.(Virtualizable).isOverridableOrImplementable() and
204+
callable.hasBody()
205+
)
206+
}
207+
208+
/** Gets an assignment to analyzable parameter `p`. */
209+
AssignableDefinition getAnAnalyzableRefDef(Parameter p) {
210+
this.isAnalyzable(p) and
211+
result.getTarget() = p and
212+
not result = TImplicitParameterDefinition(_)
213+
}
214+
215+
/**
216+
* Holds if this `ref` assignment is *not* analyzable. Equivalent with
217+
* `not this.isAnalyzable(_)`, but avoids negative recursion.
218+
*/
219+
private predicate isNonAnalyzable() {
220+
call instanceof @delegate_invocation_expr
221+
or
222+
exists(Callable callable | callable = this.getSourceDeclarationTarget(_) |
223+
callable.(Virtualizable).isOverridableOrImplementable() or
224+
not callable.hasBody()
225+
)
226+
}
227+
228+
/** Holds if this `ref` access is a potential assignment. */
229+
predicate isPotentialAssignment() {
230+
this.isNonAnalyzable() or
231+
exists(this.getAnAnalyzableRefDef(_))
232+
}
233+
}
234+
145235
/** INTERNAL: Do not use. */
146236
module AssignableInternal {
147237
private predicate tupleAssignmentDefinition(AssignExpr ae, Expr leaf) {
@@ -169,123 +259,6 @@ module AssignableInternal {
169259
)
170260
}
171261

172-
/**
173-
* A `ref` argument in a call.
174-
*
175-
* All predicates in this class deliberately do not use the `Call` class, or any
176-
* subclass thereof, as that results in too conservative negative recursion
177-
* compilation errors.
178-
*/
179-
private class RefArg extends AssignableAccess {
180-
private Expr call;
181-
182-
private int position;
183-
184-
RefArg() {
185-
this.isRefArgument() and
186-
this = call.getChildExpr(position) and
187-
(
188-
call instanceof @method_invocation_expr
189-
or
190-
call instanceof @delegate_invocation_expr
191-
or
192-
call instanceof @local_function_invocation_expr
193-
or
194-
call instanceof @object_creation_expr
195-
)
196-
}
197-
198-
pragma[noinline]
199-
Parameter getAParameter(string name) {
200-
exists(Callable callable | result = callable.getAParameter() |
201-
expr_call(call, callable) and
202-
result.getName() = name
203-
)
204-
}
205-
206-
/** Gets the parameter that this argument corresponds to. */
207-
private Parameter getParameter() {
208-
exists(string name | result = this.getAParameter(name) |
209-
// Appears in the positional part of the call
210-
result.getPosition() = position and
211-
not exists(this.getExplicitArgumentName())
212-
or
213-
// Appears in the named part of the call
214-
name = this.getExplicitArgumentName()
215-
)
216-
}
217-
218-
private Callable getSourceDeclarationTarget(Parameter p) {
219-
p = this.getParameter().getSourceDeclaration() and
220-
result.getAParameter() = p
221-
}
222-
223-
/**
224-
* Holds if the assignment to this `ref` argument via parameter `p` is
225-
* analyzable. That is, the target callable is non-overridable and from
226-
* source.
227-
*/
228-
predicate isAnalyzable(Parameter p) {
229-
exists(Callable callable | callable = this.getSourceDeclarationTarget(p) |
230-
not callable.(Virtualizable).isOverridableOrImplementable() and
231-
callable.hasBody()
232-
)
233-
}
234-
235-
/** Gets an assignment to analyzable parameter `p`. */
236-
AssignableDefinition getAnAnalyzableRefDef(Parameter p) {
237-
this.isAnalyzable(p) and
238-
result.getTarget() = p and
239-
not result = TImplicitParameterDefinition(_)
240-
}
241-
242-
/**
243-
* Holds if this `ref` assignment is *not* analyzable. Equivalent with
244-
* `not this.isAnalyzable(_)`, but avoids negative recursion.
245-
*/
246-
private predicate isNonAnalyzable() {
247-
call instanceof @delegate_invocation_expr
248-
or
249-
exists(Callable callable | callable = this.getSourceDeclarationTarget(_) |
250-
callable.(Virtualizable).isOverridableOrImplementable() or
251-
not callable.hasBody()
252-
)
253-
}
254-
255-
/** Holds if this `ref` access is a potential assignment. */
256-
predicate isPotentialAssignment() {
257-
this.isNonAnalyzable() or
258-
exists(this.getAnAnalyzableRefDef(_))
259-
}
260-
}
261-
262-
/** Holds if a node in basic block `bb` assigns to `ref` parameter `p` via definition `def`. */
263-
private predicate basicBlockRefParamDef(
264-
ControlFlow::BasicBlock bb, Parameter p, AssignableDefinition def
265-
) {
266-
def = any(RefArg arg).getAnAnalyzableRefDef(p) and
267-
bb.getANode() = def.getAControlFlowNode()
268-
}
269-
270-
/**
271-
* Holds if `p` is an analyzable `ref` parameter and there is a path from the
272-
* entry point of `p`'s callable to basic block `bb` without passing through
273-
* any assignments to `p`.
274-
*/
275-
private predicate parameterReachesWithoutDef(Parameter p, ControlFlow::BasicBlock bb) {
276-
forall(AssignableDefinition def | basicBlockRefParamDef(bb, p, def) |
277-
isUncertainRefCall(def.getTargetAccess())
278-
) and
279-
(
280-
any(RefArg arg).isAnalyzable(p) and
281-
p.getCallable().getEntryPoint() = bb.getFirstNode()
282-
or
283-
exists(ControlFlow::BasicBlock mid | parameterReachesWithoutDef(p, mid) |
284-
bb = mid.getASuccessor()
285-
)
286-
)
287-
}
288-
289262
// Not defined by dispatch in order to avoid too conservative negative recursion error
290263
Expr getExpr(AssignableDefinition def) {
291264
def = TAssignmentDefinition(result)
@@ -355,18 +328,6 @@ module AssignableInternal {
355328
)
356329
}
357330

358-
/**
359-
* Holds if the `ref` assignment to `aa` via call `c` is uncertain.
360-
*/
361-
cached
362-
predicate isUncertainRefCall(RefArg arg) {
363-
arg.isPotentialAssignment() and
364-
exists(ControlFlow::BasicBlock bb, Parameter p | arg.isAnalyzable(p) |
365-
parameterReachesWithoutDef(p, bb) and
366-
bb.getLastNode() = p.getCallable().getExitPoint()
367-
)
368-
}
369-
370331
// Not defined by dispatch in order to avoid too conservative negative recursion error
371332
cached
372333
Assignable getTarget(AssignableDefinition def) {
@@ -594,6 +555,47 @@ module AssignableDefinitions {
594555
override string toString() { result = ae.toString() }
595556
}
596557

558+
/** Holds if a node in basic block `bb` assigns to `ref` parameter `p` via definition `def`. */
559+
private predicate basicBlockRefParamDef(
560+
ControlFlow::BasicBlock bb, Parameter p, AssignableDefinition def
561+
) {
562+
def = any(RefArg arg).getAnAnalyzableRefDef(p) and
563+
bb.getANode() = def.getAControlFlowNode()
564+
}
565+
566+
/**
567+
* Holds if `p` is an analyzable `ref` parameter and there is a path from the
568+
* entry point of `p`'s callable to basic block `bb` without passing through
569+
* any assignments to `p`.
570+
*/
571+
private predicate parameterReachesWithoutDef(Parameter p, ControlFlow::BasicBlock bb) {
572+
forall(AssignableDefinition def | basicBlockRefParamDef(bb, p, def) |
573+
isUncertainRefCall(def.getTargetAccess())
574+
) and
575+
(
576+
any(RefArg arg).isAnalyzable(p) and
577+
p.getCallable().getEntryPoint() = bb.getFirstNode()
578+
or
579+
exists(ControlFlow::BasicBlock mid | parameterReachesWithoutDef(p, mid) |
580+
bb = mid.getASuccessor()
581+
)
582+
)
583+
}
584+
585+
/**
586+
* Holds if the `ref` assignment to `aa` via call `c` is uncertain.
587+
*/
588+
// Not in the cached module `Cached`, as that would introduce a dependency
589+
// on the CFG construction, and effectively collapse too many stages into one
590+
cached
591+
predicate isUncertainRefCall(RefArg arg) {
592+
arg.isPotentialAssignment() and
593+
exists(ControlFlow::BasicBlock bb, Parameter p | arg.isAnalyzable(p) |
594+
parameterReachesWithoutDef(p, bb) and
595+
bb.getLastNode() = p.getCallable().getExitPoint()
596+
)
597+
}
598+
597599
/**
598600
* A definition via an `out`/`ref` argument in a call, for example
599601
* `M(out x, ref y)`.

0 commit comments

Comments
 (0)