Skip to content

Compiler creates different byte code on Linux vs Windows/MacOS #1353

@kriegaex

Description

@kriegaex

@eric-milles, we started talking about this problem on StackOverflow here. Please re-read your own and my comments to which lately you did not react anymore, even though I created a reproducer for you on GitHub as part of my bigger Spock playground project with a dedicated branch groovy-properties-emilles, only running the problematic test on GitHub. Sorry, I have to rely on GitHub CI, because locally I neither have Linux (where the problem occurs) nor MacOS.

Now I think I pinpointed the problem to the GrEclipse compiler, because it creates different byte code for the class under test on Linux than on the other two platforms. In the Spock test, I just see the result. It is not part of the problem.

Please first look at directory geb-spock-samples/src/test/groovy/de/scrum_master/stackoverflow/q61065342. There you find:

  • 3 groovy classes which I am going to talk about more later,
  • Text files deps-linux.txt vs. deps-windows.txt, which show exactly identical Maven dependency trees for the module on both platforms. I extracted those from a previous GitHub build result in which I had added mvn dependency:tree to the build plan. So dependency management is not the problem here.
  • Text files ex-linux.txt, ex-macos.txt, ex-windows.txt which, if you diff e.g. Windows to Linux, show that the call stack leading to the problem in the Spock test on Linux really does look different there compared to the two identical ones on Windows/MacOS.

First I thought this might be some Spock quirk, but then I had the idea to attach the target directory for that Maven module as a ZIP file to the GitHub build, so I can download and compare the class files. You can do so yourself by downloading the build attachments for this build run. Scroll down to "Artifacts" and fetch the Linux and the Windows versions. Then unpack them and do a directory tree diff, focusing on *.class files. The result looks like this for me, using Total Commander's diff tool:

image

That was unexpected: The exact same GrEclipse compiler version produces different class files for 3 classes in my project, depending on which platform it runs on. Focusing on UnderTest, which is the one of those 3 classes actually used by SetGroovyPropertyTest, here is the source code:

package de.scrum_master.stackoverflow.q61065342

class UnderTest {
  void call(Collaborator collaborator, String value) {
    collaborator.x = value
  }
}

Collaborator looks like this:

package de.scrum_master.stackoverflow.q61065342

class Collaborator {
  String x
}

Simple enough, I think. FYI, the Spock 1.3 test asserting on setX to be called looks like this:

package de.scrum_master.stackoverflow.q61065342

import spock.lang.Specification

class SetGroovyPropertyTest extends Specification {
  void 'expect setX'() {
    setup:
    def collaborator = Mock(Collaborator)

    when:
    new UnderTest().call(collaborator, 'test')

    then:
    // Both of these seem to be called sometimes on Groovy 2.5, maybe depending on the OS platform,
    // test order or whatever
    1 * collaborator.setX('test')
    //1 * collaborator.setProperty('x', 'test')
  }

But we do not even have to run the Spock test in order to spot the difference. When opening the class files for UnderTest from the downloaded target directories, we see that on Windows and MacOS it looks like this when decompiled (using Fernflower, which is embedded in IntelliJ IDEA Ultimate):

package de.scrum_master.stackoverflow.q61065342;

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.Generated;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class UnderTest implements GroovyObject {
  @Generated
  public UnderTest() {
    CallSite[] var1 = $getCallSiteArray();
    super();
    MetaClass var2 = this.$getStaticMetaClass();
    this.metaClass = var2;
  }

  public void call(Collaborator collaborator, String value) {
    CallSite[] var3 = $getCallSiteArray();
    // This is the expected Groovy code found on Windows and MacOS
    ScriptBytecodeAdapter.setGroovyObjectProperty(value, UnderTest.class, collaborator, (String)"x");
  }
}

Now if we look at the Linux version, we find:

package de.scrum_master.stackoverflow.q61065342;

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.Generated;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class UnderTest implements GroovyObject {
  @Generated
  public UnderTest() {
    CallSite[] var1 = $getCallSiteArray();
    super();
    MetaClass var2 = this.$getStaticMetaClass();
    this.metaClass = var2;
  }

  public void call(Collaborator collaborator, String value) {
    CallSite[] var3 = $getCallSiteArray();
    // This is the unexpected Groovy code found on Linux
    ScriptBytecodeAdapter.setProperty(value, (Class)null, collaborator, (String)"x");
  }
}

Spot the difference?

    // This is the expected Groovy code found on Windows and MacOS
    ScriptBytecodeAdapter.setGroovyObjectProperty(value, UnderTest.class, collaborator, (String)"x");

versus

    // This is the unexpected Groovy code found on Linux
    ScriptBytecodeAdapter.setProperty(value, (Class)null, collaborator, (String)"x");

So now the question is why GrEclipse creates different byte code on different platforms and whether this is an actual GrEclipse problem as such or maybe the result of certain Groovy calls, i.e. in that case Groovy 2.5.x itself (tested on several versions up to 2.5.16) is the root cause.

I hope that my initial analysis is deserving of an answer this time.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions