Skip to content

Conversation

@davidmrdavid
Copy link
Contributor

@davidmrdavid davidmrdavid commented Sep 18, 2025

Mitigation for: google/sanitizers#749

Disclosure: I'm not an ASan compiler expert yet (I'm trying to learn!), I primarily work in the runtime. Some of this PR was developed with the help of AI tools (primarily as a "fuzzy grep engine"), but I've manually refined and tested the output, and can speak for every line. In general, I used it only to orient myself and for "rubberducking".

Context:

The msvc ASan team (👋 ) has received an internal request to improve clang's exception handling under ASan for Windows. Namely, we're interested in mitigating this bug: google/sanitizers#749

To summarize, today, clang + ASan produces a false-positive error for this program:

#include <cstdio>
#include <exception>
int main()
{
	try	{
		throw std::exception("test");
	}catch (const std::exception& ex){
		puts(ex.what());
	}
	return 0;
}

The error reads as such:

C:\Users\dajusto\source\repros\upstream>type main.cpp
#include <cstdio>
#include <exception>
int main()
{
        try     {
                throw std::exception("test");
        }catch (const std::exception& ex){
                puts(ex.what());
        }
        return 0;
}
C:\Users\dajusto\source\repros\upstream>"C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\clang.exe" -fsanitize=address -g -O0 main.cpp

C:\Users\dajusto\source\repros\upstream>a.exe
=================================================================
==19112==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000000 (pc 0x7ff72c7c11d9 bp 0x0080000ff960 sp 0x0080000fcf50 T0)
==19112==The signal is caused by a READ memory access.
==19112==Hint: address points to the zero page.
    #0 0x7ff72c7c11d8 in main C:\Users\dajusto\source\repros\upstream\main.cpp:8
    #1 0x7ff72c7d479f in _CallSettingFrame C:\repos\msvc\src\vctools\crt\vcruntime\src\eh\amd64\handlers.asm:49
    #2 0x7ff72c7c8944 in __FrameHandler3::CxxCallCatchBlock(struct _EXCEPTION_RECORD *) C:\repos\msvc\src\vctools\crt\vcruntime\src\eh\frame.cpp:1567
    #3 0x7ffb4a90e3e5  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18012e3e5)
    #4 0x7ff72c7c1128 in main C:\Users\dajusto\source\repros\upstream\main.cpp:6
    #5 0x7ff72c7c33db in invoke_main C:\repos\msvc\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
    #6 0x7ff72c7c33db in __scrt_common_main_seh C:\repos\msvc\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #7 0x7ffb49b05c06  (C:\WINDOWS\System32\KERNEL32.DLL+0x180035c06)
    #8 0x7ffb4a8455ef  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x1800655ef)

==19112==Register values:
rax = 0  rbx = 80000ff8e0  rcx = 27d76d00000  rdx = 80000ff8e0
rdi = 80000fdd50  rsi = 80000ff6a0  rbp = 80000ff960  rsp = 80000fcf50
r8  = 100  r9  = 19930520  r10 = 8000503a90  r11 = 80000fd540
r12 = 80000fd020  r13 = 0  r14 = 80000fdeb8  r15 = 0
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: access-violation C:\Users\dajusto\source\repros\upstream\main.cpp:8 in main
==19112==ABORTING

The root of the issue appears to be that ASan's instrumentation is incompatible with Window's assumptions for instantiating catch-block's parameters (ex in the snippet above).

The nitty gritty details are lost on me, but I understand that to make this work without loss of ASan coverage, a "serious" refactoring is needed. In the meantime, users risk false positive errors when pairing ASan + catch-block parameters on Windows.

To mitigate this I think we should avoid instrumenting catch-block parameters on Windows. It appears to me this is as "simple" as marking catch block parameters as "uninteresting" in AddressSanitizer::isInterestingAlloca. My manual tests seem to confirm this.

I believe this is strictly better than today's status quo, where the runtime generates false positives. Although we're now explicitly choosing to instrument less, the benefit is that now more programs can run with ASan without funky macros that disable ASan on exception blocks.

This PR: implements the mitigation above, and creates a simple new test for it.

Thanks!

@llvmbot
Copy link
Member

llvmbot commented Sep 18, 2025

@llvm/pr-subscribers-llvm-transforms

Author: David Justo (davidmrdavid)

Changes

Mitigation for: google/sanitizers#749

Disclosure: I'm not an ASan compiler expert yet (I'm trying to learn!), I primarily work in the runtime. Some of this PR was developed with the help of AI tools (primarily as a "fuzzy grep engine"), but I've manually refined and tested the output, can speak for every line. In general, I used it only to orient myself and for "rubberducking".

