From 7c805c0a802e59537ae6eccb18b7bab61978cdb2 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Tue, 13 Feb 2018 12:33:23 -0800 Subject: [PATCH] Track widget creation locations. (#4529) * Track widget creation locations. --- .../track_widget_constructor_locations.dart | 573 ++++++++++++++++++ flutter_kernel_transformers/pubspec.yaml | 116 ++++ frontend_server/BUILD.gn | 3 + frontend_server/lib/server.dart | 38 +- frontend_server/pubspec.yaml | 3 + travis/analyze.sh | 15 + travis/licenses_golden/licenses_flutter | 35 +- 7 files changed, 762 insertions(+), 21 deletions(-) create mode 100644 flutter_kernel_transformers/lib/track_widget_constructor_locations.dart create mode 100644 flutter_kernel_transformers/pubspec.yaml diff --git a/flutter_kernel_transformers/lib/track_widget_constructor_locations.dart b/flutter_kernel_transformers/lib/track_widget_constructor_locations.dart new file mode 100644 index 0000000000..75bf2f6bc3 --- /dev/null +++ b/flutter_kernel_transformers/lib/track_widget_constructor_locations.dart @@ -0,0 +1,573 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +library track_widget_constructor_locations; + +// The kernel/src import below that requires lint `ignore_for_file` +// is a temporary state of things until kernel team builds better api that would +// replace api used below. This api was made private in an effort to discourage +// further use. +// ignore_for_file: implementation_imports +import 'package:kernel/ast.dart'; +import 'package:kernel/class_hierarchy.dart'; +import 'package:meta/meta.dart'; + +// Parameter name used to track were widget constructor calls were made from. +// +// The parameter name contains a randomly generate hex string to avoid collision +// with user generated parameters. +final String _creationLocationParameterName = + r'$creationLocationd_0dea112b090073317d4'; + +/// Name of private field added to the Widget class and any other classes that +/// implement Widget. +/// +/// Regardless of what library a class implementing Widget is defined in, the +/// private field will always be defined in the context of the widget_inspector +/// library ensuring no name conflicts with regular fields. +final String _locationFieldName = r'_location'; + +bool _hasNamedParameter(FunctionNode function, String name) { + return function.namedParameters + .any((VariableDeclaration parameter) => parameter.name == name); +} + +bool _hasNamedArgument(Arguments arguments, String argumentName) { + return arguments.named + .any((NamedExpression argument) => argument.name == argumentName); +} + +VariableDeclaration _getNamedParameter( + FunctionNode function, + String parameterName, +) { + for (VariableDeclaration parameter in function.namedParameters) { + if (parameter.name == parameterName) { + return parameter; + } + } + return null; +} + +// TODO(jacobr): find a solution that supports optional positional parameters. +/// Add the creation location to the arguments list if possible. +/// +/// Returns whether the creation location argument could be added. We cannot +/// currently add the named argument for functions with optional positional +/// parameters as the current scheme requires adding the creation location as a +/// named parameter. Fortunately that is not a significant issue in practice as +/// no Widget classes in package:flutter have optional positional parameters. +/// This code degrades gracefully for constructors with optional positional +/// parameters by skipping adding the creation location argument rather than +/// failing. +void _maybeAddCreationLocationArgument( + Arguments arguments, + FunctionNode function, + Expression creationLocation, + Class locationClass, +) { + if (_hasNamedArgument(arguments, _creationLocationParameterName)) { + return; + } + if (!_hasNamedParameter(function, _creationLocationParameterName)) { + return; + } + + final NamedExpression namedArgument = + new NamedExpression(_creationLocationParameterName, creationLocation); + namedArgument.parent = arguments; + arguments.named.add(namedArgument); +} + +/// Adds a named parameter to a function if the function does not already have +/// a named parameter with the name or optional positional parameters. +bool _maybeAddNamedParameter( + FunctionNode function, + VariableDeclaration variable, +) { + if (_hasNamedParameter(function, _creationLocationParameterName)) { + // Gracefully handle if this method is called on a function that has already + // been transformed. + return false; + } + // Function has optional positional parameters so cannot have optional named + // parameters. + if (function.requiredParameterCount != function.positionalParameters.length) { + return false; + } + variable.parent = function; + function.namedParameters.add(variable); + return true; +} + +/// Transformer that modifies all calls to Widget constructors to include +/// a [DebugLocation] parameter specifying the location where the constructor +/// call was made. +/// +/// This transformer requires that all Widget constructors have already been +/// transformed to have a named parameter with the name specified by +/// `_locationParameterName`. +class _WidgetCallSiteTransformer extends Transformer { + final ClassHierarchy _hierarchy; + + /// The [Widget] class defined in the `package:flutter` library. + /// + /// Used to perform instanceof checks to determine whether Dart constructor + /// calls are creating [Widget] objects. + Class _widgetClass; + + /// The [DebugLocation] class defined in the `package:flutter` library. + Class _locationClass; + + /// Current factory constructor that node being transformed is inside. + /// + /// Used to flow the location passed in as an argument to the factory to the + /// actual constructor call within the factory. + Procedure _currentFactory; + + _WidgetCallSiteTransformer( + this._hierarchy, { + @required Class widgetClass, + @required Class locationClass, + }) + : _widgetClass = widgetClass, + _locationClass = locationClass; + + /// Builds a call to the const constructor of the [DebugLocation] + /// object specifying the location where a constructor call was made and + /// optionally the locations for all parameters passed in. + /// + /// Specifying the parameters passed in is an experimental feature. With + /// access to the source code of an application you could determine the + /// locations of the parameters passed in from the source location of the + /// constructor call but it is convenient to bundle the location and names + /// of the parameters passed in so that tools can show parameter locations + /// without reparsing the source code. + ConstructorInvocation _constructLocation( + Location location, { + String name, + ListLiteral parameterLocations, + bool showFile: true, + }) { + final List arguments = [ + new NamedExpression('line', new IntLiteral(location.line)), + new NamedExpression('column', new IntLiteral(location.column)), + ]; + if (showFile) { + arguments.add(new NamedExpression( + 'file', new StringLiteral(location.file.toString()))); + } + if (name != null) { + arguments.add(new NamedExpression('name', new StringLiteral(name))); + } + if (parameterLocations != null) { + arguments + .add(new NamedExpression('parameterLocations', parameterLocations)); + } + return new ConstructorInvocation( + _locationClass.constructors.first, + new Arguments([], named: arguments), + isConst: true, + ); + } + + @override + Procedure visitProcedure(Procedure node) { + if (node.isFactory) { + _currentFactory = node; + node.transformChildren(this); + _currentFactory = null; + return node; + } + return defaultTreeNode(node); + } + + bool _isSubclassOfWidget(Class clazz) { + // TODO(jacobr): use hierarchy.isSubclassOf once we are using the + // non-deprecated ClassHierarchy constructor. + return _hierarchy.getClassAsInstanceOf(clazz, _widgetClass) != null; + } + + @override + StaticInvocation visitStaticInvocation(StaticInvocation node) { + node.transformChildren(this); + final Procedure target = node.target; + if (!target.isFactory) { + return node; + } + final Class constructedClass = target.enclosingClass; + if (!_isSubclassOfWidget(constructedClass)) { + return node; + } + + _addLocationArgument(node, target.function, constructedClass); + return node; + } + + void _addLocationArgument(InvocationExpression node, FunctionNode function, + Class constructedClass) { + _maybeAddCreationLocationArgument( + node.arguments, + function, + _computeLocation(node, function, constructedClass), + _locationClass, + ); + } + + @override + ConstructorInvocation visitConstructorInvocation(ConstructorInvocation node) { + node.transformChildren(this); + + final Constructor constructor = node.target; + final Class constructedClass = constructor.enclosingClass; + if (!_isSubclassOfWidget(constructedClass)) { + return node; + } + + _addLocationArgument(node, constructor.function, constructedClass); + return node; + } + + Expression _computeLocation(InvocationExpression node, FunctionNode function, + Class constructedClass) { + // For factory constructors we need to use the location specified as an + // argument to the factory constructor rather than the location + // TODO(jacobr): use hierarchy.isSubclassOf once we are using the + // non-deprecated ClassHierarchy constructor. + if (_currentFactory != null && + _hierarchy.getClassAsInstanceOf(constructedClass, _currentFactory.enclosingClass) != null) { + final VariableDeclaration creationLocationParameter = _getNamedParameter( + _currentFactory.function, + _creationLocationParameterName, + ); + if (creationLocationParameter != null) { + return new VariableGet(creationLocationParameter); + } + } + + final Arguments arguments = node.arguments; + final Location location = node.location; + final List parameterLocations = + []; + final List parameters = function.positionalParameters; + for (int i = 0; i < arguments.positional.length; ++i) { + final Expression expression = arguments.positional[i]; + final VariableDeclaration parameter = parameters[i]; + parameterLocations.add(_constructLocation( + expression.location, + name: parameter.name, + showFile: false, + )); + } + for (NamedExpression expression in arguments.named) { + parameterLocations.add(_constructLocation( + expression.location, + name: expression.name, + showFile: false, + )); + } + return _constructLocation( + location, + parameterLocations: new ListLiteral( + parameterLocations, + typeArgument: _locationClass.thisType, + isConst: true, + ), + ); + } +} + +/// Rewrites all widget constructors and constructor invocations to add a +/// parameter specifying the location the constructor was called from. +/// +/// The creation location is stored as a private field named `_location` +/// on the base widget class and flowed through the constructors using a named +/// parameter. +class WidgetCreatorTracker { + Class _widgetClass; + Class _locationClass; + + /// Marker interface indicating that a private _location field is + /// available. + Class _hasCreationLocationClass; + + /// The [ClassHierarchy] that should be used after applying this transformer. + /// If any class was updated, in general we need to create a new + /// [ClassHierarchy] instance, with new dispatch targets; or at least let + /// the existing instance know that some of its dispatch tables are not + /// valid anymore. + ClassHierarchy hierarchy; + + void _resolveFlutterClasses(Iterable libraries) { + // If the Widget or Debug location classes have been updated we need to get + // the latest version + for (Library library in libraries) { + final Uri importUri = library.importUri; + if (!library.isExternal && + importUri != null && + importUri.scheme == 'package') { + if (importUri.path == 'flutter/src/widgets/framework.dart') { + for (Class class_ in library.classes) { + if (class_.name == 'Widget') { + _widgetClass = class_; + } + } + } else { + if (importUri.path == 'flutter/src/widgets/widget_inspector.dart') { + for (Class class_ in library.classes) { + if (class_.name == '_HasCreationLocation') { + _hasCreationLocationClass = class_; + } else if (class_.name == '_Location') { + _locationClass = class_; + } + } + } + } + } + } + } + + /// Modify [clazz] to add a field named [_locationFieldName] that is the + /// first parameter of all constructors of the class. + /// + /// This method should only be called for classes that implement but do not + /// extend [Widget]. + void _transformClassImplementingWidget(Class clazz) { + if (clazz.fields + .any((Field field) => field.name.name == _locationFieldName)) { + // This class has already been transformed. Skip + return; + } + clazz.implementedTypes + .add(new Supertype(_hasCreationLocationClass, [])); + // We intentionally use the library context of the _HasCreationLocation + // class for the private field even if [clazz] is in a different library + // so that all classes implementing Widget behave consistently. + final Field locationField = new Field( + new Name( + _locationFieldName, + _hasCreationLocationClass.enclosingLibrary, + ), + isFinal: true, + ); + clazz.addMember(locationField); + + final Set _handledConstructors = + new Set.identity(); + + void handleConstructor(Constructor constructor) { + if (!_handledConstructors.add(constructor)) { + return; + } + assert(!_hasNamedParameter( + constructor.function, + _creationLocationParameterName, + )); + final VariableDeclaration variable = new VariableDeclaration( + _creationLocationParameterName, + type: _locationClass.thisType, + ); + if (!_maybeAddNamedParameter(constructor.function, variable)) { + return; + } + + bool hasRedirectingInitializer = false; + for (Initializer initializer in constructor.initializers) { + if (initializer is RedirectingInitializer) { + if (initializer.target.enclosingClass == clazz) { + // We need to handle this constructor first or the call to + // addDebugLocationArgument bellow will fail due to the named + // parameter not yet existing on the constructor. + handleConstructor(initializer.target); + } + _maybeAddCreationLocationArgument( + initializer.arguments, + initializer.target.function, + new VariableGet(variable), + _locationClass, + ); + hasRedirectingInitializer = true; + break; + } + } + if (!hasRedirectingInitializer) { + constructor.initializers.add(new FieldInitializer( + locationField, + new VariableGet(variable), + )); + // TODO(jacobr): add an assert verifying the locationField is not + // null. Curently we cannot safely add this assert because we do not + // handle Widget classes with optional positional arguments. There are + // no Widget classes in the flutter repo with optional positional + // arguments but it is possible users could add classes with optional + // positional arguments. + // + // constructor.initializers.add(new AssertInitializer(new AssertStatement( + // new IsExpression( + // new VariableGet(variable), _locationClass.thisType), + // conditionStartOffset: constructor.fileOffset, + // conditionEndOffset: constructor.fileOffset, + // ))); + } + } + + // Add named parameters to all constructors. + clazz.constructors.forEach(handleConstructor); + hierarchy.applyChanges([clazz]); + } + + /// Transform the given [program]. + /// + /// It is safe to call this method on a delta program generated as part of + /// performing a hot reload. + void transform(Program program) { + final List libraries = program.libraries; + // TODO(jacobr): switch to the non-deprecated ClassHierarchy constructor + // once https://github.com/dart-lang/sdk/issues/32079 is fixed. + // ignore: deprecated_member_use + hierarchy = new ClassHierarchy.deprecated_incremental(program); + + if (libraries.isEmpty) { + return; + } + + _resolveFlutterClasses(libraries); + + if (_widgetClass == null) { + // This application doesn't actually use the package:flutter library. + return; + } + + final Set transformedClasses = new Set.identity(); + final Set librariesToTransform = new Set.identity() + ..addAll(libraries); + + for (Library library in libraries) { + if (library.isExternal) { + continue; + } + for (Class class_ in library.classes) { + _transformWidgetConstructors( + librariesToTransform, + transformedClasses, + class_, + ); + } + } + // Given the hierarchy we are using and the transforms we are applying, + // calling this method probably isn't really necessary. + hierarchy.applyChanges(transformedClasses); + + // Transform call sites to pass the location parameter. + final _WidgetCallSiteTransformer callsiteTransformer = + new _WidgetCallSiteTransformer( + hierarchy, + widgetClass: _widgetClass, + locationClass: _locationClass, + ); + + for (Library library in libraries) { + if (library.isExternal) { + continue; + } + library.transformChildren(callsiteTransformer); + } + } + + bool _isSubclassOfWidget(Class clazz) { + if (clazz == null) { + return false; + } + // TODO(jacobr): use hierarchy.isSubclassOf once we are using the + // non-deprecated ClassHierarchy constructor. + return hierarchy.getClassAsInstanceOf(clazz, _widgetClass) != null; + } + + void _transformWidgetConstructors(Set librariesToBeTransformed, + Set transformedClasses, Class clazz) { + if (!_isSubclassOfWidget(clazz) || + !librariesToBeTransformed.contains(clazz.enclosingLibrary) || + !transformedClasses.add(clazz)) { + return; + } + + // Ensure super classes have been transformed before this class. + if (clazz.superclass != null && + !transformedClasses.contains(clazz.superclass)) { + _transformWidgetConstructors( + librariesToBeTransformed, + transformedClasses, + clazz.superclass, + ); + } + + for (Procedure procedure in clazz.procedures) { + if (procedure.isFactory) { + _maybeAddNamedParameter( + procedure.function, + new VariableDeclaration( + _creationLocationParameterName, + type: _locationClass.thisType, + ), + ); + } + } + + // Handle the widget class and classes that implement but do not extend the + // widget class. + if (!_isSubclassOfWidget(clazz.superclass)) { + _transformClassImplementingWidget(clazz); + return; + } + + final Set _handledConstructors = + new Set.identity(); + + void handleConstructor(Constructor constructor) { + if (!_handledConstructors.add(constructor)) { + return; + } + + final VariableDeclaration variable = new VariableDeclaration( + _creationLocationParameterName, + type: _locationClass.thisType, + ); + if (_hasNamedParameter( + constructor.function, _creationLocationParameterName)) { + // Constructor was already rewritten. TODO(jacobr): is this case actually hit? + return; + } + if (!_maybeAddNamedParameter(constructor.function, variable)) { + return; + } + for (Initializer initializer in constructor.initializers) { + if (initializer is RedirectingInitializer) { + if (initializer.target.enclosingClass == clazz) { + // We need to handle this constructor first or the call to + // addDebugLocationArgument could fail due to the named parameter + // not existing. + handleConstructor(initializer.target); + } + + _maybeAddCreationLocationArgument( + initializer.arguments, + initializer.target.function, + new VariableGet(variable), + _locationClass, + ); + } else if (initializer is SuperInitializer && + _isSubclassOfWidget(initializer.target.enclosingClass)) { + _maybeAddCreationLocationArgument( + initializer.arguments, + initializer.target.function, + new VariableGet(variable), + _locationClass, + ); + } + } + } + + clazz.constructors.forEach(handleConstructor); + } +} diff --git a/flutter_kernel_transformers/pubspec.yaml b/flutter_kernel_transformers/pubspec.yaml new file mode 100644 index 0000000000..87d617b7af --- /dev/null +++ b/flutter_kernel_transformers/pubspec.yaml @@ -0,0 +1,116 @@ +name: flutter_kernel_transformers +version: 0.0.1-dev +description: Kernel transformers that operate on Flutter source code. +homepage: http://flutter.io +author: Flutter Authors + +dependencies: + kernel: any + meta: any + +dev_dependencies: + test: any + when: any + +dependency_overrides: + args: + path: ../../third_party/dart/third_party/pkg/args + async: + path: ../../third_party/dart/third_party/pkg/async + charcode: + path: ../../third_party/dart/third_party/pkg/charcode + collection: + path: ../../third_party/dart/third_party/pkg/collection + convert: + path: ../../third_party/dart/third_party/pkg/convert + crypto: + path: ../../third_party/dart/third_party/pkg/crypto + front_end: + path: ../../third_party/dart/pkg/front_end/ + kernel: + path: ../../third_party/dart/pkg/kernel/ + logging: + path: ../../third_party/dart/third_party/pkg/logging + meta: + path: ../../third_party/dart/pkg/meta + quiver: + path: ../../third_party/dart/third_party/pkg/quiver + package_config: + path: ../../third_party/dart/third_party/pkg_tested/package_config + path: + path: ../../third_party/dart/third_party/pkg/path + source_span: + path: ../../third_party/dart/third_party/pkg/source_span + typed_data: + path: ../../third_party/dart/third_party/pkg/typed_data + usage: + path: ../../third_party/dart/third_party/pkg/usage + vm: + path: ../../third_party/dart/pkg/vm/ + + analyzer: + path: ../../third_party/dart/pkg/analyzer + barback: + path: ../../third_party/dart/third_party/pkg/barback + boolean_selector: + path: ../../third_party/dart/third_party/pkg/boolean_selector + cli_util: + path: ../../third_party/dart/third_party/pkg/cli_util + csslib: + path: ../../third_party/dart/third_party/pkg/csslib + glob: + path: ../../third_party/dart/third_party/pkg/glob + html: + path: ../../third_party/dart/third_party/pkg/html + http: + path: ../../third_party/dart/third_party/pkg/http + http_multi_server: + path: ../../third_party/dart/third_party/pkg/http_multi_server + http_parser: + path: ../../third_party/dart/third_party/pkg/http_parser + isolate: + path: ../../third_party/dart/third_party/pkg/isolate + matcher: + path: ../../third_party/dart/third_party/pkg/matcher + mime: + path: ../../third_party/dart/third_party/pkg/mime + mockito: + path: ../../third_party/dart/third_party/pkg/mockito + package_resolver: + path: ../../third_party/dart/third_party/pkg_tested/package_resolver + plugin: + path: ../../third_party/dart/third_party/pkg/plugin + pool: + path: ../../third_party/dart/third_party/pkg/pool + pub_semver: + path: ../../third_party/dart/third_party/pkg/pub_semver + shelf: + path: ../../third_party/dart/third_party/pkg/shelf + shelf_packages_handler: + path: ../../third_party/dart/third_party/pkg/shelf_packages_handler + shelf_static: + path: ../../third_party/dart/third_party/pkg/shelf_static + shelf_web_socket: + path: ../../third_party/dart/third_party/pkg/shelf_web_socket + source_map_stack_trace: + path: ../../third_party/dart/third_party/pkg/source_map_stack_trace + source_maps: + path: ../../third_party/dart/third_party/pkg/source_maps + stack_trace: + path: ../../third_party/dart/third_party/pkg/stack_trace + stream_channel: + path: ../../third_party/dart/third_party/pkg/stream_channel + string_scanner: + path: ../../third_party/dart/third_party/pkg/string_scanner + test: + path: ../../third_party/dart/third_party/pkg/test + utf: + path: ../../third_party/dart/third_party/pkg/utf + watcher: + path: ../../third_party/dart/third_party/pkg/watcher + web_socket_channel: + path: ../../third_party/dart/third_party/pkg/web_socket_channel + when: + path: ../../third_party/pkg/when + yaml: + path: ../../third_party/dart/third_party/pkg/yaml diff --git a/frontend_server/BUILD.gn b/frontend_server/BUILD.gn index 5e2aea6efd..b792d2bce3 100644 --- a/frontend_server/BUILD.gn +++ b/frontend_server/BUILD.gn @@ -16,6 +16,9 @@ application_snapshot("frontend_server") { frontend_server_files = exec_script("//third_party/dart/tools/list_dart_files.py", [ "absolute", rebase_path("."), ], "list lines") + frontend_server_files += exec_script("//third_party/dart/tools/list_dart_files.py", + [ "absolute", rebase_path("../flutter_kernel_transformers"), ], "list lines") + frontend_server_files += exec_script("//third_party/dart/tools/list_dart_files.py", [ "absolute", rebase_path("../../third_party/dart/pkg"), ], "list lines") diff --git a/frontend_server/lib/server.dart b/frontend_server/lib/server.dart index 1928e5151e..f3896f58fc 100644 --- a/frontend_server/lib/server.dart +++ b/frontend_server/lib/server.dart @@ -1,3 +1,7 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + library frontend_server; import 'dart:async'; @@ -22,6 +26,8 @@ import 'package:usage/uuid/uuid.dart'; import 'package:vm/incremental_compiler.dart' show IncrementalCompiler; import 'package:vm/kernel_front_end.dart' show compileToKernel; +import 'package:flutter_kernel_transformers/track_widget_constructor_locations.dart'; + ArgParser _argParser = new ArgParser(allowTrailingOptions: true) ..addFlag('train', help: 'Run through sample command line to produce snapshot', @@ -46,7 +52,10 @@ ArgParser _argParser = new ArgParser(allowTrailingOptions: true) defaultsTo: true) ..addOption('packages', help: '.packages file to use for compilation', - defaultsTo: null); + defaultsTo: null) + ..addFlag('track-widget-creation', + help: 'Run a kernel transformer to track creation locations for widgets.', + defaultsTo: false); String _usage = ''' Usage: server [options] [input.dart] @@ -115,9 +124,12 @@ class BinaryPrinterFactory { } class _FrontendCompiler implements CompilerInterface { - _FrontendCompiler(this._outputStream, {this.printerFactory}) { + _FrontendCompiler(this._outputStream, {this.printerFactory, this.trackWidgetCreation}) { _outputStream ??= stdout; printerFactory ??= new BinaryPrinterFactory(); + if (trackWidgetCreation) { + widgetCreatorTracker = new WidgetCreatorTracker(); + } } StringSink _outputStream; @@ -130,6 +142,8 @@ class _FrontendCompiler implements CompilerInterface { String _kernelBinaryFilename; String _kernelBinaryFilenameIncremental; String _kernelBinaryFilenameFull; + final bool trackWidgetCreation; + WidgetCreatorTracker widgetCreatorTracker; @override Future compile( @@ -171,6 +185,7 @@ class _FrontendCompiler implements CompilerInterface { program = await _runWithPrintRedirection(() => compileToKernel(filenameUri, compilerOptions, aot: options['aot'])); } + runFlutterSpecificKernelTransforms(program); if (program != null) { final IOSink sink = new File(_kernelBinaryFilename).openWrite(); final BinaryPrinter printer = printerFactory.newBinaryPrinter(sink); @@ -223,12 +238,23 @@ class _FrontendCompiler implements CompilerInterface { } } + void runFlutterSpecificKernelTransforms(Program program) { + if (program == null) { + return; + } + + if (trackWidgetCreation) { + widgetCreatorTracker.transform(program); + } + } + @override Future recompileDelta() async { final String boundaryKey = new Uuid().generateV4(); _outputStream.writeln('result $boundaryKey'); await invalidateIfBootstrapping(); final Program deltaProgram = await _generator.compile(); + runFlutterSpecificKernelTransforms(deltaProgram); final IOSink sink = new File(_kernelBinaryFilename).openWrite(); final BinaryPrinter printer = printerFactory.newBinaryPrinter(sink); printer.writeProgramFile(deltaProgram); @@ -267,6 +293,7 @@ class _FrontendCompiler implements CompilerInterface { return Uri.base.resolve(uriPath); } + /// Runs the given function [f] in a Zone that redirects all prints into /// [_outputStream]. Future _runWithPrintRedirection(Future f()) { @@ -314,8 +341,11 @@ Future starter( return 0; } - compiler ??= - new _FrontendCompiler(output, printerFactory: binaryPrinterFactory); + compiler ??= new _FrontendCompiler( + output, + printerFactory: binaryPrinterFactory, + trackWidgetCreation: options['track-widget-creation'], + ); input ??= stdin; // Has to be a directory, that won't have any of the compiled application diff --git a/frontend_server/pubspec.yaml b/frontend_server/pubspec.yaml index 70f15896ec..f7b9ee1746 100644 --- a/frontend_server/pubspec.yaml +++ b/frontend_server/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: convert: any crypto: any front_end: any + flutter_kernel_transformers: any kernel: any logging: any meta: any @@ -71,6 +72,8 @@ dependency_overrides: path: ../../third_party/dart/third_party/pkg/convert crypto: path: ../../third_party/dart/third_party/pkg/crypto + flutter_kernel_transformers: + path: ../flutter_kernel_transformers front_end: path: ../../third_party/dart/pkg/front_end/ kernel: diff --git a/travis/analyze.sh b/travis/analyze.sh index 70f12aa638..c5ed9de81c 100755 --- a/travis/analyze.sh +++ b/travis/analyze.sh @@ -32,3 +32,18 @@ if [ -n "$RESULTS" ]; then echo "Failed." exit 1; fi + +echo "Analyzing flutter_kernel_transformers..." +pushd flutter/flutter_kernel_transformers/; pub get; popd +RESULTS=`dartanalyzer \ + --packages=flutter/flutter_kernel_transformers/.packages \ + --options flutter/analysis_options.yaml \ + flutter/flutter_kernel_transformers \ + 2>&1 \ + | grep -Ev "No issues found!" \ + | grep -Ev "Analyzing.+flutter_kernel_transformers"` +echo "$RESULTS" +if [ -n "$RESULTS" ]; then + echo "Failed." + exit 1; +fi diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index 72303bf31d..effa3f5a91 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -1881,12 +1881,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: engine -ORIGIN: ../../../flutter/fml/platform/darwin/scoped_block.h + ../../../LICENSE +ORIGIN: ../../../flutter/flutter_kernel_transformers/lib/track_widget_constructor_locations.dart + ../../../LICENSE TYPE: LicenseType.bsd -FILE: ../../../flutter/fml/platform/darwin/scoped_block.h -FILE: ../../../flutter/sky/engine/wtf/allocator/PartitionAllocator.h +FILE: ../../../flutter/flutter_kernel_transformers/lib/track_widget_constructor_locations.dart +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm ---------------------------------------------------------------------------------------------------- -Copyright (c) 2013 The Chromium Authors. All rights reserved. +Copyright 2018 The Chromium Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -1917,17 +1918,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: engine -ORIGIN: ../../../flutter/lib/ui/painting/image.cc + ../../../LICENSE +ORIGIN: ../../../flutter/fml/platform/darwin/scoped_block.h + ../../../LICENSE TYPE: LicenseType.bsd -FILE: ../../../flutter/lib/ui/painting/image.cc -FILE: ../../../flutter/lib/ui/painting/image.h -FILE: ../../../flutter/shell/common/switches.cc -FILE: ../../../flutter/shell/common/switches.h -FILE: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java -FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterMain.java -FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterView.java +FILE: ../../../flutter/fml/platform/darwin/scoped_block.h +FILE: ../../../flutter/sky/engine/wtf/allocator/PartitionAllocator.h ---------------------------------------------------------------------------------------------------- -Copyright 2013 The Chromium Authors. All rights reserved. +Copyright (c) 2013 The Chromium Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -1958,12 +1954,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: engine -ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h + ../../../LICENSE +ORIGIN: ../../../flutter/lib/ui/painting/image.cc + ../../../LICENSE TYPE: LicenseType.bsd -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm +FILE: ../../../flutter/lib/ui/painting/image.cc +FILE: ../../../flutter/lib/ui/painting/image.h +FILE: ../../../flutter/shell/common/switches.cc +FILE: ../../../flutter/shell/common/switches.h +FILE: ../../../flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java +FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterMain.java +FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterView.java ---------------------------------------------------------------------------------------------------- -Copyright 2018 The Chromium Authors. All rights reserved. +Copyright 2013 The Chromium Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are -- GitLab