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"