Skip to content
Open
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
11 changes: 11 additions & 0 deletions compiler-rt/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
# ShadowCallStack does not yet provide a runtime with compiler-rt, the tests
# include their own minimal runtime
add_subdirectory(shadowcallstack)

# TO_UPSTREAM(BoundsSafety)
# -fbounds-safety doesn't have a runtime in compiler-rt so guarding with
# `COMPILER_RT_HAS_BOUNDS_SAFETY` is unnecessary so that is why
# `compiler_rt_test_runtime` is not used here.
if(COMPILER_RT_INCLUDE_TESTS)
option(COMPILER_RT_TEST_BOUNDS_SAFETY "Include -fbounds-safety runtime tests" ON)
if(COMPILER_RT_TEST_BOUNDS_SAFETY)
add_subdirectory(bounds_safety)
endif()
endif()
endif()

# Now that we've traversed all the directories and know all the lit testsuites,
Expand Down
174 changes: 174 additions & 0 deletions compiler-rt/test/bounds_safety/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
message(STATUS "Trying to enable -fbounds-safety runtime tests")
if (NOT APPLE)
# FIXME: We should support Linux too.
message(STATUS "Skipping -fbounds-safety runtime tests on non-Apple platforms")
return()
endif()
set(BOUNDS_SAFETY_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(BOUNDS_SAFETY_HOST_TEST_SUITES)
# Currently no dependencies
set(BOUNDS_SAFETY_TEST_DEPS "")

# Detect LLDB availability and Python compatibility
find_program(LLDB_EXECUTABLE lldb HINTS ${LLVM_TOOLS_DIR})
set(_LLDB_DETECTED FALSE)
set(_LLDB_PYTHON_COMPATIBLE FALSE)
if(LLDB_EXECUTABLE)
execute_process(
COMMAND ${LLDB_EXECUTABLE} -P
OUTPUT_VARIABLE BOUNDS_SAFETY_LLDB_PYTHON_PATH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE LLDB_P_RESULT
)
if(LLDB_P_RESULT EQUAL 0)
set(_LLDB_DETECTED TRUE)
# Check that the Python interpreter used by lit can import the lldb module.
# A mismatch (e.g. Homebrew Python 3.13 vs Xcode LLDB built for Python 3.11)
# causes a confusing ImportError at test time.
execute_process(
COMMAND ${Python3_EXECUTABLE} -c
"import sys; sys.path.insert(0, '${BOUNDS_SAFETY_LLDB_PYTHON_PATH}'); import lldb"
RESULT_VARIABLE _LLDB_IMPORT_RESULT
ERROR_VARIABLE _LLDB_IMPORT_ERROR
OUTPUT_QUIET
)
if(_LLDB_IMPORT_RESULT EQUAL 0)
set(_LLDB_PYTHON_COMPATIBLE TRUE)
endif()
endif()
endif()

# CMake option: defaults to ON if LLDB detected AND the Python interpreter
# can import the lldb module. If explicitly set to ON but either condition
# fails, error the build.
option(COMPILER_RT_BOUNDS_SAFETY_USE_LLDB
"Use LLDB for bounds-safety debugger test suites" ${_LLDB_PYTHON_COMPATIBLE})

if(COMPILER_RT_BOUNDS_SAFETY_USE_LLDB AND NOT _LLDB_DETECTED)
message(FATAL_ERROR
"COMPILER_RT_BOUNDS_SAFETY_USE_LLDB=ON but LLDB was not found or "
"lldb -P failed. Set to OFF or ensure LLDB is available.")
endif()

if(COMPILER_RT_BOUNDS_SAFETY_USE_LLDB AND NOT _LLDB_PYTHON_COMPATIBLE)
message(FATAL_ERROR
"COMPILER_RT_BOUNDS_SAFETY_USE_LLDB=ON but the Python interpreter "
"used by lit (${Python3_EXECUTABLE}) cannot import the lldb module "
"from ${BOUNDS_SAFETY_LLDB_PYTHON_PATH}.\n"
"This typically means Python and LLDB were built for different Python "
"versions. Either:\n"
" - Set Python3_EXECUTABLE to the Python that matches LLDB "
"(e.g. the one bundled with Xcode), or\n"
" - Set COMPILER_RT_BOUNDS_SAFETY_USE_LLDB=OFF to disable debugger "
"test suites.\n"
"Import error: ${_LLDB_IMPORT_ERROR}")
endif()

if (COMPILER_RT_BOUNDS_SAFETY_USE_LLDB)
message(STATUS "Enabled -fbounds-safety tests using LLDB: ${BOUNDS_SAFETY_LLDB_PYTHON_PATH}")
else()
message(STATUS "Disabled -fbounds-safety tests using LLDB")
endif()

set(BOUNDS_SAFETY_OPT_LEVELS unopt opt)
set(BOUNDS_SAFETY_TRAP_KINDS unique-traps merged-traps soft-traps)
set(BOUNDS_SAFETY_RUN_MODES direct)
if(COMPILER_RT_BOUNDS_SAFETY_USE_LLDB)
list(APPEND BOUNDS_SAFETY_RUN_MODES debugger)
endif()


list(FIND SANITIZER_COMMON_SUPPORTED_OS "osx" OSX_INDEX)
if (${OSX_INDEX} EQUAL -1)
message(FATAL_ERROR "Support for osx is missing")
endif()

# TODO: Add support for other platforms
set(BOUNDS_SAFETY_APPLE_PLATFORMS osx)

foreach(platform ${BOUNDS_SAFETY_APPLE_PLATFORMS})
# Determine archs for this platform
if("${platform}" STREQUAL "osx")
# For osx, filter to host-compatible archs
set(PLATFORM_ARCHS ${DARWIN_osx_ARCHS})
darwin_filter_host_archs(PLATFORM_ARCHS PLATFORM_ARCHS)
set(BOUNDS_SAFETY_TEST_APPLE_TARGET_IS_HOST ON)
else()
# For other platforms, use all available archs
set(PLATFORM_ARCHS ${DARWIN_${platform}_ARCHS})
set(BOUNDS_SAFETY_TEST_APPLE_TARGET_IS_HOST OFF)
endif()
pythonize_bool(BOUNDS_SAFETY_TEST_APPLE_TARGET_IS_HOST)

set(BOUNDS_SAFETY_TEST_APPLE_PLATFORM "${platform}")
set(BOUNDS_SAFETY_TEST_MIN_DEPLOYMENT_TARGET_FLAG
"${DARWIN_${platform}_MIN_VER_FLAG}")

foreach(arch ${PLATFORM_ARCHS})
get_test_cflags_for_apple_platform(
"${platform}" "${arch}" BOUNDS_SAFETY_TEST_TARGET_CFLAGS)

foreach(opt_level ${BOUNDS_SAFETY_OPT_LEVELS})
# Determine which trap kinds apply for this opt level.
# At -O0 merged traps are identical to unique traps, so skip.
if("${opt_level}" STREQUAL "unopt")
set(ACTIVE_TRAP_KINDS unique-traps soft-traps)
else()
set(ACTIVE_TRAP_KINDS ${BOUNDS_SAFETY_TRAP_KINDS})
endif()

foreach(trap_kind ${ACTIVE_TRAP_KINDS})
set(BOUNDS_SAFETY_TEST_TRAP_KIND "${trap_kind}")

foreach(mode ${BOUNDS_SAFETY_RUN_MODES})
set(BOUNDS_SAFETY_TEST_TARGET_ARCH ${arch})

if("${platform}" STREQUAL "osx")
set(CONFIG_NAME "host")
else()
set(CONFIG_NAME "remote")
endif()
string(APPEND CONFIG_NAME "-${arch}-apple-${platform}--${opt_level}--${trap_kind}--${mode}")

if("${opt_level}" STREQUAL "unopt")
set(BOUNDS_SAFETY_TEST_OPTIMIZED FALSE)
else()
set(BOUNDS_SAFETY_TEST_OPTIMIZED TRUE)
endif()
pythonize_bool(BOUNDS_SAFETY_TEST_OPTIMIZED)

if("${mode}" STREQUAL "debugger")
set(BOUNDS_SAFETY_TEST_USE_DEBUGGER TRUE)
else()
set(BOUNDS_SAFETY_TEST_USE_DEBUGGER FALSE)
endif()
pythonize_bool(BOUNDS_SAFETY_TEST_USE_DEBUGGER)

configure_compiler_rt_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py
)

