OSDN Git Service

Ensure that all redefinition created dex file get on classpath
authorAlex Light <allight@google.com>
Wed, 1 Feb 2017 23:35:34 +0000 (15:35 -0800)
committerAlex Light <allight@google.com>
Thu, 2 Feb 2017 19:13:25 +0000 (11:13 -0800)
We were not adding some DexFiles to the ClassPath if there were
multiple classes from the same classloader being redefined at the same
time. We fixed this issue and made a test for it.

Test: mma -j40 test-art-host

Change-Id: I6e8961c8602367ebec5d5a948d71e58f3be2f6d7

15 files changed:
runtime/openjdkjvmti/ti_class_loader.cc
runtime/openjdkjvmti/ti_class_loader.h
runtime/openjdkjvmti/ti_redefine.cc
runtime/openjdkjvmti/ti_redefine.h
test/944-transform-classloaders/build [new file with mode: 0755]
test/944-transform-classloaders/classloader.cc [new file with mode: 0644]
test/944-transform-classloaders/expected.txt [new file with mode: 0644]
test/944-transform-classloaders/info.txt [new file with mode: 0644]
test/944-transform-classloaders/run [new file with mode: 0755]
test/944-transform-classloaders/src/CommonClassDefinition.java [new file with mode: 0644]
test/944-transform-classloaders/src/Main.java [new file with mode: 0644]
test/944-transform-classloaders/src/Transform.java [new file with mode: 0644]
test/944-transform-classloaders/src/Transform2.java [new file with mode: 0644]
test/Android.bp
test/ti-agent/common_load.cc

index c2f1792..afec0bf 100644 (file)
@@ -62,7 +62,7 @@ bool ClassLoaderHelper::AddToClassLoader(art::Thread* self,
                                          art::Handle<art::mirror::ClassLoader> loader,
                                          const art::DexFile* dex_file) {
   art::ScopedObjectAccessUnchecked soa(self);
-  art::StackHandleScope<2> hs(self);
+  art::StackHandleScope<3> hs(self);
   if (art::ClassLinker::IsBootClassLoader(soa, loader.Get())) {
     art::Runtime::Current()->GetClassLinker()->AppendToBootClassPath(self, *dex_file);
     return true;
@@ -72,8 +72,9 @@ bool ClassLoaderHelper::AddToClassLoader(art::Thread* self,
   if (java_dex_file_obj.IsNull()) {
     return false;
   }
+  art::Handle<art::mirror::LongArray> old_cookie(hs.NewHandle(GetDexFileCookie(java_dex_file_obj)));
   art::Handle<art::mirror::LongArray> cookie(hs.NewHandle(
-      AllocateNewDexFileCookie(self, java_dex_file_obj, dex_file)));
+      AllocateNewDexFileCookie(self, old_cookie, dex_file)));
   if (cookie.IsNull()) {
     return false;
   }
@@ -99,12 +100,8 @@ void ClassLoaderHelper::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_
   }
 }
 
-// TODO Really wishing I had that mirror of java.lang.DexFile now.
-art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie(
-    art::Thread* self,
-    art::Handle<art::mirror::Object> java_dex_file_obj,
-    const art::DexFile* dex_file) {
-  art::StackHandleScope<2> hs(self);
+art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::GetDexFileCookie(
+    art::Handle<art::mirror::Object> java_dex_file_obj) {
   // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until
   // the object is finalized. Since they always point to the same array if mCookie is not null we
   // just use the mInternalCookie field. We will update one or both of these fields later.
@@ -113,9 +110,15 @@ art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie(
       "mInternalCookie", "Ljava/lang/Object;");
   // TODO Add check that mCookie is either null or same as mInternalCookie
   CHECK(internal_cookie_field != nullptr);
-  art::Handle<art::mirror::LongArray> cookie(
-      hs.NewHandle(internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray()));
-  // TODO Maybe make these non-fatal.
+  return internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray();
+}
+
+// TODO Really wishing I had that mirror of java.lang.DexFile now.
+art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie(
+    art::Thread* self,
+    art::Handle<art::mirror::LongArray> cookie,
+    const art::DexFile* dex_file) {
+  art::StackHandleScope<1> hs(self);
   CHECK(cookie.Get() != nullptr);
   CHECK_GE(cookie->GetLength(), 1);
   art::Handle<art::mirror::LongArray> new_cookie(
@@ -128,8 +131,9 @@ art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie(
   // TODO Should I clear this field?
   // TODO This is a really crappy thing here with the first element being different.
   new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0));
+  // This must match the casts in runtime/native/dalvik_system_DexFile.cc:ConvertDexFilesToJavaArray
   new_cookie->SetWithoutChecks<false>(
-      1, static_cast<int64_t>(reinterpret_cast<intptr_t>(dex_file)));
+      1, static_cast<int64_t>(reinterpret_cast<uintptr_t>(dex_file)));
   new_cookie->Memcpy(2, cookie.Get(), 1, cookie->GetLength() - 1);
   return new_cookie.Get();
 }
