|  | // Copyright (c) 2024, 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. | 
|  |  | 
|  | // Test for @pragma('dyn-module:extendable') and @pragma('dyn-module:can-be-overridden'). | 
|  |  | 
|  | import 'package:vm/testing/il_matchers.dart'; | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | void myprint(Object message) { | 
|  | print(message); | 
|  | } | 
|  |  | 
|  | abstract class A1 { | 
|  | void foo(); | 
|  | void bar(); | 
|  | void baz(); | 
|  | } | 
|  |  | 
|  | class B1 extends A1 { | 
|  | void foo() { | 
|  | myprint('B1.foo'); | 
|  | } | 
|  |  | 
|  | void bar() { | 
|  | myprint('B1.bar'); | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | void baz() { | 
|  | myprint('B1.baz'); | 
|  | } | 
|  | } | 
|  |  | 
|  | class C1 extends B1 { | 
|  | @pragma('vm:never-inline') | 
|  | void baz() { | 
|  | myprint('C1.baz'); | 
|  | } | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('vm:testing:print-flow-graph') | 
|  | void callA1(A1 obj) { | 
|  | obj.foo(); | 
|  | obj.bar(); | 
|  | obj.baz(); | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('vm:testing:print-flow-graph') | 
|  | void testIsA1(obj) { | 
|  | myprint(obj is A1); | 
|  | myprint(obj is B1); | 
|  | myprint(obj is C1); | 
|  | } | 
|  |  | 
|  | @pragma('dyn-module:implicitly-extendable') | 
|  | abstract class A2 { | 
|  | void foo(); | 
|  | @pragma('dyn-module:can-be-overridden-implicitly') | 
|  | void bar(); | 
|  | void baz(); | 
|  | } | 
|  |  | 
|  | @pragma('dyn-module:extendable') | 
|  | class B2 extends A2 { | 
|  | void foo() { | 
|  | myprint('B2.foo'); | 
|  | } | 
|  |  | 
|  | @pragma('dyn-module:can-be-overridden') | 
|  | void bar() { | 
|  | myprint('B2.bar'); | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | void baz() { | 
|  | myprint('B2.baz'); | 
|  | } | 
|  | } | 
|  |  | 
|  | class C2 extends B2 { | 
|  | @pragma('vm:never-inline') | 
|  | void baz() { | 
|  | myprint('C2.baz'); | 
|  | } | 
|  | } | 
|  |  | 
|  | class D2 extends B2 {} | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('vm:testing:print-flow-graph') | 
|  | void callA2(A2 obj) { | 
|  | obj.foo(); | 
|  | obj.bar(); | 
|  | obj.baz(); | 
|  | } | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('vm:testing:print-flow-graph') | 
|  | void testIsA2(obj) { | 
|  | myprint(obj is A2); | 
|  | myprint(obj is B2); | 
|  | myprint(obj is C2); | 
|  | } | 
|  |  | 
|  | List objs = [Object(), B1(), C1(), B2(), C2()]; | 
|  |  | 
|  | main() { | 
|  | for (final obj in objs) { | 
|  | testIsA1(obj); | 
|  | testIsA2(obj); | 
|  | if (obj is A1) { | 
|  | callA1(obj); | 
|  | } | 
|  | if (obj is A2) { | 
|  | callA2(obj); | 
|  | } | 
|  | } | 
|  | testCallInTryWithControlFlow(getMyString(), int.parse('1')); | 
|  | } | 
|  |  | 
|  | void matchIL$callA1(FlowGraph graph) { | 
|  | graph.dump(); | 
|  | graph.match([ | 
|  | match.block('Graph', []), | 
|  | match.block('Function', [ | 
|  | 'obj' << match.Parameter(index: 0), | 
|  | match.CheckStackOverflow(), | 
|  | match.MoveArgument(match.any), | 
|  | match.StaticCall(match.any), | 
|  | match.MoveArgument(match.any), | 
|  | match.StaticCall(match.any), | 
|  | 'cid' << match.LoadClassId('obj'), | 
|  | match.MoveArgument('obj'), | 
|  | match.DispatchTableCall('cid'), | 
|  | match.DartReturn(match.any), | 
|  | ]), | 
|  | ]); | 
|  | } | 
|  |  | 
|  | void matchIL$callA2(FlowGraph graph) { | 
|  | graph.dump(); | 
|  | graph.match([ | 
|  | match.block('Graph', []), | 
|  | match.block('Function', [ | 
|  | 'obj' << match.Parameter(index: 0), | 
|  | match.CheckStackOverflow(), | 
|  | match.MoveArgument(match.any), | 
|  | match.StaticCall(match.any), | 
|  | 'cid1' << match.LoadClassId('obj'), | 
|  | match.Branch(match.TestRange('cid1'), ifTrue: 'B7', ifFalse: 'B8'), | 
|  | ]), | 
|  | 'B7' << | 
|  | match.block('Target', [ | 
|  | match.MoveArgument('obj'), | 
|  | match.DispatchTableCall('cid1'), | 
|  | match.Goto('B9'), | 
|  | ]), | 
|  | 'B8' << | 
|  | match.block('Target', [ | 
|  | match.MoveArgument('obj'), | 
|  | match.InstanceCall('obj'), | 
|  | match.Goto('B9'), | 
|  | ]), | 
|  | 'B9' << | 
|  | match.block('Join', [ | 
|  | 'cid2' << match.LoadClassId('obj'), | 
|  | match.Branch(match.TestRange('cid2'), ifTrue: 'B10', ifFalse: 'B11'), | 
|  | ]), | 
|  | 'B10' << | 
|  | match.block('Target', [ | 
|  | match.MoveArgument('obj'), | 
|  | match.DispatchTableCall('cid2'), | 
|  | match.Goto('B12'), | 
|  | ]), | 
|  | 'B11' << | 
|  | match.block('Target', [ | 
|  | match.MoveArgument('obj'), | 
|  | match.InstanceCall('obj'), | 
|  | match.Goto('B12'), | 
|  | ]), | 
|  | 'B12' << match.block('Join', [match.DartReturn(match.any)]), | 
|  | ]); | 
|  | } | 
|  |  | 
|  | void matchIL$testIsA1(FlowGraph graph) { | 
|  | graph.dump(); | 
|  | graph.match([ | 
|  | match.block('Graph', []), | 
|  | match.block('Function', [ | 
|  | 'obj' << match.Parameter(index: 0), | 
|  | match.CheckStackOverflow(), | 
|  | 'cid' << match.LoadClassId('obj'), | 
|  | 'test1' << match.TestRange('cid'), | 
|  | match.MoveArgument('test1'), | 
|  | match.StaticCall('test1'), | 
|  | match.MoveArgument('test1'), | 
|  | match.StaticCall('test1'), | 
|  | 'test2' << match.EqualityCompare('cid', match.any, kind: '=='), | 
|  | match.MoveArgument('test2'), | 
|  | match.StaticCall('test2'), | 
|  | match.DartReturn(match.any), | 
|  | ]), | 
|  | ]); | 
|  | } | 
|  |  | 
|  | void matchIL$testIsA2(FlowGraph graph) { | 
|  | graph.dump(); | 
|  | graph.match([ | 
|  | match.block('Graph', []), | 
|  | match.block('Function', [ | 
|  | 'obj' << match.Parameter(index: 0), | 
|  | match.CheckStackOverflow(), | 
|  | 'test1' << match.InstanceOf('obj', match.any, match.any), | 
|  | match.MoveArgument('test1'), | 
|  | match.StaticCall('test1'), | 
|  | 'test2' << match.InstanceOf('obj', match.any, match.any), | 
|  | match.MoveArgument('test2'), | 
|  | match.StaticCall('test2'), | 
|  | 'cid' << match.LoadClassId('obj'), | 
|  | 'test3' << match.EqualityCompare('cid', match.any, kind: '=='), | 
|  | match.MoveArgument('test3'), | 
|  | match.StaticCall('test3'), | 
|  | match.DartReturn(match.any), | 
|  | ]), | 
|  | ]); | 
|  | } | 
|  |  | 
|  | @pragma('dyn-module:extendable') | 
|  | class MyString { | 
|  | @pragma('dyn-module:can-be-overridden') | 
|  | @pragma('vm:never-inline') | 
|  | int get length => int.parse('2'); | 
|  |  | 
|  | @pragma('dyn-module:can-be-overridden') | 
|  | @pragma('vm:never-inline') | 
|  | String substring(int start) => 42.toString(); | 
|  | } | 
|  |  | 
|  | MyString getMyString() => [MyString()][int.parse('0')]; | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | int intParse(String str) => int.parse(str); | 
|  |  | 
|  | @pragma('vm:never-inline') | 
|  | @pragma('vm:testing:print-flow-graph') | 
|  | void testCallInTryWithControlFlow(MyString value, int pos) { | 
|  | if (pos == value.length) { | 
|  | } else { | 
|  | try { | 
|  | intParse(value.substring(pos)); | 
|  | } catch (e) {} | 
|  | } | 
|  | } | 
|  |  | 
|  | void matchIL$testCallInTryWithControlFlow(FlowGraph graph) { | 
|  | graph.dump(); | 
|  | graph.match([ | 
|  | match.block('Graph', []), | 
|  | match.block('Function', [ | 
|  | 'value' << match.Parameter(index: 0), | 
|  | 'pos' << match.Parameter(index: 1), | 
|  | match.CheckStackOverflow(), | 
|  | 'cid1' << match.LoadClassId('value'), | 
|  | match.Branch(match.TestRange('cid1'), ifTrue: 'B10', ifFalse: 'B11'), | 
|  | ]), | 
|  | 'B10' << | 
|  | match.block('Target', [ | 
|  | match.MoveArgument('value'), | 
|  | 'value_length1' << match.DispatchTableCall('cid1'), | 
|  | match.Goto('B12'), | 
|  | ]), | 
|  | 'B11' << | 
|  | match.block('Target', [ | 
|  | match.MoveArgument('value'), | 
|  | 'value_length2' << match.InstanceCall('value'), | 
|  | match.Goto('B12'), | 
|  | ]), | 
|  | 'B12' << | 
|  | match.block('Join', [ | 
|  | 'value_length' << match.Phi('value_length1', 'value_length2'), | 
|  | 'value_length_unboxed' << match.UnboxInt64('value_length'), | 
|  | match.Branch( | 
|  | match.EqualityCompare('pos', 'value_length_unboxed', kind: '=='), | 
|  | ifTrue: 'B3', | 
|  | ifFalse: 'B4', | 
|  | ), | 
|  | ]), | 
|  | 'B3' << match.block('Target', [match.Goto('B9')]), | 
|  | 'B4' << match.block('Target', [match.Goto('B5')]), | 
|  | 'B5' << match.tryBlock(tryBody: 'B6', catches: 'B8'), | 
|  | 'B6' << | 
|  | match.block('Join', [ | 
|  | 'pos_boxed' << match.BoxInt64('pos'), | 
|  | 'cid2' << match.LoadClassId('value'), | 
|  | match.Branch(match.TestRange('cid2'), ifTrue: 'B13', ifFalse: 'B14'), | 
|  | ]), | 
|  | 'B13' << | 
|  | match.block('Target', [ | 
|  | match.MoveArgument('value'), | 
|  | match.MoveArgument('pos_boxed'), | 
|  | 'value_substring1' << match.DispatchTableCall('cid2'), | 
|  | match.Goto('B15'), | 
|  | ]), | 
|  | 'B14' << | 
|  | match.block('Target', [ | 
|  | match.MoveArgument('value'), | 
|  | match.MoveArgument('pos_boxed'), | 
|  | 'value_substring2' << match.InstanceCall('value', 'pos_boxed'), | 
|  | match.Goto('B15'), | 
|  | ]), | 
|  | 'B15' << | 
|  | match.block('Join', [ | 
|  | 'value_substring' << | 
|  | match.Phi('value_substring1', 'value_substring2'), | 
|  | match.MoveArgument('value_substring'), | 
|  | match.StaticCall('value_substring'), | 
|  | match.Goto('B7'), | 
|  | ]), | 
|  | 'B8' << match.block('CatchBlock', [match.Goto('B7')]), | 
|  | 'B7' << match.block('Join', [match.Goto('B9')]), | 
|  | 'B9' << match.block('Join', [match.DartReturn(match.any)]), | 
|  | ]); | 
|  | } |