Skip to content
This repository has been archived by the owner on Nov 23, 2024. It is now read-only.

feat: correct origins for CombinedCallGraphNodes #269

Merged
merged 22 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3628803
added TODOs
lukarade May 2, 2024
2590df0
added evaluation file
lukarade May 2, 2024
d0a73fd
feat: add origin at `resolve_references`
May 4, 2024
3d1a71e
Merge branch 'main' into 267-correct-origins-for-combinedcallgraphnodes
May 4, 2024
b5fa598
feat: added flag to shorten results
lukarade May 10, 2024
91f16ec
Merge branch 'refs/heads/main' into 267-correct-origins-for-combinedc…
lukarade May 10, 2024
c379d65
fix: linter fixes
lukarade May 10, 2024
2c58b77
style: apply automated linter fixes
megalinter-bot May 10, 2024
fe01d2d
style: apply automated linter fixes
megalinter-bot May 10, 2024
a01ea64
Merge branch 'refs/heads/main' into 267-correct-origins-for-combinedc…
lukarade Jun 5, 2024
296001b
feat: detect instance variables generated with `@property`-functions
lukarade Jun 5, 2024
66d7919
Merge branch 'refs/heads/272-missing-detection-of-properties-as-insta…
lukarade Jun 5, 2024
7874fa6
fix: fixed propagation of nodes which already were inside the CGF
lukarade Jun 6, 2024
f15df39
style: apply automated linter fixes
megalinter-bot Jun 6, 2024
d7fa0f1
style: apply automated linter fixes
megalinter-bot Jun 6, 2024
8865fd6
Merge branch 'refs/heads/main' into 267-correct-origins-for-combinedc…
lukarade Jun 18, 2024
055a9a1
fix: removed all type annotations from docstrings
lukarade Jun 18, 2024
0152420
fix: added back necessary annotations from docstrings
lukarade Jun 18, 2024
7069e07
feat: activated short mode as default (only the reason count will be …
lukarade Jun 18, 2024
9475a82
fix: linter error
lukarade Jun 18, 2024
6cd89ab
style: apply automated linter fixes
megalinter-bot Jun 18, 2024
1737a01
fix: removed return types in docstrings (again)
lukarade Jun 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/library_analyzer/cli/_run_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ def _run_api_command(

api_purity = get_purity_results(src_dir_path)
out_file_api_purity = out_dir_path.joinpath(f"{package}__api_purity.json")
api_purity.to_json_file(out_file_api_purity)
api_purity.to_json_file(
out_file_api_purity,
) # Shorten is set to True by default, therefore the results will only contain the count of each reason.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Parameter,
Reasons,
Symbol,
UnknownProto,
)


Expand All @@ -21,21 +22,21 @@ class CallGraphBuilder:

Attributes
----------
classes : dict[str, ClassScope]
classes :
Classnames in the module as key and their corresponding ClassScope instance as value.
raw_reasons : dict[NodeID, Reasons]
raw_reasons :
The raw reasons for impurity for all functions.
Keys are the ids of the functions.
call_graph_forest : CallGraphForest
call_graph_forest :
The call graph forest for the given functions.
visited : set[NodeID]
visited :
A set of all visited nodes.

Parameters
----------
classes : dict[str, ClassScope]
classes :
Classnames in the module as key and their corresponding ClassScope instance as value.
raw_reasons : dict[NodeID, Reasons]
raw_reasons :
The raw reasons for impurity for all functions.
Keys are the ids of the functions.
"""
Expand All @@ -59,7 +60,7 @@ def _build_call_graph_forest(self) -> CallGraphForest:

Returns
-------
call_graph_forest : CallGraphForest
call_graph_forest :
The call graph forest for the given functions.
"""
# Prepare the classes for the call graph.
Expand Down Expand Up @@ -131,7 +132,7 @@ def _built_call_graph(self, reason: Reasons) -> None:

Parameters
----------
reason : Reasons
reason :
The raw reasons of the function.
"""
# If the node has already been visited, return
Expand All @@ -144,13 +145,16 @@ def _built_call_graph(self, reason: Reasons) -> None:
# If the node is already inside the forest and does not have any calls left, it is considered to be finished.
if self.call_graph_forest.has_graph(reason.id) and not reason.calls:
return

# If the node is already inside the forest but still has calls left, it needs to be updated.
if self.call_graph_forest.has_graph(reason.id):
cgn = self.call_graph_forest.get_graph(reason.id)
# Create a new node and add it to the forest.
cgn = CallGraphNode(
symbol=reason.function_scope.symbol, # type: ignore[union-attr] # function_scope is never None here
reasons=reason,
)
self.call_graph_forest.add_graph(reason.id, cgn)
else:
cgn = CallGraphNode(
symbol=reason.function_scope.symbol, # type: ignore[union-attr] # function_scope is never None here
reasons=reason,
)
self.call_graph_forest.add_graph(reason.id, cgn)

# The node has calls, which need to be added to the forest and to the children of the current node.
# They are sorted to ensure a deterministic order of the children (especially but not only for testing).
Expand All @@ -172,14 +176,14 @@ def _built_call_graph(self, reason: Reasons) -> None:

# Check if the node was declared inside the current module.
elif call.id not in self.raw_reasons:
self._handle_unknown_call(call, reason.id)
self._handle_unknown_call(call, reason)

# Build the call graph for the child function and add it to the children of the current node.
else:
self._built_call_graph(self.raw_reasons[call.id])
self.call_graph_forest.get_graph(reason.id).add_child(self.call_graph_forest.get_graph(call.id))

def _handle_unknown_call(self, call: Symbol, reason_id: NodeID) -> None:
def _handle_unknown_call(self, call: Symbol, reason: Reasons) -> None:
"""Handle unknown calls.

Deal with unknown calls and add them to the forest.
Expand All @@ -188,10 +192,10 @@ def _handle_unknown_call(self, call: Symbol, reason_id: NodeID) -> None:

Parameters
----------
call : Symbol
call :
The call that is unknown.
reason_id : NodeID
The id of the function that the call is in.
reason :
The reason of the function that contains the unknown call.
"""
# Deal with the case that the call calls an imported function.
if isinstance(call, Import):
Expand All @@ -200,26 +204,32 @@ def _handle_unknown_call(self, call: Symbol, reason_id: NodeID) -> None:
reasons=Reasons(id=call.id),
)
self.call_graph_forest.add_graph(call.id, imported_cgn)
self.call_graph_forest.get_graph(reason_id).add_child(self.call_graph_forest.get_graph(call.id))
self.call_graph_forest.get_graph(reason.id).add_child(self.call_graph_forest.get_graph(call.id))

# If the call was used as a member of an MemberAccessValue, it needs to be removed from the unknown_calls.
# This is due to the improved analysis that can determine the module through the receiver of that call.
# Hence, the call is handled as a call of an imported function and not as an unknown_call
# when inferring the purity later.
for unknown_call in self.call_graph_forest.get_graph(reason_id).reasons.unknown_calls:
if unknown_call.node == call.call:
for unknown_call in self.call_graph_forest.get_graph(reason.id).reasons.unknown_calls.copy().values():
if unknown_call.symbol.node == call.call:
(
self.call_graph_forest.get_graph(reason_id).reasons.remove_unknown_call(
self.call_graph_forest.get_graph(reason.id).reasons.remove_unknown_call(
NodeID.calc_node_id(call.call),
)
)

# Deal with the case that the call calls a function parameter.
elif isinstance(call, Parameter):
self.call_graph_forest.get_graph(reason_id).reasons.unknown_calls.add(call)
self.call_graph_forest.get_graph(reason.id).reasons.unknown_calls[call.id] = UnknownProto(
symbol=call,
origin=reason.function_scope.symbol if reason.function_scope else None,
)

else:
self.call_graph_forest.get_graph(reason_id).reasons.unknown_calls.add(call)
self.call_graph_forest.get_graph(reason.id).reasons.unknown_calls[call.id] = UnknownProto(
symbol=call,
origin=reason.function_scope.symbol if reason.function_scope else None,
)

def _handle_cycles(self, removed_nodes: set[NodeID] | None = None) -> None:
"""Handle cycles in the call graph.
Expand All @@ -231,7 +241,7 @@ def _handle_cycles(self, removed_nodes: set[NodeID] | None = None) -> None:

Parameters
----------
removed_nodes : set[NodeID] | None
removed_nodes :
A set of all removed nodes.
If not given, a new set is created.
"""
Expand Down Expand Up @@ -262,16 +272,16 @@ def _test_cgn_for_cycles(

Parameters
----------
cgn : CallGraphNode
cgn :
The current node in the graph that is visited.
visited_nodes : set[NewCallGraphNode] | None
visited_nodes :
A set of all visited nodes.
path : list[NodeID] | None
path :
A list of all nodes in the current path.

Returns
-------
cycle : dict[NodeID, NewCallGraphNode]
cycle :
Dict of all nodes in the cycle.
Keys are the NodeIDs of the nodes.
Returns an empty dict if no cycle is found.
Expand Down Expand Up @@ -316,7 +326,7 @@ def _contract_cycle(self, cycle: dict[NodeID, CallGraphNode]) -> None:

Parameters
----------
cycle : dict[NodeID, CallGraphNode]
cycle :
A dict of all nodes in the cycle.
Keys are the NodeIDs of the CallGraphNodes.
"""
Expand Down Expand Up @@ -368,10 +378,10 @@ def _update_pointers(self, cycle: dict[NodeID, CallGraphNode], combined_node: Co

Parameters
----------
cycle : dict[NodeID, CallGraphNode]
cycle :
A dict of all nodes in the cycle.
Keys are the NodeIDs of the nodes.
combined_node : CombinedCallGraphNode
combined_node :
The combined node that replaces all nodes in the cycle.
"""
for graph in self.call_graph_forest.graphs.values():
Expand All @@ -386,15 +396,15 @@ def build_call_graph(classes: dict[str, ClassScope], raw_reasons: dict[NodeID, R

Parameters
----------
classes : dict[str, ClassScope]
classes :
Classnames in the module as key and their corresponding ClassScope instance as value.
raw_reasons : dict[NodeID, Reasons]
raw_reasons :
The raw reasons for impurity for all functions.
Keys are the ids of the functions.

Returns
-------
call_graph_forest : CallGraphForest
call_graph_forest :
The call graph forest for the given functions.
"""
return CallGraphBuilder(classes, raw_reasons).call_graph_forest
Loading