[clang] [compiler-rt] [sanitizer] Add DoubleFreeSanitizer (DSan) (PR #213846)
Bojun Seo via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 10 04:47:07 PDT 2026
https://github.com/Bojun-Seo updated https://github.com/llvm/llvm-project/pull/213846
>From 1fabd6e50165171024d5689ff06284abc9e71b08 Mon Sep 17 00:00:00 2001
From: "bojun.seo" <bojun.seo at lge.com>
Date: Tue, 28 Apr 2026 15:13:19 +0900
Subject: [PATCH 1/5] [sanitizer] Add DoubleFreeSanitizer (DSan)
Add DoubleFreeSanitizer (DSan), a standalone runtime sanitizer
that detects double-free errors.
DSan intercepts allocation and free APIs and records per-allocation
state together with allocation and free stack traces. When an
allocation is freed again, it reports the invalid second free, the
first free, and the original allocation.
Unlike AddressSanitizer, DSan does not require compiler
instrumentation or shadow memory. This provides a focused option for
resource-constrained environments, including embedded devices, where
running ASan may not be feasible.
DSan is enabled with -fsanitize=doublefree.
RFC: DoubleFreeSanitizer proposal on Discourse (#91363)
Assisted-by: GPT-5.6 Sol
---
clang/include/clang/Basic/Sanitizers.def | 3 +
clang/include/clang/Driver/SanitizerArgs.h | 5 +
clang/lib/Driver/SanitizerArgs.cpp | 6 +-
clang/lib/Driver/ToolChains/CommonArgs.cpp | 2 +
clang/lib/Driver/ToolChains/Darwin.cpp | 3 +
clang/lib/Driver/ToolChains/Fuchsia.cpp | 1 +
clang/lib/Driver/ToolChains/Linux.cpp | 3 +
clang/lib/Driver/ToolChains/NetBSD.cpp | 1 +
clang/test/Driver/fsanitize-doublefree.c | 21 +
.../cmake/Modules/AllSupportedArchDefs.cmake | 6 +
compiler-rt/cmake/config-ix.cmake | 17 +
compiler-rt/include/CMakeLists.txt | 1 +
.../include/sanitizer/dsan_interface.h | 30 +
compiler-rt/lib/CMakeLists.txt | 2 +
compiler-rt/lib/dsan/.clang-format | 3 +
compiler-rt/lib/dsan/CMakeLists.txt | 85 +++
compiler-rt/lib/dsan/dsan.cpp | 112 ++++
compiler-rt/lib/dsan/dsan.h | 56 ++
compiler-rt/lib/dsan/dsan_allocator.cpp | 501 +++++++++++++++
compiler-rt/lib/dsan/dsan_allocator.h | 150 +++++
compiler-rt/lib/dsan/dsan_common.cpp | 31 +
compiler-rt/lib/dsan/dsan_common.h | 103 ++++
compiler-rt/lib/dsan/dsan_fuchsia.cpp | 131 ++++
compiler-rt/lib/dsan/dsan_fuchsia.h | 35 ++
compiler-rt/lib/dsan/dsan_interceptors.cpp | 575 ++++++++++++++++++
compiler-rt/lib/dsan/dsan_linux.cpp | 34 ++
compiler-rt/lib/dsan/dsan_mac.cpp | 234 +++++++
compiler-rt/lib/dsan/dsan_malloc_mac.cpp | 66 ++
compiler-rt/lib/dsan/dsan_posix.cpp | 121 ++++
compiler-rt/lib/dsan/dsan_posix.h | 49 ++
compiler-rt/lib/dsan/dsan_preinit.cpp | 21 +
compiler-rt/lib/dsan/dsan_thread.cpp | 123 ++++
compiler-rt/lib/dsan/dsan_thread.h | 66 ++
compiler-rt/lib/dsan/weak_symbols.txt | 1 +
compiler-rt/test/CMakeLists.txt | 3 +-
compiler-rt/test/dsan/CMakeLists.txt | 28 +
.../dsan/TestCases/concurrent-double-free.cpp | 28 +
compiler-rt/test/dsan/TestCases/double-free.c | 17 +
.../test/dsan/TestCases/invalid-free.c | 12 +
.../test/dsan/TestCases/large-double-free.c | 15 +
compiler-rt/test/dsan/TestCases/realloc.c | 16 +
.../test/dsan/TestCases/reallocarray.c | 18 +
compiler-rt/test/dsan/TestCases/smoke.cpp | 10 +
compiler-rt/test/dsan/lit.common.cfg.py | 114 ++++
compiler-rt/test/dsan/lit.site.cfg.py.in | 13 +
45 files changed, 2870 insertions(+), 2 deletions(-)
create mode 100644 clang/test/Driver/fsanitize-doublefree.c
create mode 100644 compiler-rt/include/sanitizer/dsan_interface.h
create mode 100644 compiler-rt/lib/dsan/.clang-format
create mode 100644 compiler-rt/lib/dsan/CMakeLists.txt
create mode 100644 compiler-rt/lib/dsan/dsan.cpp
create mode 100644 compiler-rt/lib/dsan/dsan.h
create mode 100644 compiler-rt/lib/dsan/dsan_allocator.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_allocator.h
create mode 100644 compiler-rt/lib/dsan/dsan_common.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_common.h
create mode 100644 compiler-rt/lib/dsan/dsan_fuchsia.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_fuchsia.h
create mode 100644 compiler-rt/lib/dsan/dsan_interceptors.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_linux.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_mac.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_malloc_mac.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_posix.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_posix.h
create mode 100644 compiler-rt/lib/dsan/dsan_preinit.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_thread.cpp
create mode 100644 compiler-rt/lib/dsan/dsan_thread.h
create mode 100644 compiler-rt/lib/dsan/weak_symbols.txt
create mode 100644 compiler-rt/test/dsan/CMakeLists.txt
create mode 100644 compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp
create mode 100644 compiler-rt/test/dsan/TestCases/double-free.c
create mode 100644 compiler-rt/test/dsan/TestCases/invalid-free.c
create mode 100644 compiler-rt/test/dsan/TestCases/large-double-free.c
create mode 100644 compiler-rt/test/dsan/TestCases/realloc.c
create mode 100644 compiler-rt/test/dsan/TestCases/reallocarray.c
create mode 100644 compiler-rt/test/dsan/TestCases/smoke.cpp
create mode 100644 compiler-rt/test/dsan/lit.common.cfg.py
create mode 100644 compiler-rt/test/dsan/lit.site.cfg.py.in
diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def
index da85431625026..137833fa40b1a 100644
--- a/clang/include/clang/Basic/Sanitizers.def
+++ b/clang/include/clang/Basic/Sanitizers.def
@@ -198,6 +198,9 @@ SANITIZER("scudo", Scudo)
// AllocToken
SANITIZER("alloc-token", AllocToken)
+// DoubleFreeSanitizer
+SANITIZER("doublefree", DoubleFree)
+
// Magic group, containing all sanitizers. For example, "-fno-sanitize=all"
// can be used to disable all the sanitizers.
SANITIZER_GROUP("all", All, ~SanitizerMask())
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 6a01b3e36d44c..6e774c5ef8c77 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -111,6 +111,11 @@ class SanitizerArgs {
!Sanitizers.has(SanitizerKind::Address) &&
!Sanitizers.has(SanitizerKind::HWAddress);
}
+ bool needsDsanRt() const {
+ return Sanitizers.has(SanitizerKind::DoubleFree) &&
+ !Sanitizers.has(SanitizerKind::Address) &&
+ !Sanitizers.has(SanitizerKind::HWAddress);
+ }
bool needsFuzzerInterceptors() const;
bool needsUbsanRt() const;
bool needsUbsanCXXRt() const;
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index c77ba78122a81..c1c113d91274a 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -48,7 +48,8 @@ static const SanitizerMask SupportsCoverage =
SanitizerKind::Type | SanitizerKind::MemtagStack |
SanitizerKind::MemtagHeap | SanitizerKind::MemtagGlobals |
SanitizerKind::Memory | SanitizerKind::KernelMemory | SanitizerKind::Leak |
- SanitizerKind::Undefined | SanitizerKind::Integer | SanitizerKind::Bounds |
+ SanitizerKind::DoubleFree | SanitizerKind::Undefined |
+ SanitizerKind::Integer | SanitizerKind::Bounds |
SanitizerKind::ImplicitConversion | SanitizerKind::Nullability |
SanitizerKind::DataFlow | SanitizerKind::Fuzzer |
SanitizerKind::FuzzerNoLink | SanitizerKind::FloatDivideByZero |
@@ -709,6 +710,9 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
std::make_pair(SanitizerKind::Thread, SanitizerKind::Memory),
std::make_pair(SanitizerKind::Leak,
SanitizerKind::Thread | SanitizerKind::Memory),
+ std::make_pair(SanitizerKind::DoubleFree,
+ SanitizerKind::Leak | SanitizerKind::Thread |
+ SanitizerKind::Memory | SanitizerKind::Scudo),
std::make_pair(SanitizerKind::KernelAddress,
SanitizerKind::Address | SanitizerKind::Leak |
SanitizerKind::Thread | SanitizerKind::Memory),
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 883296e43111b..019880ca24253 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1747,6 +1747,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
StaticRuntimes.push_back("dfsan");
if (SanArgs.needsLsanRt())
StaticRuntimes.push_back("lsan");
+ if (SanArgs.needsDsanRt())
+ StaticRuntimes.push_back("dsan");
if (SanArgs.needsMsanRt()) {
StaticRuntimes.push_back("msan");
if (SanArgs.linkCXXRuntimes())
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index d3de04fc5155e..a491e0cf3a74f 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -1760,6 +1760,8 @@ void DarwinClang::AddLinkRuntimeLibArgs(const ArgList &Args,
}
if (Sanitize.needsLsanRt())
AddLinkSanitizerLibArgs(Args, CmdArgs, "lsan");
+ if (Sanitize.needsDsanRt())
+ AddLinkSanitizerLibArgs(Args, CmdArgs, "dsan");
if (Sanitize.needsUbsanRt()) {
assert(Sanitize.needsSharedRt() &&
"Static sanitizer runtimes not supported");
@@ -4064,6 +4066,7 @@ Darwin::getSupportedSanitizers(BoundArch BA,
Res |= SanitizerKind::PointerSubtract;
Res |= SanitizerKind::Realtime;
Res |= SanitizerKind::Leak;
+ Res |= SanitizerKind::DoubleFree;
Res |= SanitizerKind::Fuzzer;
Res |= SanitizerKind::FuzzerNoLink;
Res |= SanitizerKind::ObjCCast;
diff --git a/clang/lib/Driver/ToolChains/Fuchsia.cpp b/clang/lib/Driver/ToolChains/Fuchsia.cpp
index abde9fa10482d..36cadd7db9bf5 100644
--- a/clang/lib/Driver/ToolChains/Fuchsia.cpp
+++ b/clang/lib/Driver/ToolChains/Fuchsia.cpp
@@ -483,6 +483,7 @@ Fuchsia::getSupportedSanitizers(BoundArch BA,
Res |= SanitizerKind::Fuzzer;
Res |= SanitizerKind::FuzzerNoLink;
Res |= SanitizerKind::Leak;
+ Res |= SanitizerKind::DoubleFree;
Res |= SanitizerKind::Scudo;
Res |= SanitizerKind::Thread;
if (getTriple().getArch() == llvm::Triple::x86_64 ||
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 1ab385a9ea001..486d22e16145a 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -997,6 +997,9 @@ Linux::getSupportedSanitizers(BoundArch BA,
if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 ||
IsRISCV64 || IsSystemZ || IsHexagon || IsLoongArch64)
Res |= SanitizerKind::Leak;
+ if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 ||
+ IsRISCV64 || IsSystemZ || IsHexagon || IsLoongArch64)
+ Res |= SanitizerKind::DoubleFree;
if (IsX86_64 || IsMIPS64 || IsAArch64 || IsPowerPC64 || IsSystemZ ||
IsLoongArch64 || IsRISCV64)
Res |= SanitizerKind::Thread;
diff --git a/clang/lib/Driver/ToolChains/NetBSD.cpp b/clang/lib/Driver/ToolChains/NetBSD.cpp
index f03114b53bb61..dab65e9b46995 100644
--- a/clang/lib/Driver/ToolChains/NetBSD.cpp
+++ b/clang/lib/Driver/ToolChains/NetBSD.cpp
@@ -521,6 +521,7 @@ NetBSD::getSupportedSanitizers(BoundArch BA,
Res |= SanitizerKind::PointerCompare;
Res |= SanitizerKind::PointerSubtract;
Res |= SanitizerKind::Leak;
+ Res |= SanitizerKind::DoubleFree;
Res |= SanitizerKind::SafeStack;
Res |= SanitizerKind::Scudo;
Res |= SanitizerKind::Vptr;
diff --git a/clang/test/Driver/fsanitize-doublefree.c b/clang/test/Driver/fsanitize-doublefree.c
new file mode 100644
index 0000000000000..f8a7bf26c0994
--- /dev/null
+++ b/clang/test/Driver/fsanitize-doublefree.c
@@ -0,0 +1,21 @@
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize=doublefree %s -### 2>&1 | FileCheck %s --check-prefix=DSAN
+// DSAN: "-fsanitize=doublefree"
+// DSAN: libclang_rt.dsan
+
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize=doublefree,undefined %s -### 2>&1 | FileCheck %s --check-prefix=DSAN-UBSAN
+// DSAN-UBSAN: libclang_rt.dsan
+// DSAN-UBSAN: libclang_rt.ubsan_standalone
+
+// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=doublefree,leak %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=DSAN-LEAK
+// DSAN-LEAK: '-fsanitize=doublefree' not allowed with '-fsanitize=leak'
+
+// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=doublefree,scudo %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=DSAN-SCUDO
+// DSAN-SCUDO: '-fsanitize=doublefree' not allowed with '-fsanitize=scudo'
+
+// RUN: not %clang --target=x86_64-unknown-freebsd -fsanitize=doublefree %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=FREEBSD
+// FREEBSD: unsupported option '-fsanitize=doublefree' for target 'x86_64-unknown-freebsd'
+
+// RUN: not %clang --target=wasm32-unknown-emscripten -fsanitize=doublefree %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=EMSCRIPTEN
+// EMSCRIPTEN: unsupported option '-fsanitize=doublefree' for target 'wasm32-unknown-emscripten'
+
+int main(void) { return 0; }
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index 9c9874d94a1f2..fffd2c69f03fb 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -85,6 +85,12 @@ else()
set(ALL_LSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64} ${ARM32}
${PPC64} ${S390X} ${RISCV64} ${HEXAGON} ${LOONGARCH64})
endif()
+if(APPLE)
+ set(ALL_DSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64})
+else()
+ set(ALL_DSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64} ${ARM32}
+ ${PPC64} ${S390X} ${RISCV64} ${HEXAGON} ${LOONGARCH64})
+endif()
if (OS_NAME MATCHES "FreeBSD")
set(ALL_MSAN_SUPPORTED_ARCH ${X86_64} ${ARM64})
else()
diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake
index 083f1c98d0f16..a36fe8f2d27a0 100644
--- a/compiler-rt/cmake/config-ix.cmake
+++ b/compiler-rt/cmake/config-ix.cmake
@@ -482,6 +482,7 @@ if(APPLE)
set(ORC_SUPPORTED_OS)
set(UBSAN_SUPPORTED_OS osx)
set(LSAN_SUPPORTED_OS osx)
+ set(DSAN_SUPPORTED_OS osx)
set(STATS_SUPPORTED_OS osx)
# FIXME: Support a general COMPILER_RT_ENABLE_OSX to match other platforms.
@@ -579,6 +580,7 @@ if(APPLE)
list(APPEND ORC_SUPPORTED_OS ${platform}sim)
list(APPEND UBSAN_SUPPORTED_OS ${platform}sim)
list(APPEND LSAN_SUPPORTED_OS ${platform}sim)
+ list(APPEND DSAN_SUPPORTED_OS ${platform}sim)
list(APPEND STATS_SUPPORTED_OS ${platform}sim)
endif()
foreach(arch ${DARWIN_${platform}sim_ARCHS})
@@ -614,6 +616,7 @@ if(APPLE)
list(APPEND UBSAN_SUPPORTED_OS ${platform})
list(APPEND TYSAN_SUPPORTED_OS ${platform})
list(APPEND LSAN_SUPPORTED_OS ${platform})
+ list(APPEND DSAN_SUPPORTED_OS ${platform})
list(APPEND STATS_SUPPORTED_OS ${platform})
endif()
foreach(arch ${DARWIN_${platform}_ARCHS})
@@ -636,6 +639,7 @@ if(APPLE)
COMPILER_RT_SUPPORTED_ARCH
)
set(LSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH})
+ set(DSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH})
set(UBSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH})
set(ASAN_ABI_SUPPORTED_ARCH ${ALL_ASAN_ABI_SUPPORTED_ARCH})
list_intersect(ASAN_SUPPORTED_ARCH
@@ -653,6 +657,9 @@ if(APPLE)
list_intersect(LSAN_SUPPORTED_ARCH
ALL_LSAN_SUPPORTED_ARCH
SANITIZER_COMMON_SUPPORTED_ARCH)
+ list_intersect(DSAN_SUPPORTED_ARCH
+ ALL_DSAN_SUPPORTED_ARCH
+ SANITIZER_COMMON_SUPPORTED_ARCH)
list_intersect(MSAN_SUPPORTED_ARCH
ALL_MSAN_SUPPORTED_ARCH
SANITIZER_COMMON_SUPPORTED_ARCH)
@@ -713,6 +720,8 @@ else()
# supported by other sanitizers (even if they build into dummy object files).
filter_available_targets(LSAN_COMMON_SUPPORTED_ARCH
${SANITIZER_COMMON_SUPPORTED_ARCH})
+ filter_available_targets(DSAN_COMMON_SUPPORTED_ARCH
+ ${SANITIZER_COMMON_SUPPORTED_ARCH})
filter_available_targets(UBSAN_COMMON_SUPPORTED_ARCH
${ALL_UBSAN_SUPPORTED_ARCH})
filter_available_targets(ASAN_SUPPORTED_ARCH ${ALL_ASAN_SUPPORTED_ARCH})
@@ -720,6 +729,7 @@ else()
filter_available_targets(FUZZER_SUPPORTED_ARCH ${ALL_FUZZER_SUPPORTED_ARCH})
filter_available_targets(DFSAN_SUPPORTED_ARCH ${ALL_DFSAN_SUPPORTED_ARCH})
filter_available_targets(LSAN_SUPPORTED_ARCH ${ALL_LSAN_SUPPORTED_ARCH})
+ filter_available_targets(DSAN_SUPPORTED_ARCH ${ALL_DSAN_SUPPORTED_ARCH})
filter_available_targets(MSAN_SUPPORTED_ARCH ${ALL_MSAN_SUPPORTED_ARCH})
filter_available_targets(HWASAN_SUPPORTED_ARCH ${ALL_HWASAN_SUPPORTED_ARCH})
filter_available_targets(MEMPROF_SUPPORTED_ARCH ${ALL_MEMPROF_SUPPORTED_ARCH})
@@ -832,6 +842,13 @@ else()
set(COMPILER_RT_HAS_LSAN FALSE)
endif()
+if (COMPILER_RT_HAS_SANITIZER_COMMON AND DSAN_SUPPORTED_ARCH AND
+ OS_NAME MATCHES "Android|Darwin|Linux|NetBSD|Fuchsia")
+ set(COMPILER_RT_HAS_DSAN TRUE)
+else()
+ set(COMPILER_RT_HAS_DSAN FALSE)
+endif()
+
if (COMPILER_RT_HAS_SANITIZER_COMMON AND MSAN_SUPPORTED_ARCH AND
OS_NAME MATCHES "Linux|FreeBSD|NetBSD")
set(COMPILER_RT_HAS_MSAN TRUE)
diff --git a/compiler-rt/include/CMakeLists.txt b/compiler-rt/include/CMakeLists.txt
index eb998478b081b..1045a1836a9fb 100644
--- a/compiler-rt/include/CMakeLists.txt
+++ b/compiler-rt/include/CMakeLists.txt
@@ -5,6 +5,7 @@ if (COMPILER_RT_BUILD_SANITIZERS)
sanitizer/common_interface_defs.h
sanitizer/coverage_interface.h
sanitizer/dfsan_interface.h
+ sanitizer/dsan_interface.h
sanitizer/hwasan_interface.h
sanitizer/linux_syscall_hooks.h
sanitizer/lsan_interface.h
diff --git a/compiler-rt/include/sanitizer/dsan_interface.h b/compiler-rt/include/sanitizer/dsan_interface.h
new file mode 100644
index 0000000000000..a545b7206678e
--- /dev/null
+++ b/compiler-rt/include/sanitizer/dsan_interface.h
@@ -0,0 +1,30 @@
+//===-- sanitizer/dsan_interface.h ------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer (DSan).
+//
+// Public interface header.
+//===----------------------------------------------------------------------===//
+#ifndef SANITIZER_DSAN_INTERFACE_H
+#define SANITIZER_DSAN_INTERFACE_H
+
+#include <sanitizer/common_interface_defs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This function may be optionally provided by user and should return
+// a string containing common sanitizer runtime options.
+const char *SANITIZER_CDECL __dsan_default_options(void);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // SANITIZER_DSAN_INTERFACE_H
diff --git a/compiler-rt/lib/CMakeLists.txt b/compiler-rt/lib/CMakeLists.txt
index a5b2fbb38762c..5fe86db866c33 100644
--- a/compiler-rt/lib/CMakeLists.txt
+++ b/compiler-rt/lib/CMakeLists.txt
@@ -45,6 +45,8 @@ if(COMPILER_RT_BUILD_SANITIZERS)
add_subdirectory(stats)
# Contains RTLSanCommon used even without COMPILER_RT_HAS_LSAN.
add_subdirectory(lsan)
+ # Contains RTDSanCommon used even without COMPILER_RT_HAS_DSAN.
+ add_subdirectory(dsan)
# Contains RTUbsan used even without COMPILER_RT_HAS_UBSAN.
add_subdirectory(ubsan)
endif()
diff --git a/compiler-rt/lib/dsan/.clang-format b/compiler-rt/lib/dsan/.clang-format
new file mode 100644
index 0000000000000..1f2a97030379d
--- /dev/null
+++ b/compiler-rt/lib/dsan/.clang-format
@@ -0,0 +1,3 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: false
+IndentPPDirectives: AfterHash
diff --git a/compiler-rt/lib/dsan/CMakeLists.txt b/compiler-rt/lib/dsan/CMakeLists.txt
new file mode 100644
index 0000000000000..616c28bc6add1
--- /dev/null
+++ b/compiler-rt/lib/dsan/CMakeLists.txt
@@ -0,0 +1,85 @@
+include_directories(..)
+
+set(DSAN_CFLAGS ${SANITIZER_COMMON_CFLAGS})
+append_rtti_flag(OFF DSAN_CFLAGS)
+
+# Too many existing bugs, needs cleanup.
+append_list_if(COMPILER_RT_HAS_WNO_FORMAT -Wno-format DSAN_CFLAGS)
+
+set(DSAN_COMMON_SOURCES
+ dsan_common.cpp
+ )
+
+set(DSAN_SOURCES
+ dsan.cpp
+ dsan_allocator.cpp
+ dsan_fuchsia.cpp
+ dsan_interceptors.cpp
+ dsan_linux.cpp
+ dsan_mac.cpp
+ dsan_malloc_mac.cpp
+ dsan_posix.cpp
+ dsan_preinit.cpp
+ dsan_thread.cpp
+ )
+
+set(DSAN_HEADERS
+ dsan.h
+ dsan_allocator.h
+ dsan_common.h
+ dsan_thread.h
+ )
+
+set(DSAN_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+# Shared DSan runtime functionality.
+add_compiler_rt_object_libraries(RTDSanCommon
+ OS ${SANITIZER_COMMON_SUPPORTED_OS}
+ ARCHS ${DSAN_COMMON_SUPPORTED_ARCH}
+ SOURCES ${DSAN_COMMON_SOURCES}
+ ADDITIONAL_HEADERS ${DSAN_HEADERS}
+ CFLAGS ${DSAN_CFLAGS})
+
+if(COMPILER_RT_HAS_DSAN)
+ add_compiler_rt_component(dsan)
+ if(APPLE)
+ set(DSAN_LINK_LIBS ${SANITIZER_COMMON_LINK_LIBS})
+
+ add_weak_symbols("dsan" WEAK_SYMBOL_LINK_FLAGS)
+ add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS)
+
+ add_compiler_rt_runtime(clang_rt.dsan
+ SHARED
+ OS ${DSAN_SUPPORTED_OS}
+ ARCHS ${DSAN_SUPPORTED_ARCH}
+ SOURCES ${DSAN_SOURCES}
+ ADDITIONAL_HEADERS ${DSAN_HEADERS}
+ OBJECT_LIBS RTDSanCommon
+ RTInterception
+ RTSanitizerCommon
+ RTSanitizerCommonLibc
+ RTSanitizerCommonCoverage
+ RTSanitizerCommonSymbolizer
+ CFLAGS ${DSAN_CFLAGS}
+ LINK_FLAGS ${SANITIZER_COMMON_LINK_FLAGS} ${WEAK_SYMBOL_LINK_FLAGS}
+ LINK_LIBS ${DSAN_LINK_LIBS}
+ PARENT_TARGET dsan)
+ else()
+ foreach(arch ${DSAN_SUPPORTED_ARCH})
+ add_compiler_rt_runtime(clang_rt.dsan
+ STATIC
+ ARCHS ${arch}
+ SOURCES ${DSAN_SOURCES}
+ $<TARGET_OBJECTS:RTInterception.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommon.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonCoverage.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}>
+ $<TARGET_OBJECTS:RTSanitizerCommonSymbolizerInternal.${arch}>
+ $<TARGET_OBJECTS:RTDSanCommon.${arch}>
+ ADDITIONAL_HEADERS ${DSAN_HEADERS}
+ CFLAGS ${DSAN_CFLAGS}
+ PARENT_TARGET dsan)
+ endforeach()
+ endif()
+endif()
diff --git a/compiler-rt/lib/dsan/dsan.cpp b/compiler-rt/lib/dsan/dsan.cpp
new file mode 100644
index 0000000000000..6c134122b120e
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan.cpp
@@ -0,0 +1,112 @@
+//=-- dsan.cpp ------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Standalone DSan RTL.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan.h"
+
+#include "dsan_allocator.h"
+#include "dsan_common.h"
+#include "dsan_thread.h"
+#include "sanitizer_common/sanitizer_flag_parser.h"
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_interface_internal.h"
+
+bool dsan_inited;
+bool dsan_init_is_running;
+
+namespace __dsan {
+
+///// Interface to the common DSan module. /////
+bool WordIsPoisoned(uptr addr) { return false; }
+
+} // namespace __dsan
+
+void __sanitizer::BufferedStackTrace::UnwindImpl(uptr pc, uptr bp,
+ void* context,
+ bool request_fast,
+ u32 max_depth) {
+ using namespace __dsan;
+ uptr stack_top = 0, stack_bottom = 0;
+ if (ThreadContextDsanBase* t = GetCurrentThread()) {
+ stack_top = t->stack_end();
+ stack_bottom = t->stack_begin();
+ }
+ if (SANITIZER_MIPS && !IsValidFrame(bp, stack_top, stack_bottom))
+ return;
+ bool fast = StackTrace::WillUseFastUnwind(request_fast);
+ Unwind(max_depth, pc, bp, context, stack_top, stack_bottom, fast);
+}
+
+using namespace __dsan;
+
+static void InitializeFlags() {
+ // Set all the default values.
+ SetCommonFlagsDefaults();
+ {
+ CommonFlags cf;
+ cf.CopyFrom(*common_flags());
+ cf.external_symbolizer_path = GetEnv("DSAN_SYMBOLIZER_PATH");
+ cf.malloc_context_size = 30;
+ cf.intercept_tls_get_addr = true;
+ cf.detect_leaks = false;
+ cf.exitcode = 77;
+ OverrideCommonFlags(cf);
+ }
+
+ FlagParser parser;
+ RegisterCommonFlags(&parser);
+
+ // Override from user-specified string.
+ const char* dsan_default_options = __dsan_default_options();
+ parser.ParseString(dsan_default_options);
+ parser.ParseStringFromEnv("DSAN_OPTIONS");
+
+ InitializeCommonFlags();
+
+ if (Verbosity())
+ ReportUnrecognizedFlags();
+
+ if (common_flags()->help)
+ parser.PrintFlagDescriptions();
+
+ __sanitizer_set_report_path(common_flags()->log_path);
+}
+
+extern "C" void __dsan_init() {
+ CHECK(!dsan_init_is_running);
+ if (dsan_inited)
+ return;
+ dsan_init_is_running = true;
+ SanitizerToolName = "DoubleFreeSanitizer";
+ CacheBinaryName();
+ AvoidCVE_2016_2143();
+ InitializeFlags();
+ InitializePlatformEarly();
+ InitCommonDsan();
+ InitializeAllocator();
+ ReplaceSystemMalloc();
+ InitializeInterceptors();
+ InitializeThreads();
+ InstallDeadlySignalHandlers(DsanOnDeadlySignal);
+ InitializeMainThread();
+ InstallAtForkHandler();
+
+ InitializeCoverage(common_flags()->coverage, common_flags()->coverage_dir);
+
+ dsan_inited = true;
+ dsan_init_is_running = false;
+}
+
+extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_print_stack_trace() {
+ GET_STACK_TRACE_FATAL;
+ stack.Print();
+}
diff --git a/compiler-rt/lib/dsan/dsan.h b/compiler-rt/lib/dsan/dsan.h
new file mode 100644
index 0000000000000..bd7d1eb9c24fe
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan.h
@@ -0,0 +1,56 @@
+//=-- dsan.h --------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Private header for standalone DSan RTL.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan_thread.h"
+#if SANITIZER_POSIX
+# include "dsan_posix.h"
+#elif SANITIZER_FUCHSIA
+# include "dsan_fuchsia.h"
+#endif
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+#define GET_STACK_TRACE(max_size, fast) \
+ __sanitizer::BufferedStackTrace stack; \
+ stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, fast, \
+ max_size);
+
+#define GET_STACK_TRACE_FATAL \
+ GET_STACK_TRACE(kStackTraceMax, common_flags()->fast_unwind_on_fatal)
+
+#define GET_STACK_TRACE_MALLOC \
+ GET_STACK_TRACE(__sanitizer::common_flags()->malloc_context_size, \
+ common_flags()->fast_unwind_on_malloc)
+
+#define GET_STACK_TRACE_THREAD GET_STACK_TRACE(kStackTraceMax, true)
+
+namespace __dsan {
+
+void InitializeInterceptors();
+void ReplaceSystemMalloc();
+void DsanOnDeadlySignal(int signo, void* siginfo, void* context);
+void InstallAtForkHandler();
+
+#define ENSURE_DSAN_INITED \
+ do { \
+ CHECK(!dsan_init_is_running); \
+ if (!dsan_inited) \
+ __dsan_init(); \
+ } while (0)
+
+} // namespace __dsan
+
+extern bool dsan_inited;
+extern bool dsan_init_is_running;
+
+extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __dsan_init();
diff --git a/compiler-rt/lib/dsan/dsan_allocator.cpp b/compiler-rt/lib/dsan/dsan_allocator.cpp
new file mode 100644
index 0000000000000..471fb18415a03
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_allocator.cpp
@@ -0,0 +1,501 @@
+//=-- dsan_allocator.cpp --------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// See dsan_allocator.h for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan_allocator.h"
+
+#include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
+#include "sanitizer_common/sanitizer_allocator_interface.h"
+#include "sanitizer_common/sanitizer_allocator_report.h"
+#include "sanitizer_common/sanitizer_atomic.h"
+#include "sanitizer_common/sanitizer_errno.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_report_decorator.h"
+#include "sanitizer_common/sanitizer_stackdepot.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+extern "C" void* memset(void* ptr, int value, uptr num);
+
+namespace __dsan {
+#if defined(__i386__) || defined(__arm__)
+static const uptr kMaxAllowedMallocSize = 1ULL << 30;
+#elif defined(__mips64) || defined(__aarch64__)
+static const uptr kMaxAllowedMallocSize = 4ULL << 30;
+#else
+static const uptr kMaxAllowedMallocSize = 1ULL << 40;
+#endif
+
+static Allocator allocator;
+
+static uptr max_malloc_size;
+
+struct SecondaryTombstone {
+ void* ptr;
+ u32 alloc_stack_id;
+ u32 free_stack_id;
+};
+
+static constexpr uptr kSecondaryTombstoneLimit = 65536;
+static Mutex secondary_tombstones_mutex;
+static InternalMmapVector<SecondaryTombstone> secondary_tombstones;
+static uptr next_secondary_tombstone;
+
+static constexpr uptr kPrimaryQuarantineSize = 1024;
+static Mutex primary_quarantine_mutex;
+static InternalMmapVector<void*> primary_quarantine;
+static uptr next_primary_quarantine;
+
+static bool FindSecondaryTombstone(void* p, SecondaryTombstone* result) {
+ Lock lock(&secondary_tombstones_mutex);
+ for (uptr i = 0; i != secondary_tombstones.size(); ++i) {
+ if (secondary_tombstones[i].ptr == p) {
+ *result = secondary_tombstones[i];
+ return true;
+ }
+ }
+ return false;
+}
+
+static void AddSecondaryTombstone(void* p, const ChunkMetadata* m) {
+ Lock lock(&secondary_tombstones_mutex);
+ const SecondaryTombstone tombstone = {p, m->alloc_stack_id, m->free_stack_id};
+ if (secondary_tombstones.size() < kSecondaryTombstoneLimit) {
+ secondary_tombstones.push_back(tombstone);
+ return;
+ }
+ secondary_tombstones[next_secondary_tombstone] = tombstone;
+ next_secondary_tombstone =
+ (next_secondary_tombstone + 1) % kSecondaryTombstoneLimit;
+}
+
+static void RemoveSecondaryTombstone(void* p) {
+ Lock lock(&secondary_tombstones_mutex);
+ for (uptr i = 0; i != secondary_tombstones.size(); ++i) {
+ if (secondary_tombstones[i].ptr != p)
+ continue;
+ secondary_tombstones[i] = secondary_tombstones.back();
+ secondary_tombstones.pop_back();
+ return;
+ }
+}
+
+static void QuarantinePrimary(void* p) {
+ void* released = nullptr;
+ {
+ Lock lock(&primary_quarantine_mutex);
+ if (primary_quarantine.size() < kPrimaryQuarantineSize) {
+ primary_quarantine.push_back(p);
+ } else {
+ released = primary_quarantine[next_primary_quarantine];
+ primary_quarantine[next_primary_quarantine] = p;
+ next_primary_quarantine =
+ (next_primary_quarantine + 1) % kPrimaryQuarantineSize;
+ }
+ }
+ if (released)
+ allocator.Deallocate(GetAllocatorCache(), released);
+}
+
+void InitializeAllocator() {
+ SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
+ allocator.InitLinkerInitialized(
+ common_flags()->allocator_release_to_os_interval_ms);
+ if (common_flags()->max_allocation_size_mb)
+ max_malloc_size = Min(common_flags()->max_allocation_size_mb << 20,
+ kMaxAllowedMallocSize);
+ else
+ max_malloc_size = kMaxAllowedMallocSize;
+}
+
+void AllocatorThreadStart() { allocator.InitCache(GetAllocatorCache()); }
+
+void AllocatorThreadFinish() {
+ allocator.SwallowCache(GetAllocatorCache());
+ allocator.DestroyCache(GetAllocatorCache());
+}
+
+static ChunkMetadata* Metadata(const void* p) {
+ return reinterpret_cast<ChunkMetadata*>(allocator.GetMetaData(p));
+}
+
+static void RegisterAllocation(const StackTrace& stack, void* p, uptr size) {
+ if (!p)
+ return;
+ if (!allocator.FromPrimary(p))
+ RemoveSecondaryTombstone(p);
+ ChunkMetadata* m = Metadata(p);
+ CHECK(m);
+ m->alloc_stack_id = StackDepotPut(stack);
+ m->free_stack_id = 0;
+ m->requested_size = size;
+ atomic_store(reinterpret_cast<atomic_uint8_t*>(m), kChunkAllocated,
+ memory_order_release);
+ RunMallocHooks(p, size);
+}
+
+// Report double-free and terminate.
+static void NORETURN ReportDoubleFree(void* p, u32 alloc_stack_id,
+ u32 first_free_stack_id,
+ const StackTrace& free_stack) {
+ class Decorator : public __sanitizer::SanitizerCommonDecorator {
+ public:
+ Decorator() : SanitizerCommonDecorator() {}
+ const char* Error() { return Red(); }
+ const char* Info() { return Blue(); }
+ };
+
+ Decorator d;
+ Printf("\n");
+ Printf("%s", d.Error());
+ Report("ERROR: DoubleFreeSanitizer: double-free on address %p\n", p);
+ Printf("%s", d.Default());
+
+ // Print the second free (current) backtrace.
+ Printf("\n");
+ Printf("%s", d.Info());
+ Printf("Second free (the invalid free) of address %p:\n", p);
+ Printf("%s", d.Default());
+ free_stack.Print();
+
+ // Print the first free backtrace.
+ if (first_free_stack_id) {
+ Printf("\n");
+ Printf("%s", d.Info());
+ Printf("First free of address %p:\n", p);
+ Printf("%s", d.Default());
+ StackDepotGet(first_free_stack_id).Print();
+ }
+
+ // Print the original allocation backtrace.
+ if (alloc_stack_id) {
+ Printf("\n");
+ Printf("%s", d.Info());
+ Printf("Original allocation of address %p:\n", p);
+ Printf("%s", d.Default());
+ StackDepotGet(alloc_stack_id).Print();
+ }
+
+ Printf("\n");
+ Printf("SUMMARY: DoubleFreeSanitizer: double-free on address %p\n", p);
+ Die();
+}
+
+static void NORETURN ReportInvalidFree(void* p, const StackTrace& stack) {
+ Report("ERROR: DoubleFreeSanitizer: invalid free on address %p\n", p);
+ stack.Print();
+ Die();
+}
+
+static ChunkMetadata* GetChunkMetadata(void* p, const StackTrace& stack) {
+ if (!p)
+ return nullptr;
+ if (!allocator.PointerIsMine(p)) {
+ SecondaryTombstone tombstone = {};
+ if (FindSecondaryTombstone(p, &tombstone))
+ ReportDoubleFree(p, tombstone.alloc_stack_id, tombstone.free_stack_id,
+ stack);
+ ReportInvalidFree(p, stack);
+ }
+ if (allocator.GetBlockBegin(p) != p)
+ ReportInvalidFree(p, stack);
+ ChunkMetadata* m = Metadata(p);
+ if (atomic_load(reinterpret_cast<atomic_uint8_t*>(m), memory_order_acquire) ==
+ kChunkInvalid)
+ ReportInvalidFree(p, stack);
+ return m;
+}
+
+static void RegisterDeallocation(const StackTrace& stack, void* p) {
+ ChunkMetadata* m = GetChunkMetadata(p, stack);
+ if (!m)
+ return;
+
+ u8 expected = kChunkAllocated;
+ if (!atomic_compare_exchange_strong(reinterpret_cast<atomic_uint8_t*>(m),
+ &expected, kChunkFreeing,
+ memory_order_acquire)) {
+ while (expected == kChunkFreeing)
+ expected = atomic_load(reinterpret_cast<atomic_uint8_t*>(m),
+ memory_order_acquire);
+ if (expected == kChunkFreed)
+ ReportDoubleFree(p, m->alloc_stack_id, m->free_stack_id, stack);
+ ReportInvalidFree(p, stack);
+ }
+
+ m->free_stack_id = StackDepotPut(stack);
+ atomic_store(reinterpret_cast<atomic_uint8_t*>(m), kChunkFreed,
+ memory_order_release);
+ RunFreeHooks(p);
+}
+
+static void* ReportAllocationSizeTooBig(uptr size, const StackTrace& stack) {
+ if (AllocatorMayReturnNull()) {
+ Report("WARNING: DoubleFreeSanitizer failed to allocate 0x%zx bytes\n",
+ size);
+ return nullptr;
+ }
+ ReportAllocationSizeTooBig(size, max_malloc_size, &stack);
+}
+
+void* Allocate(const StackTrace& stack, uptr size, uptr alignment,
+ bool cleared) {
+ if (size == 0)
+ size = 1;
+ if (size > max_malloc_size)
+ return ReportAllocationSizeTooBig(size, stack);
+ if (UNLIKELY(IsRssLimitExceeded())) {
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ ReportRssLimitExceeded(&stack);
+ }
+ void* p = allocator.Allocate(GetAllocatorCache(), size, alignment);
+ if (UNLIKELY(!p)) {
+ SetAllocatorOutOfMemory();
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ ReportOutOfMemory(size, &stack);
+ }
+ if (cleared && allocator.FromPrimary(p))
+ memset(p, 0, size);
+ RegisterAllocation(stack, p, size);
+ return p;
+}
+
+static void* Calloc(uptr nmemb, uptr size, const StackTrace& stack) {
+ if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ ReportCallocOverflow(nmemb, size, &stack);
+ }
+ size *= nmemb;
+ return Allocate(stack, size, 1, true);
+}
+
+void Deallocate(const StackTrace& stack, void* p) {
+ if (!p)
+ return;
+ RegisterDeallocation(stack, p);
+ if (allocator.FromPrimary(p)) {
+ QuarantinePrimary(p);
+ } else {
+ AddSecondaryTombstone(p, Metadata(p));
+ allocator.Deallocate(GetAllocatorCache(), p);
+ }
+}
+
+void* Reallocate(const StackTrace& stack, void* p, uptr new_size,
+ uptr alignment) {
+ if (!p)
+ return Allocate(stack, new_size, alignment, kAlwaysClearMemory);
+ if (!new_size) {
+ Deallocate(stack, p);
+ return nullptr;
+ }
+ if (new_size > max_malloc_size) {
+ ReportAllocationSizeTooBig(new_size, stack);
+ return nullptr;
+ }
+ ChunkMetadata* m = GetChunkMetadata(p, stack);
+ if (atomic_load(reinterpret_cast<atomic_uint8_t*>(m), memory_order_acquire) !=
+ kChunkAllocated)
+ ReportDoubleFree(p, m->alloc_stack_id, m->free_stack_id, stack);
+ const uptr old_size = m->requested_size;
+ void* new_p = Allocate(stack, new_size, alignment, kAlwaysClearMemory);
+ if (!new_p)
+ return nullptr;
+ internal_memcpy(new_p, p, Min(old_size, new_size));
+ Deallocate(stack, p);
+ return new_p;
+}
+
+void GetAllocatorCacheRange(uptr* begin, uptr* end) {
+ *begin = (uptr)GetAllocatorCache();
+ *end = *begin + sizeof(AllocatorCache);
+}
+
+static const void* GetMallocBegin(const void* p) {
+ if (!p)
+ return nullptr;
+ void* beg = allocator.GetBlockBegin(p);
+ if (!beg)
+ return nullptr;
+ ChunkMetadata* m = Metadata(beg);
+ if (!m)
+ return nullptr;
+ if (atomic_load(reinterpret_cast<atomic_uint8_t*>(m), memory_order_acquire) !=
+ kChunkAllocated)
+ return nullptr;
+ if (m->requested_size == 0)
+ return nullptr;
+ return (const void*)beg;
+}
+
+uptr GetMallocUsableSize(const void* p) {
+ if (!p)
+ return 0;
+ ChunkMetadata* m = Metadata(p);
+ if (!m)
+ return 0;
+ return m->requested_size;
+}
+
+uptr GetMallocUsableSizeFast(const void* p) {
+ return Metadata(p)->requested_size;
+}
+
+int dsan_posix_memalign(void** memptr, uptr alignment, uptr size,
+ const StackTrace& stack) {
+ if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) {
+ if (AllocatorMayReturnNull())
+ return errno_EINVAL;
+ ReportInvalidPosixMemalignAlignment(alignment, &stack);
+ }
+ void* ptr = Allocate(stack, size, alignment, kAlwaysClearMemory);
+ if (UNLIKELY(!ptr))
+ return errno_ENOMEM;
+ CHECK(IsAligned((uptr)ptr, alignment));
+ *memptr = ptr;
+ return 0;
+}
+
+void* dsan_aligned_alloc(uptr alignment, uptr size, const StackTrace& stack) {
+ if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(alignment, size))) {
+ errno = errno_EINVAL;
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ ReportInvalidAlignedAllocAlignment(size, alignment, &stack);
+ }
+ return SetErrnoOnNull(Allocate(stack, size, alignment, kAlwaysClearMemory));
+}
+
+void* dsan_memalign(uptr alignment, uptr size, const StackTrace& stack) {
+ if (UNLIKELY(!IsPowerOfTwo(alignment))) {
+ errno = errno_EINVAL;
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ ReportInvalidAllocationAlignment(alignment, &stack);
+ }
+ return SetErrnoOnNull(Allocate(stack, size, alignment, kAlwaysClearMemory));
+}
+
+void* dsan_malloc(uptr size, const StackTrace& stack) {
+ return SetErrnoOnNull(Allocate(stack, size, 1, kAlwaysClearMemory));
+}
+
+void dsan_free(void* p, const StackTrace& stack) { Deallocate(stack, p); }
+
+void dsan_free_sized(void* p, uptr, const StackTrace& stack) {
+ Deallocate(stack, p);
+}
+
+void dsan_free_aligned_sized(void* p, uptr, uptr, const StackTrace& stack) {
+ Deallocate(stack, p);
+}
+
+void* dsan_realloc(void* p, uptr size, const StackTrace& stack) {
+ return SetErrnoOnNull(Reallocate(stack, p, size, 1));
+}
+
+void* dsan_reallocarray(void* ptr, uptr nmemb, uptr size,
+ const StackTrace& stack) {
+ if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
+ errno = errno_ENOMEM;
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ ReportReallocArrayOverflow(nmemb, size, &stack);
+ }
+ return dsan_realloc(ptr, nmemb * size, stack);
+}
+
+void* dsan_calloc(uptr nmemb, uptr size, const StackTrace& stack) {
+ return SetErrnoOnNull(Calloc(nmemb, size, stack));
+}
+
+void* dsan_valloc(uptr size, const StackTrace& stack) {
+ return SetErrnoOnNull(
+ Allocate(stack, size, GetPageSizeCached(), kAlwaysClearMemory));
+}
+
+void* dsan_pvalloc(uptr size, const StackTrace& stack) {
+ uptr PageSize = GetPageSizeCached();
+ if (UNLIKELY(CheckForPvallocOverflow(size, PageSize))) {
+ errno = errno_ENOMEM;
+ if (AllocatorMayReturnNull())
+ return nullptr;
+ ReportPvallocOverflow(size, &stack);
+ }
+ size = size ? RoundUpTo(size, PageSize) : PageSize;
+ return SetErrnoOnNull(Allocate(stack, size, PageSize, kAlwaysClearMemory));
+}
+
+uptr dsan_mz_size(const void* p) { return GetMallocUsableSize(p); }
+
+void LockAllocator() { allocator.ForceLock(); }
+
+void UnlockAllocator() { allocator.ForceUnlock(); }
+
+} // namespace __dsan
+
+using namespace __dsan;
+
+extern "C" {
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_current_allocated_bytes() {
+ uptr stats[AllocatorStatCount];
+ allocator.GetStats(stats);
+ return stats[AllocatorStatAllocated];
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_heap_size() {
+ uptr stats[AllocatorStatCount];
+ allocator.GetStats(stats);
+ return stats[AllocatorStatMapped];
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_free_bytes() { return 1; }
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_unmapped_bytes() { return 0; }
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_estimated_allocated_size(uptr size) { return size; }
+
+SANITIZER_INTERFACE_ATTRIBUTE
+int __sanitizer_get_ownership(const void* p) {
+ return GetMallocBegin(p) != nullptr;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+const void* __sanitizer_get_allocated_begin(const void* p) {
+ return GetMallocBegin(p);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_allocated_size(const void* p) {
+ return GetMallocUsableSize(p);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_allocated_size_fast(const void* p) {
+ DCHECK_EQ(p, __sanitizer_get_allocated_begin(p));
+ uptr ret = GetMallocUsableSizeFast(p);
+ DCHECK_EQ(ret, __sanitizer_get_allocated_size(p));
+ return ret;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __sanitizer_purge_allocator() { allocator.ForceReleaseToOS(); }
+
+} // extern "C"
diff --git a/compiler-rt/lib/dsan/dsan_allocator.h b/compiler-rt/lib/dsan/dsan_allocator.h
new file mode 100644
index 0000000000000..31ee1071b2811
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_allocator.h
@@ -0,0 +1,150 @@
+//=-- dsan_allocator.h ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Allocator for standalone DSan.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef DSAN_ALLOCATOR_H
+#define DSAN_ALLOCATOR_H
+
+#include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+
+namespace __dsan {
+
+void* Allocate(const StackTrace& stack, uptr size, uptr alignment,
+ bool cleared);
+void Deallocate(const StackTrace& stack, void* p);
+void* Reallocate(const StackTrace& stack, void* p, uptr new_size,
+ uptr alignment);
+uptr GetMallocUsableSize(const void* p);
+
+void GetAllocatorCacheRange(uptr* begin, uptr* end);
+void AllocatorThreadStart();
+void AllocatorThreadFinish();
+void InitializeAllocator();
+
+const bool kAlwaysClearMemory = true;
+
+struct ChunkMetadata {
+ u8 state; // Must be first. See ChunkState.
+#if SANITIZER_WORDSIZE == 64
+ uptr requested_size : 56;
+#else
+ uptr requested_size : 32;
+ uptr padding2 : 24;
+#endif
+ u32 alloc_stack_id; // Stack trace of the allocation
+ u32 free_stack_id; // Stack trace of the first free
+};
+
+enum ChunkState : u8 {
+ kChunkInvalid,
+ kChunkAllocated,
+ kChunkFreeing,
+ kChunkFreed,
+};
+
+#if !SANITIZER_CAN_USE_ALLOCATOR64
+template <typename AddressSpaceViewTy>
+struct AP32 {
+ static const uptr kSpaceBeg = SANITIZER_MMAP_BEGIN;
+ static const u64 kSpaceSize = SANITIZER_MMAP_RANGE_SIZE;
+ static const uptr kMetadataSize = sizeof(ChunkMetadata);
+ typedef __sanitizer::CompactSizeClassMap SizeClassMap;
+ static const uptr kRegionSizeLog = 20;
+ using AddressSpaceView = AddressSpaceViewTy;
+ typedef NoOpMapUnmapCallback MapUnmapCallback;
+ static const uptr kFlags = 0;
+};
+template <typename AddressSpaceView>
+using PrimaryAllocatorASVT = SizeClassAllocator32<AP32<AddressSpaceView>>;
+using PrimaryAllocator = PrimaryAllocatorASVT<LocalAddressSpaceView>;
+#else
+# if SANITIZER_FUCHSIA || defined(__powerpc64__)
+const uptr kAllocatorSpace = ~(uptr)0;
+# if SANITIZER_RISCV64
+// See the comments in compiler-rt/lib/asan/asan_allocator.h for why these
+// values were chosen.
+const uptr kAllocatorSize = UINT64_C(1) << 33; // 8GB
+using DSanSizeClassMap = SizeClassMap</*kNumBits=*/2,
+ /*kMinSizeLog=*/5,
+ /*kMidSizeLog=*/8,
+ /*kMaxSizeLog=*/18,
+ /*kNumCachedHintT=*/8,
+ /*kMaxBytesCachedLog=*/10>;
+static_assert(DSanSizeClassMap::kNumClassesRounded <= 32,
+ "32 size classes is the optimal number to ensure tests run "
+ "efficiently on Fuchsia.");
+# else
+const uptr kAllocatorSize = 0x40000000000ULL; // 4T.
+using DSanSizeClassMap = DefaultSizeClassMap;
+# endif
+# elif SANITIZER_RISCV64
+const uptr kAllocatorSpace = ~(uptr)0;
+const uptr kAllocatorSize = 0x2000000000ULL; // 128G.
+using DSanSizeClassMap = DefaultSizeClassMap;
+# elif SANITIZER_APPLE
+const uptr kAllocatorSpace = 0x600000000000ULL;
+const uptr kAllocatorSize = 0x40000000000ULL; // 4T.
+using DSanSizeClassMap = DefaultSizeClassMap;
+# elif SANITIZER_ANDROID && defined(__aarch64__)
+const uptr kAllocatorSpace = 0x3000000000ULL;
+const uptr kAllocatorSize = 0x2000000000ULL;
+using DSanSizeClassMap = VeryCompactSizeClassMap;
+# else
+const uptr kAllocatorSpace = 0x500000000000ULL;
+const uptr kAllocatorSize = 0x40000000000ULL; // 4T.
+using DSanSizeClassMap = DefaultSizeClassMap;
+# endif
+template <typename AddressSpaceViewTy>
+struct AP64 { // Allocator64 parameters. Deliberately using a short name.
+ static const uptr kSpaceBeg = kAllocatorSpace;
+ static const uptr kSpaceSize = kAllocatorSize;
+ static const uptr kMetadataSize = sizeof(ChunkMetadata);
+ using SizeClassMap = DSanSizeClassMap;
+ typedef NoOpMapUnmapCallback MapUnmapCallback;
+ static const uptr kFlags = 0;
+ using AddressSpaceView = AddressSpaceViewTy;
+};
+
+template <typename AddressSpaceView>
+using PrimaryAllocatorASVT = SizeClassAllocator64<AP64<AddressSpaceView>>;
+using PrimaryAllocator = PrimaryAllocatorASVT<LocalAddressSpaceView>;
+#endif
+
+template <typename AddressSpaceView>
+using AllocatorASVT = CombinedAllocator<PrimaryAllocatorASVT<AddressSpaceView>>;
+using Allocator = AllocatorASVT<LocalAddressSpaceView>;
+using AllocatorCache = Allocator::AllocatorCache;
+
+Allocator::AllocatorCache* GetAllocatorCache();
+
+int dsan_posix_memalign(void** memptr, uptr alignment, uptr size,
+ const StackTrace& stack);
+void* dsan_aligned_alloc(uptr alignment, uptr size, const StackTrace& stack);
+void* dsan_memalign(uptr alignment, uptr size, const StackTrace& stack);
+void* dsan_malloc(uptr size, const StackTrace& stack);
+void dsan_free(void* p, const StackTrace& stack);
+void dsan_free_sized(void* p, uptr size, const StackTrace& stack);
+void dsan_free_aligned_sized(void* p, uptr alignment, uptr size,
+ const StackTrace& stack);
+void* dsan_realloc(void* p, uptr size, const StackTrace& stack);
+void* dsan_reallocarray(void* p, uptr nmemb, uptr size,
+ const StackTrace& stack);
+void* dsan_calloc(uptr nmemb, uptr size, const StackTrace& stack);
+void* dsan_valloc(uptr size, const StackTrace& stack);
+void* dsan_pvalloc(uptr size, const StackTrace& stack);
+uptr dsan_mz_size(const void* p);
+
+} // namespace __dsan
+
+#endif // DSAN_ALLOCATOR_H
diff --git a/compiler-rt/lib/dsan/dsan_common.cpp b/compiler-rt/lib/dsan/dsan_common.cpp
new file mode 100644
index 0000000000000..4a8a454075861
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_common.cpp
@@ -0,0 +1,31 @@
+//=-- dsan_common.cpp -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Implementation of common double-free checking functionality.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan_common.h"
+
+#include "sanitizer_common/sanitizer_common.h"
+
+namespace __dsan {
+
+void InitCommonDsan() {
+ // DoubleFreeSanitizer doesn't need complex initialization.
+ // Detection is done inline in RegisterDeallocation.
+}
+
+} // namespace __dsan
+
+extern "C" {
+SANITIZER_INTERFACE_WEAK_DEF(const char*, __dsan_default_options, void) {
+ return "";
+}
+} // extern "C"
diff --git a/compiler-rt/lib/dsan/dsan_common.h b/compiler-rt/lib/dsan/dsan_common.h
new file mode 100644
index 0000000000000..63bf7fa5156ec
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_common.h
@@ -0,0 +1,103 @@
+//=-- dsan_common.h -------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Private DSan header.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef DSAN_COMMON_H
+#define DSAN_COMMON_H
+
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_platform.h"
+#include "sanitizer_common/sanitizer_range.h"
+#include "sanitizer_common/sanitizer_stackdepot.h"
+#include "sanitizer_common/sanitizer_symbolizer.h"
+#include "sanitizer_common/sanitizer_thread_registry.h"
+
+// DoubleFreeSanitizer can run on most platforms that support sanitizers.
+#if SANITIZER_ANDROID && (__ANDROID_API__ < 28 || defined(__arm__))
+# define CAN_SANITIZE_DOUBLE_FREE 0
+#elif (SANITIZER_LINUX || SANITIZER_APPLE) && (SANITIZER_WORDSIZE == 64) && \
+ (defined(__x86_64__) || defined(__mips64) || defined(__aarch64__) || \
+ defined(__powerpc64__) || defined(__s390x__))
+# define CAN_SANITIZE_DOUBLE_FREE 1
+#elif defined(__i386__) && (SANITIZER_LINUX || SANITIZER_APPLE)
+# define CAN_SANITIZE_DOUBLE_FREE 1
+#elif defined(__arm__) && SANITIZER_LINUX
+# define CAN_SANITIZE_DOUBLE_FREE 1
+#elif defined(__hexagon__) && SANITIZER_LINUX
+# define CAN_SANITIZE_DOUBLE_FREE 1
+#elif SANITIZER_LOONGARCH64 && SANITIZER_LINUX
+# define CAN_SANITIZE_DOUBLE_FREE 1
+#elif SANITIZER_RISCV64 && SANITIZER_LINUX
+# define CAN_SANITIZE_DOUBLE_FREE 1
+#elif SANITIZER_NETBSD || SANITIZER_FUCHSIA
+# define CAN_SANITIZE_DOUBLE_FREE 1
+#else
+# define CAN_SANITIZE_DOUBLE_FREE 0
+#endif
+
+namespace __sanitizer {
+class ThreadRegistry;
+class ThreadContextBase;
+struct DTLS;
+} // namespace __sanitizer
+
+namespace __dsan {
+
+// Returns true if [addr, addr + sizeof(void *)) is poisoned.
+bool WordIsPoisoned(uptr addr);
+
+//// --------------------------------------------------------------------------
+//// Thread prototypes.
+//// --------------------------------------------------------------------------
+
+void LockThreads() SANITIZER_NO_THREAD_SAFETY_ANALYSIS;
+void UnlockThreads() SANITIZER_NO_THREAD_SAFETY_ANALYSIS;
+void EnsureMainThreadIDIsCorrect();
+
+bool GetThreadRangesLocked(ThreadID os_id, uptr* stack_begin, uptr* stack_end,
+ uptr* tls_begin, uptr* tls_end, uptr* cache_begin,
+ uptr* cache_end, DTLS** dtls);
+void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr>* caches);
+void GetThreadExtraStackRangesLocked(InternalMmapVector<Range>* ranges);
+void GetThreadExtraStackRangesLocked(ThreadID os_id,
+ InternalMmapVector<Range>* ranges);
+void GetAdditionalThreadContextPtrsLocked(InternalMmapVector<uptr>* ptrs);
+void GetRunningThreadsLocked(InternalMmapVector<ThreadID>* threads);
+void PrintThreads();
+
+//// --------------------------------------------------------------------------
+//// Allocator prototypes.
+//// --------------------------------------------------------------------------
+
+void LockAllocator();
+void UnlockAllocator();
+
+void GetAllocatorCacheRange(uptr* begin, uptr* end);
+
+void InitCommonDsan();
+
+// Forward declaration - defined in dsan_thread.h
+class ThreadContextDsanBase;
+
+ThreadContextDsanBase* GetCurrentThread();
+void SetCurrentThread(ThreadContextDsanBase* tctx);
+
+} // namespace __dsan
+
+extern "C" {
+SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE const char*
+__dsan_default_options();
+
+} // extern "C"
+
+#endif // DSAN_COMMON_H
diff --git a/compiler-rt/lib/dsan/dsan_fuchsia.cpp b/compiler-rt/lib/dsan/dsan_fuchsia.cpp
new file mode 100644
index 0000000000000..f4c698e3574af
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_fuchsia.cpp
@@ -0,0 +1,131 @@
+//=-- dsan_fuchsia.cpp ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===---------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Standalone DSan RTL code specific to Fuchsia.
+//
+//===---------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if SANITIZER_FUCHSIA
+# include <zircon/sanitizer.h>
+
+# include "dsan.h"
+# include "dsan_allocator.h"
+
+using namespace __dsan;
+
+namespace __sanitizer {
+// DSan doesn't need to do anything else special in the startup hook.
+void EarlySanitizerInit() {}
+} // namespace __sanitizer
+
+namespace __dsan {
+
+void DsanOnDeadlySignal(int signo, void* siginfo, void* context) {}
+
+ThreadContext::ThreadContext(int tid) : ThreadContextDsanBase(tid) {}
+
+struct OnCreatedArgs {
+ uptr stack_begin, stack_end;
+};
+
+// On Fuchsia, the stack bounds of a new thread are available before
+// the thread itself has started running.
+void ThreadContext::OnCreated(void* arg) {
+ // Stack bounds passed through from __sanitizer_before_thread_create_hook
+ // or InitializeMainThread.
+ auto args = reinterpret_cast<const OnCreatedArgs*>(arg);
+ stack_begin_ = args->stack_begin;
+ stack_end_ = args->stack_end;
+}
+
+struct OnStartedArgs {
+ uptr cache_begin, cache_end;
+};
+
+void ThreadContext::OnStarted(void* arg) {
+ ThreadContextDsanBase::OnStarted(arg);
+ auto args = reinterpret_cast<const OnStartedArgs*>(arg);
+ cache_begin_ = args->cache_begin;
+ cache_end_ = args->cache_end;
+}
+
+void ThreadStart(u32 tid) {
+ OnStartedArgs args;
+ GetAllocatorCacheRange(&args.cache_begin, &args.cache_end);
+ CHECK_EQ(args.cache_end - args.cache_begin, sizeof(AllocatorCache));
+ ThreadContextDsanBase::ThreadStart(tid, GetTid(), ThreadType::Regular, &args);
+}
+
+void InitializeMainThread() {
+ OnCreatedArgs args;
+ __sanitizer::GetThreadStackTopAndBottom(true, &args.stack_end,
+ &args.stack_begin);
+ u32 tid = ThreadCreate(kMainTid, true, &args);
+ CHECK_EQ(tid, 0);
+ ThreadStart(tid);
+}
+
+void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr>* caches) {
+ GetDsanThreadRegistryLocked()->RunCallbackForEachThreadLocked(
+ [](ThreadContextBase* tctx, void* arg) {
+ auto ctx = static_cast<ThreadContext*>(tctx);
+ static_cast<decltype(caches)>(arg)->push_back(ctx->cache_begin());
+ },
+ caches);
+}
+
+// DoubleFreeSanitizer doesn't need atexit check.
+void InstallAtForkHandler() {}
+
+} // namespace __dsan
+
+// These are declared (in extern "C") by <zircon/sanitizer.h>.
+// The system runtime will call our definitions directly.
+
+// This is called before each thread creation is attempted. So, in
+// its first call, the calling thread is the initial and sole thread.
+void* __sanitizer_before_thread_create_hook(thrd_t thread, bool detached,
+ const char* name, void* stack_base,
+ size_t stack_size) {
+ ENSURE_DSAN_INITED;
+ EnsureMainThreadIDIsCorrect();
+ OnCreatedArgs args;
+ args.stack_begin = reinterpret_cast<uptr>(stack_base);
+ args.stack_end = args.stack_begin + stack_size;
+ u32 parent_tid = GetCurrentThreadId();
+ u32 tid = ThreadCreate(parent_tid, detached, &args);
+ return reinterpret_cast<void*>(static_cast<uptr>(tid));
+}
+
+// This is called after creating a new thread (in the creating thread),
+// with the pointer returned by __sanitizer_before_thread_create_hook (above).
+void __sanitizer_thread_create_hook(void* hook, thrd_t thread, int error) {
+ u32 tid = static_cast<u32>(reinterpret_cast<uptr>(hook));
+ // On success, there is nothing to do here.
+ if (error != thrd_success) {
+ // Clean up the thread registry for the thread creation that didn't happen.
+ GetDsanThreadRegistryLocked()->FinishThread(tid);
+ }
+}
+
+// This is called in the newly-created thread before it runs anything else,
+// with the pointer returned by __sanitizer_before_thread_create_hook (above).
+void __sanitizer_thread_start_hook(void* hook, thrd_t self) {
+ u32 tid = static_cast<u32>(reinterpret_cast<uptr>(hook));
+ ThreadStart(tid);
+}
+
+// Each thread runs this just before it exits,
+// with the pointer returned by BeforeThreadCreateHook (above).
+// All per-thread destructors have already been called.
+void __sanitizer_thread_exit_hook(void* hook, thrd_t self) { ThreadFinish(); }
+
+#endif // SANITIZER_FUCHSIA
diff --git a/compiler-rt/lib/dsan/dsan_fuchsia.h b/compiler-rt/lib/dsan/dsan_fuchsia.h
new file mode 100644
index 0000000000000..e60fc4f2ac2d9
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_fuchsia.h
@@ -0,0 +1,35 @@
+//=-- dsan_fuchsia.h ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===---------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Standalone DSan RTL code specific to Fuchsia.
+//
+//===---------------------------------------------------------------------===//
+
+#ifndef DSAN_FUCHSIA_H
+#define DSAN_FUCHSIA_H
+
+#include "dsan_thread.h"
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if !SANITIZER_FUCHSIA
+# error "dsan_fuchsia.h is used only on Fuchsia systems (SANITIZER_FUCHSIA)"
+#endif
+
+namespace __dsan {
+
+class ThreadContext final : public ThreadContextDsanBase {
+ public:
+ explicit ThreadContext(int tid);
+ void OnCreated(void* arg) override;
+ void OnStarted(void* arg) override;
+};
+
+} // namespace __dsan
+
+#endif // DSAN_FUCHSIA_H
diff --git a/compiler-rt/lib/dsan/dsan_interceptors.cpp b/compiler-rt/lib/dsan/dsan_interceptors.cpp
new file mode 100644
index 0000000000000..8ddb6d58239e7
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_interceptors.cpp
@@ -0,0 +1,575 @@
+//=-- dsan_interceptors.cpp -----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Interceptors for standalone DSan.
+//
+//===----------------------------------------------------------------------===//
+
+#include "interception/interception.h"
+#include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
+#include "sanitizer_common/sanitizer_allocator_dlsym.h"
+#include "sanitizer_common/sanitizer_allocator_report.h"
+#include "sanitizer_common/sanitizer_atomic.h"
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_errno.h"
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_linux.h"
+#include "sanitizer_common/sanitizer_platform_interceptors.h"
+#include "sanitizer_common/sanitizer_platform_limits_netbsd.h"
+#include "sanitizer_common/sanitizer_platform_limits_posix.h"
+#if SANITIZER_POSIX
+# include "sanitizer_common/sanitizer_posix.h"
+#endif
+#include <stddef.h>
+
+#include "dsan.h"
+#include "dsan_allocator.h"
+#include "dsan_common.h"
+#include "dsan_thread.h"
+
+using namespace __dsan;
+
+extern "C" {
+int pthread_attr_init(void* attr);
+int pthread_attr_destroy(void* attr);
+int pthread_attr_getdetachstate(void* attr, int* v);
+int pthread_key_create(unsigned* key, void (*destructor)(void* v));
+int pthread_setspecific(unsigned key, const void* v);
+}
+
+struct DlsymAlloc : DlSymAllocator<DlsymAlloc> {
+ static bool UseImpl() { return dsan_init_is_running; }
+ static void OnAllocate(const void* ptr, uptr size) {
+ (void)ptr;
+ (void)size;
+ }
+ static void OnFree(const void* ptr, uptr size) {
+ (void)ptr;
+ (void)size;
+ }
+};
+
+///// Malloc/free interceptors. /////
+
+namespace std {
+struct nothrow_t;
+enum class align_val_t : size_t;
+} // namespace std
+
+#if !SANITIZER_APPLE
+INTERCEPTOR(void*, malloc, uptr size) {
+ if (DlsymAlloc::Use())
+ return DlsymAlloc::Allocate(size);
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_malloc(size, stack);
+}
+
+INTERCEPTOR(void, free, void* p) {
+ if (UNLIKELY(!p))
+ return;
+ if (DlsymAlloc::PointerIsMine(p))
+ return DlsymAlloc::Free(p);
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ dsan_free(p, stack);
+}
+
+# if SANITIZER_INTERCEPT_FREE_SIZED
+INTERCEPTOR(void, free_sized, void* p, uptr size) {
+ if (UNLIKELY(!p))
+ return;
+ if (DlsymAlloc::PointerIsMine(p))
+ return DlsymAlloc::Free(p);
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ dsan_free_sized(p, size, stack);
+}
+# define DSAN_MAYBE_INTERCEPT_FREE_SIZED INTERCEPT_FUNCTION(free_sized)
+# else
+# define DSAN_MAYBE_INTERCEPT_FREE_SIZED
+# endif
+
+# if SANITIZER_INTERCEPT_FREE_ALIGNED_SIZED
+INTERCEPTOR(void, free_aligned_sized, void* p, uptr alignment, uptr size) {
+ if (UNLIKELY(!p))
+ return;
+ if (DlsymAlloc::PointerIsMine(p))
+ return DlsymAlloc::Free(p);
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ dsan_free_aligned_sized(p, alignment, size, stack);
+}
+# define DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED \
+ INTERCEPT_FUNCTION(free_aligned_sized)
+# else
+# define DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED
+# endif
+
+INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
+ if (DlsymAlloc::Use())
+ return DlsymAlloc::Callocate(nmemb, size);
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_calloc(nmemb, size, stack);
+}
+
+INTERCEPTOR(void*, realloc, void* ptr, uptr size) {
+ if (DlsymAlloc::Use() || DlsymAlloc::PointerIsMine(ptr))
+ return DlsymAlloc::Realloc(ptr, size);
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_realloc(ptr, size, stack);
+}
+
+INTERCEPTOR(void*, reallocarray, void* q, uptr nmemb, uptr size) {
+ if (DlsymAlloc::Use() || DlsymAlloc::PointerIsMine(q)) {
+ if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
+ errno = errno_ENOMEM;
+ return nullptr;
+ }
+ return DlsymAlloc::Realloc(q, nmemb * size);
+ }
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_reallocarray(q, nmemb, size, stack);
+}
+
+INTERCEPTOR(int, posix_memalign, void** memptr, uptr alignment, uptr size) {
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_posix_memalign(memptr, alignment, size, stack);
+}
+
+INTERCEPTOR(void*, valloc, uptr size) {
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_valloc(size, stack);
+}
+#else
+# define DSAN_MAYBE_INTERCEPT_FREE_SIZED
+# define DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED
+#endif // !SANITIZER_APPLE
+
+#if SANITIZER_INTERCEPT_MEMALIGN
+INTERCEPTOR(void*, memalign, uptr alignment, uptr size) {
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_memalign(alignment, size, stack);
+}
+# define DSAN_MAYBE_INTERCEPT_MEMALIGN INTERCEPT_FUNCTION(memalign)
+#else
+# define DSAN_MAYBE_INTERCEPT_MEMALIGN
+#endif // SANITIZER_INTERCEPT_MEMALIGN
+
+#if SANITIZER_INTERCEPT___LIBC_MEMALIGN
+INTERCEPTOR(void*, __libc_memalign, uptr alignment, uptr size) {
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_memalign(alignment, size, stack);
+}
+# define DSAN_MAYBE_INTERCEPT___LIBC_MEMALIGN \
+ INTERCEPT_FUNCTION(__libc_memalign)
+#else
+# define DSAN_MAYBE_INTERCEPT___LIBC_MEMALIGN
+#endif // SANITIZER_INTERCEPT___LIBC_MEMALIGN
+
+#if SANITIZER_INTERCEPT_ALIGNED_ALLOC
+INTERCEPTOR(void*, aligned_alloc, uptr alignment, uptr size) {
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_aligned_alloc(alignment, size, stack);
+}
+# define DSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC INTERCEPT_FUNCTION(aligned_alloc)
+#else
+# define DSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC
+#endif
+
+#if SANITIZER_INTERCEPT_MALLOC_USABLE_SIZE
+INTERCEPTOR(uptr, malloc_usable_size, void* ptr) {
+ ENSURE_DSAN_INITED;
+ return GetMallocUsableSize(ptr);
+}
+# define DSAN_MAYBE_INTERCEPT_MALLOC_USABLE_SIZE \
+ INTERCEPT_FUNCTION(malloc_usable_size)
+#else
+# define DSAN_MAYBE_INTERCEPT_MALLOC_USABLE_SIZE
+#endif
+
+#if SANITIZER_INTERCEPT_MALLOPT_AND_MALLINFO
+struct fake_mallinfo {
+ int x[10];
+};
+
+INTERCEPTOR(struct fake_mallinfo, mallinfo, void) {
+ struct fake_mallinfo res;
+ internal_memset(&res, 0, sizeof(res));
+ return res;
+}
+# define DSAN_MAYBE_INTERCEPT_MALLINFO INTERCEPT_FUNCTION(mallinfo)
+
+INTERCEPTOR(int, mallopt, int cmd, int value) { return 0; }
+# define DSAN_MAYBE_INTERCEPT_MALLOPT INTERCEPT_FUNCTION(mallopt)
+#else
+# define DSAN_MAYBE_INTERCEPT_MALLINFO
+# define DSAN_MAYBE_INTERCEPT_MALLOPT
+#endif // SANITIZER_INTERCEPT_MALLOPT_AND_MALLINFO
+
+#if SANITIZER_INTERCEPT_PVALLOC
+INTERCEPTOR(void*, pvalloc, uptr size) {
+ ENSURE_DSAN_INITED;
+ GET_STACK_TRACE_MALLOC;
+ return dsan_pvalloc(size, stack);
+}
+# define DSAN_MAYBE_INTERCEPT_PVALLOC INTERCEPT_FUNCTION(pvalloc)
+#else
+# define DSAN_MAYBE_INTERCEPT_PVALLOC
+#endif // SANITIZER_INTERCEPT_PVALLOC
+
+#if SANITIZER_INTERCEPT_CFREE
+INTERCEPTOR(void, cfree, void* p) ALIAS(WRAP(free));
+# define DSAN_MAYBE_INTERCEPT_CFREE INTERCEPT_FUNCTION(cfree)
+#else
+# define DSAN_MAYBE_INTERCEPT_CFREE
+#endif // SANITIZER_INTERCEPT_CFREE
+
+#if SANITIZER_INTERCEPT_MCHECK_MPROBE
+INTERCEPTOR(int, mcheck, void (*abortfunc)(int mstatus)) { return 0; }
+
+INTERCEPTOR(int, mcheck_pedantic, void (*abortfunc)(int mstatus)) { return 0; }
+
+INTERCEPTOR(int, mprobe, void* ptr) { return 0; }
+#endif // SANITIZER_INTERCEPT_MCHECK_MPROBE
+
+// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
+#define OPERATOR_NEW_BODY(nothrow) \
+ ENSURE_DSAN_INITED; \
+ GET_STACK_TRACE_MALLOC; \
+ void* res = dsan_malloc(size, stack); \
+ if (!nothrow && UNLIKELY(!res)) \
+ ReportOutOfMemory(size, &stack); \
+ return res;
+#define OPERATOR_NEW_BODY_ALIGN(nothrow) \
+ ENSURE_DSAN_INITED; \
+ GET_STACK_TRACE_MALLOC; \
+ void* res = dsan_memalign((uptr)align, size, stack); \
+ if (!nothrow && UNLIKELY(!res)) \
+ ReportOutOfMemory(size, &stack); \
+ return res;
+
+#define OPERATOR_DELETE_BODY \
+ ENSURE_DSAN_INITED; \
+ GET_STACK_TRACE_MALLOC; \
+ dsan_free(ptr, stack);
+
+// On OS X it's not enough to just provide our own 'operator new' and
+// 'operator delete' implementations, because they're going to be in the runtime
+// dylib, and the main executable will depend on both the runtime dylib and
+// libstdc++, each of has its implementation of new and delete.
+// To make sure that C++ allocation/deallocation operators are overridden on
+// OS X we need to intercept them using their mangled names.
+#if !SANITIZER_APPLE
+
+INTERCEPTOR_ATTRIBUTE
+void* operator new(size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
+INTERCEPTOR_ATTRIBUTE
+void* operator new[](size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
+INTERCEPTOR_ATTRIBUTE
+void* operator new(size_t size, std::nothrow_t const&) {
+ OPERATOR_NEW_BODY(true /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new[](size_t size, std::nothrow_t const&) {
+ OPERATOR_NEW_BODY(true /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new(size_t size, std::align_val_t align) {
+ OPERATOR_NEW_BODY_ALIGN(false /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new[](size_t size, std::align_val_t align) {
+ OPERATOR_NEW_BODY_ALIGN(false /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new(size_t size, std::align_val_t align, std::nothrow_t const&) {
+ OPERATOR_NEW_BODY_ALIGN(true /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new[](size_t size, std::align_val_t align,
+ std::nothrow_t const&) {
+ OPERATOR_NEW_BODY_ALIGN(true /*nothrow*/);
+}
+
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr) NOEXCEPT { OPERATOR_DELETE_BODY; }
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr) NOEXCEPT { OPERATOR_DELETE_BODY; }
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, std::nothrow_t const&) { OPERATOR_DELETE_BODY; }
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, std::nothrow_t const&) {
+ OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, size_t size) NOEXCEPT { OPERATOR_DELETE_BODY; }
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, size_t size) NOEXCEPT {
+ OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, std::align_val_t) NOEXCEPT {
+ OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, std::align_val_t) NOEXCEPT {
+ OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, std::align_val_t, std::nothrow_t const&) {
+ OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, std::align_val_t, std::nothrow_t const&) {
+ OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, size_t size, std::align_val_t) NOEXCEPT {
+ OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, size_t size, std::align_val_t) NOEXCEPT {
+ OPERATOR_DELETE_BODY;
+}
+
+#else // SANITIZER_APPLE
+
+INTERCEPTOR(void*, _Znwm, size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
+INTERCEPTOR(void*, _Znam, size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
+INTERCEPTOR(void*, _ZnwmRKSt9nothrow_t, size_t size, std::nothrow_t const&) {
+ OPERATOR_NEW_BODY(true /*nothrow*/);
+}
+INTERCEPTOR(void*, _ZnamRKSt9nothrow_t, size_t size, std::nothrow_t const&) {
+ OPERATOR_NEW_BODY(true /*nothrow*/);
+}
+
+INTERCEPTOR(void, _ZdlPv, void* ptr) { OPERATOR_DELETE_BODY; }
+INTERCEPTOR(void, _ZdaPv, void* ptr) { OPERATOR_DELETE_BODY; }
+INTERCEPTOR(void, _ZdlPvRKSt9nothrow_t, void* ptr, std::nothrow_t const&) {
+ OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR(void, _ZdaPvRKSt9nothrow_t, void* ptr, std::nothrow_t const&) {
+ OPERATOR_DELETE_BODY;
+}
+
+#endif // !SANITIZER_APPLE
+
+///// Thread initialization and finalization. /////
+
+#if !SANITIZER_NETBSD && !SANITIZER_FREEBSD && !SANITIZER_FUCHSIA
+static unsigned g_thread_finalize_key;
+
+static void thread_finalize(void* v) {
+ uptr iter = (uptr)v;
+ if (iter > 1) {
+ if (pthread_setspecific(g_thread_finalize_key, (void*)(iter - 1))) {
+ Report("DoubleFreeSanitizer: failed to set thread key.\n");
+ Die();
+ }
+ return;
+ }
+ ThreadFinish();
+}
+#endif
+
+#if SANITIZER_NETBSD
+INTERCEPTOR(void, _lwp_exit) {
+ ENSURE_DSAN_INITED;
+ ThreadFinish();
+ REAL(_lwp_exit)();
+}
+# define DSAN_MAYBE_INTERCEPT__LWP_EXIT INTERCEPT_FUNCTION(_lwp_exit)
+#else
+# define DSAN_MAYBE_INTERCEPT__LWP_EXIT
+#endif
+
+#if SANITIZER_INTERCEPT_THR_EXIT
+INTERCEPTOR(void, thr_exit, ThreadID* state) {
+ ENSURE_DSAN_INITED;
+ ThreadFinish();
+ REAL(thr_exit)(state);
+}
+# define DSAN_MAYBE_INTERCEPT_THR_EXIT INTERCEPT_FUNCTION(thr_exit)
+#else
+# define DSAN_MAYBE_INTERCEPT_THR_EXIT
+#endif
+
+#if SANITIZER_POSIX
+
+template <bool Detached>
+static void* ThreadStartFunc(void* arg) {
+ u32 parent_tid = (uptr)arg;
+ uptr tid = ThreadCreate(parent_tid, Detached);
+ // Wait until the last iteration to maximize the chance that we are the last
+ // destructor to run.
+# if !SANITIZER_NETBSD && !SANITIZER_FREEBSD
+ if (pthread_setspecific(g_thread_finalize_key,
+ (void*)GetPthreadDestructorIterations())) {
+ Report("DoubleFreeSanitizer: failed to set thread key.\n");
+ Die();
+ }
+# endif
+ ThreadStart(tid, GetTid());
+ auto self = GetThreadSelf();
+ auto args = GetThreadArgRetval().GetArgs(self);
+ void* retval = (*args.routine)(args.arg_retval);
+ GetThreadArgRetval().Finish(self, retval);
+ return retval;
+}
+
+INTERCEPTOR(int, pthread_create, void* th, void* attr, void* (*callback)(void*),
+ void* param) {
+ ENSURE_DSAN_INITED;
+ EnsureMainThreadIDIsCorrect();
+
+ bool detached = [attr]() {
+ int d = 0;
+ return attr && !pthread_attr_getdetachstate(attr, &d) && IsStateDetached(d);
+ }();
+
+ __sanitizer_pthread_attr_t myattr;
+ if (!attr) {
+ pthread_attr_init(&myattr);
+ attr = &myattr;
+ }
+ AdjustStackSize(attr);
+ uptr this_tid = GetCurrentThreadId();
+ int result;
+ GetThreadArgRetval().Create(detached, {callback, param}, [&]() -> uptr {
+ result = REAL(pthread_create)(
+ th, attr, detached ? ThreadStartFunc<true> : ThreadStartFunc<false>,
+ (void*)this_tid);
+ return result ? 0 : *(uptr*)(th);
+ });
+ if (attr == &myattr)
+ pthread_attr_destroy(&myattr);
+ return result;
+}
+
+INTERCEPTOR(int, pthread_join, void* thread, void** retval) {
+ int result;
+ GetThreadArgRetval().Join((uptr)thread, [&]() {
+ result = REAL(pthread_join)(thread, retval);
+ return !result;
+ });
+ return result;
+}
+
+INTERCEPTOR(int, pthread_detach, void* thread) {
+ int result;
+ GetThreadArgRetval().Detach((uptr)thread, [&]() {
+ result = REAL(pthread_detach)(thread);
+ return !result;
+ });
+ return result;
+}
+
+INTERCEPTOR(void, pthread_exit, void* retval) {
+ GetThreadArgRetval().Finish(GetThreadSelf(), retval);
+ REAL(pthread_exit)(retval);
+}
+
+# if SANITIZER_INTERCEPT_TRYJOIN
+INTERCEPTOR(int, pthread_tryjoin_np, void* thread, void** ret) {
+ int result;
+ GetThreadArgRetval().Join((uptr)thread, [&]() {
+ result = REAL(pthread_tryjoin_np)(thread, ret);
+ return !result;
+ });
+ return result;
+}
+# define DSAN_MAYBE_INTERCEPT_TRYJOIN INTERCEPT_FUNCTION(pthread_tryjoin_np)
+# else
+# define DSAN_MAYBE_INTERCEPT_TRYJOIN
+# endif // SANITIZER_INTERCEPT_TRYJOIN
+
+# if SANITIZER_INTERCEPT_TIMEDJOIN
+INTERCEPTOR(int, pthread_timedjoin_np, void* thread, void** ret,
+ const struct timespec* abstime) {
+ int result;
+ GetThreadArgRetval().Join((uptr)thread, [&]() {
+ result = REAL(pthread_timedjoin_np)(thread, ret, abstime);
+ return !result;
+ });
+ return result;
+}
+# define DSAN_MAYBE_INTERCEPT_TIMEDJOIN \
+ INTERCEPT_FUNCTION(pthread_timedjoin_np)
+# else
+# define DSAN_MAYBE_INTERCEPT_TIMEDJOIN
+# endif // SANITIZER_INTERCEPT_TIMEDJOIN
+
+DEFINE_INTERNAL_PTHREAD_FUNCTIONS
+
+INTERCEPTOR(void, _exit, int status) { REAL(_exit)(status); }
+
+# define COMMON_INTERCEPT_FUNCTION(name) INTERCEPT_FUNCTION(name)
+# define SIGNAL_INTERCEPTOR_ENTER() ENSURE_DSAN_INITED
+# include "sanitizer_common/sanitizer_signal_interceptors.inc"
+
+#endif // SANITIZER_POSIX
+
+namespace __dsan {
+
+void InitializeInterceptors() {
+ // Fuchsia doesn't use interceptors that require any setup.
+#if !SANITIZER_FUCHSIA
+ __interception::DoesNotSupportStaticLinking();
+ InitializeSignalInterceptors();
+
+ INTERCEPT_FUNCTION(malloc);
+ INTERCEPT_FUNCTION(free);
+ DSAN_MAYBE_INTERCEPT_FREE_SIZED;
+ DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED;
+ DSAN_MAYBE_INTERCEPT_CFREE;
+ INTERCEPT_FUNCTION(calloc);
+ INTERCEPT_FUNCTION(realloc);
+ INTERCEPT_FUNCTION(reallocarray);
+ DSAN_MAYBE_INTERCEPT_MEMALIGN;
+ DSAN_MAYBE_INTERCEPT___LIBC_MEMALIGN;
+ DSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC;
+ INTERCEPT_FUNCTION(posix_memalign);
+ INTERCEPT_FUNCTION(valloc);
+ DSAN_MAYBE_INTERCEPT_PVALLOC;
+ DSAN_MAYBE_INTERCEPT_MALLOC_USABLE_SIZE;
+ DSAN_MAYBE_INTERCEPT_MALLINFO;
+ DSAN_MAYBE_INTERCEPT_MALLOPT;
+ INTERCEPT_FUNCTION(pthread_create);
+ INTERCEPT_FUNCTION(pthread_join);
+ INTERCEPT_FUNCTION(pthread_detach);
+ INTERCEPT_FUNCTION(pthread_exit);
+ DSAN_MAYBE_INTERCEPT_TIMEDJOIN;
+ DSAN_MAYBE_INTERCEPT_TRYJOIN;
+ INTERCEPT_FUNCTION(_exit);
+
+ DSAN_MAYBE_INTERCEPT__LWP_EXIT;
+ DSAN_MAYBE_INTERCEPT_THR_EXIT;
+
+# if !SANITIZER_NETBSD && !SANITIZER_FREEBSD
+ if (pthread_key_create(&g_thread_finalize_key, &thread_finalize)) {
+ Report("DoubleFreeSanitizer: failed to create thread key.\n");
+ Die();
+ }
+# endif
+
+#endif // !SANITIZER_FUCHSIA
+}
+
+} // namespace __dsan
diff --git a/compiler-rt/lib/dsan/dsan_linux.cpp b/compiler-rt/lib/dsan/dsan_linux.cpp
new file mode 100644
index 0000000000000..fa5bbffb9a5ec
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_linux.cpp
@@ -0,0 +1,34 @@
+//=-- dsan_linux.cpp ------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer. Linux/NetBSD/Fuchsia-specific
+// code.
+//
+//===----------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_FUCHSIA
+
+# include "dsan_allocator.h"
+# include "dsan_thread.h"
+
+namespace __dsan {
+
+static THREADLOCAL ThreadContextDsanBase* current_thread = nullptr;
+ThreadContextDsanBase* GetCurrentThread() { return current_thread; }
+void SetCurrentThread(ThreadContextDsanBase* tctx) { current_thread = tctx; }
+
+static THREADLOCAL AllocatorCache allocator_cache;
+AllocatorCache* GetAllocatorCache() { return &allocator_cache; }
+
+void ReplaceSystemMalloc() {}
+
+} // namespace __dsan
+
+#endif // SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_FUCHSIA
diff --git a/compiler-rt/lib/dsan/dsan_mac.cpp b/compiler-rt/lib/dsan/dsan_mac.cpp
new file mode 100644
index 0000000000000..793689862bc00
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_mac.cpp
@@ -0,0 +1,234 @@
+//===-- dsan_mac.cpp ------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+//
+// Mac-specific details.
+//===----------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+#if SANITIZER_APPLE
+
+# include <pthread.h>
+
+# include "dsan.h"
+# include "dsan_allocator.h"
+# include "dsan_thread.h"
+# include "interception/interception.h"
+# include "sanitizer_common/sanitizer_allocator_internal.h"
+
+namespace __dsan {
+struct ThreadLocalData {
+ ThreadContextDsanBase* current_thread;
+ AllocatorCache cache;
+};
+
+static pthread_key_t thread_local_key;
+static pthread_once_t thread_local_key_once = PTHREAD_ONCE_INIT;
+
+static void RestoreThreadLocalData(void* ptr) {
+ ThreadLocalData* data = static_cast<ThreadLocalData*>(ptr);
+ if (data->current_thread)
+ pthread_setspecific(thread_local_key, data);
+}
+
+static void CreateThreadLocalKey() {
+ CHECK_EQ(pthread_key_create(&thread_local_key, RestoreThreadLocalData), 0);
+}
+
+static ThreadLocalData* GetThreadLocalData(bool allocate) {
+ pthread_once(&thread_local_key_once, CreateThreadLocalKey);
+ ThreadLocalData* data =
+ static_cast<ThreadLocalData*>(pthread_getspecific(thread_local_key));
+ if (!data && allocate) {
+ data = static_cast<ThreadLocalData*>(InternalAlloc(sizeof(*data)));
+ data->current_thread = nullptr;
+ data->cache = AllocatorCache();
+ pthread_setspecific(thread_local_key, data);
+ }
+ return data;
+}
+
+ThreadContextDsanBase* GetCurrentThread() {
+ ThreadLocalData* data = GetThreadLocalData(false);
+ return data ? data->current_thread : nullptr;
+}
+
+void SetCurrentThread(ThreadContextDsanBase* tctx) {
+ GetThreadLocalData(true)->current_thread = tctx;
+}
+
+AllocatorCache* GetAllocatorCache() { return &GetThreadLocalData(true)->cache; }
+
+// Support for the following functions from libdispatch on Mac OS:
+// dispatch_async_f()
+// dispatch_async()
+// dispatch_sync_f()
+// dispatch_sync()
+// dispatch_after_f()
+// dispatch_after()
+// dispatch_group_async_f()
+// dispatch_group_async()
+// TODO(glider): libdispatch API contains other functions that we don't support
+// yet.
+//
+// dispatch_sync() and dispatch_sync_f() are synchronous, although chances are
+// they can cause jobs to run on a thread different from the current one.
+// TODO(glider): if so, we need a test for this (otherwise we should remove
+// them).
+//
+// The following functions use dispatch_barrier_async_f() (which isn't a library
+// function but is exported) and are thus supported:
+// dispatch_source_set_cancel_handler_f()
+// dispatch_source_set_cancel_handler()
+// dispatch_source_set_event_handler_f()
+// dispatch_source_set_event_handler()
+//
+// The reference manual for Grand Central Dispatch is available at
+// http://developer.apple.com/library/mac/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
+// The implementation details are at
+// http://libdispatch.macosforge.org/trac/browser/trunk/src/queue.c
+
+typedef void* dispatch_group_t;
+typedef void* dispatch_queue_t;
+typedef void* dispatch_source_t;
+typedef u64 dispatch_time_t;
+typedef void (*dispatch_function_t)(void* block);
+typedef void* (*worker_t)(void* block);
+
+// A wrapper for the ObjC blocks used to support libdispatch.
+typedef struct {
+ void* block;
+ dispatch_function_t func;
+ u32 parent_tid;
+} dsan_block_context_t;
+
+ALWAYS_INLINE
+void dsan_register_worker_thread(int parent_tid) {
+ if (GetCurrentThreadId() == kInvalidTid) {
+ u32 tid = ThreadCreate(parent_tid, true);
+ ThreadStart(tid, GetTid());
+ }
+}
+
+// For use by only those functions that allocated the context via
+// alloc_dsan_context().
+extern "C" void dsan_dispatch_call_block_and_release(void* block) {
+ dsan_block_context_t* context = (dsan_block_context_t*)block;
+ VReport(2,
+ "dsan_dispatch_call_block_and_release(): "
+ "context: %p, pthread_self: %p\n",
+ block, (void*)pthread_self());
+ dsan_register_worker_thread(context->parent_tid);
+ // Call the original dispatcher for the block.
+ context->func(context->block);
+ GET_STACK_TRACE_MALLOC;
+ dsan_free(context, stack);
+}
+
+} // namespace __dsan
+
+using namespace __dsan;
+
+// Wrap |ctxt| and |func| into an dsan_block_context_t.
+// The caller retains control of the allocated context.
+extern "C" dsan_block_context_t* alloc_dsan_context(void* ctxt,
+ dispatch_function_t func) {
+ GET_STACK_TRACE_THREAD;
+ dsan_block_context_t* dsan_ctxt =
+ (dsan_block_context_t*)dsan_malloc(sizeof(dsan_block_context_t), stack);
+ dsan_ctxt->block = ctxt;
+ dsan_ctxt->func = func;
+ dsan_ctxt->parent_tid = GetCurrentThreadId();
+ return dsan_ctxt;
+}
+
+// Define interceptor for dispatch_*_f function with the three most common
+// parameters: dispatch_queue_t, context, dispatch_function_t.
+# define INTERCEPT_DISPATCH_X_F_3(dispatch_x_f) \
+ INTERCEPTOR(void, dispatch_x_f, dispatch_queue_t dq, void* ctxt, \
+ dispatch_function_t func) { \
+ dsan_block_context_t* dsan_ctxt = alloc_dsan_context(ctxt, func); \
+ return REAL(dispatch_x_f)(dq, (void*)dsan_ctxt, \
+ dsan_dispatch_call_block_and_release); \
+ }
+
+INTERCEPT_DISPATCH_X_F_3(dispatch_async_f)
+INTERCEPT_DISPATCH_X_F_3(dispatch_sync_f)
+INTERCEPT_DISPATCH_X_F_3(dispatch_barrier_async_f)
+
+INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when, dispatch_queue_t dq,
+ void* ctxt, dispatch_function_t func) {
+ dsan_block_context_t* dsan_ctxt = alloc_dsan_context(ctxt, func);
+ return REAL(dispatch_after_f)(when, dq, (void*)dsan_ctxt,
+ dsan_dispatch_call_block_and_release);
+}
+
+INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group,
+ dispatch_queue_t dq, void* ctxt, dispatch_function_t func) {
+ dsan_block_context_t* dsan_ctxt = alloc_dsan_context(ctxt, func);
+ REAL(dispatch_group_async_f)(group, dq, (void*)dsan_ctxt,
+ dsan_dispatch_call_block_and_release);
+}
+
+# if !defined(MISSING_BLOCKS_SUPPORT)
+extern "C" {
+void dispatch_async(dispatch_queue_t dq, void (^work)(void));
+void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
+ void (^work)(void));
+void dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
+ void (^work)(void));
+void dispatch_source_set_cancel_handler(dispatch_source_t ds,
+ void (^work)(void));
+void dispatch_source_set_event_handler(dispatch_source_t ds,
+ void (^work)(void));
+}
+
+# define GET_DSAN_BLOCK(work) \
+ void (^dsan_block)(void); \
+ int parent_tid = GetCurrentThreadId(); \
+ dsan_block = ^(void) { \
+ dsan_register_worker_thread(parent_tid); \
+ work(); \
+ }
+
+INTERCEPTOR(void, dispatch_async, dispatch_queue_t dq, void (^work)(void)) {
+ GET_DSAN_BLOCK(work);
+ REAL(dispatch_async)(dq, dsan_block);
+}
+
+INTERCEPTOR(void, dispatch_group_async, dispatch_group_t dg,
+ dispatch_queue_t dq, void (^work)(void)) {
+ GET_DSAN_BLOCK(work);
+ REAL(dispatch_group_async)(dg, dq, dsan_block);
+}
+
+INTERCEPTOR(void, dispatch_after, dispatch_time_t when, dispatch_queue_t queue,
+ void (^work)(void)) {
+ GET_DSAN_BLOCK(work);
+ REAL(dispatch_after)(when, queue, dsan_block);
+}
+
+INTERCEPTOR(void, dispatch_source_set_cancel_handler, dispatch_source_t ds,
+ void (^work)(void)) {
+ if (!work) {
+ REAL(dispatch_source_set_cancel_handler)(ds, work);
+ return;
+ }
+ GET_DSAN_BLOCK(work);
+ REAL(dispatch_source_set_cancel_handler)(ds, dsan_block);
+}
+
+INTERCEPTOR(void, dispatch_source_set_event_handler, dispatch_source_t ds,
+ void (^work)(void)) {
+ GET_DSAN_BLOCK(work);
+ REAL(dispatch_source_set_event_handler)(ds, dsan_block);
+}
+# endif
+
+#endif // SANITIZER_APPLE
diff --git a/compiler-rt/lib/dsan/dsan_malloc_mac.cpp b/compiler-rt/lib/dsan/dsan_malloc_mac.cpp
new file mode 100644
index 0000000000000..515b26c49424d
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_malloc_mac.cpp
@@ -0,0 +1,66 @@
+//===-- dsan_malloc_mac.cpp -----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer (DSan).
+//
+// Mac-specific malloc interception.
+//===----------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+#if SANITIZER_APPLE
+
+# include "dsan.h"
+# include "dsan_allocator.h"
+# include "dsan_thread.h"
+
+using namespace __dsan;
+# define COMMON_MALLOC_ZONE_NAME "dsan"
+# define COMMON_MALLOC_ENTER() ENSURE_DSAN_INITED
+# define COMMON_MALLOC_SANITIZER_INITIALIZED dsan_inited
+# define COMMON_MALLOC_FORCE_LOCK()
+# define COMMON_MALLOC_FORCE_UNLOCK()
+# define COMMON_MALLOC_MEMALIGN(alignment, size) \
+ GET_STACK_TRACE_MALLOC; \
+ void* p = dsan_memalign(alignment, size, stack)
+# define COMMON_MALLOC_MALLOC(size) \
+ GET_STACK_TRACE_MALLOC; \
+ void* p = dsan_malloc(size, stack)
+# define COMMON_MALLOC_REALLOC(ptr, size) \
+ GET_STACK_TRACE_MALLOC; \
+ void* p = dsan_realloc(ptr, size, stack)
+# define COMMON_MALLOC_CALLOC(count, size) \
+ GET_STACK_TRACE_MALLOC; \
+ void* p = dsan_calloc(count, size, stack)
+# define COMMON_MALLOC_POSIX_MEMALIGN(memptr, alignment, size) \
+ GET_STACK_TRACE_MALLOC; \
+ int res = dsan_posix_memalign(memptr, alignment, size, stack)
+# define COMMON_MALLOC_VALLOC(size) \
+ GET_STACK_TRACE_MALLOC; \
+ void* p = dsan_valloc(size, stack)
+# define COMMON_MALLOC_FREE(ptr) \
+ GET_STACK_TRACE_MALLOC; \
+ dsan_free(ptr, stack)
+# define COMMON_MALLOC_FREE_SIZED(ptr, size) \
+ GET_STACK_TRACE_MALLOC; \
+ dsan_free_sized(ptr, size, stack)
+# define COMMON_MALLOC_FREE_ALIGNED_SIZED(ptr, alignment, size) \
+ GET_STACK_TRACE_MALLOC; \
+ dsan_free_aligned_sized(ptr, alignment, size, stack)
+# define COMMON_MALLOC_SIZE(ptr) uptr size = dsan_mz_size(ptr)
+# define COMMON_MALLOC_FILL_STATS(zone, stats)
+# define COMMON_MALLOC_REPORT_UNKNOWN_REALLOC(ptr, zone_ptr, zone_name) \
+ (void)zone_name; \
+ Report("mz_realloc(%p) -- attempting to realloc unallocated memory.\n", \
+ ptr);
+# define COMMON_MALLOC_NAMESPACE __dsan
+# define COMMON_MALLOC_HAS_ZONE_ENUMERATOR 0
+# define COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT 0
+
+# include "sanitizer_common/sanitizer_malloc_mac.inc"
+
+#endif // SANITIZER_APPLE
diff --git a/compiler-rt/lib/dsan/dsan_posix.cpp b/compiler-rt/lib/dsan/dsan_posix.cpp
new file mode 100644
index 0000000000000..b96488f49fd75
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_posix.cpp
@@ -0,0 +1,121 @@
+//=-- dsan_posix.cpp -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===---------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Standalone DSan RTL code common to POSIX-like systems.
+//
+//===---------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if SANITIZER_POSIX
+# include <pthread.h>
+
+# include "dsan.h"
+# include "dsan_allocator.h"
+# include "dsan_common.h"
+# include "dsan_thread.h"
+# include "sanitizer_common/sanitizer_stackdepot.h"
+# include "sanitizer_common/sanitizer_stacktrace.h"
+# include "sanitizer_common/sanitizer_tls_get_addr.h"
+
+namespace __dsan {
+
+ThreadContext::ThreadContext(int tid) : ThreadContextDsanBase(tid) {}
+
+struct OnStartedArgs {
+ uptr stack_begin;
+ uptr stack_end;
+ uptr cache_begin;
+ uptr cache_end;
+ uptr tls_begin;
+ uptr tls_end;
+ DTLS* dtls;
+};
+
+void ThreadContext::OnStarted(void* arg) {
+ ThreadContextDsanBase::OnStarted(arg);
+ auto args = reinterpret_cast<const OnStartedArgs*>(arg);
+ stack_begin_ = args->stack_begin;
+ stack_end_ = args->stack_end;
+ tls_begin_ = args->tls_begin;
+ tls_end_ = args->tls_end;
+ cache_begin_ = args->cache_begin;
+ cache_end_ = args->cache_end;
+ dtls_ = args->dtls;
+}
+
+void ThreadStart(u32 tid, ThreadID os_id, ThreadType thread_type) {
+ OnStartedArgs args;
+ GetThreadStackAndTls(tid == kMainTid, &args.stack_begin, &args.stack_end,
+ &args.tls_begin, &args.tls_end);
+ GetAllocatorCacheRange(&args.cache_begin, &args.cache_end);
+ args.dtls = DTLS_Get();
+ ThreadContextDsanBase::ThreadStart(tid, os_id, thread_type, &args);
+}
+
+bool GetThreadRangesLocked(ThreadID os_id, uptr* stack_begin, uptr* stack_end,
+ uptr* tls_begin, uptr* tls_end, uptr* cache_begin,
+ uptr* cache_end, DTLS** dtls) {
+ ThreadContext* context = static_cast<ThreadContext*>(
+ GetDsanThreadRegistryLocked()->FindThreadContextByOsIDLocked(os_id));
+ if (!context)
+ return false;
+ *stack_begin = context->stack_begin();
+ *stack_end = context->stack_end();
+ *tls_begin = context->tls_begin();
+ *tls_end = context->tls_end();
+ *cache_begin = context->cache_begin();
+ *cache_end = context->cache_end();
+ *dtls = context->dtls();
+ return true;
+}
+
+void InitializeMainThread() {
+ u32 tid = ThreadCreate(kMainTid, true);
+ CHECK_EQ(tid, kMainTid);
+ ThreadStart(tid, GetTid());
+}
+
+static void OnStackUnwind(const SignalContext& sig, const void*,
+ BufferedStackTrace* stack) {
+ stack->Unwind(StackTrace::GetNextInstructionPc(sig.pc), sig.bp, sig.context,
+ common_flags()->fast_unwind_on_fatal);
+}
+
+void DsanOnDeadlySignal(int signo, void* siginfo, void* context) {
+ HandleDeadlySignal(siginfo, context, GetCurrentThreadId(), &OnStackUnwind,
+ nullptr);
+}
+
+static void BeforeFork() {
+ VReport(2, "BeforeFork tid: %llu\n", GetTid());
+ LockThreads();
+ LockAllocator();
+ StackDepotLockBeforeFork();
+}
+
+static void AfterFork(bool fork_child) {
+ StackDepotUnlockAfterFork(fork_child);
+ UnlockAllocator();
+ UnlockThreads();
+ VReport(2, "AfterFork tid: %llu\n", GetTid());
+}
+
+void InstallAtForkHandler() {
+# if SANITIZER_SOLARIS || SANITIZER_NETBSD || SANITIZER_APPLE
+ return; // FIXME: Implement FutexWait.
+# endif
+ pthread_atfork(
+ &BeforeFork, []() { AfterFork(/* fork_child= */ false); },
+ []() { AfterFork(/* fork_child= */ true); });
+}
+
+} // namespace __dsan
+
+#endif // SANITIZER_POSIX
diff --git a/compiler-rt/lib/dsan/dsan_posix.h b/compiler-rt/lib/dsan/dsan_posix.h
new file mode 100644
index 0000000000000..03b804704db4e
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_posix.h
@@ -0,0 +1,49 @@
+//=-- dsan_posix.h -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===---------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Standalone DSan RTL code common to POSIX-like systems.
+//
+//===---------------------------------------------------------------------===//
+
+#ifndef DSAN_POSIX_H
+#define DSAN_POSIX_H
+
+#include "dsan_thread.h"
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if !SANITIZER_POSIX
+# error "dsan_posix.h is used only on POSIX-like systems (SANITIZER_POSIX)"
+#endif
+
+namespace __sanitizer {
+struct DTLS;
+}
+
+namespace __dsan {
+
+class ThreadContext final : public ThreadContextDsanBase {
+ public:
+ explicit ThreadContext(int tid);
+ void OnStarted(void* arg) override;
+ uptr tls_begin() { return tls_begin_; }
+ uptr tls_end() { return tls_end_; }
+ DTLS* dtls() { return dtls_; }
+
+ private:
+ uptr tls_begin_ = 0;
+ uptr tls_end_ = 0;
+ DTLS* dtls_ = nullptr;
+};
+
+void ThreadStart(u32 tid, ThreadID os_id,
+ ThreadType thread_type = ThreadType::Regular);
+
+} // namespace __dsan
+
+#endif // DSAN_POSIX_H
diff --git a/compiler-rt/lib/dsan/dsan_preinit.cpp b/compiler-rt/lib/dsan/dsan_preinit.cpp
new file mode 100644
index 0000000000000..4a936fd6e6422
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_preinit.cpp
@@ -0,0 +1,21 @@
+//===-- dsan_preinit.cpp --------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+//
+// Call __dsan_init at the very early stage of process startup.
+//===----------------------------------------------------------------------===//
+
+#include "dsan.h"
+
+#if SANITIZER_CAN_USE_PREINIT_ARRAY
+// This section is linked into the main executable when -fsanitize=doublefree is
+// specified to perform initialization at a very early stage.
+__attribute__((section(".preinit_array"), used)) static auto preinit =
+ __dsan_init;
+#endif
diff --git a/compiler-rt/lib/dsan/dsan_thread.cpp b/compiler-rt/lib/dsan/dsan_thread.cpp
new file mode 100644
index 0000000000000..617c1c998ad80
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_thread.cpp
@@ -0,0 +1,123 @@
+//=-- dsan_thread.cpp -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// See dsan_thread.h for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan_thread.h"
+
+#include "dsan.h"
+#include "dsan_allocator.h"
+#include "dsan_common.h"
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_placement_new.h"
+#include "sanitizer_common/sanitizer_thread_history.h"
+#include "sanitizer_common/sanitizer_thread_registry.h"
+#include "sanitizer_common/sanitizer_tls_get_addr.h"
+
+namespace __dsan {
+
+static ThreadRegistry* thread_registry;
+static ThreadArgRetval* thread_arg_retval;
+
+static Mutex mu_for_thread_context;
+static LowLevelAllocator allocator_for_thread_context;
+
+static ThreadContextBase* CreateThreadContext(u32 tid) {
+ Lock lock(&mu_for_thread_context);
+ return new (allocator_for_thread_context) ThreadContext(tid);
+}
+
+void InitializeThreads() {
+ alignas(alignof(ThreadRegistry)) static char
+ thread_registry_placeholder[sizeof(ThreadRegistry)];
+ thread_registry =
+ new (thread_registry_placeholder) ThreadRegistry(CreateThreadContext);
+
+ alignas(alignof(ThreadArgRetval)) static char
+ thread_arg_retval_placeholder[sizeof(ThreadArgRetval)];
+ thread_arg_retval = new (thread_arg_retval_placeholder) ThreadArgRetval();
+}
+
+ThreadArgRetval& GetThreadArgRetval() { return *thread_arg_retval; }
+
+ThreadContextDsanBase::ThreadContextDsanBase(int tid)
+ : ThreadContextBase(tid) {}
+
+void ThreadContextDsanBase::OnStarted(void* arg) {
+ SetCurrentThread(this);
+ AllocatorThreadStart();
+}
+
+void ThreadContextDsanBase::OnFinished() {
+ AllocatorThreadFinish();
+ DTLS_Destroy();
+ SetCurrentThread(nullptr);
+}
+
+u32 ThreadCreate(u32 parent_tid, bool detached, void* arg) {
+ return thread_registry->CreateThread(0, detached, parent_tid, arg);
+}
+
+void ThreadContextDsanBase::ThreadStart(u32 tid, ThreadID os_id,
+ ThreadType thread_type, void* arg) {
+ thread_registry->StartThread(tid, os_id, thread_type, arg);
+}
+
+void ThreadFinish() { thread_registry->FinishThread(GetCurrentThreadId()); }
+
+void EnsureMainThreadIDIsCorrect() {
+ if (GetCurrentThreadId() == kMainTid)
+ GetCurrentThread()->os_id = GetTid();
+}
+
+///// Interface to the common DSan module. /////
+
+void GetThreadExtraStackRangesLocked(ThreadID os_id,
+ InternalMmapVector<Range>* ranges) {}
+void GetThreadExtraStackRangesLocked(InternalMmapVector<Range>* ranges) {}
+
+void LockThreads() {
+ thread_registry->Lock();
+ thread_arg_retval->Lock();
+}
+
+void UnlockThreads() {
+ thread_arg_retval->Unlock();
+ thread_registry->Unlock();
+}
+
+ThreadRegistry* GetDsanThreadRegistryLocked() {
+ thread_registry->CheckLocked();
+ return thread_registry;
+}
+
+void GetRunningThreadsLocked(InternalMmapVector<ThreadID>* threads) {
+ GetDsanThreadRegistryLocked()->RunCallbackForEachThreadLocked(
+ [](ThreadContextBase* tctx, void* threads) {
+ if (tctx->status == ThreadStatusRunning) {
+ reinterpret_cast<InternalMmapVector<ThreadID>*>(threads)->push_back(
+ tctx->os_id);
+ }
+ },
+ threads);
+}
+
+void PrintThreads() {
+ InternalScopedString out;
+ PrintThreadHistory(*thread_registry, out);
+ Report("%s\n", out.data());
+}
+
+void GetAdditionalThreadContextPtrsLocked(InternalMmapVector<uptr>* ptrs) {
+ GetThreadArgRetval().GetAllPtrsLocked(ptrs);
+}
+
+} // namespace __dsan
diff --git a/compiler-rt/lib/dsan/dsan_thread.h b/compiler-rt/lib/dsan/dsan_thread.h
new file mode 100644
index 0000000000000..4e252bfc31f35
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_thread.h
@@ -0,0 +1,66 @@
+//=-- dsan_thread.h -------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DoubleFreeSanitizer.
+// Thread registry for standalone DSan.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef DSAN_THREAD_H
+#define DSAN_THREAD_H
+
+#include "sanitizer_common/sanitizer_thread_arg_retval.h"
+#include "sanitizer_common/sanitizer_thread_registry.h"
+
+namespace __dsan {
+
+class ThreadContextDsanBase : public ThreadContextBase {
+ public:
+ explicit ThreadContextDsanBase(int tid);
+ void OnStarted(void* arg) override;
+ void OnFinished() override;
+ uptr stack_begin() { return stack_begin_; }
+ uptr stack_end() { return stack_end_; }
+ uptr cache_begin() { return cache_begin_; }
+ uptr cache_end() { return cache_end_; }
+
+ // The argument is passed on to the subclass's OnStarted member function.
+ static void ThreadStart(u32 tid, ThreadID os_id, ThreadType thread_type,
+ void* onstarted_arg);
+
+ protected:
+ ~ThreadContextDsanBase() {}
+ uptr stack_begin_ = 0;
+ uptr stack_end_ = 0;
+ uptr cache_begin_ = 0;
+ uptr cache_end_ = 0;
+};
+
+// This subclass of ThreadContextDsanBase is declared in an OS-specific header.
+class ThreadContext;
+
+void InitializeThreads();
+void InitializeMainThread();
+
+ThreadRegistry* GetDsanThreadRegistryLocked();
+ThreadArgRetval& GetThreadArgRetval();
+
+u32 ThreadCreate(u32 tid, bool detached, void* arg = nullptr);
+void ThreadFinish();
+
+ThreadContextDsanBase* GetCurrentThread();
+inline u32 GetCurrentThreadId() {
+ ThreadContextDsanBase* ctx = GetCurrentThread();
+ return ctx ? ctx->tid : kInvalidTid;
+}
+void SetCurrentThread(ThreadContextDsanBase* tctx);
+void EnsureMainThreadIDIsCorrect();
+
+} // namespace __dsan
+
+#endif // DSAN_THREAD_H
diff --git a/compiler-rt/lib/dsan/weak_symbols.txt b/compiler-rt/lib/dsan/weak_symbols.txt
new file mode 100644
index 0000000000000..c60959f813550
--- /dev/null
+++ b/compiler-rt/lib/dsan/weak_symbols.txt
@@ -0,0 +1 @@
+___dsan_default_options
diff --git a/compiler-rt/test/CMakeLists.txt b/compiler-rt/test/CMakeLists.txt
index 3fab82518e75f..5e8ce9f13b670 100644
--- a/compiler-rt/test/CMakeLists.txt
+++ b/compiler-rt/test/CMakeLists.txt
@@ -58,7 +58,7 @@ endif()
umbrella_lit_testsuite_begin(check-compiler-rt)
set(COMPILER_RT_KNOWN_TEST_SUITES
- builtins;ctx_profile;fuzzer;interception;lsan;memprof;metadata
+ builtins;ctx_profile;fuzzer;interception;lsan;dsan;memprof;metadata
;orc;profile;sanitizer_common;shadowcallstack
;ubsan;xray)
list(APPEND COMPILER_RT_KNOWN_TEST_SUITES ${ALL_SANITIZERS})
@@ -136,6 +136,7 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
compiler_rt_test_runtime(interception)
compiler_rt_test_runtime(lsan)
+ compiler_rt_test_runtime(dsan)
compiler_rt_test_runtime(ubsan)
compiler_rt_test_runtime(sanitizer_common)
diff --git a/compiler-rt/test/dsan/CMakeLists.txt b/compiler-rt/test/dsan/CMakeLists.txt
new file mode 100644
index 0000000000000..858c2bbc4c8d4
--- /dev/null
+++ b/compiler-rt/test/dsan/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(DSAN_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+set(DSAN_TESTSUITES)
+
+set(DSAN_TEST_ARCH ${DSAN_SUPPORTED_ARCH})
+if(APPLE)
+ darwin_filter_host_archs(DSAN_SUPPORTED_ARCH DSAN_TEST_ARCH)
+endif()
+
+foreach(arch ${DSAN_TEST_ARCH})
+ set(DSAN_TEST_TARGET_ARCH ${arch})
+ string(TOLOWER "-${arch}" DSAN_TEST_CONFIG_SUFFIX)
+ get_test_cc_for_arch(${arch} DSAN_TEST_TARGET_CC DSAN_TEST_TARGET_CFLAGS)
+ string(TOUPPER ${arch} ARCH_UPPER_CASE)
+
+ set(DSAN_LIT_TEST_MODE "Standalone")
+ set(CONFIG_NAME ${ARCH_UPPER_CASE}DsanConfig)
+ configure_lit_site_cfg(
+ ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
+ ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py)
+ list(APPEND DSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+endforeach()
+
+set(DSAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
+list(APPEND DSAN_TEST_DEPS dsan)
+add_lit_testsuite(check-dsan "Running the DoubleFreeSanitizer tests"
+ ${DSAN_TESTSUITES}
+ DEPENDS ${DSAN_TEST_DEPS})
diff --git a/compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp b/compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp
new file mode 100644
index 0000000000000..e1061900f85d6
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp
@@ -0,0 +1,28 @@
+// REQUIRES: linux
+// RUN: %clangxx_dsan %s -pthread -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+#include <stdlib.h>
+
+static pthread_barrier_t barrier;
+static void *p;
+
+static void *Free(void *) {
+ pthread_barrier_wait(&barrier);
+ free(p);
+ return nullptr;
+}
+
+int main() {
+ pthread_t first, second;
+ p = malloc(16);
+ pthread_barrier_init(&barrier, nullptr, 2);
+ pthread_create(&first, nullptr, Free, nullptr);
+ pthread_create(&second, nullptr, Free, nullptr);
+ pthread_join(first, nullptr);
+ pthread_join(second, nullptr);
+ return 0;
+}
+
+// CHECK: ERROR: DoubleFreeSanitizer: double-free on address
diff --git a/compiler-rt/test/dsan/TestCases/double-free.c b/compiler-rt/test/dsan/TestCases/double-free.c
new file mode 100644
index 0000000000000..2f7e3a3cc7bfe
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/double-free.c
@@ -0,0 +1,17 @@
+// RUN: %clang_dsan %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main(void) {
+ void *p = malloc(16);
+ free(p);
+ free(p);
+ return 0;
+}
+
+// CHECK: ERROR: DoubleFreeSanitizer: double-free on address
+// CHECK: Second free (the invalid free) of address
+// CHECK: First free of address
+// CHECK: Original allocation of address
+// CHECK: SUMMARY: DoubleFreeSanitizer: double-free on address
diff --git a/compiler-rt/test/dsan/TestCases/invalid-free.c b/compiler-rt/test/dsan/TestCases/invalid-free.c
new file mode 100644
index 0000000000000..975d46510e9dc
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/invalid-free.c
@@ -0,0 +1,12 @@
+// RUN: %clang_dsan %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main(void) {
+ char *p = malloc(16);
+ free(p + 1);
+ return 0;
+}
+
+// CHECK: ERROR: DoubleFreeSanitizer: invalid free on address
diff --git a/compiler-rt/test/dsan/TestCases/large-double-free.c b/compiler-rt/test/dsan/TestCases/large-double-free.c
new file mode 100644
index 0000000000000..e563d31302c5d
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/large-double-free.c
@@ -0,0 +1,15 @@
+// RUN: %clang_dsan %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main(void) {
+ void *p = malloc(1 << 20);
+ free(p);
+ free(p);
+ return 0;
+}
+
+// CHECK: ERROR: DoubleFreeSanitizer: double-free on address
+// CHECK: First free of address
+// CHECK: Original allocation of address
diff --git a/compiler-rt/test/dsan/TestCases/realloc.c b/compiler-rt/test/dsan/TestCases/realloc.c
new file mode 100644
index 0000000000000..1e33af2dd5e93
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/realloc.c
@@ -0,0 +1,16 @@
+// RUN: %clang_dsan %s -o %t
+// RUN: %env_dsan_opts=allocator_may_return_null=1 %run %t
+
+#include <stdint.h>
+#include <stdlib.h>
+
+int main(void) {
+ char *p = malloc(16);
+ p[0] = 42;
+ if (realloc(p, SIZE_MAX) != NULL)
+ return 1;
+ if (p[0] != 42)
+ return 2;
+ free(p);
+ return 0;
+}
diff --git a/compiler-rt/test/dsan/TestCases/reallocarray.c b/compiler-rt/test/dsan/TestCases/reallocarray.c
new file mode 100644
index 0000000000000..444cbe86b0686
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/reallocarray.c
@@ -0,0 +1,18 @@
+// REQUIRES: linux
+// RUN: %clang_dsan %s -o %t
+// RUN: %env_dsan_opts=allocator_may_return_null=1 %run %t
+
+#define _GNU_SOURCE
+#include <stdint.h>
+#include <stdlib.h>
+
+int main(void) {
+ char *p = malloc(16);
+ p[0] = 42;
+ if (reallocarray(p, SIZE_MAX, 2) != NULL)
+ return 1;
+ if (p[0] != 42)
+ return 2;
+ free(p);
+ return 0;
+}
diff --git a/compiler-rt/test/dsan/TestCases/smoke.cpp b/compiler-rt/test/dsan/TestCases/smoke.cpp
new file mode 100644
index 0000000000000..b5713624eb542
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/smoke.cpp
@@ -0,0 +1,10 @@
+// RUN: %clangxx_dsan %s -o %t
+// RUN: %run %t
+
+#include <cstdlib>
+
+int main() {
+ void *p = std::malloc(16);
+ std::free(p);
+ return 0;
+}
diff --git a/compiler-rt/test/dsan/lit.common.cfg.py b/compiler-rt/test/dsan/lit.common.cfg.py
new file mode 100644
index 0000000000000..95f00315d7b6f
--- /dev/null
+++ b/compiler-rt/test/dsan/lit.common.cfg.py
@@ -0,0 +1,114 @@
+# -*- Python -*-
+
+# Common configuration for running double-free detection tests under DSan.
+
+import os
+import re
+
+import lit.util
+
+
+def get_required_attr(config, attr_name):
+ attr_value = getattr(config, attr_name, None)
+ if attr_value is 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.py " % attr_name
+ )
+ return attr_value
+
+
+# Setup source root.
+config.test_source_root = os.path.dirname(__file__)
+
+dsan_lit_test_mode = get_required_attr(config, "dsan_lit_test_mode")
+
+if dsan_lit_test_mode == "Standalone":
+ config.name = "DoubleFreeSanitizer-Standalone"
+ dsan_cflags = ["-fsanitize=doublefree"]
+ config.available_features.add("dsan-standalone")
+else:
+ lit_config.fatal("Unknown DSan test mode: %r" % dsan_lit_test_mode)
+config.name += config.name_suffix
+
+# Platform-specific default DSAN_OPTIONS for lit tests.
+default_common_opts_str = ":".join(list(config.default_sanitizer_opts))
+default_dsan_opts = default_common_opts_str
+if config.target_os == "Darwin":
+ # On Darwin, we default to `abort_on_error=1`, which would make tests run
+ # much slower. Let's override this and run lit tests with 'abort_on_error=0'.
+ # Also, make sure we do not overwhelm the syslog while testing.
+ default_dsan_opts += ":abort_on_error=0"
+ default_dsan_opts += ":log_to_syslog=0"
+
+if default_dsan_opts:
+ config.environment["DSAN_OPTIONS"] = default_dsan_opts
+ default_dsan_opts += ":"
+config.substitutions.append(
+ ("%env_dsan_opts=", "env DSAN_OPTIONS=" + default_dsan_opts)
+)
+
+if lit.util.which("strace"):
+ config.available_features.add("strace")
+
+clang_cflags = ["-O0", config.target_cflags] + config.debug_info_flags
+if config.android:
+ clang_cflags = clang_cflags + ["-fno-emulated-tls"]
+clang_cxxflags = config.cxx_mode_flags + clang_cflags
+dsan_incdir = config.test_source_root + "/../"
+clang_dsan_cflags = clang_cflags + dsan_cflags + ["-I%s" % dsan_incdir]
+clang_dsan_cxxflags = clang_cxxflags + dsan_cflags + ["-I%s" % dsan_incdir]
+
+config.clang_cflags = clang_cflags
+config.clang_cxxflags = clang_cxxflags
+
+
+def build_invocation(compile_flags):
+ return " " + " ".join([config.clang] + compile_flags) + " "
+
+
+config.substitutions.append(("%clang ", build_invocation(clang_cflags)))
+config.substitutions.append(("%clangxx ", build_invocation(clang_cxxflags)))
+config.substitutions.append(("%clang_dsan ", build_invocation(clang_dsan_cflags)))
+config.substitutions.append(("%clangxx_dsan ", build_invocation(clang_dsan_cxxflags)))
+
+
+# DoubleFreeSanitizer tests are currently supported on
+# Android{aarch64, x86, x86_64}, x86-64 Linux, PowerPC64 Linux, arm Linux, mips64 Linux, s390x Linux, loongarch64 Linux and x86_64 Darwin.
+supported_android = (
+ config.android
+ and config.target_arch in ["x86_64", "i386", "aarch64"]
+ and "android-thread-properties-api" in config.available_features
+)
+supported_linux = (
+ (not config.android)
+ and config.target_os == "Linux"
+ and config.host_arch
+ in [
+ "aarch64",
+ "x86_64",
+ "ppc64",
+ "ppc64le",
+ "mips64",
+ "riscv64",
+ "arm",
+ "armhf",
+ "armv7l",
+ "s390x",
+ "loongarch64",
+ ]
+)
+supported_darwin = config.target_os == "Darwin" and config.target_arch in ["x86_64"]
+supported_netbsd = config.target_os == "NetBSD" and config.target_arch in [
+ "x86_64",
+ "i386",
+]
+if not (supported_android or supported_linux or supported_darwin or supported_netbsd):
+ config.unsupported = True
+
+# Don't support Thumb due to broken fast unwinder
+if re.search("mthumb", config.target_cflags) is not None:
+ config.unsupported = True
+
+config.suffixes = [".c", ".cpp", ".mm"]
diff --git a/compiler-rt/test/dsan/lit.site.cfg.py.in b/compiler-rt/test/dsan/lit.site.cfg.py.in
new file mode 100644
index 0000000000000..a493baf439446
--- /dev/null
+++ b/compiler-rt/test/dsan/lit.site.cfg.py.in
@@ -0,0 +1,13 @@
+ at LIT_SITE_CFG_IN_HEADER@
+
+# Tool-specific config options.
+config.name_suffix = "@DSAN_TEST_CONFIG_SUFFIX@"
+config.target_cflags = "@DSAN_TEST_TARGET_CFLAGS@"
+config.dsan_lit_test_mode = "@DSAN_LIT_TEST_MODE@"
+config.target_arch = "@DSAN_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, "@DSAN_LIT_SOURCE_DIR@/lit.common.cfg.py")
>From 4105644dd4e527b5353d1538bb9c6ba1b59a53e4 Mon Sep 17 00:00:00 2001
From: "bojun.seo" <bojun.seo at lge.com>
Date: Tue, 1 Sep 2026 14:05:24 +0900
Subject: [PATCH 2/5] Revert "[sanitizer] Add DoubleFreeSanitizer (DSan)"
This reverts commit 1fabd6e50165171024d5689ff06284abc9e71b08.
---
clang/include/clang/Basic/Sanitizers.def | 3 -
clang/include/clang/Driver/SanitizerArgs.h | 5 -
clang/lib/Driver/SanitizerArgs.cpp | 6 +-
clang/lib/Driver/ToolChains/CommonArgs.cpp | 2 -
clang/lib/Driver/ToolChains/Darwin.cpp | 3 -
clang/lib/Driver/ToolChains/Fuchsia.cpp | 1 -
clang/lib/Driver/ToolChains/Linux.cpp | 3 -
clang/lib/Driver/ToolChains/NetBSD.cpp | 1 -
clang/test/Driver/fsanitize-doublefree.c | 21 -
.../cmake/Modules/AllSupportedArchDefs.cmake | 6 -
compiler-rt/cmake/config-ix.cmake | 17 -
compiler-rt/include/CMakeLists.txt | 1 -
.../include/sanitizer/dsan_interface.h | 30 -
compiler-rt/lib/CMakeLists.txt | 2 -
compiler-rt/lib/dsan/.clang-format | 3 -
compiler-rt/lib/dsan/CMakeLists.txt | 85 ---
compiler-rt/lib/dsan/dsan.cpp | 112 ----
compiler-rt/lib/dsan/dsan.h | 56 --
compiler-rt/lib/dsan/dsan_allocator.cpp | 501 ---------------
compiler-rt/lib/dsan/dsan_allocator.h | 150 -----
compiler-rt/lib/dsan/dsan_common.cpp | 31 -
compiler-rt/lib/dsan/dsan_common.h | 103 ----
compiler-rt/lib/dsan/dsan_fuchsia.cpp | 131 ----
compiler-rt/lib/dsan/dsan_fuchsia.h | 35 --
compiler-rt/lib/dsan/dsan_interceptors.cpp | 575 ------------------
compiler-rt/lib/dsan/dsan_linux.cpp | 34 --
compiler-rt/lib/dsan/dsan_mac.cpp | 234 -------
compiler-rt/lib/dsan/dsan_malloc_mac.cpp | 66 --
compiler-rt/lib/dsan/dsan_posix.cpp | 121 ----
compiler-rt/lib/dsan/dsan_posix.h | 49 --
compiler-rt/lib/dsan/dsan_preinit.cpp | 21 -
compiler-rt/lib/dsan/dsan_thread.cpp | 123 ----
compiler-rt/lib/dsan/dsan_thread.h | 66 --
compiler-rt/lib/dsan/weak_symbols.txt | 1 -
compiler-rt/test/CMakeLists.txt | 3 +-
compiler-rt/test/dsan/CMakeLists.txt | 28 -
.../dsan/TestCases/concurrent-double-free.cpp | 28 -
compiler-rt/test/dsan/TestCases/double-free.c | 17 -
.../test/dsan/TestCases/invalid-free.c | 12 -
.../test/dsan/TestCases/large-double-free.c | 15 -
compiler-rt/test/dsan/TestCases/realloc.c | 16 -
.../test/dsan/TestCases/reallocarray.c | 18 -
compiler-rt/test/dsan/TestCases/smoke.cpp | 10 -
compiler-rt/test/dsan/lit.common.cfg.py | 114 ----
compiler-rt/test/dsan/lit.site.cfg.py.in | 13 -
45 files changed, 2 insertions(+), 2870 deletions(-)
delete mode 100644 clang/test/Driver/fsanitize-doublefree.c
delete mode 100644 compiler-rt/include/sanitizer/dsan_interface.h
delete mode 100644 compiler-rt/lib/dsan/.clang-format
delete mode 100644 compiler-rt/lib/dsan/CMakeLists.txt
delete mode 100644 compiler-rt/lib/dsan/dsan.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan.h
delete mode 100644 compiler-rt/lib/dsan/dsan_allocator.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_allocator.h
delete mode 100644 compiler-rt/lib/dsan/dsan_common.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_common.h
delete mode 100644 compiler-rt/lib/dsan/dsan_fuchsia.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_fuchsia.h
delete mode 100644 compiler-rt/lib/dsan/dsan_interceptors.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_linux.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_mac.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_malloc_mac.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_posix.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_posix.h
delete mode 100644 compiler-rt/lib/dsan/dsan_preinit.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_thread.cpp
delete mode 100644 compiler-rt/lib/dsan/dsan_thread.h
delete mode 100644 compiler-rt/lib/dsan/weak_symbols.txt
delete mode 100644 compiler-rt/test/dsan/CMakeLists.txt
delete mode 100644 compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp
delete mode 100644 compiler-rt/test/dsan/TestCases/double-free.c
delete mode 100644 compiler-rt/test/dsan/TestCases/invalid-free.c
delete mode 100644 compiler-rt/test/dsan/TestCases/large-double-free.c
delete mode 100644 compiler-rt/test/dsan/TestCases/realloc.c
delete mode 100644 compiler-rt/test/dsan/TestCases/reallocarray.c
delete mode 100644 compiler-rt/test/dsan/TestCases/smoke.cpp
delete mode 100644 compiler-rt/test/dsan/lit.common.cfg.py
delete mode 100644 compiler-rt/test/dsan/lit.site.cfg.py.in
diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def
index 137833fa40b1a..da85431625026 100644
--- a/clang/include/clang/Basic/Sanitizers.def
+++ b/clang/include/clang/Basic/Sanitizers.def
@@ -198,9 +198,6 @@ SANITIZER("scudo", Scudo)
// AllocToken
SANITIZER("alloc-token", AllocToken)
-// DoubleFreeSanitizer
-SANITIZER("doublefree", DoubleFree)
-
// Magic group, containing all sanitizers. For example, "-fno-sanitize=all"
// can be used to disable all the sanitizers.
SANITIZER_GROUP("all", All, ~SanitizerMask())
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 6e774c5ef8c77..6a01b3e36d44c 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -111,11 +111,6 @@ class SanitizerArgs {
!Sanitizers.has(SanitizerKind::Address) &&
!Sanitizers.has(SanitizerKind::HWAddress);
}
- bool needsDsanRt() const {
- return Sanitizers.has(SanitizerKind::DoubleFree) &&
- !Sanitizers.has(SanitizerKind::Address) &&
- !Sanitizers.has(SanitizerKind::HWAddress);
- }
bool needsFuzzerInterceptors() const;
bool needsUbsanRt() const;
bool needsUbsanCXXRt() const;
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index c1c113d91274a..c77ba78122a81 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -48,8 +48,7 @@ static const SanitizerMask SupportsCoverage =
SanitizerKind::Type | SanitizerKind::MemtagStack |
SanitizerKind::MemtagHeap | SanitizerKind::MemtagGlobals |
SanitizerKind::Memory | SanitizerKind::KernelMemory | SanitizerKind::Leak |
- SanitizerKind::DoubleFree | SanitizerKind::Undefined |
- SanitizerKind::Integer | SanitizerKind::Bounds |
+ SanitizerKind::Undefined | SanitizerKind::Integer | SanitizerKind::Bounds |
SanitizerKind::ImplicitConversion | SanitizerKind::Nullability |
SanitizerKind::DataFlow | SanitizerKind::Fuzzer |
SanitizerKind::FuzzerNoLink | SanitizerKind::FloatDivideByZero |
@@ -710,9 +709,6 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
std::make_pair(SanitizerKind::Thread, SanitizerKind::Memory),
std::make_pair(SanitizerKind::Leak,
SanitizerKind::Thread | SanitizerKind::Memory),
- std::make_pair(SanitizerKind::DoubleFree,
- SanitizerKind::Leak | SanitizerKind::Thread |
- SanitizerKind::Memory | SanitizerKind::Scudo),
std::make_pair(SanitizerKind::KernelAddress,
SanitizerKind::Address | SanitizerKind::Leak |
SanitizerKind::Thread | SanitizerKind::Memory),
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 019880ca24253..883296e43111b 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1747,8 +1747,6 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
StaticRuntimes.push_back("dfsan");
if (SanArgs.needsLsanRt())
StaticRuntimes.push_back("lsan");
- if (SanArgs.needsDsanRt())
- StaticRuntimes.push_back("dsan");
if (SanArgs.needsMsanRt()) {
StaticRuntimes.push_back("msan");
if (SanArgs.linkCXXRuntimes())
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index a491e0cf3a74f..d3de04fc5155e 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -1760,8 +1760,6 @@ void DarwinClang::AddLinkRuntimeLibArgs(const ArgList &Args,
}
if (Sanitize.needsLsanRt())
AddLinkSanitizerLibArgs(Args, CmdArgs, "lsan");
- if (Sanitize.needsDsanRt())
- AddLinkSanitizerLibArgs(Args, CmdArgs, "dsan");
if (Sanitize.needsUbsanRt()) {
assert(Sanitize.needsSharedRt() &&
"Static sanitizer runtimes not supported");
@@ -4066,7 +4064,6 @@ Darwin::getSupportedSanitizers(BoundArch BA,
Res |= SanitizerKind::PointerSubtract;
Res |= SanitizerKind::Realtime;
Res |= SanitizerKind::Leak;
- Res |= SanitizerKind::DoubleFree;
Res |= SanitizerKind::Fuzzer;
Res |= SanitizerKind::FuzzerNoLink;
Res |= SanitizerKind::ObjCCast;
diff --git a/clang/lib/Driver/ToolChains/Fuchsia.cpp b/clang/lib/Driver/ToolChains/Fuchsia.cpp
index 36cadd7db9bf5..abde9fa10482d 100644
--- a/clang/lib/Driver/ToolChains/Fuchsia.cpp
+++ b/clang/lib/Driver/ToolChains/Fuchsia.cpp
@@ -483,7 +483,6 @@ Fuchsia::getSupportedSanitizers(BoundArch BA,
Res |= SanitizerKind::Fuzzer;
Res |= SanitizerKind::FuzzerNoLink;
Res |= SanitizerKind::Leak;
- Res |= SanitizerKind::DoubleFree;
Res |= SanitizerKind::Scudo;
Res |= SanitizerKind::Thread;
if (getTriple().getArch() == llvm::Triple::x86_64 ||
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 486d22e16145a..1ab385a9ea001 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -997,9 +997,6 @@ Linux::getSupportedSanitizers(BoundArch BA,
if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 ||
IsRISCV64 || IsSystemZ || IsHexagon || IsLoongArch64)
Res |= SanitizerKind::Leak;
- if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 ||
- IsRISCV64 || IsSystemZ || IsHexagon || IsLoongArch64)
- Res |= SanitizerKind::DoubleFree;
if (IsX86_64 || IsMIPS64 || IsAArch64 || IsPowerPC64 || IsSystemZ ||
IsLoongArch64 || IsRISCV64)
Res |= SanitizerKind::Thread;
diff --git a/clang/lib/Driver/ToolChains/NetBSD.cpp b/clang/lib/Driver/ToolChains/NetBSD.cpp
index dab65e9b46995..f03114b53bb61 100644
--- a/clang/lib/Driver/ToolChains/NetBSD.cpp
+++ b/clang/lib/Driver/ToolChains/NetBSD.cpp
@@ -521,7 +521,6 @@ NetBSD::getSupportedSanitizers(BoundArch BA,
Res |= SanitizerKind::PointerCompare;
Res |= SanitizerKind::PointerSubtract;
Res |= SanitizerKind::Leak;
- Res |= SanitizerKind::DoubleFree;
Res |= SanitizerKind::SafeStack;
Res |= SanitizerKind::Scudo;
Res |= SanitizerKind::Vptr;
diff --git a/clang/test/Driver/fsanitize-doublefree.c b/clang/test/Driver/fsanitize-doublefree.c
deleted file mode 100644
index f8a7bf26c0994..0000000000000
--- a/clang/test/Driver/fsanitize-doublefree.c
+++ /dev/null
@@ -1,21 +0,0 @@
-// RUN: %clang --target=x86_64-linux-gnu -fsanitize=doublefree %s -### 2>&1 | FileCheck %s --check-prefix=DSAN
-// DSAN: "-fsanitize=doublefree"
-// DSAN: libclang_rt.dsan
-
-// RUN: %clang --target=x86_64-linux-gnu -fsanitize=doublefree,undefined %s -### 2>&1 | FileCheck %s --check-prefix=DSAN-UBSAN
-// DSAN-UBSAN: libclang_rt.dsan
-// DSAN-UBSAN: libclang_rt.ubsan_standalone
-
-// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=doublefree,leak %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=DSAN-LEAK
-// DSAN-LEAK: '-fsanitize=doublefree' not allowed with '-fsanitize=leak'
-
-// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=doublefree,scudo %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=DSAN-SCUDO
-// DSAN-SCUDO: '-fsanitize=doublefree' not allowed with '-fsanitize=scudo'
-
-// RUN: not %clang --target=x86_64-unknown-freebsd -fsanitize=doublefree %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=FREEBSD
-// FREEBSD: unsupported option '-fsanitize=doublefree' for target 'x86_64-unknown-freebsd'
-
-// RUN: not %clang --target=wasm32-unknown-emscripten -fsanitize=doublefree %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=EMSCRIPTEN
-// EMSCRIPTEN: unsupported option '-fsanitize=doublefree' for target 'wasm32-unknown-emscripten'
-
-int main(void) { return 0; }
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index fffd2c69f03fb..9c9874d94a1f2 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -85,12 +85,6 @@ else()
set(ALL_LSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64} ${ARM32}
${PPC64} ${S390X} ${RISCV64} ${HEXAGON} ${LOONGARCH64})
endif()
-if(APPLE)
- set(ALL_DSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64})
-else()
- set(ALL_DSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64} ${ARM32}
- ${PPC64} ${S390X} ${RISCV64} ${HEXAGON} ${LOONGARCH64})
-endif()
if (OS_NAME MATCHES "FreeBSD")
set(ALL_MSAN_SUPPORTED_ARCH ${X86_64} ${ARM64})
else()
diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake
index a36fe8f2d27a0..083f1c98d0f16 100644
--- a/compiler-rt/cmake/config-ix.cmake
+++ b/compiler-rt/cmake/config-ix.cmake
@@ -482,7 +482,6 @@ if(APPLE)
set(ORC_SUPPORTED_OS)
set(UBSAN_SUPPORTED_OS osx)
set(LSAN_SUPPORTED_OS osx)
- set(DSAN_SUPPORTED_OS osx)
set(STATS_SUPPORTED_OS osx)
# FIXME: Support a general COMPILER_RT_ENABLE_OSX to match other platforms.
@@ -580,7 +579,6 @@ if(APPLE)
list(APPEND ORC_SUPPORTED_OS ${platform}sim)
list(APPEND UBSAN_SUPPORTED_OS ${platform}sim)
list(APPEND LSAN_SUPPORTED_OS ${platform}sim)
- list(APPEND DSAN_SUPPORTED_OS ${platform}sim)
list(APPEND STATS_SUPPORTED_OS ${platform}sim)
endif()
foreach(arch ${DARWIN_${platform}sim_ARCHS})
@@ -616,7 +614,6 @@ if(APPLE)
list(APPEND UBSAN_SUPPORTED_OS ${platform})
list(APPEND TYSAN_SUPPORTED_OS ${platform})
list(APPEND LSAN_SUPPORTED_OS ${platform})
- list(APPEND DSAN_SUPPORTED_OS ${platform})
list(APPEND STATS_SUPPORTED_OS ${platform})
endif()
foreach(arch ${DARWIN_${platform}_ARCHS})
@@ -639,7 +636,6 @@ if(APPLE)
COMPILER_RT_SUPPORTED_ARCH
)
set(LSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH})
- set(DSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH})
set(UBSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH})
set(ASAN_ABI_SUPPORTED_ARCH ${ALL_ASAN_ABI_SUPPORTED_ARCH})
list_intersect(ASAN_SUPPORTED_ARCH
@@ -657,9 +653,6 @@ if(APPLE)
list_intersect(LSAN_SUPPORTED_ARCH
ALL_LSAN_SUPPORTED_ARCH
SANITIZER_COMMON_SUPPORTED_ARCH)
- list_intersect(DSAN_SUPPORTED_ARCH
- ALL_DSAN_SUPPORTED_ARCH
- SANITIZER_COMMON_SUPPORTED_ARCH)
list_intersect(MSAN_SUPPORTED_ARCH
ALL_MSAN_SUPPORTED_ARCH
SANITIZER_COMMON_SUPPORTED_ARCH)
@@ -720,8 +713,6 @@ else()
# supported by other sanitizers (even if they build into dummy object files).
filter_available_targets(LSAN_COMMON_SUPPORTED_ARCH
${SANITIZER_COMMON_SUPPORTED_ARCH})
- filter_available_targets(DSAN_COMMON_SUPPORTED_ARCH
- ${SANITIZER_COMMON_SUPPORTED_ARCH})
filter_available_targets(UBSAN_COMMON_SUPPORTED_ARCH
${ALL_UBSAN_SUPPORTED_ARCH})
filter_available_targets(ASAN_SUPPORTED_ARCH ${ALL_ASAN_SUPPORTED_ARCH})
@@ -729,7 +720,6 @@ else()
filter_available_targets(FUZZER_SUPPORTED_ARCH ${ALL_FUZZER_SUPPORTED_ARCH})
filter_available_targets(DFSAN_SUPPORTED_ARCH ${ALL_DFSAN_SUPPORTED_ARCH})
filter_available_targets(LSAN_SUPPORTED_ARCH ${ALL_LSAN_SUPPORTED_ARCH})
- filter_available_targets(DSAN_SUPPORTED_ARCH ${ALL_DSAN_SUPPORTED_ARCH})
filter_available_targets(MSAN_SUPPORTED_ARCH ${ALL_MSAN_SUPPORTED_ARCH})
filter_available_targets(HWASAN_SUPPORTED_ARCH ${ALL_HWASAN_SUPPORTED_ARCH})
filter_available_targets(MEMPROF_SUPPORTED_ARCH ${ALL_MEMPROF_SUPPORTED_ARCH})
@@ -842,13 +832,6 @@ else()
set(COMPILER_RT_HAS_LSAN FALSE)
endif()
-if (COMPILER_RT_HAS_SANITIZER_COMMON AND DSAN_SUPPORTED_ARCH AND
- OS_NAME MATCHES "Android|Darwin|Linux|NetBSD|Fuchsia")
- set(COMPILER_RT_HAS_DSAN TRUE)
-else()
- set(COMPILER_RT_HAS_DSAN FALSE)
-endif()
-
if (COMPILER_RT_HAS_SANITIZER_COMMON AND MSAN_SUPPORTED_ARCH AND
OS_NAME MATCHES "Linux|FreeBSD|NetBSD")
set(COMPILER_RT_HAS_MSAN TRUE)
diff --git a/compiler-rt/include/CMakeLists.txt b/compiler-rt/include/CMakeLists.txt
index 1045a1836a9fb..eb998478b081b 100644
--- a/compiler-rt/include/CMakeLists.txt
+++ b/compiler-rt/include/CMakeLists.txt
@@ -5,7 +5,6 @@ if (COMPILER_RT_BUILD_SANITIZERS)
sanitizer/common_interface_defs.h
sanitizer/coverage_interface.h
sanitizer/dfsan_interface.h
- sanitizer/dsan_interface.h
sanitizer/hwasan_interface.h
sanitizer/linux_syscall_hooks.h
sanitizer/lsan_interface.h
diff --git a/compiler-rt/include/sanitizer/dsan_interface.h b/compiler-rt/include/sanitizer/dsan_interface.h
deleted file mode 100644
index a545b7206678e..0000000000000
--- a/compiler-rt/include/sanitizer/dsan_interface.h
+++ /dev/null
@@ -1,30 +0,0 @@
-//===-- sanitizer/dsan_interface.h ------------------------------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer (DSan).
-//
-// Public interface header.
-//===----------------------------------------------------------------------===//
-#ifndef SANITIZER_DSAN_INTERFACE_H
-#define SANITIZER_DSAN_INTERFACE_H
-
-#include <sanitizer/common_interface_defs.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// This function may be optionally provided by user and should return
-// a string containing common sanitizer runtime options.
-const char *SANITIZER_CDECL __dsan_default_options(void);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif // SANITIZER_DSAN_INTERFACE_H
diff --git a/compiler-rt/lib/CMakeLists.txt b/compiler-rt/lib/CMakeLists.txt
index 5fe86db866c33..a5b2fbb38762c 100644
--- a/compiler-rt/lib/CMakeLists.txt
+++ b/compiler-rt/lib/CMakeLists.txt
@@ -45,8 +45,6 @@ if(COMPILER_RT_BUILD_SANITIZERS)
add_subdirectory(stats)
# Contains RTLSanCommon used even without COMPILER_RT_HAS_LSAN.
add_subdirectory(lsan)
- # Contains RTDSanCommon used even without COMPILER_RT_HAS_DSAN.
- add_subdirectory(dsan)
# Contains RTUbsan used even without COMPILER_RT_HAS_UBSAN.
add_subdirectory(ubsan)
endif()
diff --git a/compiler-rt/lib/dsan/.clang-format b/compiler-rt/lib/dsan/.clang-format
deleted file mode 100644
index 1f2a97030379d..0000000000000
--- a/compiler-rt/lib/dsan/.clang-format
+++ /dev/null
@@ -1,3 +0,0 @@
-BasedOnStyle: Google
-AllowShortIfStatementsOnASingleLine: false
-IndentPPDirectives: AfterHash
diff --git a/compiler-rt/lib/dsan/CMakeLists.txt b/compiler-rt/lib/dsan/CMakeLists.txt
deleted file mode 100644
index 616c28bc6add1..0000000000000
--- a/compiler-rt/lib/dsan/CMakeLists.txt
+++ /dev/null
@@ -1,85 +0,0 @@
-include_directories(..)
-
-set(DSAN_CFLAGS ${SANITIZER_COMMON_CFLAGS})
-append_rtti_flag(OFF DSAN_CFLAGS)
-
-# Too many existing bugs, needs cleanup.
-append_list_if(COMPILER_RT_HAS_WNO_FORMAT -Wno-format DSAN_CFLAGS)
-
-set(DSAN_COMMON_SOURCES
- dsan_common.cpp
- )
-
-set(DSAN_SOURCES
- dsan.cpp
- dsan_allocator.cpp
- dsan_fuchsia.cpp
- dsan_interceptors.cpp
- dsan_linux.cpp
- dsan_mac.cpp
- dsan_malloc_mac.cpp
- dsan_posix.cpp
- dsan_preinit.cpp
- dsan_thread.cpp
- )
-
-set(DSAN_HEADERS
- dsan.h
- dsan_allocator.h
- dsan_common.h
- dsan_thread.h
- )
-
-set(DSAN_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
-
-# Shared DSan runtime functionality.
-add_compiler_rt_object_libraries(RTDSanCommon
- OS ${SANITIZER_COMMON_SUPPORTED_OS}
- ARCHS ${DSAN_COMMON_SUPPORTED_ARCH}
- SOURCES ${DSAN_COMMON_SOURCES}
- ADDITIONAL_HEADERS ${DSAN_HEADERS}
- CFLAGS ${DSAN_CFLAGS})
-
-if(COMPILER_RT_HAS_DSAN)
- add_compiler_rt_component(dsan)
- if(APPLE)
- set(DSAN_LINK_LIBS ${SANITIZER_COMMON_LINK_LIBS})
-
- add_weak_symbols("dsan" WEAK_SYMBOL_LINK_FLAGS)
- add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS)
-
- add_compiler_rt_runtime(clang_rt.dsan
- SHARED
- OS ${DSAN_SUPPORTED_OS}
- ARCHS ${DSAN_SUPPORTED_ARCH}
- SOURCES ${DSAN_SOURCES}
- ADDITIONAL_HEADERS ${DSAN_HEADERS}
- OBJECT_LIBS RTDSanCommon
- RTInterception
- RTSanitizerCommon
- RTSanitizerCommonLibc
- RTSanitizerCommonCoverage
- RTSanitizerCommonSymbolizer
- CFLAGS ${DSAN_CFLAGS}
- LINK_FLAGS ${SANITIZER_COMMON_LINK_FLAGS} ${WEAK_SYMBOL_LINK_FLAGS}
- LINK_LIBS ${DSAN_LINK_LIBS}
- PARENT_TARGET dsan)
- else()
- foreach(arch ${DSAN_SUPPORTED_ARCH})
- add_compiler_rt_runtime(clang_rt.dsan
- STATIC
- ARCHS ${arch}
- SOURCES ${DSAN_SOURCES}
- $<TARGET_OBJECTS:RTInterception.${arch}>
- $<TARGET_OBJECTS:RTSanitizerCommon.${arch}>
- $<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}>
- $<TARGET_OBJECTS:RTSanitizerCommonCoverage.${arch}>
- $<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}>
- $<TARGET_OBJECTS:RTSanitizerCommonSymbolizerInternal.${arch}>
- $<TARGET_OBJECTS:RTDSanCommon.${arch}>
- ADDITIONAL_HEADERS ${DSAN_HEADERS}
- CFLAGS ${DSAN_CFLAGS}
- PARENT_TARGET dsan)
- endforeach()
- endif()
-endif()
diff --git a/compiler-rt/lib/dsan/dsan.cpp b/compiler-rt/lib/dsan/dsan.cpp
deleted file mode 100644
index 6c134122b120e..0000000000000
--- a/compiler-rt/lib/dsan/dsan.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-//=-- dsan.cpp ------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Standalone DSan RTL.
-//
-//===----------------------------------------------------------------------===//
-
-#include "dsan.h"
-
-#include "dsan_allocator.h"
-#include "dsan_common.h"
-#include "dsan_thread.h"
-#include "sanitizer_common/sanitizer_flag_parser.h"
-#include "sanitizer_common/sanitizer_flags.h"
-#include "sanitizer_common/sanitizer_interface_internal.h"
-
-bool dsan_inited;
-bool dsan_init_is_running;
-
-namespace __dsan {
-
-///// Interface to the common DSan module. /////
-bool WordIsPoisoned(uptr addr) { return false; }
-
-} // namespace __dsan
-
-void __sanitizer::BufferedStackTrace::UnwindImpl(uptr pc, uptr bp,
- void* context,
- bool request_fast,
- u32 max_depth) {
- using namespace __dsan;
- uptr stack_top = 0, stack_bottom = 0;
- if (ThreadContextDsanBase* t = GetCurrentThread()) {
- stack_top = t->stack_end();
- stack_bottom = t->stack_begin();
- }
- if (SANITIZER_MIPS && !IsValidFrame(bp, stack_top, stack_bottom))
- return;
- bool fast = StackTrace::WillUseFastUnwind(request_fast);
- Unwind(max_depth, pc, bp, context, stack_top, stack_bottom, fast);
-}
-
-using namespace __dsan;
-
-static void InitializeFlags() {
- // Set all the default values.
- SetCommonFlagsDefaults();
- {
- CommonFlags cf;
- cf.CopyFrom(*common_flags());
- cf.external_symbolizer_path = GetEnv("DSAN_SYMBOLIZER_PATH");
- cf.malloc_context_size = 30;
- cf.intercept_tls_get_addr = true;
- cf.detect_leaks = false;
- cf.exitcode = 77;
- OverrideCommonFlags(cf);
- }
-
- FlagParser parser;
- RegisterCommonFlags(&parser);
-
- // Override from user-specified string.
- const char* dsan_default_options = __dsan_default_options();
- parser.ParseString(dsan_default_options);
- parser.ParseStringFromEnv("DSAN_OPTIONS");
-
- InitializeCommonFlags();
-
- if (Verbosity())
- ReportUnrecognizedFlags();
-
- if (common_flags()->help)
- parser.PrintFlagDescriptions();
-
- __sanitizer_set_report_path(common_flags()->log_path);
-}
-
-extern "C" void __dsan_init() {
- CHECK(!dsan_init_is_running);
- if (dsan_inited)
- return;
- dsan_init_is_running = true;
- SanitizerToolName = "DoubleFreeSanitizer";
- CacheBinaryName();
- AvoidCVE_2016_2143();
- InitializeFlags();
- InitializePlatformEarly();
- InitCommonDsan();
- InitializeAllocator();
- ReplaceSystemMalloc();
- InitializeInterceptors();
- InitializeThreads();
- InstallDeadlySignalHandlers(DsanOnDeadlySignal);
- InitializeMainThread();
- InstallAtForkHandler();
-
- InitializeCoverage(common_flags()->coverage, common_flags()->coverage_dir);
-
- dsan_inited = true;
- dsan_init_is_running = false;
-}
-
-extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_print_stack_trace() {
- GET_STACK_TRACE_FATAL;
- stack.Print();
-}
diff --git a/compiler-rt/lib/dsan/dsan.h b/compiler-rt/lib/dsan/dsan.h
deleted file mode 100644
index bd7d1eb9c24fe..0000000000000
--- a/compiler-rt/lib/dsan/dsan.h
+++ /dev/null
@@ -1,56 +0,0 @@
-//=-- dsan.h --------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Private header for standalone DSan RTL.
-//
-//===----------------------------------------------------------------------===//
-
-#include "dsan_thread.h"
-#if SANITIZER_POSIX
-# include "dsan_posix.h"
-#elif SANITIZER_FUCHSIA
-# include "dsan_fuchsia.h"
-#endif
-#include "sanitizer_common/sanitizer_flags.h"
-#include "sanitizer_common/sanitizer_stacktrace.h"
-
-#define GET_STACK_TRACE(max_size, fast) \
- __sanitizer::BufferedStackTrace stack; \
- stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, fast, \
- max_size);
-
-#define GET_STACK_TRACE_FATAL \
- GET_STACK_TRACE(kStackTraceMax, common_flags()->fast_unwind_on_fatal)
-
-#define GET_STACK_TRACE_MALLOC \
- GET_STACK_TRACE(__sanitizer::common_flags()->malloc_context_size, \
- common_flags()->fast_unwind_on_malloc)
-
-#define GET_STACK_TRACE_THREAD GET_STACK_TRACE(kStackTraceMax, true)
-
-namespace __dsan {
-
-void InitializeInterceptors();
-void ReplaceSystemMalloc();
-void DsanOnDeadlySignal(int signo, void* siginfo, void* context);
-void InstallAtForkHandler();
-
-#define ENSURE_DSAN_INITED \
- do { \
- CHECK(!dsan_init_is_running); \
- if (!dsan_inited) \
- __dsan_init(); \
- } while (0)
-
-} // namespace __dsan
-
-extern bool dsan_inited;
-extern bool dsan_init_is_running;
-
-extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __dsan_init();
diff --git a/compiler-rt/lib/dsan/dsan_allocator.cpp b/compiler-rt/lib/dsan/dsan_allocator.cpp
deleted file mode 100644
index 471fb18415a03..0000000000000
--- a/compiler-rt/lib/dsan/dsan_allocator.cpp
+++ /dev/null
@@ -1,501 +0,0 @@
-//=-- dsan_allocator.cpp --------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// See dsan_allocator.h for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "dsan_allocator.h"
-
-#include "sanitizer_common/sanitizer_allocator.h"
-#include "sanitizer_common/sanitizer_allocator_checks.h"
-#include "sanitizer_common/sanitizer_allocator_interface.h"
-#include "sanitizer_common/sanitizer_allocator_report.h"
-#include "sanitizer_common/sanitizer_atomic.h"
-#include "sanitizer_common/sanitizer_errno.h"
-#include "sanitizer_common/sanitizer_internal_defs.h"
-#include "sanitizer_common/sanitizer_report_decorator.h"
-#include "sanitizer_common/sanitizer_stackdepot.h"
-#include "sanitizer_common/sanitizer_stacktrace.h"
-
-extern "C" void* memset(void* ptr, int value, uptr num);
-
-namespace __dsan {
-#if defined(__i386__) || defined(__arm__)
-static const uptr kMaxAllowedMallocSize = 1ULL << 30;
-#elif defined(__mips64) || defined(__aarch64__)
-static const uptr kMaxAllowedMallocSize = 4ULL << 30;
-#else
-static const uptr kMaxAllowedMallocSize = 1ULL << 40;
-#endif
-
-static Allocator allocator;
-
-static uptr max_malloc_size;
-
-struct SecondaryTombstone {
- void* ptr;
- u32 alloc_stack_id;
- u32 free_stack_id;
-};
-
-static constexpr uptr kSecondaryTombstoneLimit = 65536;
-static Mutex secondary_tombstones_mutex;
-static InternalMmapVector<SecondaryTombstone> secondary_tombstones;
-static uptr next_secondary_tombstone;
-
-static constexpr uptr kPrimaryQuarantineSize = 1024;
-static Mutex primary_quarantine_mutex;
-static InternalMmapVector<void*> primary_quarantine;
-static uptr next_primary_quarantine;
-
-static bool FindSecondaryTombstone(void* p, SecondaryTombstone* result) {
- Lock lock(&secondary_tombstones_mutex);
- for (uptr i = 0; i != secondary_tombstones.size(); ++i) {
- if (secondary_tombstones[i].ptr == p) {
- *result = secondary_tombstones[i];
- return true;
- }
- }
- return false;
-}
-
-static void AddSecondaryTombstone(void* p, const ChunkMetadata* m) {
- Lock lock(&secondary_tombstones_mutex);
- const SecondaryTombstone tombstone = {p, m->alloc_stack_id, m->free_stack_id};
- if (secondary_tombstones.size() < kSecondaryTombstoneLimit) {
- secondary_tombstones.push_back(tombstone);
- return;
- }
- secondary_tombstones[next_secondary_tombstone] = tombstone;
- next_secondary_tombstone =
- (next_secondary_tombstone + 1) % kSecondaryTombstoneLimit;
-}
-
-static void RemoveSecondaryTombstone(void* p) {
- Lock lock(&secondary_tombstones_mutex);
- for (uptr i = 0; i != secondary_tombstones.size(); ++i) {
- if (secondary_tombstones[i].ptr != p)
- continue;
- secondary_tombstones[i] = secondary_tombstones.back();
- secondary_tombstones.pop_back();
- return;
- }
-}
-
-static void QuarantinePrimary(void* p) {
- void* released = nullptr;
- {
- Lock lock(&primary_quarantine_mutex);
- if (primary_quarantine.size() < kPrimaryQuarantineSize) {
- primary_quarantine.push_back(p);
- } else {
- released = primary_quarantine[next_primary_quarantine];
- primary_quarantine[next_primary_quarantine] = p;
- next_primary_quarantine =
- (next_primary_quarantine + 1) % kPrimaryQuarantineSize;
- }
- }
- if (released)
- allocator.Deallocate(GetAllocatorCache(), released);
-}
-
-void InitializeAllocator() {
- SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
- allocator.InitLinkerInitialized(
- common_flags()->allocator_release_to_os_interval_ms);
- if (common_flags()->max_allocation_size_mb)
- max_malloc_size = Min(common_flags()->max_allocation_size_mb << 20,
- kMaxAllowedMallocSize);
- else
- max_malloc_size = kMaxAllowedMallocSize;
-}
-
-void AllocatorThreadStart() { allocator.InitCache(GetAllocatorCache()); }
-
-void AllocatorThreadFinish() {
- allocator.SwallowCache(GetAllocatorCache());
- allocator.DestroyCache(GetAllocatorCache());
-}
-
-static ChunkMetadata* Metadata(const void* p) {
- return reinterpret_cast<ChunkMetadata*>(allocator.GetMetaData(p));
-}
-
-static void RegisterAllocation(const StackTrace& stack, void* p, uptr size) {
- if (!p)
- return;
- if (!allocator.FromPrimary(p))
- RemoveSecondaryTombstone(p);
- ChunkMetadata* m = Metadata(p);
- CHECK(m);
- m->alloc_stack_id = StackDepotPut(stack);
- m->free_stack_id = 0;
- m->requested_size = size;
- atomic_store(reinterpret_cast<atomic_uint8_t*>(m), kChunkAllocated,
- memory_order_release);
- RunMallocHooks(p, size);
-}
-
-// Report double-free and terminate.
-static void NORETURN ReportDoubleFree(void* p, u32 alloc_stack_id,
- u32 first_free_stack_id,
- const StackTrace& free_stack) {
- class Decorator : public __sanitizer::SanitizerCommonDecorator {
- public:
- Decorator() : SanitizerCommonDecorator() {}
- const char* Error() { return Red(); }
- const char* Info() { return Blue(); }
- };
-
- Decorator d;
- Printf("\n");
- Printf("%s", d.Error());
- Report("ERROR: DoubleFreeSanitizer: double-free on address %p\n", p);
- Printf("%s", d.Default());
-
- // Print the second free (current) backtrace.
- Printf("\n");
- Printf("%s", d.Info());
- Printf("Second free (the invalid free) of address %p:\n", p);
- Printf("%s", d.Default());
- free_stack.Print();
-
- // Print the first free backtrace.
- if (first_free_stack_id) {
- Printf("\n");
- Printf("%s", d.Info());
- Printf("First free of address %p:\n", p);
- Printf("%s", d.Default());
- StackDepotGet(first_free_stack_id).Print();
- }
-
- // Print the original allocation backtrace.
- if (alloc_stack_id) {
- Printf("\n");
- Printf("%s", d.Info());
- Printf("Original allocation of address %p:\n", p);
- Printf("%s", d.Default());
- StackDepotGet(alloc_stack_id).Print();
- }
-
- Printf("\n");
- Printf("SUMMARY: DoubleFreeSanitizer: double-free on address %p\n", p);
- Die();
-}
-
-static void NORETURN ReportInvalidFree(void* p, const StackTrace& stack) {
- Report("ERROR: DoubleFreeSanitizer: invalid free on address %p\n", p);
- stack.Print();
- Die();
-}
-
-static ChunkMetadata* GetChunkMetadata(void* p, const StackTrace& stack) {
- if (!p)
- return nullptr;
- if (!allocator.PointerIsMine(p)) {
- SecondaryTombstone tombstone = {};
- if (FindSecondaryTombstone(p, &tombstone))
- ReportDoubleFree(p, tombstone.alloc_stack_id, tombstone.free_stack_id,
- stack);
- ReportInvalidFree(p, stack);
- }
- if (allocator.GetBlockBegin(p) != p)
- ReportInvalidFree(p, stack);
- ChunkMetadata* m = Metadata(p);
- if (atomic_load(reinterpret_cast<atomic_uint8_t*>(m), memory_order_acquire) ==
- kChunkInvalid)
- ReportInvalidFree(p, stack);
- return m;
-}
-
-static void RegisterDeallocation(const StackTrace& stack, void* p) {
- ChunkMetadata* m = GetChunkMetadata(p, stack);
- if (!m)
- return;
-
- u8 expected = kChunkAllocated;
- if (!atomic_compare_exchange_strong(reinterpret_cast<atomic_uint8_t*>(m),
- &expected, kChunkFreeing,
- memory_order_acquire)) {
- while (expected == kChunkFreeing)
- expected = atomic_load(reinterpret_cast<atomic_uint8_t*>(m),
- memory_order_acquire);
- if (expected == kChunkFreed)
- ReportDoubleFree(p, m->alloc_stack_id, m->free_stack_id, stack);
- ReportInvalidFree(p, stack);
- }
-
- m->free_stack_id = StackDepotPut(stack);
- atomic_store(reinterpret_cast<atomic_uint8_t*>(m), kChunkFreed,
- memory_order_release);
- RunFreeHooks(p);
-}
-
-static void* ReportAllocationSizeTooBig(uptr size, const StackTrace& stack) {
- if (AllocatorMayReturnNull()) {
- Report("WARNING: DoubleFreeSanitizer failed to allocate 0x%zx bytes\n",
- size);
- return nullptr;
- }
- ReportAllocationSizeTooBig(size, max_malloc_size, &stack);
-}
-
-void* Allocate(const StackTrace& stack, uptr size, uptr alignment,
- bool cleared) {
- if (size == 0)
- size = 1;
- if (size > max_malloc_size)
- return ReportAllocationSizeTooBig(size, stack);
- if (UNLIKELY(IsRssLimitExceeded())) {
- if (AllocatorMayReturnNull())
- return nullptr;
- ReportRssLimitExceeded(&stack);
- }
- void* p = allocator.Allocate(GetAllocatorCache(), size, alignment);
- if (UNLIKELY(!p)) {
- SetAllocatorOutOfMemory();
- if (AllocatorMayReturnNull())
- return nullptr;
- ReportOutOfMemory(size, &stack);
- }
- if (cleared && allocator.FromPrimary(p))
- memset(p, 0, size);
- RegisterAllocation(stack, p, size);
- return p;
-}
-
-static void* Calloc(uptr nmemb, uptr size, const StackTrace& stack) {
- if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
- if (AllocatorMayReturnNull())
- return nullptr;
- ReportCallocOverflow(nmemb, size, &stack);
- }
- size *= nmemb;
- return Allocate(stack, size, 1, true);
-}
-
-void Deallocate(const StackTrace& stack, void* p) {
- if (!p)
- return;
- RegisterDeallocation(stack, p);
- if (allocator.FromPrimary(p)) {
- QuarantinePrimary(p);
- } else {
- AddSecondaryTombstone(p, Metadata(p));
- allocator.Deallocate(GetAllocatorCache(), p);
- }
-}
-
-void* Reallocate(const StackTrace& stack, void* p, uptr new_size,
- uptr alignment) {
- if (!p)
- return Allocate(stack, new_size, alignment, kAlwaysClearMemory);
- if (!new_size) {
- Deallocate(stack, p);
- return nullptr;
- }
- if (new_size > max_malloc_size) {
- ReportAllocationSizeTooBig(new_size, stack);
- return nullptr;
- }
- ChunkMetadata* m = GetChunkMetadata(p, stack);
- if (atomic_load(reinterpret_cast<atomic_uint8_t*>(m), memory_order_acquire) !=
- kChunkAllocated)
- ReportDoubleFree(p, m->alloc_stack_id, m->free_stack_id, stack);
- const uptr old_size = m->requested_size;
- void* new_p = Allocate(stack, new_size, alignment, kAlwaysClearMemory);
- if (!new_p)
- return nullptr;
- internal_memcpy(new_p, p, Min(old_size, new_size));
- Deallocate(stack, p);
- return new_p;
-}
-
-void GetAllocatorCacheRange(uptr* begin, uptr* end) {
- *begin = (uptr)GetAllocatorCache();
- *end = *begin + sizeof(AllocatorCache);
-}
-
-static const void* GetMallocBegin(const void* p) {
- if (!p)
- return nullptr;
- void* beg = allocator.GetBlockBegin(p);
- if (!beg)
- return nullptr;
- ChunkMetadata* m = Metadata(beg);
- if (!m)
- return nullptr;
- if (atomic_load(reinterpret_cast<atomic_uint8_t*>(m), memory_order_acquire) !=
- kChunkAllocated)
- return nullptr;
- if (m->requested_size == 0)
- return nullptr;
- return (const void*)beg;
-}
-
-uptr GetMallocUsableSize(const void* p) {
- if (!p)
- return 0;
- ChunkMetadata* m = Metadata(p);
- if (!m)
- return 0;
- return m->requested_size;
-}
-
-uptr GetMallocUsableSizeFast(const void* p) {
- return Metadata(p)->requested_size;
-}
-
-int dsan_posix_memalign(void** memptr, uptr alignment, uptr size,
- const StackTrace& stack) {
- if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) {
- if (AllocatorMayReturnNull())
- return errno_EINVAL;
- ReportInvalidPosixMemalignAlignment(alignment, &stack);
- }
- void* ptr = Allocate(stack, size, alignment, kAlwaysClearMemory);
- if (UNLIKELY(!ptr))
- return errno_ENOMEM;
- CHECK(IsAligned((uptr)ptr, alignment));
- *memptr = ptr;
- return 0;
-}
-
-void* dsan_aligned_alloc(uptr alignment, uptr size, const StackTrace& stack) {
- if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(alignment, size))) {
- errno = errno_EINVAL;
- if (AllocatorMayReturnNull())
- return nullptr;
- ReportInvalidAlignedAllocAlignment(size, alignment, &stack);
- }
- return SetErrnoOnNull(Allocate(stack, size, alignment, kAlwaysClearMemory));
-}
-
-void* dsan_memalign(uptr alignment, uptr size, const StackTrace& stack) {
- if (UNLIKELY(!IsPowerOfTwo(alignment))) {
- errno = errno_EINVAL;
- if (AllocatorMayReturnNull())
- return nullptr;
- ReportInvalidAllocationAlignment(alignment, &stack);
- }
- return SetErrnoOnNull(Allocate(stack, size, alignment, kAlwaysClearMemory));
-}
-
-void* dsan_malloc(uptr size, const StackTrace& stack) {
- return SetErrnoOnNull(Allocate(stack, size, 1, kAlwaysClearMemory));
-}
-
-void dsan_free(void* p, const StackTrace& stack) { Deallocate(stack, p); }
-
-void dsan_free_sized(void* p, uptr, const StackTrace& stack) {
- Deallocate(stack, p);
-}
-
-void dsan_free_aligned_sized(void* p, uptr, uptr, const StackTrace& stack) {
- Deallocate(stack, p);
-}
-
-void* dsan_realloc(void* p, uptr size, const StackTrace& stack) {
- return SetErrnoOnNull(Reallocate(stack, p, size, 1));
-}
-
-void* dsan_reallocarray(void* ptr, uptr nmemb, uptr size,
- const StackTrace& stack) {
- if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
- errno = errno_ENOMEM;
- if (AllocatorMayReturnNull())
- return nullptr;
- ReportReallocArrayOverflow(nmemb, size, &stack);
- }
- return dsan_realloc(ptr, nmemb * size, stack);
-}
-
-void* dsan_calloc(uptr nmemb, uptr size, const StackTrace& stack) {
- return SetErrnoOnNull(Calloc(nmemb, size, stack));
-}
-
-void* dsan_valloc(uptr size, const StackTrace& stack) {
- return SetErrnoOnNull(
- Allocate(stack, size, GetPageSizeCached(), kAlwaysClearMemory));
-}
-
-void* dsan_pvalloc(uptr size, const StackTrace& stack) {
- uptr PageSize = GetPageSizeCached();
- if (UNLIKELY(CheckForPvallocOverflow(size, PageSize))) {
- errno = errno_ENOMEM;
- if (AllocatorMayReturnNull())
- return nullptr;
- ReportPvallocOverflow(size, &stack);
- }
- size = size ? RoundUpTo(size, PageSize) : PageSize;
- return SetErrnoOnNull(Allocate(stack, size, PageSize, kAlwaysClearMemory));
-}
-
-uptr dsan_mz_size(const void* p) { return GetMallocUsableSize(p); }
-
-void LockAllocator() { allocator.ForceLock(); }
-
-void UnlockAllocator() { allocator.ForceUnlock(); }
-
-} // namespace __dsan
-
-using namespace __dsan;
-
-extern "C" {
-SANITIZER_INTERFACE_ATTRIBUTE
-uptr __sanitizer_get_current_allocated_bytes() {
- uptr stats[AllocatorStatCount];
- allocator.GetStats(stats);
- return stats[AllocatorStatAllocated];
-}
-
-SANITIZER_INTERFACE_ATTRIBUTE
-uptr __sanitizer_get_heap_size() {
- uptr stats[AllocatorStatCount];
- allocator.GetStats(stats);
- return stats[AllocatorStatMapped];
-}
-
-SANITIZER_INTERFACE_ATTRIBUTE
-uptr __sanitizer_get_free_bytes() { return 1; }
-
-SANITIZER_INTERFACE_ATTRIBUTE
-uptr __sanitizer_get_unmapped_bytes() { return 0; }
-
-SANITIZER_INTERFACE_ATTRIBUTE
-uptr __sanitizer_get_estimated_allocated_size(uptr size) { return size; }
-
-SANITIZER_INTERFACE_ATTRIBUTE
-int __sanitizer_get_ownership(const void* p) {
- return GetMallocBegin(p) != nullptr;
-}
-
-SANITIZER_INTERFACE_ATTRIBUTE
-const void* __sanitizer_get_allocated_begin(const void* p) {
- return GetMallocBegin(p);
-}
-
-SANITIZER_INTERFACE_ATTRIBUTE
-uptr __sanitizer_get_allocated_size(const void* p) {
- return GetMallocUsableSize(p);
-}
-
-SANITIZER_INTERFACE_ATTRIBUTE
-uptr __sanitizer_get_allocated_size_fast(const void* p) {
- DCHECK_EQ(p, __sanitizer_get_allocated_begin(p));
- uptr ret = GetMallocUsableSizeFast(p);
- DCHECK_EQ(ret, __sanitizer_get_allocated_size(p));
- return ret;
-}
-
-SANITIZER_INTERFACE_ATTRIBUTE
-void __sanitizer_purge_allocator() { allocator.ForceReleaseToOS(); }
-
-} // extern "C"
diff --git a/compiler-rt/lib/dsan/dsan_allocator.h b/compiler-rt/lib/dsan/dsan_allocator.h
deleted file mode 100644
index 31ee1071b2811..0000000000000
--- a/compiler-rt/lib/dsan/dsan_allocator.h
+++ /dev/null
@@ -1,150 +0,0 @@
-//=-- dsan_allocator.h ----------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Allocator for standalone DSan.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef DSAN_ALLOCATOR_H
-#define DSAN_ALLOCATOR_H
-
-#include "sanitizer_common/sanitizer_allocator.h"
-#include "sanitizer_common/sanitizer_common.h"
-#include "sanitizer_common/sanitizer_internal_defs.h"
-
-namespace __dsan {
-
-void* Allocate(const StackTrace& stack, uptr size, uptr alignment,
- bool cleared);
-void Deallocate(const StackTrace& stack, void* p);
-void* Reallocate(const StackTrace& stack, void* p, uptr new_size,
- uptr alignment);
-uptr GetMallocUsableSize(const void* p);
-
-void GetAllocatorCacheRange(uptr* begin, uptr* end);
-void AllocatorThreadStart();
-void AllocatorThreadFinish();
-void InitializeAllocator();
-
-const bool kAlwaysClearMemory = true;
-
-struct ChunkMetadata {
- u8 state; // Must be first. See ChunkState.
-#if SANITIZER_WORDSIZE == 64
- uptr requested_size : 56;
-#else
- uptr requested_size : 32;
- uptr padding2 : 24;
-#endif
- u32 alloc_stack_id; // Stack trace of the allocation
- u32 free_stack_id; // Stack trace of the first free
-};
-
-enum ChunkState : u8 {
- kChunkInvalid,
- kChunkAllocated,
- kChunkFreeing,
- kChunkFreed,
-};
-
-#if !SANITIZER_CAN_USE_ALLOCATOR64
-template <typename AddressSpaceViewTy>
-struct AP32 {
- static const uptr kSpaceBeg = SANITIZER_MMAP_BEGIN;
- static const u64 kSpaceSize = SANITIZER_MMAP_RANGE_SIZE;
- static const uptr kMetadataSize = sizeof(ChunkMetadata);
- typedef __sanitizer::CompactSizeClassMap SizeClassMap;
- static const uptr kRegionSizeLog = 20;
- using AddressSpaceView = AddressSpaceViewTy;
- typedef NoOpMapUnmapCallback MapUnmapCallback;
- static const uptr kFlags = 0;
-};
-template <typename AddressSpaceView>
-using PrimaryAllocatorASVT = SizeClassAllocator32<AP32<AddressSpaceView>>;
-using PrimaryAllocator = PrimaryAllocatorASVT<LocalAddressSpaceView>;
-#else
-# if SANITIZER_FUCHSIA || defined(__powerpc64__)
-const uptr kAllocatorSpace = ~(uptr)0;
-# if SANITIZER_RISCV64
-// See the comments in compiler-rt/lib/asan/asan_allocator.h for why these
-// values were chosen.
-const uptr kAllocatorSize = UINT64_C(1) << 33; // 8GB
-using DSanSizeClassMap = SizeClassMap</*kNumBits=*/2,
- /*kMinSizeLog=*/5,
- /*kMidSizeLog=*/8,
- /*kMaxSizeLog=*/18,
- /*kNumCachedHintT=*/8,
- /*kMaxBytesCachedLog=*/10>;
-static_assert(DSanSizeClassMap::kNumClassesRounded <= 32,
- "32 size classes is the optimal number to ensure tests run "
- "efficiently on Fuchsia.");
-# else
-const uptr kAllocatorSize = 0x40000000000ULL; // 4T.
-using DSanSizeClassMap = DefaultSizeClassMap;
-# endif
-# elif SANITIZER_RISCV64
-const uptr kAllocatorSpace = ~(uptr)0;
-const uptr kAllocatorSize = 0x2000000000ULL; // 128G.
-using DSanSizeClassMap = DefaultSizeClassMap;
-# elif SANITIZER_APPLE
-const uptr kAllocatorSpace = 0x600000000000ULL;
-const uptr kAllocatorSize = 0x40000000000ULL; // 4T.
-using DSanSizeClassMap = DefaultSizeClassMap;
-# elif SANITIZER_ANDROID && defined(__aarch64__)
-const uptr kAllocatorSpace = 0x3000000000ULL;
-const uptr kAllocatorSize = 0x2000000000ULL;
-using DSanSizeClassMap = VeryCompactSizeClassMap;
-# else
-const uptr kAllocatorSpace = 0x500000000000ULL;
-const uptr kAllocatorSize = 0x40000000000ULL; // 4T.
-using DSanSizeClassMap = DefaultSizeClassMap;
-# endif
-template <typename AddressSpaceViewTy>
-struct AP64 { // Allocator64 parameters. Deliberately using a short name.
- static const uptr kSpaceBeg = kAllocatorSpace;
- static const uptr kSpaceSize = kAllocatorSize;
- static const uptr kMetadataSize = sizeof(ChunkMetadata);
- using SizeClassMap = DSanSizeClassMap;
- typedef NoOpMapUnmapCallback MapUnmapCallback;
- static const uptr kFlags = 0;
- using AddressSpaceView = AddressSpaceViewTy;
-};
-
-template <typename AddressSpaceView>
-using PrimaryAllocatorASVT = SizeClassAllocator64<AP64<AddressSpaceView>>;
-using PrimaryAllocator = PrimaryAllocatorASVT<LocalAddressSpaceView>;
-#endif
-
-template <typename AddressSpaceView>
-using AllocatorASVT = CombinedAllocator<PrimaryAllocatorASVT<AddressSpaceView>>;
-using Allocator = AllocatorASVT<LocalAddressSpaceView>;
-using AllocatorCache = Allocator::AllocatorCache;
-
-Allocator::AllocatorCache* GetAllocatorCache();
-
-int dsan_posix_memalign(void** memptr, uptr alignment, uptr size,
- const StackTrace& stack);
-void* dsan_aligned_alloc(uptr alignment, uptr size, const StackTrace& stack);
-void* dsan_memalign(uptr alignment, uptr size, const StackTrace& stack);
-void* dsan_malloc(uptr size, const StackTrace& stack);
-void dsan_free(void* p, const StackTrace& stack);
-void dsan_free_sized(void* p, uptr size, const StackTrace& stack);
-void dsan_free_aligned_sized(void* p, uptr alignment, uptr size,
- const StackTrace& stack);
-void* dsan_realloc(void* p, uptr size, const StackTrace& stack);
-void* dsan_reallocarray(void* p, uptr nmemb, uptr size,
- const StackTrace& stack);
-void* dsan_calloc(uptr nmemb, uptr size, const StackTrace& stack);
-void* dsan_valloc(uptr size, const StackTrace& stack);
-void* dsan_pvalloc(uptr size, const StackTrace& stack);
-uptr dsan_mz_size(const void* p);
-
-} // namespace __dsan
-
-#endif // DSAN_ALLOCATOR_H
diff --git a/compiler-rt/lib/dsan/dsan_common.cpp b/compiler-rt/lib/dsan/dsan_common.cpp
deleted file mode 100644
index 4a8a454075861..0000000000000
--- a/compiler-rt/lib/dsan/dsan_common.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-//=-- dsan_common.cpp -----------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Implementation of common double-free checking functionality.
-//
-//===----------------------------------------------------------------------===//
-
-#include "dsan_common.h"
-
-#include "sanitizer_common/sanitizer_common.h"
-
-namespace __dsan {
-
-void InitCommonDsan() {
- // DoubleFreeSanitizer doesn't need complex initialization.
- // Detection is done inline in RegisterDeallocation.
-}
-
-} // namespace __dsan
-
-extern "C" {
-SANITIZER_INTERFACE_WEAK_DEF(const char*, __dsan_default_options, void) {
- return "";
-}
-} // extern "C"
diff --git a/compiler-rt/lib/dsan/dsan_common.h b/compiler-rt/lib/dsan/dsan_common.h
deleted file mode 100644
index 63bf7fa5156ec..0000000000000
--- a/compiler-rt/lib/dsan/dsan_common.h
+++ /dev/null
@@ -1,103 +0,0 @@
-//=-- dsan_common.h -------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Private DSan header.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef DSAN_COMMON_H
-#define DSAN_COMMON_H
-
-#include "sanitizer_common/sanitizer_common.h"
-#include "sanitizer_common/sanitizer_internal_defs.h"
-#include "sanitizer_common/sanitizer_platform.h"
-#include "sanitizer_common/sanitizer_range.h"
-#include "sanitizer_common/sanitizer_stackdepot.h"
-#include "sanitizer_common/sanitizer_symbolizer.h"
-#include "sanitizer_common/sanitizer_thread_registry.h"
-
-// DoubleFreeSanitizer can run on most platforms that support sanitizers.
-#if SANITIZER_ANDROID && (__ANDROID_API__ < 28 || defined(__arm__))
-# define CAN_SANITIZE_DOUBLE_FREE 0
-#elif (SANITIZER_LINUX || SANITIZER_APPLE) && (SANITIZER_WORDSIZE == 64) && \
- (defined(__x86_64__) || defined(__mips64) || defined(__aarch64__) || \
- defined(__powerpc64__) || defined(__s390x__))
-# define CAN_SANITIZE_DOUBLE_FREE 1
-#elif defined(__i386__) && (SANITIZER_LINUX || SANITIZER_APPLE)
-# define CAN_SANITIZE_DOUBLE_FREE 1
-#elif defined(__arm__) && SANITIZER_LINUX
-# define CAN_SANITIZE_DOUBLE_FREE 1
-#elif defined(__hexagon__) && SANITIZER_LINUX
-# define CAN_SANITIZE_DOUBLE_FREE 1
-#elif SANITIZER_LOONGARCH64 && SANITIZER_LINUX
-# define CAN_SANITIZE_DOUBLE_FREE 1
-#elif SANITIZER_RISCV64 && SANITIZER_LINUX
-# define CAN_SANITIZE_DOUBLE_FREE 1
-#elif SANITIZER_NETBSD || SANITIZER_FUCHSIA
-# define CAN_SANITIZE_DOUBLE_FREE 1
-#else
-# define CAN_SANITIZE_DOUBLE_FREE 0
-#endif
-
-namespace __sanitizer {
-class ThreadRegistry;
-class ThreadContextBase;
-struct DTLS;
-} // namespace __sanitizer
-
-namespace __dsan {
-
-// Returns true if [addr, addr + sizeof(void *)) is poisoned.
-bool WordIsPoisoned(uptr addr);
-
-//// --------------------------------------------------------------------------
-//// Thread prototypes.
-//// --------------------------------------------------------------------------
-
-void LockThreads() SANITIZER_NO_THREAD_SAFETY_ANALYSIS;
-void UnlockThreads() SANITIZER_NO_THREAD_SAFETY_ANALYSIS;
-void EnsureMainThreadIDIsCorrect();
-
-bool GetThreadRangesLocked(ThreadID os_id, uptr* stack_begin, uptr* stack_end,
- uptr* tls_begin, uptr* tls_end, uptr* cache_begin,
- uptr* cache_end, DTLS** dtls);
-void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr>* caches);
-void GetThreadExtraStackRangesLocked(InternalMmapVector<Range>* ranges);
-void GetThreadExtraStackRangesLocked(ThreadID os_id,
- InternalMmapVector<Range>* ranges);
-void GetAdditionalThreadContextPtrsLocked(InternalMmapVector<uptr>* ptrs);
-void GetRunningThreadsLocked(InternalMmapVector<ThreadID>* threads);
-void PrintThreads();
-
-//// --------------------------------------------------------------------------
-//// Allocator prototypes.
-//// --------------------------------------------------------------------------
-
-void LockAllocator();
-void UnlockAllocator();
-
-void GetAllocatorCacheRange(uptr* begin, uptr* end);
-
-void InitCommonDsan();
-
-// Forward declaration - defined in dsan_thread.h
-class ThreadContextDsanBase;
-
-ThreadContextDsanBase* GetCurrentThread();
-void SetCurrentThread(ThreadContextDsanBase* tctx);
-
-} // namespace __dsan
-
-extern "C" {
-SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE const char*
-__dsan_default_options();
-
-} // extern "C"
-
-#endif // DSAN_COMMON_H
diff --git a/compiler-rt/lib/dsan/dsan_fuchsia.cpp b/compiler-rt/lib/dsan/dsan_fuchsia.cpp
deleted file mode 100644
index f4c698e3574af..0000000000000
--- a/compiler-rt/lib/dsan/dsan_fuchsia.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-//=-- dsan_fuchsia.cpp ---------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===---------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Standalone DSan RTL code specific to Fuchsia.
-//
-//===---------------------------------------------------------------------===//
-
-#include "sanitizer_common/sanitizer_platform.h"
-
-#if SANITIZER_FUCHSIA
-# include <zircon/sanitizer.h>
-
-# include "dsan.h"
-# include "dsan_allocator.h"
-
-using namespace __dsan;
-
-namespace __sanitizer {
-// DSan doesn't need to do anything else special in the startup hook.
-void EarlySanitizerInit() {}
-} // namespace __sanitizer
-
-namespace __dsan {
-
-void DsanOnDeadlySignal(int signo, void* siginfo, void* context) {}
-
-ThreadContext::ThreadContext(int tid) : ThreadContextDsanBase(tid) {}
-
-struct OnCreatedArgs {
- uptr stack_begin, stack_end;
-};
-
-// On Fuchsia, the stack bounds of a new thread are available before
-// the thread itself has started running.
-void ThreadContext::OnCreated(void* arg) {
- // Stack bounds passed through from __sanitizer_before_thread_create_hook
- // or InitializeMainThread.
- auto args = reinterpret_cast<const OnCreatedArgs*>(arg);
- stack_begin_ = args->stack_begin;
- stack_end_ = args->stack_end;
-}
-
-struct OnStartedArgs {
- uptr cache_begin, cache_end;
-};
-
-void ThreadContext::OnStarted(void* arg) {
- ThreadContextDsanBase::OnStarted(arg);
- auto args = reinterpret_cast<const OnStartedArgs*>(arg);
- cache_begin_ = args->cache_begin;
- cache_end_ = args->cache_end;
-}
-
-void ThreadStart(u32 tid) {
- OnStartedArgs args;
- GetAllocatorCacheRange(&args.cache_begin, &args.cache_end);
- CHECK_EQ(args.cache_end - args.cache_begin, sizeof(AllocatorCache));
- ThreadContextDsanBase::ThreadStart(tid, GetTid(), ThreadType::Regular, &args);
-}
-
-void InitializeMainThread() {
- OnCreatedArgs args;
- __sanitizer::GetThreadStackTopAndBottom(true, &args.stack_end,
- &args.stack_begin);
- u32 tid = ThreadCreate(kMainTid, true, &args);
- CHECK_EQ(tid, 0);
- ThreadStart(tid);
-}
-
-void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr>* caches) {
- GetDsanThreadRegistryLocked()->RunCallbackForEachThreadLocked(
- [](ThreadContextBase* tctx, void* arg) {
- auto ctx = static_cast<ThreadContext*>(tctx);
- static_cast<decltype(caches)>(arg)->push_back(ctx->cache_begin());
- },
- caches);
-}
-
-// DoubleFreeSanitizer doesn't need atexit check.
-void InstallAtForkHandler() {}
-
-} // namespace __dsan
-
-// These are declared (in extern "C") by <zircon/sanitizer.h>.
-// The system runtime will call our definitions directly.
-
-// This is called before each thread creation is attempted. So, in
-// its first call, the calling thread is the initial and sole thread.
-void* __sanitizer_before_thread_create_hook(thrd_t thread, bool detached,
- const char* name, void* stack_base,
- size_t stack_size) {
- ENSURE_DSAN_INITED;
- EnsureMainThreadIDIsCorrect();
- OnCreatedArgs args;
- args.stack_begin = reinterpret_cast<uptr>(stack_base);
- args.stack_end = args.stack_begin + stack_size;
- u32 parent_tid = GetCurrentThreadId();
- u32 tid = ThreadCreate(parent_tid, detached, &args);
- return reinterpret_cast<void*>(static_cast<uptr>(tid));
-}
-
-// This is called after creating a new thread (in the creating thread),
-// with the pointer returned by __sanitizer_before_thread_create_hook (above).
-void __sanitizer_thread_create_hook(void* hook, thrd_t thread, int error) {
- u32 tid = static_cast<u32>(reinterpret_cast<uptr>(hook));
- // On success, there is nothing to do here.
- if (error != thrd_success) {
- // Clean up the thread registry for the thread creation that didn't happen.
- GetDsanThreadRegistryLocked()->FinishThread(tid);
- }
-}
-
-// This is called in the newly-created thread before it runs anything else,
-// with the pointer returned by __sanitizer_before_thread_create_hook (above).
-void __sanitizer_thread_start_hook(void* hook, thrd_t self) {
- u32 tid = static_cast<u32>(reinterpret_cast<uptr>(hook));
- ThreadStart(tid);
-}
-
-// Each thread runs this just before it exits,
-// with the pointer returned by BeforeThreadCreateHook (above).
-// All per-thread destructors have already been called.
-void __sanitizer_thread_exit_hook(void* hook, thrd_t self) { ThreadFinish(); }
-
-#endif // SANITIZER_FUCHSIA
diff --git a/compiler-rt/lib/dsan/dsan_fuchsia.h b/compiler-rt/lib/dsan/dsan_fuchsia.h
deleted file mode 100644
index e60fc4f2ac2d9..0000000000000
--- a/compiler-rt/lib/dsan/dsan_fuchsia.h
+++ /dev/null
@@ -1,35 +0,0 @@
-//=-- dsan_fuchsia.h ---------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===---------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Standalone DSan RTL code specific to Fuchsia.
-//
-//===---------------------------------------------------------------------===//
-
-#ifndef DSAN_FUCHSIA_H
-#define DSAN_FUCHSIA_H
-
-#include "dsan_thread.h"
-#include "sanitizer_common/sanitizer_platform.h"
-
-#if !SANITIZER_FUCHSIA
-# error "dsan_fuchsia.h is used only on Fuchsia systems (SANITIZER_FUCHSIA)"
-#endif
-
-namespace __dsan {
-
-class ThreadContext final : public ThreadContextDsanBase {
- public:
- explicit ThreadContext(int tid);
- void OnCreated(void* arg) override;
- void OnStarted(void* arg) override;
-};
-
-} // namespace __dsan
-
-#endif // DSAN_FUCHSIA_H
diff --git a/compiler-rt/lib/dsan/dsan_interceptors.cpp b/compiler-rt/lib/dsan/dsan_interceptors.cpp
deleted file mode 100644
index 8ddb6d58239e7..0000000000000
--- a/compiler-rt/lib/dsan/dsan_interceptors.cpp
+++ /dev/null
@@ -1,575 +0,0 @@
-//=-- dsan_interceptors.cpp -----------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Interceptors for standalone DSan.
-//
-//===----------------------------------------------------------------------===//
-
-#include "interception/interception.h"
-#include "sanitizer_common/sanitizer_allocator.h"
-#include "sanitizer_common/sanitizer_allocator_checks.h"
-#include "sanitizer_common/sanitizer_allocator_dlsym.h"
-#include "sanitizer_common/sanitizer_allocator_report.h"
-#include "sanitizer_common/sanitizer_atomic.h"
-#include "sanitizer_common/sanitizer_common.h"
-#include "sanitizer_common/sanitizer_errno.h"
-#include "sanitizer_common/sanitizer_flags.h"
-#include "sanitizer_common/sanitizer_internal_defs.h"
-#include "sanitizer_common/sanitizer_linux.h"
-#include "sanitizer_common/sanitizer_platform_interceptors.h"
-#include "sanitizer_common/sanitizer_platform_limits_netbsd.h"
-#include "sanitizer_common/sanitizer_platform_limits_posix.h"
-#if SANITIZER_POSIX
-# include "sanitizer_common/sanitizer_posix.h"
-#endif
-#include <stddef.h>
-
-#include "dsan.h"
-#include "dsan_allocator.h"
-#include "dsan_common.h"
-#include "dsan_thread.h"
-
-using namespace __dsan;
-
-extern "C" {
-int pthread_attr_init(void* attr);
-int pthread_attr_destroy(void* attr);
-int pthread_attr_getdetachstate(void* attr, int* v);
-int pthread_key_create(unsigned* key, void (*destructor)(void* v));
-int pthread_setspecific(unsigned key, const void* v);
-}
-
-struct DlsymAlloc : DlSymAllocator<DlsymAlloc> {
- static bool UseImpl() { return dsan_init_is_running; }
- static void OnAllocate(const void* ptr, uptr size) {
- (void)ptr;
- (void)size;
- }
- static void OnFree(const void* ptr, uptr size) {
- (void)ptr;
- (void)size;
- }
-};
-
-///// Malloc/free interceptors. /////
-
-namespace std {
-struct nothrow_t;
-enum class align_val_t : size_t;
-} // namespace std
-
-#if !SANITIZER_APPLE
-INTERCEPTOR(void*, malloc, uptr size) {
- if (DlsymAlloc::Use())
- return DlsymAlloc::Allocate(size);
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_malloc(size, stack);
-}
-
-INTERCEPTOR(void, free, void* p) {
- if (UNLIKELY(!p))
- return;
- if (DlsymAlloc::PointerIsMine(p))
- return DlsymAlloc::Free(p);
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- dsan_free(p, stack);
-}
-
-# if SANITIZER_INTERCEPT_FREE_SIZED
-INTERCEPTOR(void, free_sized, void* p, uptr size) {
- if (UNLIKELY(!p))
- return;
- if (DlsymAlloc::PointerIsMine(p))
- return DlsymAlloc::Free(p);
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- dsan_free_sized(p, size, stack);
-}
-# define DSAN_MAYBE_INTERCEPT_FREE_SIZED INTERCEPT_FUNCTION(free_sized)
-# else
-# define DSAN_MAYBE_INTERCEPT_FREE_SIZED
-# endif
-
-# if SANITIZER_INTERCEPT_FREE_ALIGNED_SIZED
-INTERCEPTOR(void, free_aligned_sized, void* p, uptr alignment, uptr size) {
- if (UNLIKELY(!p))
- return;
- if (DlsymAlloc::PointerIsMine(p))
- return DlsymAlloc::Free(p);
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- dsan_free_aligned_sized(p, alignment, size, stack);
-}
-# define DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED \
- INTERCEPT_FUNCTION(free_aligned_sized)
-# else
-# define DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED
-# endif
-
-INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
- if (DlsymAlloc::Use())
- return DlsymAlloc::Callocate(nmemb, size);
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_calloc(nmemb, size, stack);
-}
-
-INTERCEPTOR(void*, realloc, void* ptr, uptr size) {
- if (DlsymAlloc::Use() || DlsymAlloc::PointerIsMine(ptr))
- return DlsymAlloc::Realloc(ptr, size);
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_realloc(ptr, size, stack);
-}
-
-INTERCEPTOR(void*, reallocarray, void* q, uptr nmemb, uptr size) {
- if (DlsymAlloc::Use() || DlsymAlloc::PointerIsMine(q)) {
- if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
- errno = errno_ENOMEM;
- return nullptr;
- }
- return DlsymAlloc::Realloc(q, nmemb * size);
- }
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_reallocarray(q, nmemb, size, stack);
-}
-
-INTERCEPTOR(int, posix_memalign, void** memptr, uptr alignment, uptr size) {
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_posix_memalign(memptr, alignment, size, stack);
-}
-
-INTERCEPTOR(void*, valloc, uptr size) {
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_valloc(size, stack);
-}
-#else
-# define DSAN_MAYBE_INTERCEPT_FREE_SIZED
-# define DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED
-#endif // !SANITIZER_APPLE
-
-#if SANITIZER_INTERCEPT_MEMALIGN
-INTERCEPTOR(void*, memalign, uptr alignment, uptr size) {
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_memalign(alignment, size, stack);
-}
-# define DSAN_MAYBE_INTERCEPT_MEMALIGN INTERCEPT_FUNCTION(memalign)
-#else
-# define DSAN_MAYBE_INTERCEPT_MEMALIGN
-#endif // SANITIZER_INTERCEPT_MEMALIGN
-
-#if SANITIZER_INTERCEPT___LIBC_MEMALIGN
-INTERCEPTOR(void*, __libc_memalign, uptr alignment, uptr size) {
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_memalign(alignment, size, stack);
-}
-# define DSAN_MAYBE_INTERCEPT___LIBC_MEMALIGN \
- INTERCEPT_FUNCTION(__libc_memalign)
-#else
-# define DSAN_MAYBE_INTERCEPT___LIBC_MEMALIGN
-#endif // SANITIZER_INTERCEPT___LIBC_MEMALIGN
-
-#if SANITIZER_INTERCEPT_ALIGNED_ALLOC
-INTERCEPTOR(void*, aligned_alloc, uptr alignment, uptr size) {
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_aligned_alloc(alignment, size, stack);
-}
-# define DSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC INTERCEPT_FUNCTION(aligned_alloc)
-#else
-# define DSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC
-#endif
-
-#if SANITIZER_INTERCEPT_MALLOC_USABLE_SIZE
-INTERCEPTOR(uptr, malloc_usable_size, void* ptr) {
- ENSURE_DSAN_INITED;
- return GetMallocUsableSize(ptr);
-}
-# define DSAN_MAYBE_INTERCEPT_MALLOC_USABLE_SIZE \
- INTERCEPT_FUNCTION(malloc_usable_size)
-#else
-# define DSAN_MAYBE_INTERCEPT_MALLOC_USABLE_SIZE
-#endif
-
-#if SANITIZER_INTERCEPT_MALLOPT_AND_MALLINFO
-struct fake_mallinfo {
- int x[10];
-};
-
-INTERCEPTOR(struct fake_mallinfo, mallinfo, void) {
- struct fake_mallinfo res;
- internal_memset(&res, 0, sizeof(res));
- return res;
-}
-# define DSAN_MAYBE_INTERCEPT_MALLINFO INTERCEPT_FUNCTION(mallinfo)
-
-INTERCEPTOR(int, mallopt, int cmd, int value) { return 0; }
-# define DSAN_MAYBE_INTERCEPT_MALLOPT INTERCEPT_FUNCTION(mallopt)
-#else
-# define DSAN_MAYBE_INTERCEPT_MALLINFO
-# define DSAN_MAYBE_INTERCEPT_MALLOPT
-#endif // SANITIZER_INTERCEPT_MALLOPT_AND_MALLINFO
-
-#if SANITIZER_INTERCEPT_PVALLOC
-INTERCEPTOR(void*, pvalloc, uptr size) {
- ENSURE_DSAN_INITED;
- GET_STACK_TRACE_MALLOC;
- return dsan_pvalloc(size, stack);
-}
-# define DSAN_MAYBE_INTERCEPT_PVALLOC INTERCEPT_FUNCTION(pvalloc)
-#else
-# define DSAN_MAYBE_INTERCEPT_PVALLOC
-#endif // SANITIZER_INTERCEPT_PVALLOC
-
-#if SANITIZER_INTERCEPT_CFREE
-INTERCEPTOR(void, cfree, void* p) ALIAS(WRAP(free));
-# define DSAN_MAYBE_INTERCEPT_CFREE INTERCEPT_FUNCTION(cfree)
-#else
-# define DSAN_MAYBE_INTERCEPT_CFREE
-#endif // SANITIZER_INTERCEPT_CFREE
-
-#if SANITIZER_INTERCEPT_MCHECK_MPROBE
-INTERCEPTOR(int, mcheck, void (*abortfunc)(int mstatus)) { return 0; }
-
-INTERCEPTOR(int, mcheck_pedantic, void (*abortfunc)(int mstatus)) { return 0; }
-
-INTERCEPTOR(int, mprobe, void* ptr) { return 0; }
-#endif // SANITIZER_INTERCEPT_MCHECK_MPROBE
-
-// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
-#define OPERATOR_NEW_BODY(nothrow) \
- ENSURE_DSAN_INITED; \
- GET_STACK_TRACE_MALLOC; \
- void* res = dsan_malloc(size, stack); \
- if (!nothrow && UNLIKELY(!res)) \
- ReportOutOfMemory(size, &stack); \
- return res;
-#define OPERATOR_NEW_BODY_ALIGN(nothrow) \
- ENSURE_DSAN_INITED; \
- GET_STACK_TRACE_MALLOC; \
- void* res = dsan_memalign((uptr)align, size, stack); \
- if (!nothrow && UNLIKELY(!res)) \
- ReportOutOfMemory(size, &stack); \
- return res;
-
-#define OPERATOR_DELETE_BODY \
- ENSURE_DSAN_INITED; \
- GET_STACK_TRACE_MALLOC; \
- dsan_free(ptr, stack);
-
-// On OS X it's not enough to just provide our own 'operator new' and
-// 'operator delete' implementations, because they're going to be in the runtime
-// dylib, and the main executable will depend on both the runtime dylib and
-// libstdc++, each of has its implementation of new and delete.
-// To make sure that C++ allocation/deallocation operators are overridden on
-// OS X we need to intercept them using their mangled names.
-#if !SANITIZER_APPLE
-
-INTERCEPTOR_ATTRIBUTE
-void* operator new(size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
-INTERCEPTOR_ATTRIBUTE
-void* operator new[](size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
-INTERCEPTOR_ATTRIBUTE
-void* operator new(size_t size, std::nothrow_t const&) {
- OPERATOR_NEW_BODY(true /*nothrow*/);
-}
-INTERCEPTOR_ATTRIBUTE
-void* operator new[](size_t size, std::nothrow_t const&) {
- OPERATOR_NEW_BODY(true /*nothrow*/);
-}
-INTERCEPTOR_ATTRIBUTE
-void* operator new(size_t size, std::align_val_t align) {
- OPERATOR_NEW_BODY_ALIGN(false /*nothrow*/);
-}
-INTERCEPTOR_ATTRIBUTE
-void* operator new[](size_t size, std::align_val_t align) {
- OPERATOR_NEW_BODY_ALIGN(false /*nothrow*/);
-}
-INTERCEPTOR_ATTRIBUTE
-void* operator new(size_t size, std::align_val_t align, std::nothrow_t const&) {
- OPERATOR_NEW_BODY_ALIGN(true /*nothrow*/);
-}
-INTERCEPTOR_ATTRIBUTE
-void* operator new[](size_t size, std::align_val_t align,
- std::nothrow_t const&) {
- OPERATOR_NEW_BODY_ALIGN(true /*nothrow*/);
-}
-
-INTERCEPTOR_ATTRIBUTE
-void operator delete(void* ptr) NOEXCEPT { OPERATOR_DELETE_BODY; }
-INTERCEPTOR_ATTRIBUTE
-void operator delete[](void* ptr) NOEXCEPT { OPERATOR_DELETE_BODY; }
-INTERCEPTOR_ATTRIBUTE
-void operator delete(void* ptr, std::nothrow_t const&) { OPERATOR_DELETE_BODY; }
-INTERCEPTOR_ATTRIBUTE
-void operator delete[](void* ptr, std::nothrow_t const&) {
- OPERATOR_DELETE_BODY;
-}
-INTERCEPTOR_ATTRIBUTE
-void operator delete(void* ptr, size_t size) NOEXCEPT { OPERATOR_DELETE_BODY; }
-INTERCEPTOR_ATTRIBUTE
-void operator delete[](void* ptr, size_t size) NOEXCEPT {
- OPERATOR_DELETE_BODY;
-}
-INTERCEPTOR_ATTRIBUTE
-void operator delete(void* ptr, std::align_val_t) NOEXCEPT {
- OPERATOR_DELETE_BODY;
-}
-INTERCEPTOR_ATTRIBUTE
-void operator delete[](void* ptr, std::align_val_t) NOEXCEPT {
- OPERATOR_DELETE_BODY;
-}
-INTERCEPTOR_ATTRIBUTE
-void operator delete(void* ptr, std::align_val_t, std::nothrow_t const&) {
- OPERATOR_DELETE_BODY;
-}
-INTERCEPTOR_ATTRIBUTE
-void operator delete[](void* ptr, std::align_val_t, std::nothrow_t const&) {
- OPERATOR_DELETE_BODY;
-}
-INTERCEPTOR_ATTRIBUTE
-void operator delete(void* ptr, size_t size, std::align_val_t) NOEXCEPT {
- OPERATOR_DELETE_BODY;
-}
-INTERCEPTOR_ATTRIBUTE
-void operator delete[](void* ptr, size_t size, std::align_val_t) NOEXCEPT {
- OPERATOR_DELETE_BODY;
-}
-
-#else // SANITIZER_APPLE
-
-INTERCEPTOR(void*, _Znwm, size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
-INTERCEPTOR(void*, _Znam, size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
-INTERCEPTOR(void*, _ZnwmRKSt9nothrow_t, size_t size, std::nothrow_t const&) {
- OPERATOR_NEW_BODY(true /*nothrow*/);
-}
-INTERCEPTOR(void*, _ZnamRKSt9nothrow_t, size_t size, std::nothrow_t const&) {
- OPERATOR_NEW_BODY(true /*nothrow*/);
-}
-
-INTERCEPTOR(void, _ZdlPv, void* ptr) { OPERATOR_DELETE_BODY; }
-INTERCEPTOR(void, _ZdaPv, void* ptr) { OPERATOR_DELETE_BODY; }
-INTERCEPTOR(void, _ZdlPvRKSt9nothrow_t, void* ptr, std::nothrow_t const&) {
- OPERATOR_DELETE_BODY;
-}
-INTERCEPTOR(void, _ZdaPvRKSt9nothrow_t, void* ptr, std::nothrow_t const&) {
- OPERATOR_DELETE_BODY;
-}
-
-#endif // !SANITIZER_APPLE
-
-///// Thread initialization and finalization. /////
-
-#if !SANITIZER_NETBSD && !SANITIZER_FREEBSD && !SANITIZER_FUCHSIA
-static unsigned g_thread_finalize_key;
-
-static void thread_finalize(void* v) {
- uptr iter = (uptr)v;
- if (iter > 1) {
- if (pthread_setspecific(g_thread_finalize_key, (void*)(iter - 1))) {
- Report("DoubleFreeSanitizer: failed to set thread key.\n");
- Die();
- }
- return;
- }
- ThreadFinish();
-}
-#endif
-
-#if SANITIZER_NETBSD
-INTERCEPTOR(void, _lwp_exit) {
- ENSURE_DSAN_INITED;
- ThreadFinish();
- REAL(_lwp_exit)();
-}
-# define DSAN_MAYBE_INTERCEPT__LWP_EXIT INTERCEPT_FUNCTION(_lwp_exit)
-#else
-# define DSAN_MAYBE_INTERCEPT__LWP_EXIT
-#endif
-
-#if SANITIZER_INTERCEPT_THR_EXIT
-INTERCEPTOR(void, thr_exit, ThreadID* state) {
- ENSURE_DSAN_INITED;
- ThreadFinish();
- REAL(thr_exit)(state);
-}
-# define DSAN_MAYBE_INTERCEPT_THR_EXIT INTERCEPT_FUNCTION(thr_exit)
-#else
-# define DSAN_MAYBE_INTERCEPT_THR_EXIT
-#endif
-
-#if SANITIZER_POSIX
-
-template <bool Detached>
-static void* ThreadStartFunc(void* arg) {
- u32 parent_tid = (uptr)arg;
- uptr tid = ThreadCreate(parent_tid, Detached);
- // Wait until the last iteration to maximize the chance that we are the last
- // destructor to run.
-# if !SANITIZER_NETBSD && !SANITIZER_FREEBSD
- if (pthread_setspecific(g_thread_finalize_key,
- (void*)GetPthreadDestructorIterations())) {
- Report("DoubleFreeSanitizer: failed to set thread key.\n");
- Die();
- }
-# endif
- ThreadStart(tid, GetTid());
- auto self = GetThreadSelf();
- auto args = GetThreadArgRetval().GetArgs(self);
- void* retval = (*args.routine)(args.arg_retval);
- GetThreadArgRetval().Finish(self, retval);
- return retval;
-}
-
-INTERCEPTOR(int, pthread_create, void* th, void* attr, void* (*callback)(void*),
- void* param) {
- ENSURE_DSAN_INITED;
- EnsureMainThreadIDIsCorrect();
-
- bool detached = [attr]() {
- int d = 0;
- return attr && !pthread_attr_getdetachstate(attr, &d) && IsStateDetached(d);
- }();
-
- __sanitizer_pthread_attr_t myattr;
- if (!attr) {
- pthread_attr_init(&myattr);
- attr = &myattr;
- }
- AdjustStackSize(attr);
- uptr this_tid = GetCurrentThreadId();
- int result;
- GetThreadArgRetval().Create(detached, {callback, param}, [&]() -> uptr {
- result = REAL(pthread_create)(
- th, attr, detached ? ThreadStartFunc<true> : ThreadStartFunc<false>,
- (void*)this_tid);
- return result ? 0 : *(uptr*)(th);
- });
- if (attr == &myattr)
- pthread_attr_destroy(&myattr);
- return result;
-}
-
-INTERCEPTOR(int, pthread_join, void* thread, void** retval) {
- int result;
- GetThreadArgRetval().Join((uptr)thread, [&]() {
- result = REAL(pthread_join)(thread, retval);
- return !result;
- });
- return result;
-}
-
-INTERCEPTOR(int, pthread_detach, void* thread) {
- int result;
- GetThreadArgRetval().Detach((uptr)thread, [&]() {
- result = REAL(pthread_detach)(thread);
- return !result;
- });
- return result;
-}
-
-INTERCEPTOR(void, pthread_exit, void* retval) {
- GetThreadArgRetval().Finish(GetThreadSelf(), retval);
- REAL(pthread_exit)(retval);
-}
-
-# if SANITIZER_INTERCEPT_TRYJOIN
-INTERCEPTOR(int, pthread_tryjoin_np, void* thread, void** ret) {
- int result;
- GetThreadArgRetval().Join((uptr)thread, [&]() {
- result = REAL(pthread_tryjoin_np)(thread, ret);
- return !result;
- });
- return result;
-}
-# define DSAN_MAYBE_INTERCEPT_TRYJOIN INTERCEPT_FUNCTION(pthread_tryjoin_np)
-# else
-# define DSAN_MAYBE_INTERCEPT_TRYJOIN
-# endif // SANITIZER_INTERCEPT_TRYJOIN
-
-# if SANITIZER_INTERCEPT_TIMEDJOIN
-INTERCEPTOR(int, pthread_timedjoin_np, void* thread, void** ret,
- const struct timespec* abstime) {
- int result;
- GetThreadArgRetval().Join((uptr)thread, [&]() {
- result = REAL(pthread_timedjoin_np)(thread, ret, abstime);
- return !result;
- });
- return result;
-}
-# define DSAN_MAYBE_INTERCEPT_TIMEDJOIN \
- INTERCEPT_FUNCTION(pthread_timedjoin_np)
-# else
-# define DSAN_MAYBE_INTERCEPT_TIMEDJOIN
-# endif // SANITIZER_INTERCEPT_TIMEDJOIN
-
-DEFINE_INTERNAL_PTHREAD_FUNCTIONS
-
-INTERCEPTOR(void, _exit, int status) { REAL(_exit)(status); }
-
-# define COMMON_INTERCEPT_FUNCTION(name) INTERCEPT_FUNCTION(name)
-# define SIGNAL_INTERCEPTOR_ENTER() ENSURE_DSAN_INITED
-# include "sanitizer_common/sanitizer_signal_interceptors.inc"
-
-#endif // SANITIZER_POSIX
-
-namespace __dsan {
-
-void InitializeInterceptors() {
- // Fuchsia doesn't use interceptors that require any setup.
-#if !SANITIZER_FUCHSIA
- __interception::DoesNotSupportStaticLinking();
- InitializeSignalInterceptors();
-
- INTERCEPT_FUNCTION(malloc);
- INTERCEPT_FUNCTION(free);
- DSAN_MAYBE_INTERCEPT_FREE_SIZED;
- DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED;
- DSAN_MAYBE_INTERCEPT_CFREE;
- INTERCEPT_FUNCTION(calloc);
- INTERCEPT_FUNCTION(realloc);
- INTERCEPT_FUNCTION(reallocarray);
- DSAN_MAYBE_INTERCEPT_MEMALIGN;
- DSAN_MAYBE_INTERCEPT___LIBC_MEMALIGN;
- DSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC;
- INTERCEPT_FUNCTION(posix_memalign);
- INTERCEPT_FUNCTION(valloc);
- DSAN_MAYBE_INTERCEPT_PVALLOC;
- DSAN_MAYBE_INTERCEPT_MALLOC_USABLE_SIZE;
- DSAN_MAYBE_INTERCEPT_MALLINFO;
- DSAN_MAYBE_INTERCEPT_MALLOPT;
- INTERCEPT_FUNCTION(pthread_create);
- INTERCEPT_FUNCTION(pthread_join);
- INTERCEPT_FUNCTION(pthread_detach);
- INTERCEPT_FUNCTION(pthread_exit);
- DSAN_MAYBE_INTERCEPT_TIMEDJOIN;
- DSAN_MAYBE_INTERCEPT_TRYJOIN;
- INTERCEPT_FUNCTION(_exit);
-
- DSAN_MAYBE_INTERCEPT__LWP_EXIT;
- DSAN_MAYBE_INTERCEPT_THR_EXIT;
-
-# if !SANITIZER_NETBSD && !SANITIZER_FREEBSD
- if (pthread_key_create(&g_thread_finalize_key, &thread_finalize)) {
- Report("DoubleFreeSanitizer: failed to create thread key.\n");
- Die();
- }
-# endif
-
-#endif // !SANITIZER_FUCHSIA
-}
-
-} // namespace __dsan
diff --git a/compiler-rt/lib/dsan/dsan_linux.cpp b/compiler-rt/lib/dsan/dsan_linux.cpp
deleted file mode 100644
index fa5bbffb9a5ec..0000000000000
--- a/compiler-rt/lib/dsan/dsan_linux.cpp
+++ /dev/null
@@ -1,34 +0,0 @@
-//=-- dsan_linux.cpp ------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer. Linux/NetBSD/Fuchsia-specific
-// code.
-//
-//===----------------------------------------------------------------------===//
-
-#include "sanitizer_common/sanitizer_platform.h"
-
-#if SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_FUCHSIA
-
-# include "dsan_allocator.h"
-# include "dsan_thread.h"
-
-namespace __dsan {
-
-static THREADLOCAL ThreadContextDsanBase* current_thread = nullptr;
-ThreadContextDsanBase* GetCurrentThread() { return current_thread; }
-void SetCurrentThread(ThreadContextDsanBase* tctx) { current_thread = tctx; }
-
-static THREADLOCAL AllocatorCache allocator_cache;
-AllocatorCache* GetAllocatorCache() { return &allocator_cache; }
-
-void ReplaceSystemMalloc() {}
-
-} // namespace __dsan
-
-#endif // SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_FUCHSIA
diff --git a/compiler-rt/lib/dsan/dsan_mac.cpp b/compiler-rt/lib/dsan/dsan_mac.cpp
deleted file mode 100644
index 793689862bc00..0000000000000
--- a/compiler-rt/lib/dsan/dsan_mac.cpp
+++ /dev/null
@@ -1,234 +0,0 @@
-//===-- dsan_mac.cpp ------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-//
-// Mac-specific details.
-//===----------------------------------------------------------------------===//
-
-#include "sanitizer_common/sanitizer_platform.h"
-#if SANITIZER_APPLE
-
-# include <pthread.h>
-
-# include "dsan.h"
-# include "dsan_allocator.h"
-# include "dsan_thread.h"
-# include "interception/interception.h"
-# include "sanitizer_common/sanitizer_allocator_internal.h"
-
-namespace __dsan {
-struct ThreadLocalData {
- ThreadContextDsanBase* current_thread;
- AllocatorCache cache;
-};
-
-static pthread_key_t thread_local_key;
-static pthread_once_t thread_local_key_once = PTHREAD_ONCE_INIT;
-
-static void RestoreThreadLocalData(void* ptr) {
- ThreadLocalData* data = static_cast<ThreadLocalData*>(ptr);
- if (data->current_thread)
- pthread_setspecific(thread_local_key, data);
-}
-
-static void CreateThreadLocalKey() {
- CHECK_EQ(pthread_key_create(&thread_local_key, RestoreThreadLocalData), 0);
-}
-
-static ThreadLocalData* GetThreadLocalData(bool allocate) {
- pthread_once(&thread_local_key_once, CreateThreadLocalKey);
- ThreadLocalData* data =
- static_cast<ThreadLocalData*>(pthread_getspecific(thread_local_key));
- if (!data && allocate) {
- data = static_cast<ThreadLocalData*>(InternalAlloc(sizeof(*data)));
- data->current_thread = nullptr;
- data->cache = AllocatorCache();
- pthread_setspecific(thread_local_key, data);
- }
- return data;
-}
-
-ThreadContextDsanBase* GetCurrentThread() {
- ThreadLocalData* data = GetThreadLocalData(false);
- return data ? data->current_thread : nullptr;
-}
-
-void SetCurrentThread(ThreadContextDsanBase* tctx) {
- GetThreadLocalData(true)->current_thread = tctx;
-}
-
-AllocatorCache* GetAllocatorCache() { return &GetThreadLocalData(true)->cache; }
-
-// Support for the following functions from libdispatch on Mac OS:
-// dispatch_async_f()
-// dispatch_async()
-// dispatch_sync_f()
-// dispatch_sync()
-// dispatch_after_f()
-// dispatch_after()
-// dispatch_group_async_f()
-// dispatch_group_async()
-// TODO(glider): libdispatch API contains other functions that we don't support
-// yet.
-//
-// dispatch_sync() and dispatch_sync_f() are synchronous, although chances are
-// they can cause jobs to run on a thread different from the current one.
-// TODO(glider): if so, we need a test for this (otherwise we should remove
-// them).
-//
-// The following functions use dispatch_barrier_async_f() (which isn't a library
-// function but is exported) and are thus supported:
-// dispatch_source_set_cancel_handler_f()
-// dispatch_source_set_cancel_handler()
-// dispatch_source_set_event_handler_f()
-// dispatch_source_set_event_handler()
-//
-// The reference manual for Grand Central Dispatch is available at
-// http://developer.apple.com/library/mac/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
-// The implementation details are at
-// http://libdispatch.macosforge.org/trac/browser/trunk/src/queue.c
-
-typedef void* dispatch_group_t;
-typedef void* dispatch_queue_t;
-typedef void* dispatch_source_t;
-typedef u64 dispatch_time_t;
-typedef void (*dispatch_function_t)(void* block);
-typedef void* (*worker_t)(void* block);
-
-// A wrapper for the ObjC blocks used to support libdispatch.
-typedef struct {
- void* block;
- dispatch_function_t func;
- u32 parent_tid;
-} dsan_block_context_t;
-
-ALWAYS_INLINE
-void dsan_register_worker_thread(int parent_tid) {
- if (GetCurrentThreadId() == kInvalidTid) {
- u32 tid = ThreadCreate(parent_tid, true);
- ThreadStart(tid, GetTid());
- }
-}
-
-// For use by only those functions that allocated the context via
-// alloc_dsan_context().
-extern "C" void dsan_dispatch_call_block_and_release(void* block) {
- dsan_block_context_t* context = (dsan_block_context_t*)block;
- VReport(2,
- "dsan_dispatch_call_block_and_release(): "
- "context: %p, pthread_self: %p\n",
- block, (void*)pthread_self());
- dsan_register_worker_thread(context->parent_tid);
- // Call the original dispatcher for the block.
- context->func(context->block);
- GET_STACK_TRACE_MALLOC;
- dsan_free(context, stack);
-}
-
-} // namespace __dsan
-
-using namespace __dsan;
-
-// Wrap |ctxt| and |func| into an dsan_block_context_t.
-// The caller retains control of the allocated context.
-extern "C" dsan_block_context_t* alloc_dsan_context(void* ctxt,
- dispatch_function_t func) {
- GET_STACK_TRACE_THREAD;
- dsan_block_context_t* dsan_ctxt =
- (dsan_block_context_t*)dsan_malloc(sizeof(dsan_block_context_t), stack);
- dsan_ctxt->block = ctxt;
- dsan_ctxt->func = func;
- dsan_ctxt->parent_tid = GetCurrentThreadId();
- return dsan_ctxt;
-}
-
-// Define interceptor for dispatch_*_f function with the three most common
-// parameters: dispatch_queue_t, context, dispatch_function_t.
-# define INTERCEPT_DISPATCH_X_F_3(dispatch_x_f) \
- INTERCEPTOR(void, dispatch_x_f, dispatch_queue_t dq, void* ctxt, \
- dispatch_function_t func) { \
- dsan_block_context_t* dsan_ctxt = alloc_dsan_context(ctxt, func); \
- return REAL(dispatch_x_f)(dq, (void*)dsan_ctxt, \
- dsan_dispatch_call_block_and_release); \
- }
-
-INTERCEPT_DISPATCH_X_F_3(dispatch_async_f)
-INTERCEPT_DISPATCH_X_F_3(dispatch_sync_f)
-INTERCEPT_DISPATCH_X_F_3(dispatch_barrier_async_f)
-
-INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when, dispatch_queue_t dq,
- void* ctxt, dispatch_function_t func) {
- dsan_block_context_t* dsan_ctxt = alloc_dsan_context(ctxt, func);
- return REAL(dispatch_after_f)(when, dq, (void*)dsan_ctxt,
- dsan_dispatch_call_block_and_release);
-}
-
-INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group,
- dispatch_queue_t dq, void* ctxt, dispatch_function_t func) {
- dsan_block_context_t* dsan_ctxt = alloc_dsan_context(ctxt, func);
- REAL(dispatch_group_async_f)(group, dq, (void*)dsan_ctxt,
- dsan_dispatch_call_block_and_release);
-}
-
-# if !defined(MISSING_BLOCKS_SUPPORT)
-extern "C" {
-void dispatch_async(dispatch_queue_t dq, void (^work)(void));
-void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
- void (^work)(void));
-void dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
- void (^work)(void));
-void dispatch_source_set_cancel_handler(dispatch_source_t ds,
- void (^work)(void));
-void dispatch_source_set_event_handler(dispatch_source_t ds,
- void (^work)(void));
-}
-
-# define GET_DSAN_BLOCK(work) \
- void (^dsan_block)(void); \
- int parent_tid = GetCurrentThreadId(); \
- dsan_block = ^(void) { \
- dsan_register_worker_thread(parent_tid); \
- work(); \
- }
-
-INTERCEPTOR(void, dispatch_async, dispatch_queue_t dq, void (^work)(void)) {
- GET_DSAN_BLOCK(work);
- REAL(dispatch_async)(dq, dsan_block);
-}
-
-INTERCEPTOR(void, dispatch_group_async, dispatch_group_t dg,
- dispatch_queue_t dq, void (^work)(void)) {
- GET_DSAN_BLOCK(work);
- REAL(dispatch_group_async)(dg, dq, dsan_block);
-}
-
-INTERCEPTOR(void, dispatch_after, dispatch_time_t when, dispatch_queue_t queue,
- void (^work)(void)) {
- GET_DSAN_BLOCK(work);
- REAL(dispatch_after)(when, queue, dsan_block);
-}
-
-INTERCEPTOR(void, dispatch_source_set_cancel_handler, dispatch_source_t ds,
- void (^work)(void)) {
- if (!work) {
- REAL(dispatch_source_set_cancel_handler)(ds, work);
- return;
- }
- GET_DSAN_BLOCK(work);
- REAL(dispatch_source_set_cancel_handler)(ds, dsan_block);
-}
-
-INTERCEPTOR(void, dispatch_source_set_event_handler, dispatch_source_t ds,
- void (^work)(void)) {
- GET_DSAN_BLOCK(work);
- REAL(dispatch_source_set_event_handler)(ds, dsan_block);
-}
-# endif
-
-#endif // SANITIZER_APPLE
diff --git a/compiler-rt/lib/dsan/dsan_malloc_mac.cpp b/compiler-rt/lib/dsan/dsan_malloc_mac.cpp
deleted file mode 100644
index 515b26c49424d..0000000000000
--- a/compiler-rt/lib/dsan/dsan_malloc_mac.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-//===-- dsan_malloc_mac.cpp -----------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer (DSan).
-//
-// Mac-specific malloc interception.
-//===----------------------------------------------------------------------===//
-
-#include "sanitizer_common/sanitizer_platform.h"
-#if SANITIZER_APPLE
-
-# include "dsan.h"
-# include "dsan_allocator.h"
-# include "dsan_thread.h"
-
-using namespace __dsan;
-# define COMMON_MALLOC_ZONE_NAME "dsan"
-# define COMMON_MALLOC_ENTER() ENSURE_DSAN_INITED
-# define COMMON_MALLOC_SANITIZER_INITIALIZED dsan_inited
-# define COMMON_MALLOC_FORCE_LOCK()
-# define COMMON_MALLOC_FORCE_UNLOCK()
-# define COMMON_MALLOC_MEMALIGN(alignment, size) \
- GET_STACK_TRACE_MALLOC; \
- void* p = dsan_memalign(alignment, size, stack)
-# define COMMON_MALLOC_MALLOC(size) \
- GET_STACK_TRACE_MALLOC; \
- void* p = dsan_malloc(size, stack)
-# define COMMON_MALLOC_REALLOC(ptr, size) \
- GET_STACK_TRACE_MALLOC; \
- void* p = dsan_realloc(ptr, size, stack)
-# define COMMON_MALLOC_CALLOC(count, size) \
- GET_STACK_TRACE_MALLOC; \
- void* p = dsan_calloc(count, size, stack)
-# define COMMON_MALLOC_POSIX_MEMALIGN(memptr, alignment, size) \
- GET_STACK_TRACE_MALLOC; \
- int res = dsan_posix_memalign(memptr, alignment, size, stack)
-# define COMMON_MALLOC_VALLOC(size) \
- GET_STACK_TRACE_MALLOC; \
- void* p = dsan_valloc(size, stack)
-# define COMMON_MALLOC_FREE(ptr) \
- GET_STACK_TRACE_MALLOC; \
- dsan_free(ptr, stack)
-# define COMMON_MALLOC_FREE_SIZED(ptr, size) \
- GET_STACK_TRACE_MALLOC; \
- dsan_free_sized(ptr, size, stack)
-# define COMMON_MALLOC_FREE_ALIGNED_SIZED(ptr, alignment, size) \
- GET_STACK_TRACE_MALLOC; \
- dsan_free_aligned_sized(ptr, alignment, size, stack)
-# define COMMON_MALLOC_SIZE(ptr) uptr size = dsan_mz_size(ptr)
-# define COMMON_MALLOC_FILL_STATS(zone, stats)
-# define COMMON_MALLOC_REPORT_UNKNOWN_REALLOC(ptr, zone_ptr, zone_name) \
- (void)zone_name; \
- Report("mz_realloc(%p) -- attempting to realloc unallocated memory.\n", \
- ptr);
-# define COMMON_MALLOC_NAMESPACE __dsan
-# define COMMON_MALLOC_HAS_ZONE_ENUMERATOR 0
-# define COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT 0
-
-# include "sanitizer_common/sanitizer_malloc_mac.inc"
-
-#endif // SANITIZER_APPLE
diff --git a/compiler-rt/lib/dsan/dsan_posix.cpp b/compiler-rt/lib/dsan/dsan_posix.cpp
deleted file mode 100644
index b96488f49fd75..0000000000000
--- a/compiler-rt/lib/dsan/dsan_posix.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-//=-- dsan_posix.cpp -----------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===---------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Standalone DSan RTL code common to POSIX-like systems.
-//
-//===---------------------------------------------------------------------===//
-
-#include "sanitizer_common/sanitizer_platform.h"
-
-#if SANITIZER_POSIX
-# include <pthread.h>
-
-# include "dsan.h"
-# include "dsan_allocator.h"
-# include "dsan_common.h"
-# include "dsan_thread.h"
-# include "sanitizer_common/sanitizer_stackdepot.h"
-# include "sanitizer_common/sanitizer_stacktrace.h"
-# include "sanitizer_common/sanitizer_tls_get_addr.h"
-
-namespace __dsan {
-
-ThreadContext::ThreadContext(int tid) : ThreadContextDsanBase(tid) {}
-
-struct OnStartedArgs {
- uptr stack_begin;
- uptr stack_end;
- uptr cache_begin;
- uptr cache_end;
- uptr tls_begin;
- uptr tls_end;
- DTLS* dtls;
-};
-
-void ThreadContext::OnStarted(void* arg) {
- ThreadContextDsanBase::OnStarted(arg);
- auto args = reinterpret_cast<const OnStartedArgs*>(arg);
- stack_begin_ = args->stack_begin;
- stack_end_ = args->stack_end;
- tls_begin_ = args->tls_begin;
- tls_end_ = args->tls_end;
- cache_begin_ = args->cache_begin;
- cache_end_ = args->cache_end;
- dtls_ = args->dtls;
-}
-
-void ThreadStart(u32 tid, ThreadID os_id, ThreadType thread_type) {
- OnStartedArgs args;
- GetThreadStackAndTls(tid == kMainTid, &args.stack_begin, &args.stack_end,
- &args.tls_begin, &args.tls_end);
- GetAllocatorCacheRange(&args.cache_begin, &args.cache_end);
- args.dtls = DTLS_Get();
- ThreadContextDsanBase::ThreadStart(tid, os_id, thread_type, &args);
-}
-
-bool GetThreadRangesLocked(ThreadID os_id, uptr* stack_begin, uptr* stack_end,
- uptr* tls_begin, uptr* tls_end, uptr* cache_begin,
- uptr* cache_end, DTLS** dtls) {
- ThreadContext* context = static_cast<ThreadContext*>(
- GetDsanThreadRegistryLocked()->FindThreadContextByOsIDLocked(os_id));
- if (!context)
- return false;
- *stack_begin = context->stack_begin();
- *stack_end = context->stack_end();
- *tls_begin = context->tls_begin();
- *tls_end = context->tls_end();
- *cache_begin = context->cache_begin();
- *cache_end = context->cache_end();
- *dtls = context->dtls();
- return true;
-}
-
-void InitializeMainThread() {
- u32 tid = ThreadCreate(kMainTid, true);
- CHECK_EQ(tid, kMainTid);
- ThreadStart(tid, GetTid());
-}
-
-static void OnStackUnwind(const SignalContext& sig, const void*,
- BufferedStackTrace* stack) {
- stack->Unwind(StackTrace::GetNextInstructionPc(sig.pc), sig.bp, sig.context,
- common_flags()->fast_unwind_on_fatal);
-}
-
-void DsanOnDeadlySignal(int signo, void* siginfo, void* context) {
- HandleDeadlySignal(siginfo, context, GetCurrentThreadId(), &OnStackUnwind,
- nullptr);
-}
-
-static void BeforeFork() {
- VReport(2, "BeforeFork tid: %llu\n", GetTid());
- LockThreads();
- LockAllocator();
- StackDepotLockBeforeFork();
-}
-
-static void AfterFork(bool fork_child) {
- StackDepotUnlockAfterFork(fork_child);
- UnlockAllocator();
- UnlockThreads();
- VReport(2, "AfterFork tid: %llu\n", GetTid());
-}
-
-void InstallAtForkHandler() {
-# if SANITIZER_SOLARIS || SANITIZER_NETBSD || SANITIZER_APPLE
- return; // FIXME: Implement FutexWait.
-# endif
- pthread_atfork(
- &BeforeFork, []() { AfterFork(/* fork_child= */ false); },
- []() { AfterFork(/* fork_child= */ true); });
-}
-
-} // namespace __dsan
-
-#endif // SANITIZER_POSIX
diff --git a/compiler-rt/lib/dsan/dsan_posix.h b/compiler-rt/lib/dsan/dsan_posix.h
deleted file mode 100644
index 03b804704db4e..0000000000000
--- a/compiler-rt/lib/dsan/dsan_posix.h
+++ /dev/null
@@ -1,49 +0,0 @@
-//=-- dsan_posix.h -----------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===---------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Standalone DSan RTL code common to POSIX-like systems.
-//
-//===---------------------------------------------------------------------===//
-
-#ifndef DSAN_POSIX_H
-#define DSAN_POSIX_H
-
-#include "dsan_thread.h"
-#include "sanitizer_common/sanitizer_platform.h"
-
-#if !SANITIZER_POSIX
-# error "dsan_posix.h is used only on POSIX-like systems (SANITIZER_POSIX)"
-#endif
-
-namespace __sanitizer {
-struct DTLS;
-}
-
-namespace __dsan {
-
-class ThreadContext final : public ThreadContextDsanBase {
- public:
- explicit ThreadContext(int tid);
- void OnStarted(void* arg) override;
- uptr tls_begin() { return tls_begin_; }
- uptr tls_end() { return tls_end_; }
- DTLS* dtls() { return dtls_; }
-
- private:
- uptr tls_begin_ = 0;
- uptr tls_end_ = 0;
- DTLS* dtls_ = nullptr;
-};
-
-void ThreadStart(u32 tid, ThreadID os_id,
- ThreadType thread_type = ThreadType::Regular);
-
-} // namespace __dsan
-
-#endif // DSAN_POSIX_H
diff --git a/compiler-rt/lib/dsan/dsan_preinit.cpp b/compiler-rt/lib/dsan/dsan_preinit.cpp
deleted file mode 100644
index 4a936fd6e6422..0000000000000
--- a/compiler-rt/lib/dsan/dsan_preinit.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-//===-- dsan_preinit.cpp --------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-//
-// Call __dsan_init at the very early stage of process startup.
-//===----------------------------------------------------------------------===//
-
-#include "dsan.h"
-
-#if SANITIZER_CAN_USE_PREINIT_ARRAY
-// This section is linked into the main executable when -fsanitize=doublefree is
-// specified to perform initialization at a very early stage.
-__attribute__((section(".preinit_array"), used)) static auto preinit =
- __dsan_init;
-#endif
diff --git a/compiler-rt/lib/dsan/dsan_thread.cpp b/compiler-rt/lib/dsan/dsan_thread.cpp
deleted file mode 100644
index 617c1c998ad80..0000000000000
--- a/compiler-rt/lib/dsan/dsan_thread.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-//=-- dsan_thread.cpp -----------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// See dsan_thread.h for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "dsan_thread.h"
-
-#include "dsan.h"
-#include "dsan_allocator.h"
-#include "dsan_common.h"
-#include "sanitizer_common/sanitizer_common.h"
-#include "sanitizer_common/sanitizer_placement_new.h"
-#include "sanitizer_common/sanitizer_thread_history.h"
-#include "sanitizer_common/sanitizer_thread_registry.h"
-#include "sanitizer_common/sanitizer_tls_get_addr.h"
-
-namespace __dsan {
-
-static ThreadRegistry* thread_registry;
-static ThreadArgRetval* thread_arg_retval;
-
-static Mutex mu_for_thread_context;
-static LowLevelAllocator allocator_for_thread_context;
-
-static ThreadContextBase* CreateThreadContext(u32 tid) {
- Lock lock(&mu_for_thread_context);
- return new (allocator_for_thread_context) ThreadContext(tid);
-}
-
-void InitializeThreads() {
- alignas(alignof(ThreadRegistry)) static char
- thread_registry_placeholder[sizeof(ThreadRegistry)];
- thread_registry =
- new (thread_registry_placeholder) ThreadRegistry(CreateThreadContext);
-
- alignas(alignof(ThreadArgRetval)) static char
- thread_arg_retval_placeholder[sizeof(ThreadArgRetval)];
- thread_arg_retval = new (thread_arg_retval_placeholder) ThreadArgRetval();
-}
-
-ThreadArgRetval& GetThreadArgRetval() { return *thread_arg_retval; }
-
-ThreadContextDsanBase::ThreadContextDsanBase(int tid)
- : ThreadContextBase(tid) {}
-
-void ThreadContextDsanBase::OnStarted(void* arg) {
- SetCurrentThread(this);
- AllocatorThreadStart();
-}
-
-void ThreadContextDsanBase::OnFinished() {
- AllocatorThreadFinish();
- DTLS_Destroy();
- SetCurrentThread(nullptr);
-}
-
-u32 ThreadCreate(u32 parent_tid, bool detached, void* arg) {
- return thread_registry->CreateThread(0, detached, parent_tid, arg);
-}
-
-void ThreadContextDsanBase::ThreadStart(u32 tid, ThreadID os_id,
- ThreadType thread_type, void* arg) {
- thread_registry->StartThread(tid, os_id, thread_type, arg);
-}
-
-void ThreadFinish() { thread_registry->FinishThread(GetCurrentThreadId()); }
-
-void EnsureMainThreadIDIsCorrect() {
- if (GetCurrentThreadId() == kMainTid)
- GetCurrentThread()->os_id = GetTid();
-}
-
-///// Interface to the common DSan module. /////
-
-void GetThreadExtraStackRangesLocked(ThreadID os_id,
- InternalMmapVector<Range>* ranges) {}
-void GetThreadExtraStackRangesLocked(InternalMmapVector<Range>* ranges) {}
-
-void LockThreads() {
- thread_registry->Lock();
- thread_arg_retval->Lock();
-}
-
-void UnlockThreads() {
- thread_arg_retval->Unlock();
- thread_registry->Unlock();
-}
-
-ThreadRegistry* GetDsanThreadRegistryLocked() {
- thread_registry->CheckLocked();
- return thread_registry;
-}
-
-void GetRunningThreadsLocked(InternalMmapVector<ThreadID>* threads) {
- GetDsanThreadRegistryLocked()->RunCallbackForEachThreadLocked(
- [](ThreadContextBase* tctx, void* threads) {
- if (tctx->status == ThreadStatusRunning) {
- reinterpret_cast<InternalMmapVector<ThreadID>*>(threads)->push_back(
- tctx->os_id);
- }
- },
- threads);
-}
-
-void PrintThreads() {
- InternalScopedString out;
- PrintThreadHistory(*thread_registry, out);
- Report("%s\n", out.data());
-}
-
-void GetAdditionalThreadContextPtrsLocked(InternalMmapVector<uptr>* ptrs) {
- GetThreadArgRetval().GetAllPtrsLocked(ptrs);
-}
-
-} // namespace __dsan
diff --git a/compiler-rt/lib/dsan/dsan_thread.h b/compiler-rt/lib/dsan/dsan_thread.h
deleted file mode 100644
index 4e252bfc31f35..0000000000000
--- a/compiler-rt/lib/dsan/dsan_thread.h
+++ /dev/null
@@ -1,66 +0,0 @@
-//=-- dsan_thread.h -------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This file is a part of DoubleFreeSanitizer.
-// Thread registry for standalone DSan.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef DSAN_THREAD_H
-#define DSAN_THREAD_H
-
-#include "sanitizer_common/sanitizer_thread_arg_retval.h"
-#include "sanitizer_common/sanitizer_thread_registry.h"
-
-namespace __dsan {
-
-class ThreadContextDsanBase : public ThreadContextBase {
- public:
- explicit ThreadContextDsanBase(int tid);
- void OnStarted(void* arg) override;
- void OnFinished() override;
- uptr stack_begin() { return stack_begin_; }
- uptr stack_end() { return stack_end_; }
- uptr cache_begin() { return cache_begin_; }
- uptr cache_end() { return cache_end_; }
-
- // The argument is passed on to the subclass's OnStarted member function.
- static void ThreadStart(u32 tid, ThreadID os_id, ThreadType thread_type,
- void* onstarted_arg);
-
- protected:
- ~ThreadContextDsanBase() {}
- uptr stack_begin_ = 0;
- uptr stack_end_ = 0;
- uptr cache_begin_ = 0;
- uptr cache_end_ = 0;
-};
-
-// This subclass of ThreadContextDsanBase is declared in an OS-specific header.
-class ThreadContext;
-
-void InitializeThreads();
-void InitializeMainThread();
-
-ThreadRegistry* GetDsanThreadRegistryLocked();
-ThreadArgRetval& GetThreadArgRetval();
-
-u32 ThreadCreate(u32 tid, bool detached, void* arg = nullptr);
-void ThreadFinish();
-
-ThreadContextDsanBase* GetCurrentThread();
-inline u32 GetCurrentThreadId() {
- ThreadContextDsanBase* ctx = GetCurrentThread();
- return ctx ? ctx->tid : kInvalidTid;
-}
-void SetCurrentThread(ThreadContextDsanBase* tctx);
-void EnsureMainThreadIDIsCorrect();
-
-} // namespace __dsan
-
-#endif // DSAN_THREAD_H
diff --git a/compiler-rt/lib/dsan/weak_symbols.txt b/compiler-rt/lib/dsan/weak_symbols.txt
deleted file mode 100644
index c60959f813550..0000000000000
--- a/compiler-rt/lib/dsan/weak_symbols.txt
+++ /dev/null
@@ -1 +0,0 @@
-___dsan_default_options
diff --git a/compiler-rt/test/CMakeLists.txt b/compiler-rt/test/CMakeLists.txt
index 5e8ce9f13b670..3fab82518e75f 100644
--- a/compiler-rt/test/CMakeLists.txt
+++ b/compiler-rt/test/CMakeLists.txt
@@ -58,7 +58,7 @@ endif()
umbrella_lit_testsuite_begin(check-compiler-rt)
set(COMPILER_RT_KNOWN_TEST_SUITES
- builtins;ctx_profile;fuzzer;interception;lsan;dsan;memprof;metadata
+ builtins;ctx_profile;fuzzer;interception;lsan;memprof;metadata
;orc;profile;sanitizer_common;shadowcallstack
;ubsan;xray)
list(APPEND COMPILER_RT_KNOWN_TEST_SUITES ${ALL_SANITIZERS})
@@ -136,7 +136,6 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
compiler_rt_test_runtime(interception)
compiler_rt_test_runtime(lsan)
- compiler_rt_test_runtime(dsan)
compiler_rt_test_runtime(ubsan)
compiler_rt_test_runtime(sanitizer_common)
diff --git a/compiler-rt/test/dsan/CMakeLists.txt b/compiler-rt/test/dsan/CMakeLists.txt
deleted file mode 100644
index 858c2bbc4c8d4..0000000000000
--- a/compiler-rt/test/dsan/CMakeLists.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-set(DSAN_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
-
-set(DSAN_TESTSUITES)
-
-set(DSAN_TEST_ARCH ${DSAN_SUPPORTED_ARCH})
-if(APPLE)
- darwin_filter_host_archs(DSAN_SUPPORTED_ARCH DSAN_TEST_ARCH)
-endif()
-
-foreach(arch ${DSAN_TEST_ARCH})
- set(DSAN_TEST_TARGET_ARCH ${arch})
- string(TOLOWER "-${arch}" DSAN_TEST_CONFIG_SUFFIX)
- get_test_cc_for_arch(${arch} DSAN_TEST_TARGET_CC DSAN_TEST_TARGET_CFLAGS)
- string(TOUPPER ${arch} ARCH_UPPER_CASE)
-
- set(DSAN_LIT_TEST_MODE "Standalone")
- set(CONFIG_NAME ${ARCH_UPPER_CASE}DsanConfig)
- configure_lit_site_cfg(
- ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
- ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py)
- list(APPEND DSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
-endforeach()
-
-set(DSAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
-list(APPEND DSAN_TEST_DEPS dsan)
-add_lit_testsuite(check-dsan "Running the DoubleFreeSanitizer tests"
- ${DSAN_TESTSUITES}
- DEPENDS ${DSAN_TEST_DEPS})
diff --git a/compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp b/compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp
deleted file mode 100644
index e1061900f85d6..0000000000000
--- a/compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-// REQUIRES: linux
-// RUN: %clangxx_dsan %s -pthread -o %t
-// RUN: not %run %t 2>&1 | FileCheck %s
-
-#include <pthread.h>
-#include <stdlib.h>
-
-static pthread_barrier_t barrier;
-static void *p;
-
-static void *Free(void *) {
- pthread_barrier_wait(&barrier);
- free(p);
- return nullptr;
-}
-
-int main() {
- pthread_t first, second;
- p = malloc(16);
- pthread_barrier_init(&barrier, nullptr, 2);
- pthread_create(&first, nullptr, Free, nullptr);
- pthread_create(&second, nullptr, Free, nullptr);
- pthread_join(first, nullptr);
- pthread_join(second, nullptr);
- return 0;
-}
-
-// CHECK: ERROR: DoubleFreeSanitizer: double-free on address
diff --git a/compiler-rt/test/dsan/TestCases/double-free.c b/compiler-rt/test/dsan/TestCases/double-free.c
deleted file mode 100644
index 2f7e3a3cc7bfe..0000000000000
--- a/compiler-rt/test/dsan/TestCases/double-free.c
+++ /dev/null
@@ -1,17 +0,0 @@
-// RUN: %clang_dsan %s -o %t
-// RUN: not %run %t 2>&1 | FileCheck %s
-
-#include <stdlib.h>
-
-int main(void) {
- void *p = malloc(16);
- free(p);
- free(p);
- return 0;
-}
-
-// CHECK: ERROR: DoubleFreeSanitizer: double-free on address
-// CHECK: Second free (the invalid free) of address
-// CHECK: First free of address
-// CHECK: Original allocation of address
-// CHECK: SUMMARY: DoubleFreeSanitizer: double-free on address
diff --git a/compiler-rt/test/dsan/TestCases/invalid-free.c b/compiler-rt/test/dsan/TestCases/invalid-free.c
deleted file mode 100644
index 975d46510e9dc..0000000000000
--- a/compiler-rt/test/dsan/TestCases/invalid-free.c
+++ /dev/null
@@ -1,12 +0,0 @@
-// RUN: %clang_dsan %s -o %t
-// RUN: not %run %t 2>&1 | FileCheck %s
-
-#include <stdlib.h>
-
-int main(void) {
- char *p = malloc(16);
- free(p + 1);
- return 0;
-}
-
-// CHECK: ERROR: DoubleFreeSanitizer: invalid free on address
diff --git a/compiler-rt/test/dsan/TestCases/large-double-free.c b/compiler-rt/test/dsan/TestCases/large-double-free.c
deleted file mode 100644
index e563d31302c5d..0000000000000
--- a/compiler-rt/test/dsan/TestCases/large-double-free.c
+++ /dev/null
@@ -1,15 +0,0 @@
-// RUN: %clang_dsan %s -o %t
-// RUN: not %run %t 2>&1 | FileCheck %s
-
-#include <stdlib.h>
-
-int main(void) {
- void *p = malloc(1 << 20);
- free(p);
- free(p);
- return 0;
-}
-
-// CHECK: ERROR: DoubleFreeSanitizer: double-free on address
-// CHECK: First free of address
-// CHECK: Original allocation of address
diff --git a/compiler-rt/test/dsan/TestCases/realloc.c b/compiler-rt/test/dsan/TestCases/realloc.c
deleted file mode 100644
index 1e33af2dd5e93..0000000000000
--- a/compiler-rt/test/dsan/TestCases/realloc.c
+++ /dev/null
@@ -1,16 +0,0 @@
-// RUN: %clang_dsan %s -o %t
-// RUN: %env_dsan_opts=allocator_may_return_null=1 %run %t
-
-#include <stdint.h>
-#include <stdlib.h>
-
-int main(void) {
- char *p = malloc(16);
- p[0] = 42;
- if (realloc(p, SIZE_MAX) != NULL)
- return 1;
- if (p[0] != 42)
- return 2;
- free(p);
- return 0;
-}
diff --git a/compiler-rt/test/dsan/TestCases/reallocarray.c b/compiler-rt/test/dsan/TestCases/reallocarray.c
deleted file mode 100644
index 444cbe86b0686..0000000000000
--- a/compiler-rt/test/dsan/TestCases/reallocarray.c
+++ /dev/null
@@ -1,18 +0,0 @@
-// REQUIRES: linux
-// RUN: %clang_dsan %s -o %t
-// RUN: %env_dsan_opts=allocator_may_return_null=1 %run %t
-
-#define _GNU_SOURCE
-#include <stdint.h>
-#include <stdlib.h>
-
-int main(void) {
- char *p = malloc(16);
- p[0] = 42;
- if (reallocarray(p, SIZE_MAX, 2) != NULL)
- return 1;
- if (p[0] != 42)
- return 2;
- free(p);
- return 0;
-}
diff --git a/compiler-rt/test/dsan/TestCases/smoke.cpp b/compiler-rt/test/dsan/TestCases/smoke.cpp
deleted file mode 100644
index b5713624eb542..0000000000000
--- a/compiler-rt/test/dsan/TestCases/smoke.cpp
+++ /dev/null
@@ -1,10 +0,0 @@
-// RUN: %clangxx_dsan %s -o %t
-// RUN: %run %t
-
-#include <cstdlib>
-
-int main() {
- void *p = std::malloc(16);
- std::free(p);
- return 0;
-}
diff --git a/compiler-rt/test/dsan/lit.common.cfg.py b/compiler-rt/test/dsan/lit.common.cfg.py
deleted file mode 100644
index 95f00315d7b6f..0000000000000
--- a/compiler-rt/test/dsan/lit.common.cfg.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# -*- Python -*-
-
-# Common configuration for running double-free detection tests under DSan.
-
-import os
-import re
-
-import lit.util
-
-
-def get_required_attr(config, attr_name):
- attr_value = getattr(config, attr_name, None)
- if attr_value is 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.py " % attr_name
- )
- return attr_value
-
-
-# Setup source root.
-config.test_source_root = os.path.dirname(__file__)
-
-dsan_lit_test_mode = get_required_attr(config, "dsan_lit_test_mode")
-
-if dsan_lit_test_mode == "Standalone":
- config.name = "DoubleFreeSanitizer-Standalone"
- dsan_cflags = ["-fsanitize=doublefree"]
- config.available_features.add("dsan-standalone")
-else:
- lit_config.fatal("Unknown DSan test mode: %r" % dsan_lit_test_mode)
-config.name += config.name_suffix
-
-# Platform-specific default DSAN_OPTIONS for lit tests.
-default_common_opts_str = ":".join(list(config.default_sanitizer_opts))
-default_dsan_opts = default_common_opts_str
-if config.target_os == "Darwin":
- # On Darwin, we default to `abort_on_error=1`, which would make tests run
- # much slower. Let's override this and run lit tests with 'abort_on_error=0'.
- # Also, make sure we do not overwhelm the syslog while testing.
- default_dsan_opts += ":abort_on_error=0"
- default_dsan_opts += ":log_to_syslog=0"
-
-if default_dsan_opts:
- config.environment["DSAN_OPTIONS"] = default_dsan_opts
- default_dsan_opts += ":"
-config.substitutions.append(
- ("%env_dsan_opts=", "env DSAN_OPTIONS=" + default_dsan_opts)
-)
-
-if lit.util.which("strace"):
- config.available_features.add("strace")
-
-clang_cflags = ["-O0", config.target_cflags] + config.debug_info_flags
-if config.android:
- clang_cflags = clang_cflags + ["-fno-emulated-tls"]
-clang_cxxflags = config.cxx_mode_flags + clang_cflags
-dsan_incdir = config.test_source_root + "/../"
-clang_dsan_cflags = clang_cflags + dsan_cflags + ["-I%s" % dsan_incdir]
-clang_dsan_cxxflags = clang_cxxflags + dsan_cflags + ["-I%s" % dsan_incdir]
-
-config.clang_cflags = clang_cflags
-config.clang_cxxflags = clang_cxxflags
-
-
-def build_invocation(compile_flags):
- return " " + " ".join([config.clang] + compile_flags) + " "
-
-
-config.substitutions.append(("%clang ", build_invocation(clang_cflags)))
-config.substitutions.append(("%clangxx ", build_invocation(clang_cxxflags)))
-config.substitutions.append(("%clang_dsan ", build_invocation(clang_dsan_cflags)))
-config.substitutions.append(("%clangxx_dsan ", build_invocation(clang_dsan_cxxflags)))
-
-
-# DoubleFreeSanitizer tests are currently supported on
-# Android{aarch64, x86, x86_64}, x86-64 Linux, PowerPC64 Linux, arm Linux, mips64 Linux, s390x Linux, loongarch64 Linux and x86_64 Darwin.
-supported_android = (
- config.android
- and config.target_arch in ["x86_64", "i386", "aarch64"]
- and "android-thread-properties-api" in config.available_features
-)
-supported_linux = (
- (not config.android)
- and config.target_os == "Linux"
- and config.host_arch
- in [
- "aarch64",
- "x86_64",
- "ppc64",
- "ppc64le",
- "mips64",
- "riscv64",
- "arm",
- "armhf",
- "armv7l",
- "s390x",
- "loongarch64",
- ]
-)
-supported_darwin = config.target_os == "Darwin" and config.target_arch in ["x86_64"]
-supported_netbsd = config.target_os == "NetBSD" and config.target_arch in [
- "x86_64",
- "i386",
-]
-if not (supported_android or supported_linux or supported_darwin or supported_netbsd):
- config.unsupported = True
-
-# Don't support Thumb due to broken fast unwinder
-if re.search("mthumb", config.target_cflags) is not None:
- config.unsupported = True
-
-config.suffixes = [".c", ".cpp", ".mm"]
diff --git a/compiler-rt/test/dsan/lit.site.cfg.py.in b/compiler-rt/test/dsan/lit.site.cfg.py.in
deleted file mode 100644
index a493baf439446..0000000000000
--- a/compiler-rt/test/dsan/lit.site.cfg.py.in
+++ /dev/null
@@ -1,13 +0,0 @@
- at LIT_SITE_CFG_IN_HEADER@
-
-# Tool-specific config options.
-config.name_suffix = "@DSAN_TEST_CONFIG_SUFFIX@"
-config.target_cflags = "@DSAN_TEST_TARGET_CFLAGS@"
-config.dsan_lit_test_mode = "@DSAN_LIT_TEST_MODE@"
-config.target_arch = "@DSAN_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, "@DSAN_LIT_SOURCE_DIR@/lit.common.cfg.py")
>From 22fe8b6430610605290229176728d37f97aef3d4 Mon Sep 17 00:00:00 2001
From: "bojun.seo" <bojun.seo at lge.com>
Date: Wed, 26 Aug 2026 11:15:39 +0900
Subject: [PATCH 3/5] [sanitizer_common] Keep the original allocation alive
when Reallocate fails
CombinedAllocator::Reallocate() deallocated the original chunk even when the
replacement allocation could not be satisfied, so a failing realloc() released
memory that the caller still owned. Callers that correctly retry or fall back
on failure were left with a dangling pointer.
Return early instead, which matches realloc() semantics: on failure the
original allocation is untouched and remains owned by the caller.
Stand-alone LeakSanitizer is the in-tree caller that can observe this, and it
does so in the default configuration: its realloc() reaches this function
directly, so the allocator_may_return_null check in __lsan::Allocate() is
bypassed and null is returned to the program after the chunk has already gone
back on the free list. __sanitizer_get_ownership() still reports that address
as live while a later malloc() hands the very same chunk out again. This commit
stops the release; the LSan side is fixed in the next one.
AddressSanitizer and MemProfiler are unaffected: both implement their own
Reallocate(), which releases the original only once the replacement has been
allocated. InternalRealloc() is layered on this function, but it turns a null
result into a fatal ReportInternalAllocatorOutOfMemory(), so the dangling
pointer never reaches its caller there.
Assisted-by: Claude Opus 5
---
.../sanitizer_allocator_combined.h | 7 ++-
.../tests/sanitizer_allocator_test.cpp | 56 +++++++++++++++++++
2 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_allocator_combined.h b/compiler-rt/lib/sanitizer_common/sanitizer_allocator_combined.h
index 49940d9b5d505..a01b7c25ff247 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_allocator_combined.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_allocator_combined.h
@@ -106,8 +106,11 @@ class CombinedAllocator {
uptr old_size = GetActuallyAllocatedSize(p);
uptr memcpy_size = Min(new_size, old_size);
void *new_p = Allocate(cache, new_size, alignment);
- if (new_p)
- internal_memcpy(new_p, p, memcpy_size);
+ // Keep the original allocation alive when the request cannot be satisfied,
+ // matching realloc(): the caller still owns p and is responsible for it.
+ if (!new_p)
+ return nullptr;
+ internal_memcpy(new_p, p, memcpy_size);
Deallocate(cache, p);
return new_p;
}
diff --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_allocator_test.cpp b/compiler-rt/lib/sanitizer_common/tests/sanitizer_allocator_test.cpp
index 601897a64f051..9c53ac7132f86 100644
--- a/compiler-rt/lib/sanitizer_common/tests/sanitizer_allocator_test.cpp
+++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_allocator_test.cpp
@@ -750,6 +750,62 @@ void TestCombinedAllocator(uptr premapped_heap = 0) {
allocated.clear();
a->SwallowCache(&cache);
}
+
+ // A failing Reallocate() must keep the original allocation alive: the caller
+ // still owns p, so it must not be handed out again and must still be
+ // deallocatable exactly once. The size below overflows the size-plus-
+ // alignment check inside Allocate(), which is the cheapest way to make the
+ // replacement allocation fail; it logs a warning about the overflow.
+ {
+ const uptr kSize = 128;
+ char *p = reinterpret_cast<char *>(a->Allocate(&cache, kSize, 1));
+ EXPECT_NE(p, nullptr);
+ uptr *meta = reinterpret_cast<uptr *>(a->GetMetaData(p));
+ *meta = kSize;
+ internal_memset(p, 'x', kSize);
+
+ EXPECT_EQ(a->Reallocate(&cache, p, (uptr)-1, 1), nullptr);
+
+ EXPECT_EQ(*reinterpret_cast<uptr *>(a->GetMetaData(p)), kSize);
+ EXPECT_EQ(p[0], 'x');
+ EXPECT_EQ(p[kSize - 1], 'x');
+
+ // Releasing p would have put it back on the free list, so the allocator
+ // would hand the very same chunk out again.
+ void *others[8];
+ for (uptr i = 0; i < ARRAY_SIZE(others); i++) {
+ others[i] = a->Allocate(&cache, kSize, 1);
+ EXPECT_NE(others[i], p);
+ }
+ for (uptr i = 0; i < ARRAY_SIZE(others); i++)
+ a->Deallocate(&cache, others[i]);
+
+ *meta = 0;
+ a->Deallocate(&cache, p);
+ a->SwallowCache(&cache);
+ }
+
+ // Same check for the secondary allocator, where a release unmaps the chunk
+ // and ownership is therefore directly observable.
+ {
+ const uptr kLarge = 1 << 20;
+ void *p = a->Allocate(&cache, kLarge, 1);
+ EXPECT_NE(p, nullptr);
+ if (!a->FromPrimary(p)) {
+ uptr *meta = reinterpret_cast<uptr *>(a->GetMetaData(p));
+ *meta = kLarge;
+
+ EXPECT_EQ(a->Reallocate(&cache, p, (uptr)-1, 1), nullptr);
+
+ EXPECT_TRUE(a->PointerIsMine(p));
+ EXPECT_EQ(a->GetBlockBegin(p), p);
+ EXPECT_EQ(*reinterpret_cast<uptr *>(a->GetMetaData(p)), kLarge);
+ *meta = 0;
+ }
+ a->Deallocate(&cache, p);
+ a->SwallowCache(&cache);
+ }
+
a->DestroyCache(&cache);
a->TestOnlyUnmap();
}
>From d2872cb21c1c94ad66828163bf0157f20fa2735b Mon Sep 17 00:00:00 2001
From: "bojun.seo" <bojun.seo at lge.com>
Date: Wed, 9 Sep 2026 01:20:49 +0000
Subject: [PATCH 4/5] [lsan] Do not release the original allocation when
realloc fails
Reallocate() unregistered p before asking the allocator for a replacement, so a
failing realloc() reported a free hook for a pointer the caller still owned and
then re-registered that pointer with the *new* size and the realloc stack.
Hook consumers saw a free with no matching malloc, and
__sanitizer_get_allocated_size() reported a size that was never allocated.
Reorder the operation the way AddressSanitizer already does it: allocate the
replacement first and release the original only once that succeeded. On failure
p is now left completely untouched, so no hook fires and its metadata still
describes the original allocation.
The replacement now comes from __lsan::Allocate() rather than from
CombinedAllocator::Reallocate(), so realloc() also honors
allocator_may_return_null like every other allocation entry point: an
unsatisfiable request previously returned null whatever that flag said, and now
reports out-of-memory unless the flag allows null.
The same routing changes realloc(NULL, 0), which is malloc(0) by definition but
was registered with a requested size of zero: __sanitizer_get_ownership()
reported the returned pointer as not owned and __sanitizer_get_allocated_size()
reported zero for it, while malloc(0) reported one byte for the same thing. That
size is also how the double-free check added later in this series tells a live
chunk from a slot the allocator never handed out, so leaving it at zero would
have made free(realloc(NULL, 0)) report an invalid free. It is now tracked
exactly like malloc(0), and realloc_zero.c is extended to cover that so it
cannot silently regress.
LSan was the only sanitizer front-end still calling
CombinedAllocator::Reallocate(); the function keeps its other in-tree caller,
InternalRealloc().
Assisted-by: Claude Opus 5
---
compiler-rt/lib/lsan/lsan_allocator.cpp | 24 ++++--
.../Linux/realloc_failure_keeps_original.cpp | 80 +++++++++++++++++++
.../test/lsan/TestCases/realloc_zero.c | 16 ++++
3 files changed, 113 insertions(+), 7 deletions(-)
create mode 100644 compiler-rt/test/lsan/TestCases/Linux/realloc_failure_keeps_original.cpp
diff --git a/compiler-rt/lib/lsan/lsan_allocator.cpp b/compiler-rt/lib/lsan/lsan_allocator.cpp
index 110c1cfc3bf92..e0e1bf17f66f9 100644
--- a/compiler-rt/lib/lsan/lsan_allocator.cpp
+++ b/compiler-rt/lib/lsan/lsan_allocator.cpp
@@ -133,13 +133,23 @@ void *Reallocate(const StackTrace &stack, void *p, uptr new_size,
ReportAllocationSizeTooBig(new_size, stack);
return nullptr;
}
- RegisterDeallocation(p);
- void *new_p =
- allocator.Reallocate(GetAllocatorCache(), p, new_size, alignment);
- if (new_p)
- RegisterAllocation(stack, new_p, new_size);
- else if (new_size != 0)
- RegisterAllocation(stack, p, new_size);
+ if (!p)
+ return Allocate(stack, new_size, alignment, false);
+ if (!new_size) {
+ Deallocate(p);
+ return nullptr;
+ }
+ // Allocate the replacement first. If it fails, p must be left completely
+ // untouched: it is still owned by the caller, so its metadata must keep the
+ // original size and allocation stack, and no free hook may be reported.
+ ChunkMetadata *m = Metadata(p);
+ CHECK(m);
+ const uptr old_size = m->requested_size;
+ void *new_p = Allocate(stack, new_size, alignment, false);
+ if (!new_p)
+ return nullptr;
+ internal_memcpy(new_p, p, Min(new_size, old_size));
+ Deallocate(p);
return new_p;
}
diff --git a/compiler-rt/test/lsan/TestCases/Linux/realloc_failure_keeps_original.cpp b/compiler-rt/test/lsan/TestCases/Linux/realloc_failure_keeps_original.cpp
new file mode 100644
index 0000000000000..de60b7de434ee
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/Linux/realloc_failure_keeps_original.cpp
@@ -0,0 +1,80 @@
+// Verifies that a failing realloc() leaves the original allocation untouched:
+// it keeps its contents, its reported size and its allocation stack, and no
+// free hook is reported for it.
+//
+// Linux only: the setup below reads /proc/self/statm to size the address-space
+// cap that makes the replacement allocation fail.
+//
+// RUN: %clangxx_lsan %s -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s
+// REQUIRES: lsan-standalone
+
+#include <sanitizer/allocator_interface.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <sys/resource.h>
+#include <unistd.h>
+
+static void *g_freed;
+static void *g_alloced;
+
+static void OnMalloc(const volatile void *ptr, size_t) {
+ g_alloced = (void *)ptr;
+}
+
+static void OnFree(const volatile void *ptr) { g_freed = (void *)ptr; }
+
+// Caps the address space just above the current usage so that the next large
+// mmap fails inside the allocator instead of being rejected up front by
+// max_allocation_size_mb.
+static void CapAddressSpace() {
+ FILE *f = fopen("/proc/self/statm", "r");
+ assert(f);
+ unsigned long vsz_pages = 0;
+ int scanned = fscanf(f, "%lu", &vsz_pages);
+ fclose(f);
+ assert(scanned == 1);
+
+ rlimit rl;
+ int res = getrlimit(RLIMIT_AS, &rl);
+ assert(res == 0);
+ rl.rlim_cur = (rlim_t)vsz_pages * getpagesize() + (64UL << 20);
+ res = setrlimit(RLIMIT_AS, &rl);
+ assert(res == 0);
+}
+
+int main() {
+ const size_t kSize = 100;
+ char *p = (char *)malloc(kSize);
+ assert(p);
+ memset(p, 'a', kSize);
+
+ // Install the hooks only after CapAddressSpace(), which itself allocates.
+ CapAddressSpace();
+ int installed = __sanitizer_install_malloc_and_free_hooks(OnMalloc, OnFree);
+ assert(installed);
+
+ void *q = realloc(p, 512UL << 20);
+ assert(q == NULL);
+
+ // The failed realloc must report neither a free of p nor a new allocation.
+ assert(g_freed == nullptr);
+ assert(g_alloced == nullptr);
+
+ assert(__sanitizer_get_ownership(p));
+ assert(__sanitizer_get_allocated_size(p) == kSize);
+ for (size_t i = 0; i < kSize; ++i)
+ assert(p[i] == 'a');
+
+ fprintf(stderr, "original allocation survived realloc failure\n");
+ free(p);
+ assert(g_freed == p);
+ fprintf(stderr, "freed once\n");
+ return 0;
+}
+
+// CHECK: original allocation survived realloc failure
+// CHECK: freed once
diff --git a/compiler-rt/test/lsan/TestCases/realloc_zero.c b/compiler-rt/test/lsan/TestCases/realloc_zero.c
index d4ce4754d9bdf..c3039833f5ecf 100644
--- a/compiler-rt/test/lsan/TestCases/realloc_zero.c
+++ b/compiler-rt/test/lsan/TestCases/realloc_zero.c
@@ -4,10 +4,26 @@
#include <assert.h>
#include <stdlib.h>
+#if __has_feature(leak_sanitizer)
+# include <sanitizer/allocator_interface.h>
+#endif
+
int main() {
char *p = malloc(1);
// The behavior of realloc(p, 0) is implementation-defined.
// We free the allocation.
assert(realloc(p, 0) == NULL);
+
+ // realloc(NULL, 0) allocates instead, and must be tracked exactly like
+ // malloc(0): a distinct, owned, one-byte allocation. Reporting it as a
+ // zero-byte allocation would hide it from the allocator interface.
+ void *q = realloc(NULL, 0);
+ assert(q != NULL);
+#if __has_feature(leak_sanitizer)
+ assert(__sanitizer_get_ownership(q));
+ assert(__sanitizer_get_allocated_size(q) == 1);
+#endif
+ free(q);
+
p = 0;
}
>From c6d14fa1a9d6b2ac7a6884414cdf414fc762b13e Mon Sep 17 00:00:00 2001
From: "bojun.seo" <bojun.seo at lge.com>
Date: Sun, 6 Sep 2026 15:01:57 +0000
Subject: [PATCH 5/5] [lsan] Add optional standalone double-free detection
Add an opt-in check that reports a call to free() on an allocation that has
already been freed, enabled with LSAN_OPTIONS=detect_double_free=1. It is off
by default, and it applies to stand-alone LeakSanitizer only: under
AddressSanitizer or HWAddressSanitizer the allocator, and its own double-free
detection, come from that tool instead.
Chunks served by the primary allocator keep their metadata mapped after they
are freed, so the allocation stack and the first-free stack are stored inline
in ChunkMetadata, which stays 16 bytes, and the check is a single
compare-exchange that takes no lock.
Chunks served by the secondary allocator are unmapped along with their
metadata, so their stacks go into a side table instead. Validating such a
chunk, copying its stacks into the table and unmapping it all happen under one
mutex that every secondary allocation and free takes; without that, a
concurrent free could unmap the metadata between another thread's validity
check and its use. double_free_max_entries bounds that table and evicts the
oldest entry.
Validating the freed pointer is a prerequisite for the check, so while the
option is enabled an invalid free is reported as a diagnostic instead of being
handed to the allocator, which would otherwise dereference metadata that may
not be mapped and fault inside the runtime. realloc() is checked the same way.
The feature is disabled on macOS and NetBSD, where fork handling does not go
through the LSan pthread_atfork hooks that protect the side table.
Assisted-by: Claude Opus 5
---
clang/docs/LeakSanitizer.md | 33 ++
clang/docs/ReleaseNotes.md | 5 +
compiler-rt/lib/lsan/lsan.cpp | 11 +
compiler-rt/lib/lsan/lsan.h | 25 +-
compiler-rt/lib/lsan/lsan_allocator.cpp | 382 +++++++++++++++++-
compiler-rt/lib/lsan/lsan_allocator.h | 62 ++-
compiler-rt/lib/lsan/lsan_flags.inc | 15 +
compiler-rt/lib/lsan/lsan_interceptors.cpp | 47 ++-
compiler-rt/lib/lsan/lsan_mac.cpp | 2 +-
compiler-rt/lib/lsan/lsan_malloc_mac.cpp | 10 +-
compiler-rt/lib/lsan/lsan_posix.cpp | 2 +
.../lsan/TestCases/Linux/double_free_fork.cpp | 58 +++
.../test/lsan/TestCases/double_free.cpp | 38 ++
.../lsan/TestCases/double_free_eviction.cpp | 51 +++
.../test/lsan/TestCases/double_free_hooks.cpp | 43 ++
.../test/lsan/TestCases/double_free_large.cpp | 26 ++
.../TestCases/double_free_max_entries.cpp | 26 ++
.../lsan/TestCases/double_free_realloc.cpp | 81 ++++
.../test/lsan/TestCases/double_free_sized.cpp | 59 +++
.../lsan/TestCases/double_free_threaded.cpp | 53 +++
.../test/lsan/TestCases/invalid_free.cpp | 40 ++
21 files changed, 1027 insertions(+), 42 deletions(-)
create mode 100644 compiler-rt/test/lsan/TestCases/Linux/double_free_fork.cpp
create mode 100644 compiler-rt/test/lsan/TestCases/double_free.cpp
create mode 100644 compiler-rt/test/lsan/TestCases/double_free_eviction.cpp
create mode 100644 compiler-rt/test/lsan/TestCases/double_free_hooks.cpp
create mode 100644 compiler-rt/test/lsan/TestCases/double_free_large.cpp
create mode 100644 compiler-rt/test/lsan/TestCases/double_free_max_entries.cpp
create mode 100644 compiler-rt/test/lsan/TestCases/double_free_realloc.cpp
create mode 100644 compiler-rt/test/lsan/TestCases/double_free_sized.cpp
create mode 100644 compiler-rt/test/lsan/TestCases/double_free_threaded.cpp
create mode 100644 compiler-rt/test/lsan/TestCases/invalid_free.cpp
diff --git a/clang/docs/LeakSanitizer.md b/clang/docs/LeakSanitizer.md
index 816c976c0e231..27297716d238a 100644
--- a/clang/docs/LeakSanitizer.md
+++ b/clang/docs/LeakSanitizer.md
@@ -40,6 +40,39 @@ To use LeakSanitizer in stand-alone mode, link your program with
link step, so that it would link in proper LeakSanitizer run-time library
into the final executable.
+### Double-free detection
+
+Stand-alone LeakSanitizer can optionally report a call to `free` on an
+allocation that has already been freed. The check is disabled by default;
+enable it at run time through `LSAN_OPTIONS`:
+
+```console
+$ clang -g -O0 -fno-omit-frame-pointer -fsanitize=leak double-free.c -o double-free
+$ LSAN_OPTIONS=detect_double_free=1 ./double-free
+==1234==ERROR: LeakSanitizer: attempting double-free on 0x504000000010 in thread T0:
+The second free occurred here:
+...
+The first free occurred here:
+...
+The memory was allocated here:
+...
+SUMMARY: LeakSanitizer: double-free
+```
+
+A double free is undefined behavior, so build a reproducer at `-O0`: an
+optimizing compiler may delete the second `free` before the runtime can
+observe it.
+
+Validating the freed pointer is a prerequisite for the check, so while the
+option is enabled a `free` of a pointer the allocator never returned is
+reported as well, as `bad-free`. Both reports are fatal.
+`double_free_max_entries` bounds the state kept for large allocations, whose
+metadata does not survive the first free; its default is `65536`.
+
+The option applies to stand-alone LeakSanitizer only, and is not supported on
+macOS or NetBSD. Under AddressSanitizer or HWAddressSanitizer the allocator
+comes from that tool; use its own double-free diagnostics instead.
+
## Security Considerations
LeakSanitizer is a bug detection tool and its runtime is not meant to be
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 7108392abbaa1..1c408a2d90365 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -540,6 +540,11 @@ features cannot lower the translation-unit ABI level;
### Sanitizers
+- Standalone LeakSanitizer now supports optional double-free detection, enabled
+ with `LSAN_OPTIONS=detect_double_free=1`. While it is enabled, an invalid free
+ is also reported as a diagnostic instead of crashing. See the LeakSanitizer
+ documentation for details.
+
### Python Binding Changes
### OpenMP Support
diff --git a/compiler-rt/lib/lsan/lsan.cpp b/compiler-rt/lib/lsan/lsan.cpp
index 798294b499e2f..da23299ee4609 100644
--- a/compiler-rt/lib/lsan/lsan.cpp
+++ b/compiler-rt/lib/lsan/lsan.cpp
@@ -76,6 +76,17 @@ static void InitializeFlags() {
InitializeCommonFlags();
+#if SANITIZER_APPLE || SANITIZER_NETBSD
+ // The fork handling on these platforms does not go through the LSan
+ // pthread_atfork hooks that protect the double-free side table.
+ if (f->detect_double_free) {
+ Report(
+ "WARNING: LeakSanitizer: detect_double_free is not supported on this "
+ "platform, disabling it.\n");
+ f->detect_double_free = false;
+ }
+#endif
+
if (Verbosity()) ReportUnrecognizedFlags();
if (common_flags()->help) parser.PrintFlagDescriptions();
diff --git a/compiler-rt/lib/lsan/lsan.h b/compiler-rt/lib/lsan/lsan.h
index 0074ad5308785..1232bba38059a 100644
--- a/compiler-rt/lib/lsan/lsan.h
+++ b/compiler-rt/lib/lsan/lsan.h
@@ -20,18 +20,31 @@
#include "sanitizer_common/sanitizer_flags.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
-#define GET_STACK_TRACE(max_size, fast) \
- __sanitizer::BufferedStackTrace stack; \
- stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, fast, \
- max_size);
+// Unwinds from an explicit frame. The free interceptors capture their stack in
+// an out-of-line helper (see lsan_allocator.h) and pass their own pc and bp
+// along, so that the reported stack starts at the intercepted function and not
+// at the helper.
+#define GET_STACK_TRACE_AT(pc, bp, max_size, fast) \
+ __sanitizer::BufferedStackTrace stack; \
+ stack.Unwind((pc), (bp), nullptr, fast, max_size);
+
+#define GET_STACK_TRACE(max_size, fast) \
+ GET_STACK_TRACE_AT(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), \
+ max_size, fast)
#define GET_STACK_TRACE_FATAL \
GET_STACK_TRACE(kStackTraceMax, common_flags()->fast_unwind_on_fatal)
-#define GET_STACK_TRACE_MALLOC \
- GET_STACK_TRACE(__sanitizer::common_flags()->malloc_context_size, \
+#define GET_STACK_TRACE_MALLOC \
+ GET_STACK_TRACE(common_flags()->malloc_context_size, \
common_flags()->fast_unwind_on_malloc)
+// Like GET_STACK_TRACE_MALLOC, but unwinds from an explicit frame.
+#define GET_STACK_TRACE_FREE_AT(pc, bp) \
+ GET_STACK_TRACE_AT((pc), (bp), \
+ common_flags()->malloc_context_size, \
+ common_flags()->fast_unwind_on_malloc)
+
#define GET_STACK_TRACE_THREAD GET_STACK_TRACE(kStackTraceMax, true)
namespace __lsan {
diff --git a/compiler-rt/lib/lsan/lsan_allocator.cpp b/compiler-rt/lib/lsan/lsan_allocator.cpp
index e0e1bf17f66f9..6ffa00faa4697 100644
--- a/compiler-rt/lib/lsan/lsan_allocator.cpp
+++ b/compiler-rt/lib/lsan/lsan_allocator.cpp
@@ -13,15 +13,20 @@
#include "lsan_allocator.h"
+#include "lsan.h"
+#include "lsan_common.h"
#include "sanitizer_common/sanitizer_allocator.h"
#include "sanitizer_common/sanitizer_allocator_checks.h"
#include "sanitizer_common/sanitizer_allocator_interface.h"
#include "sanitizer_common/sanitizer_allocator_report.h"
+#include "sanitizer_common/sanitizer_atomic.h"
+#include "sanitizer_common/sanitizer_dense_map.h"
#include "sanitizer_common/sanitizer_errno.h"
#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_placement_new.h"
+#include "sanitizer_common/sanitizer_report_decorator.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
-#include "lsan_common.h"
extern "C" void *memset(void *ptr, int value, uptr num);
@@ -38,6 +43,113 @@ static Allocator allocator;
static uptr max_malloc_size;
+// Double-free detection keeps two stacks per chunk. Primary-allocator chunks
+// keep their metadata mapped after a free, so both stacks live inline in
+// ChunkMetadata and the check is a lock-free compare-exchange. Secondary
+// chunks are unmapped along with their metadata, so their stacks go into the
+// side table below.
+
+namespace {
+
+struct SecondaryFreeRecord {
+ u32 alloc_stack_id;
+ u32 free_stack_id;
+ // Index of this address in secondary_free_fifo, or kNoFifoSlot when the
+ // table is unbounded and nothing is ever evicted. Storing it here makes
+ // eviction and invalidation O(1).
+ u32 fifo_slot;
+};
+
+using SecondaryFreeMap = DenseMap<uptr, SecondaryFreeRecord>;
+
+const u32 kNoFifoSlot = ~0u;
+
+// Upper bound for double_free_max_entries, 4M entries. It keeps the worst-case
+// side table in the low hundreds of megabytes and turns a mistyped flag into a
+// startup warning instead of an out-of-memory death.
+const int kMaxSecondaryFreeEntries = 1 << 22;
+
+} // namespace
+
+// Guards the side table and its FIFO.
+static Mutex secondary_free_mutex;
+
+// compiler-rt runtimes must not emit static constructors, so the map is
+// placement-new'd into this buffer and never destroyed.
+alignas(64) static char secondary_free_placeholder[sizeof(SecondaryFreeMap)];
+static SecondaryFreeMap *secondary_free_map;
+
+// Addresses in first-free order. Eviction takes the oldest entry, so a bounded
+// table always remembers the most recent frees.
+static uptr *secondary_free_fifo;
+static u32 secondary_free_fifo_size;
+static u32 secondary_free_fifo_head;
+
+void LockDoubleFree() { secondary_free_mutex.Lock(); }
+
+void UnlockDoubleFree() { secondary_free_mutex.Unlock(); }
+
+static void InitializeDoubleFree() {
+ if (!flags()->detect_double_free)
+ return;
+ secondary_free_map = new (secondary_free_placeholder) SecondaryFreeMap();
+ int max_entries = flags()->double_free_max_entries;
+ if (max_entries < 0) {
+ Report(
+ "WARNING: LeakSanitizer: double_free_max_entries=%d is negative, "
+ "using 0 (unlimited) instead.\n",
+ max_entries);
+ max_entries = 0;
+ } else if (max_entries > kMaxSecondaryFreeEntries) {
+ Report(
+ "WARNING: LeakSanitizer: double_free_max_entries=%d is too large, "
+ "capping it at %d.\n",
+ max_entries, kMaxSecondaryFreeEntries);
+ max_entries = kMaxSecondaryFreeEntries;
+ }
+ if (max_entries > 0) {
+ secondary_free_fifo_size = static_cast<u32>(max_entries);
+ secondary_free_fifo = static_cast<uptr *>(
+ MmapOrDie(secondary_free_fifo_size * sizeof(uptr), "DoubleFreeFifo"));
+ }
+}
+
+static void RemoveSecondaryFreeRecordLocked(uptr chunk) {
+ auto *entry = secondary_free_map->find(chunk);
+ if (!entry)
+ return;
+ if (entry->second.fifo_slot != kNoFifoSlot)
+ secondary_free_fifo[entry->second.fifo_slot] = 0;
+ secondary_free_map->erase(chunk);
+}
+
+static void AddSecondaryFreeRecordLocked(uptr chunk, u32 alloc_stack_id,
+ u32 free_stack_id) {
+ // Drop any previous record for this address so that it does not leave a
+ // dangling FIFO slot that would later evict the record added here.
+ RemoveSecondaryFreeRecordLocked(chunk);
+
+ u32 fifo_slot = kNoFifoSlot;
+ if (secondary_free_fifo) {
+ fifo_slot = secondary_free_fifo_head;
+ if (uptr evicted = secondary_free_fifo[fifo_slot])
+ secondary_free_map->erase(evicted);
+ secondary_free_fifo[fifo_slot] = chunk;
+ if (++secondary_free_fifo_head == secondary_free_fifo_size)
+ secondary_free_fifo_head = 0;
+ }
+ (*secondary_free_map)[chunk] = {alloc_stack_id, free_stack_id, fifo_slot};
+}
+
+static bool FindSecondaryFreeRecordLocked(uptr chunk,
+ SecondaryFreeRecord *record) {
+ auto *entry = secondary_free_map->find(chunk);
+ if (!entry)
+ return false;
+ *record = entry->second;
+ return true;
+}
+
void InitializeAllocator() {
SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
allocator.InitLinkerInitialized(
@@ -47,6 +159,7 @@ void InitializeAllocator() {
kMaxAllowedMallocSize);
else
max_malloc_size = kMaxAllowedMallocSize;
+ InitializeDoubleFree();
}
void AllocatorThreadStart() { allocator.InitCache(GetAllocatorCache()); }
@@ -60,23 +173,173 @@ static ChunkMetadata *Metadata(const void *p) {
return reinterpret_cast<ChunkMetadata *>(allocator.GetMetaData(p));
}
+static atomic_uint8_t *ChunkStateOf(ChunkMetadata *m) {
+ return reinterpret_cast<atomic_uint8_t *>(m);
+}
+
+namespace {
+
+class Decorator : public __sanitizer::SanitizerCommonDecorator {
+ public:
+ Decorator() : SanitizerCommonDecorator() {}
+ const char *Error() { return Red(); }
+};
+
+} // namespace
+
+static void PrintStackById(const char *label, u32 stack_id) {
+ if (!stack_id)
+ return;
+ Printf("%s", label);
+ StackDepotGet(stack_id).Print();
+}
+
+// Renders " in thread Tn", or an empty string when the thread has no LSan
+// context yet. The wording follows AddressSanitizer's reports.
+static const char *ThreadSuffix(char *buffer, uptr size) {
+ const u32 tid = GetCurrentThreadId();
+ if (tid == static_cast<u32>(kInvalidTid))
+ internal_strncpy(buffer, "", size);
+ else
+ internal_snprintf(buffer, size, " in thread T%u", tid);
+ return buffer;
+}
+
+// Both reporters hold the error-report lock across Die(), the way ASan's
+// ScopedInErrorReport does, so a thread that reaches a report while another is
+// printing blocks on the lock instead of appending a truncated second report.
+static void NORETURN ReportDoubleFree(uptr addr, u32 alloc_stack_id,
+ u32 free_stack_id,
+ const StackTrace *second_free_stack) {
+ ScopedErrorReportLock lock;
+ char thread[64];
+ Decorator d;
+ Printf("%s", d.Error());
+ Report("ERROR: LeakSanitizer: attempting double-free on %p%s:\n",
+ (void *)addr, ThreadSuffix(thread, sizeof(thread)));
+ Printf("%s", d.Default());
+
+ Printf("The second free occurred here:\n");
+ if (second_free_stack)
+ second_free_stack->Print();
+ else
+ Printf(" <empty stack>\n\n");
+
+ PrintStackById("The first free occurred here:\n", free_stack_id);
+ PrintStackById("The memory was allocated here:\n", alloc_stack_id);
+
+ if (second_free_stack)
+ ReportErrorSummary("double-free", second_free_stack);
+ else
+ ReportErrorSummary("double-free");
+ Die();
+}
+
+static void NORETURN ReportInvalidFree(uptr addr, const StackTrace *stack) {
+ ScopedErrorReportLock lock;
+ char thread[64];
+ Decorator d;
+ Printf("%s", d.Error());
+ Report(
+ "ERROR: LeakSanitizer: attempting free on address which was not "
+ "malloc()-ed: %p%s\n",
+ (void *)addr, ThreadSuffix(thread, sizeof(thread)));
+ Printf("%s", d.Default());
+
+ if (stack) {
+ stack->Print();
+ ReportErrorSummary("bad-free", stack);
+ } else {
+ ReportErrorSummary("bad-free");
+ }
+ Die();
+}
+
+// Must be called with secondary_free_mutex released, since symbolizing can
+// re-enter the allocator.
+static void NORETURN ReportNonLiveFree(uptr chunk,
+ const SecondaryFreeRecord &record,
+ bool remembered,
+ const StackTrace *stack) {
+ if (remembered)
+ ReportDoubleFree(chunk, record.alloc_stack_id, record.free_stack_id, stack);
+ ReportInvalidFree(chunk, stack);
+}
+
static void RegisterAllocation(const StackTrace &stack, void *p, uptr size) {
if (!p) return;
ChunkMetadata *m = Metadata(p);
CHECK(m);
+ if (flags()->detect_double_free && !allocator.FromPrimary(p)) {
+ // This address may still be recorded from an earlier large allocation that
+ // was freed and unmapped; the new allocation supersedes it.
+ Lock l(&secondary_free_mutex);
+ RemoveSecondaryFreeRecordLocked(reinterpret_cast<uptr>(p));
+ }
m->tag = DisabledInThisThread() ? kIgnored : kDirectlyLeaked;
m->stack_trace_id = StackDepotPut(stack);
+ m->free_stack_id = 0;
m->requested_size = size;
- atomic_store(reinterpret_cast<atomic_uint8_t *>(m), 1, memory_order_relaxed);
+ // These fields are only acquired by the double-free checks, which run when
+ // detect_double_free is on. Paying for a release store only then avoids a
+ // barrier on every malloc() elsewhere.
+ atomic_store(ChunkStateOf(m), kChunkAllocated,
+ flags()->detect_double_free ? memory_order_release
+ : memory_order_relaxed);
RunMallocHooks(p, size);
}
-static void RegisterDeallocation(void *p) {
- if (!p) return;
- ChunkMetadata *m = Metadata(p);
- CHECK(m);
- RunFreeHooks(p);
- atomic_store(reinterpret_cast<atomic_uint8_t *>(m), 0, memory_order_relaxed);
+static void DeallocateCheckedPrimary(void *p, const StackTrace *free_stack) {
+ const uptr chunk = reinterpret_cast<uptr>(p);
+ ChunkMetadata *m = nullptr;
+ if (LIKELY(allocator.GetBlockBegin(p) == p)) {
+ m = Metadata(p);
+ CHECK(m);
+ }
+ // The check above rejects an interior pointer and an address the primary
+ // never mapped, but a mapped slot that was never handed out passes it. Such
+ // a slot has no requested size, and freeing it is an invalid free, not a
+ // double free.
+ if (UNLIKELY(!m || m->requested_size == 0))
+ ReportInvalidFree(chunk, free_stack);
+
+ u8 expected = kChunkAllocated;
+ if (UNLIKELY(!atomic_compare_exchange_strong(
+ ChunkStateOf(m), &expected, kChunkFreeing, memory_order_acq_rel))) {
+ // Lost the race, or the chunk was already free. The compare-exchange has
+ // acquire semantics, so a kChunkFree state means free_stack_id is visible.
+ // kChunkFreeing means the winner has not published it yet.
+ ReportDoubleFree(chunk, m->stack_trace_id,
+ expected == kChunkFree ? m->free_stack_id : 0, free_stack);
+ }
+ m->free_stack_id = free_stack ? StackDepotPut(*free_stack) : 0;
+ atomic_store(ChunkStateOf(m), kChunkFree, memory_order_release);
+ allocator.Deallocate(GetAllocatorCache(), p);
+}
+
+// Releasing a secondary chunk unmaps its metadata, so the stacks are copied
+// into the side table first and the whole sequence holds secondary_free_mutex,
+// or a concurrent free could unmap it mid-check.
+static void DeallocateCheckedSecondary(void *p, const StackTrace *free_stack) {
+ const uptr chunk = reinterpret_cast<uptr>(p);
+ SecondaryFreeRecord record = {};
+ bool remembered = false;
+ {
+ Lock l(&secondary_free_mutex);
+ if (LIKELY(allocator.GetBlockBegin(p) == p)) {
+ ChunkMetadata *m = Metadata(p);
+ CHECK(m);
+ const u32 free_stack_id = free_stack ? StackDepotPut(*free_stack) : 0;
+ AddSecondaryFreeRecordLocked(chunk, m->stack_trace_id, free_stack_id);
+ atomic_store(ChunkStateOf(m), kChunkFree, memory_order_relaxed);
+ allocator.Deallocate(GetAllocatorCache(), p);
+ return;
+ }
+ // A large chunk is unmapped as soon as it is freed, so a second free can
+ // only be recognized from the side table.
+ remembered = FindSecondaryFreeRecordLocked(chunk, &record);
+ }
+ ReportNonLiveFree(chunk, record, remembered, free_stack);
}
static void *ReportAllocationSizeTooBig(uptr size, const StackTrace &stack) {
@@ -122,11 +385,61 @@ static void *Calloc(uptr nmemb, uptr size, const StackTrace &stack) {
return Allocate(stack, size, 1, true);
}
-void Deallocate(void *p) {
- RegisterDeallocation(p);
+static void DeallocateUnchecked(void *p) {
+ ChunkMetadata *m = Metadata(p);
+ CHECK(m);
+ atomic_store(ChunkStateOf(m), kChunkFree, memory_order_relaxed);
allocator.Deallocate(GetAllocatorCache(), p);
}
+void Deallocate(void *p, const StackTrace *free_stack) {
+ if (!p) return;
+ // Run the hooks before the chunk state changes, so a hook still observes p as
+ // owned. AddressSanitizer runs them at the same point, before it validates p.
+ RunFreeHooks(p);
+ if (flags()->detect_double_free) {
+ if (allocator.FromPrimary(p))
+ DeallocateCheckedPrimary(p, free_stack);
+ else
+ DeallocateCheckedSecondary(p, free_stack);
+ return;
+ }
+ DeallocateUnchecked(p);
+}
+
+// Returns the requested size of the live chunk starting at `p`, and reports
+// without returning if `p` is not one. A secondary chunk is unmapped as soon as
+// it is freed, so this has to run before the old chunk's metadata is read.
+static uptr CheckedReallocSourceSize(void *p, const StackTrace *stack) {
+ const uptr chunk = reinterpret_cast<uptr>(p);
+ if (allocator.FromPrimary(p)) {
+ if (LIKELY(allocator.GetBlockBegin(p) == p)) {
+ ChunkMetadata *m = Metadata(p);
+ CHECK(m);
+ const u8 state = atomic_load(ChunkStateOf(m), memory_order_acquire);
+ if (LIKELY(state == kChunkAllocated && m->requested_size != 0))
+ return m->requested_size;
+ if (m->requested_size != 0)
+ ReportDoubleFree(chunk, m->stack_trace_id,
+ state == kChunkFree ? m->free_stack_id : 0, stack);
+ }
+ ReportInvalidFree(chunk, stack);
+ }
+
+ SecondaryFreeRecord record = {};
+ bool remembered = false;
+ {
+ Lock l(&secondary_free_mutex);
+ if (LIKELY(allocator.GetBlockBegin(p) == p)) {
+ ChunkMetadata *m = Metadata(p);
+ CHECK(m);
+ return m->requested_size;
+ }
+ remembered = FindSecondaryFreeRecordLocked(chunk, &record);
+ }
+ ReportNonLiveFree(chunk, record, remembered, stack);
+}
+
void *Reallocate(const StackTrace &stack, void *p, uptr new_size,
uptr alignment) {
if (new_size > max_malloc_size) {
@@ -136,20 +449,25 @@ void *Reallocate(const StackTrace &stack, void *p, uptr new_size,
if (!p)
return Allocate(stack, new_size, alignment, false);
if (!new_size) {
- Deallocate(p);
+ Deallocate(p, &stack);
return nullptr;
}
+ uptr old_size;
+ if (flags()->detect_double_free) {
+ old_size = CheckedReallocSourceSize(p, &stack);
+ } else {
+ ChunkMetadata *m = Metadata(p);
+ CHECK(m);
+ old_size = m->requested_size;
+ }
// Allocate the replacement first. If it fails, p must be left completely
// untouched: it is still owned by the caller, so its metadata must keep the
// original size and allocation stack, and no free hook may be reported.
- ChunkMetadata *m = Metadata(p);
- CHECK(m);
- const uptr old_size = m->requested_size;
void *new_p = Allocate(stack, new_size, alignment, false);
if (!new_p)
return nullptr;
internal_memcpy(new_p, p, Min(new_size, old_size));
- Deallocate(p);
+ Deallocate(p, &stack);
return new_p;
}
@@ -226,13 +544,34 @@ void *lsan_malloc(uptr size, const StackTrace &stack) {
return SetErrnoOnNull(Allocate(stack, size, 1, kAlwaysClearMemory));
}
-void lsan_free(void *p) {
- Deallocate(p);
+void lsan_free(void *p, const StackTrace *free_stack) {
+ Deallocate(p, free_stack);
}
-void lsan_free_sized(void *p, uptr) { Deallocate(p); }
+void lsan_free_sized(void *p, uptr, const StackTrace *free_stack) {
+ Deallocate(p, free_stack);
+}
-void lsan_free_aligned_sized(void *p, uptr, uptr) { Deallocate(p); }
+void lsan_free_aligned_sized(void *p, uptr, uptr,
+ const StackTrace *free_stack) {
+ Deallocate(p, free_stack);
+}
+
+void NOINLINE lsan_free_with_stack(void *p, uptr pc, uptr bp) {
+ GET_STACK_TRACE_FREE_AT(pc, bp);
+ Deallocate(p, &stack);
+}
+
+void NOINLINE lsan_free_sized_with_stack(void *p, uptr, uptr pc, uptr bp) {
+ GET_STACK_TRACE_FREE_AT(pc, bp);
+ Deallocate(p, &stack);
+}
+
+void NOINLINE lsan_free_aligned_sized_with_stack(void *p, uptr, uptr, uptr pc,
+ uptr bp) {
+ GET_STACK_TRACE_FREE_AT(pc, bp);
+ Deallocate(p, &stack);
+}
void *lsan_realloc(void *p, uptr size, const StackTrace &stack) {
return SetErrnoOnNull(Reallocate(stack, p, size, 1));
@@ -350,6 +689,11 @@ IgnoreObjectResult IgnoreObject(const void *p) {
if (!chunk || p < chunk) return kIgnoreObjectInvalid;
ChunkMetadata *m = Metadata(chunk);
CHECK(m);
+ // Storing the tag is a read-modify-write over the chunk state byte (see
+ // ChunkMetadata), so racing this against a free() of the same chunk can undo
+ // that free's state change. Ignoring an object another thread is freeing is a
+ // use-after-free to begin with, and the check does not make it safe: it can
+ // miss or misreport the double free that follows.
if (m->allocated && (uptr)p < (uptr)chunk + m->requested_size) {
if (m->tag == kIgnored)
return kIgnoreObjectAlreadyIgnored;
diff --git a/compiler-rt/lib/lsan/lsan_allocator.h b/compiler-rt/lib/lsan/lsan_allocator.h
index 2d0ea0b46fe0e..08765054a98e7 100644
--- a/compiler-rt/lib/lsan/lsan_allocator.h
+++ b/compiler-rt/lib/lsan/lsan_allocator.h
@@ -23,7 +23,7 @@ namespace __lsan {
void *Allocate(const StackTrace &stack, uptr size, uptr alignment,
bool cleared);
-void Deallocate(void *p);
+void Deallocate(void *p, const StackTrace *free_stack);
void *Reallocate(const StackTrace &stack, void *p, uptr new_size,
uptr alignment);
uptr GetMallocUsableSize(const void *p);
@@ -36,20 +36,53 @@ void AllocatorThreadStart();
void AllocatorThreadFinish();
void InitializeAllocator();
+// Locks protecting the double-free side table, for the fork handlers.
+void LockDoubleFree();
+void UnlockDoubleFree();
+
const bool kAlwaysClearMemory = true;
+// Lifetime of a chunk, stored in ChunkMetadata::allocated.
+//
+// kChunkFreeing is a transient state used by double-free detection: the thread
+// that won the race to free the chunk has claimed it but has not published
+// ChunkMetadata::free_stack_id yet.
+enum ChunkState : u8 {
+ kChunkFree = 0,
+ kChunkAllocated = 1,
+ kChunkFreeing = 2,
+};
+
+// Appending free_stack_id to the natural 32-bit layout would have grown the
+// per-chunk metadata to 20 bytes, so requested_size is stored as a separate
+// word there and the leftover bitfield space is left unused.
+//
+// `allocated` is accessed atomically through the first byte of the struct, so
+// it shares its storage unit with the bitfields that follow it and a plain
+// store to one of those is a read-modify-write over it. Every such store made
+// while a chunk can be freed concurrently would therefore have to go through
+// the same atomic word. Today only IgnoreObject() does that (see the comment
+// there); the allocation path writes the bitfields while the chunk is still
+// kChunkFree, where a losing compare-exchange writes nothing, and the free
+// path never touches them.
struct ChunkMetadata {
- u8 allocated : 8; // Must be first.
+ u8 allocated : 8; // Must be first. Holds a ChunkState.
ChunkTag tag : 2;
#if SANITIZER_WORDSIZE == 64
uptr requested_size : 54;
#else
- uptr requested_size : 32;
uptr padding : 22;
+ u32 requested_size;
#endif
u32 stack_trace_id;
+ // Stack of the first free().
+ u32 free_stack_id;
};
+static_assert(sizeof(ChunkMetadata) == 4 * sizeof(u32),
+ "ChunkMetadata must stay 16 bytes: it is stored for every "
+ "allocator chunk.");
+
#if !SANITIZER_CAN_USE_ALLOCATOR64
template <typename AddressSpaceViewTy>
struct AP32 {
@@ -130,9 +163,26 @@ int lsan_posix_memalign(void **memptr, uptr alignment, uptr size,
void *lsan_aligned_alloc(uptr alignment, uptr size, const StackTrace &stack);
void *lsan_memalign(uptr alignment, uptr size, const StackTrace &stack);
void *lsan_malloc(uptr size, const StackTrace &stack);
-void lsan_free(void *p);
-void lsan_free_sized(void *p, uptr size);
-void lsan_free_aligned_sized(void *p, uptr alignment, uptr size);
+void lsan_free(void *p, const StackTrace *free_stack);
+void lsan_free_sized(void *p, uptr size, const StackTrace *free_stack);
+void lsan_free_aligned_sized(void *p, uptr alignment, uptr size,
+ const StackTrace *free_stack);
+
+// free() variants that capture the calling stack for double-free reporting.
+//
+// These are deliberately out of line. A BufferedStackTrace is about 2 KiB, and
+// in most of these interceptors the compiler reserves it in the frame on entry
+// even though it is only used on one branch. Capturing inline therefore grows
+// the frame of most free() and operator delete() calls, including when
+// detect_double_free is disabled.
+//
+// The caller passes its own pc and bp so that the captured stack starts at the
+// intercepted function rather than at the helper.
+void NOINLINE lsan_free_with_stack(void *p, uptr pc, uptr bp);
+void NOINLINE lsan_free_sized_with_stack(void *p, uptr size, uptr pc, uptr bp);
+void NOINLINE lsan_free_aligned_sized_with_stack(void *p, uptr alignment,
+ uptr size, uptr pc, uptr bp);
+
void *lsan_realloc(void *p, uptr size, const StackTrace &stack);
void *lsan_reallocarray(void *p, uptr nmemb, uptr size,
const StackTrace &stack);
diff --git a/compiler-rt/lib/lsan/lsan_flags.inc b/compiler-rt/lib/lsan/lsan_flags.inc
index e0b4aa4a3299e..43ea57297b375 100644
--- a/compiler-rt/lib/lsan/lsan_flags.inc
+++ b/compiler-rt/lib/lsan/lsan_flags.inc
@@ -23,6 +23,21 @@ LSAN_FLAG(
"Aggregate two objects into one leak if this many stack frames match. If "
"zero, the entire stack trace must match.")
LSAN_FLAG(int, max_leaks, 0, "The number of leaks reported.")
+LSAN_FLAG(bool, detect_double_free, false,
+ "Detect calls to free() on an allocation that was already freed. "
+ "Validating the pointer is a prerequisite for the check, so while "
+ "this is enabled an invalid free is reported as a diagnostic "
+ "instead of reaching the allocator and crashing. Stand-alone "
+ "LeakSanitizer only: under AddressSanitizer or "
+ "HWAddressSanitizer the allocator, and its own double-free "
+ "detection, come from that tool instead and this flag does "
+ "nothing.")
+LSAN_FLAG(int, double_free_max_entries, 65536,
+ "Maximum number of freed large allocations remembered for "
+ "double-free detection. The oldest entry is evicted once the limit "
+ "is reached. Zero means unlimited; values above 4194304 are capped. "
+ "Chunks served by the primary allocator are unaffected: they are "
+ "tracked in their own metadata.")
// Flags controlling the root set of reachable memory.
LSAN_FLAG(bool, use_globals, true,
diff --git a/compiler-rt/lib/lsan/lsan_interceptors.cpp b/compiler-rt/lib/lsan/lsan_interceptors.cpp
index 5340c6ffba607..dc1748db71f82 100644
--- a/compiler-rt/lib/lsan/lsan_interceptors.cpp
+++ b/compiler-rt/lib/lsan/lsan_interceptors.cpp
@@ -35,6 +35,41 @@
using namespace __lsan;
+// Dispatches to the stack-capturing variant only when double-free detection is
+// enabled. The capturing helpers are out of line so that the stack trace buffer
+// is not reserved in this frame when the feature is off; the pc and bp of this
+// frame are passed along so that the captured stack still starts here, at the
+// intercepted function, and not inside the helper.
+#define LSAN_FREE_BODY(p) \
+ do { \
+ if (flags()->detect_double_free) { \
+ GET_CURRENT_PC_BP; \
+ lsan_free_with_stack((p), pc, bp); \
+ } else { \
+ lsan_free((p), nullptr); \
+ } \
+ } while (0)
+
+#define LSAN_FREE_SIZED_BODY(p, size) \
+ do { \
+ if (flags()->detect_double_free) { \
+ GET_CURRENT_PC_BP; \
+ lsan_free_sized_with_stack((p), (size), pc, bp); \
+ } else { \
+ lsan_free_sized((p), (size), nullptr); \
+ } \
+ } while (0)
+
+#define LSAN_FREE_ALIGNED_SIZED_BODY(p, alignment, size) \
+ do { \
+ if (flags()->detect_double_free) { \
+ GET_CURRENT_PC_BP; \
+ lsan_free_aligned_sized_with_stack((p), (alignment), (size), pc, bp); \
+ } else { \
+ lsan_free_aligned_sized((p), (alignment), (size), nullptr); \
+ } \
+ } while (0)
+
extern "C" {
int pthread_attr_init(void *attr);
int pthread_attr_destroy(void *attr);
@@ -81,7 +116,7 @@ INTERCEPTOR(void, free, void *p) {
if (DlsymAlloc::PointerIsMine(p))
return DlsymAlloc::Free(p);
ENSURE_LSAN_INITED;
- lsan_free(p);
+ LSAN_FREE_BODY(p);
}
# if SANITIZER_INTERCEPT_FREE_SIZED
@@ -91,7 +126,7 @@ INTERCEPTOR(void, free_sized, void *p, uptr size) {
if (DlsymAlloc::PointerIsMine(p))
return DlsymAlloc::Free(p);
ENSURE_LSAN_INITED;
- lsan_free_sized(p, size);
+ LSAN_FREE_SIZED_BODY(p, size);
}
# define LSAN_MAYBE_INTERCEPT_FREE_SIZED INTERCEPT_FUNCTION(free_sized)
# else
@@ -105,7 +140,7 @@ INTERCEPTOR(void, free_aligned_sized, void *p, uptr alignment, uptr size) {
if (DlsymAlloc::PointerIsMine(p))
return DlsymAlloc::Free(p);
ENSURE_LSAN_INITED;
- lsan_free_aligned_sized(p, alignment, size);
+ LSAN_FREE_ALIGNED_SIZED_BODY(p, alignment, size);
}
# define LSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED \
INTERCEPT_FUNCTION(free_aligned_sized)
@@ -263,9 +298,9 @@ INTERCEPTOR(int, mprobe, void *ptr) {
if (!nothrow && UNLIKELY(!res)) ReportOutOfMemory(size, &stack);\
return res;
-#define OPERATOR_DELETE_BODY\
- ENSURE_LSAN_INITED;\
- lsan_free(ptr);
+#define OPERATOR_DELETE_BODY \
+ ENSURE_LSAN_INITED; \
+ LSAN_FREE_BODY(ptr);
// On OS X it's not enough to just provide our own 'operator new' and
// 'operator delete' implementations, because they're going to be in the runtime
diff --git a/compiler-rt/lib/lsan/lsan_mac.cpp b/compiler-rt/lib/lsan/lsan_mac.cpp
index 990954a8b6879..c32436402a6a8 100644
--- a/compiler-rt/lib/lsan/lsan_mac.cpp
+++ b/compiler-rt/lib/lsan/lsan_mac.cpp
@@ -84,7 +84,7 @@ extern "C" void lsan_dispatch_call_block_and_release(void *block) {
lsan_register_worker_thread(context->parent_tid);
// Call the original dispatcher for the block.
context->func(context->block);
- lsan_free(context);
+ lsan_free(context, nullptr);
}
} // namespace __lsan
diff --git a/compiler-rt/lib/lsan/lsan_malloc_mac.cpp b/compiler-rt/lib/lsan/lsan_malloc_mac.cpp
index 8a16c053da238..ceff82ed66033 100644
--- a/compiler-rt/lib/lsan/lsan_malloc_mac.cpp
+++ b/compiler-rt/lib/lsan/lsan_malloc_mac.cpp
@@ -42,11 +42,13 @@ using namespace __lsan;
#define COMMON_MALLOC_VALLOC(size) \
GET_STACK_TRACE_MALLOC; \
void *p = lsan_valloc(size, stack)
-#define COMMON_MALLOC_FREE(ptr) \
- lsan_free(ptr)
-# define COMMON_MALLOC_FREE_SIZED(ptr, size) lsan_free_sized(ptr, size)
+// Double-free detection is not supported on this platform, so no free stack is
+// captured here; see InitializeFlags() in lsan.cpp.
+# define COMMON_MALLOC_FREE(ptr) lsan_free(ptr, nullptr)
+# define COMMON_MALLOC_FREE_SIZED(ptr, size) \
+ lsan_free_sized(ptr, size, nullptr)
# define COMMON_MALLOC_FREE_ALIGNED_SIZED(ptr, alignment, size) \
- lsan_free_aligned_sized(ptr, alignment, size)
+ lsan_free_aligned_sized(ptr, alignment, size, nullptr)
# define COMMON_MALLOC_SIZE(ptr) uptr size = lsan_mz_size(ptr)
# define COMMON_MALLOC_FILL_STATS(zone, stats)
# define COMMON_MALLOC_REPORT_UNKNOWN_REALLOC(ptr, zone_ptr, zone_name) \
diff --git a/compiler-rt/lib/lsan/lsan_posix.cpp b/compiler-rt/lib/lsan/lsan_posix.cpp
index ae1590b9d6fc1..0065e56a211e9 100644
--- a/compiler-rt/lib/lsan/lsan_posix.cpp
+++ b/compiler-rt/lib/lsan/lsan_posix.cpp
@@ -100,6 +100,7 @@ static void BeforeFork() {
VReport(2, "BeforeFork tid: %llu\n", GetTid());
LockGlobal();
LockThreads();
+ LockDoubleFree();
LockAllocator();
StackDepotLockBeforeFork();
}
@@ -107,6 +108,7 @@ static void BeforeFork() {
static void AfterFork(bool fork_child) {
StackDepotUnlockAfterFork(fork_child);
UnlockAllocator();
+ UnlockDoubleFree();
UnlockThreads();
UnlockGlobal();
VReport(2, "AfterFork tid: %llu\n", GetTid());
diff --git a/compiler-rt/test/lsan/TestCases/Linux/double_free_fork.cpp b/compiler-rt/test/lsan/TestCases/Linux/double_free_fork.cpp
new file mode 100644
index 0000000000000..ebbfe1a61bd4b
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/Linux/double_free_fork.cpp
@@ -0,0 +1,58 @@
+// The double-free side table must survive fork(): the atfork handlers take its
+// mutex in the LSan lock order, so a child that keeps allocating cannot inherit
+// a locked table, and detection still works on both sides. The mutex is held
+// across the unmap of a large chunk, so a churning thread has a wide window in
+// which the fork can land on it.
+//
+// RUN: %clangxx_lsan -O0 %s -pthread -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 %run %t 2>&1 | FileCheck %s
+// REQUIRES: lsan-standalone
+
+#include <cstdio>
+#include <cstdlib>
+#include <pthread.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+static volatile bool g_stop;
+
+// Keeps the allocator and the side table busy across the fork point.
+static void *Churn(void *) {
+ while (!g_stop) {
+ void *small = malloc(32);
+ void *large = malloc(1 << 20);
+ free(small);
+ free(large);
+ }
+ return nullptr;
+}
+
+int main() {
+ pthread_t t;
+ pthread_create(&t, nullptr, Churn, nullptr);
+
+ for (int i = 0; i < 8; ++i) {
+ pid_t pid = fork();
+ if (pid == 0) {
+ // The child inherits the side table. It must be usable, not deadlocked.
+ for (int j = 0; j < 64; ++j) {
+ void *p = malloc(1 << 20);
+ free(p);
+ }
+ _exit(0);
+ }
+ int status = 0;
+ waitpid(pid, &status, 0);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ fprintf(stderr, "child failed\n");
+ return 1;
+ }
+ }
+
+ g_stop = true;
+ pthread_join(t, nullptr);
+ fprintf(stderr, "fork test done\n");
+ return 0;
+}
+
+// CHECK: fork test done
diff --git a/compiler-rt/test/lsan/TestCases/double_free.cpp b/compiler-rt/test/lsan/TestCases/double_free.cpp
new file mode 100644
index 0000000000000..0c2386b71fa08
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/double_free.cpp
@@ -0,0 +1,38 @@
+// Basic double-free detection for a chunk served by the primary allocator.
+//
+// The double free is undefined behavior, so build at -O0 and route the frees
+// through an opaque function: an optimizing compiler is otherwise allowed to
+// drop the second free() before LeakSanitizer can observe it.
+//
+// RUN: %clangxx_lsan -O0 %s -o %t
+// RUN: %env_lsan_opts=detect_leaks=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OFF
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OFF
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t 2>&1 | FileCheck %s
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1:double_free_max_entries=0 not %run %t 2>&1 | FileCheck %s
+// REQUIRES: lsan-standalone
+// UNSUPPORTED: darwin, target={{.*netbsd.*}}
+
+#include <cstdio>
+#include <cstdlib>
+
+__attribute__((noinline)) static void Free(void *p) { free(p); }
+
+int main() {
+ void *p = malloc(16);
+ Free(p);
+ Free(p);
+ puts("completed");
+ return 0;
+}
+
+// The reported stack starts at the intercepted free(), not inside the runtime
+// helper that captures it.
+// CHECK: ERROR: LeakSanitizer: attempting double-free on
+// CHECK: The second free occurred here:
+// CHECK-NEXT: #0 {{.*}}free
+// CHECK: The first free occurred here:
+// CHECK: The memory was allocated here:
+// CHECK: SUMMARY: LeakSanitizer: double-free
+
+// CHECK-OFF: completed
+// CHECK-OFF-NOT: double-free
diff --git a/compiler-rt/test/lsan/TestCases/double_free_eviction.cpp b/compiler-rt/test/lsan/TestCases/double_free_eviction.cpp
new file mode 100644
index 0000000000000..763f74a9a1fd4
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/double_free_eviction.cpp
@@ -0,0 +1,51 @@
+// The side table for large allocations is bounded by double_free_max_entries
+// and evicts in FIFO order, so a bounded table always remembers the most recent
+// frees. With room for a single entry, freeing the newest address again is
+// still reported as a double free, while the evicted address is no longer
+// recognized as one and falls back to the invalid-free diagnostic.
+//
+// RUN: %clangxx_lsan -O0 %s -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1:double_free_max_entries=1 not %run %t remembered 2>&1 | FileCheck %s --check-prefix=CHECK-REMEMBERED
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1:double_free_max_entries=1 not %run %t evicted 2>&1 | FileCheck %s --check-prefix=CHECK-EVICTED
+// REQUIRES: lsan-standalone
+// UNSUPPORTED: darwin, target={{.*netbsd.*}}
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+__attribute__((noinline)) static void Free(void *p) { free(p); }
+
+int main(int argc, char **argv) {
+ if (argc != 2)
+ return 1;
+
+ void *old_chunk = malloc(2 << 20);
+ void *new_chunk = malloc(3 << 20);
+ Free(old_chunk);
+ // Only one record fits, so this evicts the one for old_chunk.
+ Free(new_chunk);
+
+ if (!strcmp(argv[1], "remembered")) {
+ fprintf(stderr, "freeing remembered chunk\n");
+ Free(new_chunk);
+ } else {
+ fprintf(stderr, "freeing evicted chunk\n");
+ Free(old_chunk);
+ }
+
+ fprintf(stderr, "not reached\n");
+ return 0;
+}
+
+// CHECK-REMEMBERED: freeing remembered chunk
+// CHECK-REMEMBERED: ERROR: LeakSanitizer: attempting double-free on
+// CHECK-REMEMBERED: SUMMARY: LeakSanitizer: double-free
+// CHECK-REMEMBERED-NOT: not reached
+
+// The evicted address is still diagnosed instead of being allowed to fault
+// inside the runtime, just no longer as a double free.
+// CHECK-EVICTED: freeing evicted chunk
+// CHECK-EVICTED: ERROR: LeakSanitizer: attempting free on address which was not
+// CHECK-EVICTED: SUMMARY: LeakSanitizer: bad-free
+// CHECK-EVICTED-NOT: not reached
diff --git a/compiler-rt/test/lsan/TestCases/double_free_hooks.cpp b/compiler-rt/test/lsan/TestCases/double_free_hooks.cpp
new file mode 100644
index 0000000000000..e1f15d62cb1da
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/double_free_hooks.cpp
@@ -0,0 +1,43 @@
+// Enabling double-free detection must not change what a free hook observes.
+// The hook has to run before the chunk state changes, so the pointer is still
+// reported as owned, exactly as when the feature is disabled.
+//
+// RUN: %clangxx_lsan -O0 %s -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=0 %run %t 2>&1 | FileCheck %s
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 %run %t 2>&1 | FileCheck %s
+// REQUIRES: lsan-standalone
+// UNSUPPORTED: darwin, target={{.*netbsd.*}}
+
+#include <sanitizer/allocator_interface.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+
+static void *g_expected;
+static int g_free_hooks;
+
+static void OnMalloc(const volatile void *, size_t) {}
+
+static void OnFree(const volatile void *ptr) {
+ if ((void *)ptr != g_expected)
+ return;
+ ++g_free_hooks;
+ fprintf(stderr, "free hook: owned=%d size=%zu\n",
+ __sanitizer_get_ownership((void *)ptr),
+ __sanitizer_get_allocated_size((void *)ptr));
+}
+
+int main() {
+ assert(__sanitizer_install_malloc_and_free_hooks(OnMalloc, OnFree));
+
+ g_expected = malloc(64);
+ free(g_expected);
+ assert(g_free_hooks == 1);
+
+ fprintf(stderr, "done\n");
+ return 0;
+}
+
+// CHECK: free hook: owned=1 size=64
+// CHECK: done
diff --git a/compiler-rt/test/lsan/TestCases/double_free_large.cpp b/compiler-rt/test/lsan/TestCases/double_free_large.cpp
new file mode 100644
index 0000000000000..cb769124c24a2
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/double_free_large.cpp
@@ -0,0 +1,26 @@
+// Double-free detection for a chunk served by the secondary allocator. Such a
+// chunk is unmapped as soon as it is freed, so its metadata is gone and the
+// second free can only be recognized from the side table.
+//
+// RUN: %clangxx_lsan -O0 %s -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t 2>&1 | FileCheck %s
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1:double_free_max_entries=0 not %run %t 2>&1 | FileCheck %s
+// REQUIRES: lsan-standalone
+// UNSUPPORTED: darwin, target={{.*netbsd.*}}
+
+#include <cstdlib>
+
+__attribute__((noinline)) static void Free(void *p) { free(p); }
+
+int main() {
+ void *p = malloc(2 << 20);
+ Free(p);
+ Free(p);
+ return 0;
+}
+
+// CHECK: ERROR: LeakSanitizer: attempting double-free on
+// CHECK: The second free occurred here:
+// CHECK: The first free occurred here:
+// CHECK: The memory was allocated here:
+// CHECK: SUMMARY: LeakSanitizer: double-free
diff --git a/compiler-rt/test/lsan/TestCases/double_free_max_entries.cpp b/compiler-rt/test/lsan/TestCases/double_free_max_entries.cpp
new file mode 100644
index 0000000000000..eff966939ffdc
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/double_free_max_entries.cpp
@@ -0,0 +1,26 @@
+// double_free_max_entries is validated at startup. A negative value falls back
+// to the unlimited table and an oversized one is capped, both with a warning,
+// rather than reaching mmap as a multi-gigabyte request. Detection keeps
+// working either way.
+//
+// RUN: %clangxx_lsan -O0 %s -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1:double_free_max_entries=-1 not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK,CHECK-NEGATIVE
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1:double_free_max_entries=2000000000 not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK,CHECK-HUGE
+// REQUIRES: lsan-standalone
+// UNSUPPORTED: darwin, target={{.*netbsd.*}}
+
+#include <cstdlib>
+
+__attribute__((noinline)) static void Free(void *p) { free(p); }
+
+int main() {
+ void *p = malloc(2 << 20);
+ Free(p);
+ Free(p);
+ return 0;
+}
+
+// CHECK-NEGATIVE: WARNING: LeakSanitizer: double_free_max_entries=-1 is negative
+// CHECK-HUGE: WARNING: LeakSanitizer: double_free_max_entries=2000000000 is too large, capping it at 4194304
+// CHECK: ERROR: LeakSanitizer: attempting double-free on
+// CHECK: SUMMARY: LeakSanitizer: double-free
diff --git a/compiler-rt/test/lsan/TestCases/double_free_realloc.cpp b/compiler-rt/test/lsan/TestCases/double_free_realloc.cpp
new file mode 100644
index 0000000000000..81865fe01f2e1
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/double_free_realloc.cpp
@@ -0,0 +1,81 @@
+// realloc() validates its pointer the same way free() does, and for the same
+// reason: a secondary chunk is unmapped as soon as it is freed, so the old
+// chunk's metadata must not be read before the pointer is known to be live.
+// Reallocating an already freed or otherwise invalid pointer is therefore
+// diagnosed, and a pointer that realloc() replaced cannot be freed again.
+//
+// RUN: %clangxx_lsan -O0 %s -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t realloc-freed-small 2>&1 | FileCheck %s --check-prefix=CHECK-DOUBLE
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t realloc-freed-large 2>&1 | FileCheck %s --check-prefix=CHECK-DOUBLE
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t free-replaced 2>&1 | FileCheck %s --check-prefix=CHECK-DOUBLE
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t realloc-interior 2>&1 | FileCheck %s --check-prefix=CHECK-BAD
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 %run %t grow-large 2>&1 | FileCheck %s --check-prefix=CHECK-OK
+// REQUIRES: lsan-standalone
+// UNSUPPORTED: darwin, target={{.*netbsd.*}}
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+__attribute__((noinline)) static void Free(void *p) { free(p); }
+__attribute__((noinline)) static void *Realloc(void *p, size_t n) {
+ return realloc(p, n);
+}
+
+int main(int argc, char **argv) {
+ if (argc != 2)
+ return 1;
+
+ if (!strcmp(argv[1], "realloc-freed-small")) {
+ void *p = malloc(16);
+ Free(p);
+ // Growing an already freed pointer is a double free.
+ Realloc(p, 32);
+ } else if (!strcmp(argv[1], "realloc-freed-large")) {
+ // Same for a chunk from the secondary allocator, whose metadata is gone by
+ // the time realloc() runs.
+ void *p = malloc(2 << 20);
+ Free(p);
+ Realloc(p, 3 << 20);
+ } else if (!strcmp(argv[1], "free-replaced")) {
+ void *p = malloc(16);
+ void *q = Realloc(p, 4096);
+ if (!q)
+ return 1;
+ // p was released by the successful realloc.
+ Free(p);
+ } else if (!strcmp(argv[1], "grow-large")) {
+ // A successful realloc() of a *live* secondary chunk. The old size is read
+ // under the side-table lock, which is a path a freed large pointer never
+ // reaches, and nothing may be reported.
+ const size_t kOld = 2 << 20;
+ char *p = (char *)malloc(kOld);
+ memset(p, 'a', kOld);
+ char *q = (char *)Realloc(p, 3 << 20);
+ if (!q || q[0] != 'a' || q[kOld - 1] != 'a')
+ return 1;
+ Free(q);
+ fprintf(stderr, "completed\n");
+ return 0;
+ } else {
+ // An interior pointer was never handed out, so this is an invalid free
+ // rather than a double free.
+ void *p = (char *)malloc(64) + 16;
+ Realloc(p, 128);
+ }
+
+ fprintf(stderr, "not reached\n");
+ return 0;
+}
+
+// CHECK-DOUBLE: ERROR: LeakSanitizer: attempting double-free on
+// CHECK-DOUBLE: The memory was allocated here:
+// CHECK-DOUBLE: SUMMARY: LeakSanitizer: double-free
+// CHECK-DOUBLE-NOT: not reached
+
+// CHECK-BAD: ERROR: LeakSanitizer: attempting free on address which was not
+// CHECK-BAD: SUMMARY: LeakSanitizer: bad-free
+// CHECK-BAD-NOT: not reached
+
+// CHECK-OK: completed
+// CHECK-OK-NOT: LeakSanitizer:
diff --git a/compiler-rt/test/lsan/TestCases/double_free_sized.cpp b/compiler-rt/test/lsan/TestCases/double_free_sized.cpp
new file mode 100644
index 0000000000000..83e7f1d1740e7
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/double_free_sized.cpp
@@ -0,0 +1,59 @@
+// free_sized() and free_aligned_sized() go through the same double-free path as
+// free(), including the out-of-line stack capture, so they need the same
+// coverage: a matching sized free must be accepted, and a second one must be
+// reported.
+//
+// RUN: %clangxx_lsan -O0 %s -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 %run %t ok 2>&1 | FileCheck %s --check-prefix=CHECK-OK
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t sized 2>&1 | FileCheck %s
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t aligned 2>&1 | FileCheck %s
+// REQUIRES: lsan-standalone
+// UNSUPPORTED: darwin, target={{.*netbsd.*}}
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+extern "C" void free_sized(void *p, size_t size);
+extern "C" void free_aligned_sized(void *p, size_t alignment, size_t size);
+
+__attribute__((noinline)) static void FreeSized(void *p, size_t size) {
+ free_sized(p, size);
+}
+
+__attribute__((noinline)) static void
+FreeAlignedSized(void *p, size_t alignment, size_t size) {
+ free_aligned_sized(p, alignment, size);
+}
+
+int main(int argc, char **argv) {
+ if (argc != 2)
+ return 1;
+
+ if (!strcmp(argv[1], "ok")) {
+ FreeSized(malloc(64), 64);
+ FreeAlignedSized(aligned_alloc(64, 128), 64, 128);
+ fprintf(stderr, "completed\n");
+ return 0;
+ }
+
+ if (!strcmp(argv[1], "sized")) {
+ void *p = malloc(64);
+ FreeSized(p, 64);
+ FreeSized(p, 64);
+ } else {
+ void *p = aligned_alloc(64, 128);
+ FreeAlignedSized(p, 64, 128);
+ FreeAlignedSized(p, 64, 128);
+ }
+
+ fprintf(stderr, "not reached\n");
+ return 0;
+}
+
+// CHECK-OK: completed
+// CHECK-OK-NOT: LeakSanitizer:
+
+// CHECK: ERROR: LeakSanitizer: attempting double-free on
+// CHECK: SUMMARY: LeakSanitizer: double-free
+// CHECK-NOT: not reached
diff --git a/compiler-rt/test/lsan/TestCases/double_free_threaded.cpp b/compiler-rt/test/lsan/TestCases/double_free_threaded.cpp
new file mode 100644
index 0000000000000..282d0a35f4ffb
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/double_free_threaded.cpp
@@ -0,0 +1,53 @@
+// Concurrent frees of the same pointer must be reported exactly once: the
+// chunk state machine lets a single thread claim the chunk, and every other
+// thread observes the double free.
+//
+// The "large" case covers the secondary allocator, where releasing a chunk
+// unmaps it together with its metadata: a losing thread must never end up
+// reading, or compare-exchanging on, memory the winning thread has already
+// unmapped. That window is a handful of instructions wide, so this is a smoke
+// test rather than a reliable reproducer; a regression surfaces as a SEGV
+// report from inside the runtime instead of the double-free report below.
+//
+// RUN: %clangxx_lsan -O0 %s -pthread -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t small 2>&1 | FileCheck %s
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t large 2>&1 | FileCheck %s
+// REQUIRES: lsan-standalone
+// UNSUPPORTED: darwin, target={{.*netbsd.*}}
+
+#include <pthread.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+static const int kThreads = 16;
+static void *g_chunk;
+static pthread_barrier_t g_barrier;
+
+static void *Racer(void *) {
+ pthread_barrier_wait(&g_barrier);
+ free(g_chunk);
+ return nullptr;
+}
+
+int main(int argc, char **argv) {
+ if (argc != 2)
+ return 1;
+ g_chunk = malloc(!strcmp(argv[1], "large") ? (2 << 20) : 64);
+ pthread_barrier_init(&g_barrier, nullptr, kThreads);
+
+ pthread_t threads[kThreads];
+ for (int i = 0; i < kThreads; ++i)
+ pthread_create(&threads[i], nullptr, Racer, nullptr);
+ for (int i = 0; i < kThreads; ++i)
+ pthread_join(threads[i], nullptr);
+
+ fprintf(stderr, "not reached\n");
+ return 0;
+}
+
+// CHECK: ERROR: LeakSanitizer: attempting double-free on
+// CHECK: The second free occurred here:
+// CHECK: SUMMARY: LeakSanitizer: double-free
+// CHECK-NOT: not reached
diff --git a/compiler-rt/test/lsan/TestCases/invalid_free.cpp b/compiler-rt/test/lsan/TestCases/invalid_free.cpp
new file mode 100644
index 0000000000000..a102d6f83e997
--- /dev/null
+++ b/compiler-rt/test/lsan/TestCases/invalid_free.cpp
@@ -0,0 +1,40 @@
+// An invalid free must produce a diagnostic instead of faulting inside the
+// runtime while it dereferences metadata for an address the allocator never
+// handed out.
+//
+// RUN: %clangxx_lsan -O0 %s -o %t
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t heap 2>&1 | FileCheck %s
+// RUN: %env_lsan_opts=detect_leaks=0:detect_double_free=1 not %run %t global 2>&1 | FileCheck %s
+// REQUIRES: lsan-standalone
+// UNSUPPORTED: darwin, target={{.*netbsd.*}}
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+static long g_global[8];
+
+__attribute__((noinline)) static void Free(void *p) { free(p); }
+
+int main(int argc, char **argv) {
+ if (argc != 2)
+ return 1;
+
+ void *p;
+ if (!strcmp(argv[1], "heap")) {
+ // Interior pointer into a live allocation.
+ p = (char *)malloc(64) + 16;
+ } else {
+ p = g_global;
+ }
+
+ fprintf(stderr, "freeing invalid pointer\n");
+ Free(p);
+ fprintf(stderr, "not reached\n");
+ return 0;
+}
+
+// CHECK: freeing invalid pointer
+// CHECK: ERROR: LeakSanitizer: attempting free on address which was not
+// CHECK: SUMMARY: LeakSanitizer: bad-free
+// CHECK-NOT: not reached
More information about the cfe-commits
mailing list