@@ -257,7 +257,29 @@ module API {
257257 )
258258 }
259259
260- /** Holds if `imp` is an import of a module named `name` */
260+ /**
261+ * Holds if `import_node` is an import of a module named `name`
262+ *
263+ * Ignores relative imports (`from ..foo import bar`).
264+ *
265+ * Note that for the statement `import pkg.mod`, the new variable introduced is `pkg` that is a
266+ * reference to the module `pkg`.
267+ *
268+ * This predicate handles (with optional `... as <new-name>`):
269+ * 1. `import <name>`
270+ * 2. `from <package> import <module>` when `<name> = <package> + "." + <module>`
271+ * 3. `from <module> import <member>` when `<name> = <module> + "." + <member>`
272+ *
273+ * Finally, in `from <module> import <member>` we consider the `ImportExpr` corresponding to
274+ * `<module>` to be a reference to that module.
275+ *
276+ * Note:
277+ * While it is technically possible that `import mypkg.foo` and `from mypkg import foo` can give different values,
278+ * it's highly unlikely that this will be a problem in production level code.
279+ * Example: If `mypkg/__init__.py` contains `foo = 42`, then `from mypkg import foo` will not import the module
280+ * `mypkg/foo.py` but the variable `foo` containing `42` -- however, `import mypkg.foo` will always cause `mypkg.foo`
281+ * to refer to the module.
282+ */
261283 private predicate imports ( DataFlow:: Node import_node , string name ) {
262284 exists ( Variable var , Import imp , Alias alias |
263285 alias = imp .getAName ( ) and
@@ -271,6 +293,25 @@ module API {
271293 import_node .asExpr ( ) = alias .getValue ( )
272294 )
273295 or
296+ // Although it may seem superfluous to consider the `foo` part of `from foo import bar as baz` to
297+ // be a reference to a module (since that reference only makes sense locally within the `import`
298+ // statement), it's important for our use of type trackers to consider this local reference to
299+ // also refer to the `foo` module. That way, if one wants to track references to the `bar`
300+ // attribute using a type tracker, one can simply write
301+ //
302+ // ```ql
303+ // DataFlow::Node bar_attr_tracker(TypeTracker t) {
304+ // t.startInAttr("bar") and
305+ // result = foo_module_tracker()
306+ // or
307+ // exists(TypeTracker t2 | result = bar_attr_tracker(t2).track(t2, t))
308+ // }
309+ // ```
310+ //
311+ // Where `foo_module_tracker` is a type tracker that tracks references to the `foo` module.
312+ // Because named imports are modelled as `AttrRead`s, the statement `from foo import bar as baz`
313+ // is interpreted as if it was an assignment `baz = foo.bar`, which means `baz` gets tracked as a
314+ // reference to `foo.bar`, as desired.
274315 exists ( ImportExpr imp_expr |
275316 not imp_expr .isRelative ( ) and
276317 imp_expr .getName ( ) = name and
0 commit comments