Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Coverage] Add testing to validate code coverage for exceptions #133463

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 20, 2025

Conversation

justincady
Copy link
Contributor

@justincady justincady commented Mar 28, 2025

While investigating an issue with code coverage reporting around
exceptions it was useful to have a baseline of what works today.

This change adds end-to-end testing to validate code coverage behavior
that is currently working with regards to exception handling.

@llvmbot llvmbot added clang Clang issues not falling into any other category compiler-rt clang:codegen IR generation bugs: mangling, exceptions, etc. PGO Profile Guided Optimizations labels Mar 28, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 28, 2025

@llvm/pr-subscribers-pgo

@llvm/pr-subscribers-clang-codegen

Author: Justin Cady (justincady)

Changes

In cases where a terminating statement exists within the try or catch
block the coverage mapping can be incorrect. Coverage reports display
lines following the last catch block as uncovered, when the lines have
been executed.

This change adjusts the mapping such that an extra region is only
created after a try block if the try itself contains the terminating
statement.

The testing validates coverage more broadly than the above to ensure
other mapping around exception handling does not regress with this
change.


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

3 Files Affected:

  • (modified) clang/lib/CodeGen/CoverageMappingGen.cpp (+8-2)
  • (modified) clang/test/CoverageMapping/trycatch.cpp (+2-2)
  • (added) compiler-rt/test/profile/Linux/coverage-exception.cpp (+142)
diff --git a/clang/lib/CodeGen/CoverageMappingGen.cpp b/clang/lib/CodeGen/CoverageMappingGen.cpp
index 73811d15979d5..f3b819bcf3b20 100644
--- a/clang/lib/CodeGen/CoverageMappingGen.cpp
+++ b/clang/lib/CodeGen/CoverageMappingGen.cpp
@@ -2123,11 +2123,17 @@ struct CounterCoverageMappingBuilder
     Counter ParentCount = getRegion().getCounter();
     propagateCounts(ParentCount, S->getTryBlock());
 
+    bool TryHasTerminateStmt = HasTerminateStmt;
+
     for (unsigned I = 0, E = S->getNumHandlers(); I < E; ++I)
       Visit(S->getHandler(I));
 
