[
https://issues.apache.org/jira/browse/GROOVY-12089?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Mattias Reichel updated GROOVY-12089:
-------------------------------------
Description:
With Groovy 5, a Grails domain class annotated with both {{@Entity}} and
{{@Sortable}} can fail during canonicalization with:
{code:java}
BUG! exception in phase 'canonicalization' in source unit '...' unexpected
NullPointerException
Caused by: java.lang.NullPointerException: Cannot read the array length because
"<local3>" is null
at
org.codehaus.groovy.classgen.VariableScopeVisitor.visitConstructorOrMethod(...)
at
org.codehaus.groovy.transform.SortableASTTransformation.createSortable(...){code}
A minimal reproducer is:
{code:java}
import grails.persistence.Entity
import groovy.transform.Sortable
@Entity
@Sortable(includes = ['a'])
class Repro {
String a
}{code}
{{@Sortable}} by itself compiles, and {{@Entity}} by itself compiles. The
failure only appears when they are combined.
According to GPT, the Groovy-side branch that introduces the null exceptions
array is in {{{}org.codehaus.groovy.ast.ClassNode#getGetterMethod(String){}}},
specifically the default-argument getter synthesis branch:
{code:java}
} else if (method.hasDefaultValue() &&
Stream.of(method.getParameters()).allMatch(Parameter::hasInitialExpression)) {
// GROOVY-11380: getter generated later by default arguments
if (isNullOrSynthetic.test(getterMethod)) {
getterMethod = new MethodNode(method.getName(), method.getModifiers() &
~ACC_ABSTRACT, method.getReturnType(), Parameter.EMPTY_ARRAY,
method.getExceptions(), null);
getterMethod.setSynthetic(true);
getterMethod.setDeclaringClass(this);
getterMethod.addAnnotations(method.getAnnotations());
AnnotatedNodeUtils.markAsGenerated(this, getterMethod);
getterMethod.setGenericsTypes(method.getGenericsTypes());
}
}
{code}
If the source method carries a null exceptions array, that null is preserved on
the synthesized getter and later blows up in
{{{}VariableScopeVisitor.visitConstructorOrMethod(...){}}}.
I can also add, that historically, Grails often created methods in AST with a
null exceptions parameter. With Groovy 5 this blew up in the same way. We have
therefore changed to consistently pass ClassNode.EMPTY_ARRAY to remedy that
situation. However, it feels like there should be a guard on the Groovy side
for this instead of blowing up.
was:
With Groovy 5, a Grails domain class annotated with both {{@Entity}} and
{{@Sortable}} can fail during canonicalization with:
{code:java}
BUG! exception in phase 'canonicalization' in source unit '...' unexpected
NullPointerException
Caused by: java.lang.NullPointerException: Cannot read the array length because
"<local3>" is null
at
org.codehaus.groovy.classgen.VariableScopeVisitor.visitConstructorOrMethod(...)
at
org.codehaus.groovy.transform.SortableASTTransformation.createSortable(...){code}
A minimal reproducer is:
{code:java}
import grails.persistence.Entity
import groovy.transform.Sortable
@Entity
@Sortable(includes = ['a'])
class Repro {
String a
}{code}
{{@Sortable}} by itself compiles, and {{@Entity}} by itself compiles. The
failure only appears when they are combined.
According to GPT, the Groovy-side branch that introduces the null exceptions
array is in {{{}org.codehaus.groovy.ast.ClassNode#getGetterMethod(String){}}},
specifically the default-argument getter synthesis branch:
{code:java}
} else if (method.hasDefaultValue() &&
Stream.of(method.getParameters()).allMatch(Parameter::hasInitialExpression)) {
// GROOVY-11380: getter generated later by default arguments
if (isNullOrSynthetic.test(getterMethod)) {
getterMethod = new MethodNode(method.getName(), method.getModifiers() &
~ACC_ABSTRACT, method.getReturnType(), Parameter.EMPTY_ARRAY,
method.getExceptions(), null);
getterMethod.setSynthetic(true);
getterMethod.setDeclaringClass(this);
getterMethod.addAnnotations(method.getAnnotations());
AnnotatedNodeUtils.markAsGenerated(this, getterMethod);
getterMethod.setGenericsTypes(method.getGenericsTypes());
}
}
{code}
If the source method carries a null exceptions array, that null is preserved on
the synthesized getter and later blows up in
{{{}VariableScopeVisitor.visitConstructorOrMethod(...){}}}.
> Groovy 5 ClassNode.getGetterMethod() can clone a getter with a null
> exceptions array when @Entity and @Sortable are combined
> ----------------------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12089
> URL: https://issues.apache.org/jira/browse/GROOVY-12089
> Project: Groovy
> Issue Type: Bug
> Components: AST Transforms
> Affects Versions: 5.0.6
> Reporter: Mattias Reichel
> Priority: Major
>
> With Groovy 5, a Grails domain class annotated with both {{@Entity}} and
> {{@Sortable}} can fail during canonicalization with:
> {code:java}
> BUG! exception in phase 'canonicalization' in source unit '...' unexpected
> NullPointerException
> Caused by: java.lang.NullPointerException: Cannot read the array length
> because "<local3>" is null
> at
> org.codehaus.groovy.classgen.VariableScopeVisitor.visitConstructorOrMethod(...)
> at
> org.codehaus.groovy.transform.SortableASTTransformation.createSortable(...){code}
> A minimal reproducer is:
> {code:java}
> import grails.persistence.Entity
> import groovy.transform.Sortable
> @Entity
> @Sortable(includes = ['a'])
> class Repro {
> String a
> }{code}
> {{@Sortable}} by itself compiles, and {{@Entity}} by itself compiles. The
> failure only appears when they are combined.
> According to GPT, the Groovy-side branch that introduces the null exceptions
> array is in
> {{{}org.codehaus.groovy.ast.ClassNode#getGetterMethod(String){}}},
> specifically the default-argument getter synthesis branch:
> {code:java}
> } else if (method.hasDefaultValue() &&
> Stream.of(method.getParameters()).allMatch(Parameter::hasInitialExpression)) {
> // GROOVY-11380: getter generated later by default arguments
> if (isNullOrSynthetic.test(getterMethod)) {
> getterMethod = new MethodNode(method.getName(), method.getModifiers()
> & ~ACC_ABSTRACT, method.getReturnType(), Parameter.EMPTY_ARRAY,
> method.getExceptions(), null);
> getterMethod.setSynthetic(true);
> getterMethod.setDeclaringClass(this);
> getterMethod.addAnnotations(method.getAnnotations());
> AnnotatedNodeUtils.markAsGenerated(this, getterMethod);
> getterMethod.setGenericsTypes(method.getGenericsTypes());
> }
> }
> {code}
> If the source method carries a null exceptions array, that null is preserved
> on the synthesized getter and later blows up in
> {{{}VariableScopeVisitor.visitConstructorOrMethod(...){}}}.
> I can also add, that historically, Grails often created methods in AST with a
> null exceptions parameter. With Groovy 5 this blew up in the same way. We
> have therefore changed to consistently pass ClassNode.EMPTY_ARRAY to remedy
> that situation. However, it feels like there should be a guard on the Groovy
> side for this instead of blowing up.
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)