/*
 * Copyright (C) 2018 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 "class_linker-inl.h"
#include "class_root-inl.h"
#include "common_runtime_test.h"
#include "handle_scope-inl.h"
#include "jvalue-inl.h"
#include "mirror/method_type.h"
#include "mirror/object_array-alloc-inl.h"
#include "mirror/object_array-inl.h"
#include "reflection.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-current-inl.h"

namespace art HIDDEN {

namespace {
  bool IsClassCastException(ObjPtr<mirror::Throwable> throwable)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    return throwable->GetClass()->DescriptorEquals("Ljava/lang/ClassCastException;");
  }

  bool IsNullPointerException(ObjPtr<mirror::Throwable> throwable)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    return throwable->GetClass()->DescriptorEquals("Ljava/lang/NullPointerException;");
  }

  bool IsWrongMethodTypeException(ObjPtr<mirror::Throwable> throwable)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    return throwable->GetClass()->DescriptorEquals("Ljava/lang/invoke/WrongMethodTypeException;");
  }

  static bool TryConversion(Handle<mirror::Class> from, Handle<mirror::Class> to, JValue* value)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    class ThrowWrongMethodTypeFunctionImpl final : public ThrowWrongMethodTypeFunction {
      void operator()() const override REQUIRES_SHARED(Locks::mutator_lock_) {
        ThrowWrongMethodTypeException("<callee-descriptor>", "<callsite-descriptor>");
      }
    };
    ThrowWrongMethodTypeFunctionImpl throw_wmt;
    return ConvertJValueCommon(throw_wmt, from.Get(), to.Get(), value);
  }
}  // namespace

class MethodHandlesTest : public CommonRuntimeTest {
 protected:
  MethodHandlesTest() {
    use_boot_image_ = true;  // Make the Runtime creation cheaper.
  }
};

//
// Primitive -> Primitive Conversions
//

TEST_F(MethodHandlesTest, SupportedPrimitiveWideningBI) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('B'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('I'));
  JValue value = JValue::FromPrimitive(static_cast<int8_t>(3));
  ASSERT_TRUE(TryConversion(from, to, &value));
  ASSERT_EQ(3, value.GetI());
  ASSERT_FALSE(soa.Self()->IsExceptionPending());
}

TEST_F(MethodHandlesTest, SupportedPrimitiveWideningCJ) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('C'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('J'));
  uint16_t raw_value = 0x8000;
  JValue value = JValue::FromPrimitive(raw_value);
  ASSERT_TRUE(TryConversion(from, to, &value));
  ASSERT_FALSE(soa.Self()->IsExceptionPending());
  ASSERT_EQ(static_cast<int64_t>(raw_value), value.GetJ());
}

TEST_F(MethodHandlesTest, SupportedPrimitiveWideningIF) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('I'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('F'));
  JValue value = JValue::FromPrimitive(-16);
  ASSERT_TRUE(TryConversion(from, to, &value));
  ASSERT_FALSE(soa.Self()->IsExceptionPending());
  ASSERT_FLOAT_EQ(-16.0f, value.GetF());
}

TEST_F(MethodHandlesTest, UnsupportedPrimitiveWideningBC) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('B'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('C'));
  JValue value;
  value.SetB(0);
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsWrongMethodTypeException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

TEST_F(MethodHandlesTest, UnsupportedPrimitiveWideningSC) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('S'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('C'));
  JValue value;
  value.SetS(0x1234);
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsWrongMethodTypeException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

TEST_F(MethodHandlesTest, UnsupportedPrimitiveWideningDJ) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('D'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('J'));
  JValue value;
  value.SetD(1e72);
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsWrongMethodTypeException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

TEST_F(MethodHandlesTest, UnsupportedPrimitiveWideningZI) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('Z'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('I'));
  JValue value;
  value.SetZ(true);
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsWrongMethodTypeException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

//
// Reference -> Reference Conversions
//

TEST_F(MethodHandlesTest, SupportedReferenceCast) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<3> hs(soa.Self());
  static const int32_t kInitialValue = 101;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Object> boxed_value = hs.NewHandle(BoxPrimitive(Primitive::kPrimInt, value));
  Handle<mirror::Class> from = hs.NewHandle(boxed_value->GetClass());
  Handle<mirror::Class> to = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Number;"));
  value.SetL(boxed_value.Get());
  ASSERT_TRUE(TryConversion(from, to, &value));
  ASSERT_FALSE(soa.Self()->IsExceptionPending());
  JValue unboxed_value;
  ASSERT_TRUE(UnboxPrimitiveForResult(value.GetL(), cl->FindPrimitiveClass('I'), &unboxed_value));
  ASSERT_EQ(kInitialValue, unboxed_value.GetI());
}

TEST_F(MethodHandlesTest, UnsupportedReferenceCast) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<3> hs(soa.Self());
  JValue value = JValue::FromPrimitive(3.733e2);
  Handle<mirror::Object> boxed_value = hs.NewHandle(BoxPrimitive(Primitive::kPrimDouble, value));
  Handle<mirror::Class> from = hs.NewHandle(boxed_value->GetClass());
  Handle<mirror::Class> to = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Integer;"));
  value.SetL(boxed_value.Get());
  ASSERT_FALSE(soa.Self()->IsExceptionPending());
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsClassCastException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

//
// Primitive -> Reference Conversions
//

TEST_F(MethodHandlesTest, SupportedPrimitiveConversionPrimitiveToBoxed) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  const int32_t kInitialValue = 1;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('I'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Integer;"));
  ASSERT_TRUE(TryConversion(from, to, &value));
  ASSERT_FALSE(soa.Self()->IsExceptionPending());
  JValue unboxed_to_value;
  ASSERT_TRUE(UnboxPrimitiveForResult(value.GetL(), from.Get(), &unboxed_to_value));
  ASSERT_EQ(kInitialValue, unboxed_to_value.GetI());
}

TEST_F(MethodHandlesTest, SupportedPrimitiveConversionPrimitiveToBoxedSuper) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  const int32_t kInitialValue = 1;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('I'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Number;"));
  ASSERT_TRUE(TryConversion(from, to, &value));
  ASSERT_FALSE(soa.Self()->IsExceptionPending());
  JValue unboxed_to_value;
  ASSERT_TRUE(UnboxPrimitiveForResult(value.GetL(), from.Get(), &unboxed_to_value));
  ASSERT_EQ(kInitialValue, unboxed_to_value.GetI());
}

TEST_F(MethodHandlesTest, UnsupportedPrimitiveConversionNotBoxable) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  const int32_t kInitialValue = 1;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('I'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Runtime;"));
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsWrongMethodTypeException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

TEST_F(MethodHandlesTest, UnsupportedPrimitiveConversionPrimitiveToBoxedWider) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  const int32_t kInitialValue = 1;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('I'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Long;"));
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsWrongMethodTypeException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

TEST_F(MethodHandlesTest, UnsupportedPrimitiveConversionPrimitiveToBoxedNarrower) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  const int32_t kInitialValue = 1;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Class> from = hs.NewHandle(cl->FindPrimitiveClass('I'));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Byte;"));
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsWrongMethodTypeException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

//
// Reference -> Primitive Conversions
//

TEST_F(MethodHandlesTest, SupportedBoxedToPrimitiveConversion) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<3> hs(soa.Self());
  const int32_t kInitialValue = 101;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Object> boxed_value = hs.NewHandle(BoxPrimitive(Primitive::kPrimInt, value));
  Handle<mirror::Class> from = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Integer;"));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('I'));
  value.SetL(boxed_value.Get());
  ASSERT_TRUE(TryConversion(from, to, &value));
  ASSERT_FALSE(soa.Self()->IsExceptionPending());
  ASSERT_EQ(kInitialValue, value.GetI());
}

TEST_F(MethodHandlesTest, SupportedBoxedToWiderPrimitiveConversion) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<3> hs(soa.Self());
  static const int32_t kInitialValue = 101;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Object> boxed_value = hs.NewHandle(BoxPrimitive(Primitive::kPrimInt, value));
  Handle<mirror::Class> from = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Integer;"));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('J'));
  value.SetL(boxed_value.Get());
  ASSERT_TRUE(TryConversion(from, to, &value));
  ASSERT_EQ(kInitialValue, value.GetJ());
}

TEST_F(MethodHandlesTest, UnsupportedNullBoxedToPrimitiveConversion) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<3> hs(soa.Self());
  JValue value = JValue::FromPrimitive(101);
  ScopedNullHandle<mirror::Object> boxed_value;
  Handle<mirror::Class> from = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Integer;"));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('I'));
  value.SetL(boxed_value.Get());
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsNullPointerException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

TEST_F(MethodHandlesTest, UnsupportedNotBoxReferenceToPrimitiveConversion) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::Class> from = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Class;"));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('I'));
  // Set value to be converted as some non-primitive type.
  JValue value;
  value.SetL(cl->FindPrimitiveClass('V'));
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsClassCastException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

TEST_F(MethodHandlesTest, UnsupportedBoxedToNarrowerPrimitiveConversionNoCast) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<3> hs(soa.Self());
  static const int32_t kInitialValue = 101;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Object> boxed_value = hs.NewHandle(BoxPrimitive(Primitive::kPrimInt, value));
  Handle<mirror::Class> from = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Integer;"));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('S'));
  value.SetL(boxed_value.Get());
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsWrongMethodTypeException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

TEST_F(MethodHandlesTest, UnsupportedBoxedToNarrowerPrimitiveConversionWithCast) {
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  StackHandleScope<3> hs(soa.Self());
  static const double kInitialValue = 1e77;
  JValue value = JValue::FromPrimitive(kInitialValue);
  Handle<mirror::Object> boxed_value = hs.NewHandle(BoxPrimitive(Primitive::kPrimDouble, value));
  Handle<mirror::Class> from = hs.NewHandle(cl->FindSystemClass(soa.Self(), "Ljava/lang/Number;"));
  Handle<mirror::Class> to = hs.NewHandle(cl->FindPrimitiveClass('F'));
  value.SetL(boxed_value.Get());
  ASSERT_FALSE(TryConversion(from, to, &value));
  ASSERT_TRUE(soa.Self()->IsExceptionPending());
  ASSERT_TRUE(IsClassCastException(soa.Self()->GetException()));
  soa.Self()->ClearException();
}

}  // namespace art