All text in the PR and in this description is written by me.

Context:

The msvc ASan team (👋 ) has received an internal request to improve clang's exception handling under ASan for Windows. Namely, we're interested in mitigating this bug: google/sanitizers#749

To summarize, today, clang + ASan produces a false-positive error for this program:

#include &lt;cstdio&gt;
#include &lt;exception&gt;
int main()
{
	try	{
		throw std::exception("test");
	}catch (const std::exception&amp; ex){
		puts(ex.what());
	}
	return 0;
}

The error reads as such:

C:\Users\dajusto\source\repros\upstream&gt;type main.cpp
#include &lt;cstdio&gt;
#include &lt;exception&gt;
int main()
{
        try     {
                throw std::exception("test");
        }catch (const std::exception&amp; ex){
                puts(ex.what());
        }
        return 0;
}
C:\Users\dajusto\source\repros\upstream&gt;"C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\clang.exe" -fsanitize=address -g -O0 main.cpp

C:\Users\dajusto\source\repros\upstream&gt;a.exe
=================================================================
==19112==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000000 (pc 0x7ff72c7c11d9 bp 0x0080000ff960 sp 0x0080000fcf50 T0)
==19112==The signal is caused by a READ memory access.
==19112==Hint: address points to the zero page.
    #<!-- -->0 0x7ff72c7c11d8 in main C:\Users\dajusto\source\repros\upstream\main.cpp:8
    #<!-- -->1 0x7ff72c7d479f in _CallSettingFrame C:\repos\msvc\src\vctools\crt\vcruntime\src\eh\amd64\handlers.asm:49
    #<!-- -->2 0x7ff72c7c8944 in __FrameHandler3::CxxCallCatchBlock(struct _EXCEPTION_RECORD *) C:\repos\msvc\src\vctools\crt\vcruntime\src\eh\frame.cpp:1567
    #<!-- -->3 0x7ffb4a90e3e5  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18012e3e5)
    #<!-- -->4 0x7ff72c7c1128 in main C:\Users\dajusto\source\repros\upstream\main.cpp:6
    #<!-- -->5 0x7ff72c7c33db in invoke_main C:\repos\msvc\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
    #<!-- -->6 0x7ff72c7c33db in __scrt_common_main_seh C:\repos\msvc\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #<!-- -->7 0x7ffb49b05c06  (C:\WINDOWS\System32\KERNEL32.DLL+0x180035c06)
    #<!-- -->8 0x7ffb4a8455ef  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x1800655ef)

==19112==Register values:
rax = 0  rbx = 80000ff8e0  rcx = 27d76d00000  rdx = 80000ff8e0
rdi = 80000fdd50  rsi = 80000ff6a0  rbp = 80000ff960  rsp = 80000fcf50
r8  = 100  r9  = 19930520  r10 = 8000503a90  r11 = 80000fd540
r12 = 80000fd020  r13 = 0  r14 = 80000fdeb8  r15 = 0
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: access-violation C:\Users\dajusto\source\repros\upstream\main.cpp:8 in main
==19112==ABORTING

The root of the issue appears to be that ASan's instrumentation is incompatible with Window's assumptions for instantiating catch-block's parameters (ex in the snippet above).

The nitty gritty details are lost on me, but I understand that to make this work without loss of ASan coverage, a "serious" refactoring is needed. In the meantime, users risk false positive errors when pairing ASan + catch-block parameters on Windows.

To mitigate this I think we should avoid instrumenting catch-block parameters on Windows. It appears to me this is as "simple" as marking catch block parameters as "uninteresting" in AddressSanitizer::isInterestingAlloca. My manual tests seem to confirm this.

I believe this is strictly better than today's status quo, where the runtime generates false positives. Although we're now explicitly choosing to instrument less, the benefit is that now more programs can run with ASan without funky macros that disable ASan on exception blocks.

This PR: implements the mitigation above, and creates a simple new test for it.

Thanks!


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

2 Files Affected:

  • (added) compiler-rt/test/asan/TestCases/Windows/basic_exception_handling.cpp (+36)
  • (modified) llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp (+15-1)
