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

Issue GROOVY-6899 is present in currently deployed 2.4 compiler #174

Closed
eric-milles opened this issue Mar 31, 2016 · 4 comments
Closed

Issue GROOVY-6899 is present in currently deployed 2.4 compiler #174

eric-milles opened this issue Mar 31, 2016 · 4 comments
Labels

Comments

@eric-milles
Copy link
Member

Could the Groovy compiler be updated to the latest stable release level? I am experiencing issue GROOVY-6899 within Eclipse. But a command-line compilation using Ant and Groovy 2.4.6 succeeds.

What I have is a simple POGO that implements an interface with type arguments. When running unit tests in Eclipse, I get this exception for every use of the MiData constructor.

java.lang.NullPointerException
    at com.sun.beans.TypeResolver.resolve(TypeResolver.java:203)
    at com.sun.beans.TypeResolver.resolve(TypeResolver.java:162)
    at com.sun.beans.TypeResolver.resolveInClass(TypeResolver.java:81)
    at java.beans.FeatureDescriptor.getReturnType(FeatureDescriptor.java:370)
    at java.beans.Introspector.getTargetEventInfo(Introspector.java:1052)
    at java.beans.Introspector.getBeanInfo(Introspector.java:427)
    at java.beans.Introspector.getBeanInfo(Introspector.java:173)
    at groovy.lang.MetaClassImpl$15.run(MetaClassImpl.java:3289)
    at java.security.AccessController.doPrivileged(Native Method)
    at groovy.lang.MetaClassImpl.addProperties(MetaClassImpl.java:3287)
    at groovy.lang.MetaClassImpl.initialize(MetaClassImpl.java:3263)
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:254)
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:285)
    at MiData.$getStaticMetaClass(MiData.groovy)
    at MiData.<init>(MiData.groovy)

@EqualsAndHashCode(includes='value') @ToString(includes='value')
class MiData implements MultiIndexed<Integer, String> {
    final String value
    final Integer primaryKey
    final String[] secondaryKeys

    MiData(String val, Integer pk, String... sk) {
        value = val
        primaryKey = pk
        secondaryKeys = sk
    }
}

// and the Java interface for reference:
public interface MultiIndexed<PK, SK> {
    PK getPrimaryKey();
    SK[] getSecondaryKeys();
}
@eric-milles
Copy link
Member Author

I was able to create a unit test for this issue. This would run from GroovySimpleTest if it were a Groovy class. (I have a separate pull request for splitting this test into some more manageable pieces.)

The issue is still present using the latest 2.4.8 snapshot. So I'm not sure if the Greclipse compiled class file is differing from the Groovyc version. I have to do some more checking on that.

    void testExtendingGenerics_GroovyExtendsJava11() {
        String[] sources = [
            'Main.groovy', '''\
            @groovy.transform.CompileStatic class Main {
              public static void main(String[] args) {
                def data = new MIData('V', 1, 'B')
                print 'no error'
              }
            }'''.stripIndent(),

            'MultiIndexed.java', '''\
            public interface MultiIndexed<PK, SK> {
              PK getPrimaryKey();
              SK[] getSecondaryKeys();
            }'''.stripIndent(),

            'MIData.groovy', '''\
            class MIData implements MultiIndexed<Integer, String> {
              final String value
              final Integer primaryKey
              final String[] secondaryKeys

              MIData(String val, Integer pk, String... sk) {
                this.value = val
                this.primaryKey = pk
                this.secondaryKeys = sk
              }
            }'''.stripIndent()
        ]

        runConformTest(sources, 'no error')
    }

@eric-milles
Copy link
Member Author

eric-milles commented Nov 10, 2016

I did a little more recon on this. Groovyc (with indy disabled) produces similar code, but it adds a field: $staticClassInfo$. And the call site array has 26 entries instead of 20. And there is an extra check in hashCode. Can anyone tell from this if a step has been missed in compilation/transformation or something else?

I compiled the class above using groovyc and then pushed the .class file through a decompiler. Here is what it generated:

