Details
-
Bug
-
Status: Closed
-
Major
-
Resolution: Fixed
-
2.4.8
Description
I created a pull request on GitHub with a failing test showing the problem: https://github.com/apache/groovy/pull/502
This test fails because ModuleNode.createStatementsClass() calls .getSuperClass().getDeclaredConstructor(SCRIPT_CONTEXT_CTOR) and gets null back, though the constructor does exist!
ModuleNode.setScriptBaseClassFromConfig(ClassNode)
calls .setSuperClass(ClassHelper.make(baseClassName)) on the scriptDummy ClassNode.
The ClassNode created for this script's base class has .lazyInitDone = true and .constructors = null
ModuleNode.createStatementsClass() calls .getSuperClass().getDeclaredConstructor(SCRIPT_CONTEXT_CTOR)
Then ClassNode.constructors is set to an empty ArrayList in ClassNode.getDeclaredConstructors(), insteaf of looking them up from the Java class.
The script constructor is then generated in ModuleNode.createStatementsClass() as:
BROKEN BEHAVIOUR
Constructor(Binding context) { super(); // Fields are initialized after the call to super() // Fields are initialized here with new Binding() instead of context setBinding(context); // This is too late, fields are initialized after super(), before this call to setBinding }
instead of
EXPECTED BEHAVIOUR
Constructor(Binding context) { super(context); // Fields are initialized after the call to super(context) }
We're calling the default constructor in the base class with super(), instead of passing along the Binding context with super(context)
This breaks initialization of Fields that depend on the Binding context, because Fields are initialized between the call to super() and the setBinding(context): http://stackoverflow.com/a/14806340/233014
This leads to MissingPropertyException because we're trying to look up variables from the new Binding() created in the default constructor, instead of the binding we passed in.
For convenience, here is the failing test:
void testBindingsInFieldInitializersWithConfigJavaBaseScript() { def config = new org.codehaus.groovy.control.CompilerConfiguration() config.scriptBaseClass = BindingScript.class.name def shell = new GroovyShell(config); def scriptText = ''' @groovy.transform.Field def script_args = getProperty('args') // Will get MissingPropertyException here because this @Field is initialized after the call to super(), before the call to setBinding in the script constructor assert script_args[0] == 'Hello Groovy' script_args[0] ''' def arg0 = 'Hello Groovy' def result = shell.run scriptText, 'TestBindingsInFieldInitializersWithConfigJavaBaseScript.groovy', [arg0] assert result == arg0 }
and the Java script base class:
package groovy.lang; /** * A Script which requires a Binding passed in the constructor and disallows calling the default constructor. */ public abstract class BindingScript extends Script { // Making the default constructor private instead gives IllegalAccessError // Removing the default constructor instead gives NoSuchMethodError // Removing both constructors just calls to the default constructor in groovy.lang.Script giving MissingPropertyException on field initialization protected BindingScript() { // This constructor erroneously gets called instead of the other one } protected BindingScript(Binding binding) { super(binding); // This is the constructor that should have been called, because then the binding would have been passed in the above call, before @Fields are initialised. } }
Attachments
Issue Links
- relates to
-
GROOVY-11154 Script subclasses cannot have constructors with arguments
- Open
- links to