index 17ed0eb..1ac4988 100644 (file)
@@ -82,9 +82,12 @@ class ClassLoaderHelper {
       art::Thread* self, art::Handle<art::mirror::ClassLoader> loader)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
 
+  static art::ObjPtr<art::mirror::LongArray> GetDexFileCookie(
+      art::Handle<art::mirror::Object> java_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
   static art::ObjPtr<art::mirror::LongArray> AllocateNewDexFileCookie(
       art::Thread* self,
-      art::Handle<art::mirror::Object> java_dex_file,
+      art::Handle<art::mirror::LongArray> old_dex_file_cookie,
       const art::DexFile* new_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_);
 
   static void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
index 4b8108a..eb4c2f9 100644 (file)
@@ -701,6 +701,60 @@ class RedefinitionDataHolder {
   DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder);
 };
 
+// Looks through the previously allocated cookies to see if we need to update them with another new
+// dexfile. This is so that even if multiple classes with the same classloader are redefined at
+// once they are all added to the classloader.
+bool Redefiner::ClassRedefinition::AllocateAndRememberNewDexFileCookie(
+    int32_t klass_index,
+    art::Handle<art::mirror::ClassLoader> source_class_loader,
+    art::Handle<art::mirror::Object> dex_file_obj,
+    /*out*/RedefinitionDataHolder* holder) {
+  art::StackHandleScope<2> hs(driver_->self_);
+  art::MutableHandle<art::mirror::LongArray> old_cookie(
+      hs.NewHandle<art::mirror::LongArray>(nullptr));
+  bool has_older_cookie = false;
+  // See if we already have a cookie that a previous redefinition got from the same classloader.
+  for (int32_t i = 0; i < klass_index; i++) {
+    if (holder->GetSourceClassLoader(i) == source_class_loader.Get()) {
+      // Since every instance of this classloader should have the same cookie associated with it we
+      // can stop looking here.
+      has_older_cookie = true;
+      old_cookie.Assign(holder->GetNewDexFileCookie(i));
+      break;
+    }
+  }
+  if (old_cookie.IsNull()) {
+    // No older cookie. Get it directly from the dex_file_obj
+    // We should not have seen this classloader elsewhere.
+    CHECK(!has_older_cookie);
+    old_cookie.Assign(ClassLoaderHelper::GetDexFileCookie(dex_file_obj));
+  }
+  // Use the old cookie to generate the new one with the new DexFile* added in.
+  art::Handle<art::mirror::LongArray>
+      new_cookie(hs.NewHandle(ClassLoaderHelper::AllocateNewDexFileCookie(driver_->self_,
+                                                                          old_cookie,
+                                                                          dex_file_.get())));
+  // Make sure the allocation worked.
+  if (new_cookie.IsNull()) {
+    return false;
+  }
+
+  // Save the cookie.
+  holder->SetNewDexFileCookie(klass_index, new_cookie.Get());
+  // If there are other copies of this same classloader we need to make sure that we all have the
+  // same cookie.
+  if (has_older_cookie) {
+    for (int32_t i = 0; i < klass_index; i++) {
+      // We will let the GC take care of the cookie we allocated for this one.
+      if (holder->GetSourceClassLoader(i) == source_class_loader.Get()) {
+        holder->SetNewDexFileCookie(i, new_cookie.Get());
+      }
+    }
+  }
+
+  return true;
+}
+
 bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
     int32_t klass_index, /*out*/RedefinitionDataHolder* holder) {
   art::ScopedObjectAccessUnchecked soa(driver_->self_);
@@ -719,11 +773,8 @@ bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
       RecordFailure(ERR(INTERNAL), "Unable to find dex file!");
       return false;
     }
