[ddc] Apply late semantics to statics with initializers

Ensures that static finals with recursive initializers will throw
a `LateInitializationError` after trying to assign the value twice,
even if the value is the same.

Change-Id: Iae85a4fab93f51161a9842a684b0efd2aeba9dbc
Fixes: https://github.com/dart-lang/sdk/issues/43358
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/162280
Reviewed-by: Mark Zhou <markzipan@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart
index 6b33186..13ab061 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart
@@ -43,6 +43,10 @@
   }
 }
 
+throwLateInitializationError(String name) {
+  throw internal.LateInitializationErrorImpl(name);
+}
+
 throwCyclicInitializationError([String? field]) {
   throw CyclicInitializationError(field);
 }
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
index 489795f..d931293 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart
@@ -785,22 +785,38 @@
 // performance in other projects (e.g. webcomponents.js ShadowDOM polyfill).
 defineLazyField(to, name, desc) => JS('', '''(() => {
   const initializer = $desc.get;
+  const final = $desc.set == null;
+  // Tracks if the initializer has been called.
+  let initialized = false;
   let init = initializer;
   let value = null;
-  let executed = false;
+  // Tracks if these local variables have been saved so they can be restored
+  // after a hot restart.
+  let savedLocals = false;
   $desc.get = function() {
     if (init == null) return value;
-    if (!executed) {
+    if (final && initialized) $throwLateInitializationError($name);
+    if (!savedLocals) {
       // Record the field on first execution so we can reset it later if
       // needed (hot restart).
       $_resetFields.push(() => {
         init = initializer;
         value = null;
-        executed = false;
+        savedLocals = false;
+        initialized = false;
       });
-      executed = true;
+      savedLocals = true;
     }
-    value = init();
+    // Must set before calling init in case it is recursive.
+    initialized = true;
+    try {
+      value = init();
+    } catch (e) {
+      // Reset to false so the initializer can be executed again if the
+      // exception was caught.
+      initialized = false;
+      throw e;
+    }
     init = null;
     return value;
   };
@@ -809,7 +825,7 @@
     $desc.set = function(x) {
       init = null;
       value = x;
-      // executed is dead since init is set to null
+      // savedLocals and initialized are dead since init is set to null
     };
   }
   return ${defineProperty(to, name, desc)};
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart
index f07c4d8..cb832e0 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart
@@ -11,7 +11,7 @@
 import 'dart:_debugger' show stackTraceMapper, trackCall;
 import 'dart:_foreign_helper' show JS, JSExportName, rest, spread;
 import 'dart:_interceptors' show JSArray, jsNull, JSFunction, NativeError;
-import 'dart:_internal' as internal show Symbol;
+import 'dart:_internal' as internal show LateInitializationErrorImpl, Symbol;
 import 'dart:_js_helper'
     show
         AssertionErrorImpl,