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

[js] rework exception handling (closes #6458) #6713

Merged
merged 7 commits into from
Oct 26, 2017
Merged

Conversation

nadako
Copy link
Member

@nadako nadako commented Oct 26, 2017

This reworks JS exception handling in a pre-analyzer/pre-dce filter that handles wrapping/unwrapping, single-catch + Std.is transformation, rethrowing, haxe.CallStack, etc.

E.g.

try {
    throw "epuc fial";
} catch (e:String) {
    trace(e);
} catch (e:Bool) {
    trace(e);
    js.Lib.rethrow();
}

is now generated as

try {
    throw new js__$Boot_HaxeError("epuc fial");
} catch( e ) {
    var e1 = (e instanceof js__$Boot_HaxeError) ? e.val : e;
    if(typeof(e1) == "string") {
        console.log("Main.hx:7:",e1);
    } else if(typeof(e1) == "boolean") {
        console.log("Main.hx:9:",e1);
        throw e;
    } else {
        throw e;
    }
}

Let's see what travis has to say about this...

@@ -26,6 +26,7 @@
return js.Object.prototype.hasOwnProperty.call(o, field);
}

@:pure
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and other similar ones) are not inferred as pure anymore, because the filter adds haxe.CallStack.lastException assignment. It might make sense to move injection of that assignment into a separate post-analyzer/dce filter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should do that...


Calling this is only possible inside a catch statement.
**/
public static function getOriginalException():Dynamic {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one allows for implementing functions like this:

static function catchToCallback<T>(fn:()->T, callback:(error:Any, result:Null<T>)->Void) {
    var result =
        try
            fn()
        catch (e:Any)
            return callback(js.Lib.getOriginalException(), null);
    callback(null, result);
}

which will be compiled nicely into

Main.catchToCallback = function(fn,callback) {
    var result;
    try {
        result = fn();
    } catch( e ) {
        callback(e,null);
        return;
    }
    callback(null,result);
};

(note how we don't unwrap e here, because we requested "original exception")

Copy link
Member

@RealyUniqueName RealyUniqueName Oct 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you will lose haxe.CallStack.exceptionsStack() functionality.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, it's just not generated in a simple case where haxe.CallStack is not used.

@nadako nadako requested a review from Simn October 26, 2017 00:51
@nadako nadako self-assigned this Oct 26, 2017
@nadako nadako added bug enhancement platform-javascript Everything related to JS / JavaScript labels Oct 26, 2017
@@ -172,12 +172,12 @@ class TestJs {
try throw false catch (e:Dynamic) {}
}

@:js('try {throw new js__$Boot_HaxeError(false);} catch( e ) {if (e instanceof js__$Boot_HaxeError) e = e.val;TestJs.use(e);}')
@:js('try {throw new js__$Boot_HaxeError(false);} catch( e ) {var e1 = (e instanceof js__$Boot_HaxeError) ? e.val : e;var e2 = e1;TestJs.use(e2);}')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This var e2 = e1; is unfortunate, but I think that's something the analyzer should handle.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably considers the variable to be a user-var. Try adding Meta.CompilerGenerated to it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will try, thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That helped!

static function testHaxeErrorUnwrappingWhenUsed() {
try throw false catch (e:Dynamic) use(e);
}

@:js("try {throw new js__$Boot_HaxeError(false);} catch( e ) {if (e instanceof js__$Boot_HaxeError) e = e.val;if( js_Boot.__instanceof(e,Bool) ) {} else throw(e);}")
@:js('try {throw new js__$Boot_HaxeError(false);} catch( e ) {var e1 = (e instanceof js__$Boot_HaxeError) ? e.val : e;if(typeof(e1) != "boolean") {throw e;}}')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was actually incorrect before (an instance of #6458)

@@ -62,7 +62,7 @@ class TestJs {
return v + v2;
}

@:js("var a = [];var tmp;try {tmp = a[0];} catch( e ) {tmp = null;}tmp;")
@:js("var a = [];var tmp;try {tmp = a[0];} catch( e ) {var e1 = (e instanceof js__$Boot_HaxeError) ? e.val : e;var e2 = e1;tmp = null;}tmp;")
Copy link
Member Author

@nadako nadako Oct 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generated code because @:analyzer(no_local_dce) is being respected, since this is now inserted as proper AST instead of doing some checks at the generator level.

*)

(*
This filter handles everything related to expressions for the JavaScript target:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "related to exceptions"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, yeah :)

let follow = Abstract.follow_with_abstracts

let rec is_js_error t =
match t with
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like follow_with_abstracts should be used here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a test that catches something which is typedeffed to/abstracts over js.Error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is meant to run with a followed type already. In fact it should be called with a tclass, hmmm...

… filter, so it doesn't interfere with purity inference and can make more informed decision whether it's actually needed
@nadako
Copy link
Member Author

nadako commented Oct 26, 2017

I think it's pretty good now. I moved haxe.CallStack.lastException storing into a separate post-DCE filter, so it doesn't interfere with purity inference and only injects assignments when CallStack.exceptionStack() is actually used.

@nadako nadako merged commit b224999 into development Oct 26, 2017
@nadako nadako deleted the js_exceptions branch October 26, 2017 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug enhancement platform-javascript Everything related to JS / JavaScript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants