// Copyright (c) 2024, 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. import 'package:front_end/src/api_prototype/dynamic_module_validator.dart' show DynamicInterfaceLanguageImplPragmas, DynamicInterfaceSpecification; import 'package:kernel/ast.dart'; import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; import 'package:kernel/core_types.dart' show CoreTypes; import 'pragma.dart' show kDynModuleCanBeOverriddenPragmaName, kDynModuleCallablePragmaName, kDynModuleExtendablePragmaName, kDynModuleImplicitlyCallablePragmaName, kDynModuleImplicitlyExtendablePragmaName, kDynModuleCanBeOverriddenImplicitlyPragmaName; void annotateComponent( String dynamicInterfaceSpecification, Uri baseUri, Component component, CoreTypes coreTypes, { Map>>? detailedDynamicInterfaceJson, }) { final spec = DynamicInterfaceSpecification( dynamicInterfaceSpecification, baseUri, component, ); discoverLanguageImplPragmasInCoreLibraries(spec, component, coreTypes); final _DetailedDynamicInterfaceLogger? logger = detailedDynamicInterfaceJson != null ? _DetailedDynamicInterfaceLogger(detailedDynamicInterfaceJson) : null; logger?.setActiveSection('extendable'); final extendableAnnotator = annotateNodes( spec.extendable, kDynModuleExtendablePragmaName, baseUri, coreTypes, annotateClasses: true, annotateFinalClasses: false, annotateStaticMembers: false, annotateInstanceMembers: false, logger: logger, ); _ImplicitExtendableAnnotator( pragmaConstant(coreTypes, kDynModuleImplicitlyExtendablePragmaName), extendableAnnotator.annotatedClasses, ).annotate(); logger?.setActiveSection('can-be-overridden'); final canBeOverriddenAnnotator = annotateNodes( spec.canBeOverridden, kDynModuleCanBeOverriddenPragmaName, baseUri, coreTypes, annotateClasses: false, annotateFinalClasses: true, annotateStaticMembers: false, annotateInstanceMembers: true, logger: logger, ); final hierarchy = ClassHierarchy(component, coreTypes); copyCanBeOverriddenToMixinApplications( canBeOverriddenAnnotator.annotatedMembers, canBeOverriddenAnnotator.pragma, component, hierarchy, ); _ImplicitOverridesAnnotator( pragmaConstant(coreTypes, kDynModuleCanBeOverriddenImplicitlyPragmaName), hierarchy, canBeOverriddenAnnotator.annotatedMembers, ).annotate(); logger?.setActiveSection('callable'); final callableAnnotator = annotateNodes( spec.callable, kDynModuleCallablePragmaName, baseUri, coreTypes, annotateClasses: true, annotateFinalClasses: true, annotateStaticMembers: true, annotateInstanceMembers: true, logger: logger, ); final implicitUsesAnnotator = _ImplicitUsesAnnotator( pragmaConstant(coreTypes, kDynModuleImplicitlyCallablePragmaName), callableAnnotator.annotatedClasses, callableAnnotator.annotatedMembers, ); implicitUsesAnnotator.annotateMixinUses(extendableAnnotator.annotatedClasses); implicitUsesAnnotator.annotateMemberUses(callableAnnotator.annotatedMembers); implicitUsesAnnotator.annotateDispatchTargets(component); } InstanceConstant pragmaConstant(CoreTypes coreTypes, String pragmaName) { return InstanceConstant(coreTypes.pragmaClass.reference, [], { coreTypes.pragmaName.fieldReference: StringConstant(pragmaName), coreTypes.pragmaOptions.fieldReference: NullConstant(), }); } _Annotator annotateNodes( Set nodes, String pragmaName, Uri baseUri, CoreTypes coreTypes, { required bool annotateClasses, required bool annotateFinalClasses, required bool annotateStaticMembers, required bool annotateInstanceMembers, _DetailedDynamicInterfaceLogger? logger, }) { final pragma = pragmaConstant(coreTypes, pragmaName); final annotator = _Annotator( pragma, annotateClasses: annotateClasses, annotateFinalClasses: annotateFinalClasses, annotateStaticMembers: annotateStaticMembers, annotateInstanceMembers: annotateInstanceMembers, logger: logger, ); for (final node in nodes) { node.accept(annotator); } return annotator; } class _Annotator extends RecursiveVisitor { final Constant pragma; final bool annotateClasses; final bool annotateFinalClasses; final bool annotateStaticMembers; final bool annotateInstanceMembers; final _DetailedDynamicInterfaceLogger? logger; final Set annotatedClasses = Set.identity(); final Set annotatedMembers = Set.identity(); _Annotator( this.pragma, { required this.annotateClasses, required this.annotateFinalClasses, required this.annotateStaticMembers, required this.annotateInstanceMembers, this.logger, }); bool get annotateMembers => annotateStaticMembers || annotateInstanceMembers; @override void visitLibrary(Library node) { for (final c in node.classes) { if (c.name[0] != '_') { c.accept(this); } } for (final ext in node.extensions) { if (ext.name[0] != '_') { ext.accept(this); } } for (final extensionType in node.extensionTypeDeclarations) { if (extensionType.name[0] != '_') { extensionType.accept(this); } } for (final exportRef in node.additionalExports) { exportRef.node!.accept(this); } if (annotateMembers) { _visitPublicMembers(node.procedures); _visitPublicMembers(node.fields); } } @override void visitClass(Class node) { annotateClass(node); if (annotateMembers) { _visitPublicMembers(node.constructors); _visitPublicMembers(node.procedures); _visitPublicMembers(node.fields); } } void _visitPublicMembers(List members) { for (final m in members) { if (!m.name.isPrivate) { m.accept(this); } } } @override void defaultMember(Member node) { annotateMember(node); } @override void visitClassReference(Class node) { annotateClass(node); } void annotateClass(Class node) { if (annotateClasses && (annotateFinalClasses || !node.isFinal) && annotatedClasses.add(node)) { logger?.logNode(node); node.addAnnotation(ConstantExpression(pragma)); } } void annotateMember(Member node) { if ((node.isInstanceMember ? annotateInstanceMembers : annotateStaticMembers) && annotatedMembers.add(node)) { logger?.logNode(node); node.addAnnotation(ConstantExpression(pragma)); } } @override void visitExtension(Extension node) { for (final md in node.memberDescriptors) { final member = md.memberReference?.node; if (member != null) { annotateMember(member as Member); } final tearOff = md.tearOffReference?.node; if (tearOff != null) { annotateMember(tearOff as Member); } } } @override void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) { for (final md in node.memberDescriptors) { final member = md.memberReference?.node; if (member != null) { annotateMember(member as Member); } final tearOff = md.tearOffReference?.node; if (tearOff != null) { annotateMember(tearOff as Member); } } } } class _ImplicitExtendableAnnotator { final Constant pragma; final Set extendableClasses; final Set implicitlyExtendable = Set.identity(); _ImplicitExtendableAnnotator(this.pragma, this.extendableClasses); void annotate() { for (final cls in extendableClasses) { annotateSupertypesOf(cls); } } void annotateSupertypesOf(Class cls) { for (final supertype in cls.supers) { final supertypeClass = supertype.classNode; if (implicitlyExtendable.add(supertypeClass) && !extendableClasses.contains(supertypeClass)) { supertypeClass.addAnnotation(ConstantExpression(pragma)); annotateSupertypesOf(supertypeClass); } } } } class _ImplicitOverridesAnnotator { final Constant pragma; final ClassHierarchy hierarchy; final Set overriddenMembers; final Set implicitlyOverriddenSetters = Set.identity(); final Set implicitlyOverriddenNonSetters = Set.identity(); _ImplicitOverridesAnnotator( this.pragma, this.hierarchy, this.overriddenMembers, ); void annotate() { for (final member in overriddenMembers) { final cls = member.enclosingClass!; if (member.hasGetter) { annotateSupertypesOf(cls, member.name, setter: false); } if (member.hasSetter) { annotateSupertypesOf(cls, member.name, setter: true); } } } void annotateSupertypesOf( Class cls, Name memberName, { required bool setter, }) { for (final supertype in cls.supers) { final supertypeClass = supertype.classNode; final member = ClassHierarchy.findMemberByName( hierarchy.getDeclaredMembers(supertypeClass, setters: setter), memberName, ); if (member != null) { final implicitlyOverridden = setter ? implicitlyOverriddenSetters : implicitlyOverriddenNonSetters; if (implicitlyOverridden.add(member) && !overriddenMembers.contains(member)) { member.addAnnotation(ConstantExpression(pragma)); } else { // The member is already annotated - do not go deeper. continue; } } annotateSupertypesOf(supertypeClass, memberName, setter: setter); } } } class _ImplicitUsesAnnotator extends RecursiveVisitor { final Constant pragma; final Set annotatedClasses = Set.identity(); final Set annotatedMembers = Set.identity(); final Set _annotatedConstantClasses = Set.identity(); final Set _visitedConstants = Set.identity(); final Map _classInfos = Map.identity(); _ImplicitUsesAnnotator( this.pragma, Set explicitlyUsedClasses, Set explicitlyUsedMembers, ) { annotatedClasses.addAll(explicitlyUsedClasses); annotatedMembers.addAll(explicitlyUsedMembers); } void annotateMixinUses(Set extendableClasses) { for (final cls in extendableClasses) { if (cls.isMixinClass || cls.isMixinDeclaration) { cls.visitChildren(this); } } } void annotateMemberUses(Set explicitlyUsedMembers) { for (final node in explicitlyUsedMembers) { node.acceptReference(this); } } @override void visitClassReference(Class node) { annotateClass(node); } @override void visitConstructorReference(Constructor node) { annotateMember(node); if (node.isConst) { annotateConstantClass(node.enclosingClass); node.visitChildren(this); } } @override void visitProcedureReference(Procedure node) { annotateMember(node); if (node.isRedirectingFactory) { final target = node.function.redirectingFactoryTarget?.target; target?.acceptReference(this); } } @override void visitFieldReference(Field node) { annotateMember(node); if (node.isConst) { (node.initializer as ConstantExpression).constant.acceptReference(this); } } @override void defaultConstantReference(Constant node) { if (_visitedConstants.add(node)) { node.visitChildren(this); } } @override void visitInstanceConstantReference(InstanceConstant node) { super.visitInstanceConstantReference(node); annotateConstantClass(node.classNode); } void annotateClass(Class node) { if (annotatedClasses.add(node)) { node.addAnnotation(ConstantExpression(pragma)); } } void annotateMember(Member node) { if (annotatedMembers.add(node)) { node.addAnnotation(ConstantExpression(pragma)); } } // Annotate class potentially used in an InstanceConstant. void annotateConstantClass(Class node) { if (_annotatedConstantClasses.add(node)) { annotateClass(node); for (final f in node.fields) { if (f.isInstanceMember) { annotateMember(f); f.initializer?.accept(this); } } final superclass = node.superclass; if (superclass != null) { annotateConstantClass(superclass); } } } void annotateDispatchTargets(Component component) { for (final m in annotatedMembers) { if (m.isInstanceMember) { _getClassInfo(m.enclosingClass!).addSelector(m); } } for (final lib in component.libraries) { for (final classNode in lib.classes) { final classInfo = _getClassInfo(classNode); classInfo.collectSelectors(); for (final selector in classInfo.setterSelectors) { final dispatchTarget = classInfo.dispatchTargetsSetters[selector]; if (dispatchTarget != null) { annotateMember(dispatchTarget); } } for (final selector in classInfo.nonSetterSelectors) { final dispatchTarget = classInfo.dispatchTargetsNonSetters[selector]; if (dispatchTarget != null) { annotateMember(dispatchTarget); } } } } } _ClassInfo _getClassInfo(Class cls) => _classInfos[cls] ??= _createClassInfo(cls); _ClassInfo _createClassInfo(Class cls) { final superclass = cls.superclass; final superclassInfo = superclass != null ? _getClassInfo(superclass) : null; final mixedInClass = cls.mixedInClass; final mixedInClassInfo = mixedInClass != null ? _getClassInfo(mixedInClass) : null; final implementedClassInfos = cls.implementedTypes .map((sup) => _getClassInfo(sup.classNode)) .toList(); return _ClassInfo( cls, superclassInfo, mixedInClassInfo, implementedClassInfos, ); } } class _ClassInfo { final Class classNode; final _ClassInfo? superclass; final _ClassInfo? mixedInClass; final List<_ClassInfo> implementedClasses; // Callable selectors. final setterSelectors = Set(); final nonSetterSelectors = Set(); bool collected = false; // Cache of dispatch targets. late final Map dispatchTargetsSetters = _initDispatchTargets( true, ); late final Map dispatchTargetsNonSetters = _initDispatchTargets( false, ); _ClassInfo( this.classNode, this.superclass, this.mixedInClass, this.implementedClasses, ); void addSelector(Member m) { if (m.isInstanceMember) { if (!_isSetter(m)) { nonSetterSelectors.add(m.name); } if (m.hasSetter) { setterSelectors.add(m.name); } } } void addAllSelectors(_ClassInfo other) { setterSelectors.addAll(other.setterSelectors); nonSetterSelectors.addAll(other.nonSetterSelectors); } void collectSelectors() { if (!collected) { final superclass = this.superclass; if (superclass != null) { superclass.collectSelectors(); addAllSelectors(superclass); } final mixedInClass = this.mixedInClass; if (mixedInClass != null) { mixedInClass.collectSelectors(); addAllSelectors(mixedInClass); } for (final c in implementedClasses) { c.collectSelectors(); addAllSelectors(c); } collected = true; } } Map _initDispatchTargets(bool setters) { Map targets; final superclass = this.superclass; if (superclass != null) { targets = Map.of( setters ? superclass.dispatchTargetsSetters : superclass.dispatchTargetsNonSetters, ); } else { targets = {}; } final mixedInClass = this.mixedInClass; if (mixedInClass != null) { targets.addAll( setters ? mixedInClass.dispatchTargetsSetters : mixedInClass.dispatchTargetsNonSetters, ); } for (Field f in classNode.fields) { if (!f.isStatic && !f.isAbstract) { if (!setters || f.hasSetter) { targets[f.name] = f; } } } for (Procedure p in classNode.procedures) { if (!p.isStatic && !p.isAbstract) { if (p.isSetter == setters) { targets[p.name] = p; } } } return targets; } static bool _isSetter(Member m) => m is Procedure && m.isSetter; } void discoverLanguageImplPragmasInCoreLibraries( DynamicInterfaceSpecification spec, Component component, CoreTypes coreTypes, ) { final languageImplPragmas = DynamicInterfaceLanguageImplPragmas(coreTypes); final visitor = _DiscoverLanguageImplPragmasVisitor( spec, languageImplPragmas, ); for (final lib in component.libraries) { if (languageImplPragmas.isPlatformLibrary(lib)) { visitor.visitLibrary(lib); } } } class _DiscoverLanguageImplPragmasVisitor extends RecursiveVisitor { final DynamicInterfaceSpecification spec; final DynamicInterfaceLanguageImplPragmas languageImplPragmas; _DiscoverLanguageImplPragmasVisitor(this.spec, this.languageImplPragmas); @override void visitClass(Class node) { if (languageImplPragmas.isExtendable(node)) { spec.extendable.add(node); } if (languageImplPragmas.isCallable(node)) { spec.callable.add(node); } node.visitChildren(this); } @override void defaultMember(Member node) { if (languageImplPragmas.isCallable(node)) { spec.callable.add(node); } if (languageImplPragmas.canBeOverridden(node)) { if (!node.isInstanceMember) { throw 'Expected instance member $node'; } spec.canBeOverridden.add(node); } } } // Mixin transformation copies all members of mixins into mixin applications. // So we need to copy can-be-overridden pragmas from members of mixins to // their copies in the transformed mixin applications. void copyCanBeOverriddenToMixinApplications( Set overriddenMembers, Constant pragma, Component component, ClassHierarchy hierarchy, ) { void processMember(Member original, Class mixinApplication) { if (!original.isInstanceMember) { return; } if (!overriddenMembers.contains(original)) { return; } final member = ClassHierarchy.findMemberByName( hierarchy.getDeclaredMembers( mixinApplication, setters: original is Procedure && original.isSetter, ), original.name, ); if (member == null) { return; } if (overriddenMembers.add(member)) { member.addAnnotation(ConstantExpression(pragma)); } } for (final library in component.libraries) { for (final cls in library.classes) { if (cls.isEliminatedMixin) { final origin = cls.implementedTypes.last.classNode; for (final proc in origin.procedures) { processMember(proc, cls); } for (final field in origin.fields) { processMember(field, cls); } } } } } class _DetailedDynamicInterfaceLogger { final Map>> json; late List> section; _DetailedDynamicInterfaceLogger(this.json); void setActiveSection(String name) { section = (json[name] ??= >[]); } void logNode(TreeNode node) { switch (node) { case Class(): section.add({ 'library': node.enclosingLibrary.importUri.toString(), 'class': node.name, }); case Member() when node.enclosingClass != null: section.add({ 'library': node.enclosingLibrary.importUri.toString(), 'class': node.enclosingClass!.demangledName, 'member': node.name.text, }); case Member() when node.enclosingClass == null: section.add({ 'library': node.enclosingLibrary.importUri.toString(), 'member': node.name.text, }); } } }