Merge changes Ib9fafbac,I05d73734,I0e95b0cb
* changes:
ART: Add JNI API
ART: Align jvmti.h with jni.h
ART: Add JNI function table manipulation
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index 9116097..e05a85a 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -46,6 +46,7 @@
ReaderWriterMutex* Locks::heap_bitmap_lock_ = nullptr;
Mutex* Locks::instrument_entrypoints_lock_ = nullptr;
Mutex* Locks::intern_table_lock_ = nullptr;
+Mutex* Locks::jni_function_table_lock_ = nullptr;
Mutex* Locks::jni_libraries_lock_ = nullptr;
Mutex* Locks::logging_lock_ = nullptr;
Mutex* Locks::mem_maps_lock_ = nullptr;
@@ -957,6 +958,7 @@
DCHECK(verifier_deps_lock_ != nullptr);
DCHECK(host_dlopen_handles_lock_ != nullptr);
DCHECK(intern_table_lock_ != nullptr);
+ DCHECK(jni_function_table_lock_ != nullptr);
DCHECK(jni_libraries_lock_ != nullptr);
DCHECK(logging_lock_ != nullptr);
DCHECK(mutator_lock_ != nullptr);
@@ -1098,6 +1100,10 @@
DCHECK(jni_weak_globals_lock_ == nullptr);
jni_weak_globals_lock_ = new Mutex("JNI weak global reference table lock", current_lock_level);
+ UPDATE_CURRENT_LOCK_LEVEL(kJniFunctionTableLock);
+ DCHECK(jni_function_table_lock_ == nullptr);
+ jni_function_table_lock_ = new Mutex("JNI function table lock", current_lock_level);
+
UPDATE_CURRENT_LOCK_LEVEL(kAbortLock);
DCHECK(abort_lock_ == nullptr);
abort_lock_ = new Mutex("abort lock", current_lock_level, true);
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 2adeb8c..21dd437 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -68,6 +68,7 @@
kRosAllocBulkFreeLock,
kMarkSweepMarkStackLock,
kTransactionLogLock,
+ kJniFunctionTableLock,
kJniWeakGlobalsLock,
kJniGlobalsLock,
kReferenceQueueSoftReferencesLock,
@@ -698,8 +699,11 @@
// Guard accesses to the JNI Weak Global Reference table.
static Mutex* jni_weak_globals_lock_ ACQUIRED_AFTER(jni_globals_lock_);
+ // Guard accesses to the JNI function table override.
+ static Mutex* jni_function_table_lock_ ACQUIRED_AFTER(jni_weak_globals_lock_);
+
// Have an exclusive aborting thread.
- static Mutex* abort_lock_ ACQUIRED_AFTER(jni_weak_globals_lock_);
+ static Mutex* abort_lock_ ACQUIRED_AFTER(jni_function_table_lock_);
// Allow mutual exclusion when manipulating Thread::suspend_count_.
// TODO: Does the trade-off of a per-thread lock make sense?
diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc
index 5a3fafa..0148a1c 100644
--- a/runtime/jni_env_ext.cc
+++ b/runtime/jni_env_ext.cc
@@ -29,6 +29,7 @@
#include "mirror/object-inl.h"
#include "nth_caller_visitor.h"
#include "thread-inl.h"
+#include "thread_list.h"
namespace art {
@@ -37,6 +38,8 @@
static constexpr size_t kMonitorsInitial = 32; // Arbitrary.
static constexpr size_t kMonitorsMax = 4096; // Arbitrary sanity check.
+const JNINativeInterface* JNIEnvExt::table_override_ = nullptr;
+
// Checking "locals" requires the mutator lock, but at creation time we're really only interested
// in validity, which isn't changing. To avoid grabbing the mutator lock, factored out and tagged
// with NO_THREAD_SAFETY_ANALYSIS.
@@ -78,10 +81,10 @@
runtime_deleted(false),
critical(0),
monitors("monitors", kMonitorsInitial, kMonitorsMax) {
- functions = unchecked_functions = GetJniNativeInterface();
- if (vm->IsCheckJniEnabled()) {
- SetCheckJniEnabled(true);
- }
+ MutexLock mu(Thread::Current(), *Locks::jni_function_table_lock_);
+ check_jni = vm->IsCheckJniEnabled();
+ functions = GetFunctionTable(check_jni);
+ unchecked_functions = GetJniNativeInterface();
}
void JNIEnvExt::SetFunctionsToRuntimeShutdownFunctions() {
@@ -107,7 +110,12 @@
void JNIEnvExt::SetCheckJniEnabled(bool enabled) {
check_jni = enabled;
- functions = enabled ? GetCheckJniNativeInterface() : GetJniNativeInterface();
+ MutexLock mu(Thread::Current(), *Locks::jni_function_table_lock_);
+ functions = GetFunctionTable(enabled);
+ // Check whether this is a no-op because of override.
+ if (enabled && JNIEnvExt::table_override_ != nullptr) {
+ LOG(WARNING) << "Enabling CheckJNI after a JNIEnv function table override is not functional.";
+ }
}
void JNIEnvExt::DumpReferenceTables(std::ostream& os) {
@@ -269,4 +277,33 @@
}
}
+static void ThreadResetFunctionTable(Thread* thread, void* arg ATTRIBUTE_UNUSED)
+ REQUIRES(Locks::jni_function_table_lock_) {
+ JNIEnvExt* env = thread->GetJniEnv();
+ bool check_jni = env->check_jni;
+ env->functions = JNIEnvExt::GetFunctionTable(check_jni);
+}
+
+void JNIEnvExt::SetTableOverride(const JNINativeInterface* table_override) {
+ MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+ MutexLock mu2(Thread::Current(), *Locks::jni_function_table_lock_);
+
+ JNIEnvExt::table_override_ = table_override;
+
+ // See if we have a runtime. Note: we cannot run other code (like JavaVMExt's CheckJNI install
+ // code), as we'd have to recursively lock the mutex.
+ Runtime* runtime = Runtime::Current();
+ if (runtime != nullptr) {
+ runtime->GetThreadList()->ForEach(ThreadResetFunctionTable, nullptr);
+ }
+}
+
+const JNINativeInterface* JNIEnvExt::GetFunctionTable(bool check_jni) {
+ const JNINativeInterface* override = JNIEnvExt::table_override_;
+ if (override != nullptr) {
+ return override;
+ }
+ return check_jni ? GetCheckJniNativeInterface() : GetJniNativeInterface();
+}
+
} // namespace art
diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h
index 5cca0ae..4004c45 100644
--- a/runtime/jni_env_ext.h
+++ b/runtime/jni_env_ext.h
@@ -43,7 +43,7 @@
void DumpReferenceTables(std::ostream& os)
REQUIRES_SHARED(Locks::mutator_lock_);
- void SetCheckJniEnabled(bool enabled);
+ void SetCheckJniEnabled(bool enabled) REQUIRES(!Locks::jni_function_table_lock_);
void PushFrame(int capacity) REQUIRES_SHARED(Locks::mutator_lock_);
void PopFrame() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -104,10 +104,27 @@
// Set the functions to the runtime shutdown functions.
void SetFunctionsToRuntimeShutdownFunctions();
+ // Set the function table override. This will install the override (or original table, if null)
+ // to all threads.
+ // Note: JNI function table overrides are sensitive to the order of operations wrt/ CheckJNI.
+ // After overriding the JNI function table, CheckJNI toggling is ignored.
+ static void SetTableOverride(const JNINativeInterface* table_override)
+ REQUIRES(!Locks::thread_list_lock_, !Locks::jni_function_table_lock_);
+
+ // Return either the regular, or the CheckJNI function table. Will return table_override_ instead
+ // if it is not null.
+ static const JNINativeInterface* GetFunctionTable(bool check_jni)
+ REQUIRES(Locks::jni_function_table_lock_);
+
private:
+ // Override of function tables. This applies to both default as well as instrumented (CheckJNI)
+ // function tables.
+ static const JNINativeInterface* table_override_ GUARDED_BY(Locks::jni_function_table_lock_);
+
// The constructor should not be called directly. It may leave the object in an erroneous state,
// and the result needs to be checked.
- JNIEnvExt(Thread* self, JavaVMExt* vm, std::string* error_msg);
+ JNIEnvExt(Thread* self, JavaVMExt* vm, std::string* error_msg)
+ REQUIRES(!Locks::jni_function_table_lock_);
// All locked objects, with the (Java caller) stack frame that locked them. Used in CheckJNI
// to ensure that only monitors locked in this native frame are being unlocked, and that at
diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc
index 4da5e23..08d1eeb 100644
--- a/runtime/jni_internal_test.cc
+++ b/runtime/jni_internal_test.cc
@@ -2346,4 +2346,39 @@
EXPECT_EQ(segment_state_now, segment_state_computed);
}
+static size_t gGlobalRefCount = 0;
+static const JNINativeInterface* gOriginalEnv = nullptr;
+
+static jobject CountNewGlobalRef(JNIEnv* env, jobject o) {
+ ++gGlobalRefCount;
+ return gOriginalEnv->NewGlobalRef(env, o);
+}
+
+// Test the table override.
+TEST_F(JniInternalTest, JNIEnvExtTableOverride) {
+ JNINativeInterface env_override;
+ memcpy(&env_override, env_->functions, sizeof(JNINativeInterface));
+
+ gOriginalEnv = env_->functions;
+ env_override.NewGlobalRef = CountNewGlobalRef;
+ gGlobalRefCount = 0;
+
+ jclass local = env_->FindClass("java/lang/Object");
+ ASSERT_TRUE(local != nullptr);
+
+ // Set the table, add a global ref, see whether the counter increases.
+ JNIEnvExt::SetTableOverride(&env_override);
+
+ jobject global = env_->NewGlobalRef(local);
+ EXPECT_EQ(1u, gGlobalRefCount);
+ env_->DeleteGlobalRef(global);
+
+ // Reset
+ JNIEnvExt::SetTableOverride(nullptr);
+
+ jobject global2 = env_->NewGlobalRef(local);
+ EXPECT_EQ(1u, gGlobalRefCount);
+ env_->DeleteGlobalRef(global2);
+}
+
} // namespace art
diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp
index 081c429..d5c6520 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -23,6 +23,7 @@
"ti_class.cc",
"ti_field.cc",
"ti_heap.cc",
+ "ti_jni.cc",
"ti_method.cc",
"ti_monitor.cc",
"ti_object.cc",
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index ab4a48b..f5020ff 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -51,6 +51,7 @@
#include "ti_class.h"
#include "ti_field.h"
#include "ti_heap.h"
+#include "ti_jni.h"
#include "ti_method.h"
#include "ti_monitor.h"
#include "ti_object.h"
@@ -802,11 +803,11 @@
}
static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table) {
- return ERR(NOT_IMPLEMENTED);
+ return JNIUtil::SetJNIFunctionTable(env, function_table);
}
static jvmtiError GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table) {
- return ERR(NOT_IMPLEMENTED);
+ return JNIUtil::GetJNIFunctionTable(env, function_table);
}
// TODO: This will require locking, so that an agent can't remove callbacks when we're dispatching
diff --git a/runtime/openjdkjvmti/jvmti.h b/runtime/openjdkjvmti/jvmti.h
index ee708cb..de07c16 100644
--- a/runtime/openjdkjvmti/jvmti.h
+++ b/runtime/openjdkjvmti/jvmti.h
@@ -74,7 +74,7 @@
typedef jlong jlocation;
struct _jrawMonitorID;
typedef struct _jrawMonitorID *jrawMonitorID;
-typedef struct JNINativeInterface_ jniNativeInterface;
+typedef struct JNINativeInterface jniNativeInterface;
/* Constants */
diff --git a/runtime/openjdkjvmti/ti_jni.cc b/runtime/openjdkjvmti/ti_jni.cc
new file mode 100644
index 0000000..88f0395
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_jni.cc
@@ -0,0 +1,91 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_jni.h"
+
+#include "jni.h"
+
+#include "art_jvmti.h"
+#include "base/mutex.h"
+#include "java_vm_ext.h"
+#include "jni_env_ext.h"
+#include "runtime.h"
+#include "thread-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError JNIUtil::SetJNIFunctionTable(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ const jniNativeInterface* function_table) {
+ // While we supporting setting null (which will reset the table), the spec says no.
+ if (function_table == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ art::JNIEnvExt::SetTableOverride(function_table);
+ return ERR(NONE);
+}
+
+jvmtiError JNIUtil::GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table) {
+ if (function_table == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+
+ // We use the generic JNIEnvExt::GetFunctionTable instead of querying a specific JNIEnv, as
+ // this has to work in the start phase.
+
+ // Figure out which table is current. Conservatively assume check-jni is off.
+ bool check_jni = false;
+ art::Runtime* runtime = art::Runtime::Current();
+ if (runtime != nullptr && runtime->GetJavaVM() != nullptr) {
+ check_jni = runtime->GetJavaVM()->IsCheckJniEnabled();
+ }
+
+ // Get that table.
+ const JNINativeInterface* current_table;
+ {
+ art::MutexLock mu(art::Thread::Current(), *art::Locks::jni_function_table_lock_);
+ current_table = art::JNIEnvExt::GetFunctionTable(check_jni);
+ }
+
+ // Allocate memory and copy the table.
+ unsigned char* data;
+ jvmtiError data_result = env->Allocate(sizeof(JNINativeInterface), &data);
+ if (data_result != ERR(NONE)) {
+ return data_result;
+ }
+ memcpy(data, current_table, sizeof(JNINativeInterface));
+
+ *function_table = reinterpret_cast<JNINativeInterface*>(data);
+
+ return ERR(NONE);
+}
+
+} // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_jni.h b/runtime/openjdkjvmti/ti_jni.h
new file mode 100644
index 0000000..906aab0
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_jni.h
@@ -0,0 +1,58 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+// Note: Currently, JNI function table changes are sensitive to the order of operations wrt/
+// CheckJNI. If an agent sets the function table, and a program than late-enables CheckJNI,
+// CheckJNI will not be working (as the agent will forward to the non-CheckJNI table).
+//
+// This behavior results from our usage of the function table to avoid a check of the
+// CheckJNI flag. A future implementation may install on loading of this plugin an
+// intermediate function table that explicitly checks the flag, so that switching CheckJNI
+// is transparently handled.
+
+class JNIUtil {
+ public:
+ static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table);
+
+ static jvmtiError GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table);
+};
+
+} // namespace openjdkjvmti
+
+#endif // ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_
diff --git a/test/928-jni-table/build b/test/928-jni-table/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/928-jni-table/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-build "$@" --experimental agents
diff --git a/test/928-jni-table/expected.txt b/test/928-jni-table/expected.txt
new file mode 100644
index 0000000..a965a70
--- /dev/null
+++ b/test/928-jni-table/expected.txt
@@ -0,0 +1 @@
+Done
diff --git a/test/928-jni-table/info.txt b/test/928-jni-table/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/928-jni-table/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/928-jni-table/jni_table.cc b/test/928-jni-table/jni_table.cc
new file mode 100644
index 0000000..5123d3a
--- /dev/null
+++ b/test/928-jni-table/jni_table.cc
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 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 <stdio.h>
+
+#include "jni.h"
+#include "openjdkjvmti/jvmti.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test927JNITable {
+
+// This test is equivalent to the jni_internal_test JNIEnvExtTableOverride.
+
+static size_t gGlobalRefCount = 0;
+static JNINativeInterface* gOriginalEnv = nullptr;
+
+static jobject CountNewGlobalRef(JNIEnv* env, jobject o) {
+ ++gGlobalRefCount;
+ return gOriginalEnv->NewGlobalRef(env, o);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_doJNITableTest(
+ JNIEnv* env, jclass klass) {
+ // Get the current table, as the delegate.
+ jvmtiError getorig_result = jvmti_env->GetJNIFunctionTable(&gOriginalEnv);
+ if (JvmtiErrorToException(env, getorig_result)) {
+ return;
+ }
+
+ // Get the current table, as the override we'll install.
+ JNINativeInterface* env_override;
+ jvmtiError getoverride_result = jvmti_env->GetJNIFunctionTable(&env_override);
+ if (JvmtiErrorToException(env, getoverride_result)) {
+ return;
+ }
+
+ env_override->NewGlobalRef = CountNewGlobalRef;
+ gGlobalRefCount = 0;
+
+ // Install the override.
+ jvmtiError setoverride_result = jvmti_env->SetJNIFunctionTable(env_override);
+ if (JvmtiErrorToException(env, setoverride_result)) {
+ return;
+ }
+
+ jobject global = env->NewGlobalRef(klass);
+ CHECK_EQ(1u, gGlobalRefCount);
+ env->DeleteGlobalRef(global);
+
+ // Install the "original." There is no real reset.
+ jvmtiError setoverride2_result = jvmti_env->SetJNIFunctionTable(gOriginalEnv);
+ if (JvmtiErrorToException(env, setoverride2_result)) {
+ return;
+ }
+
+ jobject global2 = env->NewGlobalRef(klass);
+ CHECK_EQ(1u, gGlobalRefCount);
+ env->DeleteGlobalRef(global2);
+
+ // Try to install null. Should return NULL_POINTER error.
+ jvmtiError setoverride3_result = jvmti_env->SetJNIFunctionTable(nullptr);
+ if (setoverride3_result != JVMTI_ERROR_NULL_POINTER) {
+ LOG(FATAL) << "Didn't receive NULL_POINTER";
+ }
+
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(env_override));
+}
+
+} // namespace Test927JNITable
+} // namespace art
diff --git a/test/928-jni-table/run b/test/928-jni-table/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/928-jni-table/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-run "$@" --experimental agents \
+ --experimental runtime-plugins \
+ --jvmti
diff --git a/test/928-jni-table/src/Main.java b/test/928-jni-table/src/Main.java
new file mode 100644
index 0000000..b0baea1
--- /dev/null
+++ b/test/928-jni-table/src/Main.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[1]);
+
+ doJNITableTest();
+
+ System.out.println("Done");
+ }
+
+ public static native void doJNITableTest();
+}
diff --git a/test/Android.bp b/test/Android.bp
index a1b283d..965d07a 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -267,6 +267,7 @@
"924-threads/threads.cc",
"925-threadgroups/threadgroups.cc",
"927-timers/timers.cc",
+ "928-jni-table/jni_table.cc",
"929-search/search.cc",
],
shared_libs: [
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 668d669..b1a0c91 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -306,6 +306,7 @@
925-threadgroups \
926-multi-obsolescence \
927-timers \
+ 928-jni-table \
929-search \
ifneq (,$(filter target,$(TARGET_TYPES)))