ed8e4255a4
TL;DR: Unbind canonical names doesn't do what you think it does and probably shouldn't (ever) be used. This CL stops using it in a few places. Longer version: When loading a dill file it: - First loads the table of canonical names. These have no references yet. - When a canonical name is asked for its reference it creates one if it doesn't yet have one. - When loading, for instance, a library, it asks for the reference. When unbinding a canonical name: - It removes itself (the canonical name) from the reference - It removes the reference in itself - Note: Whatever has a pointer to the reference keeps it, and the reference points to whatever node it already pointed to. This also means, that if we have a dill file that's split in two and: - Load #1 - Load #2 that works fine, but if we - Load #1 - Unbind canonical names - Binds canonical names - Load #2 stuff is not bound correctly (and an error is thrown). And - the cause of this bug: - Load #1 - Load #2 everything is fine - Unbind canonical names - Binds canonical names - Load #2' stuff is not bound correctly --- references points to stuff loaded as #2, not as #2'. On top of being weird, wrong and confusing it also caused wrong things to be but into the class hierarchy which ultimatly caused the crash. This CL fixes it by not calling unbind and force loading of dill files (at specific call sites) to create new libraries (and in the process overwriting references ".node"). Revert "[dartdevc] Retry ddc incremental compile on crash" This reverts commitecdbdf00b8. Revert "[kernel_worker] retry on failure" This reverts commit43eebea5a3. Fixes #36644 Bug: #36644 Change-Id: Id8f548179e6a409b01f2ebfa3219f94cb64b1c05 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/100380 Reviewed-by: Kevin Millikin <kmillikin@google.com> Reviewed-by: Vijay Menon <vsm@google.com> Reviewed-by: Jake Macdonald <jakemac@google.com> Reviewed-by: Jenny Messerly <jmesserly@google.com> Commit-Queue: Jens Johansen <jensj@google.com>
217 lines
6.2 KiB
Dart
217 lines
6.2 KiB
Dart
// Copyright (c) 2016, 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.
|
|
library kernel.canonical_name;
|
|
|
|
import 'ast.dart';
|
|
|
|
/// A string sequence that identifies a library, class, or member.
|
|
///
|
|
/// Canonical names are organized in a prefix tree. Each node knows its
|
|
/// parent, children, and the AST node it is currently bound to.
|
|
///
|
|
/// The following schema specifies how the canonical name of a given object
|
|
/// is defined:
|
|
///
|
|
/// Library:
|
|
/// URI of library
|
|
///
|
|
/// Class:
|
|
/// Canonical name of enclosing library
|
|
/// Name of class
|
|
///
|
|
/// Constructor:
|
|
/// Canonical name of enclosing class or library
|
|
/// "@constructors"
|
|
/// Qualified name
|
|
///
|
|
/// Field:
|
|
/// Canonical name of enclosing class or library
|
|
/// "@fields"
|
|
/// Qualified name
|
|
///
|
|
/// Typedef:
|
|
/// Canonical name of enclosing class
|
|
/// "@typedefs"
|
|
/// Name text
|
|
///
|
|
/// Procedure that is not an accessor or factory:
|
|
/// Canonical name of enclosing class or library
|
|
/// "@methods"
|
|
/// Qualified name
|
|
///
|
|
/// Procedure that is a getter:
|
|
/// Canonical name of enclosing class or library
|
|
/// "@getters"
|
|
/// Qualified name
|
|
///
|
|
/// Procedure that is a setter:
|
|
/// Canonical name of enclosing class or library
|
|
/// "@setters"
|
|
/// Qualified name
|
|
///
|
|
/// Procedure that is a factory:
|
|
/// Canonical name of enclosing class
|
|
/// "@factories"
|
|
/// Qualified name
|
|
///
|
|
/// Qualified name:
|
|
/// if private: URI of library
|
|
/// Name text
|
|
///
|
|
/// The "qualified name" allows a member to have a name that is private to
|
|
/// a library other than the one containing that member.
|
|
class CanonicalName {
|
|
CanonicalName _parent;
|
|
|
|
CanonicalName get parent => _parent;
|
|
|
|
final String name;
|
|
CanonicalName _nonRootTop;
|
|
|
|
Map<String, CanonicalName> _children;
|
|
|
|
/// The library, class, or member bound to this name.
|
|
Reference reference;
|
|
|
|
/// Temporary index used during serialization.
|
|
int index = -1;
|
|
|
|
CanonicalName._(this._parent, this.name) {
|
|
assert(name != null);
|
|
assert(parent != null);
|
|
_nonRootTop = _parent.isRoot ? this : _parent._nonRootTop;
|
|
}
|
|
|
|
CanonicalName.root()
|
|
: _parent = null,
|
|
_nonRootTop = null,
|
|
name = '';
|
|
|
|
bool get isRoot => _parent == null;
|
|
CanonicalName get nonRootTop => _nonRootTop;
|
|
|
|
Iterable<CanonicalName> get children =>
|
|
_children?.values ?? const <CanonicalName>[];
|
|
|
|
Iterable<CanonicalName> get childrenOrNull => _children?.values;
|
|
|
|
bool hasChild(String name) {
|
|
return _children != null && _children.containsKey(name);
|
|
}
|
|
|
|
CanonicalName getChild(String name) {
|
|
var map = _children ??= <String, CanonicalName>{};
|
|
return map[name] ??= new CanonicalName._(this, name);
|
|
}
|
|
|
|
CanonicalName getChildFromUri(Uri uri) {
|
|
// Note that the Uri class caches its string representation, and all library
|
|
// URIs will be stringified for serialization anyway, so there is no
|
|
// significant cost for converting the Uri to a string here.
|
|
return getChild('$uri');
|
|
}
|
|
|
|
CanonicalName getChildFromQualifiedName(Name name) {
|
|
return name.isPrivate
|
|
? getChildFromUri(name.library.importUri).getChild(name.name)
|
|
: getChild(name.name);
|
|
}
|
|
|
|
CanonicalName getChildFromMember(Member member) {
|
|
return getChild(getMemberQualifier(member))
|
|
.getChildFromQualifiedName(member.name);
|
|
}
|
|
|
|
CanonicalName getChildFromFieldWithName(Name name) {
|
|
return getChild('@fields').getChildFromQualifiedName(name);
|
|
}
|
|
|
|
CanonicalName getChildFromTypedef(Typedef typedef_) {
|
|
return getChild('@typedefs').getChild(typedef_.name);
|
|
}
|
|
|
|
/// Take ownership of a child canonical name and its subtree.
|
|
///
|
|
/// The child name is removed as a child of its current parent and this name
|
|
/// becomes the new parent. Note that this moves the entire subtree rooted at
|
|
/// the child.
|
|
///
|
|
/// This method can be used to move subtrees within a canonical name tree or
|
|
/// else move them between trees. It is safe to call this method if the child
|
|
/// name is already a child of this name.
|
|
///
|
|
/// The precondition is that this name cannot have a (different) child with
|
|
/// the same name.
|
|
void adoptChild(CanonicalName child) {
|
|
if (child._parent == this) return;
|
|
if (_children != null && _children.containsKey(child.name)) {
|
|
throw 'Cannot add a child to $this because this name already has a '
|
|
'child named ${child.name}';
|
|
}
|
|
child._parent.removeChild(child.name);
|
|
child._parent = this;
|
|
if (_children == null) _children = <String, CanonicalName>{};
|
|
_children[child.name] = child;
|
|
}
|
|
|
|
void removeChild(String name) {
|
|
_children?.remove(name);
|
|
}
|
|
|
|
void bindTo(Reference target) {
|
|
if (reference == target) return;
|
|
if (reference != null) {
|
|
throw '$this is already bound';
|
|
}
|
|
if (target.canonicalName != null) {
|
|
throw 'Cannot bind $this to ${target.node}, target is already bound to '
|
|
'${target.canonicalName}';
|
|
}
|
|
target.canonicalName = this;
|
|
this.reference = target;
|
|
}
|
|
|
|
void unbind() {
|
|
if (reference == null) return;
|
|
assert(reference.canonicalName == this);
|
|
reference.canonicalName = null;
|
|
reference = null;
|
|
}
|
|
|
|
void unbindAll() {
|
|
unbind();
|
|
Iterable<CanonicalName> children_ = childrenOrNull;
|
|
if (children_ != null) {
|
|
for (CanonicalName child in children_) {
|
|
child.unbindAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
String toString() => _parent == null ? 'root' : '$parent::$name';
|
|
|
|
Reference getReference() {
|
|
return reference ??= (new Reference()..canonicalName = this);
|
|
}
|
|
|
|
static String getMemberQualifier(Member member) {
|
|
if (member is Procedure) {
|
|
if (member.isGetter) return '@getters';
|
|
if (member.isSetter) return '@setters';
|
|
if (member.isFactory) return '@factories';
|
|
return '@methods';
|
|
}
|
|
if (member is Field) {
|
|
return '@fields';
|
|
}
|
|
if (member is Constructor) {
|
|
return '@constructors';
|
|
}
|
|
if (member is RedirectingFactoryConstructor) {
|
|
return '@factories';
|
|
}
|
|
throw 'Unexpected member: $member';
|
|
}
|
|
}
|