From 71a25c8974a4a4534d774d6d4ba4faefe5f9e2dd Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 4 Sep 2024 14:15:05 -0400 Subject: [PATCH 1/2] Update codeanalyzer.py --- .../java/codeanalyzer/codeanalyzer.py | 474 ++++++------------ 1 file changed, 161 insertions(+), 313 deletions(-) diff --git a/cldk/analysis/java/codeanalyzer/codeanalyzer.py b/cldk/analysis/java/codeanalyzer/codeanalyzer.py index c05e6b4..eff3fa8 100644 --- a/cldk/analysis/java/codeanalyzer/codeanalyzer.py +++ b/cldk/analysis/java/codeanalyzer/codeanalyzer.py @@ -12,11 +12,16 @@ from networkx import DiGraph -from cldk.analysis import AnalysisLevel from cldk.analysis.java.treesitter import JavaSitter from cldk.models.java import JGraphEdges -from cldk.models.java.models import JApplication, JCallable, JField, JMethodDetail, JType, JCompilationUnit, \ - JGraphEdgesST +from cldk.models.java.models import ( + JApplication, + JCallable, + JField, + JMethodDetail, + JType, + JCompilationUnit, +) from typing import Dict, List, Tuple from typing import Union @@ -51,14 +56,14 @@ class JCodeanalyzer: """ def __init__( - self, - project_dir: Union[str, Path], - source_code: str | None, - analysis_backend_path: Union[str, Path, None], - analysis_json_path: Union[str, Path, None], - analysis_level: str, - use_graalvm_binary: bool, - eager_analysis: bool, + self, + project_dir: Union[str, Path], + source_code: str | None, + analysis_backend_path: Union[str, Path, None], + analysis_json_path: Union[str, Path, None], + analysis_level: str, + use_graalvm_binary: bool, + eager_analysis: bool, ) -> None: self.project_dir = project_dir self.source_code = source_code @@ -68,12 +73,15 @@ def __init__( self.eager_analysis = eager_analysis self.analysis_level = analysis_level self.application = self._init_codeanalyzer( - analysis_level=1 if analysis_level == AnalysisLevel.symbol_table else 2) + analysis_level=1 if analysis_level == "symbol_table" else 2 + ) # Attributes related the Java code analysis... - if analysis_level == AnalysisLevel.call_graph: - self.call_graph: DiGraph = self._generate_call_graph(using_symbol_table=False) - else: + if analysis_level == "symbol_table": self.call_graph: DiGraph | None = None + else: + self.call_graph: DiGraph = self._generate_call_graph( + using_symbol_table=False + ) @staticmethod def _download_or_update_code_analyzer(filepath: Path) -> str: @@ -102,12 +110,18 @@ def _download_or_update_code_analyzer(filepath: Path) -> str: if match: datetime_str = match.group(0) else: - raise Exception(f"Release URL {download_url} does not contain a datetime pattern.") + raise Exception( + f"Release URL {download_url} does not contain a datetime pattern." + ) # Look for codeanalyzer.YYYYMMDDTHHMMSS.jar in the filepath - current_codeanalyzer_jars = [jarfile for jarfile in filepath.glob("*.jar")] + current_codeanalyzer_jars = [ + jarfile for jarfile in filepath.glob("*.jar") + ] if not any(current_codeanalyzer_jars): - logger.info(f"Codeanalzyer jar is not found. Downloading the latest version.") + logger.info( + f"Codeanalzyer jar is not found. Downloading the latest version." + ) filename = filepath / f"codeanalyzer.{datetime_str}.jar" urlretrieve(download_url, filename) return filename.__str__() @@ -117,9 +131,12 @@ def _download_or_update_code_analyzer(filepath: Path) -> str: if match: current_datetime_str = match.group(0) - if datetime.strptime(datetime_str, date_format) > datetime.strptime(current_datetime_str, - date_format): - logger.info(f"Codeanalzyer jar is outdated. Downloading the latest version.") + if datetime.strptime( + datetime_str, date_format + ) > datetime.strptime(current_datetime_str, date_format): + logger.info( + f"Codeanalzyer jar is outdated. Downloading the latest version." + ) # Remove the older codeanalyzer jar for jarfile in current_codeanalyzer_jars: jarfile.unlink() @@ -128,13 +145,17 @@ def _download_or_update_code_analyzer(filepath: Path) -> str: urlretrieve(download_url, filename) else: filename = current_codeanalyzer_jar_name - logger.info(f"Codeanalzyer jar is already at the latest version.") + logger.info( + f"Codeanalzyer jar is already at the latest version." + ) else: filename = current_codeanalyzer_jar_name return filename.__str__() else: - raise Exception(f"Failed to fetch release warn: {response.status_code} {response.text}") + raise Exception( + f"Failed to fetch release warn: {response.status_code} {response.text}" + ) def _get_application(self) -> JApplication: """ @@ -168,25 +189,29 @@ def _get_codeanalyzer_exec(self) -> List[str]: if self.use_graalvm_binary: with resources.as_file( - resources.files("cldk.analysis.java.codeanalyzer.bin") / "codeanalyzer") as codeanalyzer_bin_path: + resources.files("cldk.analysis.java.codeanalyzer.bin") / "codeanalyzer" + ) as codeanalyzer_bin_path: codeanalyzer_exec = shlex.split(codeanalyzer_bin_path.__str__()) else: - print(f'analysis path: {self.analysis_json_path}') - analysis_json_path_file = Path(self.analysis_json_path).joinpath("analysis.json") if self.analysis_backend_path: analysis_backend_path = Path(self.analysis_backend_path) logger.info(f"Using codeanalyzer.jar from {analysis_backend_path}") - codeanalyzer_exec = shlex.split(f"java -jar {analysis_backend_path / 'codeanalyzer.jar'}") - elif analysis_json_path_file.exists(): - logger.info(f"Using existing analysis from {self.analysis_json_path}") - codeanalyzer_exec = shlex.split(f"java -jar codeanalyzer.jar") + codeanalyzer_exec = shlex.split( + f"java -jar {analysis_backend_path / 'codeanalyzer.jar'}" + ) else: # Since the path to codeanalyzer.jar was not provided, we'll download the latest version from GitHub. - with resources.as_file(resources.files("cldk.analysis.java.codeanalyzer.jar")) as codeanalyzer_jar_path: + with resources.as_file( + resources.files("cldk.analysis.java.codeanalyzer.jar") + ) as codeanalyzer_jar_path: # Download the codeanalyzer jar if it doesn't exist, update if it's outdated, # do nothing if it's up-to-date. - codeanalyzer_jar_file = self._download_or_update_code_analyzer(codeanalyzer_jar_path) - codeanalyzer_exec = shlex.split(f"java -jar {codeanalyzer_jar_file}") + codeanalyzer_jar_file = self._download_or_update_code_analyzer( + codeanalyzer_jar_path + ) + codeanalyzer_exec = shlex.split( + f"java -jar {codeanalyzer_jar_file}" + ) return codeanalyzer_exec def _init_codeanalyzer(self, analysis_level=1) -> JApplication: @@ -206,7 +231,8 @@ def _init_codeanalyzer(self, analysis_level=1) -> JApplication: if self.analysis_json_path is None: logger.info("Reading analysis from the pipe.") codeanalyzer_args = codeanalyzer_exec + shlex.split( - f"-i {Path(self.project_dir)} --analysis-level={analysis_level}") + f"-i {Path(self.project_dir)} --analysis-level={analysis_level}" + ) try: logger.info(f"Running codeanalyzer: {' '.join(codeanalyzer_args)}") console_out: CompletedProcess[str] = subprocess.run( @@ -220,17 +246,22 @@ def _init_codeanalyzer(self, analysis_level=1) -> JApplication: raise CodeanalyzerExecutionException(str(e)) from e else: - analysis_json_path_file = Path(self.analysis_json_path).joinpath("analysis.json") + analysis_json_path_file = Path(self.analysis_json_path).joinpath( + "analysis.json" + ) if not analysis_json_path_file.exists() or self.eager_analysis: # If the analysis file does not exist, we'll run the analysis. Alternately, if the eager_analysis # flag is set, we'll run the analysis every time the object is created. This will happen regradless # of the existence of the analysis file. # Create the executable command for codeanalyzer. codeanalyzer_args = codeanalyzer_exec + shlex.split( - f"-i {Path(self.project_dir)} --analysis-level={analysis_level} -o {self.analysis_json_path}") + f"-i {Path(self.project_dir)} --analysis-level={analysis_level} -o {self.analysis_json_path}" + ) try: - logger.info(f"Running codeanalyzer subprocess with args {codeanalyzer_args}") + logger.info( + f"Running codeanalyzer subprocess with args {codeanalyzer_args}" + ) subprocess.run( codeanalyzer_args, capture_output=True, @@ -238,7 +269,9 @@ def _init_codeanalyzer(self, analysis_level=1) -> JApplication: check=True, ) if not analysis_json_path_file.exists(): - raise CodeanalyzerExecutionException("Codeanalyzer did not generate the analysis file.") + raise CodeanalyzerExecutionException( + "Codeanalyzer did not generate the analysis file." + ) except Exception as e: raise CodeanalyzerExecutionException(str(e)) from e @@ -263,8 +296,9 @@ def _codeanalyzer_single_file(self): try: print(f"Running {' '.join(codeanalyzer_cmd)}") logger.info(f"Running {' '.join(codeanalyzer_cmd)}") - console_out: CompletedProcess[str] = subprocess.run(codeanalyzer_cmd, capture_output=True, text=True, - check=True) + console_out: CompletedProcess[str] = subprocess.run( + codeanalyzer_cmd, capture_output=True, text=True, check=True + ) if console_out.returncode != 0: raise CodeanalyzerExecutionException(console_out.stderr) return JApplication(**json.loads(console_out.stdout)) @@ -327,7 +361,9 @@ def _generate_call_graph(self, using_symbol_table) -> DiGraph: """ cg = nx.DiGraph() if using_symbol_table: - NotImplementedError("Call graph generation using symbol table is not implemented yet.") + NotImplementedError( + "Call graph generation using symbol table is not implemented yet." + ) else: sdg = self.get_system_dependency_graph() tsu = JavaSitter() @@ -338,7 +374,9 @@ def _generate_call_graph(self, using_symbol_table) -> DiGraph: { "type": jge.type, "weight": jge.weight, - "calling_lines": tsu.get_calling_lines(jge.source.method.code, jge.target.method.signature), + "calling_lines": tsu.get_calling_lines( + jge.source.method.code, jge.target.method.signature + ), }, ) for jge in sdg @@ -390,16 +428,22 @@ def get_call_graph_json(self) -> str: for edge in edges: callgraph_dict = {} callgraph_dict["source_method_signature"] = edge[0][0] - callgraph_dict["source_method_body"] = self.call_graph.nodes[edge[0]]["method_detail"].method.code + callgraph_dict["source_method_body"] = self.call_graph.nodes[edge[0]][ + "method_detail" + ].method.code callgraph_dict["source_class"] = edge[0][1] callgraph_dict["target_method_signature"] = edge[1][0] - callgraph_dict["target_method_body"] = self.call_graph.nodes[edge[1]]["method_detail"].method.code + callgraph_dict["target_method_body"] = self.call_graph.nodes[edge[1]][ + "method_detail" + ].method.code callgraph_dict["target_class"] = edge[1][1] callgraph_dict["calling_lines"] = edge[2] callgraph_list.append(callgraph_dict) return json.dumps(callgraph_list) - def get_all_callers(self, target_class_name: str, target_method_signature: str, using_symbol_table: bool) -> Dict: + def get_all_callers( + self, target_class_name: str, target_method_signature: str + ) -> Dict: """ Get all the caller details for a given java method. @@ -410,16 +454,10 @@ def get_all_callers(self, target_class_name: str, target_method_signature: str, """ caller_detail_dict = {} - call_graph = None - if using_symbol_table: - call_graph = self.__raw_call_graph_using_symbol_table_target_method(target_class_name=target_class_name, - target_method_signature=target_method_signature) - else: - call_graph = self.call_graph - if (target_method_signature, target_class_name) not in call_graph.nodes(): + if (target_method_signature, target_class_name) not in self.call_graph.nodes(): return caller_detail_dict - in_edge_view = call_graph.in_edges( + in_edge_view = self.call_graph.in_edges( nbunch=( target_method_signature, target_class_name, @@ -427,16 +465,21 @@ def get_all_callers(self, target_class_name: str, target_method_signature: str, data=True, ) caller_detail_dict["caller_details"] = [] - caller_detail_dict["target_method"] = call_graph.nodes[(target_method_signature, target_class_name)][ - "method_detail"] + caller_detail_dict["target_method"] = self.call_graph.nodes[ + (target_method_signature, target_class_name) + ]["method_detail"] for source, target, data in in_edge_view: - cm = {"caller_method": call_graph.nodes[source]["method_detail"], - "calling_lines": data["calling_lines"]} + cm = { + "caller_method": self.call_graph.nodes[source]["method_detail"], + "calling_lines": data["calling_lines"], + } caller_detail_dict["caller_details"].append(cm) return caller_detail_dict - def get_all_callees(self, source_class_name: str, source_method_signature: str, using_symbol_table: bool) -> Dict: + def get_all_callees( + self, source_class_name: str, source_method_signature: str + ) -> Dict: """ Get all the callee details for a given java method. @@ -446,22 +489,19 @@ def get_all_callees(self, source_class_name: str, source_method_signature: str, Callee details in a dictionary. """ callee_detail_dict = {} - call_graph = None - if using_symbol_table: - call_graph = self.__call_graph_using_symbol_table(qualified_class_name=source_class_name, - method_signature=source_method_signature) - else: - call_graph = self.call_graph - if (source_method_signature, source_class_name) not in call_graph.nodes(): + if (source_method_signature, source_class_name) not in self.call_graph.nodes(): return callee_detail_dict - out_edge_view = call_graph.out_edges(nbunch=(source_method_signature, source_class_name), data=True) + out_edge_view = self.call_graph.out_edges( + nbunch=(source_method_signature, source_class_name), data=True + ) callee_detail_dict["callee_details"] = [] - callee_detail_dict["source_method"] = call_graph.nodes[(source_method_signature, source_class_name)][ - "method_detail"] + callee_detail_dict["source_method"] = self.call_graph.nodes[ + (source_method_signature, source_class_name) + ]["method_detail"] for source, target, data in out_edge_view: - cm = {"callee_method": call_graph.nodes[target]["method_detail"]} + cm = {"callee_method": self.call_graph.nodes[target]["method_detail"]} cm["calling_lines"] = data["calling_lines"] callee_detail_dict["callee_details"].append(cm) return callee_detail_dict @@ -598,7 +638,11 @@ def get_all_methods_in_class(self, qualified_class_name) -> Dict[str, JCallable] ci = self.get_class(qualified_class_name) if ci is None: return {} - methods = {k: v for (k, v) in ci.callable_declarations.items() if v.is_constructor is False} + methods = { + k: v + for (k, v) in ci.callable_declarations.items() + if v.is_constructor is False + } return methods def get_all_constructors(self, qualified_class_name) -> Dict[str, JCallable]: @@ -618,7 +662,11 @@ def get_all_constructors(self, qualified_class_name) -> Dict[str, JCallable]: ci = self.get_class(qualified_class_name) if ci is None: return {} - constructors = {k: v for (k, v) in ci.callable_declarations.items() if v.is_constructor is True} + constructors = { + k: v + for (k, v) in ci.callable_declarations.items() + if v.is_constructor is True + } return constructors def get_all_sub_classes(self, qualified_class_name) -> Dict[str, JType]: @@ -635,8 +683,10 @@ def get_all_sub_classes(self, qualified_class_name) -> Dict[str, JType]: all_classes = self.get_all_classes() sub_classes = {} for cls in all_classes: - if qualified_class_name in all_classes[cls].implements_list or qualified_class_name in all_classes[ - cls].extends_list: + if ( + qualified_class_name in all_classes[cls].implements_list + or qualified_class_name in all_classes[cls].extends_list + ): sub_classes[cls] = all_classes[cls] return sub_classes @@ -656,7 +706,9 @@ def get_all_fields(self, qualified_class_name) -> List[JField]: """ ci = self.get_class(qualified_class_name) if ci is None: - logging.warning(f"Class {qualified_class_name} not found in the application view.") + logging.warning( + f"Class {qualified_class_name} not found in the application view." + ) return list() return ci.field_declarations @@ -676,10 +728,14 @@ def get_all_nested_classes(self, qualified_class_name) -> List[JType]: """ ci = self.get_class(qualified_class_name) if ci is None: - logging.warning(f"Class {qualified_class_name} not found in the application view.") + logging.warning( + f"Class {qualified_class_name} not found in the application view." + ) return list() nested_classes = ci.nested_type_declerations - return [self.get_class(c) for c in nested_classes] # Assuming qualified nested class names + return [ + self.get_class(c) for c in nested_classes + ] # Assuming qualified nested class names def get_extended_classes(self, qualified_class_name) -> List[str]: """ @@ -697,7 +753,9 @@ def get_extended_classes(self, qualified_class_name) -> List[str]: """ ci = self.get_class(qualified_class_name) if ci is None: - logging.warning(f"Class {qualified_class_name} not found in the application view.") + logging.warning( + f"Class {qualified_class_name} not found in the application view." + ) return list() return ci.extends_list @@ -717,239 +775,15 @@ def get_implemented_interfaces(self, qualified_class_name) -> List[str]: """ ci = self.get_class(qualified_class_name) if ci is None: - logging.warning(f"Class {qualified_class_name} not found in the application view.") + logging.warning( + f"Class {qualified_class_name} not found in the application view." + ) return list() return ci.implements_list - def get_class_call_graph_using_symbol_table(self, qualified_class_name: str, - method_signature: str | None = None) -> ( - List)[Tuple[JMethodDetail, JMethodDetail]]: - """ - Returns call graph using symbol table. The analysis will not be - complete as symbol table has known limitation of resolving types - Args: - qualified_class_name: qualified name of the class - method_signature: method signature of the starting point of the call graph - - Returns: List[Tuple[JMethodDetail, JMethodDetail]] - List of edges - """ - call_graph = self.__call_graph_using_symbol_table(qualified_class_name, method_signature) - if method_signature is None: - filter_criteria = {node for node in call_graph.nodes if node[1] == qualified_class_name} - else: - filter_criteria = {node for node in call_graph.nodes if - tuple(node) == (method_signature, qualified_class_name)} - - graph_edges: List[Tuple[JMethodDetail, JMethodDetail]] = list() - for edge in call_graph.edges(nbunch=filter_criteria): - source: JMethodDetail = call_graph.nodes[edge[0]]["method_detail"] - target: JMethodDetail = call_graph.nodes[edge[1]]["method_detail"] - graph_edges.append((source, target)) - return graph_edges - - def __call_graph_using_symbol_table(self, - qualified_class_name: str, - method_signature: str, is_target_method: bool = False)-> DiGraph: - """ - Generate call graph using symbol table - Args: - qualified_class_name: qualified class name - method_signature: method signature - is_target_method: is the input method is a target method. By default, it is the source method - - Returns: - DiGraph: call graph - """ - cg = nx.DiGraph() - sdg = None - if is_target_method: - sdg = None - else: - sdg = self.__raw_call_graph_using_symbol_table(qualified_class_name=qualified_class_name, - method_signature=method_signature) - tsu = JavaSitter() - edge_list = [ - ( - (jge.source.method.signature, jge.source.klass), - (jge.target.method.signature, jge.target.klass), - { - "type": jge.type, - "weight": jge.weight, - "calling_lines": tsu.get_calling_lines(jge.source.method.code, jge.target.method.signature), - }, - ) - for jge in sdg - ] - for jge in sdg: - cg.add_node( - (jge.source.method.signature, jge.source.klass), - method_detail=jge.source, - ) - cg.add_node( - (jge.target.method.signature, jge.target.klass), - method_detail=jge.target, - ) - cg.add_edges_from(edge_list) - return cg - - def __raw_call_graph_using_symbol_table_target_method(self, - target_class_name: str, - target_method_signature: str, - cg=None) -> list[JGraphEdgesST]: - """ - Generates call graph using symbol table information given the target method and target class - Args: - qualified_class_name: qualified class name - method_signature: source method signature - cg: call graph - - Returns: - list[JGraphEdgesST]: list of call edges - """ - if cg is None: - cg = [] - target_method_details = self.get_method(qualified_class_name=target_class_name, - method_signature=target_method_signature) - for class_name in self.get_all_classes(): - for method in self.get_all_methods_in_class(qualified_class_name=class_name): - method_details = self.get_method(qualified_class_name=class_name, - method_signature=method) - for call_site in method_details.call_sites: - source_method_details = None - source_class = '' - callee_signature = '' - if call_site.callee_signature != '': - pattern = r'\b(?:[a-zA-Z_][\w\.]*\.)+([a-zA-Z_][\w]*)\b|<[^>]*>' - - # Find the part within the parentheses - start = call_site.callee_signature.find('(') + 1 - end = call_site.callee_signature.rfind(')') - - # Extract the elements inside the parentheses - elements = call_site.callee_signature[start:end].split(',') - - # Apply the regex to each element - simplified_elements = [re.sub(pattern, r'\1', element.strip()) for element in elements] - - # Reconstruct the string with simplified elements - callee_signature = f"{call_site.callee_signature[:start]}{', '.join(simplified_elements)}{call_site.callee_signature[end:]}" - - if call_site.receiver_type != "": - # call to any class - if self.get_class(qualified_class_name=call_site.receiver_type): - if callee_signature==target_method_signature and call_site.receiver_type == target_class_name: - source_method_details = self.get_method(method_signature=method, - qualified_class_name=class_name) - source_class = class_name - else: - # check if any method exists with the signature in the class even if the receiver type is blank - if callee_signature == target_method_signature and class_name == target_class_name: - source_method_details = self.get_method(method_signature=method, - qualified_class_name=class_name) - source_class = class_name - - if source_class != '' and source_method_details is not None: - source: JMethodDetail - target: JMethodDetail - type: str - weight: str - call_edge = JGraphEdgesST( - source=JMethodDetail(method_declaration=source_method_details.declaration, - klass=source_class, - method=source_method_details), - target=JMethodDetail(method_declaration=target_method_details.declaration, - klass=target_class_name, - method=target_method_details), - type='CALL_DEP', - weight='1') - if call_edge not in cg: - cg.append(call_edge) - return cg - - def __raw_call_graph_using_symbol_table(self, - qualified_class_name: str, - method_signature: str, - cg=None) -> list[JGraphEdgesST]: - """ - Generates call graph using symbol table information - Args: - qualified_class_name: qualified class name - method_signature: source method signature - cg: call graph - - Returns: - list[JGraphEdgesST]: list of call edges - """ - if cg is None: - cg = [] - source_method_details = self.get_method(qualified_class_name=qualified_class_name, - method_signature=method_signature) - # If the provided classname and method signature combination do not exist - if source_method_details is None: - return cg - for call_site in source_method_details.call_sites: - target_method_details = None - target_class = '' - callee_signature = '' - if call_site.callee_signature != '': - # Currently the callee signature returns the fully qualified type, whereas - # the key for JCallable does not. The below logic converts the fully qualified signature - # to the desider format. Only limitation is the nested generic type. - pattern = r'\b(?:[a-zA-Z_][\w\.]*\.)+([a-zA-Z_][\w]*)\b|<[^>]*>' - - # Find the part within the parentheses - start = call_site.callee_signature.find('(') + 1 - end = call_site.callee_signature.rfind(')') - - # Extract the elements inside the parentheses - elements = call_site.callee_signature[start:end].split(',') - - # Apply the regex to each element - simplified_elements = [re.sub(pattern, r'\1', element.strip()) for element in elements] - - # Reconstruct the string with simplified elements - callee_signature = f"{call_site.callee_signature[:start]}{', '.join(simplified_elements)}{call_site.callee_signature[end:]}" - - if call_site.receiver_type != "": - # call to any class - if self.get_class(qualified_class_name=call_site.receiver_type): - tmd = self.get_method(method_signature=callee_signature, - qualified_class_name=call_site.receiver_type) - if tmd is not None: - target_method_details = tmd - target_class = call_site.receiver_type - else: - # check if any method exists with the signature in the class even if the receiver type is blank - tmd = self.get_method(method_signature=callee_signature, - qualified_class_name=qualified_class_name) - if tmd is not None: - target_method_details = tmd - target_class = qualified_class_name - - if target_class != '' and target_method_details is not None: - source: JMethodDetail - target: JMethodDetail - type: str - weight: str - call_edge = JGraphEdgesST( - source=JMethodDetail(method_declaration=source_method_details.declaration, - klass=qualified_class_name, - method=source_method_details), - target=JMethodDetail(method_declaration=target_method_details.declaration, - klass=target_class, - method=target_method_details), - type='CALL_DEP', - weight='1') - if call_edge not in cg: - cg.append(call_edge) - cg = self.__raw_call_graph_using_symbol_table(qualified_class_name=target_class, - method_signature=target_method_details.signature, - cg=cg) - return cg - - def get_class_call_graph(self, qualified_class_name: str, method_name: str | None = None) -> List[ - Tuple[JMethodDetail, JMethodDetail]]: + def get_class_call_graph( + self, qualified_class_name: str, method_name: str | None = None + ) -> List[Tuple[JMethodDetail, JMethodDetail]]: """ A call graph for a given class and (optionally) filtered by a given method. @@ -976,10 +810,17 @@ def get_class_call_graph(self, qualified_class_name: str, method_name: str | Non # If the method name is not provided, we'll get the call graph for the entire class. if method_name is None: - filter_criteria = {node for node in self.call_graph.nodes if node[1] == qualified_class_name} + filter_criteria = { + node + for node in self.call_graph.nodes + if node[1] == qualified_class_name + } else: - filter_criteria = {node for node in self.call_graph.nodes if - tuple(node) == (method_name, qualified_class_name)} + filter_criteria = { + node + for node in self.call_graph.nodes + if tuple(node) == (method_name, qualified_class_name) + } graph_edges: List[Tuple[JMethodDetail, JMethodDetail]] = list() for edge in self.call_graph.edges(nbunch=filter_criteria): @@ -1004,8 +845,11 @@ def get_all_entry_point_methods(self) -> Dict[str, Dict[str, JCallable]]: class_method_dict = {} class_dict = self.get_all_classes() for k, v in class_dict.items(): - entry_point_methods = {method_name: callable_decl for (method_name, callable_decl) in - v.callable_declarations.items() if callable_decl.is_entry_point is True} + entry_point_methods = { + method_name: callable_decl + for (method_name, callable_decl) in v.callable_declarations.items() + if callable_decl.is_entry_point is True + } class_method_dict[k] = entry_point_methods return class_method_dict @@ -1022,5 +866,9 @@ def get_all_entry_point_classes(self) -> Dict[str, JType]: class_dict = {} symtab = self.get_symbol_table() for val in symtab.values(): - class_dict.update((k, v) for k, v in val.type_declarations.items() if v.is_entry_point is True) + class_dict.update( + (k, v) + for k, v in val.type_declarations.items() + if v.is_entry_point is True + ) return class_dict From 75d38165ba6452cd0986a9cbc22da13cdf1001de Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 4 Sep 2024 14:15:21 -0400 Subject: [PATCH 2/2] Update core.py --- cldk/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cldk/core.py b/cldk/core.py index b8df8f2..ec6f07b 100644 --- a/cldk/core.py +++ b/cldk/core.py @@ -37,7 +37,7 @@ def analysis( analysis_backend: str | None = "codeanalyzer", analysis_level: str = "symbol_table", analysis_backend_path: str | None = None, - analysis_json_path: str | Path = '.', + analysis_json_path: str | Path = None, use_graalvm_binary: bool = False, ) -> JavaAnalysis: """