dart-sdk/tests/dartdevc/hot_restart_late_test.dart
Nicholas Shahan 4c1143a048 [ddc] Reset static fields on first get or set
Fixes a memory leak issue where statics that are set but never
read before a hot restart are never reset and the functions to
set them are never garbage collected.

Update and add more tests for this specific scenario that inspect
the number of fields that will be reset during a hot restart.

Change-Id: Id5a56625279c84a37f53253a5ee667758bc22f87
Issue: https://github.com/dart-lang/sdk/issues/48349
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/232230
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
2022-02-11 01:18:01 +00:00

125 lines
4.9 KiB
Dart

// 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.
// Tests that late fields are properly reset after hot restarts.
// Requirements=nnbd
import 'dart:_runtime' as dart show hotRestart, resetFields;
import 'package:expect/expect.dart';
late String noInitializer;
late int withInitializer = 1;
class Lates {
static late String noInitializer;
static late int withInitializer = 2;
}
class LatesGeneric<T> {
static late String noInitializer;
static late int withInitializer = 3;
}
main() {
// Read this static field first to avoid interference with other reset counts.
var weakNullSafety = hasUnsoundNullSafety;
// TODO(42495) `Expect.throws` contains the use of a const that triggers an
// extra field reset but consts are not correctly reset on hot restart so the
// extra reset only appears once. Perform an initial Expect.throws here to
// avoid confusion with the reset counts later.
Expect.throws(() => throw 'foo');
var resetFieldCount = dart.resetFields.length;
// Set uninitialized static late fields. Avoid calling getters for these
// statics to ensure they are reset even if they are never accessed.
noInitializer = 'set via setter';
Lates.noInitializer = 'Lates set via setter';
LatesGeneric.noInitializer = 'LatesGeneric set via setter';
// Initialized statics should contain their values.
Expect.equals(1, withInitializer);
Expect.equals(2, Lates.withInitializer);
Expect.equals(3, LatesGeneric.withInitializer);
// In weak null safety the late field lowering introduces a second static
// field that tracks if late field has been initialized thus doubling the
// number of expected resets.
//
// In sound null safety non-nullable fields don't require the extra static to
// track initialization because null is used as a sentinel value.
//
// Weak Null Safety - 12 total field resets
// - 3 isSet write/resets for uninitialized field writes.
// - 3 write/resets for the actual uninitialized field writes.
// - 3 isSet reads/resets for initialized field reads.
// - 3 reads/resets for the actual initialized field reads.
//
// Sound Null Safety - 6 total field resets:
// - 3 write/resets for the actual uninitialized field writes.
// - 3 reads/resets for the actual initialized field reads.
var expectedResets =
weakNullSafety ? resetFieldCount + 12 : resetFieldCount + 6;
Expect.equals(expectedResets, dart.resetFields.length);
dart.hotRestart();
resetFieldCount = dart.resetFields.length;
// Late statics should throw on get when not initialized.
Expect.throws(() => noInitializer);
Expect.throws(() => Lates.noInitializer);
Expect.throws(() => LatesGeneric.noInitializer);
// Set uninitialized static late fields again.
noInitializer = 'set via setter';
Lates.noInitializer = 'Lates set via setter';
LatesGeneric.noInitializer = 'LatesGeneric set via setter';
// All statics should contain their set values.
Expect.equals('set via setter', noInitializer);
Expect.equals('Lates set via setter', Lates.noInitializer);
Expect.equals('LatesGeneric set via setter', LatesGeneric.noInitializer);
Expect.equals(1, withInitializer);
Expect.equals(2, Lates.withInitializer);
Expect.equals(3, LatesGeneric.withInitializer);
// Weak Null Safety - 12 total field resets
// - 3 isSet write/resets for uninitialized field writes.
// - 3 write/resets for the actual uninitialized field writes.
// - 3 isSet reads/resets for initialized field reads.
// - 3 reads/resets for the actual initialized field reads.
// Sound Null Safety - 6 total field resets:
// - 3 write/resets for the actual uninitialized field writes.
// - 3 reads/resets for the actual initialized field reads.
expectedResets = weakNullSafety ? resetFieldCount + 12 : resetFieldCount + 6;
Expect.equals(expectedResets, dart.resetFields.length);
dart.hotRestart();
dart.hotRestart();
resetFieldCount = dart.resetFields.length;
// Late statics should throw on get when not initialized.
Expect.throws(() => noInitializer);
Expect.throws(() => Lates.noInitializer);
Expect.throws(() => LatesGeneric.noInitializer);
// Initialized statics should contain their values.
Expect.equals(1, withInitializer);
Expect.equals(2, Lates.withInitializer);
Expect.equals(3, LatesGeneric.withInitializer);
// Weak Null Safety - 9 total field resets:
// - 3 isSet reads/resets for uninitialized field reads.
// - 3 isSet reads/resets for initialized field reads.
// - 3 reads/resets for the actual initialized field reads.
//
// Sound Null Safety - 6 total field resets:
// - 3 reads/resets for actual uninitialized field reads.
// - 3 reads/resets for the actual initialized field reads.
expectedResets = weakNullSafety ? resetFieldCount + 9 : resetFieldCount + 6;
Expect.equals(expectedResets, dart.resetFields.length);
}