@@ -78,34 +78,63 @@ module ImportResolution {
78
78
}
79
79
80
80
/**
81
- * Holds if the module `m` defines a name `name` by assigning `defn` to it. This is an
82
- * overapproximation, as `name` may not in fact be exported (e.g. by defining an `__all__` that does
83
- * not include `name`).
81
+ * Holds if the module `m` defines a name `name` with the value `val`. The value
82
+ * represents the value `name` will have at the end of the module (the last place we
83
+ * have def-use flow to).
84
+ *
85
+ * Note: The handling of re-exporting imports is a bit simplistic. We assume that if
86
+ * an import is made, it will be re-exported (which will not be the case if a new
87
+ * value is assigned to the name, or it is deleted).
84
88
*/
85
89
pragma [ nomagic]
86
- predicate module_export ( Module m , string name , DataFlow:: CfgNode defn ) {
87
- exists ( EssaVariable v , EssaDefinition essaDef |
88
- v .getName ( ) = name and
89
- v .getAUse ( ) = m .getANormalExit ( ) and
90
- allowedEssaImportStep * ( essaDef , v .getDefinition ( ) )
90
+ predicate module_export ( Module m , string name , DataFlow:: Node val ) {
91
+ // Definitions made inside `m` itself
92
+ //
93
+ // for code such as `foo = ...; foo.bar = ...` there will be TWO
94
+ // EssaDefinition/EssaVariable. One for `foo = ...` (AssignmentDefinition) and one
95
+ // for `foo.bar = ...`. The one for `foo.bar = ...` (EssaNodeRefinement). The
96
+ // EssaNodeRefinement is the one that will reach the end of the module (normal
97
+ // exit).
98
+ //
99
+ // However, we cannot just use the EssaNodeRefinement as the `val`, because the
100
+ // normal data-flow depends on use-use flow, and use-use flow targets CFG nodes not
101
+ // EssaNodes. So we need to go back from the EssaDefinition/EssaVariable that
102
+ // reaches the end of the module, to the first definition of the variable, and then
103
+ // track forwards using use-use flow to find a suitable CFG node that has flow into
104
+ // it from use-use flow.
105
+ exists ( EssaVariable lastUseVar , EssaVariable firstDef |
106
+ lastUseVar .getName ( ) = name and
107
+ // we ignore special variable $ introduced by our analysis (not used for anything)
108
+ // we ignore special variable * introduced by `from <pkg> import *` -- TODO: understand why we even have this?
109
+ not name in [ "$" , "*" ] and
110
+ lastUseVar .getAUse ( ) = m .getANormalExit ( ) and
111
+ allowedEssaImportStep * ( firstDef , lastUseVar ) and
112
+ not allowedEssaImportStep ( _, firstDef )
91
113
|
92
- defn .getNode ( ) = essaDef .( AssignmentDefinition ) .getValue ( )
114
+ not EssaFlow:: defToFirstUse ( firstDef , _) and
115
+ val .asVar ( ) = firstDef
93
116
or
94
- defn .getNode ( ) = essaDef .( ArgumentRefinement ) .getArgument ( )
117
+ exists ( ControlFlowNode mid , ControlFlowNode end |
118
+ EssaFlow:: defToFirstUse ( firstDef , mid ) and
119
+ EssaFlow:: useToNextUse * ( mid , end ) and
120
+ not EssaFlow:: useToNextUse ( end , _) and
121
+ lastUseVar .getAUse ( ) = end and
122
+ val .asCfgNode ( ) = end
123
+ )
95
124
)
96
125
or
97
- // `from <pkg> import *`
126
+ // re-exports from `from <pkg> import *`
98
127
exists ( Module importedFrom |
99
128
importedFrom = ImportStar:: getStarImported ( m ) and
100
- module_export ( importedFrom , name , defn ) and
129
+ module_export ( importedFrom , name , val ) and
101
130
potential_module_export ( importedFrom , name )
102
131
)
103
132
or
104
- // `import <pkg>` or `from <pkg> import <stuff>`
133
+ // re-exports from `import <pkg>` or `from <pkg> import <stuff>`
105
134
exists ( Alias a |
106
- defn .asExpr ( ) = a .getValue ( ) and
135
+ val .asExpr ( ) = a .getValue ( ) and
107
136
a .getAsname ( ) .( Name ) .getId ( ) = name and
108
- defn .getScope ( ) = m
137
+ val .getScope ( ) = m
109
138
)
110
139
}
111
140
0 commit comments