[MTE] test for exception cleanup.

This depends on the LLVM change https://reviews.llvm.org/D135639.

Test: adb shell /data/local/tmp/stack_tagging_helper exception_cleanup
Bug: 174878242
Change-Id: Ia1dfdbe482b40c174acaf6c1ac4ad054470d10b8
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index 0c9a2e0..8ae2257 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -1661,6 +1661,7 @@
      },
    },
    header_libs: ["bionic_libc_platform_headers"],
+   cflags: ["-fexceptions"],
 }
 
 cc_test {
@@ -1676,6 +1677,7 @@
      },
    },
    header_libs: ["bionic_libc_platform_headers"],
+   cflags: ["-fexceptions"],
 }
 
 cc_genrule {
diff --git a/tests/libs/stack_tagging_helper.cpp b/tests/libs/stack_tagging_helper.cpp
index a239dc1..d29844d 100644
--- a/tests/libs/stack_tagging_helper.cpp
+++ b/tests/libs/stack_tagging_helper.cpp
@@ -260,6 +260,81 @@
   CHECK(memtag_stack);
 }
 
+static uintptr_t GetTag(void* addr) {
+  return reinterpret_cast<uintptr_t>(addr) & (0xFULL << 56);
+}
+
+static uintptr_t GetTag(volatile void* addr) {
+  return GetTag(const_cast<void*>(addr));
+}
+
+static volatile char* throw_frame;
+static volatile char* skip_frame3_frame;
+volatile char *x;
+
+__attribute__((noinline)) void throws() {
+  // Prevent optimization.
+  if (getpid() == 0) return;
+  throw_frame = reinterpret_cast<char*>(__builtin_frame_address(0));
+  throw "error";
+}
+
+__attribute__((noinline)) void maybe_throws() {
+  // These are all unique sizes so in case of a failure, we can see which ones
+  // are not untagged from the tag dump.
+  volatile char y[5 * 16]= {};
+  x = y;
+  // Make sure y is tagged.
+  CHECK(GetTag(&y) != GetTag(__builtin_frame_address(0)));
+  throws();
+}
+
+__attribute__((noinline, no_sanitize("memtag"))) void skip_frame() {
+  volatile char y[6*16] = {};
+  x = y;
+  // Make sure y is not tagged.
+  CHECK(GetTag(&y) == GetTag(__builtin_frame_address(0)));
+  maybe_throws();
+}
+
+__attribute__((noinline)) void skip_frame2() {
+  volatile char y[7*16] = {};
+  x = y;
+  // Make sure y is tagged.
+  CHECK(GetTag(&y) != GetTag(__builtin_frame_address(0)));
+  skip_frame();
+}
+
+__attribute__((noinline, no_sanitize("memtag"))) void skip_frame3() {
+  volatile char y[8*16] = {};
+  x = y;
+  skip_frame3_frame = reinterpret_cast<char*>(__builtin_frame_address(0));
+  // Make sure y is not tagged.
+  CHECK(GetTag(&y) == GetTag(__builtin_frame_address(0)));
+  skip_frame2();
+}
+
+void test_exception_cleanup() {
+  // This is here for debugging purposes, if something goes wrong we can
+  // verify that this placeholder did not get untagged.
+  volatile char placeholder[16*16] = {};
+  x = placeholder;
+  try {
+    skip_frame3();
+  } catch (const char* e) {
+  }
+  if (throw_frame >= skip_frame3_frame) {
+    fprintf(stderr, "invalid throw frame");
+    exit(1);
+  }
+  for (char* b = const_cast<char*>(throw_frame); b < skip_frame3_frame; ++b) {
+    if (mte_get_tag(b) != b) {
+      fprintf(stderr, "invalid tag at %p", b);
+      exit(1);
+    }
+  }
+}
+
 int main(int argc, char** argv) {
   if (argc < 2) {
     printf("nothing to do\n");
@@ -296,6 +371,11 @@
     return 0;
   }
 
+  if (strcmp(argv[1], "exception_cleanup") == 0) {
+    test_exception_cleanup();
+    return 0;
+  }
+
   printf("unrecognized command: %s\n", argv[1]);
   return 1;
 }