[
https://issues.apache.org/jira/browse/GROOVY-12710?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18139359#comment-18139359
]
ASF GitHub Bot commented on GROOVY-12710:
-----------------------------------------
paulk-asert merged PR #3240:
URL: https://github.com/apache/groovy/pull/3240
> Compiler overflows the stack on static final fields whose initializers refer
> to each other
> ------------------------------------------------------------------------------------------
>
> Key: GROOVY-12710
> URL: https://issues.apache.org/jira/browse/GROOVY-12710
> Project: Groovy
> Issue Type: Bug
> Reporter: Paul King
> Priority: Major
>
> h2. Problem
> When {{static final}} fields have initializers that refer back to each other,
> directly or through other fields, the compiler throws
> {{java.lang.StackOverflowError}} and the compile is aborted. This happens
> with ordinary dynamic Groovy; no AST transform or type checking is involved.
> {code:groovy}
> class Cycle {
> static final String R = S
> static final String S = R
> }
> println Cycle.R
> {code}
> Actual:
> {noformat}
> Caught: java.lang.StackOverflowError
> at
> org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants(ExpressionUtils.java)
> at
> org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants(ExpressionUtils.java)
> ...
> at
> org.codehaus.groovy.classgen.Verifier.addFieldInitialization(Verifier.java)
> at
> org.codehaus.groovy.classgen.Verifier.addInitialization(Verifier.java)
> at org.codehaus.groovy.classgen.Verifier.visitClass(Verifier.java)
> {noformat}
> Every variant below overflows, on 2.5.23, 3.0.25, 4.0.32, 5.1.3, 6.0.0 and
> master, compiled dynamically, with {{@TypeChecked}} or with
> {{@CompileStatic}}. For comparison, the Java equivalents with javac:
> ||Fields||Groovy||javac, then running||
> |{{static final String R = S; static final String S =
> R}}|StackOverflowError|"illegal forward reference"|
> |{{static final String R = Cycle.S; static final String S =
> Cycle.R}}|StackOverflowError|compiles; prints {{null null}}|
> |{{class A { static final String X = B.Y }}}, {{class B { static final String
> Y = A.X }}}|StackOverflowError|compiles; prints {{null null}}|
> |{{interface I { String R = J.S }}}, {{interface J { String S = I.R
> }}}|StackOverflowError|compiles; prints {{null null}}|
> |{{static final String R = R}}|StackOverflowError|"self-reference in
> initializer"|
> |{{static final String R = Cycle.R}}|StackOverflowError|compiles; prints
> {{null}}|
> |{{static final int A = Cycle.B + 1; static final int B = Cycle.A +
> 1}}|StackOverflowError|compiles; prints {{1 2}}|
> Using such a field as an annotation attribute ({{@Ann(Cycle.R)}}) also
> overflows.
> Instance fields, and {{static}} fields that aren't {{final}}, with the same
> cyclic initializers compile and run, initialized in declaration order.
> h2. Cause
> {{ExpressionUtils.transformInlineConstants(Expression, ClassNode)}} inlines
> the value of a {{static final}} field by calling itself on that field's
> initializer, both for a plain name ({{S}}) and for a qualified one
> ({{Cycle.S}}). It keeps no record of the fields already being inlined, so a
> chain of initializers that leads back to an earlier field recurses until the
> stack overflows. The single-argument {{transformInlineConstants(Expression)}}
> delegates to it for fields, so it loops the same way.
> Many parts of the compiler use these methods, so the cycle can be reached
> from several places:
> * class generation ({{Verifier.addFieldInitialization}}), which is where the
> plain example above fails;
> * annotation handling ({{AnnotationVisitor}}, {{AnnotationConstantsVisitor}});
> * {{ResolveVisitor}} and {{StaticImportVisitor}};
> * {{StaticCompilationTransformer}};
> * the joint-compilation stub generator;
> * groovydoc.
> h2. Proposed fix
> Keep track of the fields being inlined during one call, and when a field is
> reached again, stop and return the expression unchanged. Its value is then
> not a compile-time constant, so the field is initialized at runtime in
> declaration order, as for the non-final case and as in Java. An annotation
> attribute that refers to such a field then gets the usual error for a value
> that isn't a constant, instead of overflowing.
> Groovy accepts forward references to static fields, while javac rejects the
> plain forward reference ({{R = S}}) and the plain self-reference ({{R = R}}).
> Compiling all the forms, with the runtime values shown above, keeps Groovy
> consistent with what it already does for non-final fields. Whether to report
> the plain forms as errors instead, to match Java, is a separate decision.
> h2. Tests
> * Each row of the table above compiles, and gives the value javac's version
> prints.
> * An annotation attribute referring to a cyclic field reports a
> constant-expression error and doesn't overflow.
> * A non-cyclic chain ({{static final String R = S}}, {{static final String S
> = 'x'}}) is still inlined as a constant, for example still usable as an
> annotation attribute.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)