@@ -3,30 +3,35 @@ import semmle.python.types.Builtins
33import semmle.python.objects.Callables
44import BytecodeExpr
55
6+ /** The XML data for a recorded call (includes all data). */
67class XMLRecordedCall extends XMLElement {
78 XMLRecordedCall ( ) { this .hasName ( "recorded_call" ) }
89
9- Call getCall ( ) { result = this .getXMLCall ( ) .getCall ( ) }
10-
10+ /** Gets the XML data for the call. */
1111 XMLCall getXMLCall ( ) { result .getParent ( ) = this }
1212
13- Function getPythonCallee ( ) { result = this .getXMLCallee ( ) .( XMLPythonCallee ) .getCallee ( ) }
14-
15- Builtin getBuiltinCallee ( ) { result = this .getXMLCallee ( ) .( XMLExternalCallee ) .getCallee ( ) }
13+ /** Gets a call matching the recorded information. */
14+ Call getACall ( ) { result = this .getXMLCall ( ) .getACall ( ) }
1615
16+ /** Gets the XML data for the callee. */
1717 XMLCallee getXMLCallee ( ) { result .getParent ( ) = this }
1818
19- /** Get a different `XMLRecordedCall` with the same result-set for `getCall`. */
19+ /** Gets a python function matching the recorded information of the callee. */
20+ Function getAPythonCallee ( ) { result = this .getXMLCallee ( ) .( XMLPythonCallee ) .getACallee ( ) }
21+
22+ /** Gets a builtin function matching the recorded information of the callee. */
23+ Builtin getABuiltinCallee ( ) { result = this .getXMLCallee ( ) .( XMLExternalCallee ) .getACallee ( ) }
24+
25+ /** Get a different `XMLRecordedCall` with the same result-set for `getACall`. */
2026 XMLRecordedCall getOtherWithSameSetOfCalls ( ) {
2127 // `rc` is for a different bytecode instruction on same line
2228 not result .getXMLCall ( ) .get_inst_index_data ( ) = this .getXMLCall ( ) .get_inst_index_data ( ) and
2329 result .getXMLCall ( ) .get_filename_data ( ) = this .getXMLCall ( ) .get_filename_data ( ) and
2430 result .getXMLCall ( ) .get_linenum_data ( ) = this .getXMLCall ( ) .get_linenum_data ( ) and
25- // set of calls is equal
26- // 1. this.getCall() issubset result.getCall()
27- not exists ( Call call | call = this .getCall ( ) | not result .getCall ( ) = call ) and
28- // 2. result.getCall() issubset this.getCall()
29- not exists ( Call call | call = result .getCall ( ) | not this .getCall ( ) = call )
31+ // set of calls are equal
32+ forall ( Call call | call = this .getACall ( ) or call = result .getACall ( ) |
33+ call = this .getACall ( ) and call = result .getACall ( )
34+ )
3035 }
3136
3237 override string toString ( ) {
@@ -46,6 +51,7 @@ class XMLRecordedCall extends XMLElement {
4651 }
4752}
4853
54+ /** The XML data for the call part a recorded call. */
4955class XMLCall extends XMLElement {
5056 XMLCall ( ) { this .hasName ( "Call" ) }
5157
@@ -55,11 +61,13 @@ class XMLCall extends XMLElement {
5561
5662 int get_inst_index_data ( ) { result = this .getAChild ( "inst_index" ) .getTextValue ( ) .toInt ( ) }
5763
58- Call getCall ( ) {
64+ /** Gets a call that matches the recorded information. */
65+ Call getACall ( ) {
5966 // TODO: do we handle calls spanning multiple lines?
6067 this .matchBytecodeExpr ( result , this .getAChild ( "bytecode_expr" ) .getAChild ( ) )
6168 }
6269
70+ /** Holds if `expr` can be fully matched with `bytecode`. */
6371 private predicate matchBytecodeExpr ( Expr expr , XMLBytecodeExpr bytecode ) {
6472 exists ( Call parent_call , XMLBytecodeCall parent_bytecode_call |
6573 parent_call
@@ -77,6 +85,7 @@ class XMLCall extends XMLElement {
7785 bytecode .( XMLBytecodeAttribute ) .get_object_data ( ) )
7886 or
7987 matchBytecodeExpr ( expr .( Call ) .getFunc ( ) , bytecode .( XMLBytecodeCall ) .get_function_data ( ) )
88+ //
8089 // I considered allowing a partial match as well. That is, if the bytecode
8190 // expression information only tells us `<unknown>.foo()`, and we find an AST
8291 // expression that matches on `.foo()`, that is good enough.
@@ -92,8 +101,10 @@ class XMLCall extends XMLElement {
92101 }
93102}
94103
104+ /** The XML data for the callee part a recorded call. */
95105abstract class XMLCallee extends XMLElement { }
96106
107+ /** The XML data for the callee part a recorded call, when the callee is a Python function. */
97108class XMLPythonCallee extends XMLCallee {
98109 XMLPythonCallee ( ) { this .hasName ( "PythonCallee" ) }
99110
@@ -103,7 +114,7 @@ class XMLPythonCallee extends XMLCallee {
103114
104115 string get_funcname_data ( ) { result = this .getAChild ( "funcname" ) .getTextValue ( ) }
105116
106- Function getCallee ( ) {
117+ Function getACallee ( ) {
107118 result .getLocation ( ) .hasLocationInfo ( this .get_filename_data ( ) , this .get_linenum_data ( ) , _, _, _)
108119 or
109120 // if function has decorator, the call will be recorded going to the first
@@ -114,14 +125,15 @@ class XMLPythonCallee extends XMLCallee {
114125 }
115126}
116127
128+ /** The XML data for the callee part a recorded call, when the callee is a C function or builtin. */
117129class XMLExternalCallee extends XMLCallee {
118130 XMLExternalCallee ( ) { this .hasName ( "ExternalCallee" ) }
119131
120132 string get_module_data ( ) { result = this .getAChild ( "module" ) .getTextValue ( ) }
121133
122134 string get_qualname_data ( ) { result = this .getAChild ( "qualname" ) .getTextValue ( ) }
123135
124- Builtin getCallee ( ) {
136+ Builtin getACallee ( ) {
125137 exists ( Builtin mod |
126138 not this .get_module_data ( ) = "None" and
127139 mod .isModule ( ) and
@@ -135,6 +147,10 @@ class XMLExternalCallee extends XMLCallee {
135147 }
136148}
137149
150+ /**
151+ * Helper predicate. If parent = `builtins` and qualname = `list.append`, it will
152+ * return the result of `builtins.list.append`.class
153+ */
138154private Builtin traverse_qualname ( Builtin parent , string qualname ) {
139155 not qualname = "__objclass__" and
140156 not qualname .matches ( "%.%" ) and
@@ -150,15 +166,15 @@ private Builtin traverse_qualname(Builtin parent, string qualname) {
150166}
151167
152168/**
153- * Class of recorded calls where we can identify both the `call` and the `callee`.
169+ * Class of recorded calls where we can identify both the `call` and the `callee` uniquely .
154170 */
155171class IdentifiedRecordedCall extends XMLRecordedCall {
156172 IdentifiedRecordedCall ( ) {
157- strictcount ( this .getCall ( ) ) = 1 and
173+ strictcount ( this .getACall ( ) ) = 1 and
158174 (
159- strictcount ( this .getPythonCallee ( ) ) = 1
175+ strictcount ( this .getAPythonCallee ( ) ) = 1
160176 or
161- strictcount ( this .getBuiltinCallee ( ) ) = 1
177+ strictcount ( this .getABuiltinCallee ( ) ) = 1
162178 )
163179 or
164180 // Handle case where the same function is called multiple times in one line, for
@@ -169,19 +185,19 @@ class IdentifiedRecordedCall extends XMLRecordedCall {
169185 // without this `strictcount`, in the case `func(); func(); func()`, if 1 of the calls
170186 // is not recorded, we woulld still mark the other two recorded calls as valid
171187 // (which is not following the rules above). + 1 to count `this` as well.
172- strictcount ( this .getCall ( ) ) = strictcount ( this .getOtherWithSameSetOfCalls ( ) ) + 1 and
188+ strictcount ( this .getACall ( ) ) = strictcount ( this .getOtherWithSameSetOfCalls ( ) ) + 1 and
173189 forex ( XMLRecordedCall rc | rc = this .getOtherWithSameSetOfCalls ( ) |
174- unique( Function f | f = this .getPythonCallee ( ) ) =
175- unique( Function f | f = rc .getPythonCallee ( ) )
190+ unique( Function f | f = this .getAPythonCallee ( ) ) =
191+ unique( Function f | f = rc .getAPythonCallee ( ) )
176192 or
177- unique( Builtin b | b = this .getBuiltinCallee ( ) ) =
178- unique( Builtin b | b = rc .getBuiltinCallee ( ) )
193+ unique( Builtin b | b = this .getABuiltinCallee ( ) ) =
194+ unique( Builtin b | b = rc .getABuiltinCallee ( ) )
179195 )
180196 }
181197
182198 override string toString ( ) {
183199 exists ( string callee_str |
184- exists ( Function callee , string path | callee = this .getPythonCallee ( ) |
200+ exists ( Function callee , string path | callee = this .getAPythonCallee ( ) |
185201 (
186202 path = callee .getLocation ( ) .getFile ( ) .getRelativePath ( )
187203 or
@@ -192,13 +208,16 @@ class IdentifiedRecordedCall extends XMLRecordedCall {
192208 callee .toString ( ) + " (" + path + ":" + callee .getLocation ( ) .getStartLine ( ) + ")"
193209 )
194210 or
195- callee_str = this .getBuiltinCallee ( ) .toString ( )
211+ callee_str = this .getABuiltinCallee ( ) .toString ( )
196212 |
197213 result = super .toString ( ) + " --> " + callee_str
198214 )
199215 }
200216}
201217
218+ /**
219+ * Class of recorded calls where we cannot identify both the `call` and the `callee` uniquely.
220+ */
202221class UnidentifiedRecordedCall extends XMLRecordedCall {
203222 UnidentifiedRecordedCall ( ) { not this instanceof IdentifiedRecordedCall }
204223}
@@ -216,26 +235,28 @@ class IgnoredRecordedCall extends XMLRecordedCall {
216235 }
217236}
218237
238+ /** Provides classes for call-graph resolution by using points-to. */
219239module PointsToBasedCallGraph {
240+ /** An IdentifiedRecordedCall that can be resolved with points-to */
220241 class ResolvableRecordedCall extends IdentifiedRecordedCall {
221242 Value calleeValue ;
222243
223244 ResolvableRecordedCall ( ) {
224245 exists ( Call call , XMLCallee xmlCallee |
225- call = this .getCall ( ) and
246+ call = this .getACall ( ) and
226247 calleeValue .getACall ( ) = call .getAFlowNode ( ) and
227248 xmlCallee = this .getXMLCallee ( ) and
228249 (
229250 xmlCallee instanceof XMLPythonCallee and
230- calleeValue .( PythonFunctionValue ) .getScope ( ) = xmlCallee .( XMLPythonCallee ) .getCallee ( )
251+ calleeValue .( PythonFunctionValue ) .getScope ( ) = xmlCallee .( XMLPythonCallee ) .getACallee ( )
231252 or
232253 xmlCallee instanceof XMLExternalCallee and
233254 calleeValue .( BuiltinFunctionObjectInternal ) .getBuiltin ( ) =
234- xmlCallee .( XMLExternalCallee ) .getCallee ( )
255+ xmlCallee .( XMLExternalCallee ) .getACallee ( )
235256 or
236257 xmlCallee instanceof XMLExternalCallee and
237258 calleeValue .( BuiltinMethodObjectInternal ) .getBuiltin ( ) =
238- xmlCallee .( XMLExternalCallee ) .getCallee ( )
259+ xmlCallee .( XMLExternalCallee ) .getACallee ( )
239260 )
240261 )
241262 }
0 commit comments