Skip to content

Conversation

@gbMattN
Copy link
Contributor

@gbMattN gbMattN commented Nov 21, 2025

Users may sometimes have a use case which the sanitizer does not explicitly support, or where they know extra information which they want to provide to the sanitizer for improved diagnostics. I've added some user facing interface functions which can be called from instrumented code that lets users do this.

There are

  • Functions that change the values in shadow memory
  • A function to print the type name in shadow memory to help users while debugging TBAA/TySan integration bugs

This is also the first step to fixing some problems set out in issue #169024

@llvmbot
Copy link
Member

llvmbot commented Nov 21, 2025

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Matthew Nagy (gbMattN)

Changes

Full diff: https://github.com/llvm/llvm-project/pull/169023.diff

11 Files Affected:

  • (modified) compiler-rt/include/CMakeLists.txt (+1)
  • (added) compiler-rt/include/sanitizer/tysan_interface.h (+42)
  • (modified) compiler-rt/lib/sanitizer_common/tests/sanitizer_common_test.cpp (+1)
  • (modified) compiler-rt/lib/tysan/CMakeLists.txt (+2)
  • (modified) compiler-rt/lib/tysan/tysan.cpp (+24-12)
  • (modified) compiler-rt/lib/tysan/tysan.h (+2)
  • (added) compiler-rt/lib/tysan/tysan_interface.cpp (+34)
  • (added) compiler-rt/lib/tysan/tysan_interface.h (+42)
  • (added) compiler-rt/test/tysan/interface-manipulate-shadow.c (+99)
  • (added) compiler-rt/test/tysan/interface-print-type.c (+33)
  • (modified) llvm/utils/gn/secondary/compiler-rt/include/BUILD.gn (+1)
diff --git a/compiler-rt/include/CMakeLists.txt b/compiler-rt/include/CMakeLists.txt
index 242d62b9b447b..8443e309d1e95 100644
--- a/compiler-rt/include/CMakeLists.txt
+++ b/compiler-rt/include/CMakeLists.txt
@@ -14,6 +14,7 @@ if (COMPILER_RT_BUILD_SANITIZERS)
     sanitizer/scudo_interface.h
     sanitizer/tsan_interface.h
     sanitizer/tsan_interface_atomic.h
