Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow runtime syntax errors to be caught #23684

Closed
nex3 opened this issue Jun 19, 2015 · 33 comments
Closed

Allow runtime syntax errors to be caught #23684

nex3 opened this issue Jun 19, 2015 · 33 comments
Assignees
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. P1 A high priority bug; for example, a single project is unusable or has many test failures type-enhancement A request for a change that isn't a bug
Milestone

Comments

@nex3
Copy link
Member

nex3 commented Jun 19, 2015

Currently the following code throws an uncatchable syntax error:

import 'dart:async';

void badFn() {continue;}

void main() {
  runZoned(() => badFn(), onError: (error, stackTrace) {
    print("caught error");
  });
}

This doesn't print "caught error", but instead kills the isolate due to the error. However, the error is only thrown at runtime, as can be seen with this modification:

void badFn() {continue;}

void main(List<String> args) {
  if (args.isNotEmpty) badFn();
}

This only throws a syntax error if an argument is passed on the command line. Moreover, the error is recoverable; for example:

// inner.dart
import 'dart:async';

void badFn() {continue;}

void main(List<String> args) {
  new Future(() => badFn());
  badFn();
}
// outer.dart
import 'dart:isolate';

main() async {
  var isolate = await Isolate.spawnUri(
      Uri.parse("inner.dart"), [], null, paused: true);
  isolate.setErrorsFatal(false);

  var receivePort = new ReceivePort();
  isolate.addErrorListener(receivePort.sendPort);
  isolate.resume(isolate.pauseCapability);

  receivePort.listen((error) => print(error.first));
}

Running outer.dart will produce two errors, so clearly there's a way to keep the isolate running and even keep badFn callable despite this syntax error.


This is important for use cases like the test runner, where user code is loaded and specific functions are run. Error reporting should be associated with the functions that cause the errors, but they can only be captured globally and there's no way to link them back to the actual function that failed.

This also means that the user can't be confident that running an arbitrary function in an error zone will capture any errors that function might produce. This is useful even when not loading code dynamically; for example, an application may want to have a global error handler for itself that shows an error-reporting dialog.

@nex3 nex3 added the area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. label Jun 19, 2015
@kevmoo
Copy link
Member

kevmoo commented Jun 22, 2015

@sgjesse @lrhn Thoughts on this? Straight-forward?

@lrhn
Copy link
Member

lrhn commented Jun 23, 2015

@iposva-google

It's the VM that decides what to do with a syntax error. I believe it currently makes it a top-level error and drops the current stack - it doesn't throw an error as such.

Setting errors non-fatal and listening on isolate errors does work to see the errors, but it doesn't mean that recovery is possible. It's also problematic to drop the stack without executing finally blocks. That wasn't a problem before errors could be made non-fatal, but now it's visible.

Example:

// inner.dart
import 'dart:async';
import 'dart:isolate';

void badFn() {continue;}

void main() {
  Isolate.current.setErrorsFatal(false);
  RawReceivePort p = new RawReceivePort(print);
  Isolate.current.addErrorListener(p.sendPort);
  Timer.run(() {  // Wait for the isolate calls to be handled.
    new Future(() => badFn());
    try {
      print("1");  // Printed
      badFn();
    } finally {
      print("2");  // Not printed!
    }
  });
}

In general it's not nice to have a try/finally execute the try and not the finally when the isolate stays alive. It may cause logical problems if a mutex is entered but not released, or similar problems.

That also makes me expect that the first syntax error will rewind right through the microtask queue code and stop executing microtasks until the next event loop event. An uncaught error in a microtask will reach the event loop, but a finally block on the way reschedules the microtask loop if there are more pending microtasks. If finally blocks are ignored, that fails too.

It may be a prettier solution to throw a SyntaxError from the call-point, just as we do for other "warn-at-compile/fail-at-runtime" errors, but that's a design decision for the VM people. The spec is silent on how a syntax error must be reported.

@munificent
Copy link
Member

cc @asadovsky

@mhausner
Copy link
Contributor

mhausner commented Feb 3, 2016

Reporting syntax errors in code that isn't executed is contrary to what the goals for Dart have been since the beginning. The stated goal was to be as forgiving as possible when running code, so that fast edit-execute cycles support rapid prototyping. The language spec reflects that, by making many errors only fire at runtime, rather than at compile time, even if they are detected by the compiler.

@mhausner mhausner closed this as completed Feb 3, 2016
@kevmoo
Copy link
Member

kevmoo commented Feb 3, 2016

"fire at runtime" is totally fine – we just want at way to know that an isolate failed BECAUSE of bad syntax with the related info.

@kevmoo kevmoo reopened this Feb 3, 2016
@nex3
Copy link
Member Author

nex3 commented Feb 3, 2016

To be clear, my ideal solution here would be: if a function with a syntax error is executed at runtime, it throws an exception (maybe a new SyntaxError type?) that works just like a normal exception.

@lrhn
Copy link
Member

lrhn commented Feb 7, 2016

I agree with Natalie - if an Isolate starts running, it should keep running and should not be torn down in the middle of executing because it calls a function that has a previously undiscovered syntax error.
Not starting at all is ok - that's what grievous syntax errors does anyway (like not being a Dart file at all). Throwing an error is also fine, that's what malformed types or undefined variables do. Letting some other syntax errors do the same is fine with me.

@kevmoo kevmoo added closed-not-planned Closed as we don't intend to take action on the reported issue type-enhancement A request for a change that isn't a bug and removed triaged labels Mar 1, 2016
@goderbauer
Copy link
Contributor

Is there any update on this? We are currently running into the same issue with our continuous test runner for dart. If the test scheduled to run contains a syntax error, there is no way to detect that.

@a-siva a-siva added this to the 1.18 milestone May 31, 2016
@sanfordredlich
Copy link

Update just to say that this is important for browser-based tests. Right now we use timeouts, which is very brittle and not guaranteed to work well in every case.

@floitschG
Copy link
Contributor

@sanfordredlich browser programs (once compiled to JavaScript) should not exhibit that problem. Obviously, if you are running your tests in Dartium, then you will be affected by this too.

@sanfordredlich
Copy link

Thanks, yes, our tests will run in Dartium until Q4 or beyond.

@kevmoo
Copy link
Member

kevmoo commented Jun 6, 2016

If I'm reading this right, the concern is that the compile-time behavior/error emitted by dart2js is different than the new runtime behavior of the VM?

I consider this very much NOT an issue – we are introducing a very useful, but very VM-specific behavior IMHO.

We want to keep a very VM-specific feature – lazy parsing – which is very different than what we do in dart2js. We just want it to do something more helpful than it does now.

@a-siva
Copy link
Contributor

a-siva commented Jun 6, 2016

There are a slew of co19 tests which fail when we enable this feature because the co19 tests have a catch block which does nothing, yet the tests are expected to fail with a compilation error.

One could say that the co19 test status files could be patched and the failing tests could be ignored, that is not a right thing to do. These tests need to be modified so that they continue to test the compilation errors that they were written to test. I would think a consistent behavior of the tools (dart2js and the VM runtime) is necessary in order for the co19 folks to get this right, otherwise you end up with one set of tests written explicitly for the way dart2js works and another for the way the runtime VM works making it more complex.

@goderbauer
Copy link
Contributor

I would also like to be able to catch syntax errors in Dartium. One thing I don't understand in the discussion on this issue: We can already catch syntax errors in the stand-alone Dart VM outside of Dartium reasonably well (package:test demonstrates that here for example). Why can the same functionality not be brought to Dartium? Personally, I find this difference between executing dart code in the stand-alone dart vm and executing dart code in Dartium very strange.

@a-siva
Copy link
Contributor

a-siva commented Jun 7, 2016

Dartium implements something called DOM Isolates which are different from the stand-alone VM isolates and I don't believe these isolates were wired to implement the new Isolate API (ability to add an error listener) and treat errors as non Fatal which your test uses. Maybe @terrylucas can comment on whether this could be done in Dartium if that is all you need.

@goderbauer
Copy link
Contributor

Any update on this?

The number 1 complained from the users of our continuous web test runner is that the runner cannot recover from syntax errors due to this issue. We simply cannot detect that there are syntax errors in the test file we are trying to run in Dartium.

@kevmoo
Copy link
Member

kevmoo commented Jul 6, 2016

We've agreed to do this. We're discussing implementation details at the moment. Stay tuned...

@kevmoo kevmoo modified the milestones: 1.19, 1.18 Jul 6, 2016
@kevmoo
Copy link
Member

kevmoo commented Jul 19, 2016

@goderbauer
Copy link
Contributor

Exciting. Thanks for working on it.

@a-siva
Copy link
Contributor

a-siva commented Jul 28, 2016

This issue is blocked on dart-lang/co19#69

@a-siva a-siva added the status-blocked Blocked from making progress by another (referenced) issue label Jul 28, 2016
@kevmoo kevmoo added the P1 A high priority bug; for example, a single project is unusable or has many test failures label Jul 28, 2016
@kevmoo
Copy link
Member

kevmoo commented Aug 9, 2016

Trying to figure out what the next steps are with dart-lang/co19#69 so we can get this landed...

@mit-mit mit-mit modified the milestones: 1.20, 1.19 Aug 10, 2016
@mit-mit
Copy link
Member

mit-mit commented Aug 10, 2016

Bumping this to 1.20, while we are close we are not quite there

@mit-mit
Copy link
Member

mit-mit commented Aug 25, 2016

dart-lang/co19#69 landed! @mhausner anything else you need to be unblocked?

@mit-mit mit-mit removed the status-blocked Blocked from making progress by another (referenced) issue label Aug 25, 2016
@mhausner
Copy link
Contributor

The co19 changes must be integrated into the sdk repo, if they aren't already.

@mit-mit
Copy link
Member

mit-mit commented Aug 29, 2016

@whesse did you look at doing that integration?

@whesse
Copy link
Contributor

whesse commented Aug 29, 2016

That is the co19 roll I did today and rolled back. With the status updates from this commit, I can land it permanently tomorrow.

@kevmoo
Copy link
Member

kevmoo commented Sep 14, 2016

mhausner added a commit that referenced this issue Sep 16, 2016
If an error happens during the compilation of a function body, an Error is thrown which can be intercepted, and ensures that finally blocks are executed before the isolate is terminated.

The language spec is vague about compilation errors. A doc describing the intentions behind this CL is at
https://docs.google.com/document/d/1_MWOgwJadLCQSBps0zD6Rj5dG4iP1UBuiDvTMH-WMzI/edit#

Example:
     1	void bad() {
     2	    return 5
     3	}
     4
     5	void main(args) {
     6	    bad();
     7	}

Before this CL:
$ dart ~/tmp/e.dart
'file:///Users/hausner/tmp/e.dart': error: line 2 pos 11: semicolon expected
  return 5
          ^
$

After this change:
$ dart ~/tmp/e.dart
Unhandled exception:
'file:///Users/hausner/tmp/e.dart': error: line 2 pos 11: semicolon expected
  return 5
          ^

#0      main (file:///Users/hausner/tmp/e.dart:6:3)
#1      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:259)
#2      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:148)
$

Notice that the stack trace points to the call site of bad(), not the text location of the syntax error. That's not a bug. The location of the syntax error is given in the error message.

BUG= #23684
[email protected], [email protected]

Review URL: https://codereview.chromium.org/2044753002 .
@kevmoo
Copy link
Member

kevmoo commented Sep 16, 2016

Fixed in 3e0d13b – Woo hoo!

Created a sample with pkg/test

Before

00:00 +0 -1: loading test/io_server_test.dart
  Failed to load "test/io_server_test.dart":
  line 16 pos 15: 'continue' is illegal here
  void badFn() {continue;}
                ^
00:00 +0 -1: Some tests failed.

After

00:00 +0 -1: serves HTTP requests with the mounted handler
  'file:///Users/kevmoo/source/github/shelf/test/io_server_test.dart': error: line 16 pos 15: 'continue' is illegal here
  void badFn() {continue;}
                ^

  test/io_server_test.dart 30:20  main.<fn>.<async>.<fn>
  dart:async                      runZoned
  test/io_server_test.dart 30:5   main.<fn>.<async>
  dart:async                      _SyncCompleter.complete
  package:http/http.dart 167:5    _withClient.<async>

00:00 +2 -1: Some tests failed.

I can see where the bad call happened. The rest of the tests ran.

Awesome!

@goderbauer
Copy link
Contributor

Very cool! Thank you! Cannot wait to get my hands on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. P1 A high priority bug; for example, a single project is unusable or has many test failures type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests