[
https://issues.apache.org/jira/browse/GROOVY-12568?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18134521#comment-18134521
]
ASF GitHub Bot commented on GROOVY-12568:
-----------------------------------------
codeconsole opened a new pull request, #3097:
URL: https://github.com/apache/groovy/pull/3097
The JVM allows access to a protected or package-private member only within
the same run-time package, which is the package name and the class loader. A
`GroovyClassLoader` defines each compilation in memory with a class loader of
its own (a `GroovyShell` script, `parseClass`, a script run with `groovy`,
JSR-223), so a class from an earlier parse, or a precompiled class used by such
a script, is in another run-time package even with the same package name.
Statically compiled code accessed such members directly and failed with
`IllegalAccessError`:
```groovy
// first parse
package p
class A { @PackageScope A(String s) {}; @PackageScope String name() { 'a' };
@PackageScope String tag = 't' }
// second parse with the same GroovyShell
package p
@CompileStatic List make() { def a = new A('x'); [a.name(), a.tag] }
```
For a compilation defined in memory, statically compiled code now reaches
such a constructor, method or field through the meta-class, as dynamic code
does. This covers `new` and map-style construction, instance and static
methods, getters and setters used as properties, method references such as
`a::name` (through a synthetic method), and field reads and writes including
`.@`, `+=` and `++`. In dynamic code, `super.f`, `super.@f` and `this.@f` of
such a field and `super.m()` of such a method work too (no `super$` method is
generated for a package-private method of another run-time package). Public
members, classes of the same compilation, and protected members used from a
subclass through `this`, `super` or a receiver of the subclass type are still
accessed directly. Code compiled to class files (groovyc, Gradle, Maven, Ant,
joint compilation with Java) keeps direct access to the members of the other
classes of its package, as javac does, and a full and an incremental build give
the same bytecode. The rule is `AsmClassGenerator.isInSameRuntimePackage`,
which #3022 introduces for constructor references; this applies it to the rest.
Protected members of another package, in a subclass, under static
compilation:
- `new A(...)` of a protected constructor of another package is reported:
"Cannot call protected constructor for p.A from class q.B". As in Java, only
`super(...)` and `new A(...) {}` can call it. It used to compile and then fail
verification. The error is raised while the class is generated, so
`@TypeChecked` code is not checked for it and works through the meta-class.
- A protected method of another package called through a receiver of the
superclass type (`a.m()`, `a.name`, `a::m`, `A::m`) is reported as "Cannot
access method", as GROOVY-7325 intended: its check compared the types the wrong
way round. These used to compile and then fail with `VerifyError`. `a.@field =
value` through such a receiver now goes through the meta-class, like the read.
With invokedynamic, a dynamic `new` of a constructor that the JVM refuses,
such as `new ObjectInputStream()` in a subclass, now fails with "could not
access constructor", as it does without invokedynamic, instead of
`GroovyBugError`.
COMPATIBILITY.md has an entry for the meta-class semantics of these accesses
and the two new compile errors.
This builds on #3022 (constructor references), which adds
`isInSameRuntimePackage`, the `GroovyClassLoader` marker and
`StaticInvocationWriter.isDirectlyAccessible`; its commits are included here
until it is merged.
## Tests
All marked `// GROOVY-12568`, each failing without the change it covers:
- `StaticCompileRuntimePackageTest`: constructors, map-style construction
(literal and `Map` variable), methods, properties, method references (also in
static and default methods of an interface) and fields of a class of another
class loader, with and without invokedynamic, compared with the same code
compiled dynamically; bytecode tests pin that classes of the same compilation,
public members, class files, incremental builds and joint compilation keep
direct access;
- `bugs.Groovy12568`: dynamic `super.m()` of a package-private method of
another class loader, and invokedynamic `new` of a protected constructor of
another package or of the JDK;
- `ConstructorsSTCTest` and `StaticCompileConstructorsTest`: `new` of a
protected constructor of another package, with `super(...)` and anonymous
classes still direct;
- `Groovy7325Bug`: protected methods of another package through a receiver
of the superclass and of the subclass.
The `groovy.*`, `gls.*`, `bugs.*`, `org.codehaus.groovy.classgen` and
`org.codehaus.groovy.transform` packages pass (13,458 tests), apart from
`bugs.Groovy12363`, which also fails on master when it runs after
`bugs.Groovy12142` in the same JVM (GROOVY-12580, #3096, fixes it), and
`bugs.Groovy9815`, whose `@Grab` download failed once and passes when rerun.
The Checkstyle and CodeNarc gates pass.
> SC: protected and package-private members of a class defined by another
> GroovyClassLoader compilation fail with IllegalAccessError
> ----------------------------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12568
> URL: https://issues.apache.org/jira/browse/GROOVY-12568
> Project: Groovy
> Issue Type: Bug
> Components: Static compilation
> Affects Versions: 6.0.0
> Reporter: Scott Murphy Heiberg
> Priority: Major
>
> The JVM allows access to a protected or package-private member only within
> the same run-time package, which is the package name and the class loader. A
> {{GroovyClassLoader}} defines each compilation in memory with a class loader
> of its own: a {{GroovyShell}} script, {{parseClass}}, a script run with
> {{groovy}}, JSR-223. So a class of an earlier parse, or a precompiled class
> used by such a script, is in another run-time package even with the same
> package name. Statically compiled code calls such members directly and fails;
> dynamic code goes through the meta-class and works.
> {code:groovy}
> def shell = new GroovyShell()
> shell.evaluate '''package p
> class A {
> @groovy.transform.PackageScope A(String s) { }
> @groovy.transform.PackageScope String name() { 'a' }
> @groovy.transform.PackageScope String tag = 't'
> }
> true
> '''
> shell.evaluate '''package p
> @groovy.transform.CompileStatic
> List make() { def a = new A('x'); [a.name(), a.tag] }
> make()
> ''' // IllegalAccessError; without @CompileStatic: ['a', 't']
> {code}
> The same happens for map-style construction, instance and static methods,
> getters and setters used as properties, method references such as
> {{a::name}}, and field reads and writes including {{.@}}, {{+=}} and {{++}}.
> In dynamic code, {{super.f}}, {{super.@f}} and {{this.@f}} of such a field,
> and {{super.m()}} of such a method, fail the same way, because the {{super$}}
> helper and the field instructions are generated directly.
> Related, in a subclass in another package, under static compilation:
> * {{new A(...)}} of a protected constructor of another package compiles and
> then fails verification. As in Java, only {{super(...)}} and {{new A(...)
> {}}} can call it.
> * a protected method of another package called through a receiver of the
> superclass type ({{a.m()}}, {{a.name}}, {{a::m}}, {{A::m}}) compiles and then
> fails with {{VerifyError}}: the GROOVY-7325 check compared the types the
> wrong way round. {{a.@field = value}} through such a receiver fails
> verification too.
> * with invokedynamic, a dynamic {{new ObjectInputStream()}} in a subclass
> fails with {{GroovyBugError}} ("member is protected") instead of "could not
> access constructor".
> Expected: such members are reached through the meta-class, as in dynamic
> code; code compiled to class files (groovyc, Gradle, Maven, joint
> compilation) keeps direct access, as javac does; the two verification
> failures are compile errors.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)