This is an automated email from the ASF dual-hosted git repository. paulk-asert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 375648707c9070a04e95355a978454afdcd1fb7b Author: Paul King <[email protected]> AuthorDate: Fri Jul 10 22:24:36 2026 +1000 GROOVY-12131: address review comments --- .../groovy/control/OptimizerVisitorTest.groovy | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/test/groovy/org/codehaus/groovy/control/OptimizerVisitorTest.groovy b/src/test/groovy/org/codehaus/groovy/control/OptimizerVisitorTest.groovy index cf23dc121d..24b90653b5 100644 --- a/src/test/groovy/org/codehaus/groovy/control/OptimizerVisitorTest.groovy +++ b/src/test/groovy/org/codehaus/groovy/control/OptimizerVisitorTest.groovy @@ -20,9 +20,11 @@ package org.codehaus.groovy.control import org.codehaus.groovy.ast.ClassHelper import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.ast.CodeVisitorSupport import org.codehaus.groovy.ast.Parameter import org.codehaus.groovy.ast.expr.ConstantExpression import org.codehaus.groovy.ast.stmt.Statement +import org.codehaus.groovy.classgen.GeneratorContext import org.junit.jupiter.api.Test import org.objectweb.asm.Opcodes @@ -83,4 +85,59 @@ class OptimizerVisitorTest { assert optimize(a, b).isEmpty() } + + /** + * Pipeline-level reproducer of the same problem through the real compiler, so the test + * survives a future rewrite of the internal caching mechanism. A buggy AST transform + * aliases (rather than copies) a constant node from class {@code A} into class {@code B}: + * {@code A} numbers the shared {@code 2.5} as {@code $const$1} (behind {@code 9.9} = + * {@code $const$0}), while {@code B}, meeting the same node first, re-stamps it + * {@code $const$0} — so {@code B} would read the wrong cached field at runtime. Since the + * fix is diagnostic only (it does not rewrite the tree), the observable behaviour is the + * warning surfacing from a genuine {@code CompilationUnit} compile rather than a corrected + * value. + */ + @Test // GROOVY-12131 + void sharedConstantNodeAcrossClassesWarnsThroughFullCompiler() { + def cu = new CompilationUnit() + cu.addSource('shared.groovy', ''' + class A { + def m() { + def a = 9.9G + def b = 2.5G + } + } + class B { + def m() {} + } + ''') + + // before OptimizerVisitor (CLASS_GENERATION), alias A's 2.5 node into B's body + cu.addPhaseOperation({ SourceUnit source, GeneratorContext context, ClassNode cn -> + if (cn.nameWithoutPackage != 'B') return + def a = cn.module.classes.find { it.nameWithoutPackage == 'A' } + def shared = findConstant(a, 2.5G) + assert shared != null: 'expected a 2.5 constant node in class A' + cn.getDeclaredMethods('m')[0].code.statements.add(0, stmt(shared)) + } as CompilationUnit.IPrimaryClassNodeOperation, Phases.CANONICALIZATION) + + cu.compile(Phases.CLASS_GENERATION) + + def collector = cu.errorCollector + def warnings = (0..<collector.warningCount).collect { collector.getWarning(it).message } + assert warnings.any { it.contains('shared between classes') && it.contains('$const$') } + } + + private static ConstantExpression findConstant(ClassNode cn, value) { + ConstantExpression result = null + def finder = new CodeVisitorSupport() { + @Override + void visitConstantExpression(ConstantExpression ce) { + if (result == null && ce.value == value) result = ce + super.visitConstantExpression(ce) + } + } + cn.methods.each { it.code?.visit(finder) } + result + } }
