91 changes: 91 additions & 0 deletions compiler-rt/lib/ubsan_minimal/ubsan_minimal_handlers.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#include <atomic>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void message(const char *msg) {
write(2, msg, strlen(msg));
}

static const int kMaxCallerPcs = 20;
static std::atomic<void *> caller_pcs[kMaxCallerPcs];
// Number of elements in caller_pcs. A special value of kMaxCallerPcs + 1 means
// that "too many errors" has already been reported.
static std::atomic<int> caller_pcs_sz;

__attribute__((noinline))
static bool report_this_error(void *caller) {
if (caller == nullptr) return false;
while (true) {
int sz = caller_pcs_sz.load(std::memory_order_relaxed);
if (sz > kMaxCallerPcs) return false; // early exit
// when sz==kMaxCallerPcs print "too many errors", but only when cmpxchg
// succeeds in order to not print it multiple times.
if (sz > 0 && sz < kMaxCallerPcs) {
void *p;
for (int i = 0; i < sz; ++i) {
p = caller_pcs[i].load(std::memory_order_relaxed);
if (p == nullptr) break; // Concurrent update.
if (p == caller) return false;
}
if (p == nullptr) continue; // FIXME: yield?
}

if (!caller_pcs_sz.compare_exchange_strong(sz, sz + 1))
continue; // Concurrent update! Try again from the start.

if (sz == kMaxCallerPcs) {
message("ubsan: too many errors\n");
return false;
}
caller_pcs[sz].store(caller, std::memory_order_relaxed);
return true;
}
}

#if defined(__ANDROID__)
extern "C" void android_set_abort_message(const char *msg);
static void abort_with_message(const char *msg) {
#if __ANDROID_API__ >= 21
android_set_abort_message(msg);
#endif
abort();
}
#else
static void abort_with_message(const char *) { abort(); }
#endif

#define INTERFACE extern "C" __attribute__((visibility("default")))

// FIXME: add caller pc to the error message (possibly as "ubsan: error-type
// @1234ABCD").
#define HANDLER(name, msg) \
INTERFACE void __ubsan_handle_##name##_minimal() { \
if (!report_this_error(__builtin_return_address(0))) return; \
message("ubsan: " msg "\n"); \
} \
\
INTERFACE void __ubsan_handle_##name##_minimal_abort() { \
message("ubsan: " msg "\n"); \
abort_with_message("ubsan: " msg); \
}

HANDLER(type_mismatch, "type-mismatch")
HANDLER(add_overflow, "add-overflow")
HANDLER(sub_overflow, "sub-overflow")
HANDLER(mul_overflow, "mul-overflow")
HANDLER(negate_overflow, "negate-overflow")
HANDLER(divrem_overflow, "divrem-overflow")
HANDLER(shift_out_of_bounds, "shift-out-of-bounds")
HANDLER(out_of_bounds, "out-of-bounds")
HANDLER(builtin_unreachable, "builtin-unreachable")
HANDLER(missing_return, "missing-return")
HANDLER(vla_bound_not_positive, "vla-bound-not-positive")
HANDLER(float_cast_overflow, "float-cast-overflow")
HANDLER(load_invalid_value, "load-invalid-value")
HANDLER(invalid_builtin, "invalid-builtin")
HANDLER(function_type_mismatch, "function-type-mismatch")
HANDLER(nonnull_arg, "nonnull-arg")
HANDLER(nullability_arg, "nullability-arg")
HANDLER(pointer_overflow, "pointer-overflow")
HANDLER(cfi_check_fail, "cfi-check-fail")
23 changes: 23 additions & 0 deletions compiler-rt/test/ubsan_minimal/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
set(UBSAN_LIT_TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR})

set(UBSAN_TEST_ARCH ${UBSAN_SUPPORTED_ARCH})

