clang-tool-chain 1.0.44__py3-none-any.whl → 1.0.47__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clang_tool_chain/__init__.py +31 -0
- clang_tool_chain/__version__.py +1 -1
- clang_tool_chain/deployment/dll_deployer.py +16 -23
- clang_tool_chain/deployment/dylib_deployer.py +8 -8
- clang_tool_chain/deployment/libdeploy.py +287 -0
- clang_tool_chain/deployment/so_deployer.py +13 -5
- clang_tool_chain/env_utils.py +137 -0
- clang_tool_chain/execution/__init__.py +6 -1
- clang_tool_chain/execution/arg_transformers.py +128 -36
- clang_tool_chain/execution/build.py +10 -4
- clang_tool_chain/execution/build_pipeline.py +7 -2
- clang_tool_chain/execution/sanitizer_env.py +77 -12
- clang_tool_chain/linker/lld.py +42 -12
- clang_tool_chain/wrapper.py +13 -0
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/METADATA +222 -25
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/RECORD +19 -17
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/entry_points.txt +1 -0
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/WHEEL +0 -0
- {clang_tool_chain-1.0.44.dist-info → clang_tool_chain-1.0.47.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,9 +14,12 @@ The Chain of Responsibility pattern allows:
|
|
|
14
14
|
|
|
15
15
|
Architecture:
|
|
16
16
|
ArgumentTransformer (ABC)
|
|
17
|
-
├── MacOSSDKTransformer (priority=100)
|
|
18
17
|
├── DirectivesTransformer (priority=50)
|
|
18
|
+
├── MacOSSDKTransformer (priority=100)
|
|
19
|
+
├── LinuxUnwindTransformer (priority=150)
|
|
19
20
|
├── LLDLinkerTransformer (priority=200)
|
|
21
|
+
├── ASANRuntimeTransformer (priority=250)
|
|
22
|
+
├── RPathTransformer (priority=275)
|
|
20
23
|
├── GNUABITransformer (priority=300)
|
|
21
24
|
└── MSVCABITransformer (priority=300)
|
|
22
25
|
|
|
@@ -35,6 +38,8 @@ from dataclasses import dataclass
|
|
|
35
38
|
from pathlib import Path
|
|
36
39
|
from typing import TYPE_CHECKING
|
|
37
40
|
|
|
41
|
+
from clang_tool_chain.env_utils import is_feature_disabled
|
|
42
|
+
|
|
38
43
|
if TYPE_CHECKING:
|
|
39
44
|
from clang_tool_chain.directives.parser import ParsedDirectives
|
|
40
45
|
|
|
@@ -111,6 +116,7 @@ class DirectivesTransformer(ArgumentTransformer):
|
|
|
111
116
|
|
|
112
117
|
Environment Variables:
|
|
113
118
|
CLANG_TOOL_CHAIN_NO_DIRECTIVES: Set to '1' to disable directive parsing
|
|
119
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to '1' to disable all automatic features
|
|
114
120
|
CLANG_TOOL_CHAIN_DIRECTIVE_VERBOSE: Set to '1' to enable debug logging
|
|
115
121
|
"""
|
|
116
122
|
|
|
@@ -119,9 +125,8 @@ class DirectivesTransformer(ArgumentTransformer):
|
|
|
119
125
|
|
|
120
126
|
def transform(self, args: list[str], context: ToolContext) -> list[str]:
|
|
121
127
|
"""Add arguments from inlined build directives in source files."""
|
|
122
|
-
# Check if directives are disabled
|
|
123
|
-
if
|
|
124
|
-
logger.debug("Directives disabled via CLANG_TOOL_CHAIN_NO_DIRECTIVES=1")
|
|
128
|
+
# Check if directives are disabled (via NO_DIRECTIVES or NO_AUTO)
|
|
129
|
+
if is_feature_disabled("DIRECTIVES"):
|
|
125
130
|
return args
|
|
126
131
|
|
|
127
132
|
# Only apply to clang/clang++ compilation commands
|
|
@@ -203,6 +208,86 @@ class MacOSSDKTransformer(ArgumentTransformer):
|
|
|
203
208
|
return _add_macos_sysroot_if_needed(args)
|
|
204
209
|
|
|
205
210
|
|
|
211
|
+
class LinuxUnwindTransformer(ArgumentTransformer):
|
|
212
|
+
"""
|
|
213
|
+
Transformer for adding bundled libunwind include/library paths on Linux.
|
|
214
|
+
|
|
215
|
+
Priority: 150 (runs after SDK but before linker)
|
|
216
|
+
|
|
217
|
+
This transformer adds include and library paths for the bundled libunwind
|
|
218
|
+
headers and libraries on Linux. This allows compilation of code that uses
|
|
219
|
+
libunwind without requiring system libunwind-dev to be installed.
|
|
220
|
+
|
|
221
|
+
When libunwind.h exists in the clang toolchain's include directory:
|
|
222
|
+
- Adds -I<clang_root>/include for header discovery
|
|
223
|
+
- Adds -L<clang_root>/lib for library discovery
|
|
224
|
+
- Adds -Wl,-rpath,<clang_root>/lib for runtime library discovery
|
|
225
|
+
|
|
226
|
+
Environment Variables:
|
|
227
|
+
CLANG_TOOL_CHAIN_NO_BUNDLED_UNWIND: Set to '1' to disable bundled libunwind
|
|
228
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to '1' to disable all automatic features
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
def priority(self) -> int:
|
|
232
|
+
return 150
|
|
233
|
+
|
|
234
|
+
def transform(self, args: list[str], context: ToolContext) -> list[str]:
|
|
235
|
+
"""Add bundled libunwind paths if available on Linux."""
|
|
236
|
+
# Only applies to Linux clang/clang++
|
|
237
|
+
if context.platform_name != "linux" or context.tool_name not in ("clang", "clang++"):
|
|
238
|
+
return args
|
|
239
|
+
|
|
240
|
+
# Check if disabled (via NO_BUNDLED_UNWIND or NO_AUTO)
|
|
241
|
+
if is_feature_disabled("BUNDLED_UNWIND"):
|
|
242
|
+
return args
|
|
243
|
+
|
|
244
|
+
# Check if compile-only (no linking)
|
|
245
|
+
is_compile_only = "-c" in args
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
from clang_tool_chain.platform.detection import get_platform_binary_dir
|
|
249
|
+
|
|
250
|
+
clang_bin = get_platform_binary_dir()
|
|
251
|
+
clang_root = clang_bin.parent
|
|
252
|
+
|
|
253
|
+
# Check if bundled libunwind.h exists
|
|
254
|
+
libunwind_header = clang_root / "include" / "libunwind.h"
|
|
255
|
+
if not libunwind_header.exists():
|
|
256
|
+
logger.debug("Bundled libunwind.h not found, skipping LinuxUnwindTransformer")
|
|
257
|
+
return args
|
|
258
|
+
|
|
259
|
+
result = list(args)
|
|
260
|
+
include_dir = clang_root / "include"
|
|
261
|
+
lib_dir = clang_root / "lib"
|
|
262
|
+
|
|
263
|
+
# Add include path (always needed for compilation)
|
|
264
|
+
include_flag = f"-I{include_dir}"
|
|
265
|
+
if include_flag not in args:
|
|
266
|
+
result = [include_flag] + result
|
|
267
|
+
logger.debug(f"Adding bundled libunwind include path: {include_flag}")
|
|
268
|
+
|
|
269
|
+
# Add library path and rpath (only for linking)
|
|
270
|
+
if not is_compile_only:
|
|
271
|
+
lib_flag = f"-L{lib_dir}"
|
|
272
|
+
if lib_flag not in args:
|
|
273
|
+
result = [lib_flag] + result
|
|
274
|
+
logger.debug(f"Adding bundled libunwind library path: {lib_flag}")
|
|
275
|
+
|
|
276
|
+
# Add rpath so runtime can find libunwind.so
|
|
277
|
+
rpath_flag = f"-Wl,-rpath,{lib_dir}"
|
|
278
|
+
# Check if any rpath to our lib dir already exists
|
|
279
|
+
has_our_rpath = any(str(lib_dir) in arg and "-rpath" in arg for arg in args)
|
|
280
|
+
if not has_our_rpath:
|
|
281
|
+
result = [rpath_flag] + result
|
|
282
|
+
logger.debug(f"Adding bundled libunwind rpath: {rpath_flag}")
|
|
283
|
+
|
|
284
|
+
return result
|
|
285
|
+
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.debug(f"LinuxUnwindTransformer error: {e}")
|
|
288
|
+
return args
|
|
289
|
+
|
|
290
|
+
|
|
206
291
|
class LLDLinkerTransformer(ArgumentTransformer):
|
|
207
292
|
"""
|
|
208
293
|
Transformer for forcing LLVM's lld linker on macOS and Linux.
|
|
@@ -291,32 +376,38 @@ class ASANRuntimeTransformer(ArgumentTransformer):
|
|
|
291
376
|
|
|
292
377
|
Priority: 250 (runs after linker but before ABI)
|
|
293
378
|
|
|
294
|
-
This transformer ensures proper ASAN runtime linking on Linux:
|
|
379
|
+
This transformer ensures proper ASAN runtime linking on Linux and Windows:
|
|
295
380
|
- Detects -fsanitize=address flag
|
|
296
381
|
- Adds -shared-libasan to use shared runtime library
|
|
297
|
-
- Adds -Wl,--allow-shlib-undefined when building shared libraries with ASAN
|
|
382
|
+
- Adds -Wl,--allow-shlib-undefined when building shared libraries with ASAN (Linux only)
|
|
298
383
|
- Prevents undefined symbol errors during linking
|
|
299
384
|
|
|
300
|
-
The shared runtime library (libclang_rt.asan.so
|
|
301
|
-
ASAN implementation, while the static wrapper library
|
|
385
|
+
The shared runtime library (libclang_rt.asan.so on Linux, libclang_rt.asan_dynamic.dll
|
|
386
|
+
on Windows) contains the full ASAN implementation, while the static wrapper library
|
|
387
|
+
only contains stubs.
|
|
302
388
|
|
|
303
389
|
When building shared libraries with sanitizers, the library may have undefined
|
|
304
390
|
symbols that will be provided by the sanitizer runtime when loaded. LLD by
|
|
305
391
|
default enforces no undefined symbols, so we need to allow them explicitly.
|
|
306
392
|
|
|
393
|
+
Note: macOS uses a different ASAN runtime mechanism and is not affected.
|
|
394
|
+
|
|
307
395
|
Environment Variables:
|
|
308
396
|
CLANG_TOOL_CHAIN_NO_SHARED_ASAN: Set to '1' to disable shared ASAN
|
|
397
|
+
CLANG_TOOL_CHAIN_NO_SANITIZER_NOTE: Set to '1' to suppress the injection note
|
|
398
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to '1' to disable all automatic features
|
|
309
399
|
"""
|
|
310
400
|
|
|
311
401
|
def priority(self) -> int:
|
|
312
402
|
return 250
|
|
313
403
|
|
|
314
404
|
def transform(self, args: list[str], context: ToolContext) -> list[str]:
|
|
315
|
-
"""Add -shared-libasan and --allow-shlib-undefined when using ASAN on Linux."""
|
|
405
|
+
"""Add -shared-libasan and --allow-shlib-undefined when using ASAN on Linux/Windows."""
|
|
316
406
|
import sys
|
|
317
407
|
|
|
318
|
-
# Only applies to Linux clang/clang++
|
|
319
|
-
|
|
408
|
+
# Only applies to Linux and Windows (GNU ABI) clang/clang++
|
|
409
|
+
# macOS uses a different ASAN runtime mechanism
|
|
410
|
+
if context.platform_name not in ("linux", "win") or context.tool_name not in ("clang", "clang++"):
|
|
320
411
|
return args
|
|
321
412
|
|
|
322
413
|
# Check if ASAN is enabled
|
|
@@ -327,23 +418,21 @@ class ASANRuntimeTransformer(ArgumentTransformer):
|
|
|
327
418
|
result = list(args)
|
|
328
419
|
injected_flags = []
|
|
329
420
|
|
|
330
|
-
# Check if user disabled shared ASAN
|
|
331
|
-
if
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
logger.debug("Shared ASAN disabled via CLANG_TOOL_CHAIN_NO_SHARED_ASAN=1")
|
|
341
|
-
|
|
342
|
-
# Check if building a shared library with ASAN
|
|
421
|
+
# Check if user disabled shared ASAN (via NO_SHARED_ASAN or NO_AUTO)
|
|
422
|
+
# Also check if -shared-libasan already present
|
|
423
|
+
if not is_feature_disabled("SHARED_ASAN") and "-shared-libasan" not in args:
|
|
424
|
+
# Add -shared-libasan to use shared runtime library
|
|
425
|
+
# This prevents undefined symbol errors during linking
|
|
426
|
+
logger.info("Adding -shared-libasan for ASAN runtime linking on Linux")
|
|
427
|
+
result = ["-shared-libasan"] + result
|
|
428
|
+
injected_flags.append("-shared-libasan")
|
|
429
|
+
|
|
430
|
+
# Check if building a shared library with ASAN on Linux
|
|
343
431
|
# Shared libraries need to allow undefined symbols that will be provided
|
|
344
432
|
# by the sanitizer runtime when the runner loads them
|
|
433
|
+
# Note: --allow-shlib-undefined is a Linux ELF linker flag, not supported on Windows
|
|
345
434
|
is_shared_lib = "-shared" in args
|
|
346
|
-
if is_shared_lib:
|
|
435
|
+
if is_shared_lib and context.platform_name == "linux":
|
|
347
436
|
# Check if --allow-shlib-undefined already present
|
|
348
437
|
has_allow_shlib_undefined = any("--allow-shlib-undefined" in arg for arg in args)
|
|
349
438
|
if not has_allow_shlib_undefined:
|
|
@@ -351,10 +440,11 @@ class ASANRuntimeTransformer(ArgumentTransformer):
|
|
|
351
440
|
result = ["-Wl,--allow-shlib-undefined"] + result
|
|
352
441
|
injected_flags.append("-Wl,--allow-shlib-undefined")
|
|
353
442
|
|
|
354
|
-
# Warn on stderr if we injected flags
|
|
355
|
-
if injected_flags:
|
|
443
|
+
# Warn on stderr if we injected flags (unless disabled)
|
|
444
|
+
if injected_flags and not is_feature_disabled("SANITIZER_NOTE"):
|
|
356
445
|
print(
|
|
357
|
-
f"clang-tool-chain: note: automatically injected sanitizer flags: {' '.join(injected_flags)}"
|
|
446
|
+
f"clang-tool-chain: note: automatically injected sanitizer flags: {' '.join(injected_flags)} "
|
|
447
|
+
"(disable with CLANG_TOOL_CHAIN_NO_SANITIZER_NOTE=1)",
|
|
358
448
|
file=sys.stderr,
|
|
359
449
|
)
|
|
360
450
|
|
|
@@ -378,6 +468,7 @@ class RPathTransformer(ArgumentTransformer):
|
|
|
378
468
|
|
|
379
469
|
Environment Variables:
|
|
380
470
|
CLANG_TOOL_CHAIN_NO_RPATH: Set to '1' to disable automatic rpath injection
|
|
471
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to '1' to disable all automatic features
|
|
381
472
|
"""
|
|
382
473
|
|
|
383
474
|
def priority(self) -> int:
|
|
@@ -401,9 +492,8 @@ class RPathTransformer(ArgumentTransformer):
|
|
|
401
492
|
if not has_deploy_flag and not deploy_from_env:
|
|
402
493
|
return args
|
|
403
494
|
|
|
404
|
-
# Check if user disabled rpath
|
|
405
|
-
if
|
|
406
|
-
logger.debug("Rpath injection disabled via CLANG_TOOL_CHAIN_NO_RPATH=1")
|
|
495
|
+
# Check if user disabled rpath (via NO_RPATH or NO_AUTO)
|
|
496
|
+
if is_feature_disabled("RPATH"):
|
|
407
497
|
return args
|
|
408
498
|
|
|
409
499
|
# Check if rpath already present
|
|
@@ -528,11 +618,12 @@ def create_default_pipeline() -> ArgumentPipeline:
|
|
|
528
618
|
This includes all standard transformers in their default priority order:
|
|
529
619
|
1. DirectivesTransformer (priority=50)
|
|
530
620
|
2. MacOSSDKTransformer (priority=100)
|
|
531
|
-
3.
|
|
532
|
-
4.
|
|
533
|
-
5.
|
|
534
|
-
6.
|
|
535
|
-
7.
|
|
621
|
+
3. LinuxUnwindTransformer (priority=150)
|
|
622
|
+
4. LLDLinkerTransformer (priority=200)
|
|
623
|
+
5. ASANRuntimeTransformer (priority=250)
|
|
624
|
+
6. RPathTransformer (priority=275)
|
|
625
|
+
7. GNUABITransformer (priority=300)
|
|
626
|
+
8. MSVCABITransformer (priority=300)
|
|
536
627
|
|
|
537
628
|
Returns:
|
|
538
629
|
Configured ArgumentPipeline ready for use
|
|
@@ -541,6 +632,7 @@ def create_default_pipeline() -> ArgumentPipeline:
|
|
|
541
632
|
[
|
|
542
633
|
DirectivesTransformer(),
|
|
543
634
|
MacOSSDKTransformer(),
|
|
635
|
+
LinuxUnwindTransformer(),
|
|
544
636
|
LLDLinkerTransformer(),
|
|
545
637
|
ASANRuntimeTransformer(),
|
|
546
638
|
RPathTransformer(),
|
|
@@ -26,6 +26,7 @@ from typing import NoReturn
|
|
|
26
26
|
|
|
27
27
|
from clang_tool_chain.cli_parsers import parse_build_args, parse_build_run_args
|
|
28
28
|
from clang_tool_chain.directives import DirectiveParser
|
|
29
|
+
from clang_tool_chain.env_utils import is_feature_disabled
|
|
29
30
|
from clang_tool_chain.execution.core import execute_tool
|
|
30
31
|
from clang_tool_chain.interrupt_utils import handle_keyboard_interrupt_properly
|
|
31
32
|
from clang_tool_chain.platform import get_platform_info
|
|
@@ -47,9 +48,13 @@ def _get_directive_args(source_path: Path) -> list[str]:
|
|
|
47
48
|
|
|
48
49
|
Returns:
|
|
49
50
|
List of compiler/linker arguments derived from directives
|
|
51
|
+
|
|
52
|
+
Environment Variables:
|
|
53
|
+
CLANG_TOOL_CHAIN_NO_DIRECTIVES: Set to '1' to disable directive parsing
|
|
54
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to '1' to disable all automatic features
|
|
50
55
|
"""
|
|
51
|
-
# Check if directives parsing is disabled via
|
|
52
|
-
if
|
|
56
|
+
# Check if directives parsing is disabled (via NO_DIRECTIVES or NO_AUTO)
|
|
57
|
+
if is_feature_disabled("DIRECTIVES"):
|
|
53
58
|
return []
|
|
54
59
|
|
|
55
60
|
try:
|
|
@@ -106,10 +111,11 @@ def get_directive_args_from_compiler_args(args: list[str]) -> list[str]:
|
|
|
106
111
|
|
|
107
112
|
Environment Variables:
|
|
108
113
|
CLANG_TOOL_CHAIN_NO_DIRECTIVES: Set to '1' to disable directive parsing
|
|
114
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to '1' to disable all automatic features
|
|
109
115
|
CLANG_TOOL_CHAIN_DIRECTIVE_VERBOSE: Set to '1' to show parsed directives
|
|
110
116
|
"""
|
|
111
|
-
# Check if directives parsing is disabled via
|
|
112
|
-
if
|
|
117
|
+
# Check if directives parsing is disabled (via NO_DIRECTIVES or NO_AUTO)
|
|
118
|
+
if is_feature_disabled("DIRECTIVES"):
|
|
113
119
|
return []
|
|
114
120
|
|
|
115
121
|
directive_args: list[str] = []
|
|
@@ -24,6 +24,7 @@ from pathlib import Path
|
|
|
24
24
|
from typing import NoReturn
|
|
25
25
|
|
|
26
26
|
from clang_tool_chain.directives import DirectiveParser
|
|
27
|
+
from clang_tool_chain.env_utils import is_feature_disabled
|
|
27
28
|
from clang_tool_chain.execution.core import run_tool
|
|
28
29
|
from clang_tool_chain.interrupt_utils import handle_keyboard_interrupt_properly
|
|
29
30
|
|
|
@@ -109,9 +110,13 @@ def _get_directive_args(source_path: Path) -> list[str]:
|
|
|
109
110
|
|
|
110
111
|
Returns:
|
|
111
112
|
List of compiler/linker arguments derived from directives
|
|
113
|
+
|
|
114
|
+
Environment Variables:
|
|
115
|
+
CLANG_TOOL_CHAIN_NO_DIRECTIVES: Set to '1' to disable directive parsing
|
|
116
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to '1' to disable all automatic features
|
|
112
117
|
"""
|
|
113
|
-
# Check if directives parsing is disabled via
|
|
114
|
-
if
|
|
118
|
+
# Check if directives parsing is disabled (via NO_DIRECTIVES or NO_AUTO)
|
|
119
|
+
if is_feature_disabled("DIRECTIVES"):
|
|
115
120
|
return []
|
|
116
121
|
|
|
117
122
|
try:
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Sanitizer runtime environment configuration.
|
|
3
3
|
|
|
4
|
-
This module provides automatic injection of ASAN_OPTIONS and
|
|
5
|
-
environment variables to improve stack trace quality
|
|
6
|
-
compiled with Address Sanitizer or Leak Sanitizer.
|
|
4
|
+
This module provides automatic injection of ASAN_OPTIONS, LSAN_OPTIONS, and
|
|
5
|
+
ASAN_SYMBOLIZER_PATH environment variables to improve stack trace quality
|
|
6
|
+
when running executables compiled with Address Sanitizer or Leak Sanitizer.
|
|
7
7
|
|
|
8
8
|
The default options fix <unknown module> entries in stack traces from
|
|
9
9
|
dlopen()'d shared libraries by enabling slow unwinding and symbolization.
|
|
10
|
+
|
|
11
|
+
The symbolizer path is automatically detected from the clang-tool-chain
|
|
12
|
+
installation, enabling proper address-to-symbol resolution without manual
|
|
13
|
+
configuration.
|
|
10
14
|
"""
|
|
11
15
|
|
|
12
16
|
import logging
|
|
13
17
|
import os
|
|
18
|
+
import shutil
|
|
19
|
+
|
|
20
|
+
from clang_tool_chain.env_utils import is_feature_disabled
|
|
14
21
|
|
|
15
22
|
logger = logging.getLogger(__name__)
|
|
16
23
|
|
|
@@ -22,6 +29,46 @@ DEFAULT_ASAN_OPTIONS = "fast_unwind_on_malloc=0:symbolize=1:detect_leaks=1"
|
|
|
22
29
|
DEFAULT_LSAN_OPTIONS = "fast_unwind_on_malloc=0:symbolize=1"
|
|
23
30
|
|
|
24
31
|
|
|
32
|
+
def get_symbolizer_path() -> str | None:
|
|
33
|
+
"""
|
|
34
|
+
Get the path to llvm-symbolizer from the clang-tool-chain installation.
|
|
35
|
+
|
|
36
|
+
This function finds the llvm-symbolizer binary bundled with clang-tool-chain,
|
|
37
|
+
which is required by ASAN/LSAN to convert memory addresses into function names
|
|
38
|
+
and source locations in stack traces.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Absolute path to llvm-symbolizer, or None if not found.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
>>> path = get_symbolizer_path()
|
|
45
|
+
>>> if path:
|
|
46
|
+
... os.environ["ASAN_SYMBOLIZER_PATH"] = path
|
|
47
|
+
|
|
48
|
+
Note:
|
|
49
|
+
Falls back to system PATH if the clang-tool-chain binary is not available.
|
|
50
|
+
This allows the function to work even when clang-tool-chain is not fully
|
|
51
|
+
installed (e.g., during development or in CI environments).
|
|
52
|
+
"""
|
|
53
|
+
# Try to find llvm-symbolizer from clang-tool-chain installation
|
|
54
|
+
try:
|
|
55
|
+
from clang_tool_chain.platform.paths import find_tool_binary
|
|
56
|
+
|
|
57
|
+
symbolizer = find_tool_binary("llvm-symbolizer")
|
|
58
|
+
return str(symbolizer)
|
|
59
|
+
except (ImportError, RuntimeError) as e:
|
|
60
|
+
logger.debug(f"Could not find llvm-symbolizer in clang-tool-chain: {e}")
|
|
61
|
+
|
|
62
|
+
# Fall back to system PATH
|
|
63
|
+
system_symbolizer = shutil.which("llvm-symbolizer")
|
|
64
|
+
if system_symbolizer:
|
|
65
|
+
logger.debug(f"Using system llvm-symbolizer: {system_symbolizer}")
|
|
66
|
+
return system_symbolizer
|
|
67
|
+
|
|
68
|
+
logger.debug("llvm-symbolizer not found in clang-tool-chain or system PATH")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
25
72
|
def detect_sanitizers_from_flags(compiler_flags: list[str]) -> tuple[bool, bool]:
|
|
26
73
|
"""
|
|
27
74
|
Detect which sanitizers are enabled from compiler flags.
|
|
@@ -64,12 +111,16 @@ def prepare_sanitizer_environment(
|
|
|
64
111
|
compiler_flags: list[str] | None = None,
|
|
65
112
|
) -> dict[str, str]:
|
|
66
113
|
"""
|
|
67
|
-
Prepare environment with optimal sanitizer options.
|
|
114
|
+
Prepare environment with optimal sanitizer options and symbolizer path.
|
|
68
115
|
|
|
69
|
-
This function injects ASAN_OPTIONS
|
|
70
|
-
if they are not already set by the user AND the
|
|
71
|
-
enabled during compilation. The injected options
|
|
72
|
-
for executables using dlopen()'d shared libraries.
|
|
116
|
+
This function injects ASAN_OPTIONS, LSAN_OPTIONS, and ASAN_SYMBOLIZER_PATH
|
|
117
|
+
environment variables if they are not already set by the user AND the
|
|
118
|
+
corresponding sanitizer was enabled during compilation. The injected options
|
|
119
|
+
improve stack trace quality for executables using dlopen()'d shared libraries.
|
|
120
|
+
|
|
121
|
+
The ASAN_SYMBOLIZER_PATH is automatically detected from the clang-tool-chain
|
|
122
|
+
installation, enabling proper address-to-symbol resolution (function names,
|
|
123
|
+
file paths, line numbers) without manual configuration.
|
|
73
124
|
|
|
74
125
|
Args:
|
|
75
126
|
base_env: Base environment dictionary to modify. If None, uses os.environ.
|
|
@@ -83,20 +134,21 @@ def prepare_sanitizer_environment(
|
|
|
83
134
|
Environment Variables:
|
|
84
135
|
CLANG_TOOL_CHAIN_NO_SANITIZER_ENV: Set to "1", "true", or "yes" to
|
|
85
136
|
disable automatic injection of sanitizer options.
|
|
137
|
+
CLANG_TOOL_CHAIN_NO_AUTO: Set to "1" to disable all automatic features.
|
|
86
138
|
ASAN_OPTIONS: If already set, preserved as-is (user config takes priority).
|
|
87
139
|
LSAN_OPTIONS: If already set, preserved as-is (user config takes priority).
|
|
140
|
+
ASAN_SYMBOLIZER_PATH: If already set, preserved as-is (user config takes priority).
|
|
88
141
|
|
|
89
142
|
Example:
|
|
90
143
|
>>> env = prepare_sanitizer_environment(compiler_flags=["-fsanitize=address"])
|
|
91
|
-
>>> # env now contains ASAN_OPTIONS and
|
|
144
|
+
>>> # env now contains ASAN_OPTIONS, LSAN_OPTIONS, and ASAN_SYMBOLIZER_PATH
|
|
92
145
|
>>> env = prepare_sanitizer_environment(compiler_flags=["-O2"])
|
|
93
146
|
>>> # env unchanged - no sanitizers enabled
|
|
94
147
|
"""
|
|
95
148
|
env = base_env.copy() if base_env is not None else os.environ.copy()
|
|
96
149
|
|
|
97
|
-
# Check if disabled via environment variable
|
|
98
|
-
if
|
|
99
|
-
logger.debug("Sanitizer environment injection disabled via CLANG_TOOL_CHAIN_NO_SANITIZER_ENV")
|
|
150
|
+
# Check if disabled via environment variable (NO_SANITIZER_ENV or NO_AUTO)
|
|
151
|
+
if is_feature_disabled("SANITIZER_ENV"):
|
|
100
152
|
return env
|
|
101
153
|
|
|
102
154
|
# If no compiler flags provided, don't inject anything (safe default)
|
|
@@ -117,4 +169,17 @@ def prepare_sanitizer_environment(
|
|
|
117
169
|
env["LSAN_OPTIONS"] = DEFAULT_LSAN_OPTIONS
|
|
118
170
|
logger.info(f"Injecting LSAN_OPTIONS={DEFAULT_LSAN_OPTIONS}")
|
|
119
171
|
|
|
172
|
+
# Inject ASAN_SYMBOLIZER_PATH if any sanitizer is enabled and not already set
|
|
173
|
+
if (asan_enabled or lsan_enabled) and "ASAN_SYMBOLIZER_PATH" not in env:
|
|
174
|
+
symbolizer_path = get_symbolizer_path()
|
|
175
|
+
if symbolizer_path:
|
|
176
|
+
env["ASAN_SYMBOLIZER_PATH"] = symbolizer_path
|
|
177
|
+
logger.info(f"Injecting ASAN_SYMBOLIZER_PATH={symbolizer_path}")
|
|
178
|
+
else:
|
|
179
|
+
logger.warning(
|
|
180
|
+
"llvm-symbolizer not found - ASAN/LSAN stack traces may show "
|
|
181
|
+
"raw addresses instead of function names. Install llvm-symbolizer "
|
|
182
|
+
"or ensure clang-tool-chain is properly installed."
|
|
183
|
+
)
|
|
184
|
+
|
|
120
185
|
return env
|
clang_tool_chain/linker/lld.py
CHANGED
|
@@ -12,6 +12,7 @@ import logging
|
|
|
12
12
|
import os
|
|
13
13
|
import sys
|
|
14
14
|
|
|
15
|
+
from clang_tool_chain.env_utils import is_feature_disabled
|
|
15
16
|
from clang_tool_chain.interrupt_utils import handle_keyboard_interrupt_properly
|
|
16
17
|
from clang_tool_chain.llvm_versions import get_llvm_version_tuple, supports_ld64_lld_flag
|
|
17
18
|
|
|
@@ -187,13 +188,16 @@ def _translate_linker_flags_for_macos_lld(args: list[str]) -> list[str]:
|
|
|
187
188
|
Translate GNU ld linker flags to ld64.lld equivalents for macOS.
|
|
188
189
|
|
|
189
190
|
When using lld on macOS (ld64.lld), certain GNU ld flags need to be
|
|
190
|
-
translated to their Mach-O equivalents:
|
|
191
|
+
translated to their Mach-O equivalents or removed:
|
|
191
192
|
- --no-undefined -> -undefined error
|
|
192
193
|
- --fatal-warnings -> -fatal_warnings
|
|
193
|
-
-
|
|
194
|
+
- --allow-shlib-undefined -> (removed, ld64 allows undefined symbols by default)
|
|
194
195
|
|
|
195
196
|
This function processes both direct linker flags and flags passed via -Wl,
|
|
196
197
|
|
|
198
|
+
A warning is printed to stderr when flags are removed, unless silenced via
|
|
199
|
+
CLANG_TOOL_CHAIN_NO_LINKER_COMPAT_NOTE=1.
|
|
200
|
+
|
|
197
201
|
Args:
|
|
198
202
|
args: Original compiler arguments
|
|
199
203
|
|
|
@@ -201,12 +205,19 @@ def _translate_linker_flags_for_macos_lld(args: list[str]) -> list[str]:
|
|
|
201
205
|
Modified arguments with translated linker flags
|
|
202
206
|
"""
|
|
203
207
|
# Map of GNU ld flags to ld64.lld equivalents
|
|
204
|
-
|
|
208
|
+
# None means the flag should be removed (no equivalent needed)
|
|
209
|
+
flag_translations: dict[str, str | None] = {
|
|
205
210
|
"--no-undefined": "-undefined error",
|
|
206
211
|
"--fatal-warnings": "-fatal_warnings",
|
|
212
|
+
# --allow-shlib-undefined: ld64 allows undefined symbols in dylibs by default,
|
|
213
|
+
# so this flag is a no-op on macOS. We remove it entirely.
|
|
214
|
+
"--allow-shlib-undefined": None,
|
|
207
215
|
# Add more translations as needed
|
|
208
216
|
}
|
|
209
217
|
|
|
218
|
+
# Track removed flags for warning
|
|
219
|
+
removed_flags: list[str] = []
|
|
220
|
+
|
|
210
221
|
result = []
|
|
211
222
|
i = 0
|
|
212
223
|
while i < len(args):
|
|
@@ -221,14 +232,18 @@ def _translate_linker_flags_for_macos_lld(args: list[str]) -> list[str]:
|
|
|
221
232
|
for flag in linker_flags:
|
|
222
233
|
# Check if this flag needs translation
|
|
223
234
|
if flag in flag_translations:
|
|
224
|
-
# Translate the flag (may result in multiple flags)
|
|
225
235
|
translated = flag_translations[flag]
|
|
226
|
-
if
|
|
236
|
+
if translated is None:
|
|
237
|
+
# Flag should be removed entirely (no equivalent on macOS)
|
|
238
|
+
removed_flags.append(flag)
|
|
239
|
+
logger.debug(f"Removed linker flag (no macOS equivalent): {flag}")
|
|
240
|
+
elif " " in translated:
|
|
227
241
|
# Multiple flags (e.g., "-undefined error")
|
|
228
242
|
translated_flags.extend(translated.split())
|
|
243
|
+
logger.debug(f"Translated linker flag: {flag} -> {translated}")
|
|
229
244
|
else:
|
|
230
245
|
translated_flags.append(translated)
|
|
231
|
-
|
|
246
|
+
logger.debug(f"Translated linker flag: {flag} -> {translated}")
|
|
232
247
|
else:
|
|
233
248
|
translated_flags.append(flag)
|
|
234
249
|
|
|
@@ -239,19 +254,34 @@ def _translate_linker_flags_for_macos_lld(args: list[str]) -> list[str]:
|
|
|
239
254
|
# Handle standalone linker flags passed directly
|
|
240
255
|
elif arg in flag_translations:
|
|
241
256
|
translated = flag_translations[arg]
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
result.append("-Wl," + ",".join(translated.split()))
|
|
257
|
+
if translated is None:
|
|
258
|
+
# Flag should be removed entirely (no equivalent on macOS)
|
|
259
|
+
removed_flags.append(arg)
|
|
260
|
+
logger.debug(f"Removed linker flag (no macOS equivalent): {arg}")
|
|
247
261
|
else:
|
|
248
|
-
|
|
262
|
+
logger.debug(f"Translated linker flag: {arg} -> {translated}")
|
|
263
|
+
# Add via -Wl, to pass to linker
|
|
264
|
+
if " " in translated:
|
|
265
|
+
# Multiple flags
|
|
266
|
+
result.append("-Wl," + ",".join(translated.split()))
|
|
267
|
+
else:
|
|
268
|
+
result.append("-Wl," + translated)
|
|
249
269
|
|
|
250
270
|
else:
|
|
251
271
|
result.append(arg)
|
|
252
272
|
|
|
253
273
|
i += 1
|
|
254
274
|
|
|
275
|
+
# Emit warning for removed flags (unless silenced)
|
|
276
|
+
if removed_flags and not is_feature_disabled("LINKER_COMPAT_NOTE"):
|
|
277
|
+
import sys
|
|
278
|
+
|
|
279
|
+
print(
|
|
280
|
+
f"clang-tool-chain: note: removed GNU linker flags not supported by ld64.lld: "
|
|
281
|
+
f"{', '.join(removed_flags)} (disable with CLANG_TOOL_CHAIN_NO_LINKER_COMPAT_NOTE=1)",
|
|
282
|
+
file=sys.stderr,
|
|
283
|
+
)
|
|
284
|
+
|
|
255
285
|
return result
|
|
256
286
|
|
|
257
287
|
|
clang_tool_chain/wrapper.py
CHANGED
|
@@ -151,6 +151,15 @@ from clang_tool_chain.execution.lldb import (
|
|
|
151
151
|
get_lldb_binary_dir,
|
|
152
152
|
)
|
|
153
153
|
|
|
154
|
+
# ============================================================================
|
|
155
|
+
# Sanitizer Environment
|
|
156
|
+
# ============================================================================
|
|
157
|
+
from clang_tool_chain.execution.sanitizer_env import (
|
|
158
|
+
detect_sanitizers_from_flags,
|
|
159
|
+
get_symbolizer_path,
|
|
160
|
+
prepare_sanitizer_environment,
|
|
161
|
+
)
|
|
162
|
+
|
|
154
163
|
# ============================================================================
|
|
155
164
|
# Linker Configuration
|
|
156
165
|
# ============================================================================
|
|
@@ -218,6 +227,10 @@ __all__ = [
|
|
|
218
227
|
"run_tool",
|
|
219
228
|
"sccache_clang_main",
|
|
220
229
|
"sccache_clang_cpp_main",
|
|
230
|
+
# Sanitizer Environment
|
|
231
|
+
"prepare_sanitizer_environment",
|
|
232
|
+
"detect_sanitizers_from_flags",
|
|
233
|
+
"get_symbolizer_path",
|
|
221
234
|
# Emscripten
|
|
222
235
|
"ensure_nodejs_available",
|
|
223
236
|
"execute_emscripten_tool",
|