[widget_cache] lookup framework Class on each run, expose isExtended method

Invalidations below the framework layer could lead to NPEs, since the framework class types will get
recreated. To ensure this doesn't happen, look them up each time.

Instead of checking the component for subtypes, use an isExtended API and the existing mixed in API to
get the same results. This should allow this check to work correctly with the new invalidation strategy.

Bug: https://github.com/dart-lang/sdk/issues/43760
Change-Id: I439c085540403e29083c74b2add642656b3fa0a3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/167441
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
jonahwilliams 2020-10-15 08:58:53 +00:00 committed by commit-bot@chromium.org
parent 16f7ce8e57
commit 1f3c76ccdd
2 changed files with 37 additions and 29 deletions

View file

@ -14,7 +14,7 @@ class WidgetCache {
WidgetCache(Component fullComponent) {
Library frameworkLibrary;
for (Library library in fullComponent.libraries) {
if (library?.importUri?.path == 'flutter/src/widgets/framework.dart') {
if (library?.importUri?.path == _frameworkLibrary) {
frameworkLibrary = library;
break;
}
@ -22,15 +22,7 @@ class WidgetCache {
if (frameworkLibrary == null) {
return;
}
for (Class classDeclaration in frameworkLibrary.classes) {
if (classDeclaration.name == _statelessWidgetClassName) {
_statelessWidget = classDeclaration;
} else if (classDeclaration.name == _statefulWidgetClassName) {
_statefulWidget = classDeclaration;
} else if (classDeclaration.name == _stateClassName) {
_state = classDeclaration;
}
}
_locatedClassDeclarations(frameworkLibrary);
_frameworkTypesLocated =
_statefulWidget != null && _state != null && _statelessWidget != null;
}
@ -44,6 +36,8 @@ class WidgetCache {
Class _statefulWidget;
bool _frameworkTypesLocated = false;
static const String _frameworkLibrary = 'flutter/src/widgets/framework.dart';
/// Mark [uri] as invalidated.
void invalidate(Uri uri) {
_invalidatedLibraries.add(uri);
@ -126,9 +120,19 @@ class WidgetCache {
return null;
}
// Update the class references to stateless, stateful, and state classes.
if (classHierarchy is ClosedWorldClassHierarchy) {
for (Library library in classHierarchy.knownLibraries) {
if (library?.importUri?.path == _frameworkLibrary) {
_locatedClassDeclarations(library);
}
}
}
if (classHierarchy.isSubclassOf(newClass, _statelessWidget) ||
classHierarchy.isSubclassOf(newClass, _statefulWidget)) {
if (_hasSubClasses(newClass, partialComponent, classHierarchy)) {
if (classHierarchy.isExtended(newClass) ||
classHierarchy.isUsedAsMixin(newClass)) {
return null;
}
return newClass.name;
@ -150,8 +154,8 @@ class WidgetCache {
if (statefulWidgetType.name == _statefulWidgetClassName) {
return null;
}
if (_hasSubClasses(
statefulWidgetType, partialComponent, classHierarchy)) {
if (classHierarchy.isExtended(statefulWidgetType) ||
classHierarchy.isUsedAsMixin(statefulWidgetType)) {
return null;
}
return statefulWidgetType.name;
@ -161,22 +165,6 @@ class WidgetCache {
return null;
}
/// Checks whether the class [node] has any subclasses.
bool _hasSubClasses(
Class node, Component component, ClassHierarchy classHierarchy) {
for (Library library in component.libraries) {
for (Class otherClass in library.classes) {
if (identical(otherClass, node)) {
continue;
}
if (classHierarchy.isSubclassOf(otherClass, node)) {
return true;
}
}
}
return false;
}
// Locate the that fully contains the edit range, or null.
Class _locateContainingClass(
Library library, int startOffset, int endOffset) {
@ -188,4 +176,16 @@ class WidgetCache {
}
return null;
}
void _locatedClassDeclarations(Library library) {
for (Class classDeclaration in library.classes) {
if (classDeclaration.name == _statelessWidgetClassName) {
_statelessWidget = classDeclaration;
} else if (classDeclaration.name == _statefulWidgetClassName) {
_statefulWidget = classDeclaration;
} else if (classDeclaration.name == _stateClassName) {
_state = classDeclaration;
}
}
}
}

View file

@ -164,6 +164,9 @@ abstract class ClassHierarchy implements ClassHierarchyBase {
/// mixin application (i.e. [Class.mixedInType]).
bool isUsedAsMixin(Class class_);
/// True if the given class is extended by another class using `extends`.
bool isExtended(Class class_);
/// Invokes [callback] for every member declared in or inherited by [class_]
/// that overrides or implements a member in a supertype of [class_]
/// (or in rare cases, overrides a member declared in [class_]).
@ -547,6 +550,11 @@ class ClosedWorldClassHierarchy implements ClassHierarchy {
return infoFor(class_).directMixers.isNotEmpty;
}
@override
bool isExtended(Class class_) {
return infoFor(class_).directExtenders.isNotEmpty;
}
List<_ClassInfo> _getRankedSuperclassInfos(_ClassInfo info) {
if (info.leastUpperBoundInfos != null) return info.leastUpperBoundInfos;
var heap = new _LubHeap()..add(info);