Bug 1874684 - Part 6: Limit day length calculations to safe integers. r=mgaudet
[gecko.git] / build / unix / rewrite_sanitizer_dylib.py
blob59198b13c8c8f97fa78d79f27b91a4eebcbaabd4
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 import os
6 import re
7 import shutil
8 import subprocess
9 import sys
10 from argparse import ArgumentParser
11 from pathlib import Path
13 from buildconfig import substs
15 """
16 Scans the given directories for binaries referencing the AddressSanitizer
17 runtime library, copies it to the main directory.
18 """
20 # This is the dylib name pattern
21 DYLIB_NAME_PATTERN = re.compile(r"libclang_rt\.(a|ub)san_osx_dynamic\.dylib")
24 def resolve_rpath(filename):
25 otoolOut = subprocess.check_output([substs["OTOOL"], "-l", filename], text=True)
26 currentCmd = None
28 # The lines we need to find look like this:
29 # ...
30 # Load command 22
31 # cmd LC_RPATH
32 # cmdsize 80
33 # path /home/build/src/clang/bin/../lib/clang/3.8.0/lib/darwin (offset 12)
34 # Load command 23
35 # ...
36 # Other load command types have a varying number of fields.
37 for line in otoolOut.splitlines():
38 cmdMatch = re.match(r"^\s+cmd ([A-Z_]+)", line)
39 if cmdMatch is not None:
40 currentCmd = cmdMatch.group(1)
41 continue
43 if currentCmd == "LC_RPATH":
44 pathMatch = re.match(r"^\s+path (.*) \(offset \d+\)", line)
45 if pathMatch is not None:
46 path = pathMatch.group(1)
47 if Path(path).is_dir():
48 return path
50 print(f"@rpath could not be resolved from {filename}", file=sys.stderr)
51 sys.exit(1)
54 def scan_directory(path):
55 dylibsCopied = set()
56 dylibsRequired = set()
58 if not path.is_dir():
59 print(f"Input path {path} is not a folder", file=sys.stderr)
60 sys.exit(1)
62 for file in path.rglob("*"):
63 if not file.is_file():
64 continue
66 # Skip all files that aren't either dylibs or executable
67 if not (file.suffix == ".dylib" or os.access(str(file), os.X_OK)):
68 continue
70 try:
71 otoolOut = subprocess.check_output(
72 [substs["OTOOL"], "-L", str(file)], text=True
74 except Exception:
75 # Errors are expected on non-mach executables, ignore them and continue
76 continue
78 for line in otoolOut.splitlines():
79 match = DYLIB_NAME_PATTERN.search(line)
81 if match is not None:
82 dylibName = match.group(0)
83 absDylibPath = line.split()[0]
85 dylibsRequired.add(dylibName)
87 if dylibName not in dylibsCopied:
88 if absDylibPath.startswith("@rpath/"):
89 rpath = resolve_rpath(str(file))
90 copyDylibPath = absDylibPath.replace("@rpath", rpath)
91 else:
92 copyDylibPath = absDylibPath
94 if Path(copyDylibPath).is_file():
95 # Copy the runtime once to the main directory, which is passed
96 # as the argument to this function.
97 shutil.copy(copyDylibPath, str(path))
98 dylibsCopied.add(dylibName)
99 else:
100 print(
101 f"dylib path in {file} was not found at: {copyDylibPath}",
102 file=sys.stderr,
105 break
107 dylibsMissing = dylibsRequired - dylibsCopied
108 if dylibsMissing:
109 for dylibName in dylibsMissing:
110 print(f"{dylibName} could not be found", file=sys.stderr)
111 sys.exit(1)
114 def parse_args(argv=None):
115 parser = ArgumentParser()
116 parser.add_argument("paths", metavar="path", type=Path, nargs="+")
117 return parser.parse_args(argv)
120 if __name__ == "__main__":
121 args = parse_args()
122 for d in args.paths:
123 scan_directory(d)