set(UBSAN_TESTSUITES)
set(UBSAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
if(NOT COMPILER_RT_STANDALONE_BUILD)
list(APPEND UBSAN_TEST_DEPS ubsan-minimal)
endif()

foreach(arch ${UBSAN_TEST_ARCH})
get_test_cc_for_arch(${arch} UBSAN_TEST_TARGET_CC UBSAN_TEST_TARGET_CFLAGS)
set(CONFIG_NAME ${arch})
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg)
list(APPEND UBSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
endforeach()

add_lit_testsuite(check-ubsan-minimal "Running UndefinedBehaviorSanitizerMinimal tests"
${UBSAN_TESTSUITES}
DEPENDS ${UBSAN_TEST_DEPS})
set_target_properties(check-ubsan-minimal PROPERTIES FOLDER "Compiler-RT Misc")
41 changes: 41 additions & 0 deletions compiler-rt/test/ubsan_minimal/TestCases/recover-dedup-limit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// RUN: %clangxx -fsanitize=signed-integer-overflow -fsanitize-recover=all %s -o %t && %run %t 2>&1 | FileCheck %s

#include <stdint.h>

#define OVERFLOW \
x = 0x7FFFFFFE; \
x += __LINE__

int main() {
int32_t x;
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow

OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow

OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow

OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow
OVERFLOW; // CHECK: add-overflow

// CHECK-NOT: add-overflow
OVERFLOW; // CHECK: too many errors
// CHECK-NOT: add-overflow
OVERFLOW;
OVERFLOW;
OVERFLOW;
}
25 changes: 25 additions & 0 deletions compiler-rt/test/ubsan_minimal/TestCases/recover-dedup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: %clangxx -fsanitize=signed-integer-overflow -fsanitize-recover=all %s -o %t && %run %t 2>&1 | FileCheck %s

#include <stdint.h>

__attribute__((noinline))
int f(int x, int y) {
// CHECK: mul-overflow
return x * y;
}

__attribute__((noinline))
int g(int x, int y) {
// CHECK: mul-overflow
return x * (y + 1);
}

int main() {
int x = 2;
for (int i = 0; i < 10; ++i)
x = f(x, x);
x = 2;
for (int i = 0; i < 10; ++i)
x = g(x, x);
// CHECK-NOT: mul-overflow
}
10 changes: 10 additions & 0 deletions compiler-rt/test/ubsan_minimal/TestCases/uadd-overflow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// RUN: %clangxx -fsanitize=unsigned-integer-overflow %s -o %t && %run %t 2>&1 | FileCheck %s
// RUN: %clangxx -fsanitize=unsigned-integer-overflow -fno-sanitize-recover=all %s -o %t && not --crash %run %t 2>&1 | FileCheck %s

#include <stdint.h>

int main() {
uint32_t k = 0x87654321;
k += 0xedcba987;
// CHECK: add-overflow
}
35 changes: 35 additions & 0 deletions compiler-rt/test/ubsan_minimal/lit.common.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- Python -*-

import os

def get_required_attr(config, attr_name):
attr_value = getattr(config, attr_name, None)
if attr_value == None:
lit_config.fatal(
"No attribute %r in test configuration! You may need to run "
"tests from your build directory or add this attribute "
"to lit.site.cfg " % attr_name)
return attr_value

# Setup source root.
config.test_source_root = os.path.dirname(__file__)

def build_invocation(compile_flags):
return " " + " ".join([config.clang] + compile_flags) + " "

target_cflags = [get_required_attr(config, "target_cflags")]
clang_ubsan_cflags = ["-fsanitize-minimal-runtime"] + target_cflags
clang_ubsan_cxxflags = config.cxx_mode_flags + clang_ubsan_cflags

# Define %clang and %clangxx substitutions to use in test RUN lines.
config.substitutions.append( ("%clang ", build_invocation(clang_ubsan_cflags)) )
config.substitutions.append( ("%clangxx ", build_invocation(clang_ubsan_cxxflags)) )

# Default test suffixes.
config.suffixes = ['.c', '.cc', '.cpp']

# Check that the host supports UndefinedBehaviorSanitizerMinimal tests
if config.host_os not in ['Darwin', 'Linux', 'FreeBSD', 'NetBSD']: # TODO: Windows
config.unsupported = True

config.available_features.add('arch=' + config.target_arch)
11 changes: 11 additions & 0 deletions compiler-rt/test/ubsan_minimal/lit.site.cfg.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@LIT_SITE_CFG_IN_HEADER@

# Tool-specific config options.
config.target_cflags = "@UBSAN_TEST_TARGET_CFLAGS@"
config.target_arch = "@UBSAN_TEST_TARGET_ARCH@"

# Load common config for all compiler-rt lit tests.
lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured")

# Load tool-specific config that would do the real work.
lit_config.load_config(config, "@UBSAN_LIT_TESTS_DIR@/lit.common.cfg")