diff --git a/compiler-rt/test/asan/TestCases/Windows/basic_exception_handling.cpp b/compiler-rt/test/asan/TestCases/Windows/basic_exception_handling.cpp
new file mode 100644
index 0000000000000..94ca4b9bf2df0
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Windows/basic_exception_handling.cpp
@@ -0,0 +1,36 @@
+// RUN: %clangxx_asan %s -o %t
+// RUN: %run %t | FileCheck %s
+
+// This test tests that declaring a parameter in a catch-block does not produce a false positive
+// ASan error on Windows.
+
+// This code is based on the repro in https://github.com/google/sanitizers/issues/749
+#include <cstdio>
+#include <exception>
+
+void throwInFunction(){
+    throw std::exception("test2");
+}
+
+int main()
+{
+    // case 1: direct throw
+	try	{
+		throw std::exception("test1");
+	} catch (const std::exception& ex){
+		puts(ex.what());
+        // CHECK: test1
+	}
+
+    // case 2: throw in function
+    try {
+        throwInFunction();
+    } catch (const std::exception& ex){
+        puts(ex.what());
+        // CHECK: test2
+    }
+
+    printf("Success!\n");
+    // CHECK: Success!
+	return 0;
+}
\ No newline at end of file
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 42c3d4a4f4c46..986d3c2861af0 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -1397,6 +1397,16 @@ void AddressSanitizer::instrumentMemIntrinsic(MemIntrinsic *MI,
   MI->eraseFromParent();
 }
 
+// Check if an alloca is a catch block parameter
+static bool isCatchParameter(const AllocaInst &AI) {
+  for (const Use &U : AI.uses()) {
+    if (isa<CatchPadInst>(U.getUser())) {
+      return true;
+    }
+  }
+  return false;
+}
+
 /// Check if we want (and can) handle this alloca.
 bool AddressSanitizer::isInterestingAlloca(const AllocaInst &AI) {
   auto [It, Inserted] = ProcessedAllocas.try_emplace(&AI);
@@ -1417,7 +1427,11 @@ bool AddressSanitizer::isInterestingAlloca(const AllocaInst &AI) {
        // swifterror allocas are register promoted by ISel
        !AI.isSwiftError() &&
        // safe allocas are not interesting
-       !(SSGI && SSGI->isSafe(AI)));
+       !(SSGI && SSGI->isSafe(AI)) &&
+       // Mitigation for https://github.com/google/sanitizers/issues/749
+       // We don't instrument Windows catch-block parameters to avoid
+       // interfering with exception handling assumptions.
+       !(TargetTriple.isOSWindows() && isCatchParameter(AI)));
 
   It->second = IsInteresting;
   return IsInteresting;

@llvmbot
Copy link
Member

llvmbot commented Sep 18, 2025

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

Author: David Justo (davidmrdavid)

Changes

Mitigation for: google/sanitizers#749

Disclosure: I'm not an ASan compiler expert yet (I'm trying to learn!), I primarily work in the runtime. Some of this PR was developed with the help of AI tools (primarily as a "fuzzy grep engine"), but I've manually refined and tested the output, can speak for every line. In general, I used it only to orient myself and for "rubberducking".

All text in the PR and in this description is written by me.

Context:

The msvc ASan team (👋 ) has received an internal request to improve clang's exception handling under ASan for Windows. Namely, we're interested in mitigating this bug: google/sanitizers#749

To summarize, today, clang + ASan produces a false-positive error for this program:

#include &lt;cstdio&gt;
#include &lt;exception&gt;
int main()
{
	try	{
		throw std::exception("test");
	}catch (const std::exception&amp; ex){
		puts(ex.what());
	}
	return 0;
}

The error reads as such:

C:\Users\dajusto\source\repros\upstream&gt;type main.cpp
#include &lt;cstdio&gt;
#include &lt;exception&gt;
int main()
{
        try     {
                throw std::exception("test");
        }catch (const std::exception&amp; ex){
                puts(ex.what());
        }
        return 0;
}
C:\Users\dajusto\source\repros\upstream&gt;"C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\clang.exe" -fsanitize=address -g -O0 main.cpp

C:\Users\dajusto\source\repros\upstream&gt;a.exe
=================================================================
==19112==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000000 (pc 0x7ff72c7c11d9 bp 0x0080000ff960 sp 0x0080000fcf50 T0)
==19112==The signal is caused by a READ memory access.
==19112==Hint: address points to the zero page.
    #<!-- -->0 0x7ff72c7c11d8 in main C:\Users\dajusto\source\repros\upstream\main.cpp:8
    #<!-- -->1 0x7ff72c7d479f in _CallSettingFrame C:\repos\msvc\src\vctools\crt\vcruntime\src\eh\amd64\handlers.asm:49
    #<!-- -->2 0x7ff72c7c8944 in __FrameHandler3::CxxCallCatchBlock(struct _EXCEPTION_RECORD *) C:\repos\msvc\src\vctools\crt\vcruntime\src\eh\frame.cpp:1567
    #<!-- -->3 0x7ffb4a90e3e5  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18012e3e5)
    #<!-- -->4 0x7ff72c7c1128 in main C:\Users\dajusto\source\repros\upstream\main.cpp:6
    #<!-- -->5 0x7ff72c7c33db in invoke_main C:\repos\msvc\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
    #<!-- -->6 0x7ff72c7c33db in __scrt_common_main_seh C:\repos\msvc\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #<!-- -->7 0x7ffb49b05c06  (C:\WINDOWS\System32\KERNEL32.DLL+0x180035c06)
    #<!-- -->8 0x7ffb4a8455ef  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x1800655ef)