-    Counter ExitCount = getRegionCounter(S);
-    pushRegion(ExitCount);
+    if (TryHasTerminateStmt) {
+      Counter ExitCount = getRegionCounter(S);
+      pushRegion(ExitCount);
+    }
+
+    HasTerminateStmt = TryHasTerminateStmt;
   }
 
   void VisitCXXCatchStmt(const CXXCatchStmt *S) {
diff --git a/clang/test/CoverageMapping/trycatch.cpp b/clang/test/CoverageMapping/trycatch.cpp
index 89fae8af9b720..e5d2482c6b151 100644
--- a/clang/test/CoverageMapping/trycatch.cpp
+++ b/clang/test/CoverageMapping/trycatch.cpp
@@ -33,5 +33,5 @@ int main() {                          // CHECK-NEXT: File 0, [[@LINE]]:12 -> [[@
   catch(const Warning &w) {           // CHECK-NEXT: File 0, [[@LINE]]:27 -> [[@LINE+2]]:4 = #4
     j = 0;
   }
-  return 0;                           // CHECK-NEXT: File 0, [[@LINE]]:3 -> [[@LINE]]:11 = #1
-}
+  return 0;                           // CHECK-NOT: File 0
+}                                     // CHECK-NOT: File 0
diff --git a/compiler-rt/test/profile/Linux/coverage-exception.cpp b/compiler-rt/test/profile/Linux/coverage-exception.cpp
new file mode 100644
index 0000000000000..c12303363fbb6
--- /dev/null
+++ b/compiler-rt/test/profile/Linux/coverage-exception.cpp
@@ -0,0 +1,142 @@
+// REQUIRES: lld-available
+// XFAIL: powerpc64-target-arch
+
+// RUN: %clangxx_profgen -std=c++17 -fuse-ld=lld -fcoverage-mapping -o %t %s
+// RUN: env LLVM_PROFILE_FILE=%t.profraw %run %t
+// RUN: llvm-profdata merge -o %t.profdata %t.profraw
+// RUN: llvm-cov show %t -instr-profile=%t.profdata 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define TRY_AND_CATCH_ALL(x)                                                   \
+  try {                                                                        \
+    (x);                                                                       \
+  } catch (...) {                                                              \
+  }
+
+#define TRY_MAYBE_CRASH(x)                                                     \
+  try {                                                                        \
+    if ((x)) {                                                                 \
+      printf("no crash\n");                                                    \
+    } else {                                                                   \
+      abort();                                                                 \
+    }                                                                          \
+  } catch (...) {                                                              \
+  }
+
+#define TRY_AND_CATCH_CRASHES(x)                                               \
+  try {                                                                        \
+    (x);                                                                       \
+  } catch (...) {                                                              \
+    abort();                                                                   \
+  }
+
+// clang-format off
+static
+int test_no_exception() {           // CHECK:  [[@LINE]]| 1|int test_no_exception()
+  int i = 0;                        // CHECK:  [[@LINE]]| 1|  int i
+  try {                             // CHECK:  [[@LINE]]| 1|  try {
+    i = 1;                          // CHECK:  [[@LINE]]| 1|    i =
+  } catch (...) {                   // CHECK:  [[@LINE]]| 1|  } catch (
+    abort();                        // CHECK:  [[@LINE]]| 0|    abort();
+  }                                 // CHECK:  [[@LINE]]| 0|  }
+  printf("%s: %u\n", __func__, i);  // CHECK:  [[@LINE]]| 1|  printf(
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}                                   // CHECK:  [[@LINE]]| 1|}
+
+static
+int test_no_exception_macro() {     // CHECK:  [[@LINE]]| 1|int test_no_exception_macro()
+  int i = 0;                        // CHECK:  [[@LINE]]| 1|  int i
+  TRY_AND_CATCH_ALL(i = 1);         // CHECK:  [[@LINE]]| 1|  TRY_AND_CATCH_ALL(
+  printf("%s: %u\n", __func__, i);  // CHECK:  [[@LINE]]| 1|  printf(
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}                                   // CHECK:  [[@LINE]]| 1|}
+
+static
+int test_exception() {              // CHECK:  [[@LINE]]| 1|int test_exception()
+  try {                             // CHECK:  [[@LINE]]| 1|  try {
+    throw 1;                        // CHECK:  [[@LINE]]| 1|    throw
+  } catch (...) {                   // CHECK:  [[@LINE]]| 1|  } catch (
+    printf("%s\n", __func__);       // CHECK:  [[@LINE]]| 1|    printf(
+  }                                 // CHECK:  [[@LINE]]| 1|  }
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}                                   // CHECK:  [[@LINE]]| 1|}
+
+static
+int test_exception_macro() {        // CHECK:  [[@LINE]]| 1|int test_exception_macro()
+  TRY_AND_CATCH_ALL(throw 1);       // CHECK:  [[@LINE]]| 1|  TRY_AND_CATCH_ALL(
+  printf("%s\n", __func__);         // CHECK:  [[@LINE]]| 1|  printf(
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}                                   // CHECK:  [[@LINE]]| 1|}
+
+static
+int test_exception_macro_nested() { // CHECK:  [[@LINE]]| 1|int test_exception_macro_nested()
+  try {                             // CHECK:  [[@LINE]]| 1|  try {
+    TRY_AND_CATCH_ALL(throw 1);     // CHECK:  [[@LINE]]| 1|    TRY_AND_CATCH_ALL(
+  } catch (...) {                   // CHECK:  [[@LINE]]| 1|  } catch (
+    abort();                        // CHECK:  [[@LINE]]| 0|    abort();
+  }                                 // CHECK:  [[@LINE]]| 0|  }
+  printf("%s\n", __func__);         // CHECK:  [[@LINE]]| 1|  printf(
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}                                   // CHECK:  [[@LINE]]| 1|}
+
+static
+int test_exception_try_crash() {    // CHECK:  [[@LINE]]| 1|int test_exception_try_crash()
+  int i = 1;                        // CHECK:  [[@LINE]]| 1|  int i
+  TRY_MAYBE_CRASH(i);               // CHECK:  [[@LINE]]| 1|  TRY_MAYBE_CRASH(
+  printf("%s\n", __func__);         // CHECK:  [[@LINE]]| 1|  printf(
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}                                   // CHECK:  [[@LINE]]| 1|}
+
+static
+int test_exception_crash() {        // CHECK:  [[@LINE]]| 1|int test_exception_crash()
+  int i = 0;                        // CHECK:  [[@LINE]]| 1|  int i
+  TRY_AND_CATCH_CRASHES(i = 1);     // CHECK:  [[@LINE]]| 1|  TRY_AND_CATCH_CRASHES(
+  printf("%s\n", __func__);         // CHECK:  [[@LINE]]| 1|  printf(
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}                                   // CHECK:  [[@LINE]]| 1|}
+
+static
+int test_conditional(int i) {       // CHECK:  [[@LINE]]| 1|int test_conditional(int i)
+  try {                             // CHECK:  [[@LINE]]| 1|  try {
+    if (i % 2 == 0) {               // CHECK:  [[@LINE]]| 1|    if (
+      printf("%s\n", __func__);     // CHECK:  [[@LINE]]| 1|      printf(
+    } else {                        // CHECK:  [[@LINE]]| 1|    } else {
+      abort();                      // CHECK:  [[@LINE]]| 0|      abort();
+    }                               // CHECK:  [[@LINE]]| 0|    }
+  } catch (...) {                   // CHECK:  [[@LINE]]| 1|  } catch (
+    abort();                        // CHECK:  [[@LINE]]| 0|    abort();
+  }                                 // CHECK:  [[@LINE]]| 0|  }
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}
+
+static
+int test_multiple_catch() {         // CHECK:  [[@LINE]]| 1|int test_multiple_catch()
+  try {                             // CHECK:  [[@LINE]]| 1|  try {
+    throw 1;                        // CHECK:  [[@LINE]]| 1|    throw
+  } catch (double) {                // CHECK:  [[@LINE]]| 1|  } catch (double)
+    abort();                        // CHECK:  [[@LINE]]| 0|    abort();
+  } catch (int) {                   // CHECK:  [[@LINE]]| 1|  } catch (int)
+    printf("int\n");                // CHECK:  [[@LINE]]| 1|    printf(
+  } catch (float) {                 // CHECK:  [[@LINE]]| 1|  } catch (float)
+    abort();                        // CHECK:  [[@LINE]]| 0|    abort();
+  } catch (...) {                   // CHECK:  [[@LINE]]| 0|  } catch (
+    abort();                        // CHECK:  [[@LINE]]| 0|    abort();
+  }                                 // CHECK:  [[@LINE]]| 0|  }
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}                                   // CHECK:  [[@LINE]]| 1|}
+
+int main() {                        // CHECK:  [[@LINE]]| 1|int main()
+  test_no_exception();              // CHECK:  [[@LINE]]| 1|  test_no_exception(
+  test_no_exception_macro();        // CHECK:  [[@LINE]]| 1|  test_no_exception_macro(
+  test_exception();                 // CHECK:  [[@LINE]]| 1|  test_exception(
+  test_exception_macro();           // CHECK:  [[@LINE]]| 1|  test_exception_macro(
+  test_exception_macro_nested();    // CHECK:  [[@LINE]]| 1|  test_exception_macro_nested(
+  test_exception_try_crash();       // CHECK:  [[@LINE]]| 1|  test_exception_try_crash(
+  test_exception_crash();           // CHECK:  [[@LINE]]| 1|  test_exception_crash(
+  test_conditional(2);              // CHECK:  [[@LINE]]| 1|  test_conditional(
+  test_multiple_catch();            // CHECK:  [[@LINE]]| 1|  test_multiple_catch(
+  return 0;                         // CHECK:  [[@LINE]]| 1|  return
+}                                   // CHECK:  [[@LINE]]| 1|}
+// clang-format on