@EqualsAndHashCode(includes={"value"})
@ToString(includes={"value"})
public class MiData implements MultiIndexed<Integer, String>, GroovyObject {
    private final String value;
    private final Integer primaryKey;
    private final String[] secondaryKeys;
    private static /* synthetic */ ClassInfo $staticClassInfo;
    public static transient /* synthetic */ boolean __$stMC;
    private transient /* synthetic */ MetaClass metaClass;
    private static /* synthetic */ ClassInfo $staticClassInfo$;
    private static /* synthetic */ SoftReference $callSiteArray;

    public /* varargs */ MiData(String string, Integer n, String ... arrstring) {
        void sk;
        void pk;
        MetaClass metaClass;
        MiData miData;
        void v;
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        this.metaClass = metaClass = this.$getStaticMetaClass();
        void var6_6 = v;
        this.value = ShortTypeHandling.castToString((Object)var6_6);
        void var7_7 = pk;
        this.primaryKey = (Integer)ScriptBytecodeAdapter.castToType((Object)var7_7, Integer.class);
        void var8_8 = sk;
        this.secondaryKeys = (String[])ScriptBytecodeAdapter.castToType((Object)var8_8, String[].class);
    }

    protected /* synthetic */ MetaClass $getStaticMetaClass() {
        if (this.getClass() != MiData.class) {
            return ScriptBytecodeAdapter.initMetaClass((Object)this);
        }
        ClassInfo classInfo = $staticClassInfo;
        if (classInfo == null) {
            $staticClassInfo = classInfo = ClassInfo.getClassInfo(this.getClass());
        }
        return classInfo.getMetaClass();
    }

