Skip to content

Commit c77183a

Browse files
authored
Merge pull request #151 from codellm-devkit/inheritance-call-graph-symbol-table
feat: Add inheritance support to Java call graph generation using symbol table
2 parents 6090cbb + 2c93a37 commit c77183a

2 files changed

Lines changed: 471 additions & 9 deletions

File tree

cldk/analysis/java/codeanalyzer/codeanalyzer.py

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -756,14 +756,17 @@ def __raw_call_graph_using_symbol_table_target_method(self, target_class_name: s
756756
callee_signature = call_site.callee_signature
757757

758758
if call_site.receiver_type != "":
759-
# call to any class
759+
# call to any class - check if the target method exists in receiver type hierarchy
760760
if self.get_class(qualified_class_name=call_site.receiver_type):
761-
if callee_signature == target_method_signature and call_site.receiver_type == target_class_name:
761+
# Use hierarchy search to find the method (including inherited methods)
762+
found_method, found_class = self.__find_method_in_hierarchy(call_site.receiver_type, callee_signature)
763+
if found_method is not None and callee_signature == target_method_signature and found_class == target_class_name:
762764
source_method_details = self.get_method(method_signature=method, qualified_class_name=class_name)
763765
source_class = class_name
764766
else:
765-
# check if any method exists with the signature in the class even if the receiver type is blank
766-
if callee_signature == target_method_signature and class_name == target_class_name:
767+
# check if any method exists with the signature in the class (including inherited) even if the receiver type is blank
768+
found_method, found_class = self.__find_method_in_hierarchy(class_name, callee_signature)
769+
if found_method is not None and callee_signature == target_method_signature and found_class == target_class_name:
767770
source_method_details = self.get_method(method_signature=method, qualified_class_name=class_name)
768771
source_class = class_name
769772

@@ -782,6 +785,46 @@ def __raw_call_graph_using_symbol_table_target_method(self, target_class_name: s
782785
cg.append(call_edge)
783786
return cg
784787

788+
def __find_method_in_hierarchy(self, qualified_class_name: str, method_signature: str) -> Tuple[JCallable | None, str]:
789+
"""Finds a method in the class hierarchy (including inherited methods).
790+
791+
Ignores interface methods and only returns concrete implementations.
792+
793+
Args:
794+
qualified_class_name (str): The qualified class name to start searching from.
795+
method_signature (str): The method signature to find.
796+
797+
Returns:
798+
Tuple[JCallable | None, str]: A tuple of (method_details, declaring_class).
799+
Returns (None, "") if the method is not found.
800+
"""
801+
# First, check if the method exists in the current class
802+
klass = self.get_class(qualified_class_name=qualified_class_name)
803+
method_details = self.get_method(method_signature=method_signature, qualified_class_name=qualified_class_name)
804+
805+
# If found and it's not an interface, return it (concrete implementation)
806+
if method_details is not None and klass is not None and not klass.is_interface:
807+
return method_details, qualified_class_name
808+
809+
# If not found or is an interface, check parent classes (extends) first
810+
# This ensures we find concrete implementations before interface methods
811+
if klass is not None:
812+
# Check extended classes (these are more likely to have concrete implementations)
813+
for parent_class in klass.extends_list:
814+
parent_method, found_class = self.__find_method_in_hierarchy(parent_class, method_signature)
815+
if parent_method is not None:
816+
return parent_method, found_class
817+
818+
# Only check implemented interfaces if no concrete implementation was found
819+
# This is a fallback for cases where only the interface method exists
820+
# for interface in klass.implements_list:
821+
# interface_method, found_class = self.__find_method_in_hierarchy(interface, method_signature)
822+
# if interface_method is not None:
823+
# return interface_method, found_class
824+
825+
# Do not return interface methods - only concrete implementations are included in call graph
826+
return None, ""
827+
785828
def __raw_call_graph_using_symbol_table(self, qualified_class_name: str, method_signature: str, cg=None) -> list[JGraphEdgesST]:
786829
"""Generates a call graph using symbol table information.
787830
@@ -826,16 +869,17 @@ def __raw_call_graph_using_symbol_table(self, qualified_class_name: str, method_
826869
if call_site.receiver_type != "":
827870
# call to any class
828871
if self.get_class(qualified_class_name=call_site.receiver_type):
829-
tmd = self.get_method(method_signature=callee_signature, qualified_class_name=call_site.receiver_type)
872+
# Check for method in the receiver type and its hierarchy
873+
tmd, found_class = self.__find_method_in_hierarchy(call_site.receiver_type, callee_signature)
830874
if tmd is not None:
831875
target_method_details = tmd
832-
target_class = call_site.receiver_type
876+
target_class = found_class
833877
else:
834-
# check if any method exists with the signature in the class even if the receiver type is blank
835-
tmd = self.get_method(method_signature=callee_signature, qualified_class_name=qualified_class_name)
878+
# check if any method exists with the signature in the class (including inherited) even if the receiver type is blank
879+
tmd, found_class = self.__find_method_in_hierarchy(qualified_class_name, callee_signature)
836880
if tmd is not None:
837881
target_method_details = tmd
838-
target_class = qualified_class_name
882+
target_class = found_class
839883

840884
if target_class != "" and target_method_details is not None:
841885
source: JMethodDetail

0 commit comments

Comments
 (0)