if("${platform}" STREQUAL "osx")
# Host tests: accumulate into main check-bounds-safety target
list(APPEND BOUNDS_SAFETY_HOST_TEST_SUITES
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
else()
# Non-host: individual targets, excluded from check-all
add_lit_testsuite(
check-bounds-safety-remote-${platform}-${arch}-${opt_level}-${trap_kind}-${mode}
"bounds-safety remote ${platform} ${arch} ${opt_level} ${trap_kind} ${mode} tests"
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}
EXCLUDE_FROM_CHECK_ALL
DEPENDS ${BOUNDS_SAFETY_TEST_DEPS})
endif()
endforeach()
endforeach()
endforeach()
endforeach()
endforeach()

add_lit_testsuite(check-bounds-safety
"Running the bounds-safety runtime tests"
${BOUNDS_SAFETY_HOST_TEST_SUITES}
DEPENDS ${BOUNDS_SAFETY_TEST_DEPS})
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// RUN: %clang_bsafe %s -o %t
// RUN: %expect-no-trap %t
// RUN: %expect-trap --verify-prefix=lower-trap %s %t arg1
// RUN: %expect-trap --verify-prefix=upper-trap %s %t arg1 arg2
#include <ptrcheck.h>
#include <stdio.h>
#include "soft_trap_runtime_impl.h"

