Skip to content

Commit b5232ac

Browse files
MarkzipanCommit Queue
authored andcommitted
[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 <[email protected]> Commit-Queue: Mark Zhou <[email protected]>
1 parent 330cc91 commit b5232ac

File tree

2 files changed

+105
-32
lines changed

2 files changed

+105
-32
lines changed

pkg/dev_compiler/lib/src/kernel/compiler_new.dart

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,10 +1373,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
13731373
if (nonExternalProperties.isNotEmpty) {
13741374
// Note that this class has no heritage. This class should never be used
13751375
// as a type. It's merely a placeholder for static members.
1376-
var body = <js_ast.Statement>[
1377-
_emitClassStatement(c, className, null, nonExternalProperties)
1378-
.toStatement()
1379-
];
1376+
var body = _emitClassStatement(c, className, null, nonExternalProperties);
13801377
var typeFormals = c.typeParameters;
13811378
if (typeFormals.isNotEmpty) {
13821379
var genericClassStmts =
@@ -1398,19 +1395,40 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
13981395
];
13991396
}
14001397

1401-
js_ast.Statement _emitClassStatement(Class c, js_ast.Expression className,
1402-
js_ast.Expression? heritage, List<js_ast.Property> properties) {
1403-
var classIdentifier = _emitScopedId(getLocalClassName(c));
1398+
List<js_ast.Statement> _emitClassStatement(
1399+
Class c,
1400+
js_ast.Expression className,
1401+
js_ast.Expression? heritage,
1402+
List<js_ast.Property> properties) {
1403+
var localClassName = getLocalClassName(c);
1404+
var classIdentifier = _emitScopedId(localClassName);
1405+
var classDefIdentifier = _emitScopedId('_$localClassName');
1406+
// In order to ensure that 'super' binds to the correct target across hot
1407+
// reloads, we rebind its pre-assigned class definition's prototype during
1408+
// link phase (alongside the embedder-resolved class definition's).
1409+
var referencesSuperKeyword = hasSuper(c);
14041410
if (_options.emitDebugSymbols) classIdentifiers[c] = classIdentifier;
14051411
if (heritage != null) {
1412+
if (referencesSuperKeyword) {
1413+
_classExtendsLinks.add(_runtimeStatement(
1414+
'classExtends(#, #)', [classDefIdentifier, heritage]));
1415+
}
14061416
_classExtendsLinks
14071417
.add(_runtimeStatement('classExtends(#, #)', [className, heritage]));
14081418
}
14091419
var classExpr = js_ast.ClassExpression(classIdentifier, null, properties);
14101420
var libraryExpr = (className as js_ast.PropertyAccess).receiver;
14111421
var propertyExpr = className.selector;
1412-
return _runtimeStatement(
1413-
'declareClass(#, #, #)', [libraryExpr, propertyExpr, classExpr]);
1422+
return referencesSuperKeyword
1423+
? [
1424+
js.statement('let # = #;', [classDefIdentifier, classExpr]),
1425+
_runtimeStatement('declareClass(#, #, #)',
1426+
[libraryExpr, propertyExpr, classDefIdentifier])
1427+
]
1428+
: [
1429+
_runtimeStatement(
1430+
'declareClass(#, #, #)', [libraryExpr, propertyExpr, classExpr])
1431+
];
14141432
}
14151433

14161434
/// Like [_emitClassStatement] but emits a Dart 2.1 mixin represented by
@@ -1449,7 +1467,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
14491467
var staticProperties = properties.where((m) => m.isStatic).toList();
14501468
var instanceProperties = properties.where((m) => !m.isStatic).toList();
14511469

1452-
body.add(_emitClassStatement(c, className, heritage, staticProperties));
1470+
body.addAll(_emitClassStatement(c, className, heritage, staticProperties));
14531471
var superclassId = _emitScopedId(getLocalClassName(c.superclass!));
14541472
var classId = className is js_ast.Identifier
14551473
? className
@@ -1484,14 +1502,14 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
14841502
void _defineClass(Class c, js_ast.Expression className,
14851503
List<js_ast.Property> properties, List<js_ast.Statement> body) {
14861504
if (c == _coreTypes.objectClass) {
1487-
body.add(_emitClassStatement(c, className, null, properties));
1505+
body.addAll(_emitClassStatement(c, className, null, properties));
14881506
return;
14891507
}
14901508

1491-
js_ast.Expression emitClassRef(InterfaceType t) {
1492-
// TODO(jmesserly): investigate this. It seems like `lazyJSType` is
1493-
// invalid for use in an `extends` clause, hence this workaround.
1494-
return _emitJSInterop(t.classNode) ?? _emitClassRef(t);
1509+
js_ast.Expression emitClassRef(InterfaceType t,
1510+
{bool resolvedFromEmbedder = false}) {
1511+
return _emitJSInterop(t.classNode) ??
1512+
_emitClassRef(t, resolvedFromEmbedder: resolvedFromEmbedder);
14951513
}
14961514

14971515
// Find the real (user declared) superclass and the list of mixins.
@@ -1563,6 +1581,10 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
15631581

15641582
// Unroll mixins.
15651583
var baseClass = emitClassRef(supertype);
1584+
// The SDK is never hot reloaded, so we can avoid the overhead of
1585+
// resolving their classes through the embedder.
1586+
var embedderResolvedBaseClass =
1587+
emitClassRef(supertype, resolvedFromEmbedder: !_isBuildingSdk);
15661588

15671589
// TODO(jmesserly): we need to unroll kernel mixins because the synthetic
15681590
// classes lack required synthetic members, such as constructors.
@@ -1622,19 +1644,25 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
16221644
js_ast.ClassExpression(
16231645
_emitScopedId(mixinName), null, forwardingMethodStubs)
16241646
]));
1625-
_classExtendsLinks
1626-
.add(_runtimeStatement('classExtends(#, #)', [mixinId, baseClass]));
1647+
_classExtendsLinks.add(_runtimeStatement(
1648+
'classExtends(#, #)', [mixinId, embedderResolvedBaseClass]));
16271649
emitMixinConstructors(mixinId, superclass, mixinClass, mixinType);
16281650
hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(mixinClass);
1629-
_mixinApplicationLinks.add(_runtimeStatement(
1630-
'applyMixin(#, #)', [mixinId, emitClassRef(mixinType)]));
1651+
// The SDK is never hot reloaded, so we can avoid the overhead of
1652+
// resolving their classes through the embedder.
1653+
_mixinApplicationLinks.add(_runtimeStatement('applyMixin(#, #)', [
1654+
mixinId,
1655+
emitClassRef(mixinType, resolvedFromEmbedder: !_isBuildingSdk)
1656+
]));
16311657
baseClass = mixinId;
1658+
embedderResolvedBaseClass = mixinId;
16321659
}
16331660

16341661
if (c.isMixinDeclaration && !c.isMixinClass) {
16351662
_emitMixinStatement(c, className, baseClass, properties, body);
16361663
} else {
1637-
body.add(_emitClassStatement(c, className, baseClass, properties));
1664+
body.addAll(_emitClassStatement(
1665+
c, className, embedderResolvedBaseClass, properties));
16381666
}
16391667

16401668
_classEmittingExtends = savedTopLevelClass;
@@ -3368,16 +3396,27 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
33683396
/// Emit the top-level name associated with [n], which should not be an
33693397
/// external interop member.
33703398
js_ast.PropertyAccess _emitTopLevelNameNoExternalInterop(NamedNode n,
3371-
{String suffix = ''}) {
3399+
{String suffix = '', bool resolvedFromEmbedder = false}) {
33723400
// Some native tests use top-level native methods.
33733401
var isTopLevelNative = n is Member && isNative(n);
33743402
return js_ast.PropertyAccess(
33753403
isTopLevelNative
33763404
? _runtimeCall('global.self')
3377-
: _emitLibraryName(getLibrary(n)),
3405+
: (resolvedFromEmbedder
3406+
? _emitEmbedderResolvedLibrary(getLibrary(n))
3407+
: _emitLibraryName(getLibrary(n))),
33783408
_emitTopLevelMemberName(n, suffix: suffix));
33793409
}
33803410

3411+
/// Emits [library] fully resolved via the Dart Dev Embedder.
3412+
///
3413+
/// Used when the 'current' hot reload's generation of a library needs to be
3414+
/// resolved.
3415+
js_ast.Expression _emitEmbedderResolvedLibrary(Library library) {
3416+
var libraryName = js.string('${library.importUri}');
3417+
return js.call('dartDevEmbedder.importLibrary(#)', [libraryName]);
3418+
}
3419+
33813420
/// Emits the member name portion of a top-level member.
33823421
///
33833422
/// NOTE: usually you should use [_emitTopLevelName] instead of this. This
@@ -3817,21 +3856,19 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
38173856
/// Note that for `package:js` types, this will emit the class we emitted
38183857
/// using `_emitJSInteropClassNonExternalMembers`, and not the runtime type
38193858
/// that we synthesize for `package:js` types.
3820-
js_ast.Expression _emitClassRef(InterfaceType type) {
3821-
if (!_emittingClassExtends && type.typeArguments.isNotEmpty) {
3822-
var genericName = _emitTopLevelNameNoExternalInterop(type.classNode);
3823-
return js.call('#', [genericName]);
3824-
}
3825-
return _emitTopLevelNameNoExternalInterop(type.classNode);
3826-
}
3859+
///
3860+
/// [resolvedFromEmbedder] looks up [type] via the embedder, which retrieves
3861+
/// the correct library in the context of hot reload. This should not be set
3862+
/// for external classes.
3863+
js_ast.Expression _emitClassRef(InterfaceType type,
3864+
{bool resolvedFromEmbedder = false}) =>
3865+
_emitTopLevelNameNoExternalInterop(type.classNode,
3866+
resolvedFromEmbedder: resolvedFromEmbedder);
38273867

38283868
Never _typeCompilationError(DartType type, String description) =>
38293869
throw UnsupportedError('$description Encountered while compiling '
38303870
'${_currentLibrary!.fileUri}, which contains the type: $type.');
38313871

3832-
bool get _emittingClassExtends =>
3833-
_currentClass != null && identical(_currentClass, _classEmittingExtends);
3834-
38353872
/// Emits an expression that lets you access statics on a [type] from code.
38363873
js_ast.Expression _emitConstructorName(InterfaceType type, Member c) {
38373874
var isSyntheticDefault =

pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,42 @@ class LabelContinueFinder extends RecursiveVisitor {
302302
found = true;
303303
}
304304

305+
/// Returns true if a class contains any 'super' calls.
306+
bool hasSuper(Class node) {
307+
var visitor = SuperFinder();
308+
node.accept(visitor);
309+
return visitor.found;
310+
}
311+
312+
class SuperFinder extends RecursiveVisitor {
313+
var found = false;
314+
315+
void visit(Statement? s) {
316+
if (!found && s != null) s.accept(this);
317+
}
318+
319+
@override
320+
void visitAbstractSuperPropertyGet(AbstractSuperPropertyGet node) =>
321+
found = true;
322+
323+
@override
324+
void visitAbstractSuperPropertySet(AbstractSuperPropertySet node) =>
325+
found = true;
326+
327+
@override
328+
void visitSuperPropertyGet(SuperPropertyGet node) => found = true;
329+
330+
@override
331+
void visitSuperPropertySet(SuperPropertySet node) => found = true;
332+
333+
@override
334+
void visitAbstractSuperMethodInvocation(AbstractSuperMethodInvocation node) =>
335+
found = true;
336+
337+
@override
338+
void visitSuperMethodInvocation(SuperMethodInvocation node) => found = true;
339+
}
340+
305341
/// Returns `true` if any of [n]s children are a [FunctionExpression] node.
306342
bool containsFunctionExpression(Node n) {
307343
var visitor = _FunctionExpressionFinder.instance;

0 commit comments

Comments
 (0)