CTS: Add skeleton for JVMTI tests

Create a hostside CTS test for JVMTI on Android.

Add a base device test library that contains an empty activity,
a special bindings class and an instrumentation test that uses
the binding class to wait for an agent.

Add an initial agent implementation with some helpers. Add binding
code that looks for the well-known bindings class and searches for
C implementations (using the Java mangling scheme) of all native
methods found in the bindings class. Then call a well-known method
to let the Java side know that the agent has attached.

Add a host test library that implements a host test. The host test
takes an APK and package name as input from a properties file. The
host test constructs a remote runner to run the given test. When
the test run on device is started, the test will extract the agent,
which is expected to be in the device test APK, send it to the device
into the test's data directory, and call attach-agent to attach the
test.

(Revert of commit b2c00541678ad24c0a1775ebef65e43cb08dc65b)

Bug: 32072923
Test: m cts
diff --git a/hostsidetests/jvmti/Android.mk b/hostsidetests/jvmti/Android.mk
new file mode 100644
index 0000000..64fe597
--- /dev/null
+++ b/hostsidetests/jvmti/Android.mk
@@ -0,0 +1,17 @@
+# Copyright (C) 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/jvmti/base/Android.mk b/hostsidetests/jvmti/base/Android.mk
new file mode 100644
index 0000000..38e1a4f
--- /dev/null
+++ b/hostsidetests/jvmti/base/Android.mk
@@ -0,0 +1,17 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/jvmti/base/app/Android.mk b/hostsidetests/jvmti/base/app/Android.mk
new file mode 100644
index 0000000..cb1d8ba
--- /dev/null
+++ b/hostsidetests/jvmti/base/app/Android.mk
@@ -0,0 +1,28 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := CtsJvmtiDeviceAppBase
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SDK_VERSION := current
+LOCAL_DEX_PREOPT := false
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctstestrunner \
+    compatibility-device-util \
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/hostsidetests/jvmti/base/app/src/android/jvmti/JvmtiActivity.java b/hostsidetests/jvmti/base/app/src/android/jvmti/JvmtiActivity.java
new file mode 100644
index 0000000..f584952
--- /dev/null
+++ b/hostsidetests/jvmti/base/app/src/android/jvmti/JvmtiActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+package android.jvmti;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.lang.Override;
+
+/**
+ * A non-functional activity. All testing is done in the JvmtiTest class.
+ */
+public class JvmtiActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+    }
+
+}
diff --git a/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JniBindings.java b/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JniBindings.java
new file mode 100644
index 0000000..822c1aa
--- /dev/null
+++ b/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JniBindings.java
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+package android.jvmti.cts;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class that contains all bindings to JNI implementations provided by the CTS JVMTI agent.
+ */
+public class JniBindings {
+
+    private static CountDownLatch sStartWaiter = new CountDownLatch(1);
+
+    /**
+     * This method will be called by the agent to inform the test that JNI methods are bound. The
+     * counterpart is waitFor.
+     */
+    @SuppressWarnings("unused")
+    private static void startup() {
+        sStartWaiter.countDown();
+    }
+
+    public static void waitFor() {
+        try {
+            if (!sStartWaiter.await(15, TimeUnit.SECONDS)) {
+                throw new RuntimeException("Timed out waiting for the agent");
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Got interrupted waiting for agent.");
+        }
+    }
+
+    // Load the given class with the given classloader, and bind all native methods to corresponding
+    // C methods in the agent. Will abort if any of the steps fail.
+    public static native void bindAgentJNI(String className, ClassLoader classLoader);
+}
diff --git a/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JvmtiTestBase.java b/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JvmtiTestBase.java
new file mode 100644
index 0000000..b0c02cf
--- /dev/null
+++ b/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JvmtiTestBase.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+package android.jvmti.cts;
+
+import android.jvmti.JvmtiActivity;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+/**
+ * Base class for JVMTI tests. Ensures that the agent is connected for the tests. If you
+ * do not subclass this test, make sure that JniBindings.waitFor is appropriately called.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public abstract class JvmtiTestBase {
+
+    /**
+     * A reference to the activity being tested.
+     */
+    protected JvmtiActivity mActivity;
+
+    @Rule
+    public ActivityTestRule<JvmtiActivity> mActivityRule =
+            new ActivityTestRule<>(JvmtiActivity.class);
+
+    @Before
+    public void setup() {
+        mActivity = mActivityRule.getActivity();
+
+        // Make sure that the agent is ready.
+        JniBindings.waitFor();
+    }
+}
diff --git a/hostsidetests/jvmti/base/host/Android.mk b/hostsidetests/jvmti/base/host/Android.mk
new file mode 100644
index 0000000..08bcb8e
--- /dev/null
+++ b/hostsidetests/jvmti/base/host/Android.mk
@@ -0,0 +1,25 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsJvmtiHostTestBase
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SDK_VERSION := current
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt compatibility-host-util
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java b/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java
new file mode 100644
index 0000000..326ff96
--- /dev/null
+++ b/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiHostTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ */
+
+package android.jvmti.cts;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.ZipUtil;
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipFile;
+
+/**
+ * Test a JVMTI device test.
+ *
+ * Reads the configuration (APK and package name) out of the embedded config.properties file. Runs
+ * the agent (expected to be packaged with the APK) into the app's /data/data directory, starts a
+ * test run and attaches the agent.
+ */
+public class JvmtiHostTest extends DeviceTestCase implements IBuildReceiver, IAbiReceiver {
+    private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
+    private CompatibilityBuildHelper mBuildHelper;
+    private IAbi mAbi;
+    private String mTestPackageName;
+    private String mTestApk;
+
+    @Override
+    public void setBuild(IBuildInfo arg0) {
+        mBuildHelper = new CompatibilityBuildHelper(arg0);
+        mTestPackageName = arg0.getBuildAttributes().get(JvmtiPreparer.PACKAGE_NAME_ATTRIBUTE);
+        mTestApk = arg0.getBuildAttributes().get(JvmtiPreparer.APK_ATTRIBUTE);
+    }
+
+    @Override
+    public void setAbi(IAbi arg0) {
+        mAbi = arg0;
+    }
+
+    /**
+     * Tests the string was successfully logged to Logcat from the activity.
+     *
+     * @throws Exception
+     */
+    public void testJvmti() throws Exception {
+        final ITestDevice device = getDevice();
+
+        if (mTestApk == null || mTestPackageName == null) {
+            throw new IllegalStateException("Incorrect configuration");
+        }
+
+        RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mTestPackageName, RUNNER,
+                device.getIDevice());
+
+        TestResults tr = new TestResults(new AttachAgent(device, mTestPackageName, mTestApk));
+
+        device.runInstrumentationTests(runner, tr);
+
+        assertTrue(tr.getErrors(), tr.hasStarted());
+        assertFalse(tr.getErrors(), tr.hasFailed());
+    }
+
+    private class AttachAgent implements Runnable {
+        private ITestDevice mDevice;
+        private String mPkg;
+        private String mApk;
+
+        public AttachAgent(ITestDevice device, String pkg, String apk) {
+            this.mDevice = device;
+            this.mPkg = pkg;
+            this.mApk = apk;
+        }
+
+        @Override
+        public void run() {
+            File tmpFile = null;
+            ZipFile zf = null;
+            try {
+                String pwd = mDevice.executeShellCommand("run-as " + mPkg + " pwd");
+                if (pwd == null) {
+                    throw new RuntimeException("pwd failed");
+                }
+                pwd = pwd.trim();
+                if (pwd.isEmpty()) {
+                    throw new RuntimeException("pwd failed");
+                }
+
+                String agentInDataData = installLibToDataData(pwd, "libctsjvmtiagent.so");
+
+                String attachReply = mDevice
+                        .executeShellCommand("am attach-agent " + mPkg + " " + agentInDataData);
+                // Don't try to parse the output. The test will time out anyways if this didn't
+                // work.
+                if (attachReply != null && !attachReply.trim().isEmpty()) {
+                    CLog.e(attachReply);
+                }
+            } catch (Exception e) {
+                throw new RuntimeException("Failed attaching", e);
+            } finally {
+                if (tmpFile != null) {
+                    tmpFile.delete();
+                }
+                if (zf != null) {
+                    try {
+                        zf.close();
+                    } catch (Exception e) {
+                        throw new RuntimeException("ZipFile close failed", e);
+                    }
+                }
+            }
+        }
+
+        String installLibToDataData(String dataData, String library) throws Exception {
+            ZipFile zf = null;
+            File tmpFile = null;
+            try {
+                String libInDataData = dataData + "/" + library;
+
+                File apkFile = mBuildHelper.getTestFile(mApk);
+                zf = new ZipFile(apkFile);
+
+                String libPathInApk = "lib/" + mAbi.getName() + "/" + library;
+                tmpFile = ZipUtil.extractFileFromZip(zf, libPathInApk);
+
+                String libInTmp = "/data/local/tmp/" + tmpFile.getName();
+                if (!mDevice.pushFile(tmpFile, libInTmp)) {
+                    throw new RuntimeException("Could not push library " + library + " to device");
+                }
+
+                String runAsCp = mDevice.executeShellCommand(
+                        "run-as " + mPkg + " cp " + libInTmp + " " + libInDataData);
+                if (runAsCp != null && !runAsCp.trim().isEmpty()) {
+                    throw new RuntimeException(runAsCp.trim());
+                }
+
+                String runAsChmod = mDevice
+                        .executeShellCommand("run-as " + mPkg + " chmod a+x " + libInDataData);
+                if (runAsChmod != null && !runAsChmod.trim().isEmpty()) {
+                    throw new RuntimeException(runAsChmod.trim());
+                }
+
+                return libInDataData;
+            } finally {
+                if (tmpFile != null) {
+                    tmpFile.delete();
+                }
+                if (zf != null) {
+                    try {
+                        zf.close();
+                    } catch (Exception e) {
+                        throw new RuntimeException("ZipFile close failed", e);
+                    }
+                }
+            }
+        }
+    }
+
+    private static class TestResults implements ITestRunListener {
+        private boolean mFailed = false;
+        private boolean mStarted = false;
+        private final Runnable mOnStart;
+        private List<String> mErrors = new LinkedList<>();
+
+        public TestResults(Runnable onStart) {
+            this.mOnStart = onStart;
+        }
+
+        public boolean hasFailed() {
+            return mFailed;
+        }
+
+        public boolean hasStarted() {
+            return mStarted;
+        }
+
+        public String getErrors() {
+            if (mErrors.isEmpty()) {
+                return "";
+            }
+            return mErrors.toString();
+        }
+
+        @Override
+        public void testAssumptionFailure(TestIdentifier arg0, String arg1) {
+            mFailed = true;
+            mErrors.add(arg0.toString() + " " + arg1);
+        }
+
+        @Override
+        public void testEnded(TestIdentifier arg0, Map<String, String> arg1) {}
+
+        @Override
+        public void testFailed(TestIdentifier arg0, String arg1) {
+            mFailed = true;
+            mErrors.add(arg0.toString() + " " + arg1);
+        }
+
+        @Override
+        public void testIgnored(TestIdentifier arg0) {}
+
+        @Override
+        public void testRunEnded(long arg0, Map<String, String> arg1) {}
+
+        @Override
+        public void testRunFailed(String arg0) {
+            mFailed = true;
+            mErrors.add(arg0);
+        }
+
+        @Override
+        public void testRunStarted(String arg0, int arg1) {
+            if (mOnStart != null) {
+                mOnStart.run();
+            }
+        }
+
+        @Override
+        public void testRunStopped(long arg0) {}
+
+        @Override
+        public void testStarted(TestIdentifier arg0) {
+            mStarted = true;
+        }
+    }
+}
diff --git a/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiPreparer.java b/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiPreparer.java
new file mode 100644
index 0000000..1577ab3
--- /dev/null
+++ b/hostsidetests/jvmti/base/host/src/android/jvmti/cts/JvmtiPreparer.java
@@ -0,0 +1,40 @@
+package android.jvmti.cts;
+
+import com.android.compatibility.common.tradefed.targetprep.ApkInstaller;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetprep.TargetSetupError;
+import java.io.File;
+
+@OptionClass(alias="jvmti-installer")
+public class JvmtiPreparer extends ApkInstaller {
+    // We re-use test-file-name to find the APK. But we need to know the package name.
+    @Option(name = "package-name",
+            description = "The package name of the device test",
+            mandatory = true)
+    private String mPackageName = null;
+
+    private String storedApkName;
+
+    public final static String PACKAGE_NAME_ATTRIBUTE = "jvmti-package-name";
+    public final static String APK_ATTRIBUTE = "jvmti-apk";
+
+    @Override
+    public void setUp(ITestDevice arg0, IBuildInfo arg1)
+            throws TargetSetupError, DeviceNotAvailableException {
+        super.setUp(arg0, arg1);
+
+        arg1.addBuildAttribute(PACKAGE_NAME_ATTRIBUTE, mPackageName);
+        arg1.addBuildAttribute(APK_ATTRIBUTE, storedApkName);
+    }
+
+    @Override
+    protected File getLocalPathForFilename(IBuildInfo arg0, String arg1, ITestDevice arg2)
+            throws TargetSetupError {
+        storedApkName = arg1;
+        return super.getLocalPathForFilename(arg0, arg1, arg2);
+    }
+}
diff --git a/hostsidetests/jvmti/base/jni/Android.mk b/hostsidetests/jvmti/base/jni/Android.mk
new file mode 100644
index 0000000..de7428c
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/Android.mk
@@ -0,0 +1,59 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libctsjvmtiagent
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := cts_agent.cpp \
+                   jni_binder.cpp \
+                   jvmti_helper.cpp \
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+LOCAL_HEADER_LIBRARIES := libopenjdkjvmti_headers
+
+LOCAL_SHARED_LIBRARIES := liblog \
+                          libdl
+
+# Platform libraries that are not available to apps. Link in statically.
+LOCAL_STATIC_LIBRARIES := libbase
+
+# Turn on all warnings.
+LOCAL_C_FLAGS := -fno-rtti \
+                 -ggdb3 \
+                 -Wall \
+                 -Wextra \
+                 -Werror \
+                 -Wunreachable-code \
+                 -Wredundant-decls \
+                 -Wshadow \
+                 -Wunused \
+                 -Wimplicit-fallthrough \
+                 -Wfloat-equal \
+                 -Wint-to-void-pointer-cast \
+                 -Wused-but-marked-unused \
+                 -Wdeprecated \
+                 -Wunreachable-code-break \
+                 -Wunreachable-code-return \
+                 -g \
+                 -O0 \
+
+LOCAL_CXX_STL := libc++_static
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/hostsidetests/jvmti/base/jni/common.h b/hostsidetests/jvmti/base/jni/common.h
new file mode 100644
index 0000000..ded4fc5
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/common.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef COMMON_H_
+#define COMMON_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace cts {
+namespace jvmti {
+
+jvmtiEnv* GetJvmtiEnv();
+
+int JniThrowNullPointerException(JNIEnv* env, const char* msg);
+
+}  // namespace jvmti
+}  // namespace cts
+
+
+#endif  // COMMON_H_
diff --git a/hostsidetests/jvmti/base/jni/cts_agent.cpp b/hostsidetests/jvmti/base/jni/cts_agent.cpp
new file mode 100644
index 0000000..50f7841
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/cts_agent.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <jvmti.h>
+
+#include "android-base/logging.h"
+#include "common.h"
+#include "jni_binder.h"
+#include "jvmti_helper.h"
+
+namespace cts {
+namespace jvmti {
+
+static jvmtiEnv* jvmti_env;
+
+jvmtiEnv* GetJvmtiEnv() {
+  return jvmti_env;
+}
+
+int JniThrowNullPointerException(JNIEnv* env, const char* msg) {
+  JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+
+  if (env->ExceptionCheck()) {
+    env->ExceptionClear();
+  }
+
+  jclass exc_class = env->FindClass("java/lang/NullPointerException");
+  if (exc_class == nullptr) {
+    return -1;
+  }
+
+  bool ok = env->ThrowNew(exc_class, msg) == JNI_OK;
+
+  env->DeleteLocalRef(exc_class);
+
+  return ok ? 0 : -1;
+}
+
+extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm,
+                                               char* options ATTRIBUTE_UNUSED,
+                                               void* reserved ATTRIBUTE_UNUSED) {
+  BindOnLoad(vm);
+
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0) != 0) {
+    LOG(FATAL) << "Could not get shared jvmtiEnv";
+  }
+
+  SetAllCapabilities(jvmti_env);
+  return 0;
+}
+
+extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm,
+                                                 char* options ATTRIBUTE_UNUSED,
+                                                 void* reserved ATTRIBUTE_UNUSED) {
+  BindOnAttach(vm);
+
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0) != 0) {
+    LOG(FATAL) << "Could not get shared jvmtiEnv";
+  }
+
+  SetAllCapabilities(jvmti_env);
+  return 0;
+}
+
+}
+}
diff --git a/hostsidetests/jvmti/base/jni/jni_binder.cpp b/hostsidetests/jvmti/base/jni/jni_binder.cpp
new file mode 100644
index 0000000..14658a1
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/jni_binder.cpp
@@ -0,0 +1,431 @@
+/*
+ * 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.
+ */
+
+#include "jni_binder.h"
+
+#include <dlfcn.h>
+#include <inttypes.h>
+#include <stdio.h>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "common.h"
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+
+namespace cts {
+namespace jvmti {
+
+static constexpr const char* kMainClass = "android/jvmti/cts/JniBindings";
+static constexpr const char* kMainClassStartup = "startup";
+
+size_t CountModifiedUtf8Chars(const char* utf8, size_t byte_count) {
+  DCHECK_LE(byte_count, strlen(utf8));
+  size_t len = 0;
+  const char* end = utf8 + byte_count;
+  for (; utf8 < end; ++utf8) {
+    int ic = *utf8;
+    len++;
+    if (LIKELY((ic & 0x80) == 0)) {
+      // One-byte encoding.
+      continue;
+    }
+    // Two- or three-byte encoding.
+    utf8++;
+    if ((ic & 0x20) == 0) {
+      // Two-byte encoding.
+      continue;
+    }
+    utf8++;
+    if ((ic & 0x10) == 0) {
+      // Three-byte encoding.
+      continue;
+    }
+
+    // Four-byte encoding: needs to be converted into a surrogate
+    // pair.
+    utf8++;
+    len++;
+  }
+  return len;
+}
+
+static uint16_t GetTrailingUtf16Char(uint32_t maybe_pair) {
+  return static_cast<uint16_t>(maybe_pair >> 16);
+}
+
+static uint16_t GetLeadingUtf16Char(uint32_t maybe_pair) {
+  return static_cast<uint16_t>(maybe_pair & 0x0000FFFF);
+}
+
+static uint32_t GetUtf16FromUtf8(const char** utf8_data_in) {
+  const uint8_t one = *(*utf8_data_in)++;
+  if ((one & 0x80) == 0) {
+    // one-byte encoding
+    return one;
+  }
+
+  const uint8_t two = *(*utf8_data_in)++;
+  if ((one & 0x20) == 0) {
+    // two-byte encoding
+    return ((one & 0x1f) << 6) | (two & 0x3f);
+  }
+
+  const uint8_t three = *(*utf8_data_in)++;
+  if ((one & 0x10) == 0) {
+    return ((one & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f);
+  }
+
+  // Four byte encodings need special handling. We'll have
+  // to convert them into a surrogate pair.
+  const uint8_t four = *(*utf8_data_in)++;
+
+  // Since this is a 4 byte UTF-8 sequence, it will lie between
+  // U+10000 and U+1FFFFF.
+  //
+  // TODO: What do we do about values in (U+10FFFF, U+1FFFFF) ? The
+  // spec says they're invalid but nobody appears to check for them.
+  const uint32_t code_point = ((one & 0x0f) << 18) | ((two & 0x3f) << 12)
+      | ((three & 0x3f) << 6) | (four & 0x3f);
+
+  uint32_t surrogate_pair = 0;
+  // Step two: Write out the high (leading) surrogate to the bottom 16 bits
+  // of the of the 32 bit type.
+  surrogate_pair |= ((code_point >> 10) + 0xd7c0) & 0xffff;
+  // Step three : Write out the low (trailing) surrogate to the top 16 bits.
+  surrogate_pair |= ((code_point & 0x03ff) + 0xdc00) << 16;
+
+  return surrogate_pair;
+}
+
+static std::string MangleForJni(const std::string& s) {
+  std::string result;
+  size_t char_count = CountModifiedUtf8Chars(s.c_str(), s.length());
+  const char* cp = &s[0];
+  for (size_t i = 0; i < char_count; ++i) {
+    uint32_t ch = GetUtf16FromUtf8(&cp);
+    if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) {
+      result.push_back(ch);
+    } else if (ch == '.' || ch == '/') {
+      result += "_";
+    } else if (ch == '_') {
+      result += "_1";
+    } else if (ch == ';') {
+      result += "_2";
+    } else if (ch == '[') {
+      result += "_3";
+    } else {
+      const uint16_t leading = GetLeadingUtf16Char(ch);
+      const uint32_t trailing = GetTrailingUtf16Char(ch);
+
+      android::base::StringAppendF(&result, "_0%04x", leading);
+      if (trailing != 0) {
+        android::base::StringAppendF(&result, "_0%04x", trailing);
+      }
+    }
+  }
+  return result;
+}
+
+static std::string GetJniShortName(const std::string& class_descriptor, const std::string& method) {
+  // Remove the leading 'L' and trailing ';'...
+  std::string class_name(class_descriptor);
+  CHECK_EQ(class_name[0], 'L') << class_name;
+  CHECK_EQ(class_name[class_name.size() - 1], ';') << class_name;
+  class_name.erase(0, 1);
+  class_name.erase(class_name.size() - 1, 1);
+
+  std::string short_name;
+  short_name += "Java_";
+  short_name += MangleForJni(class_name);
+  short_name += "_";
+  short_name += MangleForJni(method);
+  return short_name;
+}
+
+static void BindMethod(jvmtiEnv* jvmti_env,
+                       JNIEnv* env,
+                       jclass klass,
+                       jmethodID method) {
+  std::string name;
+  std::string signature;
+  std::string mangled_names[2];
+  {
+    char* name_cstr;
+    char* sig_cstr;
+    jvmtiError name_result = jvmti_env->GetMethodName(method, &name_cstr, &sig_cstr, nullptr);
+    CheckJvmtiError(jvmti_env, name_result);
+    CHECK(name_cstr != nullptr);
+    CHECK(sig_cstr != nullptr);
+    name = name_cstr;
+    signature = sig_cstr;
+
+    char* klass_name;
+    jvmtiError klass_result = jvmti_env->GetClassSignature(klass, &klass_name, nullptr);
+    CheckJvmtiError(jvmti_env, klass_result);
+
+    mangled_names[0] = GetJniShortName(klass_name, name);
+    // TODO: Long JNI name.
+
+    CheckJvmtiError(jvmti_env, Deallocate(jvmti_env, name_cstr));
+    CheckJvmtiError(jvmti_env, Deallocate(jvmti_env, sig_cstr));
+    CheckJvmtiError(jvmti_env, Deallocate(jvmti_env, klass_name));
+  }
+
+  for (const std::string& mangled_name : mangled_names) {
+    if (mangled_name.empty()) {
+      continue;
+    }
+    void* sym = dlsym(RTLD_DEFAULT, mangled_name.c_str());
+    if (sym == nullptr) {
+      continue;
+    }
+
+    JNINativeMethod native_method;
+    native_method.fnPtr = sym;
+    native_method.name = name.c_str();
+    native_method.signature = signature.c_str();
+
+    env->RegisterNatives(klass, &native_method, 1);
+
+    return;
+  }
+
+  LOG(FATAL) << "Could not find " << mangled_names[0];
+}
+
+static std::string DescriptorToDot(const char* descriptor) {
+  size_t length = strlen(descriptor);
+  if (length > 1) {
+    if (descriptor[0] == 'L' && descriptor[length - 1] == ';') {
+      // Descriptors have the leading 'L' and trailing ';' stripped.
+      std::string result(descriptor + 1, length - 2);
+      std::replace(result.begin(), result.end(), '/', '.');
+      return result;
+    } else {
+      // For arrays the 'L' and ';' remain intact.
+      std::string result(descriptor);
+      std::replace(result.begin(), result.end(), '/', '.');
+      return result;
+    }
+  }
+  // Do nothing for non-class/array descriptors.
+  return descriptor;
+}
+
+static jobject GetSystemClassLoader(JNIEnv* env) {
+  ScopedLocalRef<jclass> cl_klass(env, env->FindClass("java/lang/ClassLoader"));
+  CHECK(cl_klass.get() != nullptr);
+  jmethodID getsystemclassloader_method = env->GetStaticMethodID(cl_klass.get(),
+                                                                 "getSystemClassLoader",
+                                                                 "()Ljava/lang/ClassLoader;");
+  CHECK(getsystemclassloader_method != nullptr);
+  return env->CallStaticObjectMethod(cl_klass.get(), getsystemclassloader_method);
+}
+
+static jclass FindClassWithClassLoader(JNIEnv* env, const char* class_name, jobject class_loader) {
+  // Create a String of the name.
+  std::string descriptor = android::base::StringPrintf("L%s;", class_name);
+  std::string dot_name = DescriptorToDot(descriptor.c_str());
+  ScopedLocalRef<jstring> name_str(env, env->NewStringUTF(dot_name.c_str()));
+
+  // Call Class.forName with it.
+  ScopedLocalRef<jclass> c_klass(env, env->FindClass("java/lang/Class"));
+  CHECK(c_klass.get() != nullptr);
+  jmethodID forname_method = env->GetStaticMethodID(
+      c_klass.get(),
+      "forName",
+      "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;");
+  CHECK(forname_method != nullptr);
+
+  return static_cast<jclass>(env->CallStaticObjectMethod(c_klass.get(),
+                                                         forname_method,
+                                                         name_str.get(),
+                                                         JNI_FALSE,
+                                                         class_loader));
+}
+
+// Find the given classname. First try the implied classloader, then the system classloader,
+// then use JVMTI to find all classloaders.
+static jclass FindClass(jvmtiEnv* jvmti_env,
+                        JNIEnv* env,
+                        const char* class_name,
+                        jobject class_loader) {
+  if (class_loader != nullptr) {
+    return FindClassWithClassLoader(env, class_name, class_loader);
+  }
+
+  jclass from_implied = env->FindClass(class_name);
+  if (from_implied != nullptr) {
+    return from_implied;
+  }
+  env->ExceptionClear();
+
+  ScopedLocalRef<jobject> system_class_loader(env, GetSystemClassLoader(env));
+  CHECK(system_class_loader.get() != nullptr);
+  jclass from_system = FindClassWithClassLoader(env, class_name, system_class_loader.get());
+  if (from_system != nullptr) {
+    return from_system;
+  }
+  env->ExceptionClear();
+
+  // Look at the context classloaders of all threads.
+  jint thread_count;
+  jthread* threads;
+  CheckJvmtiError(jvmti_env, jvmti_env->GetAllThreads(&thread_count, &threads));
+  JvmtiUniquePtr threads_uptr = MakeJvmtiUniquePtr(jvmti_env, threads);
+
+  jclass result = nullptr;
+  for (jint t = 0; t != thread_count; ++t) {
+    // Always loop over all elements, as we need to free the local references.
+    if (result == nullptr) {
+      jvmtiThreadInfo info;
+      CheckJvmtiError(jvmti_env, jvmti_env->GetThreadInfo(threads[t], &info));
+      CheckJvmtiError(jvmti_env, Deallocate(jvmti_env, info.name));
+      if (info.thread_group != nullptr) {
+        env->DeleteLocalRef(info.thread_group);
+      }
+      if (info.context_class_loader != nullptr) {
+        result = FindClassWithClassLoader(env, class_name, info.context_class_loader);
+        env->ExceptionClear();
+        env->DeleteLocalRef(info.context_class_loader);
+      }
+    }
+    env->DeleteLocalRef(threads[t]);
+  }
+
+  if (result != nullptr) {
+    return result;
+  }
+
+  // TODO: Implement scanning *all* classloaders.
+  LOG(FATAL) << "Unimplemented";
+
+  return nullptr;
+}
+
+void BindFunctions(jvmtiEnv* jvmti_env, JNIEnv* env, const char* class_name, jobject class_loader) {
+  // Use JNI to load the class.
+  ScopedLocalRef<jclass> klass(env, FindClass(jvmti_env, env, class_name, class_loader));
+  CHECK(klass.get() != nullptr) << class_name;
+
+  // Use JVMTI to get the methods.
+  jint method_count;
+  jmethodID* methods;
+  jvmtiError methods_result = jvmti_env->GetClassMethods(klass.get(), &method_count, &methods);
+  CheckJvmtiError(jvmti_env, methods_result);
+
+  // Check each method.
+  for (jint i = 0; i < method_count; ++i) {
+    jint modifiers;
+    jvmtiError mod_result = jvmti_env->GetMethodModifiers(methods[i], &modifiers);
+    CheckJvmtiError(jvmti_env, mod_result);
+    constexpr jint kNative = static_cast<jint>(0x0100);
+    if ((modifiers & kNative) != 0) {
+      BindMethod(jvmti_env, env, klass.get(), methods[i]);
+    }
+  }
+
+  CheckJvmtiError(jvmti_env, Deallocate(jvmti_env, methods));
+}
+
+// Inform the main instrumentation class of our successful attach.
+static void InformMainAttach(jvmtiEnv* jvmti_env,
+                             JNIEnv* jni_env,
+                             const char* class_name,
+                             const char* method_name) {
+  // Use JNI to load the class.
+  ScopedLocalRef<jclass> klass(jni_env, FindClass(jvmti_env, jni_env, class_name, nullptr));
+  CHECK(klass.get() != nullptr) << class_name;
+
+  jmethodID method = jni_env->GetStaticMethodID(klass.get(), method_name, "()V");
+  CHECK(method != nullptr);
+
+  jni_env->CallStaticVoidMethod(klass.get(), method);
+}
+
+// TODO: Check this. This may not work on device. The classloader containing the app's classes
+//       may not have been created at this point (i.e., if it's not the system classloader).
+static void JNICALL VMInitCallback(jvmtiEnv* jvmti_env,
+                                   JNIEnv* jni_env,
+                                   jthread thread ATTRIBUTE_UNUSED) {
+  // Bind kMainClass native methods.
+  BindFunctions(jvmti_env, jni_env, kMainClass);
+
+  // And let the test know that we have started up.
+  InformMainAttach(jvmti_env, jni_env, kMainClass, kMainClassStartup);
+
+  // And delete the jvmtiEnv.
+  jvmti_env->DisposeEnvironment();
+}
+
+// Install a phase callback that will bind JNI functions on VMInit.
+void BindOnLoad(JavaVM* vm) {
+  // Use a new jvmtiEnv. Otherwise we might collide with table changes.
+  jvmtiEnv* install_env;
+  if (vm->GetEnv(reinterpret_cast<void**>(&install_env), JVMTI_VERSION_1_0) != 0) {
+    LOG(FATAL) << "Could not get jvmtiEnv";
+  }
+  SetAllCapabilities(install_env);
+
+  {
+    jvmtiEventCallbacks callbacks;
+    memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+    callbacks.VMInit = VMInitCallback;
+
+    CheckJvmtiError(install_env, install_env->SetEventCallbacks(&callbacks, sizeof(callbacks)));
+  }
+
+  {
+    CheckJvmtiError(install_env, install_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                       JVMTI_EVENT_VM_INIT,
+                                                                       nullptr));
+  }
+}
+
+// Ensure binding of the Main class when the agent is started through OnAttach.
+void BindOnAttach(JavaVM* vm) {
+  // Get a JNIEnv. As the thread is attached, we must not destroy it.
+  JNIEnv* env;
+  if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != 0) {
+    LOG(FATAL) << "Could not get JNIEnv";
+  }
+
+  jvmtiEnv* jvmti_env;
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0) != 0) {
+    LOG(FATAL) << "Could not get jvmtiEnv";
+  }
+  SetAllCapabilities(jvmti_env);
+
+  BindFunctions(jvmti_env, env, kMainClass);
+
+  // And let the test know that we have started up.
+  InformMainAttach(jvmti_env, env, kMainClass, kMainClassStartup);
+
+  if (jvmti_env->DisposeEnvironment() != JVMTI_ERROR_NONE) {
+    LOG(FATAL) << "Could not dispose temporary jvmtiEnv";
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JniBindings_bindAgentJNI(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jstring className, jobject classLoader) {
+  ScopedUtfChars name(env, className);
+  BindFunctions(GetJvmtiEnv(), env, name.c_str(), classLoader);
+}
+
+}  // namespace jvmti
+}  // namespace cts
diff --git a/hostsidetests/jvmti/base/jni/jni_binder.h b/hostsidetests/jvmti/base/jni/jni_binder.h
new file mode 100644
index 0000000..fb311ee
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/jni_binder.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef JNI_BINDER_H_
+#define JNI_BINDER_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace cts {
+namespace jvmti {
+
+// Load the class through JNI. Inspect it, find all native methods. Construct the corresponding
+// mangled name, run dlsym and bind the method.
+//
+// This will abort on failure.
+void BindFunctions(jvmtiEnv* jvmti_env,
+                   JNIEnv* env,
+                   const char* class_name,
+                   jobject class_loader = nullptr);
+
+// Ensure binding of the Main class when the agent is started through OnLoad.
+void BindOnLoad(JavaVM* vm);
+
+// Ensure binding of the Main class when the agent is started through OnAttach.
+void BindOnAttach(JavaVM* vm);
+
+}  // namespace jvmti
+}  // namespace cts
+
+#endif  // JNI_BINDER_H_
diff --git a/hostsidetests/jvmti/base/jni/jni_helper.h b/hostsidetests/jvmti/base/jni/jni_helper.h
new file mode 100644
index 0000000..14aba8b
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/jni_helper.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#ifndef JNI_HELPER_H_
+#define JNI_HELPER_H_
+
+#include "jni.h"
+#include "scoped_local_ref.h"
+
+namespace cts {
+namespace jvmti {
+
+// Create an object array using a lambda that returns a local ref for each element.
+template <typename T>
+static jobjectArray CreateObjectArray(JNIEnv* env,
+                                      jint length,
+                                      const char* component_type_descriptor,
+                                      T src) {
+  if (length < 0) {
+    return nullptr;
+  }
+
+  ScopedLocalRef<jclass> obj_class(env, env->FindClass(component_type_descriptor));
+  if (obj_class.get() == nullptr) {
+    return nullptr;
+  }
+
+  ScopedLocalRef<jobjectArray> ret(env, env->NewObjectArray(length, obj_class.get(), nullptr));
+  if (ret.get() == nullptr) {
+    return nullptr;
+  }
+
+  for (jint i = 0; i < length; ++i) {
+    jobject element = src(i);
+    env->SetObjectArrayElement(ret.get(), static_cast<jint>(i), element);
+    env->DeleteLocalRef(element);
+    if (env->ExceptionCheck()) {
+      return nullptr;
+    }
+  }
+
+  return ret.release();
+}
+
+}  // namespace jvmti
+}  // namespace cts
+
+#endif  // JNI_HELPER_H_
diff --git a/hostsidetests/jvmti/base/jni/jvmti_helper.cpp b/hostsidetests/jvmti/base/jni/jvmti_helper.cpp
new file mode 100644
index 0000000..a8a1aec
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/jvmti_helper.cpp
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+#include "jvmti_helper.h"
+
+#include <algorithm>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <sstream>
+#include <string.h>
+
+#include "android-base/logging.h"
+#include "scoped_local_ref.h"
+
+namespace cts {
+namespace jvmti {
+
+void CheckJvmtiError(jvmtiEnv* env, jvmtiError error) {
+  if (error != JVMTI_ERROR_NONE) {
+    char* error_name;
+    jvmtiError name_error = env->GetErrorName(error, &error_name);
+    if (name_error != JVMTI_ERROR_NONE) {
+      LOG(FATAL) << "Unable to get error name for " << error;
+    }
+    LOG(FATAL) << "Unexpected error: " << error_name;
+  }
+}
+
+void SetAllCapabilities(jvmtiEnv* env) {
+  jvmtiCapabilities caps;
+  jvmtiError error1 = env->GetPotentialCapabilities(&caps);
+  CheckJvmtiError(env, error1);
+  jvmtiError error2 = env->AddCapabilities(&caps);
+  CheckJvmtiError(env, error2);
+}
+
+bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error) {
+  if (error == JVMTI_ERROR_NONE) {
+    return false;
+  }
+
+  ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+  if (rt_exception.get() == nullptr) {
+    // CNFE should be pending.
+    return true;
+  }
+
+  char* err;
+  CheckJvmtiError(jvmti_env, jvmti_env->GetErrorName(error, &err));
+
+  env->ThrowNew(rt_exception.get(), err);
+
+  Deallocate(jvmti_env, err);
+  return true;
+}
+
+std::ostream& operator<<(std::ostream& os, const jvmtiError& rhs) {
+  switch (rhs) {
+    case JVMTI_ERROR_NONE:
+      return os << "NONE";
+    case JVMTI_ERROR_INVALID_THREAD:
+      return os << "INVALID_THREAD";
+    case JVMTI_ERROR_INVALID_THREAD_GROUP:
+      return os << "INVALID_THREAD_GROUP";
+    case JVMTI_ERROR_INVALID_PRIORITY:
+      return os << "INVALID_PRIORITY";
+    case JVMTI_ERROR_THREAD_NOT_SUSPENDED:
+      return os << "THREAD_NOT_SUSPENDED";
+    case JVMTI_ERROR_THREAD_SUSPENDED:
+      return os << "THREAD_SUSPENDED";
+    case JVMTI_ERROR_THREAD_NOT_ALIVE:
+      return os << "THREAD_NOT_ALIVE";
+    case JVMTI_ERROR_INVALID_OBJECT:
+      return os << "INVALID_OBJECT";
+    case JVMTI_ERROR_INVALID_CLASS:
+      return os << "INVALID_CLASS";
+    case JVMTI_ERROR_CLASS_NOT_PREPARED:
+      return os << "CLASS_NOT_PREPARED";
+    case JVMTI_ERROR_INVALID_METHODID:
+      return os << "INVALID_METHODID";
+    case JVMTI_ERROR_INVALID_LOCATION:
+      return os << "INVALID_LOCATION";
+    case JVMTI_ERROR_INVALID_FIELDID:
+      return os << "INVALID_FIELDID";
+    case JVMTI_ERROR_NO_MORE_FRAMES:
+      return os << "NO_MORE_FRAMES";
+    case JVMTI_ERROR_OPAQUE_FRAME:
+      return os << "OPAQUE_FRAME";
+    case JVMTI_ERROR_TYPE_MISMATCH:
+      return os << "TYPE_MISMATCH";
+    case JVMTI_ERROR_INVALID_SLOT:
+      return os << "INVALID_SLOT";
+    case JVMTI_ERROR_DUPLICATE:
+      return os << "DUPLICATE";
+    case JVMTI_ERROR_NOT_FOUND:
+      return os << "NOT_FOUND";
+    case JVMTI_ERROR_INVALID_MONITOR:
+      return os << "INVALID_MONITOR";
+    case JVMTI_ERROR_NOT_MONITOR_OWNER:
+      return os << "NOT_MONITOR_OWNER";
+    case JVMTI_ERROR_INTERRUPT:
+      return os << "INTERRUPT";
+    case JVMTI_ERROR_INVALID_CLASS_FORMAT:
+      return os << "INVALID_CLASS_FORMAT";
+    case JVMTI_ERROR_CIRCULAR_CLASS_DEFINITION:
+      return os << "CIRCULAR_CLASS_DEFINITION";
+    case JVMTI_ERROR_FAILS_VERIFICATION:
+      return os << "FAILS_VERIFICATION";
+    case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED:
+      return os << "UNSUPPORTED_REDEFINITION_METHOD_ADDED";
+    case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED:
+      return os << "UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED";
+    case JVMTI_ERROR_INVALID_TYPESTATE:
+      return os << "INVALID_TYPESTATE";
+    case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED:
+      return os << "UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED";
+    case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED:
+      return os << "UNSUPPORTED_REDEFINITION_METHOD_DELETED";
+    case JVMTI_ERROR_UNSUPPORTED_VERSION:
+      return os << "UNSUPPORTED_VERSION";
+    case JVMTI_ERROR_NAMES_DONT_MATCH:
+      return os << "NAMES_DONT_MATCH";
+    case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED:
+      return os << "UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED";
+    case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED:
+      return os << "UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED";
+    case JVMTI_ERROR_UNMODIFIABLE_CLASS:
+      return os << "JVMTI_ERROR_UNMODIFIABLE_CLASS";
+    case JVMTI_ERROR_NOT_AVAILABLE:
+      return os << "NOT_AVAILABLE";
+    case JVMTI_ERROR_MUST_POSSESS_CAPABILITY:
+      return os << "MUST_POSSESS_CAPABILITY";
+    case JVMTI_ERROR_NULL_POINTER:
+      return os << "NULL_POINTER";
+    case JVMTI_ERROR_ABSENT_INFORMATION:
+      return os << "ABSENT_INFORMATION";
+    case JVMTI_ERROR_INVALID_EVENT_TYPE:
+      return os << "INVALID_EVENT_TYPE";
+    case JVMTI_ERROR_ILLEGAL_ARGUMENT:
+      return os << "ILLEGAL_ARGUMENT";
+    case JVMTI_ERROR_NATIVE_METHOD:
+      return os << "NATIVE_METHOD";
+    case JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED:
+      return os << "CLASS_LOADER_UNSUPPORTED";
+    case JVMTI_ERROR_OUT_OF_MEMORY:
+      return os << "OUT_OF_MEMORY";
+    case JVMTI_ERROR_ACCESS_DENIED:
+      return os << "ACCESS_DENIED";
+    case JVMTI_ERROR_WRONG_PHASE:
+      return os << "WRONG_PHASE";
+    case JVMTI_ERROR_INTERNAL:
+      return os << "INTERNAL";
+    case JVMTI_ERROR_UNATTACHED_THREAD:
+      return os << "UNATTACHED_THREAD";
+    case JVMTI_ERROR_INVALID_ENVIRONMENT:
+      return os << "INVALID_ENVIRONMENT";
+  }
+  LOG(FATAL) << "Unexpected error type " << static_cast<int>(rhs);
+  __builtin_unreachable();
+}
+
+}  // namespace jvmti
+}  // namespace cts
diff --git a/hostsidetests/jvmti/base/jni/jvmti_helper.h b/hostsidetests/jvmti/base/jni/jvmti_helper.h
new file mode 100644
index 0000000..898fa96
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/jvmti_helper.h
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#ifndef JVMTI_HELPER_H_
+#define JVMTI_HELPER_H_
+
+#include "jni.h"
+#include "jvmti.h"
+#include <memory>
+#include <ostream>
+
+#include "android-base/logging.h"
+
+namespace cts {
+namespace jvmti {
+
+// Add all capabilities to the given env.
+void SetAllCapabilities(jvmtiEnv* env);
+
+// Check whether the given error is NONE. If not, print out the corresponding error message
+// and abort.
+void CheckJvmtiError(jvmtiEnv* env, jvmtiError error);
+
+// Convert the given error to a RuntimeException with a message derived from the error. Returns
+// true on error, false if error is JVMTI_ERROR_NONE.
+bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error);
+
+class JvmtiDeleter {
+ public:
+  JvmtiDeleter() : env_(nullptr) {}
+  explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
+
+  JvmtiDeleter(JvmtiDeleter&) = default;
+  JvmtiDeleter(JvmtiDeleter&&) = default;
+  JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
+
+  void operator()(unsigned char* ptr) const {
+    CHECK(env_ != nullptr);
+    jvmtiError ret = env_->Deallocate(ptr);
+    CheckJvmtiError(env_, ret);
+  }
+
+ private:
+  mutable jvmtiEnv* env_;
+};
+
+using JvmtiUniquePtr = std::unique_ptr<unsigned char, JvmtiDeleter>;
+
+template <typename T>
+static inline JvmtiUniquePtr MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) {
+  return JvmtiUniquePtr(reinterpret_cast<unsigned char*>(mem), JvmtiDeleter(env));
+}
+
+template <typename T>
+static inline jvmtiError Deallocate(jvmtiEnv* env, T* mem) {
+  return env->Deallocate(reinterpret_cast<unsigned char*>(mem));
+}
+
+// To print jvmtiError. Does not rely on GetErrorName, so is an approximation.
+std::ostream& operator<<(std::ostream& os, const jvmtiError& rhs);
+
+}  // namespace jvmti
+}  // namespace cts
+
+#endif  // JVMTI_HELPER_H_
diff --git a/hostsidetests/jvmti/base/jni/scoped_local_ref.h b/hostsidetests/jvmti/base/jni/scoped_local_ref.h
new file mode 100644
index 0000000..4622480
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/scoped_local_ref.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef SCOPED_LOCAL_REF_H_
+#define SCOPED_LOCAL_REF_H_
+
+#include "jni.h"
+
+#include <stddef.h>
+
+#include "android-base/macros.h"
+
+namespace cts {
+namespace jvmti {
+
+template<typename T>
+class ScopedLocalRef {
+public:
+    ScopedLocalRef(JNIEnv* env, T localRef) : mEnv(env), mLocalRef(localRef) {
+    }
+
+    ~ScopedLocalRef() {
+        reset();
+    }
+
+    void reset(T ptr = NULL) {
+        if (ptr != mLocalRef) {
+            if (mLocalRef != NULL) {
+                mEnv->DeleteLocalRef(mLocalRef);
+            }
+            mLocalRef = ptr;
+        }
+    }
+
+    T release() __attribute__((warn_unused_result)) {
+        T localRef = mLocalRef;
+        mLocalRef = NULL;
+        return localRef;
+    }
+
+    T get() const {
+        return mLocalRef;
+    }
+
+private:
+    JNIEnv* const mEnv;
+    T mLocalRef;
+
+    DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
+};
+
+}
+}
+
+#endif  // SCOPED_LOCAL_REF_H_
diff --git a/hostsidetests/jvmti/base/jni/scoped_primitive_array.h b/hostsidetests/jvmti/base/jni/scoped_primitive_array.h
new file mode 100644
index 0000000..c030b7d
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/scoped_primitive_array.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef SCOPED_PRIMITIVE_ARRAY_H_
+#define SCOPED_PRIMITIVE_ARRAY_H_
+
+#include "jni.h"
+
+#include "android-base/macros.h"
+#include "common.h"
+
+namespace cts {
+namespace jvmti {
+
+#ifdef POINTER_TYPE
+#error POINTER_TYPE is defined.
+#else
+#define POINTER_TYPE(T) T*  /* NOLINT */
+#endif
+
+#ifdef REFERENCE_TYPE
+#error REFERENCE_TYPE is defined.
+#else
+#define REFERENCE_TYPE(T) T&  /* NOLINT */
+#endif
+
+// ScopedBooleanArrayRO, ScopedByteArrayRO, ScopedCharArrayRO, ScopedDoubleArrayRO,
+// ScopedFloatArrayRO, ScopedIntArrayRO, ScopedLongArrayRO, and ScopedShortArrayRO provide
+// convenient read-only access to Java arrays from JNI code. This is cheaper than read-write
+// access and should be used by default.
+#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(PRIMITIVE_TYPE, NAME) \
+    class Scoped ## NAME ## ArrayRO { \
+    public: \
+        explicit Scoped ## NAME ## ArrayRO(JNIEnv* env) \
+        : mEnv(env), mJavaArray(nullptr), mRawArray(nullptr), mSize(0) {} \
+        Scoped ## NAME ## ArrayRO(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \
+        : mEnv(env) { \
+            if (javaArray == nullptr) { \
+                mJavaArray = nullptr; \
+                mSize = 0; \
+                mRawArray = nullptr; \
+                JniThrowNullPointerException(env, nullptr); \
+            } else { \
+                reset(javaArray); \
+            } \
+        } \
+        ~Scoped ## NAME ## ArrayRO() { \
+            if (mRawArray != nullptr && mRawArray != mBuffer) { \
+                mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, JNI_ABORT); \
+            } \
+        } \
+        void reset(PRIMITIVE_TYPE ## Array javaArray) { \
+            mJavaArray = javaArray; \
+            mSize = mEnv->GetArrayLength(mJavaArray); \
+            if (mSize <= buffer_size) { \
+                mEnv->Get ## NAME ## ArrayRegion(mJavaArray, 0, mSize, mBuffer); \
+                mRawArray = mBuffer; \
+            } else { \
+                mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, nullptr); \
+            } \
+        } \
+        const PRIMITIVE_TYPE* get() const { return mRawArray; } \
+        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
+        const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
+        size_t size() const { return mSize; } \
+    private: \
+        static const jsize buffer_size = 1024; \
+        JNIEnv* const mEnv; \
+        PRIMITIVE_TYPE ## Array mJavaArray; \
+        POINTER_TYPE(PRIMITIVE_TYPE) mRawArray; \
+        jsize mSize; \
+        PRIMITIVE_TYPE mBuffer[buffer_size]; \
+        DISALLOW_COPY_AND_ASSIGN(Scoped ## NAME ## ArrayRO); \
+    }
+
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jboolean, Boolean);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jbyte, Byte);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jchar, Char);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jdouble, Double);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jfloat, Float);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jint, Int);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jlong, Long);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jshort, Short);
+
+#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO
+
+// ScopedBooleanArrayRW, ScopedByteArrayRW, ScopedCharArrayRW, ScopedDoubleArrayRW,
+// ScopedFloatArrayRW, ScopedIntArrayRW, ScopedLongArrayRW, and ScopedShortArrayRW provide
+// convenient read-write access to Java arrays from JNI code. These are more expensive,
+// since they entail a copy back onto the Java heap, and should only be used when necessary.
+#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(PRIMITIVE_TYPE, NAME) \
+    class Scoped ## NAME ## ArrayRW { \
+    public: \
+        explicit Scoped ## NAME ## ArrayRW(JNIEnv* env) \
+        : mEnv(env), mJavaArray(nullptr), mRawArray(nullptr) {} \
+        Scoped ## NAME ## ArrayRW(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \
+        : mEnv(env), mJavaArray(javaArray), mRawArray(nullptr) { \
+            if (mJavaArray == nullptr) { \
+                JniThrowNullPointerException(env, nullptr); \
+            } else { \
+                mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, nullptr); \
+            } \
+        } \
+        ~Scoped ## NAME ## ArrayRW() { \
+            if (mRawArray) { \
+                mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, 0); \
+            } \
+        } \
+        void reset(PRIMITIVE_TYPE ## Array javaArray) { \
+            mJavaArray = javaArray; \
+            mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, nullptr); \
+        } \
+        const PRIMITIVE_TYPE* get() const { return mRawArray; } \
+        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
+        const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
+        POINTER_TYPE(PRIMITIVE_TYPE) get() { return mRawArray; }  \
+        REFERENCE_TYPE(PRIMITIVE_TYPE) operator[](size_t n) { return mRawArray[n]; } \
+        size_t size() const { return mEnv->GetArrayLength(mJavaArray); } \
+    private: \
+        JNIEnv* const mEnv; \
+        PRIMITIVE_TYPE ## Array mJavaArray; \
+        POINTER_TYPE(PRIMITIVE_TYPE) mRawArray; \
+        DISALLOW_COPY_AND_ASSIGN(Scoped ## NAME ## ArrayRW); \
+    }
+
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jboolean, Boolean);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jbyte, Byte);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jchar, Char);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jdouble, Double);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jfloat, Float);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jint, Int);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jlong, Long);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jshort, Short);
+
+#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW
+#undef POINTER_TYPE
+#undef REFERENCE_TYPE
+
+}
+}
+
+#endif  // SCOPED_PRIMITIVE_ARRAY_H_
diff --git a/hostsidetests/jvmti/base/jni/scoped_utf_chars.h b/hostsidetests/jvmti/base/jni/scoped_utf_chars.h
new file mode 100644
index 0000000..8d28a7a
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/scoped_utf_chars.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef SCOPED_UTF_CHARS_H_
+#define SCOPED_UTF_CHARS_H_
+
+#include "jni.h"
+
+#include <string.h>
+
+#include "android-base/macros.h"
+#include "common.h"
+
+namespace cts {
+namespace jvmti {
+
+class ScopedUtfChars {
+ public:
+  ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) {
+    if (s == nullptr) {
+      utf_chars_ = nullptr;
+      JniThrowNullPointerException(env, nullptr);
+    } else {
+      utf_chars_ = env->GetStringUTFChars(s, nullptr);
+    }
+  }
+
+  ScopedUtfChars(ScopedUtfChars&& rhs) :
+      env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) {
+    rhs.env_ = nullptr;
+    rhs.string_ = nullptr;
+    rhs.utf_chars_ = nullptr;
+  }
+
+  ~ScopedUtfChars() {
+    if (utf_chars_) {
+      env_->ReleaseStringUTFChars(string_, utf_chars_);
+    }
+  }
+
+  ScopedUtfChars& operator=(ScopedUtfChars&& rhs) {
+    if (this != &rhs) {
+      // Delete the currently owned UTF chars.
+      this->~ScopedUtfChars();
+
+      // Move the rhs ScopedUtfChars and zero it out.
+      env_ = rhs.env_;
+      string_ = rhs.string_;
+      utf_chars_ = rhs.utf_chars_;
+      rhs.env_ = nullptr;
+      rhs.string_ = nullptr;
+      rhs.utf_chars_ = nullptr;
+    }
+    return *this;
+  }
+
+  const char* c_str() const {
+    return utf_chars_;
+  }
+
+  size_t size() const {
+    return strlen(utf_chars_);
+  }
+
+  const char& operator[](size_t n) const {
+    return utf_chars_[n];
+  }
+
+ private:
+  JNIEnv* env_;
+  jstring string_;
+  const char* utf_chars_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedUtfChars);
+};
+
+}
+}
+
+#endif  // SCOPED_UTF_CHARS_H_