Skip to content

Commit 2407c8b

Browse files
committed
Python: CG trace: Better handling of builtins without __module__
Not 100% perfect, but better
1 parent 9c76618 commit 2407c8b

File tree

5 files changed

+58
-6
lines changed

5 files changed

+58
-6
lines changed

python/tools/recorded-call-graph-metrics/ql/lib/RecordedCalls.qll

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,8 @@ class XMLExternalCallee extends XMLCallee {
135135

136136
Builtin getACallee() {
137137
exists(Builtin mod |
138-
not this.get_module_data() = "None" and
139138
mod.isModule() and
140139
mod.getName() = this.get_module_data()
141-
or
142-
this.get_module_data() = "None" and
143-
mod = Builtin::builtinModule()
144140
|
145141
result = traverse_qualname(mod, this.get_qualname_data())
146142
)

python/tools/recorded-call-graph-metrics/src/cg_trace/tracer.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,41 @@ class Callee:
9595

9696

9797
BUILTIN_FUNCTION_OR_METHOD = type(print)
98+
METHOD_DESCRIPTOR_TYPE = type(dict.get)
99+
100+
101+
_unknown_module_fixup_cache = dict()
102+
103+
104+
def _unkown_module_fixup(func):
105+
# TODO: Doesn't work for everything (for example: `OrderedDict.fromkeys`, `object.__new__`)
106+
107+
module = func.__module__
108+
qualname = func.__qualname__
109+
cls_name, method_name = qualname.split(".")
110+
111+
key = (module, qualname)
112+
if key in _unknown_module_fixup_cache:
113+
return _unknown_module_fixup_cache[key]
114+
115+
matching_classes = list()
116+
for klass in object.__subclasses__():
117+
# type(dict.get) == METHOD_DESCRIPTOR_TYPE
118+
# type(dict.__new__) == BUILTIN_FUNCTION_OR_METHOD
119+
if klass.__qualname__ == cls_name and type(
120+
getattr(klass, method_name, None)
121+
) in [BUILTIN_FUNCTION_OR_METHOD, METHOD_DESCRIPTOR_TYPE]:
122+
matching_classes.append(klass)
123+
124+
if len(matching_classes) == 1:
125+
klass = matching_classes[0]
126+
ret = klass.__module__
127+
else:
128+
if DEBUG:
129+
LOGGER.debug(f"Found more than one matching class for {module} {qualname}")
130+
ret = None
131+
_unknown_module_fixup_cache[key] = ret
132+
return ret
98133

99134

100135
@better_compare_for_dataclass
@@ -109,9 +144,19 @@ class ExternalCallee(Callee):
109144

110145
@classmethod
111146
def from_arg(cls, func):
147+
# builtin bound methods seems to always return `None` for __module__, but we
148+
# might be able to recover the lost information by looking through all classes.
149+
# For example, `dict().get.__module__ is None` and `dict().get.__qualname__ ==
150+
# "dict.get"`
151+
152+
module = func.__module__
153+
qualname = func.__qualname__
154+
if module is None and qualname.count(".") == 1:
155+
module = _unkown_module_fixup(func)
156+
112157
return cls(
113-
module=func.__module__,
114-
qualname=func.__qualname__,
158+
module=module,
159+
qualname=qualname,
115160
is_builtin=type(func) == BUILTIN_FUNCTION_OR_METHOD,
116161
)
117162

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
d = dict()
2+
3+
d.get("foo") or d.get("bar")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import socket
2+
3+
sock = socket.socket()
4+
print(sock.getsockname())
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import io
2+
3+
# the `io.open` is just an alias for `_io.open`, but we record the external callee as `io.open` :|
4+
io.open("foo")

0 commit comments

Comments
 (0)