Skip to content

Commit 2dc6bdc

Browse files
committed
Issue 108: Refactor C Analysis (and add test cases).
Signed-off-by: Rahul Krishna <i.m.ralk@gmail.com>
1 parent faf8911 commit 2dc6bdc

2,274 files changed

Lines changed: 680730 additions & 141 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cldk/analysis/c/c_analysis.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def _init_application(self, project_dir: Path) -> CApplication:
5656
return CApplication(translation_units=translation_units)
5757

5858
def get_c_application(self) -> CApplication:
59-
"""returns the C application object.
59+
"""Obtain the C application object.
6060
6161
Returns:
6262
CApplication: C application object.
@@ -90,15 +90,15 @@ def is_parsable(self, source_code: str) -> bool:
9090
raise NotImplementedError("Support for this functionality has not been implemented yet.")
9191

9292
def get_call_graph(self) -> nx.DiGraph:
93-
"""returns the call graph of the C code.
93+
"""Should return the call graph of the C code.
9494
9595
Returns:
9696
nx.DiGraph: The call graph of the C code.
9797
"""
9898
raise NotImplementedError("Support for this functionality has not been implemented yet.")
9999

100100
def get_call_graph_json(self) -> str:
101-
"""returns a serialized call graph in json.
101+
"""Should return a serialized call graph in json.
102102
103103
Raises:
104104
NotImplementedError: Raised when this functionality is not suported.
@@ -110,7 +110,7 @@ def get_call_graph_json(self) -> str:
110110
raise NotImplementedError("Producing a call graph over a single file is not implemented yet.")
111111

112112
def get_callers(self, function: CFunction) -> Dict:
113-
"""returns a dictionary of callers of the target method.
113+
"""Should return a dictionary of callers of the target method.
114114
115115
Args:
116116
function (CFunction): A CFunction object.
@@ -125,7 +125,7 @@ def get_callers(self, function: CFunction) -> Dict:
125125
raise NotImplementedError("Generating all callers over a single file is not implemented yet.")
126126

127127
def get_callees(self, function: CFunction) -> Dict:
128-
"""returns a dictionary of callees in a fuction.
128+
"""Should return a dictionary of callees in a fuction.
129129
130130
Args:
131131
function (CFunction): A CFunction object.
@@ -139,7 +139,7 @@ def get_callees(self, function: CFunction) -> Dict:
139139
raise NotImplementedError("Generating all callees over a single file is not implemented yet.")
140140

141141
def get_functions(self) -> Dict[str, CFunction]:
142-
"""returns all functions in the project.
142+
"""Should return all functions in the project.
143143
144144
Raises:
145145
NotImplementedError: Raised when current AnalysisEngine does not support this function.
@@ -151,7 +151,7 @@ def get_functions(self) -> Dict[str, CFunction]:
151151
return translation_unit.functions
152152

153153
def get_function(self, function_name: str, file_name: Optional[str]) -> CFunction | List[CFunction]:
154-
"""returns a function object given the function name.
154+
"""Should return a function object given the function name.
155155
156156
Args:
157157
function_name (str): The name of the function.
@@ -163,7 +163,7 @@ def get_function(self, function_name: str, file_name: Optional[str]) -> CFunctio
163163
raise NotImplementedError("Support for this functionality has not been implemented yet.")
164164

165165
def get_C_file(self, file_name: str) -> str:
166-
"""returns a class given qualified class name.
166+
"""Should return a class given qualified class name.
167167
168168
Args:
169169
file_name (str): The name of the file.
@@ -191,7 +191,7 @@ def get_C_compilation_unit(self, file_path: str) -> CTranslationUnit:
191191
return self.c_application.translation_units.get(file_path)
192192

193193
def get_functions_in_file(self, file_name: str) -> List[CFunction]:
194-
"""returns a dictionary of all methods of the given class.
194+
"""Should return a dictionary of all methods of the given class.
195195
196196
Args:
197197
file_name (str): The name of the file.
@@ -205,7 +205,7 @@ def get_functions_in_file(self, file_name: str) -> List[CFunction]:
205205
raise NotImplementedError("Support for this functionality has not been implemented yet.")
206206

207207
def get_macros(self) -> List[CMacro]:
208-
"""returns a list of all macros in the C code.
208+
"""Should return a list of all macros in the C code.
209209
210210
Raises:
211211
NotImplementedError: Raised when current AnalysisEngine does not support this function.
@@ -216,7 +216,7 @@ def get_macros(self) -> List[CMacro]:
216216
raise NotImplementedError("Support for this functionality has not been implemented yet.")
217217

218218
def get_macros_in_file(self, file_name: str) -> List[CMacro] | None:
219-
"""returns a list of all macros in the given file.
219+
"""Should return a list of all macros in the given file.
220220
221221
Args:
222222
file_name (str): The name of the file.
@@ -231,7 +231,7 @@ def get_macros_in_file(self, file_name: str) -> List[CMacro] | None:
231231

232232

233233
def get_includes(self) -> List[str]:
234-
"""returns a list of all include statements across all files in the C code.
234+
"""Should return a list of all include statements across all files in the C code.
235235
236236
Returns:
237237
List[str]: A list of all include statements. Returns empty list if none found.
@@ -243,7 +243,7 @@ def get_includes(self) -> List[str]:
243243

244244

245245
def get_includes_in_file(self, file_name: str) -> List[str] | None:
246-
"""returns a list of all include statements in the given file.
246+
"""Should return a list of all include statements in the given file.
247247
248248
Args:
249249
file_name (str): The name of the file to search in.
@@ -257,7 +257,7 @@ def get_includes_in_file(self, file_name: str) -> List[str] | None:
257257

258258

259259
def get_macros(self) -> List[CMacro]:
260-
"""returns a list of all macro definitions across all files in the C code.
260+
"""Should return a list of all macro definitions across all files in the C code.
261261
262262
Returns:
263263
List[CMacro]: A list of all macro definitions. Returns empty list if none found.
@@ -269,7 +269,7 @@ def get_macros(self) -> List[CMacro]:
269269

270270

271271
def get_macros_in_file(self, file_name: str) -> List[CMacro] | None:
272-
"""returns a list of all macro definitions in the given file.
272+
"""Should return a list of all macro definitions in the given file.
273273
274274
Args:
275275
file_name (str): The name of the file to search in.
@@ -283,7 +283,7 @@ def get_macros_in_file(self, file_name: str) -> List[CMacro] | None:
283283

284284

285285
def get_typedefs(self) -> List[CTypedef]:
286-
"""returns a list of all typedef declarations across all files in the C code.
286+
"""Should return a list of all typedef declarations across all files in the C code.
287287
288288
Returns:
289289
List[CTypedef]: A list of all typedef declarations. Returns empty list if none found.
@@ -295,7 +295,7 @@ def get_typedefs(self) -> List[CTypedef]:
295295

296296

297297
def get_typedefs_in_file(self, file_name: str) -> List[CTypedef] | None:
298-
"""returns a list of all typedef declarations in the given file.
298+
"""Should return a list of all typedef declarations in the given file.
299299
300300
Args:
301301
file_name (str): The name of the file to search in.
@@ -309,7 +309,7 @@ def get_typedefs_in_file(self, file_name: str) -> List[CTypedef] | None:
309309

310310

311311
def get_structs(self) -> List[CStruct]:
312-
"""returns a list of all struct/union declarations across all files in the C code.
312+
"""Should return a list of all struct/union declarations across all files in the C code.
313313
314314
Returns:
315315
List[CStruct]: A list of all struct/union declarations. Returns empty list if none found.
@@ -321,7 +321,7 @@ def get_structs(self) -> List[CStruct]:
321321

322322

323323
def get_structs_in_file(self, file_name: str) -> List[CStruct] | None:
324-
"""returns a list of all struct/union declarations in the given file.
324+
"""Should return a list of all struct/union declarations in the given file.
325325
326326
Args:
327327
file_name (str): The name of the file to search in.
@@ -335,7 +335,7 @@ def get_structs_in_file(self, file_name: str) -> List[CStruct] | None:
335335

336336

337337
def get_enums(self) -> List[CEnum]:
338-
"""returns a list of all enum declarations across all files in the C code.
338+
"""Should return a list of all enum declarations across all files in the C code.
339339
340340
Returns:
341341
List[CEnum]: A list of all enum declarations. Returns empty list if none found.
@@ -347,7 +347,7 @@ def get_enums(self) -> List[CEnum]:
347347

348348

349349
def get_enums_in_file(self, file_name: str) -> List[CEnum] | None:
350-
"""returns a list of all enum declarations in the given file.
350+
"""Should return a list of all enum declarations in the given file.
351351
352352
Args:
353353
file_name (str): The name of the file to search in.
@@ -361,7 +361,7 @@ def get_enums_in_file(self, file_name: str) -> List[CEnum] | None:
361361

362362

363363
def get_globals(self, file_name: str) -> List[CVariable] | None:
364-
"""returns a list of all global variable declarations in the given file.
364+
"""Should return a list of all global variable declarations in the given file.
365365
366366
Args:
367367
file_name (str): The name of the file to search in.

cldk/analysis/c/clang/clang_analyzer.py

Lines changed: 48 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,67 +12,22 @@
1212

1313
# First, we only import Config from clang.cindex
1414
from clang.cindex import Config
15-
16-
17-
def find_libclang() -> str:
18-
"""
19-
Locates the libclang library on the system based on the operating system.
20-
This function runs before any other Clang functionality is used, ensuring
21-
proper initialization of the Clang environment.
22-
"""
23-
system = platform.system()
24-
25-
# On macOS, we check both Apple Silicon and Intel paths
26-
if system == "Darwin":
27-
possible_paths = [
28-
"/opt/homebrew/opt/llvm/lib/libclang.dylib", # Apple Silicon
29-
"/usr/local/opt/llvm/lib/libclang.dylib", # Intel Mac
30-
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib",
31-
]
32-
install_instructions = "Install LLVM using: brew install llvm"
33-
34-
# On Linux, we check various common installation paths
35-
elif system == "Linux":
36-
from pathlib import Path
37-
38-
lib_paths = [Path("/usr/lib"), Path("/usr/lib64")]
39-
possible_paths = [
40-
str(p) for base in lib_paths if base.exists()
41-
for p in base.rglob("libclang*.so*")
42-
]
43-
44-
install_instructions = "Install libclang development package using your system's package manager"
45-
else:
46-
raise RuntimeError(f"Unsupported operating system: {system}")
47-
48-
# Check each possible path and return the first one that exists
49-
for path in possible_paths:
50-
if os.path.exists(path):
51-
logger.info(f"Found libclang at: {path}")
52-
return path
53-
54-
# If no library is found, provide clear installation instructions
55-
raise RuntimeError(f"Could not find libclang library. \n" f"Please ensure LLVM is installed:\n{install_instructions}")
56-
57-
58-
# Initialize libclang at module level
59-
try:
60-
libclang_path = find_libclang()
61-
Config.set_library_file(libclang_path)
62-
logger.info("Successfully initialized libclang")
63-
64-
# Now that libclang is initialized, we can safely import other Clang components
65-
from clang.cindex import Index, TranslationUnit, CursorKind, TypeKind, CompilationDatabase
66-
67-
except Exception as e:
68-
logger.error(f"Failed to initialize libclang: {e}")
69-
raise
15+
from clang.cindex import Index, TranslationUnit, CursorKind, TypeKind, CompilationDatabase
7016

7117

7218
class ClangAnalyzer:
7319
"""Analyzes C code using Clang's Python bindings."""
7420

