MethodHandles: Enable return value conversions.
Enables return values conversions and simplify the type conversion
logic. Ensures check for WrongMethodTypeExceptions before invoking a
method handle.
Test: m test-art-host-run-test-956-methodhandles
Bug: 30550796
Change-Id: I8add3ad0a19b43010946b4fb9f1c0f0949287860
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 5e4bb41..cb775cd 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -827,6 +827,20 @@
return klass;
}
+// Returns true iff. the callsite type for a polymorphic invoke is transformer
+// like, i.e that it has a single input argument whose type is
+// dalvik.system.EmulatedStackFrame.
+static inline bool IsCallerTransformer(Handle<mirror::MethodType> callsite_type)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ObjPtr<mirror::ObjectArray<mirror::Class>> param_types(callsite_type->GetPTypes());
+ if (param_types->GetLength() == 1) {
+ ObjPtr<mirror::Class> param(param_types->GetWithoutChecks(0));
+ return param == WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_EmulatedStackFrame);
+ }
+
+ return false;
+}
+
template<bool is_range, bool do_access_check>
inline bool DoInvokePolymorphic(Thread* self,
ShadowFrame& shadow_frame,
@@ -838,6 +852,11 @@
const uint32_t vRegC = (is_range) ? inst->VRegC_4rcc() : inst->VRegC_45cc();
const int invoke_method_idx = (is_range) ? inst->VRegB_4rcc() : inst->VRegB_45cc();
+ // Initialize |result| to 0 as this is the default return value for
+ // polymorphic invocations of method handle types with void return
+ // and provides sane return result in error cases.
+ result->SetJ(0);
+
// Determine if this invocation is MethodHandle.invoke() or
// MethodHandle.invokeExact().
bool is_invoke_exact = IsInvokeExact(shadow_frame.GetMethod()->GetDeclaringClass()->GetDexFile(),
@@ -859,7 +878,6 @@
// Note that the invoke type is kVirtual here because a call to a signature
// polymorphic method is shaped like a virtual call at the bytecode level.
ThrowNullPointerExceptionForMethodAccess(invoke_method_idx, InvokeType::kVirtual);
- result->SetJ(0);
return false;
}
@@ -880,14 +898,13 @@
// This implies we couldn't resolve one or more types in this method handle.
if (UNLIKELY(callsite_type.Get() == nullptr)) {
CHECK(self->IsExceptionPending());
- result->SetJ(0);
return false;
}
const MethodHandleKind handle_kind = method_handle->GetHandleKind();
Handle<mirror::MethodType> handle_type(hs.NewHandle(method_handle->GetMethodType()));
CHECK(handle_type.Get() != nullptr);
- if (is_invoke_exact) {
+ {
// We need to check the nominal type of the handle in addition to the
// real type. The "nominal" type is present when MethodHandle.asType is
// called any handle, and results in the declared type of the handle
@@ -900,9 +917,17 @@
check_type.Assign(nominal_type.Ptr());
}
- if (UNLIKELY(!callsite_type->IsExactMatch(check_type.Ptr()))) {
- ThrowWrongMethodTypeException(check_type.Ptr(), callsite_type.Get());
- return false;
+ if (is_invoke_exact) {
+ if (UNLIKELY(!callsite_type->IsExactMatch(check_type.Ptr()))) {
+ ThrowWrongMethodTypeException(check_type.Ptr(), callsite_type.Get());
+ return false;
+ }
+ } else {
+ if (UNLIKELY(!IsCallerTransformer(callsite_type) &&
+ !callsite_type->IsConvertible(check_type.Ptr()))) {
+ ThrowWrongMethodTypeException(check_type.Ptr(), callsite_type.Get());
+ return false;
+ }
}
}
@@ -932,7 +957,7 @@
// TODO: Unfortunately, we have to postpone dynamic receiver based checks
// because the receiver might be cast or might come from an emulated stack
// frame, which means that it is unknown at this point. We perform these
- // checks inside DoCallPolymorphic right before we do the actualy invoke.
+ // checks inside DoCallPolymorphic right before we do the actual invoke.
} else if (handle_kind == kInvokeDirect) {
// String constructors are a special case, they are replaced with StringFactory
// methods.
@@ -965,40 +990,38 @@
CHECK(called_method != nullptr);
}
+ bool call_success;
if (handle_kind == kInvokeTransform) {
- return DoCallTransform<is_range>(called_method,
- callsite_type,
- handle_type,
- self,
- shadow_frame,
- method_handle /* receiver */,
- result,
- arg,
- first_src_reg);
+ call_success = DoCallTransform<is_range>(called_method,
+ callsite_type,
+ handle_type,
+ self,
+ shadow_frame,
+ method_handle /* receiver */,
+ result,
+ arg,
+ first_src_reg);
} else {
- return DoCallPolymorphic<is_range>(called_method,
- callsite_type,
- handle_type,
- self,
- shadow_frame,
- result,
- arg,
- first_src_reg,
- handle_kind);
+ call_success = DoCallPolymorphic<is_range>(called_method,
+ callsite_type,
+ handle_type,
+ self,
+ shadow_frame,
+ result,
+ arg,
+ first_src_reg,
+ handle_kind);
}
+ if (LIKELY(call_success && ConvertReturnValue(callsite_type, handle_type, result))) {
+ return true;
+ }
+ DCHECK(self->IsExceptionPending());
+ return false;
} else {
DCHECK(!is_range);
ArtField* field = method_handle->GetTargetField();
Primitive::Type field_type = field->GetTypeAsPrimitiveType();;
- if (!is_invoke_exact) {
- if (handle_type->GetPTypes()->GetLength() != callsite_type->GetPTypes()->GetLength()) {
- // Too many arguments to setter or getter.
- ThrowWrongMethodTypeException(callsite_type.Get(), handle_type.Get());
- return false;
- }
- }
-
switch (handle_kind) {
case kInstanceGet: {
ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(first_src_reg);
@@ -1029,7 +1052,6 @@
return false;
}
ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(first_src_reg);
- result->SetL(0);
return DoFieldPutForInvokePolymorphic(self, shadow_frame, obj, field, field_type, value);
}
case kStaticPut: {
@@ -1039,7 +1061,6 @@
return false;
}
ObjPtr<mirror::Object> obj = field->GetDeclaringClass();
- result->SetL(0);
return DoFieldPutForInvokePolymorphic(self, shadow_frame, obj, field, field_type, value);
}
default:
@@ -1120,20 +1141,6 @@
}
}
-// Returns true iff. the callsite type for a polymorphic invoke is transformer
-// like, i.e that it has a single input argument whose type is
-// dalvik.system.EmulatedStackFrame.
-static inline bool IsCallerTransformer(Handle<mirror::MethodType> callsite_type)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::ObjectArray<mirror::Class>> param_types(callsite_type->GetPTypes());
- if (param_types->GetLength() == 1) {
- ObjPtr<mirror::Class> param(param_types->GetWithoutChecks(0));
- return param == WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_EmulatedStackFrame);
- }
-
- return false;
-}
-
template <bool is_range>
static inline bool DoCallPolymorphic(ArtMethod* called_method,
Handle<mirror::MethodType> callsite_type,
@@ -1245,8 +1252,6 @@
PerformCall(self, code_item, shadow_frame.GetMethod(), first_dest_reg, new_shadow_frame, result);
- // TODO(narayan): Perform return value conversions.
-
// If the caller of this signature polymorphic method was a transformer,
// we need to copy the result back out to the emulated stack frame.
if (is_caller_transformer && !self->IsExceptionPending()) {
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index 491d139..f1adc32 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -21,168 +21,70 @@
#include "jvalue-inl.h"
#include "reflection.h"
#include "reflection-inl.h"
+#include "well_known_classes.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;";
+#define PRIMITIVES_LIST(V) \
+ V(Primitive::kPrimBoolean, Boolean, Boolean, Z) \
+ V(Primitive::kPrimByte, Byte, Byte, B) \
+ V(Primitive::kPrimChar, Char, Character, C) \
+ V(Primitive::kPrimShort, Short, Short, S) \
+ V(Primitive::kPrimInt, Int, Integer, I) \
+ V(Primitive::kPrimLong, Long, Long, J) \
+ V(Primitive::kPrimFloat, Float, Float, F) \
+ V(Primitive::kPrimDouble, Double, Double, D)
// 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;
+#define LOOKUP_PRIMITIVE(primitive, _, __, ___) \
+ if (klass->DescriptorEquals(Primitive::BoxedDescriptor(primitive))) { \
+ *type = primitive; \
+ return true; \
}
-}
-// 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);
+ PRIMITIVES_LIST(LOOKUP_PRIMITIVE);
+#undef LOOKUP_PRIMITIVE
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)
+ObjPtr<mirror::Class> GetBoxedPrimitiveClass(Primitive::Type type)
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;
+ ScopedAssertNoThreadSuspension ants(__FUNCTION__);
+ jmethodID m = nullptr;
+ switch (type) {
+#define CASE_PRIMITIVE(primitive, _, java_name, __) \
+ case primitive: \
+ m = WellKnownClasses::java_lang_ ## java_name ## _valueOf; \
+ break;
+ PRIMITIVES_LIST(CASE_PRIMITIVE);
+#undef CASE_PRIMITIVE
+ case Primitive::Type::kPrimNot:
+ case Primitive::Type::kPrimVoid:
+ return nullptr;
}
- 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;
- }
+ return jni::DecodeArtMethod(m)->GetDeclaringClass();
+}
- JValue boxed_value;
+bool GetUnboxedTypeAndValue(ObjPtr<mirror::Object> o, Primitive::Type* type, JValue* value)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ScopedAssertNoThreadSuspension ants(__FUNCTION__);
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;
+#define CASE_PRIMITIVE(primitive, abbrev, _, shorthand) \
+ if (klass == GetBoxedPrimitiveClass(primitive)) { \
+ *type = primitive; \
+ value->Set ## shorthand(primitive_field->Get ## abbrev(o)); \
+ return true; \
}
-
- if (!ConvertPrimitiveValueNoThrow(src_class->GetPrimitiveType(),
- dst_class->GetPrimitiveType(),
- boxed_value,
- unboxed_value)) {
- ThrowClassCastException(src_class, dst_class);
- return false;
- }
- return true;
+ PRIMITIVES_LIST(CASE_PRIMITIVE)
+#undef CASE_PRIMITIVE
+ return false;
}
inline bool IsReferenceType(Primitive::Type type) {
@@ -195,6 +97,71 @@
} // namespace
+bool IsParameterTypeConvertible(ObjPtr<mirror::Class> from, ObjPtr<mirror::Class> to)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // This function returns true if there's any conceivable conversion
+ // between |from| and |to|. It's expected this method will be used
+ // to determine if a WrongMethodTypeException should be raised. The
+ // decision logic follows the documentation for MethodType.asType().
+ if (from == to) {
+ return true;
+ }
+
+ Primitive::Type from_primitive = from->GetPrimitiveType();
+ Primitive::Type to_primitive = to->GetPrimitiveType();
+ DCHECK(from_primitive != Primitive::Type::kPrimVoid);
+ DCHECK(to_primitive != Primitive::Type::kPrimVoid);
+
+ // If |to| and |from| are references.
+ if (IsReferenceType(from_primitive) && IsReferenceType(to_primitive)) {
+ // Assignability is determined during parameter conversion when
+ // invoking the associated method handle.
+ return true;
+ }
+
+ // If |to| and |from| are primitives and a widening conversion exists.
+ if (Primitive::IsWidenable(from_primitive, to_primitive)) {
+ return true;
+ }
+
+ // If |to| is a reference and |from| is a primitive, then boxing conversion.
+ if (IsReferenceType(to_primitive) && IsPrimitiveType(from_primitive)) {
+ return to->IsAssignableFrom(GetBoxedPrimitiveClass(from_primitive));
+ }
+
+ // If |from| is a reference and |to| is a primitive, then unboxing conversion.
+ if (IsPrimitiveType(to_primitive) && IsReferenceType(from_primitive)) {
+ if (from->DescriptorEquals("Ljava/lang/Object;")) {
+ // Object might be converted into a primitive during unboxing.
+ return true;
+ } else if (Primitive::IsNumericType(to_primitive) &&
+ from->DescriptorEquals("Ljava/lang/Number;")) {
+ // Number might be unboxed into any of the number primitive types.
+ return true;
+ }
+ Primitive::Type unboxed_type;
+ if (GetUnboxedPrimitiveType(from, &unboxed_type)) {
+ return Primitive::IsWidenable(unboxed_type, to_primitive);
+ }
+ }
+
+ return false;
+}
+
+bool IsReturnTypeConvertible(ObjPtr<mirror::Class> from, ObjPtr<mirror::Class> to)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (to->GetPrimitiveType() == Primitive::Type::kPrimVoid) {
+ // Result will be ignored.
+ return true;
+ } else if (from->GetPrimitiveType() == Primitive::Type::kPrimVoid) {
+ // Returned value will be 0 / null.
+ return true;
+ } else {
+ // Otherwise apply usual parameter conversion rules.
+ return IsParameterTypeConvertible(from, to);
+ }
+}
+
bool ConvertJValueCommon(
Handle<mirror::MethodType> callsite_type,
Handle<mirror::MethodType> callee_type,
@@ -209,14 +176,23 @@
const Primitive::Type from_type = from->GetPrimitiveType();
const Primitive::Type to_type = to->GetPrimitiveType();
+ // Put incoming value into |src_value| and set return value to 0.
+ // Errors and conversions from void require the return value to be 0.
+ const JValue src_value(*value);
+ value->SetJ(0);
+
+ // Conversion from void set result to zero.
+ if (from_type == Primitive::kPrimVoid) {
+ return true;
+ }
+
// 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))) {
+ if (UNLIKELY(!ConvertPrimitiveValueNoThrow(from_type, to_type, src_value, value))) {
ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
- value->SetJ(0);
return false;
}
return true;
@@ -229,12 +205,7 @@
// 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);
-
+ Handle<mirror::Object> h_obj(hs.NewHandle(src_value.GetL()));
if (h_obj.Get() != nullptr && !to->IsAssignableFrom(h_obj->GetClass())) {
ThrowClassCastException(h_to.Get(), h_obj->GetClass());
return false;
@@ -243,10 +214,6 @@
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
@@ -254,29 +221,26 @@
Primitive::Type type;
if (!GetUnboxedPrimitiveType(to, &type)) {
ObjPtr<mirror::Class> boxed_from_class = GetBoxedPrimitiveClass(from_type);
- if (boxed_from_class->IsSubClass(h_to.Get())) {
+ if (boxed_from_class->IsSubClass(to)) {
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);
+ if (!ConvertPrimitiveValueNoThrow(from_type, type, src_value, value)) {
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);
+ ObjPtr<mirror::Object> boxed = BoxPrimitive(type, src_value);
value->SetL(boxed.Ptr());
return true;
} else {
@@ -284,33 +248,27 @@
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()));
+ ObjPtr<mirror::Object> from_obj(src_value.GetL());
+ if (UNLIKELY(from_obj == nullptr)) {
+ ThrowNullPointerException(
+ StringPrintf("Expected to unbox a '%s' primitive type but was returned null",
+ from->PrettyDescriptor().c_str()).c_str());
+ return false;
+ }
- // |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())) {
+ Primitive::Type unboxed_type;
+ JValue unboxed_value;
+ if (UNLIKELY(!GetUnboxedTypeAndValue(from_obj, &unboxed_type, &unboxed_value))) {
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());
+ if (UNLIKELY(!ConvertPrimitiveValueNoThrow(unboxed_type, to_type, unboxed_value, value))) {
+ ThrowClassCastException(from, to);
return false;
}
- return UnboxPrimitiveForMethodHandles(h_obj.Get(), h_to.Get(), value);
+ return true;
}
}
diff --git a/runtime/method_handles.h b/runtime/method_handles.h
index 0cc69f2..54c772a 100644
--- a/runtime/method_handles.h
+++ b/runtime/method_handles.h
@@ -59,6 +59,16 @@
return handle_kind <= kLastInvokeKind;
}
+// Returns true if there is a possible conversion from |from| to |to|
+// for a MethodHandle parameter.
+bool IsParameterTypeConvertible(ObjPtr<mirror::Class> from,
+ ObjPtr<mirror::Class> to);
+
+// Returns true if there is a possible conversion from |from| to |to|
+// for the return type of a MethodHandle.
+bool IsReturnTypeConvertible(ObjPtr<mirror::Class> from,
+ ObjPtr<mirror::Class> to);
+
// Performs a conversion from type |from| to a distinct type |to| as
// part of conversion of |caller_type| to |callee_type|. The value to
// be converted is in |value|. Returns true on success and updates
diff --git a/runtime/mirror/method_type.cc b/runtime/mirror/method_type.cc
index 9b0f872..5d77a16 100644
--- a/runtime/mirror/method_type.cc
+++ b/runtime/mirror/method_type.cc
@@ -18,6 +18,7 @@
#include "class-inl.h"
#include "gc_root-inl.h"
+#include "method_handles.h"
namespace art {
namespace mirror {
@@ -43,25 +44,44 @@
return mt.Get();
}
-bool MethodType::IsExactMatch(mirror::MethodType* other) REQUIRES_SHARED(Locks::mutator_lock_) {
- if (GetRType() != other->GetRType()) {
- return false;
- }
-
+bool MethodType::IsExactMatch(mirror::MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_) {
mirror::ObjectArray<Class>* const p_types = GetPTypes();
const int32_t params_length = p_types->GetLength();
- mirror::ObjectArray<Class>* const other_p_types = other->GetPTypes();
- if (params_length != other_p_types->GetLength()) {
+ mirror::ObjectArray<Class>* const target_p_types = target->GetPTypes();
+ if (params_length != target_p_types->GetLength()) {
+ return false;
+ }
+ for (int32_t i = 0; i < params_length; ++i) {
+ if (p_types->GetWithoutChecks(i) != target_p_types->GetWithoutChecks(i)) {
+ return false;
+ }
+ }
+ return GetRType() == target->GetRType();
+}
+
+bool MethodType::IsConvertible(mirror::MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_) {
+ mirror::ObjectArray<Class>* const p_types = GetPTypes();
+ const int32_t params_length = p_types->GetLength();
+
+ mirror::ObjectArray<Class>* const target_p_types = target->GetPTypes();
+ if (params_length != target_p_types->GetLength()) {
+ return false;
+ }
+
+ // Perform return check before invoking method handle otherwise side
+ // effects from the invocation may be observable before
+ // WrongMethodTypeException is raised.
+ if (!IsReturnTypeConvertible(target->GetRType(), GetRType())) {
return false;
}
for (int32_t i = 0; i < params_length; ++i) {
- if (p_types->GetWithoutChecks(i) != other_p_types->GetWithoutChecks(i)) {
+ if (!IsParameterTypeConvertible(p_types->GetWithoutChecks(i),
+ target_p_types->GetWithoutChecks(i))) {
return false;
}
}
-
return true;
}
diff --git a/runtime/mirror/method_type.h b/runtime/mirror/method_type.h
index fa700b6..9a98143 100644
--- a/runtime/mirror/method_type.h
+++ b/runtime/mirror/method_type.h
@@ -52,9 +52,13 @@
static void ResetClass() REQUIRES_SHARED(Locks::mutator_lock_);
static void VisitRoots(RootVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_);
- // Returns true iff. |other| is an exact match for this method type, i.e
+ // Returns true iff. |this| is an exact match for method type |target|, i.e
// iff. they have the same return types and parameter types.
- bool IsExactMatch(mirror::MethodType* other) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool IsExactMatch(mirror::MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Returns true iff. |this| can be converted to match |target| method type, i.e
+ // iff. they have convertible return types and parameter types.
+ bool IsConvertible(mirror::MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_);
// Returns the pretty descriptor for this method type, suitable for display in
// exception messages and the like.
diff --git a/runtime/primitive.cc b/runtime/primitive.cc
index d29a060..2380284 100644
--- a/runtime/primitive.cc
+++ b/runtime/primitive.cc
@@ -31,11 +31,35 @@
"PrimVoid",
};
+static const char* kBoxedDescriptors[] = {
+ "Ljava/lang/Object;",
+ "Ljava/lang/Boolean;",
+ "Ljava/lang/Byte;",
+ "Ljava/lang/Character;",
+ "Ljava/lang/Short;",
+ "Ljava/lang/Integer;",
+ "Ljava/lang/Long;",
+ "Ljava/lang/Float;",
+ "Ljava/lang/Double;",
+ "Ljava/lang/Void;",
+};
+
+#define COUNT_OF(x) (sizeof(x) / sizeof(x[0]))
+
const char* Primitive::PrettyDescriptor(Primitive::Type type) {
+ static_assert(COUNT_OF(kTypeNames) == static_cast<size_t>(Primitive::kPrimLast) + 1,
+ "Missing element");
CHECK(Primitive::kPrimNot <= type && type <= Primitive::kPrimVoid) << static_cast<int>(type);
return kTypeNames[type];
}
+const char* Primitive::BoxedDescriptor(Primitive::Type type) {
+ static_assert(COUNT_OF(kBoxedDescriptors) == static_cast<size_t>(Primitive::kPrimLast) + 1,
+ "Missing element");
+ CHECK(Primitive::kPrimNot <= type && type <= Primitive::kPrimVoid) << static_cast<int>(type);
+ return kBoxedDescriptors[type];
+}
+
std::ostream& operator<<(std::ostream& os, const Primitive::Type& type) {
int32_t int_type = static_cast<int32_t>(type);
if (type >= Primitive::kPrimNot && type <= Primitive::kPrimVoid) {
diff --git a/runtime/primitive.h b/runtime/primitive.h
index 18f45ff..7cc47ad 100644
--- a/runtime/primitive.h
+++ b/runtime/primitive.h
@@ -138,6 +138,9 @@
static const char* PrettyDescriptor(Type type);
+ // Returns the descriptor corresponding to the boxed type of |type|.
+ static const char* BoxedDescriptor(Type type);
+
static bool IsFloatingPointType(Type type) {
return type == kPrimFloat || type == kPrimDouble;
}
@@ -158,6 +161,32 @@
}
}
+ // Return true if |type| is an numeric type.
+ static bool IsNumericType(Type type) {
+ switch (type) {
+ case Primitive::Type::kPrimNot: return false;
+ case Primitive::Type::kPrimBoolean: return false;
+ case Primitive::Type::kPrimByte: return true;
+ case Primitive::Type::kPrimChar: return false;
+ case Primitive::Type::kPrimShort: return true;
+ case Primitive::Type::kPrimInt: return true;
+ case Primitive::Type::kPrimLong: return true;
+ case Primitive::Type::kPrimFloat: return true;
+ case Primitive::Type::kPrimDouble: return true;
+ case Primitive::Type::kPrimVoid: return false;
+ }
+ }
+
+ // Returns true if |from| and |to| are the same or a widening conversion exists between them.
+ static bool IsWidenable(Type from, Type to) {
+ static_assert(Primitive::Type::kPrimByte < Primitive::Type::kPrimShort, "Bad ordering");
+ static_assert(Primitive::Type::kPrimShort < Primitive::Type::kPrimInt, "Bad ordering");
+ static_assert(Primitive::Type::kPrimInt < Primitive::Type::kPrimLong, "Bad ordering");
+ static_assert(Primitive::Type::kPrimLong < Primitive::Type::kPrimFloat, "Bad ordering");
+ static_assert(Primitive::Type::kPrimFloat < Primitive::Type::kPrimDouble, "Bad ordering");
+ return IsNumericType(from) && IsNumericType(to) && from <= to;
+ }
+
static bool IsIntOrLongType(Type type) {
return type == kPrimInt || type == kPrimLong;
}
diff --git a/test/956-methodhandles/expected.txt b/test/956-methodhandles/expected.txt
index 9ca448c..0a5caa1 100644
--- a/test/956-methodhandles/expected.txt
+++ b/test/956-methodhandles/expected.txt
@@ -5,3 +5,5 @@
privateRyan_D
Received exception: Expected (java.lang.String, java.lang.String)java.lang.String but was (java.lang.String, java.lang.Object)void
String constructors done.
+testReferenceReturnValueConversions done.
+testPrimitiveReturnValueConversions done.
diff --git a/test/956-methodhandles/src/Main.java b/test/956-methodhandles/src/Main.java
index d0c658f..3d714c9 100644
--- a/test/956-methodhandles/src/Main.java
+++ b/test/956-methodhandles/src/Main.java
@@ -69,6 +69,7 @@
testAsType();
testConstructors();
testStringConstructors();
+ testReturnValueConversions();
}
public static void testfindSpecial_invokeSuperBehaviour() throws Throwable {
@@ -685,6 +686,168 @@
System.out.println("String constructors done.");
}
+
+ private static void testReferenceReturnValueConversions() throws Throwable {
+ MethodHandle mh = MethodHandles.lookup().findStatic(
+ Float.class, "valueOf", MethodType.methodType(Float.class, String.class));
+
+ // No conversion
+ Float f = (Float) mh.invokeExact("1.375");
+ if (f.floatValue() != 1.375) {
+ fail();
+ }
+ f = (Float) mh.invoke("1.875");
+ if (f.floatValue() != 1.875) {
+ fail();
+ }
+
+ // Bad conversion
+ try {
+ int i = (int) mh.invokeExact("7.77");
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ try {
+ int i = (int) mh.invoke("7.77");
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ // Assignment to super-class.
+ Number n = (Number) mh.invoke("1.11");
+ try {
+ Number o = (Number) mh.invokeExact("1.11");
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ // Assignment to widened boxed primitive class.
+ try {
+ Double u = (Double) mh.invoke("1.11");
+ fail();
+ } catch (ClassCastException e) {}
+
+ try {
+ Double v = (Double) mh.invokeExact("1.11");
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ // Unboxed
+ float p = (float) mh.invoke("1.11");
+ if (p != 1.11f) {
+ fail();
+ }
+
+ // Unboxed and widened
+ double d = (double) mh.invoke("2.5");
+ if (d != 2.5) {
+ fail();
+ }
+
+ // Interface
+ Comparable<Float> c = (Comparable<Float>) mh.invoke("2.125");
+ if (c.compareTo(new Float(2.125f)) != 0) {
+ fail();
+ }
+
+ System.out.println("testReferenceReturnValueConversions done.");
+ }
+
+ private static void testPrimitiveReturnValueConversions() throws Throwable {
+ MethodHandle mh = MethodHandles.lookup().findStatic(
+ Math.class, "min", MethodType.methodType(int.class, int.class, int.class));
+
+ final int SMALL = -8972;
+ final int LARGE = 7932529;
+
+ // No conversion
+ if ((int) mh.invokeExact(LARGE, SMALL) != SMALL) {
+ fail();
+ } else if ((int) mh.invoke(LARGE, SMALL) != SMALL) {
+ fail();
+ } else if ((int) mh.invokeExact(SMALL, LARGE) != SMALL) {
+ fail();
+ } else if ((int) mh.invoke(SMALL, LARGE) != SMALL) {
+ fail();
+ }
+
+ // int -> long
+ try {
+ if ((long) mh.invokeExact(LARGE, SMALL) != (long) SMALL) {}
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ if ((long) mh.invoke(LARGE, SMALL) != (long) SMALL) {
+ fail();
+ }
+
+ // int -> short
+ try {
+ if ((short) mh.invokeExact(LARGE, SMALL) != (short) SMALL) {}
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ try {
+ if ((short) mh.invoke(LARGE, SMALL) != (short) SMALL) {
+ fail();
+ }
+ } catch (WrongMethodTypeException e) {}
+
+ // int -> Integer
+ try {
+ if (!((Integer) mh.invokeExact(LARGE, SMALL)).equals(new Integer(SMALL))) {}
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ if (!((Integer) mh.invoke(LARGE, SMALL)).equals(new Integer(SMALL))) {
+ fail();
+ }
+
+ // int -> Long
+ try {
+ Long l = (Long) mh.invokeExact(LARGE, SMALL);
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ try {
+ Long l = (Long) mh.invoke(LARGE, SMALL);
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ // int -> Short
+ try {
+ Short s = (Short) mh.invokeExact(LARGE, SMALL);
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ try {
+ Short s = (Short) mh.invoke(LARGE, SMALL);
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ // int -> Process
+ try {
+ Process p = (Process) mh.invokeExact(LARGE, SMALL);
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ try {
+ Process p = (Process) mh.invoke(LARGE, SMALL);
+ fail();
+ } catch (WrongMethodTypeException e) {}
+
+ // void -> Object
+ mh = MethodHandles.lookup().findStatic(System.class, "gc", MethodType.methodType(void.class));
+ Object o = (Object) mh.invoke();
+ if (o != null) fail();
+
+ // void -> long
+ long l = (long) mh.invoke();
+ if (l != 0) fail();
+
+ System.out.println("testPrimitiveReturnValueConversions done.");
+ }
+
+ public static void testReturnValueConversions() throws Throwable {
+ testReferenceReturnValueConversions();
+ testPrimitiveReturnValueConversions();
+ }
}
-
-
diff --git a/test/959-invoke-polymorphic-accessors/src/Main.java b/test/959-invoke-polymorphic-accessors/src/Main.java
index 824a436..b7ecf8e 100644
--- a/test/959-invoke-polymorphic-accessors/src/Main.java
+++ b/test/959-invoke-polymorphic-accessors/src/Main.java
@@ -780,16 +780,28 @@
} catch (WrongMethodTypeException e) {}
}
+ /*package*/ static Number getDoubleAsNumber() {
+ return new Double(1.4e77);
+ }
+ /*package*/ static Number getFloatAsNumber() {
+ return new Float(7.77);
+ }
+ /*package*/ static Object getFloatAsObject() {
+ return new Float(-7.77);
+ }
+
private static void testMemberSetter() throws Throwable {
ValueHolder valueHolder = new ValueHolder();
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle h0 = lookup.findSetter(ValueHolder.class, "m_f", float.class);
h0.invoke(valueHolder, 0.22f);
h0.invoke(valueHolder, new Float(1.11f));
- Number floatNumber = new Float(0.88f);
+ Number floatNumber = getFloatAsNumber();
h0.invoke(valueHolder, floatNumber);
assertTrue(valueHolder.m_f == floatNumber.floatValue());
-
+ Object objNumber = getFloatAsObject();
+ h0.invoke(valueHolder, objNumber);
+ assertTrue(valueHolder.m_f == ((Float) objNumber).floatValue());
try {
h0.invoke(valueHolder, (Float)null);
unreachable();
@@ -799,12 +811,17 @@
h0.invoke(valueHolder, (short)2);
h0.invoke(valueHolder, 3);
h0.invoke(valueHolder, 4l);
+
+ assertTrue(null == (Object) h0.invoke(valueHolder, 33));
+ assertTrue(0.0f == (float) h0.invoke(valueHolder, 33));
+ assertTrue(0l == (long) h0.invoke(valueHolder, 33));
+
try {
h0.invoke(valueHolder, 0.33);
unreachable();
} catch (WrongMethodTypeException e) {}
try {
- Number doubleNumber = new Double(0.89);
+ Number doubleNumber = getDoubleAsNumber();
h0.invoke(valueHolder, doubleNumber);
unreachable();
} catch (ClassCastException e) {}
@@ -847,12 +864,17 @@
h0.invoke((short)2);
h0.invoke(3);
h0.invoke(4l);
+
+ assertTrue(null == (Object) h0.invoke(33));
+ assertTrue(0.0f == (float) h0.invoke(33));
+ assertTrue(0l == (long) h0.invoke(33));
+
try {
h0.invoke(0.33);
unreachable();
} catch (WrongMethodTypeException e) {}
try {
- Number doubleNumber = new Double(0.89);
+ Number doubleNumber = getDoubleAsNumber();
h0.invoke(doubleNumber);
unreachable();
} catch (ClassCastException e) {}