1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include <functional> |
6 | |
7 | #include "platform/assert.h" |
8 | |
9 | #include "vm/class_finalizer.h" |
10 | #include "vm/compiler/backend/il_test_helper.h" |
11 | #include "vm/symbols.h" |
12 | #include "vm/type_testing_stubs.h" |
13 | #include "vm/type_testing_stubs_test.h" |
14 | #include "vm/unit_test.h" |
15 | |
16 | #if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \ |
17 | defined(TARGET_ARCH_X64) |
18 | |
19 | namespace dart { |
20 | |
21 | static void FinalizeAndCanonicalize(const Class& klass, AbstractType* type) { |
22 | *type = ClassFinalizer::FinalizeType(klass, *type); |
23 | ASSERT(type->IsCanonical()); |
24 | } |
25 | |
26 | static void CanonicalizeTAV(TypeArguments* tav) { |
27 | *tav = tav->Canonicalize(); |
28 | } |
29 | |
30 | static void RunTTSTest( |
31 | const Object& instance, |
32 | const AbstractType& dst_type, |
33 | const TypeArguments& instantiator_tav, |
34 | const TypeArguments& function_tav, |
35 | std::function<void(const Object& result, const SubtypeTestCache& stc)> lazy, |
36 | std::function<void(const Object& result, |
37 | const SubtypeTestCache& stc, |
38 | const Bool& abi_regs_modified, |
39 | const Bool& rest_regs_modified)> nonlazy) { |
40 | ASSERT(instantiator_tav.IsNull() || instantiator_tav.IsCanonical()); |
41 | ASSERT(function_tav.IsNull() || function_tav.IsCanonical()); |
42 | auto thread = Thread::Current(); |
43 | |
44 | // Build a stub which will do calling conversion to call TTS stubs. |
45 | const auto& klass = |
46 | Class::Handle(thread->isolate()->class_table()->At(kInstanceCid)); |
47 | const auto& symbol = String::Handle( |
48 | Symbols::New(thread, OS::SCreate(thread->zone(), "TTSTest" ))); |
49 | const auto& function = Function::Handle( |
50 | Function::New(symbol, FunctionLayout::kRegularFunction, false, false, |
51 | false, false, false, klass, TokenPosition::kNoSource)); |
52 | compiler::ObjectPoolBuilder pool_builder; |
53 | const auto& invoke_tts = Code::Handle( |
54 | StubCode::Generate("InvokeTTS" , &pool_builder, &GenerateInvokeTTSStub)); |
55 | const auto& pool = |
56 | ObjectPool::Handle(ObjectPool::NewFromBuilder(pool_builder)); |
57 | invoke_tts.set_object_pool(pool.raw()); |
58 | invoke_tts.set_owner(function); |
59 | invoke_tts.set_exception_handlers( |
60 | ExceptionHandlers::Handle(ExceptionHandlers::New(0))); |
61 | |
62 | EXPECT(pool.Length() == 2); |
63 | const intptr_t kSubtypeTestCacheIndex = 0; |
64 | |
65 | const auto& arguments_descriptor = |
66 | Array::Handle(ArgumentsDescriptor::NewBoxed(0, 6)); |
67 | const auto& arguments = Array::Handle(Array::New(6)); |
68 | const auto& abi_regs_modified_box = Array::Handle(Array::New(1)); |
69 | const auto& rest_regs_modified_box = Array::Handle(Array::New(1)); |
70 | arguments.SetAt(0, abi_regs_modified_box); |
71 | arguments.SetAt(1, rest_regs_modified_box); |
72 | arguments.SetAt(2, instance); |
73 | arguments.SetAt(3, instantiator_tav); |
74 | arguments.SetAt(4, function_tav); |
75 | arguments.SetAt(5, dst_type); |
76 | |
77 | // Ensure we have a) uninitialized TTS b) no/empty SubtypeTestCache. |
78 | dst_type.SetTypeTestingStub(StubCode::LazySpecializeTypeTest()); |
79 | EXPECT(dst_type.type_test_stub() == StubCode::LazySpecializeTypeTest().raw()); |
80 | EXPECT(pool.ObjectAt(kSubtypeTestCacheIndex) == Object::null()); |
81 | |
82 | auto& result = Object::Handle(); |
83 | auto& result2 = Object::Handle(); |
84 | auto& abi_regs_modified = Bool::Handle(); |
85 | auto& rest_regs_modified = Bool::Handle(); |
86 | auto& tts = Code::Handle(); |
87 | auto& tts2 = Code::Handle(); |
88 | auto& stc = SubtypeTestCache::Handle(); |
89 | auto& stc2 = SubtypeTestCache::Handle(); |
90 | |
91 | // First invocation will a) specialize the TTS b) may create SubtypeTestCache |
92 | result = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments, |
93 | thread); |
94 | stc ^= pool.ObjectAt(kSubtypeTestCacheIndex); |
95 | tts = dst_type.type_test_stub(); |
96 | if (!result.IsError()) { |
97 | EXPECT(tts.raw() != StubCode::LazySpecializeTypeTest().raw()); |
98 | } |
99 | lazy(result, stc); |
100 | |
101 | // Second invocation will a) keep TTS b) keep optional SubtypeTestCache |
102 | result2 = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments, |
103 | thread); |
104 | stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex); |
105 | tts2 = dst_type.type_test_stub(); |
106 | abi_regs_modified ^= abi_regs_modified_box.At(0); |
107 | rest_regs_modified ^= rest_regs_modified_box.At(0); |
108 | EXPECT(result2.IsError() || !abi_regs_modified.IsNull()); |
109 | EXPECT(tts2.raw() == tts.raw()); |
110 | EXPECT(stc2.raw() == stc.raw()); |
111 | nonlazy(result2, stc2, abi_regs_modified, rest_regs_modified); |
112 | |
113 | // Third invocation will a) explicitly install TTS beforehand b) keep optional |
114 | // SubtypeTestCache |
115 | // (This is to simulate AOT where we don't use lazy specialization but |
116 | // precompile the TTS) |
117 | auto& dst_type_to_specialize = AbstractType::Handle(dst_type.raw()); |
118 | if (dst_type_to_specialize.IsTypeParameter()) { |
119 | dst_type_to_specialize = TypeParameter::Cast(dst_type).GetFromTypeArguments( |
120 | instantiator_tav, function_tav); |
121 | } |
122 | TypeTestingStubGenerator::SpecializeStubFor(thread, dst_type_to_specialize); |
123 | tts = dst_type.type_test_stub(); |
124 | |
125 | result2 = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments, |
126 | thread); |
127 | stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex); |
128 | tts2 = dst_type.type_test_stub(); |
129 | abi_regs_modified ^= abi_regs_modified_box.At(0); |
130 | rest_regs_modified ^= rest_regs_modified_box.At(0); |
131 | EXPECT(result2.IsError() || !abi_regs_modified.IsNull()); |
132 | EXPECT(tts2.raw() == tts.raw()); |
133 | EXPECT(stc2.raw() == stc.raw()); |
134 | nonlazy(result2, stc2, abi_regs_modified, rest_regs_modified); |
135 | } |
136 | |
137 | static void ExpectLazilyHandledViaTTS(const Object& result, |
138 | const SubtypeTestCache& stc) { |
139 | // Ensure the type test succeeded. |
140 | EXPECT(result.IsNull()); |
141 | // Ensure we didn't fall back to the subtype test cache. |
142 | EXPECT(stc.IsNull()); |
143 | } |
144 | |
145 | static void ExpectHandledViaTTS(const Object& result, |
146 | const SubtypeTestCache& stc, |
147 | const Bool& abi_regs_modified, |
148 | const Bool& rest_regs_modified) { |
149 | ExpectLazilyHandledViaTTS(result, stc); |
150 | // Ensure the TTS abi registers were preserved. |
151 | EXPECT(!abi_regs_modified.value()); |
152 | // Ensure the non-TTS abi registers were preserved. |
153 | EXPECT(!rest_regs_modified.value()); |
154 | } |
155 | |
156 | static void ExpectLazilyHandledViaSTC(const Object& result, |
157 | const SubtypeTestCache& stc) { |
158 | // Ensure the type test succeeded. |
159 | EXPECT(result.IsNull()); |
160 | // Ensure we did fall back to the subtype test cache. |
161 | EXPECT(!stc.IsNull()); |
162 | // Ensure the test is marked as succeeding in the STC. |
163 | EXPECT(stc.NumberOfChecks() == 1); |
164 | SubtypeTestCacheTable entries(Array::Handle(stc.cache())); |
165 | EXPECT(entries[0].Get<SubtypeTestCache::kTestResult>() == |
166 | Object::bool_true().raw()); |
167 | } |
168 | |
169 | static void ExpectHandledViaSTC(const Object& result, |
170 | const SubtypeTestCache& stc, |
171 | const Bool& abi_regs_modified, |
172 | const Bool& rest_regs_modified) { |
173 | ExpectLazilyHandledViaSTC(result, stc); |
174 | // Ensure the TTS/STC abi registers were preserved. |
175 | EXPECT(!abi_regs_modified.value()); |
176 | } |
177 | |
178 | static void ExpectLazilyFailedViaTTS(const Object& result, |
179 | const SubtypeTestCache& stc) { |
180 | // Ensure we have not updated STC (which we shouldn't do in case the type test |
181 | // fails, i.e. an exception is thrown). |
182 | EXPECT(stc.IsNull()); |
183 | // Ensure we get a proper exception for the type test. |
184 | EXPECT(result.IsUnhandledException()); |
185 | const auto& error = |
186 | Instance::Handle(UnhandledException::Cast(result).exception()); |
187 | EXPECT(strstr(error.ToCString(), "_TypeError" )); |
188 | } |
189 | |
190 | static void ExpectFailedViaTTS(const Object& result, |
191 | const SubtypeTestCache& stc, |
192 | const Bool& abi_regs_modified, |
193 | const Bool& rest_regs_modified) { |
194 | ExpectLazilyFailedViaTTS(result, stc); |
195 | } |
196 | |
197 | static void ExpectLazilyFailedViaSTC(const Object& result, |
198 | const SubtypeTestCache& stc) { |
199 | // Ensure we have not updated STC (which we shouldn't do in case the type test |
200 | // fails, i.e. an exception is thrown). |
201 | EXPECT(stc.IsNull()); |
202 | // Ensure we get a proper exception for the type test. |
203 | EXPECT(result.IsUnhandledException()); |
204 | const auto& error = |
205 | Instance::Handle(UnhandledException::Cast(result).exception()); |
206 | EXPECT(strstr(error.ToCString(), "_TypeError" )); |
207 | } |
208 | |
209 | static void ExpectFailedViaSTC(const Object& result, |
210 | const SubtypeTestCache& stc, |
211 | const Bool& abi_regs_modified, |
212 | const Bool& rest_regs_modified) { |
213 | ExpectLazilyFailedViaSTC(result, stc); |
214 | } |
215 | |
216 | const char* kSubtypeRangeCheckScript = |
217 | R"( |
218 | class I<T, U> {} |
219 | class I2 {} |
220 | |
221 | class Base<T> {} |
222 | |
223 | class A extends Base<int> {} |
224 | class A1 extends A implements I2 {} |
225 | class A2<T> extends A implements I<int, T> {} |
226 | |
227 | class B extends Base<String> {} |
228 | class B1 extends B implements I2 {} |
229 | class B2<T> extends B implements I<T, String> {} |
230 | |
231 | genericFun<A, B>() {} |
232 | |
233 | createI() => I<int, String>(); |
234 | createI2() => I2(); |
235 | createBaseInt() => Base<int>(); |
236 | createA() => A(); |
237 | createA1() => A1(); |
238 | createA2() => A2<int>(); |
239 | createB() => B(); |
240 | createB1() => B1(); |
241 | createB2() => B2<int>(); |
242 | createBaseIStringDouble() => Base<I<String, double>>(); |
243 | createBaseA2Int() => Base<A2<int>>(); |
244 | createBaseA2A1() => Base<A2<A1>>(); |
245 | createBaseB2Int() => Base<B2<int>>(); |
246 | )" ; |
247 | |
248 | ISOLATE_UNIT_TEST_CASE(TTS_SubtypeRangeCheck) { |
249 | const auto& root_library = |
250 | Library::Handle(LoadTestScript(kSubtypeRangeCheckScript)); |
251 | const auto& class_a = Class::Handle(GetClass(root_library, "A" )); |
252 | const auto& class_base = Class::Handle(GetClass(root_library, "Base" )); |
253 | const auto& class_i = Class::Handle(GetClass(root_library, "I" )); |
254 | const auto& class_i2 = Class::Handle(GetClass(root_library, "I2" )); |
255 | const auto& class_null = Class::Handle(Class::null()); |
256 | |
257 | const auto& obj_i = Object::Handle(Invoke(root_library, "createI" )); |
258 | const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2" )); |
259 | const auto& obj_baseint = |
260 | Object::Handle(Invoke(root_library, "createBaseInt" )); |
261 | const auto& obj_a = Object::Handle(Invoke(root_library, "createA" )); |
262 | const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1" )); |
263 | const auto& obj_a2 = Object::Handle(Invoke(root_library, "createA2" )); |
264 | const auto& obj_b = Object::Handle(Invoke(root_library, "createB" )); |
265 | const auto& obj_b1 = Object::Handle(Invoke(root_library, "createB1" )); |
266 | const auto& obj_b2 = Object::Handle(Invoke(root_library, "createB2" )); |
267 | |
268 | const auto& type_dynamic = Type::Handle(Type::DynamicType()); |
269 | auto& type_object = Type::Handle(Type::ObjectType()); |
270 | type_object = type_object.ToNullability(Nullability::kNullable, Heap::kNew); |
271 | |
272 | const auto& tav_null = TypeArguments::Handle(TypeArguments::null()); |
273 | |
274 | auto& tav_object = TypeArguments::Handle(TypeArguments::New(1)); |
275 | tav_object.SetTypeAt(0, type_object); |
276 | CanonicalizeTAV(&tav_object); |
277 | |
278 | auto& tav_object_dynamic = TypeArguments::Handle(TypeArguments::New(2)); |
279 | tav_object_dynamic.SetTypeAt(0, type_object); |
280 | tav_object_dynamic.SetTypeAt(1, type_dynamic); |
281 | CanonicalizeTAV(&tav_object_dynamic); |
282 | |
283 | auto& tav_dynamic_t = TypeArguments::Handle(TypeArguments::New(2)); |
284 | tav_dynamic_t.SetTypeAt(0, type_dynamic); |
285 | tav_dynamic_t.SetTypeAt( |
286 | 1, TypeParameter::Handle(GetClassTypeParameter(class_base, "T" ))); |
287 | CanonicalizeTAV(&tav_dynamic_t); |
288 | |
289 | // We will generate specialized TTS for instantiated interface types |
290 | // where there are no type arguments or the type arguments are top |
291 | // types. |
292 | // |
293 | // obj as A // Subclass ranges |
294 | // obj as Base<Object> // Subclass ranges with top-type tav |
295 | // obj as I2 // Subtype ranges |
296 | // obj as I<Object, dynamic> // Subtype ranges with top-type tav |
297 | // |
298 | |
299 | // <...> as A |
300 | const auto& type_a = AbstractType::Handle(class_a.RareType()); |
301 | RunTTSTest(obj_i, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
302 | ExpectFailedViaTTS); |
303 | RunTTSTest(obj_i2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
304 | ExpectFailedViaTTS); |
305 | RunTTSTest(obj_baseint, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
306 | ExpectFailedViaTTS); |
307 | RunTTSTest(obj_a, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
308 | ExpectHandledViaTTS); |
309 | RunTTSTest(obj_a1, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
310 | ExpectHandledViaTTS); |
311 | RunTTSTest(obj_a2, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
312 | ExpectHandledViaTTS); |
313 | RunTTSTest(obj_b, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
314 | ExpectFailedViaTTS); |
315 | RunTTSTest(obj_b1, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
316 | ExpectFailedViaTTS); |
317 | RunTTSTest(obj_b2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
318 | ExpectFailedViaTTS); |
319 | |
320 | // <...> as Base<Object> |
321 | auto& type_base = AbstractType::Handle( |
322 | Type::New(class_base, tav_object, TokenPosition::kNoSource)); |
323 | FinalizeAndCanonicalize(class_null, &type_base); |
324 | RunTTSTest(obj_i, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
325 | ExpectFailedViaTTS); |
326 | RunTTSTest(obj_i2, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
327 | ExpectFailedViaTTS); |
328 | RunTTSTest(obj_baseint, type_base, tav_null, tav_null, |
329 | ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
330 | RunTTSTest(obj_a, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
331 | ExpectHandledViaTTS); |
332 | RunTTSTest(obj_a1, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
333 | ExpectHandledViaTTS); |
334 | RunTTSTest(obj_a2, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
335 | ExpectHandledViaTTS); |
336 | RunTTSTest(obj_b, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
337 | ExpectHandledViaTTS); |
338 | RunTTSTest(obj_b1, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
339 | ExpectHandledViaTTS); |
340 | RunTTSTest(obj_b2, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
341 | ExpectHandledViaTTS); |
342 | |
343 | // <...> as I2 |
344 | const auto& type_i2 = AbstractType::Handle(class_i2.RareType()); |
345 | RunTTSTest(obj_i, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
346 | ExpectFailedViaTTS); |
347 | RunTTSTest(obj_i2, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
348 | ExpectHandledViaTTS); |
349 | RunTTSTest(obj_baseint, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
350 | ExpectFailedViaTTS); |
351 | RunTTSTest(obj_a, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
352 | ExpectFailedViaTTS); |
353 | RunTTSTest(obj_a1, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
354 | ExpectHandledViaTTS); |
355 | RunTTSTest(obj_a2, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
356 | ExpectFailedViaTTS); |
357 | RunTTSTest(obj_b, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
358 | ExpectFailedViaTTS); |
359 | RunTTSTest(obj_b1, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS, |
360 | ExpectHandledViaTTS); |
361 | RunTTSTest(obj_b2, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
362 | ExpectFailedViaTTS); |
363 | |
364 | // <...> as I<Object, dynamic> |
365 | auto& type_i_object_dynamic = AbstractType::Handle( |
366 | Type::New(class_i, tav_object_dynamic, TokenPosition::kNoSource)); |
367 | FinalizeAndCanonicalize(class_null, &type_i_object_dynamic); |
368 | RunTTSTest(obj_i, type_i_object_dynamic, tav_null, tav_null, |
369 | ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
370 | RunTTSTest(obj_i2, type_i_object_dynamic, tav_null, tav_null, |
371 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
372 | RunTTSTest(obj_baseint, type_i_object_dynamic, tav_null, tav_null, |
373 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
374 | RunTTSTest(obj_a, type_i_object_dynamic, tav_null, tav_null, |
375 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
376 | RunTTSTest(obj_a1, type_i_object_dynamic, tav_null, tav_null, |
377 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
378 | RunTTSTest(obj_a2, type_i_object_dynamic, tav_null, tav_null, |
379 | ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
380 | RunTTSTest(obj_b, type_i_object_dynamic, tav_null, tav_null, |
381 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
382 | RunTTSTest(obj_b1, type_i_object_dynamic, tav_null, tav_null, |
383 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
384 | RunTTSTest(obj_b2, type_i_object_dynamic, tav_null, tav_null, |
385 | ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
386 | |
387 | // We do not generate TTS for uninstantiated types if we would need to use |
388 | // subtype range checks for the class of the interface type. |
389 | // |
390 | // obj as I<dynamic, T> |
391 | // |
392 | auto& type_dynamic_t = AbstractType::Handle( |
393 | Type::New(class_i, tav_dynamic_t, TokenPosition::kNoSource)); |
394 | FinalizeAndCanonicalize(class_base, &type_dynamic_t); |
395 | RunTTSTest(obj_i, type_dynamic_t, tav_object, tav_null, |
396 | ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
397 | RunTTSTest(obj_i2, type_dynamic_t, tav_object, tav_null, |
398 | ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
399 | RunTTSTest(obj_baseint, type_dynamic_t, tav_object, tav_null, |
400 | ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
401 | RunTTSTest(obj_a, type_dynamic_t, tav_object, tav_null, |
402 | ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
403 | RunTTSTest(obj_a1, type_dynamic_t, tav_object, tav_null, |
404 | ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
405 | RunTTSTest(obj_a2, type_dynamic_t, tav_object, tav_null, |
406 | ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
407 | RunTTSTest(obj_b, type_dynamic_t, tav_object, tav_null, |
408 | ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
409 | RunTTSTest(obj_b1, type_dynamic_t, tav_object, tav_null, |
410 | ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
411 | RunTTSTest(obj_b2, type_dynamic_t, tav_object, tav_null, |
412 | ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
413 | |
414 | // obj as Object (with null safety) |
415 | Isolate* isolate = Isolate::Current(); |
416 | if (isolate->null_safety()) { |
417 | auto& type_non_nullable_object = |
418 | Type::Handle(isolate->object_store()->non_nullable_object_type()); |
419 | RunTTSTest(obj_a, type_non_nullable_object, tav_null, tav_null, |
420 | ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
421 | RunTTSTest(Object::null_object(), type_non_nullable_object, tav_null, |
422 | tav_null, ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
423 | } |
424 | } |
425 | |
426 | ISOLATE_UNIT_TEST_CASE(TTS_GenericSubtypeRangeCheck) { |
427 | const auto& root_library = |
428 | Library::Handle(LoadTestScript(kSubtypeRangeCheckScript)); |
429 | const auto& class_a1 = Class::Handle(GetClass(root_library, "A1" )); |
430 | const auto& class_a2 = Class::Handle(GetClass(root_library, "A2" )); |
431 | const auto& class_base = Class::Handle(GetClass(root_library, "Base" )); |
432 | const auto& class_i = Class::Handle(GetClass(root_library, "I" )); |
433 | const auto& fun_generic = |
434 | Function::Handle(GetFunction(root_library, "genericFun" )); |
435 | const auto& class_null = Class::Handle(Class::null()); |
436 | |
437 | const auto& obj_i = Object::Handle(Invoke(root_library, "createI" )); |
438 | const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2" )); |
439 | const auto& obj_baseint = |
440 | Object::Handle(Invoke(root_library, "createBaseInt" )); |
441 | const auto& obj_a = Object::Handle(Invoke(root_library, "createA" )); |
442 | const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1" )); |
443 | const auto& obj_a2 = Object::Handle(Invoke(root_library, "createA2" )); |
444 | const auto& obj_b = Object::Handle(Invoke(root_library, "createB" )); |
445 | const auto& obj_b1 = Object::Handle(Invoke(root_library, "createB1" )); |
446 | const auto& obj_b2 = Object::Handle(Invoke(root_library, "createB2" )); |
447 | const auto& obj_basea2int = |
448 | Object::Handle(Invoke(root_library, "createBaseA2Int" )); |
449 | const auto& obj_basea2a1 = |
450 | Object::Handle(Invoke(root_library, "createBaseA2A1" )); |
451 | const auto& obj_baseb2int = |
452 | Object::Handle(Invoke(root_library, "createBaseB2Int" )); |
453 | const auto& obj_baseistringdouble = |
454 | Object::Handle(Invoke(root_library, "createBaseIStringDouble" )); |
455 | |
456 | const auto& type_dynamic = Type::Handle(Type::DynamicType()); |
457 | auto& type_int = Type::Handle(Type::IntType()); |
458 | if (!TestCase::IsNNBD()) { |
459 | type_int = type_int.ToNullability(Nullability::kLegacy, Heap::kNew); |
460 | } |
461 | auto& type_string = Type::Handle(Type::StringType()); |
462 | if (!TestCase::IsNNBD()) { |
463 | type_string = type_string.ToNullability(Nullability::kLegacy, Heap::kNew); |
464 | } |
465 | auto& type_object = Type::Handle(Type::ObjectType()); |
466 | type_object = type_object.ToNullability( |
467 | TestCase::IsNNBD() ? Nullability::kNullable : Nullability::kLegacy, |
468 | Heap::kNew); |
469 | auto& type_a1 = Type::Handle(class_a1.DeclarationType()); |
470 | if (!TestCase::IsNNBD()) { |
471 | type_a1 = type_a1.ToNullability(Nullability::kLegacy, Heap::kNew); |
472 | } |
473 | FinalizeAndCanonicalize(class_null, &type_a1); |
474 | |
475 | const auto& tav_null = TypeArguments::Handle(TypeArguments::null()); |
476 | |
477 | auto& tav_object_dynamic = TypeArguments::Handle(TypeArguments::New(2)); |
478 | tav_object_dynamic.SetTypeAt(0, type_object); |
479 | tav_object_dynamic.SetTypeAt(1, type_dynamic); |
480 | CanonicalizeTAV(&tav_object_dynamic); |
481 | |
482 | auto& tav_dynamic_int = TypeArguments::Handle(TypeArguments::New(2)); |
483 | tav_dynamic_int.SetTypeAt(0, type_dynamic); |
484 | tav_dynamic_int.SetTypeAt(1, type_int); |
485 | CanonicalizeTAV(&tav_dynamic_int); |
486 | |
487 | auto& tav_dynamic_string = TypeArguments::Handle(TypeArguments::New(2)); |
488 | tav_dynamic_string.SetTypeAt(0, type_dynamic); |
489 | tav_dynamic_string.SetTypeAt(1, type_string); |
490 | CanonicalizeTAV(&tav_dynamic_string); |
491 | |
492 | auto& tav_int = TypeArguments::Handle(TypeArguments::New(1)); |
493 | tav_int.SetTypeAt(0, type_int); |
494 | CanonicalizeTAV(&tav_int); |
495 | |
496 | auto& type_i_object_dynamic = AbstractType::Handle( |
497 | Type::New(class_i, tav_object_dynamic, TokenPosition::kNoSource)); |
498 | FinalizeAndCanonicalize(class_null, &type_i_object_dynamic); |
499 | const auto& tav_iod = TypeArguments::Handle(TypeArguments::New(1)); |
500 | tav_iod.SetTypeAt(0, type_i_object_dynamic); |
501 | |
502 | // We will generate specialized TTS for instantiated interface types |
503 | // where there are no type arguments or the type arguments are top |
504 | // types. |
505 | // |
506 | // obj as Base<I<Object, dynamic>> // Subclass ranges for Base, subtype |
507 | // // ranges tav arguments. |
508 | // obj as Base<T> // Subclass ranges for Base, type |
509 | // // equality for instantiator type arg T |
510 | // obj as Base<B> // Subclass ranges for Base, type |
511 | // // equality for function type arg B. |
512 | // |
513 | |
514 | // <...> as Base<I<Object, dynamic>> |
515 | auto& type_base_i_object_dynamic = AbstractType::Handle( |
516 | Type::New(class_base, tav_iod, TokenPosition::kNoSource)); |
517 | FinalizeAndCanonicalize(class_null, &type_base_i_object_dynamic); |
518 | RunTTSTest(obj_baseb2int, type_base_i_object_dynamic, tav_null, tav_null, |
519 | ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
520 | RunTTSTest(obj_baseistringdouble, type_base_i_object_dynamic, tav_null, |
521 | tav_null, ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
522 | RunTTSTest(obj_a, type_base_i_object_dynamic, tav_null, tav_null, |
523 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
524 | RunTTSTest(obj_a1, type_base_i_object_dynamic, tav_null, tav_null, |
525 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
526 | RunTTSTest(obj_a2, type_base_i_object_dynamic, tav_null, tav_null, |
527 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
528 | RunTTSTest(obj_b, type_base_i_object_dynamic, tav_null, tav_null, |
529 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
530 | RunTTSTest(obj_b1, type_base_i_object_dynamic, tav_null, tav_null, |
531 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
532 | RunTTSTest(obj_b2, type_base_i_object_dynamic, tav_null, tav_null, |
533 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
534 | |
535 | // <...> as Base<T> with T instantiantiator type parameter (T == int) |
536 | const auto& tav_baset = TypeArguments::Handle(TypeArguments::New(1)); |
537 | tav_baset.SetTypeAt( |
538 | 0, TypeParameter::Handle(GetClassTypeParameter(class_base, "T" ))); |
539 | auto& type_base_t = AbstractType::Handle( |
540 | Type::New(class_base, tav_baset, TokenPosition::kNoSource)); |
541 | FinalizeAndCanonicalize(class_base, &type_base_t); |
542 | RunTTSTest(obj_baseint, type_base_t, tav_int, tav_null, |
543 | ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
544 | RunTTSTest(obj_baseistringdouble, type_base_t, tav_int, tav_null, |
545 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
546 | |
547 | // <...> as Base<B> with B function type parameter |
548 | const auto& tav_baseb = TypeArguments::Handle(TypeArguments::New(1)); |
549 | tav_baseb.SetTypeAt( |
550 | 0, TypeParameter::Handle(GetFunctionTypeParameter(fun_generic, "B" ))); |
551 | auto& type_base_b = AbstractType::Handle( |
552 | Type::New(class_base, tav_baseb, TokenPosition::kNoSource)); |
553 | FinalizeAndCanonicalize(class_null, &type_base_b); |
554 | // With B == int |
555 | RunTTSTest(obj_baseint, type_base_b, tav_null, tav_dynamic_int, |
556 | ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
557 | RunTTSTest(obj_baseistringdouble, type_base_b, tav_null, tav_dynamic_int, |
558 | ExpectLazilyFailedViaTTS, ExpectFailedViaTTS); |
559 | // With B == dynamic (null vector) |
560 | RunTTSTest(obj_baseint, type_base_b, tav_null, tav_null, |
561 | ExpectLazilyHandledViaTTS, ExpectHandledViaTTS); |
562 | RunTTSTest(obj_i2, type_base_b, tav_null, tav_null, ExpectLazilyFailedViaTTS, |
563 | ExpectFailedViaTTS); |
564 | |
565 | // We do not generate TTS for uninstantiated types if we would need to use |
566 | // subtype range checks for the class of the interface type. |
567 | // |
568 | // obj as I<dynamic, String> // I is generic & implemented. |
569 | // obj as Base<A2<T>> // A2<T> is not instantiated. |
570 | // obj as Base<A2<A1>> // A2<A1> is not a rare type. |
571 | // |
572 | |
573 | // <...> as I<dynamic, String> |
574 | RELEASE_ASSERT(class_i.is_implemented()); |
575 | auto& type_i_dynamic_string = Type::Handle( |
576 | Type::New(class_i, tav_dynamic_string, TokenPosition::kNoSource)); |
577 | type_i_dynamic_string = type_i_dynamic_string.ToNullability( |
578 | Nullability::kNonNullable, Heap::kNew); |
579 | FinalizeAndCanonicalize(class_null, &type_i_dynamic_string); |
580 | RunTTSTest(obj_i, type_i_dynamic_string, tav_null, tav_null, |
581 | ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
582 | RunTTSTest(obj_baseint, type_i_dynamic_string, tav_null, tav_null, |
583 | ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
584 | |
585 | // <...> as Base<A2<T>> |
586 | const auto& tav_t = TypeArguments::Handle(TypeArguments::New(1)); |
587 | tav_t.SetTypeAt( |
588 | 0, TypeParameter::Handle(GetClassTypeParameter(class_base, "T" ))); |
589 | auto& type_a2_t = |
590 | Type::Handle(Type::New(class_a2, tav_t, TokenPosition::kNoSource)); |
591 | type_a2_t = type_a2_t.ToNullability(Nullability::kLegacy, Heap::kNew); |
592 | FinalizeAndCanonicalize(class_null, &type_a2_t); |
593 | const auto& tav_a2_t = TypeArguments::Handle(TypeArguments::New(1)); |
594 | tav_a2_t.SetTypeAt(0, type_a2_t); |
595 | auto& type_base_a2_t = |
596 | Type::Handle(Type::New(class_base, tav_a2_t, TokenPosition::kNoSource)); |
597 | type_base_a2_t = |
598 | type_base_a2_t.ToNullability(Nullability::kNonNullable, Heap::kNew); |
599 | FinalizeAndCanonicalize(class_null, &type_base_a2_t); |
600 | RunTTSTest(obj_basea2int, type_base_a2_t, tav_null, tav_null, |
601 | ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
602 | RunTTSTest(obj_baseint, type_base_a2_t, tav_null, tav_null, |
603 | ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
604 | |
605 | // <...> as Base<A2<A1>> |
606 | const auto& tav_a1 = TypeArguments::Handle(TypeArguments::New(1)); |
607 | tav_a1.SetTypeAt(0, type_a1); |
608 | auto& type_a2_a1 = |
609 | Type::Handle(Type::New(class_a2, tav_a1, TokenPosition::kNoSource)); |
610 | type_a2_a1 = type_a2_a1.ToNullability(Nullability::kLegacy, Heap::kNew); |
611 | FinalizeAndCanonicalize(class_null, &type_a2_a1); |
612 | const auto& tav_a2_a1 = TypeArguments::Handle(TypeArguments::New(1)); |
613 | tav_a2_a1.SetTypeAt(0, type_a2_a1); |
614 | auto& type_base_a2_a1 = |
615 | Type::Handle(Type::New(class_base, tav_a2_a1, TokenPosition::kNoSource)); |
616 | type_base_a2_a1 = |
617 | type_base_a2_a1.ToNullability(Nullability::kNonNullable, Heap::kNew); |
618 | FinalizeAndCanonicalize(class_null, &type_base_a2_a1); |
619 | RunTTSTest(obj_basea2a1, type_base_a2_a1, tav_null, tav_null, |
620 | ExpectLazilyHandledViaSTC, ExpectHandledViaSTC); |
621 | RunTTSTest(obj_basea2int, type_base_a2_a1, tav_null, tav_null, |
622 | ExpectLazilyFailedViaSTC, ExpectFailedViaSTC); |
623 | } |
624 | |
625 | ISOLATE_UNIT_TEST_CASE(TTS_Regress40964) { |
626 | const char* kScript = |
627 | R"( |
628 | class A<T> { |
629 | test(x) => x as B<T>; |
630 | } |
631 | class B<T> {} |
632 | class C<T> {} |
633 | |
634 | createACint() => A<C<int>>(); |
635 | createBCint() => B<C<int>>(); |
636 | createBCnum() => B<C<num>>(); |
637 | )" ; |
638 | |
639 | const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
640 | const auto& class_b = Class::Handle(GetClass(root_library, "B" )); |
641 | const auto& class_null = Class::Handle(Class::null()); |
642 | |
643 | const auto& acint = Object::Handle(Invoke(root_library, "createACint" )); |
644 | const auto& bcint = Object::Handle(Invoke(root_library, "createBCint" )); |
645 | const auto& bcnum = Object::Handle(Invoke(root_library, "createBCnum" )); |
646 | |
647 | // dst_type = B<T> |
648 | const auto& dst_tav = TypeArguments::Handle(TypeArguments::New(1)); |
649 | dst_tav.SetTypeAt(0, |
650 | TypeParameter::Handle(GetClassTypeParameter(class_b, "T" ))); |
651 | auto& dst_type = |
652 | Type::Handle(Type::New(class_b, dst_tav, TokenPosition::kNoSource)); |
653 | FinalizeAndCanonicalize(class_null, &dst_type); |
654 | const auto& cint_tav = |
655 | TypeArguments::Handle(Instance::Cast(acint).GetTypeArguments()); |
656 | const auto& function_tav = TypeArguments::Handle(); |
657 | |
658 | // a as B<T> -- a==B<C<int>, T==<C<int>> |
659 | RunTTSTest(bcint, dst_type, cint_tav, function_tav, ExpectLazilyHandledViaTTS, |
660 | ExpectHandledViaTTS); |
661 | |
662 | // a as B<T> -- a==B<C<num>, T==<C<int>> |
663 | RunTTSTest(bcnum, dst_type, cint_tav, function_tav, ExpectLazilyFailedViaTTS, |
664 | ExpectFailedViaTTS); |
665 | } |
666 | |
667 | } // namespace dart |
668 | |
669 | #endif // defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \ |
670 | // defined(TARGET_ARCH_X64) |
671 | |