0

Right now I am debugging some weird errors which occur in my GWT (version 2.5.1) application since Firefox 46. The javascript code generated by GWT contains multiple instances of this pattern:

function nullMethod() {
}

var v;
function f() {
   f = nullMethod;
   v = { name : 'Joe' };
   console.log("called");
}

// this is called from multiple places 
console.log((f(), v).name);
console.log((f(), v).name);
console.log((f(), v).name);

Seems that this somehow implements the singleton pattern. But for some reason this does not prevent that the initial declared method is invoked again. The string 'called' is printed to the console multiple times.

But if I try to reproduce this with a little test everything works as expected. The observations from above was made by adding console outputs to the generated code (>5MB).

Now the really weird stuff. If I add "console.log(f.toString())" as the first statement of the function, it prints the intial function on the first invocation. All further invocations print the nullMethod.

Can somebody explain what could be the cause? IE11 works fine. Chrome has the same issue as Firefox 46. I just didn't find old Chrome versions to verify since when this behaviour was introduced. Is it valid to overwrite a function declared this way?

jsbin.com gives a warning on the function re-assignment line:

Line 6: 'f' is a function.
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Daniel K.
  • 5,747
  • 3
  • 19
  • 22

1 Answers1

1

The method f in this case is a Java static initializer. Running it more than once will cause problems in the emulated Java, which expects that this can only run a single time when the class is first referenced or loaded (ignoring classloader issues, since GWT doesnt emulate classloaders).

However, sufficiently old copies of GWT wrap up blocks of code in a try/catch block (iirc this was related to IE6 issues), and at the time, JS scope rules had some ambiguity around them which made this code work consistently, since all browsers supported this. This was known as a "sloppy mode self-defining function".

As part of ES2015, it has been decided that the try block has a different scope than the outer "function-hosted" one (i.e. when you declare something as a function foo() {...}, it exists in a high-level scope). See a ton of discussion on this at https://github.com/tc39/ecma262/issues/162.

This change means that some formerly sane programs no longer work correctly - for a time, this included Google Inbox as well as others.

Updating to a newer version of GWT, or using a custom Linker that no longer wraps each block of top-level JS in old GWT should resolve this. If you still must support whatever ancient browser that requires this behavior (with enough code archeology, I'm sure we could find out why GWT initially did it, but I haven't done this yet), you are going to have to find a compromise that works in both ancient and bleeding edge browsers.

Colin Alworth
  • 17,801
  • 2
  • 26
  • 39
  • Thanks! I now know that I have to verify some more projects I was working on. – Daniel K. May 22 '16 at 14:13
  • I know for that GWT 2.5.1 is affected. I did not find any further information on this issue on the net. I tried reproducing this issue with various GWT versions with a simple demo application. But it seems that the compiler does not always emit flawed code. Upgrading my application to a newer GWT version is not that easy because of some dependencies. Do you have a list of affected GWT versions? – Daniel K. May 23 '16 at 08:26
  • @DanielK. it is my understanding that it isn't just the compiler, but specifically the linker - you could hypothetically use a broken linker on latest gwt, or you could use a working linker on gwt 2.0. With that said, I just quickly reviewed the source of all _built-in_ linkers in GWT 2.5.1, and none appear to emit try/catch wrappers around all functions, so either you have a custom linker or ported a much older linker that used to be part of GWT. – Colin Alworth May 23 '16 at 12:57
  • In my case every section starts with '{' and ends with '}' without and try/catch in place. After manually commenting out these curley braces from the generated javascript code the application worked fine. But that can't be the solution because this may generate collisions. I'll have a look at the linker tomorrow. Thanks! – Daniel K. May 23 '16 at 15:17
  • Again, no default linker appears to do that, so I'd be concerned about what added that and why. Chances seem good that it is a custom linker in your project or in a library you use, and that you can tweak it to simply not do this. – Colin Alworth May 23 '16 at 18:19
  • The "Standard" linker is used which resolves to the "IFrameLinker". I found the method printJsBlock() which is contained in the class com.google.gwt.dev.js.JsToStringGenerationVisitor. It conditionally calls _blockOpen() and _blockClose() when braces are needed. But I am not that sure if this code is used during linking. Other mentioned linkers during compile were ["RPC policy file manifest", "RPC log linker", "Export CompilationResult symbol maps", "Emit compile report artifacts"] which also all are contained in gwt-user-2.5.1.jar or gwt-dev-2.5.1.jar. – Daniel K. May 24 '16 at 07:25
  • The blockOpen/Closed stuff is part of the AST serialization stuff, it is going to be called whenever you have an if/switch/for/while/function/etc block to create. The `printJsBlock` method conditionally does start a block, but only if needsBraces is true (which means x is not a global block, but top-level stuff is by definition a global block). The other non-primary linkers shouldn't be involved here. Debug your compilation and step into IFrameLinker's superclass method `splitPrimaryJavaScript` - this will be given the entire compiled JS and be tasked with breaking it up into script blocks. – Colin Alworth May 24 '16 at 17:53
  • @colin-alworh At that point the entire compiled JS already contained the braces. I discovered that our GWT module used for development (.gwt.xml) contains `` to improve the compile time during development (see: [link](http://stackoverflow.com/a/8216430/1599926)). This seems to set `splitBlocks` to `true` (based on `isIE6orUnknown`) in the method `JavaToJavaScriptCompiler.compilePermutation()`. As a result multiple ` – Daniel K. May 25 '16 at 11:20
  • That's a new one for me, let me dig a bit, but IE6 has since been removed from GWT (as it is old and terrible and dead). Unless you are supporting that browser, perhaps disabling it might help? "splitBlocks" doesn't appear anywhere in current versions of GWT, but I'll do some excavation. – Colin Alworth May 25 '16 at 14:25
  • 1
    Okay, found this at least in GWT 2.5.1 (did not check other versions). Simply disabling IE6 (and so IE7 as well) should set isIE6orUnknown to false, and so splitBlocks as well, even when you collapse all properties. I did not test this, just reviewed the code. Upgrading to a recent version of GWT should have the same effect, since IE6 is no longer supported. – Colin Alworth May 25 '16 at 14:33
  • Thanks for your support! – Daniel K. May 25 '16 at 14:36