    public int hashCode() {
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        Object _result = arrcallSite[0].callStatic(HashCodeHelper.class);
        if (__$stMC || BytecodeInterface8.disabledStandardMetaClass()) {
            if (!DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[1].call(arrcallSite[2].callCurrent((GroovyObject)this), (Object)this))) {
                Object object;
                _result = object = arrcallSite[3].callStatic(HashCodeHelper.class, _result, arrcallSite[4].callCurrent((GroovyObject)this));
            }
        } else if (!DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[5].call((Object)this.getValue(), (Object)this))) {
            Object object;
            _result = object = arrcallSite[6].callStatic(HashCodeHelper.class, _result, (Object)this.getValue());
        }
        return DefaultTypeTransformation.intUnbox((Object)_result);
    }

    public boolean canEqual(Object other) {
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        return other instanceof MiData;
    }

    public boolean equals(Object other) {
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        if (ScriptBytecodeAdapter.compareEqual((Object)other, (Object)null)) {
            return false;
        }
        if (DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[7].callCurrent((GroovyObject)this, other))) {
            return true;
        }
        if (!(other instanceof MiData)) {
            return false;
        }
        MiData otherTyped = (MiData)other;
        if (!DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[8].call((Object)otherTyped, (Object)this))) {
            return false;
        }
        if (!ScriptBytecodeAdapter.compareEqual((Object)arrcallSite[9].callCurrent((GroovyObject)this), (Object)arrcallSite[10].call((Object)otherTyped))) {
            return false;
        }
        return true;
    }

    public String toString() {
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        Object _result = arrcallSite[11].callConstructor(StringBuilder.class);
        Boolean $toStringFirst = Boolean.TRUE;
        arrcallSite[12].call(_result, (Object)"com.trgr.cobalt.search.dal.MiData(");
        if (DefaultTypeTransformation.booleanUnbox((Object)$toStringFirst)) {
            Boolean bl;
            $toStringFirst = bl = Boolean.FALSE;
        } else {
            arrcallSite[13].call(_result, (Object)", ");
        }
        if (__$stMC || BytecodeInterface8.disabledStandardMetaClass()) {
            if (DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[14].call(arrcallSite[15].callCurrent((GroovyObject)this), (Object)this))) {
                arrcallSite[16].call(_result, (Object)"(this)");
            } else {
                arrcallSite[17].call(_result, arrcallSite[18].callStatic(InvokerHelper.class, arrcallSite[19].callCurrent((GroovyObject)this)));
            }
        } else if (DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[20].call((Object)this.getValue(), (Object)this))) {
            arrcallSite[21].call(_result, (Object)"(this)");
        } else {
            arrcallSite[22].call(_result, arrcallSite[23].callStatic(InvokerHelper.class, (Object)this.getValue()));
        }
        arrcallSite[24].call(_result, (Object)")");
        return ShortTypeHandling.castToString((Object)arrcallSite[25].call(_result));
    }

    public /* synthetic */ MetaClass getMetaClass() {
        MetaClass metaClass = this.metaClass;
        if (metaClass != null) {
            return metaClass;
        }
        this.metaClass = this.$getStaticMetaClass();
        return this.metaClass;
    }

    public /* synthetic */ void setMetaClass(MetaClass metaClass) {
        this.metaClass = metaClass;
    }

    public /* synthetic */ Object invokeMethod(String string, Object object) {
        return this.getMetaClass().invokeMethod((Object)this, string, object);
    }

    public /* synthetic */ Object getProperty(String string) {
        return this.getMetaClass().getProperty((Object)this, string);
    }

    public /* synthetic */ void setProperty(String string, Object object) {
        this.getMetaClass().setProperty((Object)this, string, object);
    }

    public final String getValue() {
        return this.value;
    }

    public final Integer getPrimaryKey() {
        return this.primaryKey;
    }

    public final String[] getSecondaryKeys() {
        return this.secondaryKeys;
    }

    public /* synthetic */ String super$1$toString() {
        return super.toString();
    }

    public /* synthetic */ boolean super$1$equals(Object object) {
        return super.equals(object);
    }

    public /* synthetic */ int super$1$hashCode() {
        return super.hashCode();
    }

    private static /* synthetic */ void $createCallSiteArray_1(String[] arrstring) {
        arrstring[0] = "initHash";
        arrstring[1] = "is";
        arrstring[2] = "getValue";
        arrstring[3] = "updateHash";
        arrstring[4] = "getValue";
        arrstring[5] = "is";
        arrstring[6] = "updateHash";
        arrstring[7] = "is";
        arrstring[8] = "canEqual";
        arrstring[9] = "getValue";
        arrstring[10] = "getValue";
        arrstring[11] = "<$constructor$>";
        arrstring[12] = "append";
        arrstring[13] = "append";
        arrstring[14] = "is";
        arrstring[15] = "getValue";
        arrstring[16] = "append";
        arrstring[17] = "append";
        arrstring[18] = "toString";
        arrstring[19] = "getValue";
        arrstring[20] = "is";
        arrstring[21] = "append";
        arrstring[22] = "append";
        arrstring[23] = "toString";
        arrstring[24] = "append";
        arrstring[25] = "toString";
    }

    private static /* synthetic */ CallSiteArray $createCallSiteArray() {
        String[] arrstring = new String[26];
        MiData.$createCallSiteArray_1(arrstring);
        return new CallSiteArray(MiData.class, arrstring);
    }

    private static /* synthetic */ CallSite[] $getCallSiteArray() {
        CallSiteArray callSiteArray;
        if ($callSiteArray == null || (callSiteArray = (CallSiteArray)$callSiteArray.get()) == null) {
            callSiteArray = MiData.$createCallSiteArray();
            $callSiteArray = new SoftReference<CallSiteArray>(callSiteArray);
        }
        return callSiteArray.array;
    }
}

Then I compile using Greclipse and decompile and I get this:

@EqualsAndHashCode(includes={"value"})
@ToString(includes={"value"})
public class MiDatav implements MultiIndexed<Integer, String>, GroovyObject {
    private final String value;
    private final Integer primaryKey;
    private final String[] secondaryKeys;
    private static /* synthetic */ ClassInfo $staticClassInfo;
    public static transient /* synthetic */ boolean __$stMC;
    private transient /* synthetic */ MetaClass metaClass;
    private static /* synthetic */ SoftReference $callSiteArray;

    public /* varargs */ MiData(String string, Integer n, String ... arrstring) {
        void pk;
        void v;
        MetaClass metaClass;
        MiData miData;
        void sk;
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        this.metaClass = metaClass = this.$getStaticMetaClass();
        void var6_6 = v;
        this.value = ShortTypeHandling.castToString((Object)var6_6);
        void var7_7 = pk;
        this.primaryKey = (Integer)ScriptBytecodeAdapter.castToType((Object)var7_7, Integer.class);
        void var8_8 = sk;
        this.secondaryKeys = (String[])ScriptBytecodeAdapter.castToType((Object)var8_8, String[].class);
    }

    public int hashCode() {
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        Object _result = arrcallSite[0].callStatic(HashCodeHelper.class);
        if (!DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[1].call(arrcallSite[2].callCurrent((GroovyObject)this), (Object)this))) {
            Object object;
            _result = object = arrcallSite[3].callStatic(HashCodeHelper.class, _result, arrcallSite[4].callCurrent((GroovyObject)this));
        }
        return DefaultTypeTransformation.intUnbox((Object)_result);
    }

    public boolean canEqual(Object other) {
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        return other instanceof MiData;
    }

    public boolean equals(Object other) {
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        if (ScriptBytecodeAdapter.compareEqual((Object)other, (Object)null)) {
            return false;
        }
        if (DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[5].callCurrent((GroovyObject)this, other))) {
            return true;
        }
        if (!(other instanceof MiData)) {
            return false;
        }
        MiData otherTyped = (MiData)other;
        if (!DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[6].call((Object)otherTyped, (Object)this))) {
            return false;
        }
        if (!ScriptBytecodeAdapter.compareEqual((Object)arrcallSite[7].callCurrent((GroovyObject)this), (Object)arrcallSite[8].call((Object)otherTyped))) {
            return false;
        }
        return true;
    }

    public String toString() {
        CallSite[] arrcallSite = MiData.$getCallSiteArray();
        Object _result = arrcallSite[9].callConstructor(StringBuilder.class);
        Boolean $toStringFirst = Boolean.TRUE;
        arrcallSite[10].call(_result, (Object)"com.trgr.cobalt.search.dal.MiData(");
        if (DefaultTypeTransformation.booleanUnbox((Object)$toStringFirst)) {
            Boolean bl;
            $toStringFirst = bl = Boolean.FALSE;
        } else {
            arrcallSite[11].call(_result, (Object)", ");
        }
        if (DefaultTypeTransformation.booleanUnbox((Object)arrcallSite[12].call(arrcallSite[13].callCurrent((GroovyObject)this), (Object)this))) {
            arrcallSite[14].call(_result, (Object)"(this)");
        } else {
            arrcallSite[15].call(_result, arrcallSite[16].callStatic(InvokerHelper.class, arrcallSite[17].callCurrent((GroovyObject)this)));
        }
        arrcallSite[18].call(_result, (Object)")");
        return ShortTypeHandling.castToString((Object)arrcallSite[19].call(_result));
    }

    protected /* synthetic */ MetaClass $getStaticMetaClass() {
        if (this.getClass() != MiData.class) {
            return ScriptBytecodeAdapter.initMetaClass((Object)this);
        }
        ClassInfo classInfo = $staticClassInfo;
        if (classInfo == null) {
            $staticClassInfo = classInfo = ClassInfo.getClassInfo(this.getClass());
        }
        return classInfo.getMetaClass();
    }

    public /* synthetic */ MetaClass getMetaClass() {
        MetaClass metaClass = this.metaClass;
        if (metaClass != null) {
            return metaClass;
        }
        this.metaClass = this.$getStaticMetaClass();
        return this.metaClass;
    }

    public /* synthetic */ void setMetaClass(MetaClass metaClass) {
        this.metaClass = metaClass;
    }

    public /* synthetic */ Object invokeMethod(String string, Object object) {
        return this.getMetaClass().invokeMethod((Object)this, string, object);
    }

    public /* synthetic */ Object getProperty(String string) {
        return this.getMetaClass().getProperty((Object)this, string);
    }

    public /* synthetic */ void setProperty(String string, Object object) {
        this.getMetaClass().setProperty((Object)this, string, object);
    }

    public final String getValue() {
        return this.value;
    }

    public final Integer getPrimaryKey() {
        return this.primaryKey;
    }

    public final String[] getSecondaryKeys() {
        return this.secondaryKeys;
    }

    public /* synthetic */ int super$1$hashCode() {
        return super.hashCode();
    }

    public /* synthetic */ String super$1$toString() {
        return super.toString();
    }

    public /* synthetic */ boolean super$1$equals(Object object) {
        return super.equals(object);
    }

    private static /* synthetic */ void $createCallSiteArray_1(String[] arrstring) {
        arrstring[0] = "initHash";
        arrstring[1] = "is";
        arrstring[2] = "getValue";
        arrstring[3] = "updateHash";
        arrstring[4] = "getValue";
        arrstring[5] = "is";
        arrstring[6] = "canEqual";
        arrstring[7] = "getValue";
        arrstring[8] = "getValue";
        arrstring[9] = "<$constructor$>";
        arrstring[10] = "append";
        arrstring[11] = "append";
        arrstring[12] = "is";
        arrstring[13] = "getValue";
        arrstring[14] = "append";
        arrstring[15] = "append";
        arrstring[16] = "toString";
        arrstring[17] = "getValue";
        arrstring[18] = "append";
        arrstring[19] = "toString";
    }

    private static /* synthetic */ CallSiteArray $createCallSiteArray() {
        String[] arrstring = new String[20];
        MiData.$createCallSiteArray_1((String[])arrstring);
        return new CallSiteArray(MiData.class, arrstring);
    }

    private static /* synthetic */ CallSite[] $getCallSiteArray() {
        CallSiteArray callSiteArray;
        if ($callSiteArray == null || (callSiteArray = (CallSiteArray)$callSiteArray.get()) == null) {
            callSiteArray = MiData.$createCallSiteArray();
            $callSiteArray = new SoftReference<CallSiteArray>(callSiteArray);
        }
        return callSiteArray.array;
    }
}

@eric-milles
Copy link
Member Author

I'm going to see if I can disable the int optimization in the Ant build to see if this issue still exists in core Groovy under specific circumstances.

@eric-milles eric-milles added the bug label Sep 7, 2017
@eric-milles
Copy link
Member Author

It looks like the failure occurs dealing with the generic array return type of getSecondaryKeys. When I write the equivalent Java class, I get a slightly different return type.

MiData.groovy

class MIData implements MultiIndexed<Integer, String> {
  final String value
  final Integer primaryKey
  final String[] secondaryKeys

  MIData(String val, Integer pk, String... sk) {
    this.value = val
    this.primaryKey = pk
    this.secondaryKeys = sk
  }
}

MiData2.java

public class MIData2 implements MultiIndexed<Integer, String> {
  private final String value;
  private final Integer primaryKey;
  private final String[] secondaryKeys;

  public MIData2(String val, Integer pk, String... sk) {
    this.value = val;
    this.primaryKey = pk;
    this.secondaryKeys = sk;
  }

  public String getValue() { return value; }
  public Integer getPrimaryKey() { return primaryKey; }
  public String[] getSecondaryKeys() { return secondaryKeys; }
}

javap MiData.class

public class MIData implements MultiIndexed<java.lang.Integer, java.lang.String>, groovy.lang.GroovyObject {
  public static transient boolean __$stMC;
  public MIData(java.lang.String, java.lang.Integer, java.lang.String...);
  protected groovy.lang.MetaClass $getStaticMetaClass();
  public groovy.lang.MetaClass getMetaClass();
  public void setMetaClass(groovy.lang.MetaClass);
  public java.lang.Object invokeMethod(java.lang.String, java.lang.Object);
  public java.lang.Object getProperty(java.lang.String);
  public void setProperty(java.lang.String, java.lang.Object);
  public final java.lang.String getValue();
  public final java.lang.Integer getPrimaryKey();
  public final java.lang.String[] getSecondaryKeys();
  public final java.lang.Object getPrimaryKey();
  public final SK[] getSecondaryKeys();
}

javap MiData2.class

public class MIData2 implements MultiIndexed<java.lang.Integer, java.lang.String> {
  public MIData2(java.lang.String, java.lang.Integer, java.lang.String...);
  public java.lang.String getValue();
  public java.lang.Integer getPrimaryKey();
  public java.lang.String[] getSecondaryKeys();
  public java.lang.Object[] getSecondaryKeys();
  public java.lang.Object getPrimaryKey();
}

The return type of the bridge method is Object[] in Java and SK[] in Groovy. The bean introspection is failing on the groovy return type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant