Commit 90028813 authored by Ryan Macnak's avatar Ryan Macnak Committed by GitHub

When parts of the program are changed in a hot reload, but not executed during...

When parts of the program are changed in a hot reload, but not executed during the reassemble, warn that a restart may be needed. (#12304)
parent dfa13a14
...@@ -339,6 +339,16 @@ class DevFS { ...@@ -339,6 +339,16 @@ class DevFS {
Uri _baseUri; Uri _baseUri;
Uri get baseUri => _baseUri; Uri get baseUri => _baseUri;
Uri deviceUriToHostUri(Uri deviceUri) {
final String deviceUriString = deviceUri.toString();
final String baseUriString = baseUri.toString();
if (deviceUriString.startsWith(baseUriString)) {
final String deviceUriSuffix = deviceUriString.substring(baseUriString.length);
return rootDirectory.uri.resolve(deviceUriSuffix);
}
return deviceUri;
}
Future<Uri> create() async { Future<Uri> create() async {
printTrace('DevFS: Creating new filesystem on the device ($_baseUri)'); printTrace('DevFS: Creating new filesystem on the device ($_baseUri)');
try { try {
......
...@@ -139,6 +139,26 @@ class FlutterDevice { ...@@ -139,6 +139,26 @@ class FlutterDevice {
return reports; return reports;
} }
// Lists program elements changed in the most recent reload that have not
// since executed.
Future<List<ProgramElement>> unusedChangesInLastReload() async {
final List<Future<List<ProgramElement>>> reports =
<Future<List<ProgramElement>>>[];
for (FlutterView view in views) {
reports.add(view.uiIsolate.getUnusedChangesInLastReload());
}
final List<ProgramElement> elements = <ProgramElement>[];
for (Future<List<ProgramElement>> report in reports) {
for (ProgramElement element in await report) {
elements.add(new ProgramElement(element.qualifiedName,
devFS.deviceUriToHostUri(element.uri),
element.line,
element.column));
}
}
return elements;
}
Future<Null> debugDumpApp() async { Future<Null> debugDumpApp() async {
for (FlutterView view in views) for (FlutterView view in views)
await view.uiIsolate.flutterDebugDumpApp(); await view.uiIsolate.flutterDebugDumpApp();
...@@ -805,14 +825,15 @@ abstract class ResidentRunner { ...@@ -805,14 +825,15 @@ abstract class ResidentRunner {
} }
class OperationResult { class OperationResult {
static final OperationResult ok = new OperationResult(0, ''); OperationResult(this.code, this.message, [this.hint]);
OperationResult(this.code, this.message);
final int code; final int code;
final String message; final String message;
final String hint;
bool get isOk => code == 0; bool get isOk => code == 0;
static final OperationResult ok = new OperationResult(0, '');
} }
/// Given the value of the --target option, return the path of the Dart file /// Given the value of the --target option, return the path of the Dart file
......
...@@ -426,6 +426,8 @@ class HotRunner extends ResidentRunner { ...@@ -426,6 +426,8 @@ class HotRunner extends ResidentRunner {
status.cancel(); status.cancel();
if (result.isOk) if (result.isOk)
printStatus("${result.message} in ${getElapsedAsMilliseconds(timer.elapsed)}."); printStatus("${result.message} in ${getElapsedAsMilliseconds(timer.elapsed)}.");
if (result.hint != null)
printStatus(result.hint);
return result; return result;
} catch (error) { } catch (error) {
status.cancel(); status.cancel();
...@@ -434,6 +436,14 @@ class HotRunner extends ResidentRunner { ...@@ -434,6 +436,14 @@ class HotRunner extends ResidentRunner {
} }
} }
String _uriToRelativePath(Uri uri) {
final String path = uri.toString();
final String base = new Uri.file(projectRootPath).toString();
if (path.startsWith(base))
return path.substring(base.length + 1);
return path;
}
Future<OperationResult> _reloadSources({ bool pause: false }) async { Future<OperationResult> _reloadSources({ bool pause: false }) async {
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
for (FlutterView view in device.views) { for (FlutterView view in device.views) {
...@@ -622,9 +632,41 @@ class HotRunner extends ResidentRunner { ...@@ -622,9 +632,41 @@ class HotRunner extends ResidentRunner {
!reassembleTimedOut && !reassembleTimedOut &&
shouldReportReloadTime) shouldReportReloadTime)
flutterUsage.sendTiming('hot', 'reload', reloadTimer.elapsed); flutterUsage.sendTiming('hot', 'reload', reloadTimer.elapsed);
String unusedElementMessage;
if (!reassembleAndScheduleErrors && !reassembleTimedOut) {
final List<Future<List<ProgramElement>>> unusedReports =
<Future<List<ProgramElement>>>[];
for (FlutterDevice device in flutterDevices)
unusedReports.add(device.unusedChangesInLastReload());
final List<ProgramElement> unusedElements = <ProgramElement>[];
for (Future<List<ProgramElement>> unusedReport in unusedReports)
unusedElements.addAll(await unusedReport);
if (unusedElements.isNotEmpty) {
unusedElementMessage =
'\nThe following program elements were changed by the reload, '
'but did not run when the view was reassembled. If this code '
'only runs at start-up, you will need to restart ("R") for '
'the changes to have an effect.';
for (ProgramElement unusedElement in unusedElements) {
final String name = unusedElement.qualifiedName;
final String path = _uriToRelativePath(unusedElement.uri);
final int line = unusedElement.line;
String elementDescription;
if (line == null) {
elementDescription = '$name ($path)';
} else {
elementDescription = '$name ($path:$line)';
}
unusedElementMessage += '\n - $elementDescription';
}
}
}
return new OperationResult( return new OperationResult(
reassembleAndScheduleErrors ? 1 : OperationResult.ok.code, reassembleAndScheduleErrors ? 1 : OperationResult.ok.code,
reloadMessage reloadMessage, unusedElementMessage
); );
} }
......
...@@ -897,6 +897,25 @@ class HeapSpace extends ServiceObject { ...@@ -897,6 +897,25 @@ class HeapSpace extends ServiceObject {
} }
} }
// A function, field or class along with its source location.
class ProgramElement {
ProgramElement(this.qualifiedName, this.uri, this.line, this.column);
final String qualifiedName;
final Uri uri;
final int line;
final int column;
@override
String toString() {
if (line == null) {
return '$qualifiedName ($uri)';
} else {
return '$qualifiedName ($uri:$line)';
}
}
}
/// An isolate running inside the VM. Instances of the Isolate class are always /// An isolate running inside the VM. Instances of the Isolate class are always
/// canonicalized. /// canonicalized.
class Isolate extends ServiceObjectOwner { class Isolate extends ServiceObjectOwner {
...@@ -1029,6 +1048,58 @@ class Isolate extends ServiceObjectOwner { ...@@ -1029,6 +1048,58 @@ class Isolate extends ServiceObjectOwner {
} }
} }
Future<Map<String, dynamic>> getObject(Map<String, dynamic> objectRef) {
return invokeRpcRaw('getObject',
params: <String, dynamic>{'objectId': objectRef['id']});
}
Future<ProgramElement> _describeElement(Map<String, dynamic> elementRef) async {
String name = elementRef['name'];
Map<String, dynamic> owner = elementRef['owner'];
while (owner != null) {
final String ownerType = owner['type'];
if (ownerType == 'Library' || ownerType == '@Library')
break;
final String ownerName = owner['name'];
name = "$ownerName.$name";
owner = owner['owner'];
}
final Map<String, dynamic> fullElement = await getObject(elementRef);
final Map<String, dynamic> location = fullElement['location'];
final int tokenPos = location['tokenPos'];
final Map<String, dynamic> script = await getObject(location['script']);
// The engine's tag handler doesn't seem to create proper URIs.
Uri uri = Uri.parse(script['uri']);
if (uri.scheme == '')
uri = uri.replace(scheme: 'file');
// See https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md
for (List<int> lineTuple in script['tokenPosTable']) {
final int line = lineTuple[0];
for (int i = 1; i < lineTuple.length; i += 2) {
if (lineTuple[i] == tokenPos) {
final int column = lineTuple[i + 1];
return new ProgramElement(name, uri, line, column);
}
}
}
return new ProgramElement(name, uri, null, null);
}
// Lists program elements changed in the most recent reload that have not
// since executed.
Future<List<ProgramElement>> getUnusedChangesInLastReload() async {
final Map<String, dynamic> response =
await invokeRpcRaw('_getUnusedChangesInLastReload');
final List<Future<ProgramElement>> unusedElements =
<Future<ProgramElement>>[];
for (Map<String, dynamic> element in response['unused'])
unusedElements.add(_describeElement(element));
return Future.wait(unusedElements);
}
/// Resumes the isolate. /// Resumes the isolate.
Future<Map<String, dynamic>> resume() { Future<Map<String, dynamic>> resume() {
return invokeRpcRaw('resume'); return invokeRpcRaw('resume');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment