ART: Add system properties support

Add simple support for GetSystemProperties, GetSystemProperty and
SetSystemProperty. Add a test.

Bug: 31455788
Test: m test-art-host-run-test-922-properties
Change-Id: I02914f04643f0f8fab96f1b372925c2c5306fc9b
diff --git a/test/910-methods/methods.cc b/test/910-methods/methods.cc
index 0f8892e..fa9679d 100644
--- a/test/910-methods/methods.cc
+++ b/test/910-methods/methods.cc
@@ -114,33 +114,13 @@
   return modifiers;
 }
 
-static bool ErrorToException(JNIEnv* 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;
-  jvmti_env->GetErrorName(error, &err);
-
-  env->ThrowNew(rt_exception.get(), err);
-
-  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
-  return true;
-}
-
 extern "C" JNIEXPORT jint JNICALL Java_Main_getMaxLocals(
     JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method) {
   jmethodID id = env->FromReflectedMethod(method);
 
   jint max_locals;
   jvmtiError result = jvmti_env->GetMaxLocals(id, &max_locals);
-  if (ErrorToException(env, result)) {
+  if (JvmtiErrorToException(env, result)) {
     return -1;
   }
 
@@ -153,7 +133,7 @@
 
   jint arguments;
   jvmtiError result = jvmti_env->GetArgumentsSize(id, &arguments);
-  if (ErrorToException(env, result)) {
+  if (JvmtiErrorToException(env, result)) {
     return -1;
   }
 
@@ -167,7 +147,7 @@
   jlong start;
   jlong end;
   jvmtiError result = jvmti_env->GetMethodLocation(id, &start, &end);
-  if (ErrorToException(env, result)) {
+  if (JvmtiErrorToException(env, result)) {
     return -1;
   }
 
@@ -181,7 +161,7 @@
   jlong start;
   jlong end;
   jvmtiError result = jvmti_env->GetMethodLocation(id, &start, &end);
-  if (ErrorToException(env, result)) {
+  if (JvmtiErrorToException(env, result)) {
     return -1;
   }
 
@@ -194,7 +174,7 @@
 
   jboolean is_native;
   jvmtiError result = jvmti_env->IsMethodNative(id, &is_native);
-  if (ErrorToException(env, result)) {
+  if (JvmtiErrorToException(env, result)) {
     return JNI_FALSE;
   }
 
@@ -207,7 +187,7 @@
 
   jboolean is_obsolete;
   jvmtiError result = jvmti_env->IsMethodObsolete(id, &is_obsolete);
-  if (ErrorToException(env, result)) {
+  if (JvmtiErrorToException(env, result)) {
     return JNI_FALSE;
   }
 
@@ -220,7 +200,7 @@
 
   jboolean is_synthetic;
   jvmtiError result = jvmti_env->IsMethodSynthetic(id, &is_synthetic);
-  if (ErrorToException(env, result)) {
+  if (JvmtiErrorToException(env, result)) {
     return JNI_FALSE;
   }
 
diff --git a/test/922-properties/build b/test/922-properties/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/922-properties/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/922-properties/expected.txt b/test/922-properties/expected.txt
new file mode 100644
index 0000000..0be939b
--- /dev/null
+++ b/test/922-properties/expected.txt
@@ -0,0 +1,59 @@
+Recommended properties:
+ "java.class.path": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.library.path": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vm.info": OK !!!JVMTI_ERROR_NOT_AVAILABLE
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vm.name": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vm.vendor": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vm.version": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+Missing recommended properties: [java.vm.info]
+Other properties:
+ "file.encoding": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "file.separator": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.class.version": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.compiler": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.ext.dirs": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.net.preferIPv6Addresses": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.specification.name": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.specification.vendor": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.specification.version": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vendor": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vendor.url": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.version": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vm.specification.name": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vm.specification.vendor": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vm.specification.version": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "java.vm.vendor.url": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "line.separator": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "os.name": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+ "path.separator": OK
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+Non-specified property:
+ "java.boot.class.path": ERROR !!!JVMTI_ERROR_NOT_AVAILABLE
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
+Non-specified property (2):
+ "a": OK !!!JVMTI_ERROR_NOT_AVAILABLE
+  Setting value to "abc": !!!JVMTI_ERROR_NOT_AVAILABLE
diff --git a/test/922-properties/info.txt b/test/922-properties/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/922-properties/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/922-properties/properties.cc b/test/922-properties/properties.cc
new file mode 100644
index 0000000..b1e7fce
--- /dev/null
+++ b/test/922-properties/properties.cc
@@ -0,0 +1,107 @@
+/*
+ * 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 "properties.h"
+
+#include <stdio.h>
+
+#include "base/macros.h"
+#include "jni.h"
+#include "openjdkjvmti/jvmti.h"
+#include "ScopedUtfChars.h"
+
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test922Properties {
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getSystemProperties(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) {
+  jint count;
+  char** properties;
+  jvmtiError result = jvmti_env->GetSystemProperties(&count, &properties);
+  if (JvmtiErrorToException(env, result)) {
+    return nullptr;
+  }
+
+  auto callback = [&](jint i) -> jstring {
+    char* data = properties[i];
+    if (data == nullptr) {
+      return nullptr;
+    }
+    jstring ret = env->NewStringUTF(data);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(data));
+    return ret;
+  };
+  jobjectArray ret = CreateObjectArray(env, count, "java/lang/String", callback);
+
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(properties));
+
+  return ret;
+}
+
+extern "C" JNIEXPORT jstring JNICALL Java_Main_getSystemProperty(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring key) {
+  ScopedUtfChars string(env, key);
+  if (string.c_str() == nullptr) {
+    return nullptr;
+  }
+
+  char* value = nullptr;
+  jvmtiError result = jvmti_env->GetSystemProperty(string.c_str(), &value);
+  if (JvmtiErrorToException(env, result)) {
+    return nullptr;
+  }
+
+  jstring ret = (value == nullptr) ? nullptr : env->NewStringUTF(value);
+
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(value));
+
+  return ret;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_setSystemProperty(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring key, jstring value) {
+  ScopedUtfChars key_string(env, key);
+  if (key_string.c_str() == nullptr) {
+    return;
+  }
+  ScopedUtfChars value_string(env, value);
+  if (value_string.c_str() == nullptr) {
+    return;
+  }
+
+  jvmtiError result = jvmti_env->SetSystemProperty(key_string.c_str(), value_string.c_str());
+  if (JvmtiErrorToException(env, result)) {
+    return;
+  }
+}
+
+// Don't do anything
+jint OnLoad(JavaVM* vm,
+            char* options ATTRIBUTE_UNUSED,
+            void* reserved ATTRIBUTE_UNUSED) {
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) {
+    printf("Unable to get jvmti env!\n");
+    return 1;
+  }
+  SetAllCapabilities(jvmti_env);
+  return 0;
+}
+
+}  // namespace Test922Properties
+}  // namespace art
diff --git a/test/922-properties/properties.h b/test/922-properties/properties.h
new file mode 100644
index 0000000..84feb10
--- /dev/null
+++ b/test/922-properties/properties.h
@@ -0,0 +1,30 @@
+/*
+ * 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 ART_TEST_922_PROPERTIES_PROPERTIES_H_
+#define ART_TEST_922_PROPERTIES_PROPERTIES_H_
+
+#include <jni.h>
+
+namespace art {
+namespace Test922Properties {
+
+jint OnLoad(JavaVM* vm, char* options, void* reserved);
+
+}  // namespace Test922Properties
+}  // namespace art
+
+#endif  // ART_TEST_922_PROPERTIES_PROPERTIES_H_
diff --git a/test/922-properties/run b/test/922-properties/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/922-properties/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/922-properties/src/Main.java b/test/922-properties/src/Main.java
new file mode 100644
index 0000000..6cec6e9
--- /dev/null
+++ b/test/922-properties/src/Main.java
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+
+import java.util.Set;
+import java.util.TreeSet;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[1]);
+
+    doTest();
+  }
+
+  public static void doTest() throws Exception {
+    Set<String> recommendedProperties = getRecommendedProperties();
+
+    System.out.println("Recommended properties:");
+    for (String key : recommendedProperties) {
+      checkProperty(key);
+    }
+
+    Set<String> allProperties = getAllProperties();
+
+    Set<String> retained = new TreeSet<String>(recommendedProperties);
+    retained.retainAll(allProperties);
+    if (!retained.equals(recommendedProperties)) {
+      Set<String> missing = new TreeSet<String>(recommendedProperties);
+      missing.removeAll(retained);
+      System.out.println("Missing recommended properties: " + missing);
+    }
+
+    Set<String> nonRecommended = new TreeSet<String>(allProperties);
+    nonRecommended.removeAll(recommendedProperties);
+
+    System.out.println("Other properties:");
+    for (String key : nonRecommended) {
+      checkProperty(key);
+    }
+
+    System.out.println("Non-specified property:");
+    String key = generate(allProperties);
+    checkProperty(key);
+
+    System.out.println("Non-specified property (2):");
+    String key2 = generateUnique(allProperties);
+    checkProperty(key2);
+  }
+
+  private static Set<String> getRecommendedProperties() {
+    Set<String> keys = new TreeSet<String>();
+    keys.add("java.vm.vendor");
+    keys.add("java.vm.version");
+    keys.add("java.vm.name");
+    keys.add("java.vm.info");
+    keys.add("java.library.path");
+    keys.add("java.class.path");
+    return keys;
+  }
+
+  private static Set<String> getAllProperties() {
+    Set<String> keys = new TreeSet<String>();
+    String[] props = getSystemProperties();
+    for (String p : props) {
+      keys.add(p);
+    }
+    return keys;
+  }
+
+  private static boolean equals(String s1, String s2) {
+    if (s1 == null && s2 == null) {
+      return true;
+    } else if (s1 != null) {
+      return s1.equals(s2);
+    } else {
+      return false;
+    }
+  }
+
+  private static void checkProperty(String key) {
+    System.out.print(" \"" + key + "\": ");
+    String err = null;
+    String value = null;
+    try {
+      value = getSystemProperty(key);
+    } catch (RuntimeException e) {
+      err = e.getMessage();
+    }
+    String sysValue = System.getProperty(key);
+    if (equals(value, sysValue)) {
+      System.out.print("OK");
+      if (err != null) {
+        System.out.println(" !!!" + err);
+      } else {
+        System.out.println();
+      }
+    } else {
+      System.out.println("ERROR !!!" + err);
+    }
+
+    System.out.print("  Setting value to \"abc\": ");
+    try {
+      setSystemProperty(key, "abc");
+      System.out.println("SUCCEEDED");
+    } catch (RuntimeException e) {
+      System.out.println("!!!" + e.getMessage());
+    }
+  }
+
+  private static String generateUnique(Set<String> others) {
+    // Construct something. To be deterministic, just use "a+".
+    StringBuilder sb = new StringBuilder("a");
+    for (;;) {
+      String key = sb.toString();
+      if (!others.contains(key)) {
+        return key;
+      }
+      sb.append('a');
+    }
+  }
+
+  private static String generate(Set<String> others) {
+    // First check for something in the overall System properties.
+    TreeSet<String> sysProps = new TreeSet<String>(System.getProperties().stringPropertyNames());
+    sysProps.removeAll(others);
+    if (!sysProps.isEmpty()) {
+      // Find something that starts with "java" or "os," trying to be platform-independent.
+      for (String s: sysProps) {
+        if (s.startsWith("java.") || s.startsWith("os.")) {
+          return s;
+        }
+      }
+      // Just return the first thing.
+      return sysProps.iterator().next();
+    }
+
+    return generateUnique(others);
+  }
+
+  private static native String[] getSystemProperties();
+  private static native String getSystemProperty(String key);
+  private static native void setSystemProperty(String key, String value);
+}
diff --git a/test/Android.bp b/test/Android.bp
index 5a8f43e..f6648d1 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -262,6 +262,7 @@
         "913-heaps/heaps.cc",
         "918-fields/fields.cc",
         "920-objects/objects.cc",
+        "922-properties/properties.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index c48f573..a3f6864 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -294,6 +294,7 @@
   919-obsolete-fields \
   920-objects \
   921-hello-failure \
+  922-properties \
 
 ifneq (,$(filter target,$(TARGET_TYPES)))
   ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \
diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc
index 01b6c98..6f98f10 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -40,6 +40,26 @@
   env->AddCapabilities(&caps);
 }
 
+bool JvmtiErrorToException(JNIEnv* 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;
+  jvmti_env->GetErrorName(error, &err);
+
+  env->ThrowNew(rt_exception.get(), err);
+
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+  return true;
+}
+
 namespace common_redefine {
 
 static void throwRedefinitionError(jvmtiEnv* jvmti, JNIEnv* env, jclass target, jvmtiError res) {
diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h
index 76543fe..642ca03 100644
--- a/test/ti-agent/common_helper.h
+++ b/test/ti-agent/common_helper.h
@@ -65,6 +65,8 @@
 
 void SetAllCapabilities(jvmtiEnv* env);
 
+bool JvmtiErrorToException(JNIEnv* env, jvmtiError error);
+
 }  // namespace art
 
 #endif  // ART_TEST_TI_AGENT_COMMON_HELPER_H_
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index d7579ca..e309a89 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -39,6 +39,7 @@
 #include "913-heaps/heaps.h"
 #include "918-fields/fields.h"
 #include "920-objects/objects.h"
+#include "922-properties/properties.h"
 
 namespace art {
 
@@ -76,6 +77,7 @@
   { "919-obsolete-fields", common_redefine::OnLoad, nullptr },
   { "920-objects", Test920Objects::OnLoad, nullptr },
   { "921-hello-failure", common_redefine::OnLoad, nullptr },
+  { "922-properties", Test922Properties::OnLoad, nullptr },
 };
 
 static AgentLib* FindAgent(char* name) {