==19112==Register values:
rax = 0  rbx = 80000ff8e0  rcx = 27d76d00000  rdx = 80000ff8e0
rdi = 80000fdd50  rsi = 80000ff6a0  rbp = 80000ff960  rsp = 80000fcf50
r8  = 100  r9  = 19930520  r10 = 8000503a90  r11 = 80000fd540
r12 = 80000fd020  r13 = 0  r14 = 80000fdeb8  r15 = 0
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: access-violation C:\Users\dajusto\source\repros\upstream\main.cpp:8 in main
==19112==ABORTING

The root of the issue appears to be that ASan's instrumentation is incompatible with Window's assumptions for instantiating catch-block's parameters (ex in the snippet above).

The nitty gritty details are lost on me, but I understand that to make this work without loss of ASan coverage, a "serious" refactoring is needed. In the meantime, users risk false positive errors when pairing ASan + catch-block parameters on Windows.

To mitigate this I think we should avoid instrumenting catch-block parameters on Windows. It appears to me this is as "simple" as marking catch block parameters as "uninteresting" in AddressSanitizer::isInterestingAlloca. My manual tests seem to confirm this.

I believe this is strictly better than today's status quo, where the runtime generates false positives. Although we're now explicitly choosing to instrument less, the benefit is that now more programs can run with ASan without funky macros that disable ASan on exception blocks.

This PR: implements the mitigation above, and creates a simple new test for it.

Thanks!


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

2 Files Affected:

  • (added) compiler-rt/test/asan/TestCases/Windows/basic_exception_handling.cpp (+36)
  • (modified) llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp (+15-1)
diff --git a/compiler-rt/test/asan/TestCases/Windows/basic_exception_handling.cpp b/compiler-rt/test/asan/TestCases/Windows/basic_exception_handling.cpp
new file mode 100644
index 0000000000000..94ca4b9bf2df0
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Windows/basic_exception_handling.cpp
@@ -0,0 +1,36 @@
+// RUN: %clangxx_asan %s -o %t
+// RUN: %run %t | FileCheck %s
+
+// This test tests that declaring a parameter in a catch-block does not produce a false positive
+// ASan error on Windows.
+
+// This code is based on the repro in https://github.com/google/sanitizers/issues/749
+#include <cstdio>
+#include <exception>
+
+void throwInFunction(){
+    throw std::exception("test2");
+}
+
+int main()
+{
+    // case 1: direct throw
+	try	{
+		throw std::exception("test1");
+	} catch (const std::exception& ex){
+		puts(ex.what());
+        // CHECK: test1
+	}
+
+    // case 2: throw in function
+    try {
+        throwInFunction();
+    } catch (const std::exception& ex){
+        puts(ex.what());
+        // CHECK: test2
+    }
+
+    printf("Success!\n");
+    // CHECK: Success!
+	return 0;
+}
\ No newline at end of file
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 42c3d4a4f4c46..986d3c2861af0 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -1397,6 +1397,16 @@ void AddressSanitizer::instrumentMemIntrinsic(MemIntrinsic *MI,
   MI->eraseFromParent();
 }
 
+// Check if an alloca is a catch block parameter
+static bool isCatchParameter(const AllocaInst &AI) {
+  for (const Use &U : AI.uses()) {
+    if (isa<CatchPadInst>(U.getUser())) {
+      return true;
+    }
+  }
+  return false;
+}
+
 /// Check if we want (and can) handle this alloca.
 bool AddressSanitizer::isInterestingAlloca(const AllocaInst &AI) {
   auto [It, Inserted] = ProcessedAllocas.try_emplace(&AI);
@@ -1417,7 +1427,11 @@ bool AddressSanitizer::isInterestingAlloca(const AllocaInst &AI) {
        // swifterror allocas are register promoted by ISel
        !AI.isSwiftError() &&
        // safe allocas are not interesting
-       !(SSGI && SSGI->isSafe(AI)));
+       !(SSGI && SSGI->isSafe(AI)) &&
+       // Mitigation for https://github.com/google/sanitizers/issues/749
+       // We don't instrument Windows catch-block parameters to avoid
+       // interfering with exception handling assumptions.
+       !(TargetTriple.isOSWindows() && isCatchParameter(AI)));
 
   It->second = IsInteresting;
   return IsInteresting;

@github-actions
Copy link

github-actions bot commented Sep 18, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@davidmrdavid
Copy link
Contributor Author

FYI @rnk - since you contributed to the discussion of the bug I'm trying to mitigate. Would appreciate your thoughts on this PR. Thanks!

Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I think this is a correct fix. Catch objects simply shouldn't participate in UAR detection, given the EH ABI requirements.

// Mitigation for https://github.com/google/sanitizers/issues/749
// We don't instrument Windows catch-block parameters to avoid
// interfering with exception handling assumptions.
!(TargetTriple.isOSWindows() && isCatchParameter(AI)));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do a simple up-front pass over all basic blocks looking for catchpads and build up a set of catchpad parameters, and make this O(1) by testing for set membership.

Allocas may have a very high number of uses, so this seems worth optimizing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice above that isAllocaPromotable is also O(#uses), and that seems bad, honestly :(

@davidmrdavid
Copy link
Contributor Author

Following up to this request: #159618 (comment)

I could use a bit of assistance, I think I'm doing something wrong.

So, I tried producing the .ll post asan-pass for my change, and without my change. When comparing the resulting .lls, they appear identical, despite them producing very different runtime outcomes when using clang in full.

I suspect I'm doing something wrong when generating these .ll files.

For debugging, this is the .cpp file I'm using as a base:

class MyClass {};
#include <exception>
#include <cstdio>

int main() {
  try {
    throw 1;
  } catch (const int ex) {
    printf("%d\n", ex);
    return -1;
  }
  return 0;
}

(please ignore the dead code of class MyClass {} and the include of exception``. I forgot to remove them when generating the .ll, with is an arduous process when doing with and without my change).

To obtain the .ll post asan-pass without my change, I'm running the following sequence of commands:

C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\clang.exe -g0 -O0 -emit-llvm -c main.cpp -o main_before.bc
C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\llvm-extract.exe -func=main main_before.bc -o main_func_before.bc
C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\llvm-dis.exe main_func_before.bc -o main_func_dis_before.ll
C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\\opt.exe -S main_func_dis_before.ll -passes=asan > out_before.txt
// ... and this is just a sanity check that the error persists at runtime
C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\clang.exe -fsanitize=address -g -O0 main.cpp & a.exe
-978572156

To obtain the .ll post asan-pass with my change, I'm running these commands instead:

C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\clang.exe -g0 -O0 -emit-llvm -c main.cpp -o main_after.bc
C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\llvm-extract.exe -func=main main_after.bc -o main_func_after.bc
C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\llvm-dis.exe main_func_after.bc -o main_func_dis_after.ll
C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\\opt.exe -S main_func_dis_after.ll -passes=asan > out_after.txt
// ... and this is just a sanity check the error is fixed at runtime
C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\clang.exe -fsanitize=address -g -O0 main.cpp & a.exe
1

Below, I'm attaching my out_before.txt and out_after.txt files, which are nearly identical except that the "Module ID" is naturally different.

out_before.txt
out_after.txt

This doesn't seem right, does it? Am I doing something obviously wrong? Thanks!

@thurstond
Copy link
Contributor

thurstond commented Oct 1, 2025

@davidmrdavid Shot in the dark: is opt.exe out of date? At least on Linux, it's common for clang to be rebuilt without building opt (or vice-versa), and then they can become out of sync.

For example:

  • ninja check-asan will update clang but not opt (those tests only use clang)
  • LIT_FILTER=AddressSanitizer ninja check-llvm will update opt but not clang (those tests only use opt)

@davidmrdavid
Copy link
Contributor Author

@thurstond - thanks for the tip, but I've been fully deleting my build directory between builds (build.runtimes in this case), thus forcefully re-generating opt.exe, and the output remains the same.

If it helps, this is how I'm building llvm:

:: Create and enter build directory (`build.runtimes`)
mkdir %LLVM_ROOT%\build.runtimes
cd %LLVM_ROOT%\build.runtimes

:: Configure the build
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DLLVM_ENABLE_PROJECTS=clang -DLLVM_ENABLE_RUNTIMES=compiler-rt ..\llvm\

:: Build LLVM
ninja

Something weird is that, when running my local clang, I do see my own ad-hoc debug messages in AddressSanitizer.cpp being emitted (printed via llvm::errs() << "DEBUG: foo\n"; at various points in the file) but when running opt I don't see those emitted at all. Not sure if that's expected.

I think I'm going to have to dive deeper, and try to attach a debugger on opt to see what's running. I may also try to attend office hours and/or check in the discord for advice. But yeah if anyone sees that I'm doing anything wrong here, please do let me know. As-is, I'm unable to get the .ll unit test working right :-) .

