Add 2048-bad-native-registry run test

This allows us to check for reasonable behavior in the event of a
NativeAllocationRegistry with a nonterminative native free function.

Test: art/test/testrunner/testrunner.py --host -b --64 -t
Test:     2048-bad-native-registry
Test: Manually inspected 32- and 64-bit output.
Bug: 279677364
Change-Id: Id0d4b275f5a5090feed78971baf022dbace7426a
diff --git a/test/2048-bad-native-registry/build.py b/test/2048-bad-native-registry/build.py
new file mode 100644
index 0000000..7025b81
--- /dev/null
+++ b/test/2048-bad-native-registry/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def build(ctx):
+  if ctx.jvm:
+    return  # The test does not build on JVM
+  ctx.default_build()
diff --git a/test/2048-bad-native-registry/expected-stderr.txt b/test/2048-bad-native-registry/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2048-bad-native-registry/expected-stderr.txt
diff --git a/test/2048-bad-native-registry/expected-stdout.txt b/test/2048-bad-native-registry/expected-stdout.txt
new file mode 100644
index 0000000..48a6058
--- /dev/null
+++ b/test/2048-bad-native-registry/expected-stdout.txt
@@ -0,0 +1,6 @@
+JNI_OnLoad called
+Returning bad finalizer: 0xN
+Returning placeholder object: 0xN
+About to null reference.
+Native finalizer looping
+TimeoutException on FinalizerWatchdogDaemon:ReferenceQueueDaemon timed out while targeting libcore.util.NativeAllocationRegistry$CleanerThunk@N(freeFunction = 0xN, nativePtr = 0xN, size = 666)
diff --git a/test/2048-bad-native-registry/info.txt b/test/2048-bad-native-registry/info.txt
new file mode 100644
index 0000000..79f3192
--- /dev/null
+++ b/test/2048-bad-native-registry/info.txt
@@ -0,0 +1,9 @@
+This is an adaptation of 030-bad-finalizer and 2041-bad-cleaner
+to Java 8 Cleaners created indirectly via NativeAllocationRegistry. These
+run directly in the ReferenceQueueDaemon. The native finalizer never finishes.
+ART is expected to detect this situation and abort the VM (so you should
+see a message to that effect in the log output).
+
+We have this test in addition to 2041-bad-cleaner mostly to test that the output
+includes useful information in this important case. This is also the only test
+of the toString() method provided by that Runnable.
diff --git a/test/2048-bad-native-registry/native_finalizer.cc b/test/2048-bad-native-registry/native_finalizer.cc
new file mode 100644
index 0000000..14b0460
--- /dev/null
+++ b/test/2048-bad-native-registry/native_finalizer.cc
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+
+#include <cstdint>
+#include <stdio.h>
+#include <unistd.h>
+
+namespace art {
+
+static int nativeObj;
+
+static void BadNativeFinalizer(void *p) {
+  if (p != &nativeObj) {
+    printf("Finalizer was passed unexpected argument: %p, not %p\n", p, &nativeObj);
+  }
+  printf("Native finalizer looping\n");
+  volatile bool always_true = true;
+  while (always_true) { sleep(1); }
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_Main_getBadFreeFunction(JNIEnv*, jclass) {
+  printf("Returning bad finalizer: %p\n", &BadNativeFinalizer);  // Delete for comparison.
+  return reinterpret_cast<uintptr_t>(&BadNativeFinalizer);
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_Main_getNativeObj(JNIEnv*, jclass) {
+  printf("Returning placeholder object: %p\n", &nativeObj);
+  return reinterpret_cast<uintptr_t>(&nativeObj);
+}
+
+}  // namespace art
diff --git a/test/2048-bad-native-registry/run.py b/test/2048-bad-native-registry/run.py
new file mode 100644
index 0000000..f5e6e21
--- /dev/null
+++ b/test/2048-bad-native-registry/run.py
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def run(ctx, args):
+  ctx.default_run(args, android_log_tags="*:f", expected_exit_code=2)
+  # Do not compare addresses, so replace hex numbers with 'N'.
+  ctx.run(fr"sed -i 's/0x[0-9a-f][0-9a-f]*/0xN/g' '{args.stdout_file}'")
+  ctx.run(fr"sed -i 's/@[0-9a-f][0-9a-f]*/@N/g' '{args.stdout_file}'")
diff --git a/test/2048-bad-native-registry/src/Main.java b/test/2048-bad-native-registry/src/Main.java
new file mode 100644
index 0000000..10905fa
--- /dev/null
+++ b/test/2048-bad-native-registry/src/Main.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import dalvik.system.VMRuntime;
+import java.util.concurrent.CountDownLatch;
+import libcore.util.NativeAllocationRegistry;
+import java.util.concurrent.TimeoutException;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+/**
+ * Test a class with a bad finalizer.
+ *
+ * This test is inherently flaky. It assumes that the system will schedule the finalizer daemon
+ * and finalizer watchdog daemon enough to reach the timeout and throwing the fatal exception.
+ * This uses somewhat simpler logic than 2041-bad-cleaner, since the handshake implemented there
+ * is harder to replicate here. We bump up the timeout below a bit to compensate.
+ */
+public class Main {
+    public static void main(String[] args) throws Exception {
+        System.loadLibrary(args[0]);
+        ClassLoader cl = Main.class.getClassLoader();
+        NativeAllocationRegistry registry =
+            NativeAllocationRegistry.createNonmalloced(cl, getBadFreeFunction(), 666);
+        // Replace the global uncaught exception handler, so the exception shows up on stdout.
+        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+            public void uncaughtException(Thread thread, Throwable e) {
+                if (e instanceof TimeoutException) {
+                    System.out.println("TimeoutException on "
+                        + thread.getName() + ":" + e.getMessage());
+                } else {
+                    System.out.println("Unexpected exception " + e);
+                }
+                System.exit(2);
+            }
+        });
+        // A separate method to ensure no dex register keeps the object alive.
+        createBadCleanable(registry);
+
+        // Should have at least two iterations to trigger finalization, but just to make sure run
+        // some more.
+        for (int i = 0; i < 5; i++) {
+            Runtime.getRuntime().gc();
+        }
+
+        // Now fall asleep with a timeout. The timeout is large enough that we expect the
+        // finalizer daemon to have killed the process before the deadline elapses.
+        // The timeout is also large enough to cover the extra 5 seconds we wait
+        // to dump threads, plus potentially substantial gcstress overhead.
+        // The RQ timeout is currently effectively 5 * the finalizer timeout.
+        // Note: the timeout is here (instead of an infinite sleep) to protect the test
+        //       environment (e.g., in case this is run without a timeout wrapper).
+        final long timeout = 150 * 1000 + 5 * VMRuntime.getRuntime().getFinalizerTimeoutMs();
+        long remainingWait = timeout;
+        final long waitStart = System.currentTimeMillis();
+        while (remainingWait > 0) {
+            synchronized (args) {  // Just use an already existing object for simplicity...
+                try {
+                    args.wait(remainingWait);
+                } catch (Exception e) {
+                    System.out.println("UNEXPECTED EXCEPTION");
+                }
+            }
+            remainingWait = timeout - (System.currentTimeMillis() - waitStart);
+        }
+
+        // We should not get here.
+        System.out.println("UNREACHABLE");
+        System.exit(0);
+    }
+
+    private static void createBadCleanable(NativeAllocationRegistry registry) {
+        Object badCleanable = new Object();
+        long nativeObj = getNativeObj();
+        registry.registerNativeAllocation(badCleanable, nativeObj);
+
+        System.out.println("About to null reference.");
+        badCleanable = null;  // Not that this would make a difference, could be eliminated earlier.
+    }
+
+    private static native long getNativeObj();
+    private static native long getBadFreeFunction();
+
+}
diff --git a/test/2048-bad-native-registry/test-metadata.json b/test/2048-bad-native-registry/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2048-bad-native-registry/test-metadata.json
@@ -0,0 +1,5 @@
+{
+  "build-param": {
+    "jvm-supported": "false"
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 6bc5990..836beb8 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -989,6 +989,7 @@
         "2036-jni-filechannel/jni_filechannel.cc",
         "2037-thread-name-inherit/thread_name_inherit.cc",
         "2040-huge-native-alloc/huge_native_buf.cc",
+        "2048-bad-native-registry/native_finalizer.cc",
         "2235-JdkUnsafeTest/unsafe_test.cc",
 	"2262-miranda-methods/jni_invoke.cc",
         "common/runtime_state.cc",
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 7782cee..0455db4 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -373,7 +373,8 @@
                   "030-bad-finalizer",
                   "080-oom-throw",
                   "1336-short-finalizer-timeout",
-                  "2041-bad-cleaner"],
+                  "2041-bad-cleaner",
+                  "2048-bad-native-registry"],
         "bug": "http://b/36377828",
         "variant": "interp-ac"
     },
@@ -1074,6 +1075,7 @@
           "1946-list-descriptors",
           "1947-breakpoint-redefine-deopt",
           "2041-bad-cleaner",
+          "2048-bad-native-registry",
           "2230-profile-save-hotness",
           "2245-checker-smali-instance-of-comparison",
           "2251-checker-irreducible-loop-do-not-inline"