Skip to content

Commit ba9dc9f

Browse files
Copilotyoff
authored andcommitted
Python: wire import-statement bindings into the shared CFG (green)
Adds `ImportStmt` and `ImportStarStmt` wrappers in `AstNodeImpl.qll`. For each `Alias` in an import statement, both the value (module/member expression) and the bound `asname` Name become children of the CFG node for the import statement, in evaluation order. Without this, every `Name` introduced by `import` / `from .. import ..` lacked a CFG node, even though `Name.defines(v)` returns true for it on the AST side. This was the highest-volume gap: 20,332 missing import aliases across CPython. Removes the corresponding MISSING: annotations from imports.py. Verified: all 24 ControlFlow/evaluation-order tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 768ebc1 commit ba9dc9f

2 files changed

Lines changed: 59 additions & 9 deletions

File tree

python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,54 @@ module Ast implements AstSig<Py::Location> {
512512
}
513513
}
514514

515+
/**
516+
* An `import` statement (`import a, b` or `from m import a, b`).
517+
*
518+
* Each alias contributes two children in evaluation order: first the
519+
* value expression (which performs the import side-effect), then the
520+
* bound `asname` Name (the in-scope binding). This makes both reachable
521+
* from the CFG and allows `Name.defines(v)` for `asname` Names to have
522+
* corresponding CFG nodes — which is essential for SSA to see import
523+
* bindings.
524+
*/
525+
additional class ImportStmt extends Stmt {
526+
private Py::Import imp;
527+
528+
ImportStmt() { this = TPyStmt(imp) }
529+
530+
/** Gets the value (module/member expression) of the `n`th alias. */
531+
Expr getValue(int n) { result.asExpr() = imp.getName(n).getValue() }
532+
533+
/** Gets the bound `asname` of the `n`th alias. */
534+
Expr getAsname(int n) { result.asExpr() = imp.getName(n).getAsname() }
535+
536+
/** Gets the number of aliases in this import statement. */
537+
int getNumberOfAliases() { result = count(int i | exists(imp.getName(i))) }
538+
539+
override AstNode getChild(int index) {
540+
exists(int i |
541+
index = 2 * i and result = this.getValue(i)
542+
or
543+
index = 2 * i + 1 and result = this.getAsname(i)
544+
)
545+
}
546+
}
547+
548+
/**
549+
* A `from m import *` statement. Evaluates the module expression but
550+
* binds no name (the bindings happen by side-effect at runtime, which
551+
* is not modelled at the CFG level).
552+
*/
553+
additional class ImportStarStmt extends Stmt {
554+
private Py::ImportStar imp;
555+
556+
ImportStarStmt() { this = TPyStmt(imp) }
557+
558+
Expr getModule() { result.asExpr() = imp.getModule() }
559+
560+
override AstNode getChild(int index) { index = 0 and result = this.getModule() }
561+
}
562+
515563
/** A `with` statement. */
516564
additional class WithStmt extends Stmt {
517565
private Py::With withStmt;
Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
# Import aliases. All bound names below currently lack a CFG node.
1+
# Import aliases — all bound names below are now reachable via the new
2+
# CFG's `ImportStmt` wrapper.
23

3-
import os # $ MISSING: cfgdefines=os
4-
import os.path # $ MISSING: cfgdefines=os
5-
import os as o # $ MISSING: cfgdefines=o
6-
from os import path # $ MISSING: cfgdefines=path
7-
from os import path as p # $ MISSING: cfgdefines=p
8-
from os import sep, linesep # $ MISSING: cfgdefines=sep MISSING: cfgdefines=linesep
4+
import os # $ cfgdefines=os
5+
import os.path # $ cfgdefines=os
6+
import os as o # $ cfgdefines=o
7+
from os import path # $ cfgdefines=path
8+
from os import path as p # $ cfgdefines=p
9+
from os import sep, linesep # $ cfgdefines=sep cfgdefines=linesep
910
from os import (
10-
getcwd, # $ MISSING: cfgdefines=getcwd
11-
getcwdb, # $ MISSING: cfgdefines=getcwdb
11+
getcwd, # $ cfgdefines=getcwd
12+
getcwdb, # $ cfgdefines=getcwdb
1213
)
14+

0 commit comments

Comments
 (0)