Search code examples
gwtjsnigwt-super-dev-modedev-mode

DevMode Launch fails in GWT JSNI project


I have run into a weird issue with gwt dev mode debugging.

Following is a JSNI wrapper I am writing https://github.com/sillysachin/GWTAMChart

It is fairly small and simple project with lots of JSNI, JavaScriptObject and JSON code. It wraps over popular amcharts charting library. It works well when debugged in SuperDevMode and in Production.

However I am not able to debug the project in Internet Explorer with Dev Mode Debugging.

java.lang.ClassFormatError: Duplicate method name&signature in class file com/google/gwt/core/client/JavaScriptObject$

The main exception thrown is not helping me figure which part of code is breaking !!!!!

java.lang.ClassFormatError: Duplicate method name&signature in class file com/google/gwt/core/client/JavaScriptObject$
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:643)
    at com.google.gwt.dev.shell.CompilingClassLoader.findClass(CompilingClassLoader.java:1142)
    at com.google.gwt.dev.shell.CompilingClassLoader.loadClass(CompilingClassLoader.java:1215)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:270)
    at com.google.gwt.dev.shell.JsValueGlue.set(JsValueGlue.java:220)
    at com.google.gwt.dev.shell.ModuleSpaceOOPHM.doInvoke(ModuleSpaceOOPHM.java:130)
    at com.google.gwt.dev.shell.ModuleSpace.invokeNative(ModuleSpace.java:589)
    at com.google.gwt.dev.shell.ModuleSpace.invokeNativeVoid(ModuleSpace.java:315)
    at com.google.gwt.dev.shell.ModuleSpace.onLoad(ModuleSpace.java:359)
    at com.google.gwt.dev.shell.OophmSessionHandler.loadModule(OophmSessionHandler.java:200)
    at com.google.gwt.dev.shell.BrowserChannelServer.processConnection(BrowserChannelServer.java:530)
    at com.google.gwt.dev.shell.BrowserChannelServer.run(BrowserChannelServer.java:368)
    at java.lang.Thread.run(Thread.java:745)

Solution

  • The problematic class is AmChartJSO implementing the IsAmChart interface - all the methods are declared twice in JavaScriptObject$. Snippet from the bytecode:

    public final synthetic com_amcharts_api_IsAmChart_setVersion(Ljava/lang/String;)V
      ALOAD 0
      ALOAD 1
      INVOKESTATIC com/amcharts/jso/AmChartJSO$.setVersion$ (Lcom/amcharts/jso/AmChartJSO;Ljava/lang/String;)V
      RETURN
      MAXSTACK = 2
      MAXLOCALS = 2
    
    // access flags 0x1011
    public final synthetic com_amcharts_api_IsAmChart_setVersion(Ljava/lang/String;)V
      ALOAD 0
      ALOAD 1
      INVOKESTATIC com/amcharts/jso/AmChartJSO$.setVersion$ (Lcom/amcharts/jso/AmChartJSO;Ljava/lang/String;)V
      RETURN
      MAXSTACK = 2
      MAXLOCALS = 2
    

    It seems you've run into a limitation of overlay types - only one JavaScriptObject subtype can implement any given interface:

    Practically speaking, this means that only one JavaScriptObject type may implement any given interface, but any number of non-JavaScriptObject types may also implement that interface.

    Looking at your code, this restriction is broken: AmChartJSO implements IsAmChart, but AmCoordinateChartJSO implements IsAmCoordinateChart which extends IsAmChart - and thus two JSOs implement the same interface. If I understand this restriction correctly, you can't even subclass a JSO that implements an interface.

    I've done a quick test and this code fails too:

    public class Test extends JavaScriptObject implements TakesValue<String> {
        protected Test() {
        }
    
        @Override
        public final void setValue(String value) {
        }
    
        @Override
        public final String getValue() {
            return null;
        }
    }
    
    public class Test2 extends Test {
        protected Test2() {
        }
    }
    

    With a similarly useless exception:

    java.lang.NullPointerException: null
        at com.google.gwt.dev.shell.CompilingClassLoader$MySingleJsoImplData.findOverloadUsingErasure(CompilingClassLoader.java:703)
        at com.google.gwt.dev.shell.CompilingClassLoader$MySingleJsoImplData.<init>(CompilingClassLoader.java:593)
        at com.google.gwt.dev.shell.CompilingClassLoader.<init>(CompilingClassLoader.java:980)
        at com.google.gwt.dev.shell.ShellModuleSpaceHost.onModuleReady(ShellModuleSpaceHost.java:137)
        at com.google.gwt.dev.shell.ModuleSpace.onLoad(ModuleSpace.java:340)
        at com.google.gwt.dev.shell.OophmSessionHandler.loadModule(OophmSessionHandler.java:200)
        at com.google.gwt.dev.shell.BrowserChannelServer.processConnection(BrowserChannelServer.java:526)
        at com.google.gwt.dev.shell.BrowserChannelServer.run(BrowserChannelServer.java:364)
        at java.lang.Thread.run(Thread.java:745)
    

    Please see this thread on GWT's mailing list for workarounds and general discussion of this problem.


    To dump the generated class files yourself

    For future reference, you can dump the generated class filed by setting the gwt.dev.classDump system property (-Dgwt.dev.classDump=true). See this wiki page for more information. By default the classes are written to the rewritten-classes folder (in your case it will be war/rewritten-classes). The classes are organized by packages, so finding JavaScriptObject$ is easy: rewritten-classes/com/google/gwt/core/client/JavaScriptObject$.class.

    Now, all you need to do is disassemble it - I've used the Bytecode Outline plugin for Eclipse and got the bytecode of JavaScriptObject$.class.

    To find out which methods were duplicated, I could just load the class file with a classloader and let the JVM figure it out... But I was feeling lazy so I've just greped for public final synthetic in the bytecode and run uniq -D to see only the duplicated entries.