Ensure invoking obsolete methods throws errors.
This sets the entrypoint for obsolete methods to a special stub that
will ensure that calling them results in an Error being thrown.
Previously we were allowing obsolete methods to be run if they could
reach the appropriate places in the runtime.
Getting into the state where this is possible is extremely difficult
since one can only get an jmethodID to an obsolete method by snatching
it off the stack (or by inspecting internal runtime data). From there
normally invoking it will do lookup on the receiver which will get you
the original version.
Bug: 36867251
Bug: 31455788
Test: ./test.py --host -j40
Test: (with aosp_marlin-userdebug device) ./test.py --target -j4
Change-Id: I2ca0503966a4e3de18dd89cb7ff224eba1459b49
(cherry picked from commit db01a091aefbd78b56777f4c2e8c5e3f2d8c2712)
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 72aa785..029de46 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1614,6 +1614,11 @@
DELIVER_PENDING_EXCEPTION
END art_quick_to_interpreter_bridge
+/*
+ * Called to attempt to execute an obsolete method.
+ */
+ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMethod
+
/*
* Routine that intercepts method calls and returns.
*/
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 5b5d2ef..54adce6 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2152,6 +2152,11 @@
RETURN_OR_DELIVER_PENDING_EXCEPTION
END art_quick_to_interpreter_bridge
+/*
+ * Called to attempt to execute an obsolete method.
+ */
+ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMethod
+
//
// Instrumentation-related stubs
diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S
index 5d61539..362bbb1 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -1877,6 +1877,14 @@
DELIVER_PENDING_EXCEPTION
END art_quick_to_interpreter_bridge
+ .extern artInvokeObsoleteMethod
+ENTRY art_invoke_obsolete_method_stub
+ SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
+ la $t9, artInvokeObsoleteMethod
+ jalr $t9 # (Method* method, Thread* self)
+ move $a1, rSELF # pass Thread::Current
+END art_invoke_obsolete_method_stub
+
/*
* Routine that intercepts method calls and returns.
*/
diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S
index 3ee9c4a..807693f 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -1818,6 +1818,13 @@
DELIVER_PENDING_EXCEPTION
END art_quick_to_interpreter_bridge
+ .extern artInvokeObsoleteMethod
+ENTRY art_invoke_obsolete_method_stub
+ SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
+ jal artInvokeObsoleteMethod # (Method* method, Thread* self)
+ move $a1, rSELF # pass Thread::Current
+END art_invoke_obsolete_method_stub
+
/*
* Routine that intercepts method calls and returns.
*/
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 5f38dc8..6c0bcc9 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -1938,6 +1938,11 @@
END_FUNCTION art_quick_to_interpreter_bridge
/*
+ * Called by managed code, saves callee saves and then calls artInvokeObsoleteMethod
+ */
+ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMethod
+
+ /*
* Routine that intercepts method calls and returns.
*/
DEFINE_FUNCTION art_quick_instrumentation_entry
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index e87b165..8e2acab 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -1902,6 +1902,12 @@
END_FUNCTION art_quick_to_interpreter_bridge
/*
+ * Called to catch an attempt to invoke an obsolete method.
+ * RDI = method being called.
+ */
+ONE_ARG_RUNTIME_EXCEPTION art_invoke_obsolete_method_stub, artInvokeObsoleteMethod
+
+ /*
* Routine that intercepts method calls and returns.
*/
DEFINE_FUNCTION art_quick_instrumentation_entry
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 80a8773..5a71be6 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -337,7 +337,8 @@
// Ensure that we won't be accidentally calling quick compiled code when -Xint.
if (kIsDebugBuild && runtime->GetInstrumentation()->IsForcedInterpretOnly()) {
CHECK(!runtime->UseJitCompilation());
- const void* oat_quick_code = (IsNative() || !IsInvokable() || IsProxyMethod())
+ const void* oat_quick_code =
+ (IsNative() || !IsInvokable() || IsProxyMethod() || IsObsolete())
? nullptr
: GetOatMethodQuickCode(runtime->GetClassLinker()->GetImagePointerSize());
CHECK(oat_quick_code == nullptr || oat_quick_code != GetEntryPointFromQuickCompiledCode())
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 3c18704..b8ff2c2 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -8507,6 +8507,15 @@
}
}
+void ClassLinker::SetEntryPointsForObsoleteMethod(ArtMethod* method) const {
+ DCHECK(method->IsObsolete());
+ // We cannot mess with the entrypoints of native methods because they are used to determine how
+ // large the method's quick stack frame is. Without this information we cannot walk the stacks.
+ if (!method->IsNative()) {
+ method->SetEntryPointFromQuickCompiledCode(GetInvokeObsoleteMethodStub());
+ }
+}
+
void ClassLinker::DumpForSigQuit(std::ostream& os) {
ScopedObjectAccess soa(Thread::Current());
ReaderMutexLock mu(soa.Self(), *Locks::classlinker_classes_lock_);
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index ef51d82..a26e63b 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -544,6 +544,10 @@
void SetEntryPointsToInterpreter(ArtMethod* method) const
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Set the entrypoints up for an obsolete method.
+ void SetEntryPointsForObsoleteMethod(ArtMethod* method) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Attempts to insert a class into a class table. Returns null if
// the class was inserted, otherwise returns an existing class with
// the same descriptor and ClassLoader.
diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc
index 4f4bed0..6758d75 100644
--- a/runtime/common_throws.cc
+++ b/runtime/common_throws.cc
@@ -313,6 +313,14 @@
ArtMethod::PrettyMethod(method).c_str()).c_str());
}
+// InternalError
+
+void ThrowInternalError(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ ThrowException("Ljava/lang/InternalError;", nullptr, fmt, &args);
+ va_end(args);
+}
// IOException
diff --git a/runtime/common_throws.h b/runtime/common_throws.h
index 55a8938..4afef79 100644
--- a/runtime/common_throws.h
+++ b/runtime/common_throws.h
@@ -151,6 +151,12 @@
void ThrowIncompatibleClassChangeErrorForMethodConflict(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
+// InternalError
+
+void ThrowInternalError(const char* fmt, ...)
+ __attribute__((__format__(__printf__, 1, 2)))
+ REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
+
// IOException
void ThrowIOException(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2)))
diff --git a/runtime/entrypoints/quick/quick_throw_entrypoints.cc b/runtime/entrypoints/quick/quick_throw_entrypoints.cc
index 1520e13..565b4ed 100644
--- a/runtime/entrypoints/quick/quick_throw_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_throw_entrypoints.cc
@@ -29,6 +29,15 @@
self->QuickDeliverException();
}
+extern "C" NO_RETURN uint64_t artInvokeObsoleteMethod(ArtMethod* method, Thread* self)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(method->IsObsolete());
+ ScopedQuickEntrypointChecks sqec(self);
+ ThrowInternalError("Attempting to invoke obsolete version of '%s'.",
+ method->PrettyMethod().c_str());
+ self->QuickDeliverException();
+}
+
// Called by generated code to throw an exception.
extern "C" NO_RETURN void artDeliverExceptionFromCode(mirror::Throwable* exception, Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/entrypoints/runtime_asm_entrypoints.h b/runtime/entrypoints/runtime_asm_entrypoints.h
index 2842c5a..4ca52de 100644
--- a/runtime/entrypoints/runtime_asm_entrypoints.h
+++ b/runtime/entrypoints/runtime_asm_entrypoints.h
@@ -40,6 +40,12 @@
return reinterpret_cast<const void*>(art_quick_to_interpreter_bridge);
}
+// Return the address of stub code for attempting to invoke an obsolete method.
+extern "C" void art_invoke_obsolete_method_stub(ArtMethod*);
+static inline const void* GetInvokeObsoleteMethodStub() {
+ return reinterpret_cast<const void*>(art_invoke_obsolete_method_stub);
+}
+
// Return the address of quick stub code for handling JNI calls.
extern "C" void art_quick_generic_jni_trampoline(ArtMethod*);
static inline const void* GetQuickGenericJniStub() {
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index 67e949f..bf49e84 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -360,6 +360,14 @@
return;
}
+ // This can happen if we are in forced interpreter mode and an obsolete method is called using
+ // reflection.
+ if (UNLIKELY(method->IsObsolete())) {
+ ThrowInternalError("Attempting to invoke obsolete version of '%s'.",
+ method->PrettyMethod().c_str());
+ return;
+ }
+
const char* old_cause = self->StartAssertNoThreadSuspension("EnterInterpreterFromInvoke");
const DexFile::CodeItem* code_item = method->GetCodeItem();
uint16_t num_regs;
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index 6e0d9a0..7d95de8 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -186,6 +186,7 @@
DCHECK_EQ(new_obsolete_method->GetDeclaringClass(), old_method->GetDeclaringClass());
new_obsolete_method->SetIsObsolete();
new_obsolete_method->SetDontCompile();
+ cl->SetEntryPointsForObsoleteMethod(new_obsolete_method);
obsolete_maps_->RecordObsolete(old_method, new_obsolete_method);
// Update JIT Data structures to point to the new method.
art::jit::Jit* jit = art::Runtime::Current()->GetJit();
diff --git a/runtime/stack.cc b/runtime/stack.cc
index 0628643..333128b 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -649,7 +649,7 @@
}
const void* code = method->GetEntryPointFromQuickCompiledCode();
- if (code == GetQuickInstrumentationEntryPoint()) {
+ if (code == GetQuickInstrumentationEntryPoint() || code == GetInvokeObsoleteMethodStub()) {
return;
}
diff --git a/test/984-obsolete-invoke/expected.txt b/test/984-obsolete-invoke/expected.txt
new file mode 100644
index 0000000..8052c46
--- /dev/null
+++ b/test/984-obsolete-invoke/expected.txt
@@ -0,0 +1,10 @@
+hello
+transforming calling function
+Retrieving obsolete method from current stack
+goodbye
+Invoking redefined version of method.
+Hello - Transformed
+Not doing anything here
+Goodbye - Transformed
+invoking obsolete method
+Caught expected error from attempting to invoke an obsolete method.
diff --git a/test/984-obsolete-invoke/info.txt b/test/984-obsolete-invoke/info.txt
new file mode 100644
index 0000000..48e0de0
--- /dev/null
+++ b/test/984-obsolete-invoke/info.txt
@@ -0,0 +1,4 @@
+Tests basic obsolete method support
+
+Tests that a specific method of potentially executing obsolete method code does
+not work.
diff --git a/test/984-obsolete-invoke/obsolete_invoke.cc b/test/984-obsolete-invoke/obsolete_invoke.cc
new file mode 100644
index 0000000..27e36ba
--- /dev/null
+++ b/test/984-obsolete-invoke/obsolete_invoke.cc
@@ -0,0 +1,70 @@
+/*
+ * 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 "android-base/macros.h"
+#include "jni.h"
+#include "jvmti.h"
+#include "mirror/class-inl.h"
+#include "scoped_local_ref.h"
+
+// Test infrastructure
+#include "test_env.h"
+
+#include "jvmti_helper.h"
+
+namespace art {
+namespace Test984ObsoleteInvoke {
+
+static constexpr size_t kNumFrames = 30;
+
+extern "C" JNIEXPORT jobject JNICALL Java_Main_getFirstObsoleteMethod984(JNIEnv* env, jclass) {
+ jthread cur;
+ jint frame_count;
+ jvmtiFrameInfo frames[kNumFrames];
+ // jint cur_start = 0;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetCurrentThread(&cur))) {
+ // ERROR
+ return nullptr;
+ }
+ if (JvmtiErrorToException(env, jvmti_env,
+ jvmti_env->GetStackTrace(cur,
+ 0,
+ kNumFrames,
+ frames,
+ &frame_count))) {
+ // ERROR
+ return nullptr;
+ }
+ for (jint i = 0; i < frame_count; i++) {
+ jmethodID method = frames[i].method;
+ jboolean is_obsolete = false;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->IsMethodObsolete(method, &is_obsolete))) {
+ // ERROR
+ return nullptr;
+ }
+ if (is_obsolete) {
+ return env->ToReflectedMethod(env->FindClass("java/lang/reflect/Method"),
+ method,
+ JNI_TRUE);
+ }
+ }
+ ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+ env->ThrowNew(rt_exception.get(), "Unable to find obsolete method!");
+ return nullptr;
+}
+
+} // namespace Test984ObsoleteInvoke
+} // namespace art
diff --git a/test/984-obsolete-invoke/run b/test/984-obsolete-invoke/run
new file mode 100755
index 0000000..c6e62ae
--- /dev/null
+++ b/test/984-obsolete-invoke/run
@@ -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/984-obsolete-invoke/src/Main.java b/test/984-obsolete-invoke/src/Main.java
new file mode 100644
index 0000000..1a8d9bc
--- /dev/null
+++ b/test/984-obsolete-invoke/src/Main.java
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+import java.lang.reflect.Method;
+import java.util.Base64;
+
+public class Main {
+ // class Transform {
+ // public static void sayHi(Runnable r) {
+ // System.out.println("Hello - Transformed");
+ // r.run();
+ // System.out.println("Goodbye - Transformed");
+ // }
+ // }
+ private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAJAoACAARCQASABMIABQKABUAFgsAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" +
+ "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" +
+ "KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAAkACgcAHAwAHQAeAQATSGVsbG8gLSBU" +
+ "cmFuc2Zvcm1lZAcAHwwAIAAhBwAiDAAjAAoBABVHb29kYnllIC0gVHJhbnNmb3JtZWQBAAlUcmFu" +
+ "c2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZh" +
+ "L2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZh" +
+ "L2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAABwAIAAAAAAACAAAA" +
+ "CQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQAJAA0ADgABAAsAAAA7AAIA" +
+ "AQAAABeyAAISA7YABCq5AAUBALIAAhIGtgAEsQAAAAEADAAAABIABAAAAAMACAAEAA4ABQAWAAYA" +
+ "AQAPAAAAAgAQ");
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQCMekj2NPwzrEp/v+2yzzSg8xZvBtU1bC1QAwAAcAAAAHhWNBIAAAAAAAAAALACAAAR" +
+ "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAMAgAARAEAAKIB" +
+ "AACqAQAAwQEAANYBAADjAQAA+gEAAA4CAAAkAgAAOAIAAEwCAABcAgAAXwIAAGMCAAB3AgAAfAIA" +
+ "AIUCAACKAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" +
+ "lAEAAAsAAAAGAAAAnAEAAAUAAQANAAAAAAAAAAAAAAAAAAEAEAAAAAEAAgAOAAAAAgAAAAAAAAAD" +
+ "AAAADwAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAJ8CAAAAAAAAAQABAAEAAACRAgAABAAAAHAQ" +
+ "AwAAAA4AAwABAAIAAACWAgAAFAAAAGIAAAAbAQIAAABuIAIAEAByEAQAAgBiAAAAGwEBAAAAbiAC" +
+ "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AFUdvb2RieWUgLSBUcmFuc2Zvcm1lZAATSGVsbG8g" +
+ "LSBUcmFuc2Zvcm1lZAALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEv" +
+ "bGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJM" +
+ "amF2YS9sYW5nL1N5c3RlbTsADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00" +
+ "LjMxAANvdXQAB3ByaW50bG4AA3J1bgAFc2F5SGkAAQAHDgADAQAHDoc8hwAAAAIAAICABMQCAQnc" +
+ "AgAAAA0AAAAAAAAAAQAAAAAAAAABAAAAEQAAAHAAAAACAAAABwAAALQAAAADAAAAAwAAANAAAAAE" +
+ "AAAAAQAAAPQAAAAFAAAABQAAAPwAAAAGAAAAAQAAACQBAAABIAAAAgAAAEQBAAABEAAAAgAAAJQB" +
+ "AAACIAAAEQAAAKIBAAADIAAAAgAAAJECAAAAIAAAAQAAAJ8CAAAAEAAAAQAAALACAAA=");
+
+ public static void main(String[] args) {
+ doTest();
+ }
+
+ // The Method that holds an obsolete method pointer. We will fill it in by getting a jmethodID
+ // from a stack with an obsolete method in it. There should be no other ways to obtain an obsolete
+ // jmethodID in ART without unsafe casts.
+ public static Method obsolete_method = null;
+
+ public static void doTest() {
+ // Capture the obsolete method.
+ //
+ // NB The obsolete method must be direct so that we will not look in the receiver type to get
+ // the actual method.
+ Transform.sayHi(() -> {
+ System.out.println("transforming calling function");
+ doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+ System.out.println("Retrieving obsolete method from current stack");
+ // This should get the obsolete sayHi method (as the only obsolete method on the current
+ // threads stack).
+ Main.obsolete_method = getFirstObsoleteMethod984();
+ });
+
+ // Prove we did actually redefine something.
+ System.out.println("Invoking redefined version of method.");
+ Transform.sayHi(() -> { System.out.println("Not doing anything here"); });
+
+ System.out.println("invoking obsolete method");
+ try {
+ obsolete_method.invoke(null, (Runnable)() -> {
+ throw new Error("Unexpected code running from invoke of obsolete method!");
+ });
+ throw new Error("Running obsolete method did not throw exception");
+ } catch (Throwable e) {
+ if (e instanceof InternalError || e.getCause() instanceof InternalError) {
+ System.out.println("Caught expected error from attempting to invoke an obsolete method.");
+ } else {
+ System.out.println("Unexpected error type for calling obsolete method! Expected either "
+ + "an InternalError or something that is caused by an InternalError.");
+ throw new Error("Unexpected error caught: ", e);
+ }
+ }
+ }
+
+ // Transforms the class
+ private static native void doCommonClassRedefinition(Class<?> target,
+ byte[] classfile,
+ byte[] dexfile);
+
+ // Gets the first obsolete method on the current threads stack (NB only looks through the first 30
+ // stack frames).
+ private static native Method getFirstObsoleteMethod984();
+}
diff --git a/test/984-obsolete-invoke/src/Transform.java b/test/984-obsolete-invoke/src/Transform.java
new file mode 100644
index 0000000..536de84
--- /dev/null
+++ b/test/984-obsolete-invoke/src/Transform.java
@@ -0,0 +1,25 @@
+/*
+ * 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 {
+ // This method must be 'static' so that when we try to invoke it through a j.l.r.Method we will
+ // simply use the jmethodID directly and not do any lookup in any receiver object.
+ public static void sayHi(Runnable r) {
+ System.out.println("hello");
+ r.run();
+ System.out.println("goodbye");
+ }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 40f7edd..b79006f 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -298,6 +298,7 @@
"945-obsolete-native/obsolete_native.cc",
"980-redefine-object/redefine_object.cc",
"983-source-transform-verify/source_transform.cc",
+ "984-obsolete-invoke/obsolete_invoke.cc",
],
}