// lower-trap-merged{bad_read}
// upper-trap-merged{bad_read}
int bad_read(int *__bidi_indexable ptr, int idx) {
// lower-trap@+2{indexing below lower bound in 'ptr[idx]'}
// upper-trap@+1{indexing above upper bound in 'ptr[idx]'}
return ptr[idx];
}

int main(int argc, const char **__counted_by(argc) argv) {
int pad;
int local[] = {0, 1};
int pad2;
int result = 0;
if (argc == 1) {
result = bad_read(local, 1);
} else if (argc == 2) {
result = bad_read(local, -1);
} else {
result = bad_read(local, 2);
}
printf("result: %d\n", result);
return 0;
}
88 changes: 88 additions & 0 deletions compiler-rt/test/bounds_safety/lit.cfg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# -*- Python -*-

import os
import shlex

import lit.formats


config.name = "BoundsSafety :: " + config.name_suffix

config.suffixes = [".c"]

config.test_source_root = os.path.dirname(__file__)
config.test_exec_root = os.path.join(
config.compiler_rt_obj_root, "test", "bounds-safety",
config.name_suffix
)

# Build the %clang_bsafe substitution.
clang_bsafe_flags = [
config.clang,
"-fbounds-safety",
"-g",
"-O2" if config.optimized else "-O0",
config.target_cflags,
]
if config.trap_kind == "unique-traps":
clang_bsafe_flags.append("-fbounds-safety-unique-traps")
elif config.trap_kind == "merged-traps":
clang_bsafe_flags.append("-fno-bounds-safety-unique-traps")
assert config.optimized
elif config.trap_kind == "soft-traps":
clang_bsafe_flags.append("-fbounds-safety-soft-traps=call-minimal")

# Add utils/ to include path for soft_trap_runtime_impl.h.
utils_dir = os.path.join(os.path.dirname(__file__), "utils")
clang_bsafe_flags.append("-I" + utils_dir)