@justincady
Copy link
Contributor Author

In testing this against a large codebase I found an unhandled case that leads to incorrect coverage. I'll be updating the PR in the near future.

@justincady justincady force-pushed the try-terminate-cov branch from 6f79ddb to f96b527 Compare May 7, 2025 13:19
@justincady justincady changed the title [Coverage] Fix region creation after try statements [Coverage] Add testing to validate code coverage for exceptions May 7, 2025
@justincady
Copy link
Contributor Author

Further investigation revealed the true root cause, filed as #138871.

I have updated this PR to instead only add tests that are currently working, as I found it helpful to have this validation when investigating the linked issue.

@justincady justincady requested a review from evodius96 May 15, 2025 15:21
@justincady
Copy link
Contributor Author

Ping. :)

Updated the description to make it clear this is a test-only change. It just validates existing coverage behavior in anticipation of other changes.

cc @chapuni @MaskRay @ZequanWu @evodius96

Copy link
Contributor

@evodius96 evodius96 left a comment

Choose a reason for hiding this comment

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

LGTM

While investigating an issue with code coverage reporting around
exceptions it was useful to have a baseline of what works today.

This change adds end-to-end testing to validate code coverage behavior
that is currently working with regards to exception handling.
@justincady justincady force-pushed the try-terminate-cov branch from f96b527 to 40c451b Compare May 20, 2025 15:29
@justincady justincady merged commit 0931874 into llvm:main May 20, 2025
10 checks passed
kostasalv pushed a commit to kostasalv/llvm-project that referenced this pull request May 21, 2025
…#133463)

While investigating an issue with code coverage reporting around
exceptions it was useful to have a baseline of what works today.

This change adds end-to-end testing to validate code coverage behavior
that is currently working with regards to exception handling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang Clang issues not falling into any other category compiler-rt PGO Profile Guided Optimizations
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.