Skip to content

Commit 6158cd5

Browse files
OSS-Fuzz Teamcopybara-github
authored andcommitted
index_build: Support custom compiler toolchains
- Use CLANG_TOOLCHAIN and CLANG_VERSION environment variables to determine this. - Use a more robust approach to wrapping the compiler by creating a "shadow" toolchain at `/opt/toolchain` with `bin` and `lib` symlinked in from CLANG_TOOLCHAIN (except for `clang` and `clang++`). `clang` and `clang++` are symlinked from our `clang_wrapper.py` - Allow -ffile-compilation-dir=. and -fdebug-compilation-dir=. Chrome GN builds use these and they're OK. - Move -gen-cdb-fragment-path injection into the clang wrapper instead, so we don't have to rely on this being propagated properly from index_build.py. This makes V8 indexing work, enough if other flags are not propagated yet. PiperOrigin-RevId: 801938479
1 parent 8698378 commit 6158cd5

File tree

2 files changed

+101
-42
lines changed

2 files changed

+101
-42
lines changed

infra/base-images/base-builder/indexer/clang_wrapper.py

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,35 +46,40 @@
4646
# When we notice a project using these flags,
4747
# we should figure out how to handle them.
4848
_DISALLOWED_CLANG_FLAGS = (
49-
"-fdebug-compilation-dir=",
5049
"-fdebug-prefix-map=",
51-
"-ffile-compilation-dir=",
5250
"-ffile-prefix-map=",
5351
)
5452

53+
# Chromium GN builds use these flags with a period to make paths relative to
54+
# the out directory. This is OK.
55+
_ALLOWED_CLANG_FLAGS_ONLY_WITH_PERIOD = (
56+
"-fdebug-compilation-dir=",
57+
"-ffile-compilation-dir=",
58+
)
59+
5560
SRC = Path(os.getenv("SRC", "/src"))
5661
# On OSS-Fuzz build infra, $OUT is not /out.
5762
OUT = Path(os.getenv("OUT", "/out"))
5863
INDEXES_PATH = Path(os.getenv("INDEXES_PATH", "/indexes"))
5964
FUZZER_ENGINE = os.getenv("LIB_FUZZING_ENGINE", "/usr/lib/libFuzzingEngine.a")
6065

6166

62-
def rewrite_argv0(argv: Sequence[str]) -> list[str]:
67+
def rewrite_argv0(argv: Sequence[str], clang_toolchain: str) -> list[str]:
6368
"""Rewrite argv[0] to point to the real clang location."""
6469
# We do this because we've set PATH to our wrapper.
65-
rewritten = [os.path.join("/usr/local/bin/", os.path.basename(argv[0]))]
70+
rewritten = [os.path.join(clang_toolchain, "bin", os.path.basename(argv[0]))]
6671
rewritten.extend(argv[1:])
6772
return rewritten
6873

6974

70-
def execute(argv: Sequence[str]) -> None:
71-
argv = rewrite_argv0(argv)
75+
def execute(argv: Sequence[str], clang_toolchain: str) -> None:
76+
argv = rewrite_argv0(argv, clang_toolchain)
7277
print("About to execute...", argv)
7378
os.execv(argv[0], tuple(argv))
7479

7580

76-
def run(argv: Sequence[str]) -> None:
77-
argv = rewrite_argv0(argv)
81+
def run(argv: Sequence[str], clang_toolchain: str) -> None:
82+
argv = rewrite_argv0(argv, clang_toolchain)
7883
print("About to run...", argv)
7984
ret = subprocess.run(argv, check=False)
8085
if ret.returncode != 0:
@@ -371,7 +376,17 @@ def check_fuzzing_engine_and_fix_argv(argv: MutableSequence[str]) -> bool:
371376