7521
def __init__(self, compilation_database_path: Optional[Path] = None):
22+
23+
# Initialize libclang at module level
24+
try:
25+
Config.set_library_file(self.__find_libclang())
26+
except Exception as e:
27+
logger.error(f"Failed to initialize libclang: {e}")
28+
raise Exception("Failed to initialize libclang")
29+
30+
logger.info("Successfully initialized libclang")
7631
# Configure Clang before creating the Index
7732
self.index = Index.create()
7833
self.compilation_database = None
@@ -81,6 +36,44 @@ def __init__(self, compilation_database_path: Optional[Path] = None):
8136
if compilation_database_path:
8237
self.compilation_database = CompilationDatabase.fromDirectory(str(compilation_database_path))
8338

39+
def __find_libclang(self) -> str:
40+
"""
41+
Locates the libclang library on the system based on the operating system.
42+
This function runs before any other Clang functionality is used, ensuring
43+
proper initialization of the Clang environment.
44+
"""
45+
46+
system = platform.system()
47+
48+
# On macOS, we check both Apple Silicon and Intel paths
49+
if system == "Darwin":
50+
possible_paths = [
51+
"/opt/homebrew/opt/llvm/lib/libclang.dylib", # Apple Silicon
52+
"/usr/local/opt/llvm/lib/libclang.dylib", # Intel Mac
53+
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib",
54+
]
55+
install_instructions = "Install LLVM using: brew install llvm"
56+
57+
# On Linux, we check various common installation paths
58+
elif system == "Linux":
59+
from pathlib import Path
60+
61+
lib_paths = [Path("/usr/lib"), Path("/usr/lib64")]
62+
possible_paths = [str(p) for base in lib_paths if base.exists() for p in base.rglob("libclang*.so*")]
63+
64+
install_instructions = "Install libclang development package using your system's package manager"
65+
else:
66+
raise RuntimeError(f"Unsupported operating system: {system}")
67+
68+
# Check each possible path and return the first one that exists
69+
for path in possible_paths:
70+
if os.path.exists(path):
71+
logger.info(f"Found libclang at: {path}")
72+
return path
73+
74+
# If no library is found, provide clear installation instructions
75+
raise RuntimeError(f"Could not find libclang library. \n" f"Please ensure LLVM is installed:\n{install_instructions}")
76+
8477
def analyze_file(self, file_path: Path) -> CTranslationUnit:
8578
"""Analyzes a single C source file using Clang."""
8679

0 commit comments

Comments
 (0)