# Expose configuration properties as preprocessor macros for test cases.
clang_bsafe_flags.append("-DTEST_OPTIMIZED={}".format(int(config.optimized)))
clang_bsafe_flags.append("-DTEST_UNIQUE_TRAPS={}".format(
int(config.trap_kind != "merged-traps")))
clang_bsafe_flags.append("-DTEST_USE_DEBUGGER={}".format(int(config.use_debugger)))
clang_bsafe_flags.append("-DTEST_SOFT_TRAP={}".format(
int(config.trap_kind == "soft-traps")))

config.substitutions.append(
("%clang_bsafe", " " + " ".join(clang_bsafe_flags) + " ")
)

# Build %expect-trap and %expect-no-trap substitutions.
python_exec = shlex.quote(config.python_executable)
scripts_dir = os.path.join(os.path.dirname(__file__), "scripts")
expect_trap_script = os.path.join(scripts_dir, "expect_trap.py")
expect_no_trap_script = os.path.join(scripts_dir, "expect_no_trap.py")

debugger_flags = ""
if config.use_debugger:
debugger_flags = " --use-debugger --lldb-python-path {}".format(
shlex.quote(config.lldb_python_path)
)

merged_trap_flag = " --merged-traps" if config.trap_kind == "merged-traps" else ""
soft_trap_flag = " --soft-traps" if config.trap_kind == "soft-traps" else ""

config.substitutions.append(
("%expect-trap", "{} {}{}{}{}".format(
python_exec, expect_trap_script, debugger_flags, merged_trap_flag,
soft_trap_flag))
)
config.substitutions.append(
(
"%expect-no-trap",
"{} {}{}{}".format(python_exec, expect_no_trap_script, debugger_flags,
soft_trap_flag),
)
)

# Add features for REQUIRES/UNSUPPORTED lines.
if config.use_debugger:
config.available_features.add("run-under-debugger")
else:
config.available_features.add("run-directly")
if config.optimized:
config.available_features.add("optimized")
else:
config.available_features.add("unoptimized")
config.available_features.add(config.trap_kind)
34 changes: 34 additions & 0 deletions compiler-rt/test/bounds_safety/lit.site.cfg.py.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@LIT_SITE_CFG_IN_HEADER@

config.name_suffix = "@CONFIG_NAME@"
config.target_cflags = "@BOUNDS_SAFETY_TEST_TARGET_CFLAGS@"
config.target_arch = "@BOUNDS_SAFETY_TEST_TARGET_ARCH@"
config.optimized = @BOUNDS_SAFETY_TEST_OPTIMIZED_PYBOOL@
config.use_debugger = @BOUNDS_SAFETY_TEST_USE_DEBUGGER_PYBOOL@
config.trap_kind = "@BOUNDS_SAFETY_TEST_TRAP_KIND@"
config.lldb_python_path = "@BOUNDS_SAFETY_LLDB_PYTHON_PATH@"
config.apple_platform = "@BOUNDS_SAFETY_TEST_APPLE_PLATFORM@"
config.apple_platform_min_deployment_target_flag = "@BOUNDS_SAFETY_TEST_MIN_DEPLOYMENT_TARGET_FLAG@"
config.apple_target_is_host = @BOUNDS_SAFETY_TEST_APPLE_TARGET_IS_HOST_PYBOOL@

# FIXME: Commented out because the performance is terrible due to the lit files
# doing lots of work that should not be done at test time (spawning
# processes for things that won't change). There are lots of variants of this
# test suite and there's no caching so this work gets done for each test suite.
# We should fix this so we can take advantage of the infrastructure
# for running outside of the host machine.
#lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured")

# HACK: These should come from loading
# "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured"
# but we've commented out loading it so we have to manually set these to get
# things to work.
config.compiler_rt_obj_root = "@COMPILER_RT_BINARY_DIR@"
config.clang = "@COMPILER_RT_RESOLVED_TEST_COMPILER@"
config.python_executable = "@Python3_EXECUTABLE@"
# Setup test format.
import lit
config.test_format = lit.formats.ShTest(execute_external=False)

# Consume this configuration and setup subscriptions, features, etc.
lit_config.load_config(config, "@BOUNDS_SAFETY_LIT_SOURCE_DIR@/lit.cfg.py")
Loading