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/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp
index d486380..be06dd7 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -25,6 +25,7 @@
            "ti_heap.cc",
            "ti_method.cc",
            "ti_object.cc",
+           "ti_properties.cc",
            "ti_stack.cc",
            "ti_redefine.cc",
            "transform.cc"],
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index c472b54..936049f 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -51,6 +51,7 @@
 #include "ti_heap.h"
 #include "ti_method.h"
 #include "ti_object.h"
+#include "ti_properties.h"
 #include "ti_redefine.h"
 #include "ti_stack.h"
 #include "transform.h"
@@ -1034,15 +1035,15 @@
   }
 
   static jvmtiError GetSystemProperties(jvmtiEnv* env, jint* count_ptr, char*** property_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return PropertiesUtil::GetSystemProperties(env, count_ptr, property_ptr);
   }
 
   static jvmtiError GetSystemProperty(jvmtiEnv* env, const char* property, char** value_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return PropertiesUtil::GetSystemProperty(env, property, value_ptr);
   }
 
   static jvmtiError SetSystemProperty(jvmtiEnv* env, const char* property, const char* value) {
-    return ERR(NOT_IMPLEMENTED);
+    return PropertiesUtil::SetSystemProperty(env, property, value);
   }
 
   static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr) {
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index 48b29a3..5eadc5a 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -105,9 +105,10 @@
 
 using JvmtiUniquePtr = std::unique_ptr<unsigned char, JvmtiDeleter>;
 
+template <typename T>
 ALWAYS_INLINE
-static inline JvmtiUniquePtr MakeJvmtiUniquePtr(jvmtiEnv* env, unsigned char* mem) {
-  return JvmtiUniquePtr(mem, JvmtiDeleter(env));
+static inline JvmtiUniquePtr MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) {
+  return JvmtiUniquePtr(reinterpret_cast<unsigned char*>(mem), JvmtiDeleter(env));
 }
 
 ALWAYS_INLINE
diff --git a/runtime/openjdkjvmti/ti_properties.cc b/runtime/openjdkjvmti/ti_properties.cc
new file mode 100644
index 0000000..46b9e71
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_properties.cc
@@ -0,0 +1,192 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_properties.h"
+
+#include <string.h>
+#include <vector>
+
+#include "art_jvmti.h"
+#include "runtime.h"
+
+namespace openjdkjvmti {
+
+// Hardcoded properties. Tests ensure that these are consistent with libcore's view, as seen
+// in System.java and AndroidHardcodedSystemProperties.java.
+static constexpr const char* kProperties[][2] = {
+    // Recommended by the spec.
+    { "java.vm.vendor", "The Android Project" },
+    { "java.vm.version", "2.1.0" },  // This is Runtime::GetVersion().
+    { "java.vm.name", "Dalvik" },
+    // Android does not provide java.vm.info.
+    //
+    // These are other values provided by AndroidHardcodedSystemProperties.
+    { "java.class.version", "50.0" },
+    { "java.version", "0" },
+    { "java.compiler", "" },
+    { "java.ext.dirs", "" },
+
+    { "java.specification.name", "Dalvik Core Library" },
+    { "java.specification.vendor", "The Android Project" },
+    { "java.specification.version", "0.9" },
+
+    { "java.vendor", "The Android Project" },
+    { "java.vendor.url", "http://www.android.com/" },
+    { "java.vm.name", "Dalvik" },
+    { "java.vm.specification.name", "Dalvik Virtual Machine Specification" },
+    { "java.vm.specification.vendor", "The Android Project" },
+    { "java.vm.specification.version", "0.9" },
+    { "java.vm.vendor", "The Android Project" },
+
+    { "java.vm.vendor.url", "http://www.android.com/" },
+
+    { "java.net.preferIPv6Addresses", "false" },
+
+    { "file.encoding", "UTF-8" },
+
+    { "file.separator", "/" },
+    { "line.separator", "\n" },
+    { "path.separator", ":" },
+
+    { "os.name", "Linux" },
+};
+static constexpr size_t kPropertiesSize = arraysize(kProperties);
+static constexpr const char* kPropertyLibraryPath = "java.library.path";
+static constexpr const char* kPropertyClassPath = "java.class.path";
+
+static jvmtiError Copy(jvmtiEnv* env, const char* in, char** out) {
+  unsigned char* data = nullptr;
+  jvmtiError result = CopyString(env, in, &data);
+  *out = reinterpret_cast<char*>(data);
+  return result;
+}
+
+jvmtiError PropertiesUtil::GetSystemProperties(jvmtiEnv* env,
+                                               jint* count_ptr,
+                                               char*** property_ptr) {
+  if (count_ptr == nullptr || property_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  unsigned char* array_data;
+  jvmtiError array_alloc_result = env->Allocate((kPropertiesSize + 2) * sizeof(char*), &array_data);
+  if (array_alloc_result != ERR(NONE)) {
+    return array_alloc_result;
+  }
+  JvmtiUniquePtr array_data_ptr = MakeJvmtiUniquePtr(env, array_data);
+  char** array = reinterpret_cast<char**>(array_data);
+
+  std::vector<JvmtiUniquePtr> property_copies;
+
+  {
+    char* libpath_data;
+    jvmtiError libpath_result = Copy(env, kPropertyLibraryPath, &libpath_data);
+    if (libpath_result != ERR(NONE)) {
+      return libpath_result;
+    }
+    array[0] = libpath_data;
+    property_copies.push_back(MakeJvmtiUniquePtr(env, libpath_data));
+  }
+
+  {
+    char* classpath_data;
+    jvmtiError classpath_result = Copy(env, kPropertyClassPath, &classpath_data);
+    if (classpath_result != ERR(NONE)) {
+      return classpath_result;
+    }
+    array[1] = classpath_data;
+    property_copies.push_back(MakeJvmtiUniquePtr(env, classpath_data));
+  }
+
+  for (size_t i = 0; i != kPropertiesSize; ++i) {
+    char* data;
+    jvmtiError data_result = Copy(env, kProperties[i][0], &data);
+    if (data_result != ERR(NONE)) {
+      return data_result;
+    }
+    array[i + 2] = data;
+    property_copies.push_back(MakeJvmtiUniquePtr(env, data));
+  }
+
+  // Everything is OK, release the data.
+  array_data_ptr.release();
+  for (auto& uptr : property_copies) {
+    uptr.release();
+  }
+
+  *count_ptr = kPropertiesSize + 2;
+  *property_ptr = array;
+
+  return ERR(NONE);
+}
+
+jvmtiError PropertiesUtil::GetSystemProperty(jvmtiEnv* env,
+                                             const char* property,
+                                             char** value_ptr) {
+  if (property == nullptr || value_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  if (strcmp(property, kPropertyLibraryPath) == 0) {
+    // TODO: In the live phase, we should probably compare to System.getProperty. java.library.path
+    //       may not be set initially, and is then freely modifiable.
+    const std::vector<std::string>& runtime_props = art::Runtime::Current()->GetProperties();
+    for (const std::string& prop_assignment : runtime_props) {
+      size_t assign_pos = prop_assignment.find('=');
+      if (assign_pos != std::string::npos && assign_pos > 0) {
+        if (prop_assignment.substr(0, assign_pos) == kPropertyLibraryPath) {
+          return Copy(env, prop_assignment.substr(assign_pos + 1).c_str(), value_ptr);
+        }
+      }
+    }
+    return ERR(NOT_AVAILABLE);
+  }
+
+  if (strcmp(property, kPropertyClassPath) == 0) {
+    return Copy(env, art::Runtime::Current()->GetClassPathString().c_str(), value_ptr);
+  }
+
+  for (size_t i = 0; i != kPropertiesSize; ++i) {
+    if (strcmp(property, kProperties[i][0]) == 0) {
+      return Copy(env, kProperties[i][1], value_ptr);
+    }
+  }
+
+  return ERR(NOT_AVAILABLE);
+}
+
+jvmtiError PropertiesUtil::SetSystemProperty(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                             const char* property ATTRIBUTE_UNUSED,
+                                             const char* value ATTRIBUTE_UNUSED) {
+  // We do not allow manipulation of any property here.
+  return ERR(NOT_AVAILABLE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_properties.h b/runtime/openjdkjvmti/ti_properties.h
new file mode 100644
index 0000000..7073481
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_properties.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_PROPERTIES_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_PROPERTIES_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class PropertiesUtil {
+ public:
+  static jvmtiError GetSystemProperties(jvmtiEnv* env, jint* count_ptr, char*** property_ptr);
+
+  static jvmtiError GetSystemProperty(jvmtiEnv* env, const char* property, char** value_ptr);
+
+  static jvmtiError SetSystemProperty(jvmtiEnv* env, const char* property, const char* value);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_PROPERTIES_H_
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) {