diff options
| -rw-r--r-- | runtime/interpreter/interpreter_common.cc | 107 | ||||
| -rw-r--r-- | runtime/method_handles.cc | 304 | ||||
| -rw-r--r-- | runtime/method_handles.h | 10 | ||||
| -rw-r--r-- | runtime/mirror/method_type.cc | 32 | ||||
| -rw-r--r-- | runtime/mirror/method_type.h | 8 | ||||
| -rw-r--r-- | runtime/primitive.cc | 24 | ||||
| -rw-r--r-- | runtime/primitive.h | 29 | ||||
| -rw-r--r-- | test/956-methodhandles/expected.txt | 2 | ||||
| -rw-r--r-- | test/956-methodhandles/src/Main.java | 165 | ||||
| -rw-r--r-- | test/959-invoke-polymorphic-accessors/src/Main.java | 30 |
10 files changed, 474 insertions, 237 deletions
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 5e4bb4145d..cb775cd7a3 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -827,6 +827,20 @@ inline static ObjPtr<mirror::Class> GetAndInitializeDeclaringClass(Thread* self, 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 @@ inline bool DoInvokePolymorphic(Thread* self, 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 @@ inline bool DoInvokePolymorphic(Thread* self, // 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 @@ inline bool DoInvokePolymorphic(Thread* self, // 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 @@ inline bool DoInvokePolymorphic(Thread* self, 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 @@ inline bool DoInvokePolymorphic(Thread* self, // 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 @@ inline bool DoInvokePolymorphic(Thread* self, 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 @@ inline bool DoInvokePolymorphic(Thread* self, 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 @@ inline bool DoInvokePolymorphic(Thread* self, 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 @@ inline void CopyRegisters(ShadowFrame& caller_frame, } } -// 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 @@ static inline bool DoCallPolymorphic(ArtMethod* called_method, 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 491d13926f..f1adc32ce2 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; \ } + + PRIMITIVES_LIST(LOOKUP_PRIMITIVE); +#undef LOOKUP_PRIMITIVE + 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(); + jmethodID m = nullptr; 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"; +#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; } + return jni::DecodeArtMethod(m)->GetDeclaringClass(); } -// 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) +bool GetUnboxedTypeAndValue(ObjPtr<mirror::Object> o, Primitive::Type* type, JValue* 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; + 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 @@ inline bool IsPrimitiveType(Primitive::Type type) { } // 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 @@ bool ConvertJValueCommon( 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 @@ bool ConvertJValueCommon( // 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 @@ bool ConvertJValueCommon( 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 @@ bool ConvertJValueCommon( 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 @@ bool ConvertJValueCommon( 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); + 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; + } - // 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 0cc69f2046..54c772a4f2 100644 --- a/runtime/method_handles.h +++ b/runtime/method_handles.h @@ -59,6 +59,16 @@ inline bool IsInvoke(const MethodHandleKind handle_kind) { 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 9b0f87242c..5d77a16e7d 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 @@ mirror::MethodType* MethodType::Create(Thread* const self, return mt.Get(); } -bool MethodType::IsExactMatch(mirror::MethodType* other) REQUIRES_SHARED(Locks::mutator_lock_) { - if (GetRType() != other->GetRType()) { +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 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 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; + } + + // 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 fa700b68ff..9a98143144 100644 --- a/runtime/mirror/method_type.h +++ b/runtime/mirror/method_type.h @@ -52,9 +52,13 @@ class MANAGED MethodType : public Object { 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 d29a06043a..2380284535 100644 --- a/runtime/primitive.cc +++ b/runtime/primitive.cc @@ -31,11 +31,35 @@ static const char* kTypeNames[] = { "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 18f45ffe9d..7cc47ad79b 100644 --- a/runtime/primitive.h +++ b/runtime/primitive.h @@ -138,6 +138,9 @@ class Primitive { 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 @@ class Primitive { } } + // 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 9ca448ce74..0a5caa157e 100644 --- a/test/956-methodhandles/expected.txt +++ b/test/956-methodhandles/expected.txt @@ -5,3 +5,5 @@ foo_B 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 d0c658f819..3d714c9dc2 100644 --- a/test/956-methodhandles/src/Main.java +++ b/test/956-methodhandles/src/Main.java @@ -69,6 +69,7 @@ public class Main { testAsType(); testConstructors(); testStringConstructors(); + testReturnValueConversions(); } public static void testfindSpecial_invokeSuperBehaviour() throws Throwable { @@ -685,6 +686,168 @@ public class Main { 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 824a436f3b..b7ecf8e818 100644 --- a/test/959-invoke-polymorphic-accessors/src/Main.java +++ b/test/959-invoke-polymorphic-accessors/src/Main.java @@ -780,16 +780,28 @@ public class Main { } 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 @@ public class Main { 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 @@ public class Main { 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) {} |