-    holder->SetNewDexFileCookie(klass_index,
-                                ClassLoaderHelper::AllocateNewDexFileCookie(driver_->self_,
-                                                                            dex_file_obj,
-                                                                            dex_file_.get()).Ptr());
-    if (holder->GetNewDexFileCookie(klass_index) == nullptr) {
+    // Allocate the new dex file cookie.
+    if (!AllocateAndRememberNewDexFileCookie(klass_index, loader, dex_file_obj, holder)) {
       driver_->self_->AssertPendingOOMException();
       driver_->self_->ClearException();
       RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader");
index 5bcaef8..5aa7dde 100644 (file)
@@ -145,6 +145,13 @@ class Redefiner {
     bool FinishRemainingAllocations(int32_t klass_index, /*out*/RedefinitionDataHolder* holder)
         REQUIRES_SHARED(art::Locks::mutator_lock_);
 
+    bool AllocateAndRememberNewDexFileCookie(
+        int32_t klass_index,
+        art::Handle<art::mirror::ClassLoader> source_class_loader,
+        art::Handle<art::mirror::Object> dex_file_obj,
+        /*out*/RedefinitionDataHolder* holder)
+          REQUIRES_SHARED(art::Locks::mutator_lock_);
+
     void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass)
         REQUIRES(art::Locks::mutator_lock_);
 
diff --git a/test/944-transform-classloaders/build b/test/944-transform-classloaders/build
new file mode 100755 (executable)
index 0000000..898e2e5
--- /dev/null
@@ -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/944-transform-classloaders/classloader.cc b/test/944-transform-classloaders/classloader.cc
new file mode 100644 (file)
index 0000000..5fbd8e1
--- /dev/null
@@ -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.
+ */
+
+#include "base/macros.h"
+#include "jni.h"
+#include "mirror/class-inl.h"
+#include "openjdkjvmti/jvmti.h"
+#include "ScopedLocalRef.h"
+
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test944TransformClassloaders {
+
+
+extern "C" JNIEXPORT jlong JNICALL Java_Main_getDexFilePointer(JNIEnv* env, jclass, jclass klass) {
+  if (Runtime::Current() == nullptr) {
+    env->ThrowNew(env->FindClass("java/lang/Exception"),
+                  "We do not seem to be running in ART! Unable to get dex file.");
+    return 0;
+  }
+  ScopedObjectAccess soa(env);
+  // This sequence of casts must be the same as those done in
+  // runtime/native/dalvik_system_DexFile.cc in order to ensure that we get the same results.
+  return static_cast<jlong>(reinterpret_cast<uintptr_t>(
+      &soa.Decode<mirror::Class>(klass)->GetDexFile()));
+}
+
+}  // namespace Test944TransformClassloaders
+}  // namespace art
diff --git a/test/944-transform-classloaders/expected.txt b/test/944-transform-classloaders/expected.txt
new file mode 100644 (file)
index 0000000..7952247
--- /dev/null
@@ -0,0 +1,5 @@
+hello
+hello2
+Goodbye
+Goodbye2
+Passed
diff --git a/test/944-transform-classloaders/info.txt b/test/944-transform-classloaders/info.txt
new file mode 100644 (file)
index 0000000..9155564
--- /dev/null
@@ -0,0 +1,7 @@
+Tests that redefined dex files are stored in the appropriate classloader.
+
+This test cannot run on the RI.
+
+We use reflection with setAccessible(true) to examine the private internals of
+classloaders. Changes to the internal operation or definition of
+dalvik.system.BaseDexClassLoader might cause this test to fail.
diff --git a/test/944-transform-classloaders/run b/test/944-transform-classloaders/run
new file mode 100755 (executable)
index 0000000..c6e62ae
--- /dev/null
@@ -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-run "$@" --jvmti
diff --git a/test/944-transform-classloaders/src/CommonClassDefinition.java b/test/944-transform-classloaders/src/CommonClassDefinition.java
new file mode 100644 (file)
index 0000000..62602a0
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class CommonClassDefinition {
+  public final Class<?> target;
+  public final byte[] class_file_bytes;
+  public final byte[] dex_file_bytes;
+
+  CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+    this.target = target;
+    this.class_file_bytes = class_file_bytes;
+    this.dex_file_bytes = dex_file_bytes;
+  }
+}
diff --git a/test/944-transform-classloaders/src/Main.java b/test/944-transform-classloaders/src/Main.java
new file mode 100644 (file)
index 0000000..4911e00
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * 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.Arrays;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.lang.reflect.*;
+public class Main {
+
+  /**
+   * base64 encoded class/dex file for
+   * class Transform {
+   *   public void sayHi() {
+   *    System.out.println("Goodbye");
+   *   }
+   * }
+   */
+  private static CommonClassDefinition TRANSFORM_DEFINITION = new CommonClassDefinition(
+      Transform.class,
+      Base64.getDecoder().decode(
+        "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" +
+        "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" +
+        "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" +
+        "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" +
+        "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" +
+        "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" +
+        "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="),
+      Base64.getDecoder().decode(
+        "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" +
+        "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" +
+        "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" +
+        "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" +
+        "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" +
+        "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" +
+        "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" +
+        "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" +
+        "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" +
+        "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" +
+        "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" +
+        "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" +
+        "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="));
+
+  /**
+   * base64 encoded class/dex file for
+   * class Transform2 {
+   *   public void sayHi() {
+   *    System.out.println("Goodbye2");
+   *   }
+   * }
+   */
+  private static CommonClassDefinition TRANSFORM2_DEFINITION = new CommonClassDefinition(
+      Transform2.class,
+      Base64.getDecoder().decode(
+        "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" +
+        "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA9UcmFuc2Zvcm0yLmphdmEM" +
+        "AAcACAcAFgwAFwAYAQAIR29vZGJ5ZTIHABkMABoAGwEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcv" +
+        "T2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEA" +
+        "E2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAA" +
+        "BQAGAAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAAQABAAsA" +
+        "CAABAAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAADAAgABAABAAwAAAACAA0="),
+      Base64.getDecoder().decode(
+        "ZGV4CjAzNQABX6vL8OT7aGLjbzFBEfCM9Aaz+zzGzVnQAgAAcAAAAHhWNBIAAAAAAAAAADACAAAO" +
+        "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACwAQAAIAEAAGIB" +
+        "AABqAQAAdAEAAIIBAACZAQAArQEAAMEBAADVAQAA5gEAAOkBAADtAQAAAQIAAAYCAAAPAgAAAgAA" +
+        "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" +
+        "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAACECAAAA" +
+        "AAAAAQABAAEAAAAWAgAABAAAAHAQAwAAAA4AAwABAAIAAAAbAgAACQAAAGIAAAAbAQEAAABuIAIA" +
+        "EAAOAAAAAQAAAAMABjxpbml0PgAIR29vZGJ5ZTIADExUcmFuc2Zvcm0yOwAVTGphdmEvaW8vUHJp" +
+        "bnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEv" +
+        "bGFuZy9TeXN0ZW07AA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTQuMjQA" +
+        "A291dAAHcHJpbnRsbgAFc2F5SGkAAQAHDgADAAcOhwAAAAEBAICABKACAQG4AgANAAAAAAAAAAEA" +
+        "AAAAAAAAAQAAAA4AAABwAAAAAgAAAAYAAACoAAAAAwAAAAIAAADAAAAABAAAAAEAAADYAAAABQAA" +
+        "AAQAAADgAAAABgAAAAEAAAAAAQAAASAAAAIAAAAgAQAAARAAAAEAAABcAQAAAiAAAA4AAABiAQAA" +
+        "AyAAAAIAAAAWAgAAACAAAAEAAAAhAgAAABAAAAEAAAAwAgAA"));
+
+  public static void main(String[] args) throws Exception {
+    doTest();
+    System.out.println("Passed");
+  }
+
+  private static void checkIsInstance(Class<?> klass, Object o) throws Exception {
+    if (!klass.isInstance(o)) {
+      throw new Exception(klass + " is not the class of " + o);
+    }
+  }
+
+  private static boolean arrayContains(long[] arr, long value) {
+    if (arr == null) {
+      return false;
+    }
+    for (int i = 0; i < arr.length; i++) {
+      if (arr[i] == value) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Checks that we can find the dex-file for the given class in its classloader.
+   *
+   * Throws if it fails.
+   */
+  private static void checkDexFileInClassLoader(Class<?> klass) throws Exception {
+    // If all the android BCP classes were availible when compiling this test and access checks
+    // weren't a thing this function would be written as follows:
+    //
+    // long dexFilePtr = getDexFilePointer(klass);
+    // dalvik.system.BaseDexClassLoader loader =
+    //     (dalvik.system.BaseDexClassLoader)klass.getClassLoader();
+    // dalvik.system.DexPathList pathListValue = loader.pathList;
+    // dalvik.system.DexPathList.Element[] elementArrayValue = pathListValue.dexElements;
+    // int array_length = elementArrayValue.length;
+    // for (int i = 0; i < array_length; i++) {
+    //   dalvik.system.DexPathList.Element curElement = elementArrayValue[i];
+    //   dalvik.system.DexFile curDexFile = curElement.dexFile;
+    //   if (curDexFile == null) {
+    //     continue;
+    //   }
+    //   long[] curCookie = (long[])curDexFile.mCookie;
+    //   long[] curInternalCookie = (long[])curDexFile.mInternalCookie;
+    //   if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) {
+    //     return;
+    //   }
+    // }
+    // throw new Exception(
+    //     "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass);
+
+    // Get all the fields and classes we need by reflection.
+    Class<?> baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
+    Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
+
+    Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
+    Field elementArrayField = dexPathListClass.getDeclaredField("dexElements");
+
+    Class<?> dexPathListElementClass = Class.forName("dalvik.system.DexPathList$Element");
+    Field dexFileField = dexPathListElementClass.getDeclaredField("dexFile");
+
+    Class<?> dexFileClass = Class.forName("dalvik.system.DexFile");
+    Field dexFileCookieField = dexFileClass.getDeclaredField("mCookie");
+    Field dexFileInternalCookieField = dexFileClass.getDeclaredField("mInternalCookie");
+
+    // Make all the fields accessible
+    AccessibleObject.setAccessible(new AccessibleObject[] { pathListField,
+                                                            elementArrayField,
+                                                            dexFileField,
+                                                            dexFileCookieField,
+                                                            dexFileInternalCookieField }, true);
+
+    long dexFilePtr = getDexFilePointer(klass);
+
+    ClassLoader loader = klass.getClassLoader();
+    checkIsInstance(baseDexClassLoaderClass, loader);
+    // DexPathList pathListValue = ((BaseDexClassLoader) loader).pathList;
+    Object pathListValue = pathListField.get(loader);
+
+    checkIsInstance(dexPathListClass, pathListValue);
+
+    // DexPathList.Element[] elementArrayValue = pathListValue.dexElements;
+    Object elementArrayValue = elementArrayField.get(pathListValue);
+    if (!elementArrayValue.getClass().isArray() ||
+        elementArrayValue.getClass().getComponentType() != dexPathListElementClass) {
+      throw new Exception("elementArrayValue is not an " + dexPathListElementClass + " array!");
+    }
+    // int array_length = elementArrayValue.length;
+    int array_length = Array.getLength(elementArrayValue);
+    for (int i = 0; i < array_length; i++) {
+      // DexPathList.Element curElement = elementArrayValue[i];
+      Object curElement = Array.get(elementArrayValue, i);
+      checkIsInstance(dexPathListElementClass, curElement);
+
+      // DexFile curDexFile = curElement.dexFile;
+      Object curDexFile = dexFileField.get(curElement);
+      if (curDexFile == null) {
+        continue;
+      }
+      checkIsInstance(dexFileClass, curDexFile);
+
+      // long[] curCookie = (long[])curDexFile.mCookie;
+      long[] curCookie = (long[])dexFileCookieField.get(curDexFile);
+      // long[] curInternalCookie = (long[])curDexFile.mInternalCookie;
+      long[] curInternalCookie = (long[])dexFileInternalCookieField.get(curDexFile);
+
+      if (arrayContains(curCookie, dexFilePtr) || arrayContains(curInternalCookie, dexFilePtr)) {
+        return;
+      }
+    }
+    throw new Exception(
+        "Unable to find dex file pointer " + dexFilePtr + " in class loader for " + klass);
+  }
+
+  private static void doTest() throws Exception {
+    Transform t = new Transform();
+    Transform2 t2 = new Transform2();
+
+    long initial_t1_dex = getDexFilePointer(Transform.class);
+    long initial_t2_dex = getDexFilePointer(Transform2.class);
+    if (initial_t2_dex != initial_t1_dex) {
+      throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " +
+                          "have different initial dex files!");
+    }
+    checkDexFileInClassLoader(Transform.class);
+    checkDexFileInClassLoader(Transform2.class);
+
+    // Make sure they are loaded
+    t.sayHi();
+    t2.sayHi();
+    // Redefine both of the classes.
+    doMultiClassRedefinition(TRANSFORM_DEFINITION, TRANSFORM2_DEFINITION);
+    // Make sure we actually transformed them!
+    t.sayHi();
+    t2.sayHi();
+
+    long final_t1_dex = getDexFilePointer(Transform.class);
+    long final_t2_dex = getDexFilePointer(Transform2.class);
+    if (final_t2_dex == final_t1_dex) {
+      throw new Exception("The classes " + Transform.class + " and " + Transform2.class + " " +
+                          "have the same initial dex files!");
+    } else if (final_t1_dex == initial_t1_dex) {
+      throw new Exception("The class " + Transform.class + " did not get a new dex file!");
+    } else if (final_t2_dex == initial_t2_dex) {
+      throw new Exception("The class " + Transform2.class + " did not get a new dex file!");
+    }
+    // Check to make sure the new dex files are in the class loader.
+    checkDexFileInClassLoader(Transform.class);
+    checkDexFileInClassLoader(Transform2.class);
+  }
+
+  private static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  // Gets the 'long' (really a native pointer) that is stored in the ClassLoader representing the
+  // DexFile a class is loaded from. This is converted from the DexFile* in the same way it is done
+  // in runtime/native/dalvik_system_DexFile.cc
+  private static native long getDexFilePointer(Class<?> target);
+  // Transforms the classes
+  private static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                            byte[][] classfiles,
+                                                            byte[][] dexfiles);
+}
diff --git a/test/944-transform-classloaders/src/Transform.java b/test/944-transform-classloaders/src/Transform.java
new file mode 100644 (file)
index 0000000..8e8af35
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 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.
+ */
+
+class Transform {
+  public void sayHi() {
+    // Use lower 'h' to make sure the string will have a different string id
+    // than the transformation (the transformation code is the same except
+    // the actual printed String, which was making the test inacurately passing
+    // in JIT mode when loading the string from the dex cache, as the string ids
+    // of the two different strings were the same).
+    // We know the string ids will be different because lexicographically:
+    // "Goodbye" < "LTransform;" < "hello".
+    System.out.println("hello");
+  }
+}
diff --git a/test/944-transform-classloaders/src/Transform2.java b/test/944-transform-classloaders/src/Transform2.java
new file mode 100644 (file)
index 0000000..eb22842
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 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.
+ */
+
+class Transform2 {
+  public void sayHi() {
+    System.out.println("hello2");
+  }
+}
index 1070645..d3244a6 100644 (file)
@@ -273,6 +273,7 @@ art_cc_defaults {
         "931-agent-thread/agent_thread.cc",
         "933-misc-events/misc_events.cc",
         "936-search-onload/search_onload.cc",
+        "944-transform-classloaders/classloader.cc",
     ],
     shared_libs: [
         "libbase",
index 008e2e5..c5a9356 100644 (file)
@@ -121,6 +121,7 @@ static AgentLib agents[] = {
   { "941-recursive-obsolete-jit", common_redefine::OnLoad, nullptr },
   { "942-private-recursive", common_redefine::OnLoad, nullptr },
   { "943-private-recursive-jit", common_redefine::OnLoad, nullptr },
+  { "944-transform-classloaders", common_redefine::OnLoad, nullptr },
 };
 
 static AgentLib* FindAgent(char* name) {