+    sanitizer/tysan_interface.h
     sanitizer/ubsan_interface.h
     )
   set(FUZZER_HEADERS
diff --git a/compiler-rt/include/sanitizer/tysan_interface.h b/compiler-rt/include/sanitizer/tysan_interface.h
new file mode 100644
index 0000000000000..8cb35ae4b5c09
--- /dev/null
+++ b/compiler-rt/include/sanitizer/tysan_interface.h
@@ -0,0 +1,42 @@
+//===-- tsan_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 TypeSanitizer.
+//
+// Public interface header for TySan.
+//===----------------------------------------------------------------------===//
+#ifndef SANITIZER_TYSAN_INTERFACE_H
+#define SANITIZER_TYSAN_INTERFACE_H
+
+#include <sanitizer/common_interface_defs.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Copies the shadow memory for the source user memory into the shadow memory for the destination user memory
+void SANITIZER_CDECL __tysan_copy_shadow(const void *dst, const void *src, size_t type_size);
+
+// Copies the shadow memory for the source user memory into the shadow memory for each element in the
+// destination array in user memory 
+void SANITIZER_CDECL __tysan_copy_shadow_array(const void *dst_array, const void *src, size_t type_size, size_t arraySize);
+
+// Clears the shadow memory for the given range of user memory.
+void SANITIZER_CDECL __tysan_reset_shadow(const void *addr, size_t size);
+
+// Writes the name of the type represented in the shadow memory for the given location in user memory
+// into the given buffer, up to the given size.
+// Returns the length written.
+int SANITIZER_CDECL __tysan_get_type_name(const void *addr, char *buffer, size_t buffer_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_common_test.cpp b/compiler-rt/lib/sanitizer_common/tests/sanitizer_common_test.cpp
index 111e55ef36bfb..917ba847ad478 100644
--- a/compiler-rt/lib/sanitizer_common/tests/sanitizer_common_test.cpp
+++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_common_test.cpp
@@ -21,6 +21,7 @@
 #include "../../../include/sanitizer/asan_interface.h"
 #include "../../../include/sanitizer/msan_interface.h"
 #include "../../../include/sanitizer/tsan_interface.h"
+#include "../../../include/sanitizer/tysan_interface.h"
 #include "gtest/gtest.h"
 #include "sanitizer_common/sanitizer_allocator_internal.h"
 #include "sanitizer_common/sanitizer_common.h"
diff --git a/compiler-rt/lib/tysan/CMakeLists.txt b/compiler-rt/lib/tysan/CMakeLists.txt
index 7d13ae3963919..a878a46322bd2 100644
--- a/compiler-rt/lib/tysan/CMakeLists.txt
+++ b/compiler-rt/lib/tysan/CMakeLists.txt
@@ -4,11 +4,13 @@ include_directories(..)
 set(TYSAN_SOURCES
   tysan.cpp
   tysan_interceptors.cpp
+  tysan_interface.cpp
   )
 
 SET(TYSAN_HEADERS
   tysan.h
   tysan_flags.inc
+  tysan_interface.h
   tysan_platform.h
   )
 
diff --git a/compiler-rt/lib/tysan/tysan.cpp b/compiler-rt/lib/tysan/tysan.cpp
index 1c67adeba0fc5..fdffe784d0c74 100644
--- a/compiler-rt/lib/tysan/tysan.cpp
+++ b/compiler-rt/lib/tysan/tysan.cpp
@@ -56,29 +56,41 @@ static const char *getDisplayName(const char *Name) {
   return DName;
 }
 
-static void printTDName(tysan_type_descriptor *td) {
-  if (((sptr)td) <= 0) {
-    Printf("<unknown type>");
-    return;
+int getTDName(void *_td, char *buffer, uptr buffer_size, bool asset_on_error){
+  tysan_type_descriptor* td = (tysan_type_descriptor*)_td;
+  if(((sptr)td) <= 0){
+    return internal_snprintf(buffer, buffer_size, "<unknown type>");
   }
 
+  uptr written = 0;
   switch (td->Tag) {
   default:
-    CHECK(false && "invalid enum value");
+    if (asset_on_error)
+      CHECK(false && "invalid enum value");
+    else
+      written = internal_snprintf(buffer, buffer_size, "<invalid shadow>");
     break;
   case TYSAN_MEMBER_TD:
-    printTDName(td->Member.Access);
-    if (td->Member.Access != td->Member.Base) {
-      Printf(" (in ");
-      printTDName(td->Member.Base);
-      Printf(" at offset %zu)", td->Member.Offset);
+    written = getTDName(td->Member.Access, buffer, buffer_size, false);
+    if (td->Member.Access != td->Member.Base && written != buffer_size) {
+      written += internal_snprintf(&buffer[written], buffer_size - written, " (in ");
+      written += getTDName(td->Member.Base, &buffer[written], buffer_size - written, false);
+      written += internal_snprintf(&buffer[written], buffer_size - written, " at offset %zu)", td->Member.Offset);
     }
     break;
   case TYSAN_STRUCT_TD:
-    Printf("%s", getDisplayName(
-                     (char *)(td->Struct.Members + td->Struct.MemberCount)));
+    written = internal_snprintf(buffer, buffer_size, "%s",
+      getDisplayName((char *)(td->Struct.Members + td->Struct.MemberCount)));
     break;
   }
+  return written;
+}
+
+static void printTDName(tysan_type_descriptor *td) {
+  static const uptr nameBufferSize = 512;
+  static char nameBuffer[nameBufferSize];
+  getTDName(td, nameBuffer, nameBufferSize, true);
+  Printf("%s", nameBuffer);
 }
 
 static tysan_type_descriptor *getRootTD(tysan_type_descriptor *TD) {
diff --git a/compiler-rt/lib/tysan/tysan.h b/compiler-rt/lib/tysan/tysan.h
index 97df28037b0d2..b7b1306cf10bd 100644
--- a/compiler-rt/lib/tysan/tysan.h
+++ b/compiler-rt/lib/tysan/tysan.h
@@ -20,11 +20,13 @@ using __sanitizer::sptr;
 using __sanitizer::u16;
 using __sanitizer::uptr;
 
+#include "tysan_interface.h"
 #include "tysan_platform.h"
 
 extern "C" {
 void tysan_set_type_unknown(const void *addr, uptr size);
 void tysan_copy_types(const void *daddr, const void *saddr, uptr size);
+int getTDName(void *td, char *buffer, uptr buffer_size, bool assert_on_error);
 }
 
 namespace __tysan {
diff --git a/compiler-rt/lib/tysan/tysan_interface.cpp b/compiler-rt/lib/tysan/tysan_interface.cpp
new file mode 100644
index 0000000000000..c2da6246b0044
--- /dev/null
+++ b/compiler-rt/lib/tysan/tysan_interface.cpp
@@ -0,0 +1,34 @@
+//===-- tysan_interface.inc --------------------------------------*- 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 TypeSanitizer.
+//
+//===----------------------------------------------------------------------===//
+#include "tysan.h"
+#include "tysan_interface.h"
+
+void __tysan_copy_shadow(const void *dst, const void *src, size_t type_size){
+    tysan_copy_types(dst, src, type_size);
+}
+
+void __tysan_copy_shadow_array(const void *dst_array, const void *src, size_t type_size, size_t arraySize){
+    const void* dst = dst_array;
+    for(size_t i = 0; i < arraySize; i++){
+        tysan_copy_types(dst, src, type_size);
+        dst = (void*)(((uptr)dst) + type_size);
+    }
+}
+
+void __tysan_reset_shadow(const void *addr, size_t size){
+    tysan_set_type_unknown(addr, size);
+}
+
+int __tysan_get_type_name(const void *addr, char *buffer, size_t buffer_size){
+    void** shadow = (void**)__tysan::shadow_for(addr);
+    return getTDName(*shadow, buffer, buffer_size, false);
+}
diff --git a/compiler-rt/lib/tysan/tysan_interface.h b/compiler-rt/lib/tysan/tysan_interface.h
new file mode 100644
index 0000000000000..5a5e5cc08d2b3
--- /dev/null
+++ b/compiler-rt/lib/tysan/tysan_interface.h
@@ -0,0 +1,42 @@
+//===-- tysan_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 TypeSanitizer.
+//
+// The functions declared in this header will be inserted by the instrumentation
+// module.
+// This header can be included by the instrumented program or by TySan tests.
+//===----------------------------------------------------------------------===//
+
+#ifndef TYSAN_INTERFACE_H
+#define TYSAN_INTERFACE_H
+
+#include <sanitizer_common/sanitizer_internal_defs.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tysan_copy_shadow(const void *dst, const void *src, size_t type_size);
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tysan_copy_shadow_array(const void *dst_array, const void *src, size_t type_size, size_t arraySize);
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tysan_reset_shadow(const void *addr, size_t size);
+
+SANITIZER_INTERFACE_ATTRIBUTE
+int __tysan_get_type_name(const void *addr, char *buffer, size_t buffer_size);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif
diff --git a/compiler-rt/test/tysan/interface-manipulate-shadow.c b/compiler-rt/test/tysan/interface-manipulate-shadow.c
new file mode 100644
index 0000000000000..504de2876cf26
--- /dev/null
+++ b/compiler-rt/test/tysan/interface-manipulate-shadow.c
@@ -0,0 +1,99 @@
+// REQUIRES: system-linux || system-darwin
+// RUN: %clang_tysan %s -g -shared -fpic -o %t.so -DBUILD_SO
+// RUN: %clang_tysan %s -g -o %t
+// RUN: %run %t %t.so 2>&1 | FileCheck %s
+
+// Compilers can't optimize using type aliasing across the bounds of dynamic librarys
+// When passing memory between instrumented executables and dlls, you may want to alter TySan's
+// shadow to prevent it from catching technically correct, yet harmless aliasing violations
+
+#ifdef BUILD_SO
+float useFloatArray(float* mem){
+    mem[0] = 2.f;
+    mem[1] = 3.f;
+    return mem[0] + mem[1];
+}
+
+int useIntArray(int* mem){
+    mem[0] = 2;
+    mem[1] = 3;
+    mem[2] = 5;
+    return mem[0] + mem[1] + mem[2];
+}
+#else
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <sanitizer/tysan_interface.h>
+#include <stdio.h>
+
+typedef float(*lib_func1_t)(float*);
+typedef int(*lib_func2_t)(int*);
+
+void print_flush(const char* message){
+    printf("%s\n", message);
+    fflush(stdout);
+}
+
+int main(int argc, char* argv[]){
+    assert(argc >=2);
+    void *libHandle = dlopen(argv[1], RTLD_LAZY);
+    assert(libHandle);
+
+    lib_func1_t useFloatArray = (lib_func1_t)dlsym(libHandle, "useFloatArray");
+    lib_func2_t useIntArray = (lib_func2_t)dlsym(libHandle, "useIntArray");
+
+    char memory[sizeof(int) * 3];
+    int iResult = 0;
+    float fResult = 0.f;
+    print_flush("Calling with omnipotent char memory");
+    fResult = useFloatArray((float*)memory);
+    print_flush("Shadow now has floats in");
+    iResult = useIntArray((int*)memory);
+
+// CHECK: Calling with omnipotent char memory
+// CHECK-NEXT: Shadow now has floats in
+// CHECK-NEXT: ERROR: TypeSanitizer: type-aliasing-violation on address
+// CHECK-NEXT: WRITE of size 4 at 0x{{.*}} with type int accesses an existing object of type float
+    
+    __tysan_reset_shadow(memory, sizeof(memory));
+    print_flush("Shadow has been reset");
+    useIntArray((int*)memory);
+    print_flush("Completed int array");
+    
+// CHECK: Shadow has been reset
+// CHECK-NEXT: Completed int array
+
+    // Set shadow type to float
+    __tysan_copy_shadow_array(memory, &fResult, sizeof(float), 3);
+    print_flush("Float array with float set shadow");
+    useFloatArray((float*)memory);
+    print_flush("Int array with float set shadow");
+    useIntArray((int*)memory);
+
+// CHECK: Float array with float set shadow
+// CHECK-NEXT: Int array with float set shadow
+// CHECK-NEXT: ERROR: TypeSanitizer: type-aliasing-violation on address
+// CHECK-NEXT: WRITE of size 4 at 0x{{.*}} with type int accesses an existing object of type float
+
+    // Set shadow type to int
+    for(size_t i = 0; i < 3; i++){
+        __tysan_copy_shadow(&memory[sizeof(int) * i], &iResult, sizeof(int));
+    }
+    print_flush("Float array with int set shadow");
+    useFloatArray((float*)memory);
+    print_flush("Int array with int set shadow");
+    useIntArray((int*)memory);
+    print_flush("Completed int array");
+
+// CHECK: Float array with int set shadow
+// CHECK-NEXT: ERROR: TypeSanitizer: type-aliasing-violation on address
+// CHECK-NEXT: WRITE of size 4 at 0x{{.*}} with type float accesses an existing object of type int
+// CHECK: Int array with int set shadow
+// CHECK-NEXT: Completed int array
+
+    dlclose(libHandle);
+    return 0;
+}
+
+#endif
diff --git a/compiler-rt/test/tysan/interface-print-type.c b/compiler-rt/test/tysan/interface-print-type.c
new file mode 100644
index 0000000000000..c61a7dc477353
--- /dev/null
+++ b/compiler-rt/test/tysan/interface-print-type.c
@@ -0,0 +1,33 @@
+// RUN: %clang_tysan %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s
+
+#include <sanitizer/tysan_interface.h>
+#include <stdio.h>
+
+struct S{
+    int i;
+    float f;
+};
+
+void printInt(int* i){
+    const int bufferSize = 512;
+    static char nameBuffer[bufferSize];
+    __tysan_get_type_name(i, nameBuffer, 512);
+    printf("%d, %s\n", *i, nameBuffer);
+    fflush(stdout);
+}
+
+int main(){
+    struct S s;
+    s.i = 4;
+    printInt((int*)&s);
+// CHECK: 4, int (in S at offset 0)
+
+    s.f = 5.0f;
+// CHECK: ERROR: TypeSanitizer: type-aliasing-violation
+// CHECK: READ of size 4 at 0x{{.*}} with type int accesses an existing object of type float (in S at offset 4)
+// CHECK: {{.*}}, float (in S at offset 4)
+    printInt((int*)&s.f);
+
+    return 0;
+}
diff --git a/llvm/utils/gn/secondary/compiler-rt/include/BUILD.gn b/llvm/utils/gn/secondary/compiler-rt/include/BUILD.gn
index 273fd7172da62..8e8ce837ef21e 100644
--- a/llvm/utils/gn/secondary/compiler-rt/include/BUILD.gn
+++ b/llvm/utils/gn/secondary/compiler-rt/include/BUILD.gn
@@ -22,6 +22,7 @@ copy("include") {
     "sanitizer/scudo_interface.h",
     "sanitizer/tsan_interface.h",
     "sanitizer/tsan_interface_atomic.h",
+    sanitizer/tysan_interface.h
     "sanitizer/ubsan_interface.h",
   ]
   outputs = [ "$clang_resource_dir/include/{{source_target_relative}}" ]

@llvm llvm deleted a comment from github-actions bot Nov 21, 2025
@github-actions
Copy link

🐧 Linux x64 Test Results

  • 5822 tests passed
  • 1323 tests skipped

@llvmbot llvmbot added the compiler-rt:tsan Thread sanitizer label Nov 21, 2025
@gbMattN gbMattN removed the compiler-rt:tsan Thread sanitizer label Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants