[ 
https://issues.apache.org/jira/browse/GROOVY-8096?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17755573#comment-17755573
 ] 

Eric Milles commented on GROOVY-8096:
-------------------------------------

It was noted in the description that {{ModuleNode#createStatementClass}} checks 
the script's super class for a constructor that accepts a {{Binding}}.  If 
found, it adds {{super(context)}} to the generated binding constructor, 
otherwise it adds {{super.setBinding(context)}} to the generated constructor 
and {{super()}} is added before this by {{AsmClassGenerator}}.  This was added 
in Groovy 2.4:

https://github.com/apache/groovy/blob/1ea6927ad9c6ec7e05266e1385e3787866c888ad/src/main/java/org/codehaus/groovy/ast/ModuleNode.java#L427

The creation of the signature (parameter array) has since been inlined.  Also 
there was mention of super constructor initialization problems.  When the 
script base class is set through configuration, its name is set as the script's 
super class and constructors are checked before the class is resolved.

> setScriptBaseClass with Java base class breaks @Field initialization from 
> Binding due to generated call to wrong constructor
> ----------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-8096
>                 URL: https://issues.apache.org/jira/browse/GROOVY-8096
>             Project: Groovy
>          Issue Type: Bug
>          Components: Compiler, GroovyScriptEngine
>    Affects Versions: 2.4.8
>            Reporter: Christoffer Hammarström
>            Priority: Major
>              Labels: test
>
> 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*
> {code:java}
>      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
>      }
> {code}
> instead of
> *EXPECTED BEHAVIOUR*
> {code:java}
>      Constructor(Binding context) {
>          super(context); // Fields are initialized after the call to 
> super(context)
>      }
> {code}
> 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:
> {code:java|title=GroovyShellTest2.groovy}
>     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
>     }
> {code}
> and the Java script base class:
> {code:java|title=BindingScript.java}
> 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.
>     }
> }
> {code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to