[clang] [compiler-rt] [llvm] [TySan] Let TypeSanitizer know about conservative path TBAA data (PR #227782)
Matthew Nagy via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 1 03:18:18 PDT 2026
https://github.com/gbMattN updated https://github.com/llvm/llvm-project/pull/227782
>From 9e91aaca49966ffe7a8665c562e8356cf4fae9a7 Mon Sep 17 00:00:00 2001
From: gbMattN <matthew.nagy at sony.com>
Date: Wed, 30 Sep 2026 17:07:22 +0100
Subject: [PATCH 1/3] [TySan] Let TypeSanitizer know about conservative path
TBAA data
---
clang/lib/CodeGen/CodeGenTBAA.cpp | 3 ++
compiler-rt/lib/tysan/tysan.cpp | 22 +++++++++++++--
.../tysan/no-false-positive-issue208646.cpp | 11 ++++++++
.../tysan/no-false-positive-issue208647.cpp | 10 +++++++
.../tysan/no-false-positive-issue208655.cpp | 15 ++++++++++
.../tysan/no-false-positive-issue210643.cpp | 11 ++++++++
.../Instrumentation/TypeSanitizer.cpp | 28 ++++++++++++++++++-
7 files changed, 96 insertions(+), 4 deletions(-)
create mode 100644 compiler-rt/test/tysan/no-false-positive-issue208646.cpp
create mode 100644 compiler-rt/test/tysan/no-false-positive-issue208647.cpp
create mode 100644 compiler-rt/test/tysan/no-false-positive-issue208655.cpp
create mode 100644 compiler-rt/test/tysan/no-false-positive-issue210643.cpp
diff --git a/clang/lib/CodeGen/CodeGenTBAA.cpp b/clang/lib/CodeGen/CodeGenTBAA.cpp
index 1854df7c7c0f1..acdf6abc59b61 100644
--- a/clang/lib/CodeGen/CodeGenTBAA.cpp
+++ b/clang/lib/CodeGen/CodeGenTBAA.cpp
@@ -367,6 +367,9 @@ llvm::MDNode *CodeGenTBAA::getTypeInfoHelper(const Type *Ty) {
}
// For now, handle any other kind of type conservatively.
+ if(Features.Sanitize.has(SanitizerKind::Type)){
+ return createScalarTypeNode("TysanConservativeTBAA", getChar(), 1);
+ }
return getChar();
}
diff --git a/compiler-rt/lib/tysan/tysan.cpp b/compiler-rt/lib/tysan/tysan.cpp
index 8b3b60dc4cf83..cc638cef5db83 100644
--- a/compiler-rt/lib/tysan/tysan.cpp
+++ b/compiler-rt/lib/tysan/tysan.cpp
@@ -128,12 +128,27 @@ static tysan_type_descriptor *getRootTD(tysan_type_descriptor *TD) {
return RootTD;
}
+// Currently, Clang's TBAA system does not correctly describe every possible
+// type. For unhandled types, it makes the most conservative choice, emitting
+// omnipotent char. TySan needs to handle this seperately to a real omnipotent
+// char otherwise the user may get false positives. When compiling with TySan
+// enabled, clang will emit a special TBAA type to show that the conservative
+// path has been taken. When the transformation pass finds this TBAA, it will
+// set this global variable. This then allows quick comparison of TDs at
+// runtime.
+static tysan_type_descriptor *__tysan_conservative_tbaa_descriptor = nullptr;
+extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
+__tysan_set_conservative_tbaa_descriptor(
+ tysan_type_descriptor *conservativeTBAATD) {
+ __tysan_conservative_tbaa_descriptor = conservativeTBAATD;
+}
+
// Walk up TDA to see if it reaches TDB.
static bool walkAliasTree(tysan_type_descriptor *TDA,
tysan_type_descriptor *TDB, uptr OffsetA,
uptr OffsetB) {
do {
- if (TDA == TDB)
+ if (TDA == TDB || TDA == __tysan_conservative_tbaa_descriptor)
return OffsetA == OffsetB;
if (TDA->Tag == TYSAN_STRUCT_TD) {
@@ -182,7 +197,6 @@ static bool isAliasingLegalUp(tysan_type_descriptor *TDA,
OffsetA = TDA->Member.Offset;
TDA = TDA->Member.Base;
}
-
return walkAliasTree(TDA, TDB, OffsetA, OffsetB);
}
@@ -213,7 +227,9 @@ static bool isAliasingLegalWithOffset(tysan_type_descriptor *TDA,
static bool isAliasingLegal(tysan_type_descriptor *TDA,
tysan_type_descriptor *TDB, uptr OffsetB = 0) {
- if (TDA == TDB || !TDB || !TDA)
+ if (TDA == TDB || !TDB || !TDA ||
+ TDA == __tysan_conservative_tbaa_descriptor ||
+ TDB == __tysan_conservative_tbaa_descriptor)
return true;
// Aliasing is legal is the two types have different root nodes.
diff --git a/compiler-rt/test/tysan/no-false-positive-issue208646.cpp b/compiler-rt/test/tysan/no-false-positive-issue208646.cpp
new file mode 100644
index 0000000000000..236b4a86d0a2b
--- /dev/null
+++ b/compiler-rt/test/tysan/no-false-positive-issue208646.cpp
@@ -0,0 +1,11 @@
+// RUN: %clangxx_tysan -O0 %s -o %t && %run %t
+
+#include <string>
+#include <optional>
+
+static std::optional<std::string> optional_var = std::nullopt;
+
+int main() {
+ optional_var = "this is a random long string (short one does not reproduce)";
+ return 0;
+}
diff --git a/compiler-rt/test/tysan/no-false-positive-issue208647.cpp b/compiler-rt/test/tysan/no-false-positive-issue208647.cpp
new file mode 100644
index 0000000000000..ef8b004496a16
--- /dev/null
+++ b/compiler-rt/test/tysan/no-false-positive-issue208647.cpp
@@ -0,0 +1,10 @@
+// RUN: %clangxx_tysan -O0 %s -o %t && %run %t
+
+#include <variant>
+
+int main() {
+ std::variant<int, double> v;
+ v = 1;
+ v = 3.5;
+ return 0;
+}
diff --git a/compiler-rt/test/tysan/no-false-positive-issue208655.cpp b/compiler-rt/test/tysan/no-false-positive-issue208655.cpp
new file mode 100644
index 0000000000000..7518b244513cd
--- /dev/null
+++ b/compiler-rt/test/tysan/no-false-positive-issue208655.cpp
@@ -0,0 +1,15 @@
+// RUN: %clangxx_tysan -O0 %s -o %t && %run %t
+
+#include <vector>
+
+struct Registry {
+ std::vector<int> arr;
+ char temp_byte;
+ bool bool_var = false;
+};
+
+int main() {
+ static Registry r;
+ r.arr.push_back(0);
+ r.bool_var = true;
+}
diff --git a/compiler-rt/test/tysan/no-false-positive-issue210643.cpp b/compiler-rt/test/tysan/no-false-positive-issue210643.cpp
new file mode 100644
index 0000000000000..d667f75646cfd
--- /dev/null
+++ b/compiler-rt/test/tysan/no-false-positive-issue210643.cpp
@@ -0,0 +1,11 @@
+// RUN: %clangxx_tysan -O0 %s -o %t && %run %t
+
+struct A {
+ int elems[3];
+};
+
+A a;
+
+int main() {
+ a.elems[0] = 1;
+}
diff --git a/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
index cea6e9e316c1d..0f66904b0e8c2 100644
--- a/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
@@ -80,6 +80,7 @@ struct TypeSanitizer {
TypeSanitizer(Module &M);
bool sanitizeFunction(Function &F, const TargetLibraryInfo &TLI);
void instrumentGlobals(Module &M);
+ void setConservativeTBAA(Module &M);
private:
typedef SmallDenseMap<const MDNode *, GlobalVariable *, 8>
@@ -127,6 +128,9 @@ struct TypeSanitizer {
FunctionCallee TysanInstrumentWithShadowUpdate;
FunctionCallee TysanSetShadowType;
+ FunctionCallee TysanSetConservativeTBAADescriptor;
+ GlobalVariable *ConservativeTBAADescriptor;
+
/// Callback to set types for gloabls.
Function *TysanGlobalsSetTypeFunction;
};
@@ -134,7 +138,8 @@ struct TypeSanitizer {
TypeSanitizer::TypeSanitizer(Module &M)
: TargetTriple(M.getTargetTriple()),
- AnonNameRegex("^_ZTS.*N[1-9][0-9]*_GLOBAL__N") {
+ AnonNameRegex("^_ZTS.*N[1-9][0-9]*_GLOBAL__N"),
+ ConservativeTBAADescriptor(nullptr) {
const DataLayout &DL = M.getDataLayout();
IntptrTy = DL.getIntPtrType(M.getContext());
PtrShift = countr_zero(IntptrTy->getPrimitiveSizeInBits() / 8);
@@ -186,6 +191,12 @@ void TypeSanitizer::initializeCallbacks(Module &M) {
IRB.getPtrTy(), // Pointer to the new type descriptor
U64Ty // Size of data we access in bytes
);
+
+ TysanSetConservativeTBAADescriptor = M.getOrInsertFunction(
+ "__tysan_set_conservative_tbaa_descriptor", Attr, IRB.getVoidTy(),
+ IRB.getPtrTy() // Pointer to the type descriptor that describes the TBAA
+ // clang generates under the conservative TBAA path
+ );
}
void TypeSanitizer::instrumentGlobals(Module &M) {
@@ -234,6 +245,16 @@ void TypeSanitizer::instrumentGlobals(Module &M) {
}
}
+void TypeSanitizer::setConservativeTBAA(Module &M) {
+ if (ConservativeTBAADescriptor) {
+ IRBuilder<> IRB(cast<Function>(TysanCtorFunction.getCallee())
+ ->getEntryBlock()
+ .getTerminator());
+ IRB.CreateCall(TysanSetConservativeTBAADescriptor,
+ {ConservativeTBAADescriptor});
+ }
+}
+
static const char LUT[] = "0123456789abcdef";
static std::string encodeName(StringRef Name) {
@@ -391,6 +412,9 @@ bool TypeSanitizer::generateBaseTypeDescriptor(
TD, EncodedName);
M.insertGlobalVariable(TDGV);
+ if (Name == "TysanConservativeTBAA") {
+ ConservativeTBAADescriptor = TDGV;
+ }
if (ShouldBeComdat) {
if (TargetTriple.isOSBinFormatELF()) {
Comdat *TDComdat = M.getOrInsertComdat(EncodedName);
@@ -973,5 +997,7 @@ PreservedAnalyses TypeSanitizerPass::run(Module &M,
}
}
+ TySan.setConservativeTBAA(M);
+
return PreservedAnalyses::none();
}
>From 65e40b0402ea00242eafc96de7aa933f599c5c7f Mon Sep 17 00:00:00 2001
From: gbMattN <matthew.nagy at sony.com>
Date: Wed, 30 Sep 2026 17:52:42 +0100
Subject: [PATCH 2/3] Format repro's copied from issues
---
.../test/tysan/no-false-positive-issue208646.cpp | 4 ++--
.../test/tysan/no-false-positive-issue208655.cpp | 12 ++++++------
.../test/tysan/no-false-positive-issue210643.cpp | 6 ++----
3 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/compiler-rt/test/tysan/no-false-positive-issue208646.cpp b/compiler-rt/test/tysan/no-false-positive-issue208646.cpp
index 236b4a86d0a2b..fc8a2da6e8b11 100644
--- a/compiler-rt/test/tysan/no-false-positive-issue208646.cpp
+++ b/compiler-rt/test/tysan/no-false-positive-issue208646.cpp
@@ -1,11 +1,11 @@
// RUN: %clangxx_tysan -O0 %s -o %t && %run %t
-#include <string>
#include <optional>
+#include <string>
static std::optional<std::string> optional_var = std::nullopt;
-int main() {
+int main() {
optional_var = "this is a random long string (short one does not reproduce)";
return 0;
}
diff --git a/compiler-rt/test/tysan/no-false-positive-issue208655.cpp b/compiler-rt/test/tysan/no-false-positive-issue208655.cpp
index 7518b244513cd..cba71c5b5be49 100644
--- a/compiler-rt/test/tysan/no-false-positive-issue208655.cpp
+++ b/compiler-rt/test/tysan/no-false-positive-issue208655.cpp
@@ -3,13 +3,13 @@
#include <vector>
struct Registry {
- std::vector<int> arr;
- char temp_byte;
- bool bool_var = false;
+ std::vector<int> arr;
+ char temp_byte;
+ bool bool_var = false;
};
int main() {
- static Registry r;
- r.arr.push_back(0);
- r.bool_var = true;
+ static Registry r;
+ r.arr.push_back(0);
+ r.bool_var = true;
}
diff --git a/compiler-rt/test/tysan/no-false-positive-issue210643.cpp b/compiler-rt/test/tysan/no-false-positive-issue210643.cpp
index d667f75646cfd..4a74abd43d02d 100644
--- a/compiler-rt/test/tysan/no-false-positive-issue210643.cpp
+++ b/compiler-rt/test/tysan/no-false-positive-issue210643.cpp
@@ -1,11 +1,9 @@
// RUN: %clangxx_tysan -O0 %s -o %t && %run %t
struct A {
- int elems[3];
+ int elems[3];
};
A a;
-int main() {
- a.elems[0] = 1;
-}
+int main() { a.elems[0] = 1; }
>From e9014d6e0ca5db33bda386c6e354ecd1ae7d6f80 Mon Sep 17 00:00:00 2001
From: gbMattN <matthew.nagy at sony.com>
Date: Thu, 1 Oct 2026 11:17:59 +0100
Subject: [PATCH 3/3] Replace global sentinal with new TD tag
---
compiler-rt/lib/tysan/tysan.cpp | 23 +++----------
compiler-rt/lib/tysan/tysan.h | 2 +-
.../Instrumentation/TypeSanitizer.cpp | 33 +++----------------
3 files changed, 11 insertions(+), 47 deletions(-)
diff --git a/compiler-rt/lib/tysan/tysan.cpp b/compiler-rt/lib/tysan/tysan.cpp
index cc638cef5db83..26388e4e16ac7 100644
--- a/compiler-rt/lib/tysan/tysan.cpp
+++ b/compiler-rt/lib/tysan/tysan.cpp
@@ -119,6 +119,8 @@ static tysan_type_descriptor *getRootTD(tysan_type_descriptor *TD) {
TD = nullptr;
} else if (TD->Tag == TYSAN_MEMBER_TD) {
TD = TD->Member.Access;
+ } else if (TD->Tag == TYSAN_CONSERVATIVE_ALIAS_TD) {
+ return RootTD;
} else {
CHECK(false && "invalid enum value");
break;
@@ -128,27 +130,12 @@ static tysan_type_descriptor *getRootTD(tysan_type_descriptor *TD) {
return RootTD;
}
-// Currently, Clang's TBAA system does not correctly describe every possible
-// type. For unhandled types, it makes the most conservative choice, emitting
-// omnipotent char. TySan needs to handle this seperately to a real omnipotent
-// char otherwise the user may get false positives. When compiling with TySan
-// enabled, clang will emit a special TBAA type to show that the conservative
-// path has been taken. When the transformation pass finds this TBAA, it will
-// set this global variable. This then allows quick comparison of TDs at
-// runtime.
-static tysan_type_descriptor *__tysan_conservative_tbaa_descriptor = nullptr;
-extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
-__tysan_set_conservative_tbaa_descriptor(
- tysan_type_descriptor *conservativeTBAATD) {
- __tysan_conservative_tbaa_descriptor = conservativeTBAATD;
-}
-
// Walk up TDA to see if it reaches TDB.
static bool walkAliasTree(tysan_type_descriptor *TDA,
tysan_type_descriptor *TDB, uptr OffsetA,
uptr OffsetB) {
do {
- if (TDA == TDB || TDA == __tysan_conservative_tbaa_descriptor)
+ if (TDA == TDB || TDA->Tag == TYSAN_CONSERVATIVE_ALIAS_TD)
return OffsetA == OffsetB;
if (TDA->Tag == TYSAN_STRUCT_TD) {
@@ -228,8 +215,8 @@ static bool isAliasingLegalWithOffset(tysan_type_descriptor *TDA,
static bool isAliasingLegal(tysan_type_descriptor *TDA,
tysan_type_descriptor *TDB, uptr OffsetB = 0) {
if (TDA == TDB || !TDB || !TDA ||
- TDA == __tysan_conservative_tbaa_descriptor ||
- TDB == __tysan_conservative_tbaa_descriptor)
+ TDA->Tag == TYSAN_CONSERVATIVE_ALIAS_TD ||
+ TDB->Tag == TYSAN_CONSERVATIVE_ALIAS_TD)
return true;
// Aliasing is legal is the two types have different root nodes.
diff --git a/compiler-rt/lib/tysan/tysan.h b/compiler-rt/lib/tysan/tysan.h
index 791c6a47ce5f8..8ac957250b002 100644
--- a/compiler-rt/lib/tysan/tysan.h
+++ b/compiler-rt/lib/tysan/tysan.h
@@ -33,7 +33,7 @@ extern bool tysan_init_is_running;
void InitializeInterceptors();
-enum { TYSAN_MEMBER_TD = 1, TYSAN_STRUCT_TD = 2 };
+enum { TYSAN_MEMBER_TD = 1, TYSAN_STRUCT_TD = 2, TYSAN_CONSERVATIVE_ALIAS_TD = 3 };
struct tysan_member_type_descriptor {
struct tysan_type_descriptor *Base;
diff --git a/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
index 0f66904b0e8c2..d62944c137435 100644
--- a/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
@@ -80,7 +80,6 @@ struct TypeSanitizer {
TypeSanitizer(Module &M);
bool sanitizeFunction(Function &F, const TargetLibraryInfo &TLI);
void instrumentGlobals(Module &M);
- void setConservativeTBAA(Module &M);
private:
typedef SmallDenseMap<const MDNode *, GlobalVariable *, 8>
@@ -128,9 +127,6 @@ struct TypeSanitizer {
FunctionCallee TysanInstrumentWithShadowUpdate;
FunctionCallee TysanSetShadowType;
- FunctionCallee TysanSetConservativeTBAADescriptor;
- GlobalVariable *ConservativeTBAADescriptor;
-
/// Callback to set types for gloabls.
Function *TysanGlobalsSetTypeFunction;
};
@@ -138,8 +134,7 @@ struct TypeSanitizer {
TypeSanitizer::TypeSanitizer(Module &M)
: TargetTriple(M.getTargetTriple()),
- AnonNameRegex("^_ZTS.*N[1-9][0-9]*_GLOBAL__N"),
- ConservativeTBAADescriptor(nullptr) {
+ AnonNameRegex("^_ZTS.*N[1-9][0-9]*_GLOBAL__N") {
const DataLayout &DL = M.getDataLayout();
IntptrTy = DL.getIntPtrType(M.getContext());
PtrShift = countr_zero(IntptrTy->getPrimitiveSizeInBits() / 8);
@@ -191,12 +186,6 @@ void TypeSanitizer::initializeCallbacks(Module &M) {
IRB.getPtrTy(), // Pointer to the new type descriptor
U64Ty // Size of data we access in bytes
);
-
- TysanSetConservativeTBAADescriptor = M.getOrInsertFunction(
- "__tysan_set_conservative_tbaa_descriptor", Attr, IRB.getVoidTy(),
- IRB.getPtrTy() // Pointer to the type descriptor that describes the TBAA
- // clang generates under the conservative TBAA path
- );
}
void TypeSanitizer::instrumentGlobals(Module &M) {
@@ -245,16 +234,6 @@ void TypeSanitizer::instrumentGlobals(Module &M) {
}
}
-void TypeSanitizer::setConservativeTBAA(Module &M) {
- if (ConservativeTBAADescriptor) {
- IRBuilder<> IRB(cast<Function>(TysanCtorFunction.getCallee())
- ->getEntryBlock()
- .getTerminator());
- IRB.CreateCall(TysanSetConservativeTBAADescriptor,
- {ConservativeTBAADescriptor});
- }
-}
-
static const char LUT[] = "0123456789abcdef";
static std::string encodeName(StringRef Name) {
@@ -385,7 +364,10 @@ bool TypeSanitizer::generateBaseTypeDescriptor(
TDSubData.push_back(C);
};
- PushTDSub(ConstantInt::get(IntptrTy, 2));
+ int TDTag = 2; // 2 is for structs
+ if (Name == "TysanConservativeTBAA")
+ TDTag = 3; // 3 is for TDs that conservatively alias with everything
+ PushTDSub(ConstantInt::get(IntptrTy, TDTag));
PushTDSub(ConstantInt::get(IntptrTy, Members.size()));
// Types that are in an anonymous namespace are local to this module.
@@ -412,9 +394,6 @@ bool TypeSanitizer::generateBaseTypeDescriptor(
TD, EncodedName);
M.insertGlobalVariable(TDGV);
- if (Name == "TysanConservativeTBAA") {
- ConservativeTBAADescriptor = TDGV;
- }
if (ShouldBeComdat) {
if (TargetTriple.isOSBinFormatELF()) {
Comdat *TDComdat = M.getOrInsertComdat(EncodedName);
@@ -997,7 +976,5 @@ PreservedAnalyses TypeSanitizerPass::run(Module &M,
}
}
- TySan.setConservativeTBAA(M);
-
return PreservedAnalyses::none();
}
More information about the llvm-commits
mailing list