CTS: Add a JVMTI tagging test

Copy ART run-test for basic tagging functionality to CTS. Change
implementation to compare in code instead of text output.

(cherry picked from commit 5358ee6cfd8f22790e90ca46345b76787e00d8a8)

Bug: 32072923
Test: m cts
Test: cts-tradefed run cts-dev --module CtsJvmtiTaggingHostTestCases
Change-Id: I20147b823a8f9e5d288e8e5edc195ab1101375b4
diff --git a/hostsidetests/jvmti/base/jni/Android.mk b/hostsidetests/jvmti/base/jni/Android.mk
index de7428c..295f4c8 100644
--- a/hostsidetests/jvmti/base/jni/Android.mk
+++ b/hostsidetests/jvmti/base/jni/Android.mk
@@ -25,6 +25,9 @@
                    jni_binder.cpp \
                    jvmti_helper.cpp \
 
+# Tagging.
+LOCAL_SRC_FILES += tagging.cpp
+
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 LOCAL_HEADER_LIBRARIES := libopenjdkjvmti_headers
 
diff --git a/hostsidetests/jvmti/base/jni/tagging.cpp b/hostsidetests/jvmti/base/jni/tagging.cpp
new file mode 100644
index 0000000..7ef996c
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/tagging.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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 "android-base/logging.h"
+#include "android-base/macros.h"
+#include "common.h"
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "jvmti.h"
+#include "scoped_primitive_array.h"
+
+namespace cts {
+namespace jvmti {
+namespace tagging {
+
+extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JvmtiTaggingTest_setTag(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject obj, jlong tag) {
+  jvmtiEnv* jvmti_env = GetJvmtiEnv();
+  jvmtiError ret = jvmti_env->SetTag(obj, tag);
+  JvmtiErrorToException(env, jvmti_env, ret);
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_android_jvmti_cts_JvmtiTaggingTest_getTag(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject obj) {
+  jvmtiEnv* jvmti_env = GetJvmtiEnv();
+  jlong tag = 0;
+  jvmtiError ret = jvmti_env->GetTag(obj, &tag);
+  if (JvmtiErrorToException(env, jvmti_env, ret)) {
+    return 0;
+  }
+  return tag;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_android_jvmti_cts_JvmtiTaggingTest_getTaggedObjects(
+    JNIEnv* env,
+    jclass klass ATTRIBUTE_UNUSED,
+    jlongArray searchTags,
+    jboolean returnObjects,
+    jboolean returnTags) {
+  jvmtiEnv* jvmti_env = GetJvmtiEnv();
+  ScopedLongArrayRO scoped_array(env);
+  if (searchTags != nullptr) {
+    scoped_array.reset(searchTags);
+  }
+  const jlong* tag_ptr = scoped_array.get();
+  if (tag_ptr == nullptr) {
+    // Can never pass null.
+    tag_ptr = reinterpret_cast<const jlong*>(1);
+  }
+
+  jint result_count = -1;
+  jobject* result_object_array = nullptr;
+  jobject** result_object_array_ptr = returnObjects == JNI_TRUE ? &result_object_array : nullptr;
+  jlong* result_tag_array = nullptr;
+  jlong** result_tag_array_ptr = returnTags == JNI_TRUE ? &result_tag_array : nullptr;
+
+  jvmtiError ret = jvmti_env->GetObjectsWithTags(scoped_array.size(),
+                                                 tag_ptr,
+                                                 &result_count,
+                                                 result_object_array_ptr,
+                                                 result_tag_array_ptr);
+  if (JvmtiErrorToException(env, jvmti_env, ret)) {
+    return nullptr;
+  }
+
+  CHECK_GE(result_count, 0);
+
+  jobjectArray resultObjectArray = nullptr;
+  if (returnObjects == JNI_TRUE) {
+    auto callback = [&](jint i) {
+      return result_object_array[i];
+    };
+    resultObjectArray = CreateObjectArray(env, result_count, "java/lang/Object", callback);
+    if (resultObjectArray == nullptr) {
+      return nullptr;
+    }
+  }
+  if (result_object_array != nullptr) {
+    CheckJvmtiError(jvmti_env, Deallocate(jvmti_env, result_object_array));
+  }
+
+  jlongArray resultTagArray = nullptr;
+  if (returnTags == JNI_TRUE) {
+    resultTagArray = env->NewLongArray(result_count);
+    if (resultTagArray == nullptr) {
+      return nullptr;
+    }
+    env->SetLongArrayRegion(resultTagArray, 0, result_count, result_tag_array);
+  }
+  if (result_tag_array != nullptr) {
+    CheckJvmtiError(jvmti_env, Deallocate(jvmti_env, result_tag_array));
+  }
+
+  jobject count_integer;
+  {
+    ScopedLocalRef<jclass> integer_class(env, env->FindClass("java/lang/Integer"));
+    jmethodID methodID = env->GetMethodID(integer_class.get(), "<init>", "(I)V");
+    count_integer = env->NewObject(integer_class.get(), methodID, result_count);
+    if (count_integer == nullptr) {
+      return nullptr;
+    }
+  }
+
+  auto callback = [&](jint i) -> jobject {
+    switch(i) {
+      case 0:
+        return resultObjectArray;
+      case 1:
+        return resultTagArray;
+      case 2:
+        return count_integer;
+      default:
+        LOG(FATAL) << "Unexpected";
+        return nullptr;
+    }
+  };
+  return CreateObjectArray(env, 3, "java/lang/Object", callback);
+}
+
+}  // namespace tagging
+}  // namespace jvmti
+}  // namespace cts
+
diff --git a/hostsidetests/jvmti/tagging/Android.mk b/hostsidetests/jvmti/tagging/Android.mk
new file mode 100644
index 0000000..61e06a4
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/Android.mk
@@ -0,0 +1,26 @@
+# 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 $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsJvmtiTaggingHostTestCases
+LOCAL_STATIC_JAVA_LIBRARIES := CtsJvmtiHostTestBase
+LOCAL_MODULE_TAGS := tests
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/jvmti/tagging/AndroidTest.xml b/hostsidetests/jvmti/tagging/AndroidTest.xml
new file mode 100644
index 0000000..6d76722
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<configuration description="Config for CTS JVMTI test cases">
+    <target_preparer class="android.jvmti.cts.JvmtiPreparer">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsJvmtiTaggingDeviceApp.apk" />
+        <option name="package-name" value="android.jvmti.cts.tagging" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsJvmtiTaggingHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/jvmti/tagging/app/Android.mk b/hostsidetests/jvmti/tagging/app/Android.mk
new file mode 100644
index 0000000..343dc32
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/app/Android.mk
@@ -0,0 +1,33 @@
+# 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 := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_STATIC_JAVA_LIBRARIES := CtsJvmtiDeviceAppBase
+LOCAL_JNI_SHARED_LIBRARIES := libctsjvmtiagent
+LOCAL_MULTILIB := both
+LOCAL_SDK_VERSION := current
+
+# TODO: Refactor. This is the only thing every changing.
+LOCAL_PACKAGE_NAME := CtsJvmtiTaggingDeviceApp
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/jvmti/tagging/app/AndroidManifest.xml b/hostsidetests/jvmti/tagging/app/AndroidManifest.xml
new file mode 100755
index 0000000..03509ce
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/app/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.jvmti.cts.tagging">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.jvmti.JvmtiActivity" >
+        </activity>
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:label="CTS tests for JVMTI"
+        android:targetPackage="android.jvmti.cts.tagging" >
+    </instrumentation>
+</manifest>
+
diff --git a/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java b/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java
new file mode 100644
index 0000000..d2cfe96
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java
@@ -0,0 +1,252 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Check tagging-related functionality.
+ */
+public class JvmtiTaggingTest extends JvmtiTestBase {
+
+    @Before
+    public void setUp() throws Exception {
+        // Bind our native methods.
+        JniBindings.bindAgentJNI("android/jvmti/cts/JvmtiTaggingTest", getClass().getClassLoader());
+    }
+
+    private static WeakReference<Object> test() {
+        Object o1 = new Object();
+        setTag(o1, 1);
+
+        Object o2 = new Object();
+        setTag(o2, 2);
+
+        assertEquals(1, getTag(o1));
+        assertEquals(2, getTag(o2));
+
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().gc();
+
+        assertEquals(1, getTag(o1));
+        assertEquals(2, getTag(o2));
+
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().gc();
+
+        setTag(o1, 10);
+        setTag(o2, 20);
+
+        assertEquals(10, getTag(o1));
+        assertEquals(20, getTag(o2));
+
+        return new WeakReference<Object>(o1);
+    }
+
+    // Very simplistic tagging.
+    @Test
+    public void testTagging() throws Exception {
+        test();
+    }
+
+    @Test
+    public void testTaggingGC() {
+        WeakReference<Object> weak = test();
+
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().gc();
+
+        if (weak.get() != null) {
+            throw new RuntimeException("WeakReference not cleared");
+        }
+    }
+
+    private ArrayList<Object> l;
+
+    @Test
+    public void testGetTaggedObjects() {
+        // Use an array list to ensure that the objects stay live for a bit. Also gives us a source
+        // to compare to. We use index % 10 as the tag.
+        l = new ArrayList<>();
+
+        for (int i = 0; i < 20; i++) {
+            Integer o = new Integer(i);
+            l.add(o);
+            if (i % 10 != 0) {
+                setTag(o, i % 10);
+            }
+        }
+
+        GetTaggedObjectsExpectation exp1 = new GetTaggedObjectsExpectation(18);
+        getTaggedObjectsRun(null, false, false, exp1);
+
+        GetTaggedObjectsExpectation exp2 = new GetTaggedObjectsExpectation(18);
+        exp2.add(l.get(1), 1).add(l.get(11), 1).add(l.get(2), 2).add(l.get(12), 2).add(l.get(3), 3)
+                .add(l.get(13), 3).add(l.get(4), 4).add(l.get(14), 4).add(l.get(5), 5)
+                .add(l.get(15), 5).add(l.get(6), 6).add(l.get(16), 6).add(l.get(7), 7)
+                .add(l.get(17), 7).add(l.get(8), 8).add(l.get(18), 8).add(l.get(9), 9)
+                .add(l.get(19), 9);
+        getTaggedObjectsRun(null, true, true, exp2);
+
+        GetTaggedObjectsExpectation exp3 = new GetTaggedObjectsExpectation(4);
+        exp3.add(l.get(2), 2).add(l.get(12), 2).add(l.get(5), 5).add(l.get(15), 5);
+        getTaggedObjectsRun(new long[] {2, 5}, true, true, exp3);
+
+        GetTaggedObjectsExpectation exp4 = new GetTaggedObjectsExpectation(18);
+        exp4.add(null, 1).add(null, 1).add(null, 2).add(null, 2).add(null, 3).add(null, 3)
+                .add(null, 4).add(null, 4).add(null, 5).add(null, 5).add(null, 6).add(null, 6)
+                .add(null, 7).add(null, 7).add(null, 8).add(null, 8).add(null, 9).add(null, 9);
+        getTaggedObjectsRun(null, false, true, exp4);
+
+        GetTaggedObjectsExpectation exp5 = new GetTaggedObjectsExpectation(18);
+        for (int i = 0; i < l.size(); i++) {
+            if (i % 10 != 0) {
+                exp5.add(l.get(i), 0);
+            }
+        }
+        getTaggedObjectsRun(null, true, false, exp5);
+
+        l = null;
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().gc();
+    }
+
+    private static void getTaggedObjectsRun(long[] searchTags, boolean returnObjects,
+            boolean returnTags, GetTaggedObjectsExpectation exp) {
+        Object[] result = getTaggedObjects(searchTags, returnObjects, returnTags);
+
+        Object[] objects = (Object[]) result[0];
+        long[] tags = (long[]) result[1];
+        int count = (int) result[2];
+
+        exp.check(count, objects, tags);
+    }
+
+    private static class GetTaggedObjectsExpectation {
+        List<Pair> expectations = new LinkedList<>();
+        int count;
+
+        public GetTaggedObjectsExpectation(int c) {
+            count = c;
+        }
+
+        public void check(int count, Object[] objects, long[] tags) {
+            assertEquals(this.count, count);
+
+            if (objects == null && tags == null) {
+                assertTrue(expectations.isEmpty());
+                return;
+            }
+
+            int l1 = objects == null ? 0 : objects.length;
+            int l2 = tags == null ? 0 : tags.length;
+            int l = Math.max(l1, l2);
+            List<Pair> tmp = new ArrayList<>(l);
+            for (int i = 0; i < l; i++) {
+                tmp.add(new Pair(objects == null ? null : objects[i], tags == null ? 0 : tags[i]));
+            }
+            Collections.sort(tmp);
+            Collections.sort(expectations);
+
+            if (!expectations.equals(tmp)) {
+                for (int i = 0; i < expectations.size(); i++) {
+                    Pair p1 = expectations.get(i);
+                    Pair p2 = tmp.get(i);
+                    if (!p1.equals(p2)) {
+                        String s = "Not equal: " + p1 + "[" + System.identityHashCode(p1.obj) +
+                                "] vs " + p2 + "[" + System.identityHashCode(p2.obj);
+                        throw new RuntimeException(s);
+                    }
+                }
+            }
+
+            assertEquals(expectations, tmp);
+        }
+
+        public GetTaggedObjectsExpectation add(Object o, long l) {
+            expectations.add(new Pair(o, l));
+            return this;
+        }
+    }
+
+    private static class Pair implements Comparable<Pair> {
+        Object obj;
+        long tag;
+
+        public Pair(Object o, long t) {
+            obj = o;
+            tag = t;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof Pair) {
+                Pair p = (Pair)obj;
+                return tag == p.tag && (p.obj == null ? this.obj == null : p.obj.equals(this.obj));
+            }
+            return false;
+        }
+
+        @Override
+        public int compareTo(Pair p) {
+            if (tag != p.tag) {
+                return Long.compare(tag, p.tag);
+            }
+
+            if ((obj instanceof Comparable) && (p.obj instanceof Comparable)) {
+                // It's not really correct, but w/e, best effort.
+                int result = ((Comparable<Object>) obj).compareTo(p.obj);
+                if (result != 0) {
+                    return result;
+                }
+            }
+
+            if (obj != null && p.obj != null) {
+                return obj.hashCode() - p.obj.hashCode();
+            }
+
+            if (obj != null) {
+                return 1;
+            }
+
+            if (p.obj != null) {
+                return -1;
+            }
+
+            return hashCode() - p.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return "<" + obj + ";" + tag + ">";
+        }
+    }
+
+
+    private static native void setTag(Object o, long tag);
+
+    private static native long getTag(Object o);
+
+    private static native Object[] getTaggedObjects(long[] searchTags, boolean returnObjects,
+            boolean returnTags);
+}