Skip to content

Commit e452286

Browse files
authored
Merge pull request #22547 from asgerf/unified/local-variables-and-self
Unified: Introduce local variables and 'self' bindings
2 parents 9ddc15c + d2070cf commit e452286

25 files changed

Lines changed: 398 additions & 166 deletions

‎shared/namebinding/codeql/namebinding/LocalNameBinding.qll‎

Lines changed: 77 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -337,54 +337,91 @@ module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location>
337337
)
338338
}
339339

340-
private predicate accessCandInLookupScope(AstNode n, string name, Scope lookup) {
341-
accessCand(n, name) and
342-
(
343-
lookupStartsAt(n, lookup)
344-
or
345-
not lookupStartsAt(n, _) and
346-
lookup = getEnclosingScope(n)
347-
)
348-
}
349-
350-
pragma[nomagic]
351-
private predicate lookupInScope(string name, Scope lookup, Scope scope) {
352-
accessCandInLookupScope(_, name, lookup) and
353-
scope = lookup
354-
or
355-
exists(Scope mid |
356-
lookupInScope(name, lookup, mid) and
357-
not declInScope(name, mid) and
358-
not isTopScope(mid) and
359-
scope = getEnclosingScope(mid)
360-
)
361-
}
362-
363340
private predicate declInScope(string name, AstNode scope) {
364341
declInScope(_, name, scope) or
365342
implicitDeclInScope(name, scope)
366343
}
367344

345+
signature predicate accessCandSig(AstNode n, string name);
346+
368347
/**
369-
* Holds if `name`, when resolved from `lookup`, may resolve to one of the uncertain members of `scope`.
348+
* Allows resolution of access candidates.
349+
*
350+
* This is instantiated once by the local name binding library itself in order to populate `LocalAccess`.
351+
* It can be instantiated further by the client, to resolve additional lookups at a later evaluation stage.
370352
*/
371-
pragma[nomagic]
372-
private predicate lookupInUncertainScope(string name, Scope lookup, Scope scope) {
373-
lookupInScope(name, lookup, scope) and
374-
uncertainScope(scope) and
375-
not declInScope(name, scope)
353+
module ResolveAccesses<accessCandSig/2 accessCandInput> {
354+
private predicate accessCandInLookupScope(AstNode n, string name, Scope lookup) {
355+
accessCandInput(n, name) and
356+
(
357+
lookupStartsAt(n, lookup)
358+
or
359+
not lookupStartsAt(n, _) and
360+
lookup = getEnclosingScope(n)
361+
)
362+
}
363+
364+
pragma[nomagic]
365+
private predicate lookupInScope(string name, Scope lookup, Scope scope) {
366+
accessCandInLookupScope(_, name, lookup) and
367+
scope = lookup
368+
or
369+
exists(Scope mid |
370+
lookupInScope(name, lookup, mid) and
371+
not declInScope(name, mid) and
372+
not isTopScope(mid) and
373+
scope = getEnclosingScope(mid)
374+
)
375+
}
376+
377+
pragma[nomagic]
378+
private predicate resolveInScope(string name, Scope lookup, Local l) {
379+
exists(Scope scope | lookupInScope(name, lookup, scope) |
380+
l = TExplicitLocal(_, name, scope) or
381+
l = TImplicitLocal(name, scope)
382+
)
383+
}
384+
385+
/** Holds if `access` resolves to `l`. */
386+
predicate access(AstNode access, Local l) {
387+
exists(Scope lookup, string name |
388+
accessCandInLookupScope(access, name, lookup) and
389+
resolveInScope(name, lookup, l)
390+
)
391+
}
392+
393+
/**
394+
* Holds if `name`, when resolved from `lookup`, may resolve to one of the uncertain members of `scope`.
395+
*/
396+
pragma[nomagic]
397+
private predicate lookupInUncertainScope(string name, Scope lookup, Scope scope) {
398+
lookupInScope(name, lookup, scope) and
399+
uncertainScope(scope) and
400+
not declInScope(name, scope)
401+
}
402+
403+
/**
404+
* Gets an uncertain scope in which the `accessCand` pair may resolve.
405+
*/
406+
AstNode getAnUncertainScope(AstNode access, string name) {
407+
exists(Scope lookup |
408+
accessCandInLookupScope(access, name, lookup) and
409+
lookupInUncertainScope(name, lookup, result)
410+
)
411+
}
376412
}
377413

378-
/**
379-
* Gets an uncertain scope in which the `accessCand` pair may resolve.
380-
*/
381-
AstNode getAnUncertainScope(AstNode access, string name) {
382-
exists(Scope lookup |
383-
accessCandInLookupScope(access, name, lookup) and
384-
lookupInUncertainScope(name, lookup, result)
385-
)
414+
private module DefaultAccesses = ResolveAccesses<accessCand/2>;
415+
416+
/** Holds if `access` resolves to `l`. */
417+
cached
418+
private predicate access(AstNode access, Local l) {
419+
CachedStage::ref() and
420+
DefaultAccesses::access(access, l)
386421
}
387422

423+
predicate getAnUncertainScope = DefaultAccesses::getAnUncertainScope/2;
424+
388425
cached
389426
private newtype TLocal =
390427
TExplicitLocal(AstNode definingNode, string name, AstNode scope) {
@@ -447,23 +484,10 @@ module LocalNameBinding<LocationSig Location, LocalNameBindingInputSig<Location>
447484
override string getName() { result = name }
448485

449486
override Location getLocation() { result = scope.getLocation() }
450-
}
451-
452-
pragma[nomagic]
453-
private predicate resolveInScope(string name, Scope lookup, Local l) {
454-
exists(Scope scope | lookupInScope(name, lookup, scope) |
455-
l = TExplicitLocal(_, name, scope) or
456-
l = TImplicitLocal(name, scope)
457-
)
458-
}
459487

460-
cached
461-
private predicate access(AstNode access, Local l) {
462-
CachedStage::ref() and
463-
exists(Scope lookup, string name |
464-
accessCandInLookupScope(access, name, lookup) and
465-
resolveInScope(name, lookup, l)
466-
)
488+
/** Holds if this variable has the given name and scope. */
489+
pragma[nomagic]
490+
predicate hasNameAndScope(string name_, AstNode scope_) { name = name_ and scope = scope_ }
467491
}
468492

469493
/** A local access. */

‎unified/ql/lib/codeql/Definitions.qll‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
*/
44

55
private import unified
6-
private import codeql.unified.internal.StaticNameBinding
6+
private import codeql.unified.internal.NameBinding
77

88
/**
99
* Holds if `reference` refers to `definition`.
1010
*/
1111
cached
12-
predicate definitionOf(Identifier reference, NameDeclaration definition, string kind) {
12+
predicate definitionOf(Identifier reference, NameBinding definition, string kind) {
1313
definition = getStaticBindingTarget(reference) and
14-
not reference instanceof NameDeclaration and
14+
not reference instanceof NameBinding and
1515
kind = "name"
1616
}

‎unified/ql/lib/codeql/unified/internal/AnalysisQuality.qll‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
private import unified
22
private import codeql.util.ReportStats
3-
private import codeql.unified.internal.StaticNameBinding
4-
private import codeql.unified.internal.LocalNameBinding
5-
private import codeql.unified.internal.NameBindingPlugin
3+
private import codeql.unified.internal.NameBinding
64

75
/** Stats about name nodes that static name binding could resolve. */
86
module StaticNameResolutionStats implements EntityStatsSig {
@@ -44,15 +42,21 @@ module StaticNameResolutionStats implements EntityStatsSig {
4442
this = getIdentifierFromRef(ref) and
4543
not memberAccessDependsOnTypeInference(ref)
4644
) and
47-
not this instanceof NameDeclaration
45+
not this instanceof NameBinding
4846
}
4947

5048
NameBindingNode getTarget() {
51-
(
52-
result.asIdentifier() = getStaticBindingTarget(this)
53-
or
54-
result.isModuleScopeNode(_) and
55-
result.(NamespaceNode).ref().isIdentifier(this)
49+
result.asIdentifier() = getStaticBindingTarget(this)
50+
or
51+
result.isModuleScopeNode(_) and
52+
result.(NamespaceNode).ref().isIdentifier(this)
53+
or
54+
// Resolving to an implicitly-declared local such as "self" should count as
55+
// as a successfully resolved name
56+
exists(LocalName implicitLocal |
57+
implicitLocal = this.(LocalNameAccess).getLocalName() and
58+
not exists(implicitLocal.getABinding()) and
59+
result.isLocalName(implicitLocal)
5660
)
5761
}
5862

‎unified/ql/lib/codeql/unified/internal/AstExtra.qll‎

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
private import unified
6+
private import codeql.unified.internal.NameBindingPlugin
67

78
module Public {
89
/** A short-circuiting logical AND expression. */
@@ -29,24 +30,14 @@ module Public {
2930
* Declaration of a local or top-level variable.
3031
*/
3132
class LocalVariableDeclaration extends VariableDeclaration {
32-
private Block block;
33-
34-
LocalVariableDeclaration() { this = block.getStmt(_) }
35-
36-
/** Gets the block in which this variable is declared. */
37-
Block getDeclaringBlock() { result = block }
33+
LocalVariableDeclaration() { not isStaticMember(this) and not isInstanceMember(this) }
3834
}
3935

4036
/**
4137
* Declaration of a local or top-level function.
4238
*/
4339
class LocalFunctionDeclaration extends FunctionDeclaration {
44-
private Block block;
45-
46-
LocalFunctionDeclaration() { this = block.getStmt(_) }
47-
48-
/** Gets the block in which this function is declared. */
49-
Block getDeclaringBlock() { result = block }
40+
LocalFunctionDeclaration() { not isStaticMember(this) and not isInstanceMember(this) }
5041
}
5142

5243
/**

‎unified/ql/lib/codeql/unified/internal/ControlFlowGraph.qll‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,7 @@ private module Ast implements AstSig<Location> {
3939
not skipControlFlow(result)
4040
}
4141

42-
Callable getEnclosingCallable(AstNode node) {
43-
exists(AstNode parent | parent = node.getParent() |
44-
result = parent
45-
or
46-
not parent instanceof Callable and
47-
result = getEnclosingCallable(parent)
48-
)
49-
}
42+
Callable getEnclosingCallable(AstNode node) { result = node.getEnclosingCallable() }
5043

5144
class Callable = U::Callable;
5245

‎unified/ql/lib/codeql/unified/internal/FacadeAst.qll‎

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,47 @@ module Unified {
2323
)
2424
}
2525

26-
/** Gets the nearest enclosing class declaration, possibly this node itself. */
26+
/** Gets the nearest enclosing class declaration, if any. */
2727
ClassLikeDeclaration getEnclosingClass() {
28-
result = this
29-
or
30-
not this instanceof ClassLikeDeclaration and
31-
result = this.getParent().getEnclosingClass()
28+
exists(AstNode parent | parent = this.getParent() |
29+
result = parent
30+
or
31+
not parent instanceof ClassLikeDeclaration and
32+
result = parent.getEnclosingClass()
33+
)
34+
}
35+
36+
private AstNode overrideEnclosingCallableParent() {
37+
exists(FunctionExpr func |
38+
// Capture declarations are evaluated as part of the outer context, and
39+
// considered to be captured by the function expression.
40+
this = func.getACaptureDeclaration() and
41+
result = func.getParent()
42+
)
43+
}
44+
45+
/**
46+
* Gets the nearest callable containing this AST node.
47+
*
48+
* If this node is itself a callable, this gets the outer callable, not the node itself.
49+
*
50+
* Note that the `TopLevel` is callable, so all nodes other than the `TopLevel` itself has an enclosing callable.
51+
*
52+
* In some cases this predicate skips overs the syntactically-enclosing callable in order to get the callable in which
53+
* the AST is actually evaluated (such as for capture declarations in a function expression).
54+
*/
55+
Callable getEnclosingCallable() {
56+
exists(AstNode parent |
57+
parent = this.overrideEnclosingCallableParent()
58+
or
59+
not exists(this.overrideEnclosingCallableParent()) and
60+
parent = this.getParent()
61+
|
62+
result = parent
63+
or
64+
not parent instanceof Callable and
65+
result = parent.getEnclosingCallable()
66+
)
3267
}
3368

3469
/** Gets the depth of this node in the AST. The root node has a depth of 0. */

0 commit comments

Comments
 (0)