-
Notifications
You must be signed in to change notification settings - Fork 570
Error with time.Sleep #279
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
Comments
It looks like the logic for externalize and internalize in prelude has some special handling of The current code has a guard that checks if I see two possible approaches to fixing this, and after thinking about it pretty hard, I am still on the fence as to which is better. Possible solution 1One approach is to simply change the guard to check that both diff --git a/compiler/prelude/jsmapping.go b/compiler/prelude/jsmapping.go
index 38874da..5187f8b 100644
--- a/compiler/prelude/jsmapping.go
+++ b/compiler/prelude/jsmapping.go
@@ -89,7 +89,7 @@ var $externalize = function(v, t) {
return s;
case $kindStruct:
var timePkg = $packages["time"];
- if (timePkg && v.constructor === timePkg.Time.ptr) {
+ if (timePkg && timePkg.Time && v.constructor === timePkg.Time.ptr) {
var milli = $div64(v.UnixNano(), new $Int64(0, 1000000));
return new Date($flatten64(milli));
}
@@ -271,7 +271,7 @@ var $internalize = function(v, t, recv) {
return new $Bool(!!v);
case Date:
var timePkg = $packages["time"];
- if (timePkg) {
+ if (timePkg && timePkg.Time) {
return new timePkg.Time(timePkg.Unix(new $Int64(0, 0), new $Int64(0, v.getTime() * 1000000)));
}
case Function: That will definitely fix the "Uncaught TypeError" error. However, I find it hard to be extremely confident at this time that it's not going to cause trouble in some unforeseen edge case where It seems like that should never happen because if you're doing any logic that depends on Possible solution 2A more safe (to reason about), but possibly less efficient solution is to make it so that if diff --git a/compiler/natives/time/time.go b/compiler/natives/time/time.go
index fc79721..64169bf 100644
--- a/compiler/natives/time/time.go
+++ b/compiler/natives/time/time.go
@@ -9,6 +9,14 @@ import (
"github.com/gopherjs/gopherjs/js"
)
+// Make sure time.Time struct is always included with this package (despite DCE),
+// because it's needed for ex/internalization. See issue https://github.com/gopherjs/gopherjs/issues/279.
+func init() {
+ // avoid dead code elimination
+ t := Time{}
+ _ = t
+}
+
type runtimeTimer struct {
i int32
when int64 Something similar is done in a few other places. Doing this does end up bringing in (or rather, prevent from being eliminated) a significant part of @neelance, what are your thoughts? |
Hmm, this is not easy. I think the first solution should work, since if you do |
That sounds good to me. It seems that solution 1 should not have issues. We can revisit this if it does. Should I make a PR for it (with a test), or will you? |
I'd appreciate a PR if you want to. :-) |
Sure, I can do that. It'll be done within 16 hours (after sleep)! |
Making a test for this is turning out to be a little problematic because importing "testing" package causes the bug to disappear, since that package imports "time" and uses I've opened #284 with a proposal we change compiler tests to be done like Go compiler tests are done, rather than relying on a very high level mechanism of "testing" package. If that's done, then in the future we can refactor this test to be shorter (a single .go file with what's needed to reproduce it). |
On topic of this issue itself, while in the process of making a thorough test, I have discovered a flaw in my original fix and fixed it. It affects the internalize case only: @@ -271,7 +271,7 @@ var $internalize = function(v, t, recv) {
return new $Bool(!!v);
case Date:
var timePkg = $packages["time"];
- if (timePkg) {
+ if (timePkg && timePkg.Time) {
return new timePkg.Time(timePkg.Unix(new $Int64(0, 0), new $Int64(0, v.getTime() * 1000000)));
}
case Function: Notice that the internalize case uses more things from "time" package than just I will correct that fix to the following, which should fix that problem: -if (timePkg && timePkg.Time) {
+if (timePkg && timePkg.Unix) { // timePkg.Time will be present implicitly because it's referenced by time.Unix. If you prefer I write slightly less performant but more explicit check, then it can be: -if (timePkg && timePkg.Time) {
+if (timePkg && timePkg.Unix && timePkg.Time) { |
I've made another discovery. It is possible for the possible solution 1 approach to fail and produce incorrect behavior. I was able to come up with a concrete reproducible case for it. In order to make my test very thorough, I wanted to ensure two things happen:
I couldn't just print JavaScript Date object as is, because that would cause the output to be non-deterministic (default output depends on your local timezone, so tests would fail if timezone is incorrectly set). So I went with the following code: // Excercise internalization of JavaScript Date object (with its special handling of time.Time).
date := js.Global.Get("Date").New("2015-08-29T20:56:00.869Z").Interface()
js.Global.Set("myDate", date)
js.Global.Get("console").Call("log", js.Global.Get("myDate").Call("toUTCString")) If I ensure
Otherwise, what ends up happening is the case Date:
var timePkg = $packages["time"];
if (timePkg && timePkg.Unix) { // timePkg.Time will be present implicitly because it's referenced by time.Unix.
return new timePkg.Time(timePkg.Unix(new $Int64(0, 0), new $Int64(0, v.getTime() * 1000000)));
}
// No break here, so if above if condition is false, we'll fallthrough to case Function
// and internalize it as that.
case Function:
var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true);
return new funcType($internalize(v, funcType)); And the program panics. If I modify it to just
The above demonstrates that it's possible not to import "time" package at all in Go code, yet still try to internalize Date object and then externalize that object. In both cases, the if guard will be skipped, and some unexpected behavior will occur. Conclusion is, fixing externalize case is easy. Fixing internalize so it works even if time package is not available is harder. Perhaps we need a custom internal type to use when time package isn't imported? Or reuse something like I will make a PR with just the failing test case, let's think and decide on how to best implement the fix. |
…ime/Date. This is a test for issue #279. Proper testing for this issue prevents "testing" package from being imported. So, implement it as a low level single file .go test and invoke it from backend. In the time_inexternalization.go, you can use these statements for manual testing: var _ = time.Now() // Force time.Time not to be DCEed. var _ = time.Unix(0, 0) // Force time.Unix (and time.Time implicitly) not to be DCEed. Adding both of those statements (or just time.Unix one, since it also includes time.Time indirectly) would cause the test to pass. But a true fix will be in the compiler itself, in a way that will avoid the panics/incorrect behavior even when time.Time and/or time.Unix are DCEed, or time package is not imported at all.
…ime/Date. This is a test for issue #279. Proper testing for this issue prevents "testing" package from being imported. So, implement it as a low level single file .go test and invoke it from backend. In the time_inexternalization.go, you can use these statements for manual testing: var _ = time.Now() // Force time.Time not to be DCEed. var _ = time.Unix(0, 0) // Force time.Unix (and time.Time implicitly) not to be DCEed. Adding both of those statements (or just time.Unix one, since it also includes time.Time indirectly) would cause the test to pass. But a true fix will be in the compiler itself, in a way that will avoid the panics/incorrect behavior even when time.Time and/or time.Unix are DCEed, or time package is not imported at all.
I tried to keep going with possible solution 1, fixing the issues I discovered, but I'm finding it doesn't work well to handle all cases, so I will switch to using possible solution 2 (also modified to take into account and resolve the new findings) because it's simpler and solves all edge cases more nicely. To elaborate on why possible solution isn't working well, it's the following reason: Suppose your user program imports "time" package and uses only date := js.Global.Get("Date").New("2015-08-29T20:56:00.869Z").Interface().(time.Time) This line panics, because the Dealing with that becomes problematic, as you have to consider the following situations:
I've considered "vendoring" Instead of all that, I'll go with possible solution 2 which will reduce the number of cases to deal with to just two:
That seems like the best solution. I'll make a PR (edit: #286) into PR #285 for a separate review. Comments or improvement suggestions, if any, are welcome. |
…e/Date. Fixes #279 (initial report). The internalization/externalization code only checks if the time package is present and assumes time.Unix and time.Time are available if so. Previously, it was possible for time package to be present, but either time.Unix or time.Time were not (due to dead code elimination), causing possible panics. In time package itself, ensure time.Unix func and time.Time struct it returns are always included (despite DCE) to prevent that from happening.
Fixes #279 (additional findings). When time package is not imported, it's still possible to internalize a Date object (into an interface{}). Due to fallthrough being default, this resulted in Function case running when internalizing Date with no time package. That makes no sense, and means that trying to externalize the Date object back into JavaScript world would fail to produce the original Date object (also, calling methods on it would obviously fail, since it's not a real Date object). For example, consider the following code that doesn't import time package, it can now run and produce expected results: date := js.Global.Get("Date").New().Interface() js.Global.Get("console").Call("log", date)
Fixes #279 (additional findings). When time package is not imported, it's still possible to internalize a Date object (into an interface{}). Due to fallthrough being default, this resulted in Function case running when internalizing Date with no time package. That makes no sense, and means that trying to externalize the Date object back into JavaScript world would fail to produce the original Date object (also, calling methods on it would obviously fail, since it's not a real Date object). For example, consider the following code that doesn't import time package, it can now run and produce expected results: date := js.Global.Get("Date").New().Interface() js.Global.Get("console").Call("log", date)
Fixes #279 (additional findings). When time package is not imported, it's still possible to internalize a Date object (into an interface{}). Due to fallthrough being default, this resulted in Function case running when internalizing Date with no time package. That makes no sense, and means that trying to externalize the Date object back into JavaScript world would fail to produce the original Date object (also, calling methods on it would obviously fail, since it's not a real Date object). For example, consider the following code that doesn't import time package, it can now run and produce expected results: date := js.Global.Get("Date").New().Interface() js.Global.Get("console").Call("log", date)
Thanks for all the effort! :-) |
…ime/Date. This is a test for issue #279. Proper testing for this issue prevents "testing" package from being imported. So, implement it as a low level single file .go test and invoke it from backend. In the time_inexternalization.go, you can use these statements for manual testing: var _ = time.Now() // Force time.Time not to be DCEed. var _ = time.Unix(0, 0) // Force time.Unix (and time.Time implicitly) not to be DCEed. Adding both of those statements (or just time.Unix one, since it also includes time.Time indirectly) would cause the test to pass. But a true fix will be in the compiler itself, in a way that will avoid the panics/incorrect behavior even when time.Time and/or time.Unix are DCEed, or time package is not imported at all.
…e/Date. Fixes #279 (initial report). The internalization/externalization code only checks if the time package is present and assumes time.Unix and time.Time are available if so. Previously, it was possible for time package to be present, but either time.Unix or time.Time were not (due to dead code elimination), causing possible panics. In time package itself, ensure time.Unix func and time.Time struct it returns are always included (despite DCE) to prevent that from happening.
Fixes #279 (additional findings). When time package is not imported, it's still possible to internalize a Date object (into an interface{}). Due to fallthrough being default, this resulted in Function case running when internalizing Date with no time package. That makes no sense, and means that trying to externalize the Date object back into JavaScript world would fail to produce the original Date object (also, calling methods on it would obviously fail, since it's not a real Date object). For example, consider the following code that doesn't import time package, it can now run and produce expected results: date := js.Global.Get("Date").New().Interface() js.Global.Get("console").Call("log", date)
Fixes #279 (additional findings). When time package is not imported, it's still possible to internalize a Date object (into an interface{}). Due to fallthrough being default, this resulted in Function case running when internalizing Date with no time package. That makes no sense, and means that trying to externalize the Date object back into JavaScript world would fail to produce the original Date object (also, calling methods on it would obviously fail, since it's not a real Date object). For example, consider the following code that doesn't import time package, it can now run and produce expected results: date := js.Global.Get("Date").New().Interface() js.Global.Get("console").Call("log", date)
if use gopherjs with time.Sleep without any reference to time.Time structure, this code:
produces error in browser:
in function $externalize
timePkg.Time is not defined in javascript code
The text was updated successfully, but these errors were encountered: