From 27d1512a75d4d3cd6989982f13138514fed9c612 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 6 Jul 2020 19:00:57 +0200 Subject: [PATCH 1/5] Python: MWE for call-graph tracing and ql comparison --- .../recorded-call-graph-metrics/README.md | 17 ++ .../recorded-call-graph-metrics/cg_trace.py | 224 ++++++++++++++++++ .../example/simple.py | 10 + .../example/simple.xml | 6 + .../ql/PointsToFound.ql | 9 + .../ql/RecordedCalls.qll | 38 +++ .../ql/UnidentifiedRecordedCalls.ql | 7 + .../recorded-call-graph-metrics/ql/qlpack.yml | 4 + .../recreate-db.sh | 23 ++ 9 files changed, 338 insertions(+) create mode 100644 python/tools/recorded-call-graph-metrics/README.md create mode 100755 python/tools/recorded-call-graph-metrics/cg_trace.py create mode 100644 python/tools/recorded-call-graph-metrics/example/simple.py create mode 100644 python/tools/recorded-call-graph-metrics/example/simple.xml create mode 100644 python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql create mode 100644 python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll create mode 100644 python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql create mode 100644 python/tools/recorded-call-graph-metrics/ql/qlpack.yml create mode 100755 python/tools/recorded-call-graph-metrics/recreate-db.sh diff --git a/python/tools/recorded-call-graph-metrics/README.md b/python/tools/recorded-call-graph-metrics/README.md new file mode 100644 index 00000000000..a249dce5a84 --- /dev/null +++ b/python/tools/recorded-call-graph-metrics/README.md @@ -0,0 +1,17 @@ +# Recorded Call Graph Metrics + +also known as _call graph tracing_. + +Execute a python program and for each call being made, record the call and callable. This allows us to compare call graph resolution from static analysis with actual data -- that is, can we statically determine the target of each actual call correctly. + +This is still in the early stages, and currently only support a very minimal working example (to show that this approach might work). + +The next hurdle is being able to handle multiple calls on the same line, such as + +- `foo(); bar()` +- `foo(bar())` +- `foo().bar()` + +## How do I give it a spin? + +Run the `recreate-db.sh` script to create the database `cg-trace-example-db`, which will include the `example/simple.xml` trace from executing the `example/simple.py` code. Then run the queries inside the `ql/` directory. diff --git a/python/tools/recorded-call-graph-metrics/cg_trace.py b/python/tools/recorded-call-graph-metrics/cg_trace.py new file mode 100755 index 00000000000..67256e47b3e --- /dev/null +++ b/python/tools/recorded-call-graph-metrics/cg_trace.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 + +"""Call Graph tracing. + +Execute a python program and for each call being made, record the call and callable. This +allows us to compare call graph resolution from static analysis with actual data -- that +is, can we statically determine the target of each actual call correctly. + +If there is 100% code coverage from the Python execution, it would also be possible to +look at the precision of the call graph resolutions -- that is, do we expect a function to +be able to be called in a place where it is not? Currently not something we're looking at. +""" + +# read: https://eli.thegreenplace.net/2012/03/23/python-internals-how-callables-work/ + +# TODO: Know that a call to a C-function was made. See +# https://docs.python.org/3/library/bdb.html#bdb.Bdb.trace_dispatch. Maybe use `lxml` as +# test + +# For inspiration, look at these projects: +# - https://github.com/joerick/pyinstrument (capture call-stack every ms for profiling) +# - https://github.com/gak/pycallgraph (display call-graph with graphviz after python execution) + +import argparse +import bdb +from io import StringIO +import sys +import os +import dis +import dataclasses +import csv +import xml.etree.ElementTree as ET + +# Copy-Paste and uncomment for interactive ipython sessions +# import IPython; IPython.embed(); sys.exit() + + +@dataclasses.dataclass(frozen=True) +class Call(): + """A call to a callable + """ + filename: str + linenum: int + inst_index: int + + @classmethod + def from_frame(cls, frame, debugger: bdb.Bdb): + code = frame.f_code + + # Uncomment to see the bytecode + # b = dis.Bytecode(frame.f_code, current_offset=frame.f_lasti) + # print(b.dis(), file=sys.__stderr__) + + return cls( + filename = debugger.canonic(code.co_filename), + linenum = frame.f_lineno, + inst_index = frame.f_lasti, + ) + + +@dataclasses.dataclass(frozen=True) +class Callable(): + """A callable (Function/Lambda) should (hopefully) be uniquely identified by its name and + location (filename+line number) + + TODO: Callable is maybe not a good name, since classes with __call__ will return true + for the python code `callable(cls)` -- will have to consider how __call__ is handled + """ + funcname: str + filename: str + linenum: int + + @classmethod + def from_frame(cls, frame, debugger: bdb.Bdb): + code = frame.f_code + return cls( + funcname = code.co_name, + filename = debugger.canonic(code.co_filename), + linenum = frame.f_lineno, + ) + + +class CallGraphTracer(bdb.Bdb): + """Tracer that records calls being made + + It would seem obvious that this should have extended `trace` library + (https://docs.python.org/3/library/trace.html), but that part is not extensible -- + however, the basic debugger (bdb) is, and provides maybe a bit more help than just + using `sys.settrace` directly. + """ + + recorded_calls: set + + def __init__(self): + self.recorded_calls = set() + super().__init__() + + def user_call(self, frame, argument_list): + call = Call.from_frame(frame.f_back, self) + callable = Callable.from_frame(frame, self) + + # _print(f'{call} -> {callable}') + self.recorded_calls.add((call, callable)) + + +################################################################################ +# Export +################################################################################ + + +class Exporter: + + @staticmethod + def export(recorded_calls, outfile_path): + raise NotImplementedError() + + @staticmethod + def dataclass_to_dict(obj): + d = dataclasses.asdict(obj) + prefix = obj.__class__.__name__.lower() + return {f"{prefix}_{key}": val for (key, val) in d.items()} + + +class CSVExporter(Exporter): + + @staticmethod + def export(recorded_calls, outfile_path): + with open(outfile_path, 'w', newline='') as csv_file: + writer = None + for (call, callable) in recorded_calls: + + data = { + **Exporter.dataclass_to_dict(call), + **Exporter.dataclass_to_dict(callable) + } + + if writer is None: + writer = csv.DictWriter(csv_file, fieldnames=data.keys()) + writer.writeheader() + + writer.writerow(data) + + + print(f'output written to {outfile_path}') + + # embed(); sys.exit() + + +class XMLExporter(Exporter): + + @staticmethod + def export(recorded_calls, outfile_path): + + root = ET.Element('root') + + for (call, callable) in recorded_calls: + data = { + **Exporter.dataclass_to_dict(call), + **Exporter.dataclass_to_dict(callable) + } + + rc = ET.SubElement(root, 'recorded_call') + # this xml library only supports serializing attributes that have string values + rc.attrib = {k: str(v) for k, v in data.items()} + + tree = ET.ElementTree(root) + tree.write(outfile_path, encoding='utf-8') + + +################################################################################ +# __main__ +################################################################################ + + +if __name__ == "__main__": + + + parser = argparse.ArgumentParser() + + + parser.add_argument('--csv') + parser.add_argument('--xml') + + parser.add_argument('progname', help='file to run as main program') + parser.add_argument('arguments', nargs=argparse.REMAINDER, + help='arguments to the program') + + opts = parser.parse_args() + + # These details of setting up the program to be run is very much inspired by `trace` + # from the standard library + sys.argv = [opts.progname, *opts.arguments] + sys.path[0] = os.path.dirname(opts.progname) + + with open(opts.progname) as fp: + code = compile(fp.read(), opts.progname, 'exec') + + # try to emulate __main__ namespace as much as possible + globs = { + '__file__': opts.progname, + '__name__': '__main__', + '__package__': None, + '__cached__': None, + } + + real_stdout = sys.stdout + real_stderr = sys.stderr + captured_stdout = StringIO() + + sys.stdout = captured_stdout + cgt = CallGraphTracer() + cgt.run(code, globs, globs) + sys.stdout = real_stdout + + if opts.csv: + CSVExporter.export(cgt.recorded_calls, opts.csv) + elif opts.xml: + XMLExporter.export(cgt.recorded_calls, opts.xml) + else: + for (call, callable) in cgt.recorded_calls: + print(f'{call} -> {callable}') + + print('--- captured stdout ---') + print(captured_stdout.getvalue(), end='') diff --git a/python/tools/recorded-call-graph-metrics/example/simple.py b/python/tools/recorded-call-graph-metrics/example/simple.py new file mode 100644 index 00000000000..626d402cb20 --- /dev/null +++ b/python/tools/recorded-call-graph-metrics/example/simple.py @@ -0,0 +1,10 @@ +def foo(): + print('foo') + +def bar(): + print('bar') + +foo() +bar() + +foo(); bar() diff --git a/python/tools/recorded-call-graph-metrics/example/simple.xml b/python/tools/recorded-call-graph-metrics/example/simple.xml new file mode 100644 index 00000000000..94ceb3a7923 --- /dev/null +++ b/python/tools/recorded-call-graph-metrics/example/simple.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql b/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql new file mode 100644 index 00000000000..9510aec598e --- /dev/null +++ b/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql @@ -0,0 +1,9 @@ +import RecordedCalls + +from ValidRecordedCall rc, Call call, Function callable, CallableValue callableValue +where + call = rc.getCall() and + callable = rc.getCallable() and + callableValue.getScope() = callable and + callableValue.getACall() = call.getAFlowNode() +select call, "-->", callable diff --git a/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll b/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll new file mode 100644 index 00000000000..01b6bf82f2e --- /dev/null +++ b/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll @@ -0,0 +1,38 @@ +import python + +class RecordedCall extends XMLElement { + RecordedCall() { + this.hasName("recorded_call") + } + + string call_filename() { result = this.getAttributeValue("call_filename") } + + int call_linenum() { result = this.getAttributeValue("call_linenum").toInt() } + + int call_inst_index() { result = this.getAttributeValue("call_inst_index").toInt() } + + Call getCall() { + // TODO: handle calls spanning multiple lines + result.getLocation().hasLocationInfo(this.call_filename(), this.call_linenum(), _, _, _) + } + + string callable_filename() { result = this.getAttributeValue("callable_filename") } + + int callable_linenum() { result = this.getAttributeValue("callable_linenum").toInt() } + + string callable_funcname() { result = this.getAttributeValue("callable_funcname") } + + Function getCallable() { + result.getLocation().hasLocationInfo(this.callable_filename(), this.callable_linenum(), _, _, _) + } +} + +/** + * Class of recorded calls where we can uniquely identify both the `call` and the `callable`. + */ +class ValidRecordedCall extends RecordedCall { + ValidRecordedCall() { + strictcount(this.getCall()) = 1 and + strictcount(this.getCallable()) = 1 + } +} diff --git a/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql b/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql new file mode 100644 index 00000000000..b2f85832f8c --- /dev/null +++ b/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql @@ -0,0 +1,7 @@ +import RecordedCalls + +from RecordedCall rc +where not rc instanceof ValidRecordedCall +select "Could not uniquely identify this recorded call (either call or callable was not uniquely identified)", + rc.call_filename(), rc.call_linenum(), rc.call_inst_index(), "-->", rc.callable_filename(), + rc.callable_linenum(), rc.callable_funcname() diff --git a/python/tools/recorded-call-graph-metrics/ql/qlpack.yml b/python/tools/recorded-call-graph-metrics/ql/qlpack.yml new file mode 100644 index 00000000000..1b4b6c0ca8c --- /dev/null +++ b/python/tools/recorded-call-graph-metrics/ql/qlpack.yml @@ -0,0 +1,4 @@ +name: codeql-python-recorded-call-graph-metrics +version: 0.0.1 +libraryPathDependencies: codeql-python +extractor: python diff --git a/python/tools/recorded-call-graph-metrics/recreate-db.sh b/python/tools/recorded-call-graph-metrics/recreate-db.sh new file mode 100755 index 00000000000..8c41f51e9ba --- /dev/null +++ b/python/tools/recorded-call-graph-metrics/recreate-db.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e +set -x + +DB="cg-trace-example-db" +SRC="example/" +XMLDIR="$SRC" +PYTHON_EXTRACTOR=$(codeql resolve extractor --language=python) + + +./cg_trace.py --xml example/simple.xml example/simple.py + +rm -rf "$DB" + + +codeql database init --source-root="$SRC" --language=python "$DB" +codeql database trace-command --working-dir="$SRC" "$DB" "$PYTHON_EXTRACTOR/tools/autobuild.sh" +codeql database index-files --language xml --include-extension .xml --working-dir="$XMLDIR" "$DB" +codeql database finalize "$DB" + +set +x +echo "Created database '$DB'" From 42227c625dc62ee309413b7877404de24ecd601b Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 7 Jul 2020 11:33:54 +0200 Subject: [PATCH 2/5] Python: Fix grammar Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com> --- python/tools/recorded-call-graph-metrics/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tools/recorded-call-graph-metrics/README.md b/python/tools/recorded-call-graph-metrics/README.md index a249dce5a84..47931f7fad8 100644 --- a/python/tools/recorded-call-graph-metrics/README.md +++ b/python/tools/recorded-call-graph-metrics/README.md @@ -4,7 +4,7 @@ also known as _call graph tracing_. Execute a python program and for each call being made, record the call and callable. This allows us to compare call graph resolution from static analysis with actual data -- that is, can we statically determine the target of each actual call correctly. -This is still in the early stages, and currently only support a very minimal working example (to show that this approach might work). +This is still in the early stages, and currently only supports a very minimal working example (to show that this approach might work). The next hurdle is being able to handle multiple calls on the same line, such as From 3127bb27d0a75c0fa3c695a5d40d3a0fd5c321ad Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 13 Jul 2020 10:54:47 +0200 Subject: [PATCH 3/5] Python: Remove strange empty line --- python/tools/recorded-call-graph-metrics/cg_trace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/tools/recorded-call-graph-metrics/cg_trace.py b/python/tools/recorded-call-graph-metrics/cg_trace.py index 67256e47b3e..1a238f66e72 100755 --- a/python/tools/recorded-call-graph-metrics/cg_trace.py +++ b/python/tools/recorded-call-graph-metrics/cg_trace.py @@ -128,7 +128,6 @@ class CSVExporter(Exporter): with open(outfile_path, 'w', newline='') as csv_file: writer = None for (call, callable) in recorded_calls: - data = { **Exporter.dataclass_to_dict(call), **Exporter.dataclass_to_dict(callable) From 1d9c3b3bcdbdaca6c26ddfad204df7fdf0186e09 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 14 Jul 2020 14:12:02 +0200 Subject: [PATCH 4/5] Python: call-graph tracing: callable => callee to use consistent naming --- .../recorded-call-graph-metrics/README.md | 2 +- .../recorded-call-graph-metrics/cg_trace.py | 31 +++++++++---------- .../example/simple.xml | 8 ++--- .../ql/PointsToFound.ql | 10 +++--- .../ql/RecordedCalls.qll | 14 ++++----- .../ql/UnidentifiedRecordedCalls.ql | 6 ++-- 6 files changed, 35 insertions(+), 36 deletions(-) diff --git a/python/tools/recorded-call-graph-metrics/README.md b/python/tools/recorded-call-graph-metrics/README.md index 47931f7fad8..4e257c0c34d 100644 --- a/python/tools/recorded-call-graph-metrics/README.md +++ b/python/tools/recorded-call-graph-metrics/README.md @@ -2,7 +2,7 @@ also known as _call graph tracing_. -Execute a python program and for each call being made, record the call and callable. This allows us to compare call graph resolution from static analysis with actual data -- that is, can we statically determine the target of each actual call correctly. +Execute a python program and for each call being made, record the call and callee. This allows us to compare call graph resolution from static analysis with actual data -- that is, can we statically determine the target of each actual call correctly. This is still in the early stages, and currently only supports a very minimal working example (to show that this approach might work). diff --git a/python/tools/recorded-call-graph-metrics/cg_trace.py b/python/tools/recorded-call-graph-metrics/cg_trace.py index 1a238f66e72..dadb97819ba 100755 --- a/python/tools/recorded-call-graph-metrics/cg_trace.py +++ b/python/tools/recorded-call-graph-metrics/cg_trace.py @@ -2,7 +2,7 @@ """Call Graph tracing. -Execute a python program and for each call being made, record the call and callable. This +Execute a python program and for each call being made, record the call and callee. This allows us to compare call graph resolution from static analysis with actual data -- that is, can we statically determine the target of each actual call correctly. @@ -37,7 +37,7 @@ import xml.etree.ElementTree as ET @dataclasses.dataclass(frozen=True) class Call(): - """A call to a callable + """A call """ filename: str linenum: int @@ -59,12 +59,11 @@ class Call(): @dataclasses.dataclass(frozen=True) -class Callable(): - """A callable (Function/Lambda) should (hopefully) be uniquely identified by its name and - location (filename+line number) +class Callee(): + """A callee (Function/Lambda/???) - TODO: Callable is maybe not a good name, since classes with __call__ will return true - for the python code `callable(cls)` -- will have to consider how __call__ is handled + should (hopefully) be uniquely identified by its name and location (filename+line + number) """ funcname: str filename: str @@ -97,10 +96,10 @@ class CallGraphTracer(bdb.Bdb): def user_call(self, frame, argument_list): call = Call.from_frame(frame.f_back, self) - callable = Callable.from_frame(frame, self) + callee = Callee.from_frame(frame, self) - # _print(f'{call} -> {callable}') - self.recorded_calls.add((call, callable)) + # _print(f'{call} -> {callee}') + self.recorded_calls.add((call, callee)) ################################################################################ @@ -127,10 +126,10 @@ class CSVExporter(Exporter): def export(recorded_calls, outfile_path): with open(outfile_path, 'w', newline='') as csv_file: writer = None - for (call, callable) in recorded_calls: + for (call, callee) in recorded_calls: data = { **Exporter.dataclass_to_dict(call), - **Exporter.dataclass_to_dict(callable) + **Exporter.dataclass_to_dict(callee) } if writer is None: @@ -152,10 +151,10 @@ class XMLExporter(Exporter): root = ET.Element('root') - for (call, callable) in recorded_calls: + for (call, callee) in recorded_calls: data = { **Exporter.dataclass_to_dict(call), - **Exporter.dataclass_to_dict(callable) + **Exporter.dataclass_to_dict(callee) } rc = ET.SubElement(root, 'recorded_call') @@ -216,8 +215,8 @@ if __name__ == "__main__": elif opts.xml: XMLExporter.export(cgt.recorded_calls, opts.xml) else: - for (call, callable) in cgt.recorded_calls: - print(f'{call} -> {callable}') + for (call, callee) in cgt.recorded_calls: + print(f'{call} -> {callee}') print('--- captured stdout ---') print(captured_stdout.getvalue(), end='') diff --git a/python/tools/recorded-call-graph-metrics/example/simple.xml b/python/tools/recorded-call-graph-metrics/example/simple.xml index 94ceb3a7923..c325139fdb2 100644 --- a/python/tools/recorded-call-graph-metrics/example/simple.xml +++ b/python/tools/recorded-call-graph-metrics/example/simple.xml @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql b/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql index 9510aec598e..95ab83eadf2 100644 --- a/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql +++ b/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql @@ -1,9 +1,9 @@ import RecordedCalls -from ValidRecordedCall rc, Call call, Function callable, CallableValue callableValue +from ValidRecordedCall rc, Call call, Function callee, CallableValue calleeValue where call = rc.getCall() and - callable = rc.getCallable() and - callableValue.getScope() = callable and - callableValue.getACall() = call.getAFlowNode() -select call, "-->", callable + callee = rc.getCallee() and + calleeValue.getScope() = callee and + calleeValue.getACall() = call.getAFlowNode() +select call, "-->", callee diff --git a/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll b/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll index 01b6bf82f2e..183956962f2 100644 --- a/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll +++ b/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll @@ -16,23 +16,23 @@ class RecordedCall extends XMLElement { result.getLocation().hasLocationInfo(this.call_filename(), this.call_linenum(), _, _, _) } - string callable_filename() { result = this.getAttributeValue("callable_filename") } + string callee_filename() { result = this.getAttributeValue("callee_filename") } - int callable_linenum() { result = this.getAttributeValue("callable_linenum").toInt() } + int callee_linenum() { result = this.getAttributeValue("callee_linenum").toInt() } - string callable_funcname() { result = this.getAttributeValue("callable_funcname") } + string callee_funcname() { result = this.getAttributeValue("callee_funcname") } - Function getCallable() { - result.getLocation().hasLocationInfo(this.callable_filename(), this.callable_linenum(), _, _, _) + Function getCallee() { + result.getLocation().hasLocationInfo(this.callee_filename(), this.callee_linenum(), _, _, _) } } /** - * Class of recorded calls where we can uniquely identify both the `call` and the `callable`. + * Class of recorded calls where we can uniquely identify both the `call` and the `callee`. */ class ValidRecordedCall extends RecordedCall { ValidRecordedCall() { strictcount(this.getCall()) = 1 and - strictcount(this.getCallable()) = 1 + strictcount(this.getCallee()) = 1 } } diff --git a/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql b/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql index b2f85832f8c..f465f30b5d1 100644 --- a/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql +++ b/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql @@ -2,6 +2,6 @@ import RecordedCalls from RecordedCall rc where not rc instanceof ValidRecordedCall -select "Could not uniquely identify this recorded call (either call or callable was not uniquely identified)", - rc.call_filename(), rc.call_linenum(), rc.call_inst_index(), "-->", rc.callable_filename(), - rc.callable_linenum(), rc.callable_funcname() +select "Could not uniquely identify this recorded call (either call or callee was not uniquely identified)", + rc.call_filename(), rc.call_linenum(), rc.call_inst_index(), "-->", rc.callee_filename(), + rc.callee_linenum(), rc.callee_funcname() From f1601d643aa46158725dd59dbb3af368170b9977 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 14 Jul 2020 14:12:56 +0200 Subject: [PATCH 5/5] Python: autoformat --- .../ql/PointsToFound.ql | 8 ++-- .../ql/RecordedCalls.qll | 38 +++++++++---------- .../ql/UnidentifiedRecordedCalls.ql | 4 +- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql b/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql index 95ab83eadf2..a22a2f3a597 100644 --- a/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql +++ b/python/tools/recorded-call-graph-metrics/ql/PointsToFound.ql @@ -2,8 +2,8 @@ import RecordedCalls from ValidRecordedCall rc, Call call, Function callee, CallableValue calleeValue where - call = rc.getCall() and - callee = rc.getCallee() and - calleeValue.getScope() = callee and - calleeValue.getACall() = call.getAFlowNode() + call = rc.getCall() and + callee = rc.getCallee() and + calleeValue.getScope() = callee and + calleeValue.getACall() = call.getAFlowNode() select call, "-->", callee diff --git a/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll b/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll index 183956962f2..e3c4bede44d 100644 --- a/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll +++ b/python/tools/recorded-call-graph-metrics/ql/RecordedCalls.qll @@ -1,38 +1,36 @@ import python class RecordedCall extends XMLElement { - RecordedCall() { - this.hasName("recorded_call") - } + RecordedCall() { this.hasName("recorded_call") } - string call_filename() { result = this.getAttributeValue("call_filename") } + string call_filename() { result = this.getAttributeValue("call_filename") } - int call_linenum() { result = this.getAttributeValue("call_linenum").toInt() } + int call_linenum() { result = this.getAttributeValue("call_linenum").toInt() } - int call_inst_index() { result = this.getAttributeValue("call_inst_index").toInt() } + int call_inst_index() { result = this.getAttributeValue("call_inst_index").toInt() } - Call getCall() { - // TODO: handle calls spanning multiple lines - result.getLocation().hasLocationInfo(this.call_filename(), this.call_linenum(), _, _, _) - } + Call getCall() { + // TODO: handle calls spanning multiple lines + result.getLocation().hasLocationInfo(this.call_filename(), this.call_linenum(), _, _, _) + } - string callee_filename() { result = this.getAttributeValue("callee_filename") } + string callee_filename() { result = this.getAttributeValue("callee_filename") } - int callee_linenum() { result = this.getAttributeValue("callee_linenum").toInt() } + int callee_linenum() { result = this.getAttributeValue("callee_linenum").toInt() } - string callee_funcname() { result = this.getAttributeValue("callee_funcname") } + string callee_funcname() { result = this.getAttributeValue("callee_funcname") } - Function getCallee() { - result.getLocation().hasLocationInfo(this.callee_filename(), this.callee_linenum(), _, _, _) - } + Function getCallee() { + result.getLocation().hasLocationInfo(this.callee_filename(), this.callee_linenum(), _, _, _) + } } /** * Class of recorded calls where we can uniquely identify both the `call` and the `callee`. */ class ValidRecordedCall extends RecordedCall { - ValidRecordedCall() { - strictcount(this.getCall()) = 1 and - strictcount(this.getCallee()) = 1 - } + ValidRecordedCall() { + strictcount(this.getCall()) = 1 and + strictcount(this.getCallee()) = 1 + } } diff --git a/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql b/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql index f465f30b5d1..414c24a9fd6 100644 --- a/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql +++ b/python/tools/recorded-call-graph-metrics/ql/UnidentifiedRecordedCalls.ql @@ -3,5 +3,5 @@ import RecordedCalls from RecordedCall rc where not rc instanceof ValidRecordedCall select "Could not uniquely identify this recorded call (either call or callee was not uniquely identified)", - rc.call_filename(), rc.call_linenum(), rc.call_inst_index(), "-->", rc.callee_filename(), - rc.callee_linenum(), rc.callee_funcname() + rc.call_filename(), rc.call_linenum(), rc.call_inst_index(), "-->", rc.callee_filename(), + rc.callee_linenum(), rc.callee_funcname()