@@ -380,13 +380,14 @@ module API {
380380 // The `builtins` module should always be implicitly available
381381 name = "builtins"
382382 } or
383+ MkModuleExport ( Module mod ) or
383384 /** A use of an API member at the node `nd`. */
384385 MkUse ( DataFlow:: Node nd ) { use ( _, _, nd ) } or
385386 MkDef ( DataFlow:: Node nd ) { rhs ( _, _, nd ) }
386387
387388 class TUse = MkModuleImport or MkUse ;
388389
389- class TDef = MkDef ;
390+ class TDef = MkDef or MkModuleExport ;
390391
391392 /**
392393 * Holds if the dotted module name `sub` refers to the `member` member of `base`.
@@ -440,21 +441,19 @@ module API {
440441 )
441442 }
442443
444+ // TODO: Compare with JS, check that I'm not missing stuff
443445 /**
444446 * Holds if `rhs` is the right-hand side of a definition of a node that should have an
445447 * incoming edge from `base` labeled `lbl` in the API graph.
446448 */
447449 cached
448450 predicate rhs ( TApiNode base , Label:: ApiLabel lbl , DataFlow:: Node rhs ) {
449- /*
450- * exists(string m, string prop | // TODO: Figure out module exports in Python
451- * base = MkModuleExport(m) and
452- * lbl = Label::member(prop) and
453- * exports(m, prop, rhs)
454- * )
455- * or
456- */
457-
451+ exists ( Module mod , string prop |
452+ base = MkModuleExport ( mod ) and
453+ exports ( mod , prop , rhs ) and
454+ lbl = Label:: member ( prop )
455+ )
456+ or
458457 exists ( DataFlow:: Node def , DataFlow:: LocalSourceNode pred |
459458 rhs ( base , def ) and pred = trackDefNode ( def )
460459 |
@@ -557,13 +556,23 @@ module API {
557556 ref .asExpr ( ) = fn .getInnerScope ( ) .getArg ( i )
558557 )
559558 /*
560- * or // TODO: Figure out self.
559+ * or // TODO: Figure out self. (and arg = -2, that might be a thing in python)
561560 * lbl = Label::receiver() and
562561 * ref = fn.getReceiver()
563562 */
564563
565564 )
566565 or
566+ /*
567+ * or // TODO: Figure out classes.
568+ * exists(DataFlow::Node def, DataFlow::ClassNode cls, int i |
569+ * rhs(base, def) and cls = trackDefNode(def)
570+ * |
571+ * lbl = Label::parameter(i) and
572+ * ref = cls.getConstructor().getParameter(i)
573+ * )
574+ */
575+
567576 // Built-ins, treated as members of the module `builtins`
568577 base = MkModuleImport ( "builtins" ) and
569578 lbl = Label:: member ( any ( string name | ref = Builtins:: likelyBuiltin ( name ) ) )
@@ -674,7 +683,8 @@ module API {
674683 */
675684 cached
676685 predicate rhs ( TApiNode nd , DataFlow:: Node rhs ) {
677- // exists(string m | nd = MkModuleExport(m) | exports(m, rhs)) // TODO: Figure out module exported in Py.
686+ // TODO: There are no "default" exports in python, right? E.g. in `import foo`, `foo` cannot be a function.
687+ // exists(string m | nd = MkModuleExport(m) | exports(m, rhs))
678688 // or
679689 nd = MkDef ( rhs )
680690 }
@@ -687,11 +697,13 @@ module API {
687697 /* There's an edge from the root node for each imported module. */
688698 exists ( string m |
689699 pred = MkRoot ( ) and
690- lbl = Label:: mod ( m )
691- |
692- succ = MkModuleImport ( m ) and
700+ lbl = Label:: mod ( m ) and
693701 // Only allow undotted names to count as base modules.
694702 not m .matches ( "%.%" )
703+ |
704+ succ = MkModuleImport ( m )
705+ or
706+ succ = MkModuleExport ( any ( Module mod | mod .getName ( ) = m and mod .isPackage ( ) ) )
695707 )
696708 or
697709 /* Step from the dotted module name `foo.bar` to `foo.bar.baz` along an edge labeled `baz` */
@@ -706,10 +718,18 @@ module API {
706718 succ = MkUse ( ref )
707719 )
708720 or
721+ exists ( Module parentMod , Module childMod , string edge |
722+ pred = MkModuleExport ( parentMod ) and
723+ succ = MkModuleExport ( childMod ) and
724+ parentMod .getSubModule ( edge ) = childMod and // TODO: __init__.py shows up here, handle those in some other way. See e.g. https://stackoverflow.com/questions/38927979/default-export-in-python-3
725+ lbl = Label:: member ( edge )
726+ )
727+ or
709728 exists ( DataFlow:: Node rhs |
710729 rhs ( pred , lbl , rhs ) and
711730 succ = MkDef ( rhs )
712731 )
732+ // TODO: Compare with JS, check that I'm not missing stuff
713733 }
714734
715735 /**
@@ -737,12 +757,21 @@ module API {
737757 private import semmle.python.dataflow.new.internal.ImportStar
738758
739759 newtype TLabel =
740- MkLabelModule ( string mod ) { exists ( Impl:: MkModuleImport ( mod ) ) } or
760+ MkLabelModule ( string mod ) {
761+ (
762+ exists ( Impl:: MkModuleImport ( mod ) )
763+ or
764+ exists ( Module m | exists ( Impl:: MkModuleExport ( m ) ) | mod = m .getName ( ) )
765+ ) and
766+ not mod .matches ( "%.%" ) // only top level modules count as base modules
767+ } or
741768 MkLabelMember ( string member ) {
742769 member = any ( DataFlow:: AttrRef pr ) .getAttributeName ( ) or
743770 exists ( Builtins:: likelyBuiltin ( member ) ) or
744771 ImportStar:: namePossiblyDefinedInImportStar ( _, member , _) or
745- Impl:: prefix_member ( _, member , _)
772+ Impl:: prefix_member ( _, member , _) or
773+ exists ( any ( Module mod ) .getSubModule ( member ) ) or
774+ exports ( _, member , _)
746775 } or
747776 MkLabelUnknownMember ( ) or
748777 MkLabelParameter ( int i ) {
@@ -850,3 +879,14 @@ module API {
850879 LabelAwait await ( ) { any ( ) }
851880 }
852881}
882+
883+ /** Holds if module `mod` exports `rhs` under the name `prop`. */
884+ private predicate exports ( Module mod , string prop , DataFlow:: Node rhs ) {
885+ exists ( Assign assign |
886+ assign = mod .getAStmt ( ) and
887+ rhs .asExpr ( ) = assign .getValue ( ) and
888+ exists ( Variable v | assign .defines ( v ) and prop = v .getId ( ) )
889+ )
890+ // TODO: Re-exports.
891+ // TODO: use this predicate with __init__.py?
892+ }
0 commit comments