| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include <functional> |
| |
| #include "platform/assert.h" |
| |
| #include "vm/class_finalizer.h" |
| #include "vm/compiler/backend/il_test_helper.h" |
| #include "vm/symbols.h" |
| #include "vm/type_testing_stubs.h" |
| #include "vm/type_testing_stubs_test.h" |
| #include "vm/unit_test.h" |
| |
| #if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \ |
| defined(TARGET_ARCH_X64) |
| |
| namespace dart { |
| |
| static void FinalizeAndCanonicalize(const Class& klass, AbstractType* type) { |
| *type = ClassFinalizer::FinalizeType(klass, *type); |
| ASSERT(type->IsCanonical()); |
| } |
| |
| static void CanonicalizeTAV(TypeArguments* tav) { |
| *tav = tav->Canonicalize(); |
| } |
| |
| static void RunTTSTest( |
| const Object& instance, |
| const AbstractType& dst_type, |
| const TypeArguments& instantiator_tav, |
| const TypeArguments& function_tav, |
| std::function<void(const Object& result, const SubtypeTestCache& stc)> lazy, |
| std::function<void(const Object& result, |
| const SubtypeTestCache& stc, |
| const Bool& abi_regs_modified, |
| const Bool& rest_regs_modified)> nonlazy) { |
| ASSERT(instantiator_tav.IsNull() || instantiator_tav.IsCanonical()); |
| ASSERT(function_tav.IsNull() || function_tav.IsCanonical()); |
| auto thread = Thread::Current(); |
| |
| // Build a stub which will do calling conversion to call TTS stubs. |
| const auto& klass = |
| Class::Handle(thread->isolate()->class_table()->At(kInstanceCid)); |
| const auto& symbol = String::Handle( |
| Symbols::New(thread, OS::SCreate(thread->zone(), "TTSTest"))); |
| const auto& function = Function::Handle( |
| Function::New(symbol, FunctionLayout::kRegularFunction, false, false, |
| false, false, false, klass, TokenPosition::kNoSource)); |
| compiler::ObjectPoolBuilder pool_builder; |
| const auto& invoke_tts = Code::Handle( |
| StubCode::Generate("InvokeTTS", &pool_builder, &GenerateInvokeTTSStub)); |
| const auto& pool = |
| ObjectPool::Handle(ObjectPool::NewFromBuilder(pool_builder)); |
| invoke_tts.set_object_pool(pool.raw()); |
| invoke_tts.set_owner(function); |
| invoke_tts.set_exception_handlers( |
| ExceptionHandlers::Handle(ExceptionHandlers::New(0))); |
| |
| EXPECT(pool.Length() == 2); |
| const intptr_t kSubtypeTestCacheIndex = 0; |
| |
| const auto& arguments_descriptor = |
| Array::Handle(ArgumentsDescriptor::NewBoxed(0, 6)); |
| const auto& arguments = Array::Handle(Array::New(6)); |
| const auto& abi_regs_modified_box = Array::Handle(Array::New(1)); |
| const auto& rest_regs_modified_box = Array::Handle(Array::New(1)); |
| arguments.SetAt(0, abi_regs_modified_box); |
| arguments.SetAt(1, rest_regs_modified_box); |
| arguments.SetAt(2, instance); |
| arguments.SetAt(3, instantiator_tav); |
| arguments.SetAt(4, function_tav); |
| arguments.SetAt(5, dst_type); |
| |
| // Ensure we have a) uninitialized TTS b) no/empty SubtypeTestCache. |
| dst_type.SetTypeTestingStub(StubCode::LazySpecializeTypeTest()); |
| EXPECT(dst_type.type_test_stub() == StubCode::LazySpecializeTypeTest().raw()); |
| EXPECT(pool.ObjectAt(kSubtypeTestCacheIndex) == Object::null()); |
| |
| auto& result = Object::Handle(); |
| auto& result2 = Object::Handle(); |
| auto& abi_regs_modified = Bool::Handle(); |
| auto& rest_regs_modified = Bool::Handle(); |
| auto& tts = Code::Handle(); |
| auto& tts2 = Code::Handle(); |
| auto& stc = SubtypeTestCache::Handle(); |
| auto& stc2 = SubtypeTestCache::Handle(); |
| |
| // First invocation will a) specialize the TTS b) may create SubtypeTestCache |
| result = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments, |
| thread); |
| stc ^= pool.ObjectAt(kSubtypeTestCacheIndex); |
| tts = dst_type.type_test_stub(); |
| if (!result.IsError()) { |
| EXPECT(tts.raw() != StubCode::LazySpecializeTypeTest().raw()); |
| } |
| lazy(result, stc); |
| |
| // Second invocation will a) keep TTS b) keep optional SubtypeTestCache |
| result2 = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments, |
| thread); |
| stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex); |
| tts2 = dst_type.type_test_stub(); |
| abi_regs_modified ^= abi_regs_modified_box.At(0); |
| rest_regs_modified ^= rest_regs_modified_box.At(0); |
| EXPECT(result2.IsError() || !abi_regs_modified.IsNull()); |
| EXPECT(tts2.raw() == tts.raw()); |
| EXPECT(stc2.raw() == stc.raw()); |
| nonlazy(result2, stc2, abi_regs_modified, rest_regs_modified); |
| |
| // Third invocation will a) explicitly install TTS beforehand b) keep optional |
| // SubtypeTestCache |
| // (This is to simulate AOT where we don't use lazy specialization but |
| // precompile the TTS) |
| auto& dst_type_to_specialize = AbstractType::Handle(dst_type.raw()); |
| if (dst_type_to_specialize.IsTypeParameter()) { |
| dst_type_to_specialize = TypeParameter::Cast(dst_type).GetFromTypeArguments( |
| instantiator_tav, function_tav); |
| } |
| TypeTestingStubGenerator::SpecializeStubFor(thread, dst_type_to_specialize); |
| tts = dst_type.type_test_stub(); |
| |
| result2 = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments, |
| thread); |
| stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex); |
| tts2 = dst_type.type_test_stub(); |
| abi_regs_modified ^= abi_regs_modified_box.At(0); |
| rest_regs_modified ^= rest_regs_modified_box.At(0); |
| EXPECT(result2.IsError() || !abi_regs_modified.IsNull()); |
| EXPECT(tts2.raw() == tts.raw()); |
| EXPECT(stc2.raw() == stc.raw()); |
| nonlazy(result2, stc2, abi_regs_modified, rest_regs_modified); |
| } |
| |
| static void ExpectLazilyHandledViaTTS(const Object& result, |
| const SubtypeTestCache& stc) { |
| // Ensure the type test succeeded. |
| EXPECT(result.IsNull()); |
| // Ensure we didn't fall back to the subtype test cache. |
| EXPECT(stc.IsNull()); |
| } |
| |
| static void ExpectHandledViaTTS(const Object& result, |
| const SubtypeTestCache& stc, |
| const Bool& abi_regs_modified, |
| const Bool& rest_regs_modified) { |
| ExpectLazilyHandledViaTTS(result, stc); |
| // Ensure the TTS abi registers were preserved. |
| EXPECT(!abi_regs_modified.value()); |
| // Ensure the non-TTS abi registers were preserved. |
| EXPECT(!rest_regs_modified.value()); |
| } |
| |
| static void ExpectLazilyHandledViaSTC(const Object& result, |
| const SubtypeTestCache& stc) { |
| // Ensure the type test succeeded. |
| EXPECT(result.IsNull()); |
| // Ensure we did fall back to the subtype test cache. |
| EXPECT(!stc.IsNull()); |
| // Ensure the test is marked as succeeding in the STC. |
| EXPECT(stc.NumberOfChecks() == 1); |
| SubtypeTestCacheTable entries(Array::Handle(stc.cache())); |
| EXPECT(entries[0].Get<SubtypeTestCache::kTestResult>() == |
| Object::bool_true().raw()); |
| } |
| |
| static void ExpectHandledViaSTC(const Object& result, |
| const SubtypeTestCache& stc, |
| const Bool& abi_regs_modified, |
| const Bool& rest_regs_modified) { |
| ExpectLazilyHandledViaSTC(result, stc); |
| // Ensure the TTS/STC abi registers were preserved. |
| EXPECT(!abi_regs_modified.value()); |
| } |
| |
| static void ExpectLazilyFailedViaTTS(const Object& result, |
| const SubtypeTestCache& stc) { |
| // Ensure we have not updated STC (which we shouldn't do in case the type test |
| // fails, i.e. an exception is thrown). |
| EXPECT(stc.IsNull()); |
| // Ensure we get a proper exception for the type test. |
| EXPECT(result.IsUnhandledException()); |
| const auto& error = |
| Instance::Handle(UnhandledException::Cast(result).exception()); |
| EXPECT(strstr(error.ToCString(), "_TypeError")); |
| } |
| |
| static void ExpectFailedViaTTS(const Object& result, |
| const SubtypeTestCache& stc, |
| const Bool& abi_regs_modified, |
| const Bool& rest_regs_modified) { |
| ExpectLazilyFailedViaTTS(result, stc); |
| } |
| |
| static void ExpectLazilyFailedViaSTC(const Object& result, |
| const SubtypeTestCache& stc) { |
| // Ensure we have not updated STC (which we shouldn't do in case the type test |
| // fails, i.e. an exception is thrown). |
| EXPECT(stc.IsNull()); |
| // Ensure we get a proper exception for the type test. |
| EXPECT(result.IsUnhandledException()); |
| const auto& error = |
| Instance::Handle(UnhandledException::Cast(result).exception()); |
| EXPECT(strstr(error.ToCString(), "_TypeError")); |
| } |
| |
| static void ExpectFailedViaSTC(const Object& result, |
| const SubtypeTestCache& stc, |
| const Bool& abi_regs_modified, |
| const Bool& rest_regs_modified) { |
| ExpectLazilyFailedViaSTC(result, stc); |
| } |
| |
| const char* kSubtypeRangeCheckScript = |
| R"( |
| class I<T, U> {} |
| class I2 {} |
| |
| class Base<T> {} |
| |
| class A extends Base<int> {} |
| class A1 extends A implements I2 {} |
| class A2<T> extends A implements I<int, T> {} |
| |
| class B extends Base<String> {} |
| class B1 extends B implements I2 {} |
| class B2<T> extends B implements I<T, String> {} |
| |
| genericFun<A, B>() {} |
| |
| createI() => I<int, String>(); |
| createI2() => I2(); |
| createBaseInt() => Base<int>(); |
| createA() => A(); |
| createA1() => A1(); |
| createA2() => A2<int>(); |
| createB() => B(); |
| createB1() => B1(); |
| createB2() => B2<int>(); |
| createBaseIStringDouble() => Base<I<String, double>>(); |
| createBaseA2Int() => Base<A2<int>>(); |
| createBaseA2A1() => Base<A2<A1>>(); |
| createBaseB2Int() => Base<B2<int>>(); |
| )"; |
| |
| ISOLATE_UNIT_TEST_CASE(TTS_SubtypeRangeCheck) { |
| const auto& root_library = |
| Library::Handle(LoadTestScript(kSubtypeRangeCheckScript)); |
| const auto& class_a = Class::Handle(GetClass(root_library, "A")); |
| const auto& class_base = Class::Handle(GetClass(root_library, "Base")); |
| const auto& class_i = Class::Handle(GetClass(root_library, "I")); |
| const auto& class_i2 = Class::Handle(GetClass(root_library, "I2")); |
| const auto& class_null = Class::Handle(Class::null()); |
| |
| const auto& obj_i = Object::Handle(Invoke(root_library, "createI")); |
| const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2")); |
| const auto& obj_baseint = |
| Object::Handle(Invoke(root_library, "createBaseInt")); |
| const auto& obj_a = Object::Handle(Invoke(root_library, "createA")); |
| const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1")); |
| const auto& obj_a2 = Object::Handle(Invoke(root_library, "createA2")); |
| const auto& obj_b = Object::Handle(Invoke(root_library, "createB")); |
| const auto& obj_b1 = Object::Handle(Invoke(root_library, "createB1")); |
| const auto& obj_b2 = Object::Handle(Invoke(root_library, "createB2")); |
| |
| const auto& type_dynamic = Type::Handle(Type::DynamicType()); |
| auto& type_object = Type::Handle(Type::ObjectType()); |
| type_object = type_object.ToNullability(Nullability::kNullable, Heap::kNew); |
| |
| const auto& tav_null = TypeArguments::Handle(TypeArguments::null()); |
| |
| auto& tav_object = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_object.SetTypeAt(0, type_object); |
| CanonicalizeTAV(&tav_object); |
| |
| auto& tav_object_dynamic = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_object_dynamic.SetTypeAt(0, type_object); |
| tav_object_dynamic.SetTypeAt(1, type_dynamic); |
| CanonicalizeTAV(&tav_object_dynamic); |
| |
| auto& tav_dynamic_t = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_dynamic_t.SetTypeAt(0, type_dynamic); |
| tav_dynamic_t.SetTypeAt( |
| 1, TypeParameter::Handle(GetClassTypeParameter(class_base, "T"))); |
| CanonicalizeTAV(&tav_dynamic_t); |
| |
| // We will generate specialized TTS for instantiated interface types |
| // where there are no type arguments or the type arguments are top |
| // types. |
| // |
| // obj as A // Subclass ranges |
| // obj as Base<Object> // Subclass ranges with top-type tav |
| // obj as I2 // Subtype ranges |
| // obj as I<Object, dynamic> // Subtype ranges with top-type tav |
| // |
| |
| // <...> as A |
| const auto& type_a = AbstractType::Handle(class_a.RareType()); |
| RunTTSTest(obj_i, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_i2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_baseint, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_a, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a1, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a2, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_b1, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_b2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| |
| // <...> as Base<Object> |
| auto& type_base = AbstractType::Handle( |
| Type::New(class_base, tav_object, TokenPosition::kNoSource)); |
| FinalizeAndCanonicalize(class_null, &type_base); |
| RunTTSTest(obj_i, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_i2, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_baseint, type_base, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_a, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a1, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a2, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b1, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b2, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| |
| // <...> as I2 |
| const auto& type_i2 = AbstractType::Handle(class_i2.RareType()); |
| RunTTSTest(obj_i, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_i2, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_baseint, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_a, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_a1, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_a2, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_b, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| RunTTSTest(obj_b1, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| RunTTSTest(obj_b2, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| |
| // <...> as I<Object, dynamic> |
| auto& type_i_object_dynamic = AbstractType::Handle( |
| Type::New(class_i, tav_object_dynamic, TokenPosition::kNoSource)); |
| FinalizeAndCanonicalize(class_null, &type_i_object_dynamic); |
| RunTTSTest(obj_i, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_i2, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_baseint, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a1, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a2, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_b, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b1, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b2, type_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| |
| // We do not generate TTS for uninstantiated types if we would need to use |
| // subtype range checks for the class of the interface type. |
| // |
| // obj as I<dynamic, T> |
| // |
| auto& type_dynamic_t = AbstractType::Handle( |
| Type::New(class_i, tav_dynamic_t, TokenPosition::kNoSource)); |
| FinalizeAndCanonicalize(class_base, &type_dynamic_t); |
| RunTTSTest(obj_i, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_i2, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_baseint, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_a, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_a1, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_a2, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_b, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_b1, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| RunTTSTest(obj_b2, type_dynamic_t, tav_object, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| |
| // obj as Object (with null safety) |
| Isolate* isolate = Isolate::Current(); |
| if (isolate->null_safety()) { |
| auto& type_non_nullable_object = |
| Type::Handle(isolate->object_store()->non_nullable_object_type()); |
| RunTTSTest(obj_a, type_non_nullable_object, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(Object::null_object(), type_non_nullable_object, tav_null, |
| tav_null, ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| } |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(TTS_GenericSubtypeRangeCheck) { |
| const auto& root_library = |
| Library::Handle(LoadTestScript(kSubtypeRangeCheckScript)); |
| const auto& class_a1 = Class::Handle(GetClass(root_library, "A1")); |
| const auto& class_a2 = Class::Handle(GetClass(root_library, "A2")); |
| const auto& class_base = Class::Handle(GetClass(root_library, "Base")); |
| const auto& class_i = Class::Handle(GetClass(root_library, "I")); |
| const auto& fun_generic = |
| Function::Handle(GetFunction(root_library, "genericFun")); |
| const auto& class_null = Class::Handle(Class::null()); |
| |
| const auto& obj_i = Object::Handle(Invoke(root_library, "createI")); |
| const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2")); |
| const auto& obj_baseint = |
| Object::Handle(Invoke(root_library, "createBaseInt")); |
| const auto& obj_a = Object::Handle(Invoke(root_library, "createA")); |
| const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1")); |
| const auto& obj_a2 = Object::Handle(Invoke(root_library, "createA2")); |
| const auto& obj_b = Object::Handle(Invoke(root_library, "createB")); |
| const auto& obj_b1 = Object::Handle(Invoke(root_library, "createB1")); |
| const auto& obj_b2 = Object::Handle(Invoke(root_library, "createB2")); |
| const auto& obj_basea2int = |
| Object::Handle(Invoke(root_library, "createBaseA2Int")); |
| const auto& obj_basea2a1 = |
| Object::Handle(Invoke(root_library, "createBaseA2A1")); |
| const auto& obj_baseb2int = |
| Object::Handle(Invoke(root_library, "createBaseB2Int")); |
| const auto& obj_baseistringdouble = |
| Object::Handle(Invoke(root_library, "createBaseIStringDouble")); |
| |
| const auto& type_dynamic = Type::Handle(Type::DynamicType()); |
| auto& type_int = Type::Handle(Type::IntType()); |
| if (!TestCase::IsNNBD()) { |
| type_int = type_int.ToNullability(Nullability::kLegacy, Heap::kNew); |
| } |
| auto& type_string = Type::Handle(Type::StringType()); |
| if (!TestCase::IsNNBD()) { |
| type_string = type_string.ToNullability(Nullability::kLegacy, Heap::kNew); |
| } |
| auto& type_object = Type::Handle(Type::ObjectType()); |
| type_object = type_object.ToNullability( |
| TestCase::IsNNBD() ? Nullability::kNullable : Nullability::kLegacy, |
| Heap::kNew); |
| auto& type_a1 = Type::Handle(class_a1.DeclarationType()); |
| if (!TestCase::IsNNBD()) { |
| type_a1 = type_a1.ToNullability(Nullability::kLegacy, Heap::kNew); |
| } |
| FinalizeAndCanonicalize(class_null, &type_a1); |
| |
| const auto& tav_null = TypeArguments::Handle(TypeArguments::null()); |
| |
| auto& tav_object_dynamic = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_object_dynamic.SetTypeAt(0, type_object); |
| tav_object_dynamic.SetTypeAt(1, type_dynamic); |
| CanonicalizeTAV(&tav_object_dynamic); |
| |
| auto& tav_dynamic_int = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_dynamic_int.SetTypeAt(0, type_dynamic); |
| tav_dynamic_int.SetTypeAt(1, type_int); |
| CanonicalizeTAV(&tav_dynamic_int); |
| |
| auto& tav_dynamic_string = TypeArguments::Handle(TypeArguments::New(2)); |
| tav_dynamic_string.SetTypeAt(0, type_dynamic); |
| tav_dynamic_string.SetTypeAt(1, type_string); |
| CanonicalizeTAV(&tav_dynamic_string); |
| |
| auto& tav_int = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_int.SetTypeAt(0, type_int); |
| CanonicalizeTAV(&tav_int); |
| |
| auto& type_i_object_dynamic = AbstractType::Handle( |
| Type::New(class_i, tav_object_dynamic, TokenPosition::kNoSource)); |
| FinalizeAndCanonicalize(class_null, &type_i_object_dynamic); |
| const auto& tav_iod = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_iod.SetTypeAt(0, type_i_object_dynamic); |
| |
| // We will generate specialized TTS for instantiated interface types |
| // where there are no type arguments or the type arguments are top |
| // types. |
| // |
| // obj as Base<I<Object, dynamic>> // Subclass ranges for Base, subtype |
| // // ranges tav arguments. |
| // obj as Base<T> // Subclass ranges for Base, type |
| // // equality for instantiator type arg T |
| // obj as Base<B> // Subclass ranges for Base, type |
| // // equality for function type arg B. |
| // |
| |
| // <...> as Base<I<Object, dynamic>> |
| auto& type_base_i_object_dynamic = AbstractType::Handle( |
| Type::New(class_base, tav_iod, TokenPosition::kNoSource)); |
| FinalizeAndCanonicalize(class_null, &type_base_i_object_dynamic); |
| RunTTSTest(obj_baseb2int, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_baseistringdouble, type_base_i_object_dynamic, tav_null, |
| tav_null, ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_a, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a1, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_a2, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b1, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| RunTTSTest(obj_b2, type_base_i_object_dynamic, tav_null, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| |
| // <...> as Base<T> with T instantiantiator type parameter (T == int) |
| const auto& tav_baset = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_baset.SetTypeAt( |
| 0, TypeParameter::Handle(GetClassTypeParameter(class_base, "T"))); |
| auto& type_base_t = AbstractType::Handle( |
| Type::New(class_base, tav_baset, TokenPosition::kNoSource)); |
| FinalizeAndCanonicalize(class_base, &type_base_t); |
| RunTTSTest(obj_baseint, type_base_t, tav_int, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_baseistringdouble, type_base_t, tav_int, tav_null, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| |
| // <...> as Base<B> with B function type parameter |
| const auto& tav_baseb = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_baseb.SetTypeAt( |
| 0, TypeParameter::Handle(GetFunctionTypeParameter(fun_generic, "B"))); |
| auto& type_base_b = AbstractType::Handle( |
| Type::New(class_base, tav_baseb, TokenPosition::kNoSource)); |
| FinalizeAndCanonicalize(class_null, &type_base_b); |
| // With B == int |
| RunTTSTest(obj_baseint, type_base_b, tav_null, tav_dynamic_int, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_baseistringdouble, type_base_b, tav_null, tav_dynamic_int, |
| ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
| // With B == dynamic (null vector) |
| RunTTSTest(obj_baseint, type_base_b, tav_null, tav_null, |
| ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
| RunTTSTest(obj_i2, type_base_b, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| |
| // We do not generate TTS for uninstantiated types if we would need to use |
| // subtype range checks for the class of the interface type. |
| // |
| // obj as I<dynamic, String> // I is generic & implemented. |
| // obj as Base<A2<T>> // A2<T> is not instantiated. |
| // obj as Base<A2<A1>> // A2<A1> is not a rare type. |
| // |
| |
| // <...> as I<dynamic, String> |
| RELEASE_ASSERT(class_i.is_implemented()); |
| auto& type_i_dynamic_string = Type::Handle( |
| Type::New(class_i, tav_dynamic_string, TokenPosition::kNoSource)); |
| type_i_dynamic_string = type_i_dynamic_string.ToNullability( |
| Nullability::kNonNullable, Heap::kNew); |
| FinalizeAndCanonicalize(class_null, &type_i_dynamic_string); |
| RunTTSTest(obj_i, type_i_dynamic_string, tav_null, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_baseint, type_i_dynamic_string, tav_null, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| |
| // <...> as Base<A2<T>> |
| const auto& tav_t = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_t.SetTypeAt( |
| 0, TypeParameter::Handle(GetClassTypeParameter(class_base, "T"))); |
| auto& type_a2_t = |
| Type::Handle(Type::New(class_a2, tav_t, TokenPosition::kNoSource)); |
| type_a2_t = type_a2_t.ToNullability(Nullability::kLegacy, Heap::kNew); |
| FinalizeAndCanonicalize(class_null, &type_a2_t); |
| const auto& tav_a2_t = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_a2_t.SetTypeAt(0, type_a2_t); |
| auto& type_base_a2_t = |
| Type::Handle(Type::New(class_base, tav_a2_t, TokenPosition::kNoSource)); |
| type_base_a2_t = |
| type_base_a2_t.ToNullability(Nullability::kNonNullable, Heap::kNew); |
| FinalizeAndCanonicalize(class_null, &type_base_a2_t); |
| RunTTSTest(obj_basea2int, type_base_a2_t, tav_null, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_baseint, type_base_a2_t, tav_null, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| |
| // <...> as Base<A2<A1>> |
| const auto& tav_a1 = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_a1.SetTypeAt(0, type_a1); |
| auto& type_a2_a1 = |
| Type::Handle(Type::New(class_a2, tav_a1, TokenPosition::kNoSource)); |
| type_a2_a1 = type_a2_a1.ToNullability(Nullability::kLegacy, Heap::kNew); |
| FinalizeAndCanonicalize(class_null, &type_a2_a1); |
| const auto& tav_a2_a1 = TypeArguments::Handle(TypeArguments::New(1)); |
| tav_a2_a1.SetTypeAt(0, type_a2_a1); |
| auto& type_base_a2_a1 = |
| Type::Handle(Type::New(class_base, tav_a2_a1, TokenPosition::kNoSource)); |
| type_base_a2_a1 = |
| type_base_a2_a1.ToNullability(Nullability::kNonNullable, Heap::kNew); |
| FinalizeAndCanonicalize(class_null, &type_base_a2_a1); |
| RunTTSTest(obj_basea2a1, type_base_a2_a1, tav_null, tav_null, |
| ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
| RunTTSTest(obj_basea2int, type_base_a2_a1, tav_null, tav_null, |
| ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(TTS_Regress40964) { |
| const char* kScript = |
| R"( |
| class A<T> { |
| test(x) => x as B<T>; |
| } |
| class B<T> {} |
| class C<T> {} |
| |
| createACint() => A<C<int>>(); |
| createBCint() => B<C<int>>(); |
| createBCnum() => B<C<num>>(); |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& class_b = Class::Handle(GetClass(root_library, "B")); |
| const auto& class_null = Class::Handle(Class::null()); |
| |
| const auto& acint = Object::Handle(Invoke(root_library, "createACint")); |
| const auto& bcint = Object::Handle(Invoke(root_library, "createBCint")); |
| const auto& bcnum = Object::Handle(Invoke(root_library, "createBCnum")); |
| |
| // dst_type = B<T> |
| const auto& dst_tav = TypeArguments::Handle(TypeArguments::New(1)); |
| dst_tav.SetTypeAt(0, |
| TypeParameter::Handle(GetClassTypeParameter(class_b, "T"))); |
| auto& dst_type = |
| Type::Handle(Type::New(class_b, dst_tav, TokenPosition::kNoSource)); |
| FinalizeAndCanonicalize(class_null, &dst_type); |
| const auto& cint_tav = |
| TypeArguments::Handle(Instance::Cast(acint).GetTypeArguments()); |
| const auto& function_tav = TypeArguments::Handle(); |
| |
| // a as B<T> -- a==B<C<int>, T==<C<int>> |
| RunTTSTest(bcint, dst_type, cint_tav, function_tav, ExpectLazilyHandledViaTTS, |
| ExpectHandledViaTTS); |
| |
| // a as B<T> -- a==B<C<num>, T==<C<int>> |
| RunTTSTest(bcnum, dst_type, cint_tav, function_tav, ExpectLazilyFailedViaTTS, |
| ExpectFailedViaTTS); |
| } |
| |
| } // namespace dart |
| |
| #endif // defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \ |
| // defined(TARGET_ARCH_X64) |