blob: 8f8d502df294697ba87e20f60186191d418a9a00 [file]
// Copyright (c) 2026, 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.
// Regression test for issue 63809: dart2wasm compiler replaced constructor
// invocations, static methods, constructor bodies, and closures of cyclic types
// with unreachable, discarding throwing initializers and breaking static calls or
// constructor closures.
import "package:expect/expect.dart";
// Case 1: Throwing Constructor Initializer
abstract class Api {
Future<String> act(String id);
}
abstract class Client {
factory Client() => throw UnsupportedError('Client not supported');
Future<String> get(String url);
}
class CycleClient implements Client {
final Client _inner;
CycleClient({Client? inner}) : _inner = inner ?? Client();
@override
@pragma('wasm:entry-point')
Future<String> get(String url) => _inner.get(url);
}
class ConcreteApi implements Api {
final Client _client;
ConcreteApi(this._client);
@override
@pragma('wasm:entry-point')
Future<String> act(String id) async {
final response = await _client.get('https://example.com/$id');
return response;
}
}
// Case 2: Static method on cyclic class
class CyclicWithStatic {
final CyclicWithStatic next;
CyclicWithStatic(this.next);
@pragma('wasm:entry-point')
CyclicWithStatic getNext() => next;
static String foo() => 'foo';
}
class CyclicWithClosures {
final CyclicWithClosures next;
final int value;
CyclicWithClosures._dummy(this.next, this.value);
CyclicWithClosures(int val)
: value = val,
next = (() {
if (val >= 3) {
throw StateError('Max cycle depth reached');
}
var closureInInit = () => val + 1;
return CyclicWithClosures(closureInInit());
})() {
var closureInBody = () => value * 2;
Expect.equals(val * 2, closureInBody());
}
}
@pragma('wasm:never-inline')
int evalClosure(int Function() f) => f();
// Owl Finding 1: Closures in constructor initializer lists of cyclic classes
class CyclicWithInitClosure implements Client {
final Client _inner;
final int value;
CyclicWithInitClosure(int v)
: value = evalClosure(() {
if (v < 0) throw ArgumentError('negative');
return v * 2;
}),
_inner = Client();
@override
@pragma('wasm:entry-point')
Future<String> get(String url) => _inner.get(url);
}
// Owl Finding 2: Uninitialized locals of ExtensionType / TypeParameter wrapping cyclic classes
class CyclicBaseClass {
final CyclicBaseClass next;
CyclicBaseClass(this.next);
}
extension type ExtCyclic(CyclicBaseClass c) {}
@pragma('wasm:never-inline')
void testExtCyclicLocal(bool cond, ExtCyclic? val) {
ExtCyclic x;
if (cond) {
x = val!;
Expect.isNotNull(x);
}
}
@pragma('wasm:never-inline')
void testTypeParamCyclicLocal<T extends CyclicBaseClass>(bool cond, T? val) {
T t;
if (cond) {
t = val!;
Expect.isNotNull(t);
}
}
// Owl Finding 3: Super initializer chaining on cyclic subclasses
class NonCyclicSuper {
final String label;
NonCyclicSuper(int x)
: label = (() {
if (x < 0) throw RangeError('negative super arg');
return 'val: $x';
})();
}
class CyclicSub extends NonCyclicSuper {
final CyclicSub next;
CyclicSub._dummy(this.next, int x) : super(x);
CyclicSub(int x)
: next = (() {
if (x < 0) throw RangeError('negative cyclic sub arg');
return CyclicSub(x + 1);
})(),
super(x);
}
int get dynamicNegativeOne => int.parse('-1');
int get dynamicOne => int.parse('1');
void testConstructorThrow() {
Expect.throws(() {
ConcreteApi(CycleClient());
}, (e) => e is UnsupportedError);
}
void testStaticMethods() {
Expect.equals('foo', CyclicWithStatic.foo());
}
void testConstructorClosures() {
// We use dynamic inputs instead of constants to prevent TFA from completely replacing with unreachable
Expect.throws(() {
CyclicWithClosures(dynamicOne);
}, (e) => e is StateError);
}
void testRealInitializerClosure() {
Expect.throws(() {
CyclicWithInitClosure(dynamicNegativeOne);
}, (e) => e is ArgumentError);
}
void testCyclicSubSuperInit() {
Expect.throws(() => CyclicSub(dynamicNegativeOne), (e) => e is RangeError);
}
void main() {
testConstructorThrow();
testStaticMethods();
testConstructorClosures();
testRealInitializerClosure();
testExtCyclicLocal(false, null);
testTypeParamCyclicLocal(false, null);
testCyclicSubSuperInit();
}