Skip to content

Commit 6c664e9

Browse files
authored
Merge pull request github#14035 from asgerf/shared/variable-capture-nested
Variable capture: synchronize with aliases in nested scopes
2 parents 7af1e96 + 1286235 commit 6c664e9

File tree

2 files changed

+41
-11
lines changed

2 files changed

+41
-11
lines changed

java/ql/test/library-tests/dataflow/capture/B.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ void testCapturedLambda() {
210210
r1.run();
211211
};
212212
r2.run();
213-
sink(out.get(0)); // $ MISSING: hasValueFlow=double.capture.out
213+
sink(out.get(0)); // $ hasValueFlow=double.capture.out
214214
}
215215

216216
void testEnhancedForStmtCapture() {

shared/dataflow/codeql/dataflow/VariableCapture.qll

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,14 @@ module Flow<InputSig Input> implements OutputSig<Input> {
391391
msg = "ClosureExpr has no body" and not ce.hasBody(_)
392392
}
393393

394-
query predicate closureAliasMustBeLocal(ClosureExpr ce, Expr access, string msg) {
394+
query predicate closureAliasMustBeInSameScope(ClosureExpr ce, Expr access, string msg) {
395395
exists(BasicBlock bb1, BasicBlock bb2 |
396396
ce.hasAliasedAccess(access) and
397397
ce.hasCfgNode(bb1, _) and
398398
access.hasCfgNode(bb2, _) and
399-
bb1.getEnclosingCallable() != bb2.getEnclosingCallable() and
400-
msg = "ClosureExpr has non-local alias - these are ignored"
399+
not bb1.getEnclosingCallable() = callableGetEnclosingCallable*(bb2.getEnclosingCallable()) and
400+
msg =
401+
"ClosureExpr has an alias outside the scope of its enclosing callable - these are ignored"
401402
)
402403
}
403404

@@ -440,7 +441,7 @@ module Flow<InputSig Input> implements OutputSig<Input> {
440441
n = strictcount(VariableWrite vw | localWriteStep(vw, msg)) or
441442
n = strictcount(VariableRead vr | uniqueReadVariable(vr, msg)) or
442443
n = strictcount(ClosureExpr ce | closureMustHaveBody(ce, msg)) or
443-
n = strictcount(ClosureExpr ce, Expr access | closureAliasMustBeLocal(ce, access, msg)) or
444+
n = strictcount(ClosureExpr ce, Expr access | closureAliasMustBeInSameScope(ce, access, msg)) or
444445
n = strictcount(CapturedVariable v, Callable c | variableAccessAstNesting(v, c, msg)) or
445446
n = strictcount(Callable c | uniqueCallableLocation(c, msg))
446447
}
@@ -518,10 +519,39 @@ module Flow<InputSig Input> implements OutputSig<Input> {
518519
}
519520

520521
/** Gets the enclosing callable of `ce`. */
521-
private Callable closureExprGetCallable(ClosureExpr ce) {
522+
private Callable closureExprGetEnclosingCallable(ClosureExpr ce) {
522523
exists(BasicBlock bb | ce.hasCfgNode(bb, _) and result = bb.getEnclosingCallable())
523524
}
524525

526+
/** Gets the enclosing callable of `inner`. */
527+
pragma[nomagic]
528+
private Callable callableGetEnclosingCallable(Callable inner) {
529+
exists(ClosureExpr closure |
530+
closure.hasBody(inner) and
531+
result = closureExprGetEnclosingCallable(closure)
532+
)
533+
}
534+
535+
/**
536+
* Gets a callable that contains `ce`, or a reference to `ce` into which `ce` could be inlined without
537+
* bringing any variables out of scope.
538+
*
539+
* If `ce` was to be inlined into that reference, the resulting callable
540+
* would become the enclosing callable, and thus capture the same variables as `ce`.
541+
* In some sense, we model captured aliases as if this inlining has happened.
542+
*/
543+
private Callable closureExprGetAReferencingCallable(ClosureExpr ce) {
544+
result = closureExprGetEnclosingCallable(ce)
545+
or
546+
exists(Expr expr, BasicBlock bb |
547+
ce.hasAliasedAccess(expr) and
548+
expr.hasCfgNode(bb, _) and
549+
result = bb.getEnclosingCallable() and
550+
// The reference to `ce` is allowed to occur in a more deeply nested context
551+
closureExprGetEnclosingCallable(ce) = callableGetEnclosingCallable*(result)
552+
)
553+
}
554+
525555
/**
526556
* Holds if `v` is available in `c` through capture. This can either be due to
527557
* an explicit variable reference or through the construction of a closure
@@ -534,7 +564,7 @@ module Flow<InputSig Input> implements OutputSig<Input> {
534564
)
535565
or
536566
exists(ClosureExpr ce |
537-
c = closureExprGetCallable(ce) and
567+
c = closureExprGetAReferencingCallable(ce) and
538568
closureCaptures(ce, v) and
539569
c != v.getCallable()
540570
)
@@ -562,15 +592,15 @@ module Flow<InputSig Input> implements OutputSig<Input> {
562592

563593
/**
564594
* Holds if `access` is a reference to `ce` evaluated in the `i`th node of `bb`.
565-
* The reference is restricted to be in the same callable as `ce` as a
595+
* The reference is restricted to be nested within the same callable as `ce` as a
566596
* precaution, even though this is expected to hold for all the given aliased
567597
* accesses.
568598
*/
569-
private predicate localClosureAccess(ClosureExpr ce, Expr access, BasicBlock bb, int i) {
599+
private predicate localOrNestedClosureAccess(ClosureExpr ce, Expr access, BasicBlock bb, int i) {
570600
ce.hasAliasedAccess(access) and
571601
access.hasCfgNode(bb, i) and
572602
pragma[only_bind_out](bb.getEnclosingCallable()) =
573-
pragma[only_bind_out](closureExprGetCallable(ce))
603+
pragma[only_bind_out](closureExprGetAReferencingCallable(ce))
574604
}
575605

576606
/**
@@ -587,7 +617,7 @@ module Flow<InputSig Input> implements OutputSig<Input> {
587617
exists(ClosureExpr ce | closureCaptures(ce, v) |
588618
ce.hasCfgNode(bb, i) and ce = closure
589619
or
590-
localClosureAccess(ce, closure, bb, i)
620+
localOrNestedClosureAccess(ce, closure, bb, i)
591621
) and
592622
if v.getCallable() != bb.getEnclosingCallable() then topScope = false else topScope = true
593623
}

0 commit comments

Comments
 (0)