Skip to content

[MLIR, Python] Make it easy to run tests with ASan on mac #115524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions mlir/test/get_darwin_real_python.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# On macOS, system python binaries like /usr/bin/python and $(xcrun -f python3)
# are shims. They do some light validation work and then spawn the "real" python
# binary. Find the "real" python by asking dyld -- sys.executable reports the
# wrong thing more often than not. This is also useful when we're running under
# a Homebrew python3 binary, which also appears to be some kind of shim.
def getDarwinRealPythonExecutable():
import ctypes

dyld = ctypes.cdll.LoadLibrary("/usr/lib/system/libdyld.dylib")
namelen = ctypes.c_ulong(1024)
name = ctypes.create_string_buffer(b"\000", namelen.value)
dyld._NSGetExecutablePath(ctypes.byref(name), ctypes.byref(namelen))
return name.value.decode("utf-8").strip()


print(getDarwinRealPythonExecutable())
99 changes: 95 additions & 4 deletions mlir/test/lit.cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import platform
import re
import shutil
import subprocess
import tempfile

Expand Down Expand Up @@ -77,6 +78,75 @@ def add_runtime(name):
return ToolSubst(f"%{name}", find_runtime(name))


# Provide the path to asan runtime lib 'libclang_rt.asan_osx_dynamic.dylib' if
# available. This is darwin specific since it's currently only needed on darwin.
# Stolen from llvm/test/lit.cfg.py with a few modifications
def get_asan_rtlib():
if not "asan" in config.available_features or not "Darwin" in config.host_os:
return ""
# Find the asan rt lib
resource_dir = (
subprocess.check_output([config.host_cc.strip(), "-print-resource-dir"])
.decode("utf-8")
.strip()
)
return os.path.join(
resource_dir, "lib", "darwin", "libclang_rt.asan_osx_dynamic.dylib"
)


# On macOS, we can't do the DYLD_INSERT_LIBRARIES trick with a shim python
# binary as the ASan interceptors get loaded too late. Also, when SIP is
# enabled, we can't inject libraries into system binaries at all, so we need a
# copy of the "real" python to work with.
# Stolen from lldb/test/API/lit.cfg.py with a few modifications
def find_real_python_interpreter():
# If we're running in a virtual environment, we have to copy Python into
# the virtual environment for it to work.
if sys.prefix != sys.base_prefix:
copied_python = os.path.join(sys.prefix, "bin", "copied-python")
else:
copied_python = os.path.join(config.lldb_build_directory, "copied-python")

# Avoid doing any work if we already copied the binary.
if os.path.isfile(copied_python):
return copied_python

# Find the "real" python binary.
real_python = (
subprocess.check_output(
[
config.python_executable,
os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"get_darwin_real_python.py",
),
]
)
.decode("utf-8")
.strip()
)

shutil.copy(real_python, copied_python)

# Now make sure the copied Python works. The Python in Xcode has a relative
# RPATH and cannot be copied.
try:
# We don't care about the output, just make sure it runs.
subprocess.check_call([copied_python, "-V"])
except subprocess.CalledProcessError:
# The copied Python didn't work. Assume we're dealing with the Python
# interpreter in Xcode. Given that this is not a system binary SIP
# won't prevent us form injecting the interceptors, but when running in
# a virtual environment, we can't use it directly. Create a symlink
# instead.
os.remove(copied_python)
os.symlink(real_python, copied_python)

# The copied Python works.
return copied_python


llvm_config.with_system_environment(["HOME", "INCLUDE", "LIB", "TMP", "TEMP"])

llvm_config.use_default_substitutions()
Expand All @@ -91,6 +161,7 @@ def add_runtime(name):
"LICENSE.txt",
"lit.cfg.py",
"lit.site.cfg.py",
"get_darwin_real_python.py",
]

# Tweak the PATH to include the tools dir.
Expand Down Expand Up @@ -172,10 +243,30 @@ def add_runtime(name):
)

python_executable = config.python_executable
# Python configuration with sanitizer requires some magic preloading. This will only work on clang/linux.
# TODO: detect Darwin/Windows situation (or mark these tests as unsupported on these platforms).
if "asan" in config.available_features and "Linux" in config.host_os:
python_executable = f"LD_PRELOAD=$({config.host_cxx} -print-file-name=libclang_rt.asan-{config.host_arch}.so) {config.python_executable}"
# Python configuration with sanitizer requires some magic preloading. This will only work on clang/linux/darwin.
# TODO: detect Windows situation (or mark these tests as unsupported on these platforms).
if "asan" in config.available_features:
if "Linux" in config.host_os:
python_executable = f"LD_PRELOAD=$({config.host_cxx} -print-file-name=libclang_rt.asan-{config.host_arch}.so) {config.python_executable}"
if "Darwin" in config.host_os:
# Ensure we use a non-shim Python executable, for the `DYLD_INSERT_LIBRARIES`
# env variable to take effect
real_python_executable = find_real_python_interpreter()
if real_python_executable:
python_executable = real_python_executable
lit_config.note(
"Using {} instead of {}".format(
python_executable, config.python_executable
)
)

asan_rtlib = get_asan_rtlib()
lit_config.note("Using ASan rtlib {}".format(asan_rtlib))
config.environment["MallocNanoZone"] = "0"
config.environment["ASAN_OPTIONS"] = "detect_stack_use_after_return=1"
config.environment["DYLD_INSERT_LIBRARIES"] = asan_rtlib


# On Windows the path to python could contains spaces in which case it needs to be provided in quotes.
# This is the equivalent of how %python is setup in llvm/utils/lit/lit/llvm/config.py.
elif "Windows" in config.host_os:
Expand Down
Loading