| // Copyright (c) 2012, 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. |
| |
| /** |
| * This library contains the infrastructure to parse and integrate patch files. |
| * |
| * Three types of elements can be patched: [LibraryElement], [ClassElement], |
| * [FunctionElement]. Patches are introduced in patch libraries which are loaded |
| * together with the corresponding origin library. Which libraries that are |
| * patched is determined by the [dart2jsPatchPath] field of [LibraryInfo] found |
| * in [:lib/_internal/libraries.dart:]. |
| * |
| * Patch libraries are parsed like regular library and thus provided with their |
| * own elements. These elements which are distinct from the elements from the |
| * patched library and the relation between patched and patch elements is |
| * established through the [:patch:] and [:origin:] fields found on |
| * [LibraryElement], [ClassElement] and [FunctionElement]. The [:patch:] fields |
| * are set on the patched elements to point to their corresponding patch |
| * element, and the [:origin:] elements are set on the patch elements to point |
| * their corresponding patched elements. |
| * |
| * The fields [Element.isPatched] and [Element.isPatch] can be used to determine |
| * whether the [:patch:] or [:origin:] field, respectively, has been set on an |
| * element, regardless of whether the element is one of the three patchable |
| * element types or not. |
| * |
| * ## Variants of Classes and Functions ## |
| * |
| * With patches there are four variants of classes and function: |
| * |
| * Regular: A class or function which is not declared in a patch library and |
| * which has no corresponding patch. |
| * Origin: A class or function which is not declared in a patch library and |
| * which has a corresponding patch. Origin functions must use the [:external:] |
| * modifier and can have no body. Origin classes and functions are also |
| * called 'patched'. |
| * Patch: A class or function which is declared in a patch library and which |
| * has a corresponding origin. Both patch classes and patch functions must use |
| * the [:patch:] modifier. |
| * Injected: A class or function (or even field) which is declared in a |
| * patch library and which has no corresponding origin. An injected element |
| * cannot use the [:patch:] modifier. Injected elements are never visible from |
| * outside the patch library in which they have been declared. For this |
| * reason, injected elements are often declared private and therefore called |
| * also called 'patch private'. |
| * |
| * Examples of the variants is shown in the code below: |
| * |
| * // In the origin library: |
| * class RegularClass { // A regular class. |
| * void regularMethod() {} // A regular method. |
| * } |
| * class PatchedClass { // An origin class. |
| * int regularField; // A regular field. |
| * void regularMethod() {} // A regular method. |
| * external void patchedMethod(); // An origin method. |
| * } |
| * |
| * // In the patch library: |
| * class _InjectedClass { // An injected class. |
| * void _injectedMethod() {} // An injected method. |
| * } |
| * patch class PatchedClass { // A patch class. |
| * int _injectedField; { // An injected field. |
| * patch void patchedMethod() {} // A patch method. |
| * } |
| * |
| * |
| * ## Declaration and Implementation ## |
| * |
| * With patches we have two views on elements: as the 'declaration' which |
| * introduces the entity and defines its interface, and as the 'implementation' |
| * which defines the actual implementation of the entity. |
| * |
| * Every element has a 'declaration' and an 'implementation' element. For |
| * regular and injected elements these are the same. For origin elements the |
| * declaration is the element itself and the implementation is the patch element |
| * found through its [:patch:] field. For patch elements the implementation is |
| * the element itself and the declaration is the origin element found through |
| * its [:origin:] field. The declaration and implementation of any element is |
| * conveniently available through the [Element.declaration] and |
| * [Element.implementation] getters. |
| * |
| * Most patch-related invariants enforced through-out the compiler are defined |
| * in terms of 'declaration' and 'implementation', and tested through the |
| * predicate getters [Element.isDeclaration] and [Element.isImplementation]. |
| * Patch invariants are stated both in comments and as assertions. |
| * |
| * |
| * ## General invariant guidelines ## |
| * |
| * For [LibraryElement] we always use declarations. This means the |
| * [Element.getLibrary] method will only return library declarations. Patch |
| * library implementations are only accessed through calls to |
| * [Element.getImplementationLibrary] which is used to setup the correct |
| * [Element.enclosingElement] relation between patch/injected elements and the |
| * patch library. |
| * |
| * For [ClassElement] and [FunctionElement] we use declarations for determining |
| * identity and implementations for work based on the AST nodes, such as |
| * resolution, type-checking, type inference, building SSA graphs, etc. |
| * - Worklist only contain declaration elements. |
| * - Most maps and sets use declarations exclusively, and their individual |
| * invariants are stated in the field comments. |
| * - [TreeElements] only map to patch elements from inside a patch library. |
| * TODO(johnniwinther): Simplify this invariant to use only declarations in |
| * [TreeElements]. |
| * - Builders shift between declaration and implementation depending on usages. |
| * - Compile-time constants use constructor implementation exclusively. |
| * - Work on function parameters is performed on the declaration of the function |
| * element. |
| */ |
| |
| library patchparser; |
| |
| import "dart:uri"; |
| import "tree/tree.dart" as tree; |
| import "dart2jslib.dart" as leg; // CompilerTask, Compiler. |
| import "apiimpl.dart"; |
| import "scanner/scannerlib.dart"; // Scanner, Parsers, Listeners |
| import "elements/elements.dart"; |
| import 'util/util.dart'; |
| |
| class PatchParserTask extends leg.CompilerTask { |
| PatchParserTask(leg.Compiler compiler): super(compiler); |
| final String name = "Patching Parser"; |
| |
| /** |
| * Scans a library patch file, applies the method patches and |
| * injections to the library, and returns a list of class |
| * patches. |
| */ |
| void patchLibrary(leg.LibraryDependencyHandler handler, |
| Uri patchUri, LibraryElement originLibrary) { |
| |
| leg.Script script = compiler.readScript(patchUri, null); |
| var patchLibrary = new LibraryElement(script, patchUri, originLibrary); |
| handler.registerNewLibrary(patchLibrary); |
| LinkBuilder<tree.LibraryTag> imports = new LinkBuilder<tree.LibraryTag>(); |
| compiler.withCurrentElement(patchLibrary.entryCompilationUnit, () { |
| // This patches the elements of the patch library into [library]. |
| // Injected elements are added directly under the compilation unit. |
| // Patch elements are stored on the patched functions or classes. |
| scanLibraryElements(patchLibrary.entryCompilationUnit, imports); |
| }); |
| // After scanning declarations, we handle the import tags in the patch. |
| // TODO(lrn): These imports end up in the original library and are in |
| // scope for the original methods too. This should be fixed. |
| compiler.importHelperLibrary(originLibrary); |
| for (tree.LibraryTag tag in imports.toLink()) { |
| compiler.libraryLoader.registerLibraryFromTag(handler, patchLibrary, tag); |
| } |
| } |
| |
| void scanLibraryElements( |
| CompilationUnitElement compilationUnit, |
| LinkBuilder<tree.LibraryTag> imports) { |
| measure(() { |
| // TODO(lrn): Possibly recursively handle #source directives in patch. |
| leg.Script script = compilationUnit.script; |
| Token tokens = new StringScanner(script.text).tokenize(); |
| Function idGenerator = compiler.getNextFreeClassId; |
| PatchListener patchListener = |
| new PatchElementListener(compiler, |
| compilationUnit, |
| idGenerator, |
| imports); |
| new PatchParser(patchListener).parseUnit(tokens); |
| }); |
| } |
| |
| void parsePatchClassNode(PartialClassElement element) { |
| // Parse [PartialClassElement] using a "patch"-aware parser instead |
| // of calling its [parseNode] method. |
| if (element.cachedNode != null) return; |
| |
| return measure(() => compiler.withCurrentElement(element, () { |
| PatchMemberListener listener = new PatchMemberListener(compiler, element); |
| Parser parser = new PatchClassElementParser(listener); |
| Token token = parser.parseTopLevelDeclaration(element.beginToken); |
| assert(identical(token, element.endToken.next)); |
| element.cachedNode = listener.popNode(); |
| assert(listener.nodes.isEmpty); |
| |
| Link<Element> patches = element.localMembers; |
| applyContainerPatch(element.origin, patches); |
| })); |
| } |
| |
| void applyContainerPatch(ScopeContainerElement original, |
| Link<Element> patches) { |
| while (!patches.isEmpty) { |
| Element patchElement = patches.head; |
| Element originalElement = original.localLookup(patchElement.name); |
| if (patchElement.isAccessor() && originalElement != null) { |
| if (!identical(originalElement.kind, ElementKind.ABSTRACT_FIELD)) { |
| compiler.internalError( |
| "Cannot patch non-getter/setter with getter/setter", |
| element: originalElement); |
| } |
| AbstractFieldElement originalField = originalElement; |
| if (patchElement.isGetter()) { |
| originalElement = originalField.getter; |
| } else { |
| originalElement = originalField.setter; |
| } |
| } |
| if (originalElement == null) { |
| if (isPatchElement(patchElement)) { |
| compiler.internalError("Cannot patch non-existing member '" |
| "${patchElement.name.slowToString()}'."); |
| } |
| } else { |
| patchMember(originalElement, patchElement); |
| } |
| patches = patches.tail; |
| } |
| } |
| |
| bool isPatchElement(Element element) { |
| // TODO(lrn): More checks needed if we introduce metadata for real. |
| // In that case, it must have the identifier "native" as metadata. |
| for (Link link = element.metadata; !link.isEmpty; link = link.tail) { |
| if (link.head is PatchMetadataAnnotation) return true; |
| } |
| return false; |
| } |
| |
| void patchMember(Element originalElement, Element patchElement) { |
| // The original library has an element with the same name as the patch |
| // library element. |
| // In this case, the patch library element must be a function marked as |
| // "patch" and it must have the same signature as the function it patches. |
| if (!isPatchElement(patchElement)) { |
| compiler.internalError("Cannot overwrite existing '" |
| "${originalElement.name.slowToString()}' with non-patch."); |
| } |
| if (originalElement is! FunctionElement) { |
| // TODO(lrn): Handle class declarations too. |
| compiler.internalError("Can only patch functions", element: originalElement); |
| } |
| FunctionElement original = originalElement; |
| if (!original.modifiers.isExternal()) { |
| compiler.internalError("Can only patch external functions.", element: original); |
| } |
| if (patchElement is! FunctionElement || |
| !patchSignatureMatches(original, patchElement)) { |
| compiler.internalError("Can only patch functions with matching signatures", |
| element: original); |
| } |
| applyFunctionPatch(original, patchElement); |
| } |
| |
| bool patchSignatureMatches(FunctionElement original, FunctionElement patch) { |
| // TODO(lrn): Check that patches actually match the signature of |
| // the function it's patching. |
| return true; |
| } |
| |
| void applyFunctionPatch(FunctionElement element, |
| FunctionElement patchElement) { |
| if (element.isPatched) { |
| compiler.internalError("Trying to patch a function more than once.", |
| element: element); |
| } |
| if (element.cachedNode != null) { |
| compiler.internalError("Trying to patch an already compiled function.", |
| element: element); |
| } |
| // Don't just assign the patch field. This also updates the cachedNode. |
| element.setPatch(patchElement); |
| patchElement.origin = element; |
| } |
| } |
| |
| /** |
| * Extension of the [Listener] interface to handle the extra "patch" pseudo- |
| * keyword in patch files. |
| * Patch files shouldn't have a type named "patch". |
| */ |
| abstract class PatchListener extends Listener { |
| void beginPatch(Token patch); |
| void endPatch(Token patch); |
| } |
| |
| /** |
| * Partial parser that extends the top-level and class grammars to allow the |
| * word "patch" in front of some declarations. |
| */ |
| class PatchParser extends PartialParser { |
| PatchParser(PatchListener listener) : super(listener); |
| |
| PatchListener get patchListener => listener; |
| |
| bool isPatch(Token token) { |
| return token.stringValue == null && |
| token.slowToString() == "patch"; |
| } |
| |
| /** |
| * Parse top-level declarations, and allow "patch" in front of functions |
| * and classes. |
| */ |
| Token parseTopLevelDeclaration(Token token) { |
| if (!isPatch(token)) { |
| return super.parseTopLevelDeclaration(token); |
| } |
| Token patch = token; |
| token = token.next; |
| String value = token.stringValue; |
| if (identical(value, 'interface') |
| || identical(value, 'typedef') |
| || identical(value, '#') |
| || identical(value, 'abstract')) { |
| // At the top level, you can only patch functions and classes. |
| // Patch classes and functions can't be marked abstract. |
| return listener.unexpected(patch); |
| } |
| patchListener.beginPatch(patch); |
| token = super.parseTopLevelDeclaration(token); |
| patchListener.endPatch(patch); |
| return token; |
| } |
| |
| /** |
| * Parse a class member. |
| * If the member starts with "patch", it's a member override. |
| * Only methods can be overridden, including constructors, getters and |
| * setters, but not fields. If "patch" occurs in front of a field, the error |
| * is caught elsewhere. |
| */ |
| Token parseMember(Token token) { |
| if (!isPatch(token)) { |
| return super.parseMember(token); |
| } |
| Token patch = token; |
| patchListener.beginPatch(patch); |
| token = super.parseMember(token.next); |
| patchListener.endPatch(patch); |
| return token; |
| } |
| } |
| |
| /** |
| * Partial parser for patch files that also handles the members of class |
| * declarations. |
| */ |
| class PatchClassElementParser extends PatchParser { |
| PatchClassElementParser(PatchListener listener) : super(listener); |
| |
| Token parseClassBody(Token token) => fullParseClassBody(token); |
| } |
| |
| /** |
| * Extension of [ElementListener] for parsing patch files. |
| */ |
| class PatchElementListener extends ElementListener implements PatchListener { |
| final LinkBuilder<tree.LibraryTag> imports; |
| bool isMemberPatch = false; |
| bool isClassPatch = false; |
| |
| PatchElementListener(leg.DiagnosticListener listener, |
| CompilationUnitElement patchElement, |
| int idGenerator(), |
| this.imports) |
| : super(listener, patchElement, idGenerator); |
| |
| MetadataAnnotation popMetadata() { |
| // TODO(ahe): Remove this method. |
| popNode(); // Discard null. |
| return new PatchMetadataAnnotation(); |
| } |
| |
| void beginPatch(Token token) { |
| if (identical(token.next.stringValue, "class")) { |
| isClassPatch = true; |
| } else { |
| isMemberPatch = true; |
| } |
| handleIdentifier(token); |
| } |
| |
| void endPatch(Token token) { |
| if (identical(token.next.stringValue, "class")) { |
| isClassPatch = false; |
| } else { |
| isMemberPatch = false; |
| } |
| } |
| |
| /** |
| * Allow script tags (import only, the parser rejects the rest for now) in |
| * patch files. The import tags will be added to the library. |
| */ |
| bool allowLibraryTags() => true; |
| |
| void addLibraryTag(tree.LibraryTag tag) { |
| super.addLibraryTag(tag); |
| imports.addLast(tag); |
| } |
| |
| void pushElement(Element element) { |
| if (isMemberPatch || (isClassPatch && element is ClassElement)) { |
| // Apply patch. |
| element.addMetadata(popMetadata()); |
| LibraryElement originLibrary = compilationUnitElement.getLibrary(); |
| assert(originLibrary.isPatched); |
| Element existing = originLibrary.localLookup(element.name); |
| if (isMemberPatch) { |
| if (element is! FunctionElement) { |
| listener.internalErrorOnElement(element, |
| "Member patch is not a function."); |
| } |
| FunctionElement functionElement = element; |
| if (identical(existing.kind, ElementKind.ABSTRACT_FIELD)) { |
| if (!element.isAccessor()) { |
| listener.internalErrorOnElement( |
| functionElement, "Patching non-accessor with accessor"); |
| } |
| AbstractFieldElement field = existing; |
| if (functionElement.isGetter()) { |
| existing = field.getter; |
| } else { |
| existing = field.setter; |
| } |
| } |
| if (existing is! FunctionElement) { |
| listener.internalErrorOnElement(functionElement, |
| "No corresponding method for patch."); |
| } |
| FunctionElement existingFunction = existing; |
| if (existingFunction.isPatched) { |
| listener.internalErrorOnElement( |
| functionElement, "Patching the same function more than once."); |
| } |
| existingFunction.patch = functionElement; |
| functionElement.origin = existingFunction; |
| } else { |
| assert(leg.invariant(element, element is ClassElement)); |
| ClassElement classElement = element; |
| if (existing is! ClassElement) { |
| listener.internalErrorOnElement( |
| classElement, "Patching a non-class with a class patch."); |
| } |
| ClassElement existingClass = existing; |
| if (existingClass.isPatched) { |
| listener.internalErrorOnElement( |
| classElement, "Patching the same class more than once."); |
| } |
| existingClass.patch = classElement; |
| classElement.origin = existingClass; |
| } |
| } |
| super.pushElement(element); |
| } |
| } |
| |
| /** |
| * Extension of [MemberListener] for parsing patch class bodies. |
| */ |
| class PatchMemberListener extends MemberListener implements PatchListener { |
| bool isMemberPatch = false; |
| bool isClassPatch = false; |
| PatchMemberListener(leg.DiagnosticListener listener, |
| Element enclosingElement) |
| : super(listener, enclosingElement); |
| |
| MetadataAnnotation popMetadata() { |
| // TODO(ahe): Remove this method. |
| popNode(); // Discard null. |
| return new PatchMetadataAnnotation(); |
| } |
| |
| void beginPatch(Token token) { |
| if (identical(token.next.stringValue, "class")) { |
| isClassPatch = true; |
| } else { |
| isMemberPatch = true; |
| } |
| handleIdentifier(token); |
| } |
| |
| void endPatch(Token token) { |
| if (identical(token.next.stringValue, "class")) { |
| isClassPatch = false; |
| } else { |
| isMemberPatch = false; |
| } |
| } |
| |
| void addMember(Element element) { |
| if (isMemberPatch || (isClassPatch && element is ClassElement)) { |
| element.addMetadata(popMetadata()); |
| } |
| super.addMember(element); |
| } |
| } |
| |
| // TODO(ahe): Get rid of this class. |
| class PatchMetadataAnnotation extends MetadataAnnotation { |
| final leg.Constant value = null; |
| |
| PatchMetadataAnnotation() : super(STATE_DONE); |
| } |