@thurstond
Copy link
Contributor

thurstond commented Oct 1, 2025

edit: please ignore, I noticed it's already in your .ll

davidmrdavid@ Ah, try adding this to the top of your .ll file:

target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc"

(or similar; I stole this snippet from llvm/test/Instrumentation/AddressSanitizer/do-not-instrument-globals-windows.ll.

opt is designed to be portable so it doesn't know or care that you're running on Windows, unless you tell it to act as if it is Windows.

@thurstond
Copy link
Contributor

thurstond commented Oct 1, 2025

@davidmrdavid opt -passes=asan will only instrument functions that are annotated with sanitize_address (see any test file e.g., llvm/test/Instrumentation/AddressSanitizer/localescape.ll). The .ll file dumped from clang won't have that, so you need to manually add it.

Third-time lucky? :-)

@davidmrdavid
Copy link
Contributor Author

Thanks, yes - that seems to have worked, thanks a lot :-) . I should be able to push a test sometime next week, but I've confirmed locally that adding the missing sanitize_address annotation was key. Third time was the charm!

@davidmrdavid
Copy link
Contributor Author

My latest diff should contain a working unit test, thanks to the suggestions here ^.
Would appreciate another round of reviews, @rnk / @antoniofrighetto . Thank you!

Copy link
Contributor

@antoniofrighetto antoniofrighetto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@davidmrdavid
Copy link
Contributor Author

Thanks for the approval @antoniofrighetto. I incorporated your last feedback, could I get a final re-approval? Then I can merge myself, thanks a lot for your help here!

@davidmrdavid
Copy link
Contributor Author

davidmrdavid commented Oct 17, 2025

Thanks again for the re-approval :) .

As I mentioned in my last comment - I'll take that re-approval as an 'ok' to merge. I incorporated the final suggestion as-is, so I'm assuming I'm good to go. If the right etiquette is to wait for yet another re-approval, please let me know and I'll be cognizant of that next time. My apologies if this was the wrong move, just trying to be cognizant of the reviewer burden after 2 approvals now.

Thanks a lot everyone for your time, I'm excited to merge my first codegen contribution, and hope to make many more :-) . Merging now!

@davidmrdavid davidmrdavid merged commit 90e0933 into llvm:main Oct 17, 2025
10 checks passed
@davidmrdavid davidmrdavid deleted the dev/dajusto/dont-asanize-catch-params-on-windows branch October 17, 2025 20:47
@mstorsjo
Copy link
Member

This testcase fails to compile in mingw environments, with libc++ as a C++ standard library - https://github.com/mstorsjo/llvm-mingw/actions/runs/18608410056/job/53074081871:

D:\a\llvm-mingw\llvm-mingw\llvm-project\compiler-rt\test\asan\TestCases\Windows\basic_exception_handling.cpp:11:32: error: no matching conversion for functional-style cast from 'const char[6]' to 'std::exception'
   11 | void throwInFunction() { throw std::exception("test2"); }
      |                                ^~~~~~~~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:75:25: note: candidate constructor not viable: no known conversion from 'const char[6]' to 'const exception' for 1st argument
   75 |   _LIBCPP_HIDE_FROM_ABI exception(const exception&) _NOEXCEPT            = default;
      |                         ^         ~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:74:25: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
   74 |   _LIBCPP_HIDE_FROM_ABI exception() _NOEXCEPT {}
      |                         ^
D:\a\llvm-mingw\llvm-mingw\llvm-project\compiler-rt\test\asan\TestCases\Windows\basic_exception_handling.cpp:16:11: error: no matching conversion for functional-style cast from 'const char[6]' to 'std::exception'
   16 |     throw std::exception("test1");
      |           ^~~~~~~~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:75:25: note: candidate constructor not viable: no known conversion from 'const char[6]' to 'const exception' for 1st argument
   75 |   _LIBCPP_HIDE_FROM_ABI exception(const exception&) _NOEXCEPT            = default;
      |                         ^         ~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:74:25: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
   74 |   _LIBCPP_HIDE_FROM_ABI exception() _NOEXCEPT {}
      |                         ^
2 errors generated.

Looking at https://en.cppreference.com/w/cpp/error/exception/exception.html, I don't see any std::exception constructor taking a const char* parameter.

