From b5232ac632e26ffc4b4ca3d20afd51d337dbb39b Mon Sep 17 00:00:00 2001 From: MarkZ Date: Tue, 14 Jan 2025 11:06:10 -0800 Subject: [PATCH] [ddc] Resolving link-time class members via the embedder. Note: this may lead to memory leaks if all class declarations are persisted across hot reloads. Fixes #59628 Change-Id: Iae82d6166602d6e4a005748e64e84b3bbbc81894 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403389 Reviewed-by: Nicholas Shahan Commit-Queue: Mark Zhou --- .../lib/src/kernel/compiler_new.dart | 101 ++++++++++++------ .../lib/src/kernel/kernel_helpers.dart | 36 +++++++ 2 files changed, 105 insertions(+), 32 deletions(-) diff --git a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart index 8b7838a8fc52..24018c8f90ea 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart @@ -1373,10 +1373,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor if (nonExternalProperties.isNotEmpty) { // Note that this class has no heritage. This class should never be used // as a type. It's merely a placeholder for static members. - var body = [ - _emitClassStatement(c, className, null, nonExternalProperties) - .toStatement() - ]; + var body = _emitClassStatement(c, className, null, nonExternalProperties); var typeFormals = c.typeParameters; if (typeFormals.isNotEmpty) { var genericClassStmts = @@ -1398,19 +1395,40 @@ class LibraryCompiler extends ComputeOnceConstantVisitor ]; } - js_ast.Statement _emitClassStatement(Class c, js_ast.Expression className, - js_ast.Expression? heritage, List properties) { - var classIdentifier = _emitScopedId(getLocalClassName(c)); + List _emitClassStatement( + Class c, + js_ast.Expression className, + js_ast.Expression? heritage, + List properties) { + var localClassName = getLocalClassName(c); + var classIdentifier = _emitScopedId(localClassName); + var classDefIdentifier = _emitScopedId('_$localClassName'); + // In order to ensure that 'super' binds to the correct target across hot + // reloads, we rebind its pre-assigned class definition's prototype during + // link phase (alongside the embedder-resolved class definition's). + var referencesSuperKeyword = hasSuper(c); if (_options.emitDebugSymbols) classIdentifiers[c] = classIdentifier; if (heritage != null) { + if (referencesSuperKeyword) { + _classExtendsLinks.add(_runtimeStatement( + 'classExtends(#, #)', [classDefIdentifier, heritage])); + } _classExtendsLinks .add(_runtimeStatement('classExtends(#, #)', [className, heritage])); } var classExpr = js_ast.ClassExpression(classIdentifier, null, properties); var libraryExpr = (className as js_ast.PropertyAccess).receiver; var propertyExpr = className.selector; - return _runtimeStatement( - 'declareClass(#, #, #)', [libraryExpr, propertyExpr, classExpr]); + return referencesSuperKeyword + ? [ + js.statement('let # = #;', [classDefIdentifier, classExpr]), + _runtimeStatement('declareClass(#, #, #)', + [libraryExpr, propertyExpr, classDefIdentifier]) + ] + : [ + _runtimeStatement( + 'declareClass(#, #, #)', [libraryExpr, propertyExpr, classExpr]) + ]; } /// Like [_emitClassStatement] but emits a Dart 2.1 mixin represented by @@ -1449,7 +1467,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor var staticProperties = properties.where((m) => m.isStatic).toList(); var instanceProperties = properties.where((m) => !m.isStatic).toList(); - body.add(_emitClassStatement(c, className, heritage, staticProperties)); + body.addAll(_emitClassStatement(c, className, heritage, staticProperties)); var superclassId = _emitScopedId(getLocalClassName(c.superclass!)); var classId = className is js_ast.Identifier ? className @@ -1484,14 +1502,14 @@ class LibraryCompiler extends ComputeOnceConstantVisitor void _defineClass(Class c, js_ast.Expression className, List properties, List body) { if (c == _coreTypes.objectClass) { - body.add(_emitClassStatement(c, className, null, properties)); + body.addAll(_emitClassStatement(c, className, null, properties)); return; } - js_ast.Expression emitClassRef(InterfaceType t) { - // TODO(jmesserly): investigate this. It seems like `lazyJSType` is - // invalid for use in an `extends` clause, hence this workaround. - return _emitJSInterop(t.classNode) ?? _emitClassRef(t); + js_ast.Expression emitClassRef(InterfaceType t, + {bool resolvedFromEmbedder = false}) { + return _emitJSInterop(t.classNode) ?? + _emitClassRef(t, resolvedFromEmbedder: resolvedFromEmbedder); } // Find the real (user declared) superclass and the list of mixins. @@ -1563,6 +1581,10 @@ class LibraryCompiler extends ComputeOnceConstantVisitor // Unroll mixins. var baseClass = emitClassRef(supertype); + // The SDK is never hot reloaded, so we can avoid the overhead of + // resolving their classes through the embedder. + var embedderResolvedBaseClass = + emitClassRef(supertype, resolvedFromEmbedder: !_isBuildingSdk); // TODO(jmesserly): we need to unroll kernel mixins because the synthetic // classes lack required synthetic members, such as constructors. @@ -1622,19 +1644,25 @@ class LibraryCompiler extends ComputeOnceConstantVisitor js_ast.ClassExpression( _emitScopedId(mixinName), null, forwardingMethodStubs) ])); - _classExtendsLinks - .add(_runtimeStatement('classExtends(#, #)', [mixinId, baseClass])); + _classExtendsLinks.add(_runtimeStatement( + 'classExtends(#, #)', [mixinId, embedderResolvedBaseClass])); emitMixinConstructors(mixinId, superclass, mixinClass, mixinType); hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(mixinClass); - _mixinApplicationLinks.add(_runtimeStatement( - 'applyMixin(#, #)', [mixinId, emitClassRef(mixinType)])); + // The SDK is never hot reloaded, so we can avoid the overhead of + // resolving their classes through the embedder. + _mixinApplicationLinks.add(_runtimeStatement('applyMixin(#, #)', [ + mixinId, + emitClassRef(mixinType, resolvedFromEmbedder: !_isBuildingSdk) + ])); baseClass = mixinId; + embedderResolvedBaseClass = mixinId; } if (c.isMixinDeclaration && !c.isMixinClass) { _emitMixinStatement(c, className, baseClass, properties, body); } else { - body.add(_emitClassStatement(c, className, baseClass, properties)); + body.addAll(_emitClassStatement( + c, className, embedderResolvedBaseClass, properties)); } _classEmittingExtends = savedTopLevelClass; @@ -3368,16 +3396,27 @@ class LibraryCompiler extends ComputeOnceConstantVisitor /// Emit the top-level name associated with [n], which should not be an /// external interop member. js_ast.PropertyAccess _emitTopLevelNameNoExternalInterop(NamedNode n, - {String suffix = ''}) { + {String suffix = '', bool resolvedFromEmbedder = false}) { // Some native tests use top-level native methods. var isTopLevelNative = n is Member && isNative(n); return js_ast.PropertyAccess( isTopLevelNative ? _runtimeCall('global.self') - : _emitLibraryName(getLibrary(n)), + : (resolvedFromEmbedder + ? _emitEmbedderResolvedLibrary(getLibrary(n)) + : _emitLibraryName(getLibrary(n))), _emitTopLevelMemberName(n, suffix: suffix)); } + /// Emits [library] fully resolved via the Dart Dev Embedder. + /// + /// Used when the 'current' hot reload's generation of a library needs to be + /// resolved. + js_ast.Expression _emitEmbedderResolvedLibrary(Library library) { + var libraryName = js.string('${library.importUri}'); + return js.call('dartDevEmbedder.importLibrary(#)', [libraryName]); + } + /// Emits the member name portion of a top-level member. /// /// NOTE: usually you should use [_emitTopLevelName] instead of this. This @@ -3817,21 +3856,19 @@ class LibraryCompiler extends ComputeOnceConstantVisitor /// Note that for `package:js` types, this will emit the class we emitted /// using `_emitJSInteropClassNonExternalMembers`, and not the runtime type /// that we synthesize for `package:js` types. - js_ast.Expression _emitClassRef(InterfaceType type) { - if (!_emittingClassExtends && type.typeArguments.isNotEmpty) { - var genericName = _emitTopLevelNameNoExternalInterop(type.classNode); - return js.call('#', [genericName]); - } - return _emitTopLevelNameNoExternalInterop(type.classNode); - } + /// + /// [resolvedFromEmbedder] looks up [type] via the embedder, which retrieves + /// the correct library in the context of hot reload. This should not be set + /// for external classes. + js_ast.Expression _emitClassRef(InterfaceType type, + {bool resolvedFromEmbedder = false}) => + _emitTopLevelNameNoExternalInterop(type.classNode, + resolvedFromEmbedder: resolvedFromEmbedder); Never _typeCompilationError(DartType type, String description) => throw UnsupportedError('$description Encountered while compiling ' '${_currentLibrary!.fileUri}, which contains the type: $type.'); - bool get _emittingClassExtends => - _currentClass != null && identical(_currentClass, _classEmittingExtends); - /// Emits an expression that lets you access statics on a [type] from code. js_ast.Expression _emitConstructorName(InterfaceType type, Member c) { var isSyntheticDefault = diff --git a/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart b/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart index f84935265667..9903e177d968 100644 --- a/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart +++ b/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart @@ -302,6 +302,42 @@ class LabelContinueFinder extends RecursiveVisitor { found = true; } +/// Returns true if a class contains any 'super' calls. +bool hasSuper(Class node) { + var visitor = SuperFinder(); + node.accept(visitor); + return visitor.found; +} + +class SuperFinder extends RecursiveVisitor { + var found = false; + + void visit(Statement? s) { + if (!found && s != null) s.accept(this); + } + + @override + void visitAbstractSuperPropertyGet(AbstractSuperPropertyGet node) => + found = true; + + @override + void visitAbstractSuperPropertySet(AbstractSuperPropertySet node) => + found = true; + + @override + void visitSuperPropertyGet(SuperPropertyGet node) => found = true; + + @override + void visitSuperPropertySet(SuperPropertySet node) => found = true; + + @override + void visitAbstractSuperMethodInvocation(AbstractSuperMethodInvocation node) => + found = true; + + @override + void visitSuperMethodInvocation(SuperMethodInvocation node) => found = true; +} + /// Returns `true` if any of [n]s children are a [FunctionExpression] node. bool containsFunctionExpression(Node n) { var visitor = _FunctionExpressionFinder.instance;