372377
def _has_disallowed_clang_flags(argv: Sequence[str]) -> bool:
373378
"""Checks if the command line arguments contain disallowed flags."""
374-
return any(arg.startswith(_DISALLOWED_CLANG_FLAGS) for arg in argv)
379+
if any(arg.startswith(_DISALLOWED_CLANG_FLAGS) for arg in argv):
380+
return True
381+
382+
if any(
383+
arg.startswith(_ALLOWED_CLANG_FLAGS_ONLY_WITH_PERIOD)
384+
and not arg.endswith("=.")
385+
for arg in argv
386+
):
387+
return True
388+
389+
return False
375390

376391

377392
@dataclasses.dataclass(frozen=True)
@@ -400,7 +415,16 @@ def _filter_compile_commands(
400415
unused_cc_paths = set()
401416

402417
for compile_command in compile_commands:
403-
cc_path = Path(compile_command["directory"]) / compile_command["file"]
418+
if (
419+
"-ffile-compilation-dir=." in compile_command["arguments"]
420+
or "-fdebug-compilation-dir=." in compile_command["arguments"]
421+
):
422+
# Handle build systems that make their debug paths relative.
423+
directory = Path(".")
424+
else:
425+
directory = Path(compile_command["directory"])
426+
427+
cc_path = Path(directory / compile_command["file"])
404428
if cc_path in cu_paths:
405429
filtered_compile_commands.append(compile_command)
406430
used_cu_paths.add(cc_path)
@@ -489,10 +513,27 @@ def main(argv: list[str]) -> None:
489513
if _has_disallowed_clang_flags(argv):
490514
raise ValueError("Disallowed clang flags found, aborting.")
491515

516+
# TODO: b/441872725 - Migrate more flags to be appended in the clang wrapper
517+
# instead.
518+
cdb_path = index_build.OUT / "cdb"
519+
argv.extend(("-gen-cdb-fragment-path", cdb_path.as_posix()))
520+
argv.extend((
521+
"-isystem",
522+
(
523+
f"{compile_settings.clang_toolchain}/lib/clang/"
524+
f"{compile_settings.clang_version}"
525+
),
526+
"-resource-dir",
527+
(
528+
f"{compile_settings.clang_toolchain}/lib/clang/"
529+
f"{compile_settings.clang_version}"
530+
),
531+
))
532+
492533
if "-E" in argv:
493534
# Preprocessor-only invocation.
494535
modified_argv = remove_flag_and_value(argv, "-gen-cdb-fragment-path")
495-
execute(modified_argv)
536+
execute(modified_argv, compile_settings.clang_toolchain)
496537

497538
fuzzing_engine_in_argv = check_fuzzing_engine_and_fix_argv(argv)
498539
indexer_targets: list[str] = [
@@ -502,29 +543,24 @@ def main(argv: list[str]) -> None:
502543
# If we are linking, collect the relevant flags and dependencies.
503544
output_file = get_flag_value(argv, "-o")
504545
if not output_file:
505-
execute(argv) # Missing output file
546+
execute(argv, compile_settings.clang_toolchain) # Missing output file
506547

507548
output_file = Path(output_file)
508549

509550
if output_file.name.endswith(".o"):
510-
execute(argv) # Not a real linker command
551+
execute(argv, compile_settings.clang_toolchain) # Not a real linker command
511552

512553
if indexer_targets:
513554
if output_file.name not in indexer_targets:
514555
# Not a relevant linker command
515556
print(f"Not indexing as {output_file} is not in the allowlist")
516-
execute(argv)
557+
execute(argv, compile_settings.clang_toolchain)
517558
elif not fuzzing_engine_in_argv:
518559
# Not a fuzz target.
519-
execute(argv)
560+
execute(argv, compile_settings.clang_toolchain)
520561

521562
print(f"Linking {argv}")
522563

523-
cdb_path = get_flag_value(argv, "-gen-cdb-fragment-path")
524-
assert cdb_path, f"Missing Compile Directory Path: {argv}"
525-
526-
cdb_path = Path(cdb_path)
527-
528564
# We can now run the linker and look at the output of some files.
529565
dependency_file = (cdb_path / output_file.name).with_suffix(".deps")
530566
why_extract_file = (cdb_path / output_file.name).with_suffix(".why_extract")
@@ -535,7 +571,7 @@ def main(argv: list[str]) -> None:
535571
# We force lld, but it doesn't include this dir by default.
536572
argv.append("-L/usr/local/lib")
537573
argv.append("-Qunused-arguments")
538-
run(argv)
574+
run(argv, compile_settings.clang_toolchain)
539575

540576
build_id = get_build_id(output_file)
541577
assert build_id is not None

infra/base-images/base-builder/indexer/index_build.py

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,20 @@
4444
_LD_BINARY = 'ld-linux-x86-64.so.2'
4545
_LD_PATH = Path('/lib64') / _LD_BINARY
4646
_LLVM_READELF_PATH = '/usr/local/bin/llvm-readelf'
47-
_CLANG_VERSION = '18'
4847

4948
DEFAULT_COVERAGE_FLAGS = '-fsanitize-coverage=bb,no-prune,trace-pc-guard'
5049
DEFAULT_FUZZING_ENGINE = 'fuzzing_engine.cc'
5150

51+
_CLANG_VERSION = os.getenv('CLANG_VERSION', '18')
52+
_CLANG_TOOLCHAIN = Path(os.getenv('CLANG_TOOLCHAIN', '/usr/local'))
53+
_TOOLCHAIN_WITH_WRAPPER = Path('/opt/toolchain')
54+
55+
INDEXER_DIR = Path(__file__).parent
56+
5257
# Some build systems isolate the compiler environment from the parent process,
5358
# so we can't always rely on using environment variables to pass settings to the
5459
# wrapper. Get around this by writing to a file instead.
55-
COMPILE_SETTINGS_PATH = Path(__file__).parent / 'compile_settings.json'
60+
COMPILE_SETTINGS_PATH = INDEXER_DIR / 'compile_settings.json'
5661

5762
EXTRA_CFLAGS = (
5863
'-fno-omit-frame-pointer '
@@ -61,16 +66,15 @@
6166
'-fsanitize=address '
6267
'-Wno-invalid-offsetof '
6368
'{coverage_flags} '
64-
f'-gen-cdb-fragment-path {OUT}/cdb '
6569
'-Qunused-arguments '
66-
f'-isystem /usr/local/lib/clang/{_CLANG_VERSION} '
67-
f'-resource-dir /usr/local/lib/clang/{_CLANG_VERSION} '
6870
)
6971

7072

7173
@dataclasses.dataclass(slots=True, frozen=True)
7274
class CompileSettings:
7375
coverage_flags: str
76+
clang_toolchain: str
77+
clang_version: str
7478

7579

7680
def read_compile_settings() -> CompileSettings:
@@ -100,26 +104,38 @@ def set_env_vars(coverage_flags: str):
100104
os.environ['CC'] = 'clang'
101105
os.environ['COMPILING_PROJECT'] = 'True'
102106
# Force users of clang to use our wrapper. This fixes e.g. libcups.
103-
os.environ['PATH'] = f"/opt/indexer:{os.environ.get('PATH')}"
107+
os.environ['PATH'] = (
108+
f"{_TOOLCHAIN_WITH_WRAPPER / 'bin'}:{os.environ.get('PATH')}"
109+
)
104110

105111
existing_cflags = os.environ.get('CFLAGS', '')
106112
extra_cflags = EXTRA_CFLAGS.format(coverage_flags=coverage_flags)
107113
os.environ['CFLAGS'] = f'{existing_cflags} {extra_cflags}'.strip()
108114

109115

110116
def set_up_wrapper_dir():
111-
"""Set up symlinks to everything in /usr/local/bin/.
117+
"""Sets up a shadow toolchain.
112118
113-
Do this so build systems that snoop around clang's directory don't explode.
119+
This sets up our clang wrapper for clang/clang++ and symlinks everything else
120+
to point to the real toolchain.
114121
"""
115-
real_dir = '/usr/local/bin'
116-
indexer_dir = '/opt/indexer'
117-
for name in os.listdir():
118-
src = os.path.join(real_dir, name)
119-
dst = os.path.join(indexer_dir, name)
120-
if name not in {'clang', 'clang++'}:
122+
if _TOOLCHAIN_WITH_WRAPPER.exists():
123+
shutil.rmtree(_TOOLCHAIN_WITH_WRAPPER)
124+
_TOOLCHAIN_WITH_WRAPPER.mkdir(parents=True)
125+
126+
# Set up symlinks to every binary except for clang.
127+
wrapper_bin_dir = _TOOLCHAIN_WITH_WRAPPER / 'bin'
128+
wrapper_bin_dir.mkdir()
129+
for name in os.listdir(_CLANG_TOOLCHAIN / 'bin'):
130+
if name in ('clang', 'clang++'):
121131
continue
122-
os.symlink(src, dst)
132+
133+
os.symlink(_CLANG_TOOLCHAIN / 'bin' / name, wrapper_bin_dir / name)
134+
os.symlink(_CLANG_TOOLCHAIN / 'lib', _TOOLCHAIN_WITH_WRAPPER / 'lib')
135+
136+
# Set up our compiler wrappers.
137+
os.symlink(INDEXER_DIR / 'clang_wrapper.py', wrapper_bin_dir / 'clang')
138+
os.symlink(INDEXER_DIR / 'clang_wrapper.py', wrapper_bin_dir / 'clang++')
123139

124140

125141
@dataclasses.dataclass(slots=True, frozen=True)
@@ -334,11 +350,17 @@ def build_project(
334350
if binaries_only:
335351
os.environ['INDEXER_BINARIES_ONLY'] = '1'
336352

337-
write_compile_settings(CompileSettings(coverage_flags=coverage_flags))
353+
write_compile_settings(
354+
CompileSettings(
355+
coverage_flags=coverage_flags,
356+
clang_toolchain=_CLANG_TOOLCHAIN.as_posix(),
357+
clang_version=_CLANG_VERSION,
358+
)
359+
)
338360

339361
fuzzing_engine_dir = copy_fuzzing_engine(fuzzing_engine)
340362
build_fuzzing_engine_command = [
341-
'/opt/indexer/clang++',
363+
f'{_CLANG_TOOLCHAIN}/bin/clang++',
342364
'-c',
343365
'-Wall',
344366
'-Wextra',
@@ -352,16 +374,16 @@ def build_project(
352374
'-gen-cdb-fragment-path',
353375
f'{OUT}/cdb',
354376
'-Qunused-arguments',
355-
f'-isystem /usr/local/lib/clang/{_CLANG_VERSION}',
377+
f'-isystem {_CLANG_TOOLCHAIN}/lib/clang/{_CLANG_VERSION}',
356378
'/usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9',
357379
'-I',
358380
'/usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/x86_64-linux-gnu/c++/9',
359381
'-I',
360382
'/usr/lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/backward',
361383
'-I',
362-
f'/usr/local/lib/clang/{_CLANG_VERSION}/include',
384+
f'{_CLANG_TOOLCHAIN}/lib/clang/{_CLANG_VERSION}/include',
363385
'-I',
364-
'/usr/local/include',
386+
f'{_CLANG_TOOLCHAIN}/include',
365387
'-I',
366388
'/usr/include/x86_64-linux-gnu',
367389
'-I',
@@ -379,7 +401,6 @@ def build_project(
379401
if os.path.exists(lib_fuzzing_engine):
380402
os.remove(lib_fuzzing_engine)
381403
os.symlink('/opt/indexer/fuzzing_engine.a', lib_fuzzing_engine)
382-
set_up_wrapper_dir()
383404

384405
compile_command = ['/usr/local/bin/compile']
385406
if compile_args:
@@ -804,6 +825,8 @@ def main():
804825
SNAPSHOT_DIR.mkdir(exist_ok=True)
805826
# We don't have an existing /out dir on oss-fuzz's build infra.
806827
OUT.mkdir(parents=True, exist_ok=True)
828+
829+
set_up_wrapper_dir()
807830
build_project(
808831
None if args.targets_all_index else targets_to_index,
809832
args.compile_arg,

0 commit comments

Comments
 (0)