@davidmrdavid
Copy link
Contributor Author

davidmrdavid commented Oct 19, 2025

Thanks for letting me know, @mstorsjo.

I see from your link the following note:

The Microsoft implementation includes non-standard constructors taking strings thus allowing instances to be thrown directly with a meaningful error message. The nearest standard equivalents are std::runtime_error or std::logic_error.

from: https://en.cppreference.com/w/cpp/error/exception/exception.html

I must have been implicitly relying on that MSVC behavior, as I was working on an "x64 Visual Studio native tools" shell for my testing. My apologies.

Question: does the LLVM CI not use mingw (or clang's libc)? , I'm just trying to understand why this didn't get caught in the PR CI. I could use some guidance understanding that better, to avoid this in the future.

Anyways, on Monday morning (traveling at the moment) I can submit a PR to remediate the situation. Either by relying on std::runtime_error, or simply by removing the runtime test, which should be unnecessary given the .ll test.

If anyone wants to remediate this sooner that Monday morning PDT: removing the .cpp test file introduced in this PR should suffice ^.

@davidmrdavid
Copy link
Contributor Author

davidmrdavid commented Oct 19, 2025

Ah, I found enough time to whip up a quick fix PR. Here it goes: #164137 (comment)

I still would like to understand why this we didn't catch in the PR's CI. I'd love to help improve that, for stability :). I'll have to investigate deeper later, but if anyone knows the answer, that'd be helpful to me. Thanks!

thurstond pushed a commit that referenced this pull request Oct 19, 2025
…/TestCases/Windows/basic_exception_handling.cpp` (#164137)

**Follow up to:** #159618

**Context**

The linked PR ^ introduced a new test to ensure that ASan on Windows no
longer instruments catch-parameters. This test used a non-standard
constructor of `std::exception`,one that accepted strings as their
input, which _somehow_ passed the PR CI but would fail to compile on
mingw with libc++.

This was originally reported by @mstorsjo, in this comment:
```
This testcase fails to compile in mingw environments, with libc++ as a C++ standard library - https://github.com/mstorsjo/llvm-mingw/actions/runs/18608410056/job/53074081871:

D:\a\llvm-mingw\llvm-mingw\llvm-project\compiler-rt\test\asan\TestCases\Windows\basic_exception_handling.cpp:11:32: error: no matching conversion for functional-style cast from 'const char[6]' to 'std::exception'
   11 | void throwInFunction() { throw std::exception("test2"); }
      |                                ^~~~~~~~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:75:25: note: candidate constructor not viable: no known conversion from 'const char[6]' to 'const exception' for 1st argument
   75 |   _LIBCPP_HIDE_FROM_ABI exception(const exception&) _NOEXCEPT            = default;
      |                         ^         ~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:74:25: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
   74 |   _LIBCPP_HIDE_FROM_ABI exception() _NOEXCEPT {}
      |                         ^
D:\a\llvm-mingw\llvm-mingw\llvm-project\compiler-rt\test\asan\TestCases\Windows\basic_exception_handling.cpp:16:11: error: no matching conversion for functional-style cast from 'const char[6]' to 'std::exception'
   16 |     throw std::exception("test1");
      |           ^~~~~~~~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:75:25: note: candidate constructor not viable: no known conversion from 'const char[6]' to 'const exception' for 1st argument
   75 |   _LIBCPP_HIDE_FROM_ABI exception(const exception&) _NOEXCEPT            = default;
      |                         ^         ~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:74:25: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
   74 |   _LIBCPP_HIDE_FROM_ABI exception() _NOEXCEPT {}
      |                         ^
2 errors generated.
Looking at https://en.cppreference.com/w/cpp/error/exception/exception.html, I don't see any std::exception constructor taking a const char* parameter.
```

_from:_
#159618 (comment)

**This PR** adjusts the faulty test case to rely on
`std::runtime_error`, which contains a standard constructor accepting a
string. This should suffice to make the test pass on mingw. I tested
this on godbolt: https://godbolt.org/z/M4hPv5Wvx
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Oct 19, 2025
…t/test/asan/TestCases/Windows/basic_exception_handling.cpp` (#164137)

**Follow up to:** #159618

**Context**

The linked PR ^ introduced a new test to ensure that ASan on Windows no
longer instruments catch-parameters. This test used a non-standard
constructor of `std::exception`,one that accepted strings as their
input, which _somehow_ passed the PR CI but would fail to compile on
mingw with libc++.

This was originally reported by @mstorsjo, in this comment:
```
This testcase fails to compile in mingw environments, with libc++ as a C++ standard library - https://github.com/mstorsjo/llvm-mingw/actions/runs/18608410056/job/53074081871:

D:\a\llvm-mingw\llvm-mingw\llvm-project\compiler-rt\test\asan\TestCases\Windows\basic_exception_handling.cpp:11:32: error: no matching conversion for functional-style cast from 'const char[6]' to 'std::exception'
   11 | void throwInFunction() { throw std::exception("test2"); }
      |                                ^~~~~~~~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:75:25: note: candidate constructor not viable: no known conversion from 'const char[6]' to 'const exception' for 1st argument
   75 |   _LIBCPP_HIDE_FROM_ABI exception(const exception&) _NOEXCEPT            = default;
      |                         ^         ~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:74:25: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
   74 |   _LIBCPP_HIDE_FROM_ABI exception() _NOEXCEPT {}
      |                         ^
D:\a\llvm-mingw\llvm-mingw\llvm-project\compiler-rt\test\asan\TestCases\Windows\basic_exception_handling.cpp:16:11: error: no matching conversion for functional-style cast from 'const char[6]' to 'std::exception'
   16 |     throw std::exception("test1");
      |           ^~~~~~~~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:75:25: note: candidate constructor not viable: no known conversion from 'const char[6]' to 'const exception' for 1st argument
   75 |   _LIBCPP_HIDE_FROM_ABI exception(const exception&) _NOEXCEPT            = default;
      |                         ^         ~~~~~~~~~~~~~~~~
C:/llvm-mingw/include/c++/v1/__exception/exception.h:74:25: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
   74 |   _LIBCPP_HIDE_FROM_ABI exception() _NOEXCEPT {}
      |                         ^
2 errors generated.
Looking at https://en.cppreference.com/w/cpp/error/exception/exception.html, I don't see any std::exception constructor taking a const char* parameter.
```

_from:_
llvm/llvm-project#159618 (comment)

**This PR** adjusts the faulty test case to rely on
`std::runtime_error`, which contains a standard constructor accepting a
string. This should suffice to make the test pass on mingw. I tested
this on godbolt: https://godbolt.org/z/M4hPv5Wvx
@mstorsjo
Copy link
Member

Thanks for the prompt fix!

Btw, as a small side note - while it's good with backreferences to who reported what, having an @username reference in github commits causes a lot of spam when the commits get pushed to other downstream repos - see https://discourse.llvm.org/t/forbidding-username-in-commits/86997 for discussion on that.

Question: does the LLVM CI not use mingw (or clang's libc)? , I'm just trying to understand why this didn't get caught in the PR CI. I could use some guidance understanding that better, to avoid this in the future.

The LLVM premerge CI only tests 2 configurations, a plain linux and plain windows configuration. I'm not even sure if compiler-rt tests are ran in that config at all, or if they are ran in both configurations. (If you did hit CI errors in earlier incarnations of the patch - we'd know that it does get ran in some form at least :-) )

As LLVM supports targeting many dozens of different targets (both architectures, OSes and environments within those OSes), we don't have test coverage for all of them in the premerge CI (which needs to be quick) - most of the other targets are covered in various buildbots. For mingw, I don't have any HW of my own (as an individual volunteer contributor) that I could set up for such a buildbot, but I run nightly test runs on github actions, where I catch some breakage later on at least.

But if it is relevant to test things more widely, it's somewhat easy to do through github actions. See mstorsjo@gha-mingw-compiler-rt - there I have a commit which adds an extra test job, which gets executed on push to a branch (it doesn't even need to be part of an opened PR, you can just push it to a branch in your fork - although you may need to enable github actions for your fork). See https://github.com/mstorsjo/llvm-project/actions/runs/18630556946 for a recent test run. That runs tests for both i686, x86_64 and aarch64. Note that this configuration just uses the latest successful nightly llvm-mingw build, it doesn't test building a new compiler, so it might not work perfectly if the compiler-rt test changes require a matchingly updated compiler though. (Building a new compiler from scratch takes 2-3 hours, while this test in this form runs in ~10-15 minutes.)

@davidmrdavid
Copy link
Contributor Author

Btw, as a small side note - while it's good with backreferences to who reported what, having an @username reference in github commits causes a lot of spam when the commits get pushed to other downstream repos

Thanks for letting me know, and my apologies for the spam. I'll reply on that Discourse thread to see if we can introduce automate checks to protect against this.

But if it is relevant to test things more widely, it's somewhat easy to do through github actions. See mstorsjo@gha-mingw-compiler-rt - there I have a commit [...]

Thanks for the pointers, this is great I'll look to set up something similar for future pushes. Thanks!

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.

6 participants