Argument conversions for setter/getter MethodHandles.
Test: m test-art-host-run-test-959-invoke-polymorphic-accessors
Bug: 30550796
Change-Id: I9898605fc9f9f8a80f47f9559f3ccb99b02b07c8
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
new file mode 100644
index 0000000..491d139
--- /dev/null
+++ b/runtime/method_handles.cc
@@ -0,0 +1,317 @@
+/*
+ * 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.
+ */
+
+#include "method_handles.h"
+
+#include "method_handles-inl.h"
+#include "jvalue.h"
+#include "jvalue-inl.h"
+#include "reflection.h"
+#include "reflection-inl.h"
+
+namespace art {
+
+namespace {
+
+static const char* kBoxedBooleanClass = "Ljava/lang/Boolean;";
+static const char* kBoxedByteClass = "Ljava/lang/Byte;";
+static const char* kBoxedCharacterClass = "Ljava/lang/Character;";
+static const char* kBoxedDoubleClass = "Ljava/lang/Double;";
+static const char* kBoxedFloatClass = "Ljava/lang/Float;";
+static const char* kBoxedIntegerClass = "Ljava/lang/Integer;";
+static const char* kBoxedLongClass = "Ljava/lang/Long;";
+static const char* kBoxedShortClass = "Ljava/lang/Short;";
+
+// Assigns |type| to the primitive type associated with |klass|. Returns
+// true iff. |klass| was a boxed type (Integer, Long etc.), false otherwise.
+bool GetUnboxedPrimitiveType(ObjPtr<mirror::Class> klass, Primitive::Type* type)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ScopedAssertNoThreadSuspension ants(__FUNCTION__);
+ if (klass->DescriptorEquals(kBoxedBooleanClass)) {
+ (*type) = Primitive::kPrimBoolean;
+ return true;
+ } else if (klass->DescriptorEquals(kBoxedByteClass)) {
+ (*type) = Primitive::kPrimByte;
+ return true;
+ } else if (klass->DescriptorEquals(kBoxedCharacterClass)) {
+ (*type) = Primitive::kPrimChar;
+ return true;
+ } else if (klass->DescriptorEquals(kBoxedFloatClass)) {
+ (*type) = Primitive::kPrimFloat;
+ return true;
+ } else if (klass->DescriptorEquals(kBoxedDoubleClass)) {
+ (*type) = Primitive::kPrimDouble;
+ return true;
+ } else if (klass->DescriptorEquals(kBoxedIntegerClass)) {
+ (*type) = Primitive::kPrimInt;
+ return true;
+ } else if (klass->DescriptorEquals(kBoxedLongClass)) {
+ (*type) = Primitive::kPrimLong;
+ return true;
+ } else if (klass->DescriptorEquals(kBoxedShortClass)) {
+ (*type) = Primitive::kPrimShort;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Returns the class corresponding to the boxed type for the primitive |type|.
+ObjPtr<mirror::Class> GetBoxedPrimitiveClass(Primitive::Type type)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ScopedAssertNoThreadSuspension ants(__FUNCTION__);
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ switch (type) {
+ case Primitive::kPrimBoolean:
+ return class_linker->FindSystemClass(Thread::Current(), kBoxedBooleanClass);
+ case Primitive::kPrimByte:
+ return class_linker->FindSystemClass(Thread::Current(), kBoxedByteClass);
+ case Primitive::kPrimChar:
+ return class_linker->FindSystemClass(Thread::Current(), kBoxedCharacterClass);
+ case Primitive::kPrimShort:
+ return class_linker->FindSystemClass(Thread::Current(), kBoxedShortClass);
+ case Primitive::kPrimInt:
+ return class_linker->FindSystemClass(Thread::Current(), kBoxedIntegerClass);
+ case Primitive::kPrimLong:
+ return class_linker->FindSystemClass(Thread::Current(), kBoxedLongClass);
+ case Primitive::kPrimFloat:
+ return class_linker->FindSystemClass(Thread::Current(), kBoxedFloatClass);
+ case Primitive::kPrimDouble:
+ return class_linker->FindSystemClass(Thread::Current(), kBoxedDoubleClass);
+ case Primitive::kPrimNot:
+ case Primitive::kPrimVoid:
+ LOG(FATAL) << "Unreachable";
+ return nullptr;
+ }
+}
+
+// Returns true if |klass| is a boxed primitive type or a sub-class of a boxed primitive type.
+bool IsSubClassOfBoxedPrimitive(const Handle<mirror::Class>& klass)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ StackHandleScope<1> hs(Thread::Current());
+ MutableHandle<mirror::Class> h_klass(hs.NewHandle(klass.Get()));
+ do {
+ Primitive::Type type;
+ if (GetUnboxedPrimitiveType(h_klass.Get(), &type)) {
+ return true;
+ }
+ h_klass.Assign(h_klass->GetSuperClass());
+ } while (h_klass.Get() != nullptr);
+ return false;
+}
+
+// Unboxed the value |o| to |unboxed_value| of type |dst_class|.
+// |unboxed_value| must be zero on entry to avoid dangling pointers.
+// Returns true on success, false if an exception is raised.
+bool UnboxPrimitiveForMethodHandles(ObjPtr<mirror::Object> o,
+ ObjPtr<mirror::Class> dst_class,
+ JValue* unboxed_value)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Check unboxed_value does not contain a dangling pointer.
+ DCHECK_EQ(unboxed_value->GetJ(), 0);
+ DCHECK(dst_class->IsPrimitive());
+
+ // This is derived from UnboxPrimitive() in reflection.cc, but with
+ // exceptions appropriate to method handles.
+ if (UNLIKELY(dst_class->GetPrimitiveType() == Primitive::kPrimVoid)) {
+ ThrowClassCastException(o->GetClass(), dst_class);
+ return false;
+ }
+ if (UNLIKELY(o == nullptr)) {
+ ThrowNullPointerException(
+ StringPrintf("Expected to unbox a '%s' primitive type but was returned null",
+ dst_class->PrettyDescriptor().c_str()).c_str());
+ return false;
+ }
+
+ JValue boxed_value;
+ ObjPtr<mirror::Class> klass = o->GetClass();
+ ObjPtr<mirror::Class> src_class = nullptr;
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ ArtField* primitive_field = &klass->GetIFieldsPtr()->At(0);
+ if (klass->DescriptorEquals(kBoxedBooleanClass)) {
+ src_class = class_linker->FindPrimitiveClass('Z');
+ boxed_value.SetZ(primitive_field->GetBoolean(o));
+ } else if (klass->DescriptorEquals(kBoxedByteClass)) {
+ src_class = class_linker->FindPrimitiveClass('B');
+ boxed_value.SetB(primitive_field->GetByte(o));
+ } else if (klass->DescriptorEquals(kBoxedCharacterClass)) {
+ src_class = class_linker->FindPrimitiveClass('C');
+ boxed_value.SetC(primitive_field->GetChar(o));
+ } else if (klass->DescriptorEquals(kBoxedFloatClass)) {
+ src_class = class_linker->FindPrimitiveClass('F');
+ boxed_value.SetF(primitive_field->GetFloat(o));
+ } else if (klass->DescriptorEquals(kBoxedDoubleClass)) {
+ src_class = class_linker->FindPrimitiveClass('D');
+ boxed_value.SetD(primitive_field->GetDouble(o));
+ } else if (klass->DescriptorEquals(kBoxedIntegerClass)) {
+ src_class = class_linker->FindPrimitiveClass('I');
+ boxed_value.SetI(primitive_field->GetInt(o));
+ } else if (klass->DescriptorEquals(kBoxedLongClass)) {
+ src_class = class_linker->FindPrimitiveClass('J');
+ boxed_value.SetJ(primitive_field->GetLong(o));
+ } else if (klass->DescriptorEquals(kBoxedShortClass)) {
+ src_class = class_linker->FindPrimitiveClass('S');
+ boxed_value.SetS(primitive_field->GetShort(o));
+ } else {
+ std::string temp;
+ ThrowIllegalArgumentException(
+ StringPrintf("result has type %s, got %s",
+ dst_class->PrettyDescriptor().c_str(),
+ PrettyDescriptor(o->GetClass()->GetDescriptor(&temp)).c_str()).c_str());
+ return false;
+ }
+
+ if (!ConvertPrimitiveValueNoThrow(src_class->GetPrimitiveType(),
+ dst_class->GetPrimitiveType(),
+ boxed_value,
+ unboxed_value)) {
+ ThrowClassCastException(src_class, dst_class);
+ return false;
+ }
+ return true;
+}
+
+inline bool IsReferenceType(Primitive::Type type) {
+ return type == Primitive::kPrimNot;
+}
+
+inline bool IsPrimitiveType(Primitive::Type type) {
+ return !IsReferenceType(type);
+}
+
+} // namespace
+
+bool ConvertJValueCommon(
+ Handle<mirror::MethodType> callsite_type,
+ Handle<mirror::MethodType> callee_type,
+ ObjPtr<mirror::Class> from,
+ ObjPtr<mirror::Class> to,
+ JValue* value) {
+ // The reader maybe concerned about the safety of the heap object
+ // that may be in |value|. There is only one case where allocation
+ // is obviously needed and that's for boxing. However, in the case
+ // of boxing |value| contains a non-reference type.
+
+ const Primitive::Type from_type = from->GetPrimitiveType();
+ const Primitive::Type to_type = to->GetPrimitiveType();
+
+ // This method must be called only when the types don't match.
+ DCHECK(from != to);
+
+ if (IsPrimitiveType(from_type) && IsPrimitiveType(to_type)) {
+ // The source and target types are both primitives.
+ if (UNLIKELY(!ConvertPrimitiveValueNoThrow(from_type, to_type, *value, value))) {
+ ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
+ value->SetJ(0);
+ return false;
+ }
+ return true;
+ } else if (IsReferenceType(from_type) && IsReferenceType(to_type)) {
+ // They're both reference types. If "from" is null, we can pass it
+ // through unchanged. If not, we must generate a cast exception if
+ // |to| is not assignable from the dynamic type of |ref|.
+ //
+ // Playing it safe with StackHandleScope here, not expecting any allocation
+ // in mirror::Class::IsAssignable().
+ StackHandleScope<2> hs(Thread::Current());
+ Handle<mirror::Class> h_to(hs.NewHandle(to));
+ Handle<mirror::Object> h_obj(hs.NewHandle(value->GetL()));
+
+ // |value| will now be the result value, invalidate its existing value
+ // as |h_obj| now owns it.
+ value->SetJ(0);
+
+ if (h_obj.Get() != nullptr && !to->IsAssignableFrom(h_obj->GetClass())) {
+ ThrowClassCastException(h_to.Get(), h_obj->GetClass());
+ return false;
+ }
+ value->SetL(h_obj.Get());
+ return true;
+ } else if (IsReferenceType(to_type)) {
+ DCHECK(IsPrimitiveType(from_type));
+ // Playing it safe with StackHandleScope here with regards to
+ // GetUnboxedPrimitiveType() and GetBoxedPrimitiveClass().
+ StackHandleScope<1> hs(Thread::Current());
+ Handle<mirror::Class> h_to(hs.NewHandle(to));
+ // The source type is a primitive and the target type is a reference, so we must box.
+ // The target type maybe a super class of the boxed source type, for example,
+ // if the source type is int, it's boxed type is java.lang.Integer, and the target
+ // type could be java.lang.Number.
+ Primitive::Type type;
+ if (!GetUnboxedPrimitiveType(to, &type)) {
+ ObjPtr<mirror::Class> boxed_from_class = GetBoxedPrimitiveClass(from_type);
+ if (boxed_from_class->IsSubClass(h_to.Get())) {
+ type = from_type;
+ } else {
+ value->SetJ(0);
+ ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
+ return false;
+ }
+ }
+
+ if (UNLIKELY(from_type != type)) {
+ value->SetJ(0);
+ ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
+ return false;
+ }
+
+ if (!ConvertPrimitiveValueNoThrow(from_type, type, *value, value)) {
+ value->SetJ(0);
+ ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
+ return false;
+ }
+
+ // Then perform the actual boxing, and then set the reference.
+ ObjPtr<mirror::Object> boxed = BoxPrimitive(type, *value);
+ value->SetL(boxed.Ptr());
+ return true;
+ } else {
+ // The source type is a reference and the target type is a primitive, so we must unbox.
+ DCHECK(IsReferenceType(from_type));
+ DCHECK(IsPrimitiveType(to_type));
+
+ // Use StackHandleScope to protect |from|, |to|, and the reference
+ // in |value| from heap re-arrangements that could be triggered
+ // ahead of unboxing step.
+ StackHandleScope<3> hs(Thread::Current());
+ Handle<mirror::Class> h_to(hs.NewHandle(to));
+ Handle<mirror::Class> h_from(hs.NewHandle(from));
+ Handle<mirror::Object> h_obj(hs.NewHandle(value->GetL()));
+
+ // |value| will now be the result value, invalidate its existing value
+ // as |h_obj| now owns it.
+ value->SetJ(0);
+
+ // Check source type is a boxed primitive or has a boxed primitive super-class.
+ ObjPtr<mirror::Class> boxed_to_class = GetBoxedPrimitiveClass(to_type);
+ if (!IsSubClassOfBoxedPrimitive(h_from) && !boxed_to_class->IsSubClass(h_from.Get())) {
+ ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
+ return false;
+ }
+
+ if (h_obj.Get() == nullptr) {
+ ThrowNullPointerException(
+ StringPrintf("Expected to unbox a '%s' but instance was null",
+ h_from->PrettyDescriptor().c_str()).c_str());
+ return false;
+ }
+
+ return UnboxPrimitiveForMethodHandles(h_obj.Get(), h_to.Get(), value);
+ }
+}
+
+} // namespace art