G
YouTube TikTok Games
Skip to content

GROOVY-12717: SC: report new of a protected constructor of another package and a protected method through a superclass receiver - #3251

Open
codeconsole wants to merge 5 commits into
apache:masterfrom
codeconsole:sc-protected-other-package
Open

codeconsole wants to merge 5 commits into
apache:masterfrom
codeconsole:sc-protected-other-package

Conversation

@codeconsole

Copy link
Copy Markdown
Contributor

Under @CompileStatic, this compiles and then fails when the class is loaded:

// package p
class A { protected A(String s) {}; protected String im() { 'im' }; protected String getQp() { 'qp' } }

// package q
@CompileStatic class B extends p.A {
    B() { super('b') }
    static test(p.A a) {
        new p.A('x')  // VerifyError: Bad access to protected <init> method
        a.im()        // VerifyError: Bad access to protected data in invokevirtual
        a.qp          // the same, through the getter
        java.util.function.Supplier<String> s = a::im  // BootstrapMethodError
    }
}

5.0.8 and 6.0.0 fail the same way. Neither has to do with class loaders: these are Java's rules for a protected member outside its package.

  • 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. That includes a map-style construction, which calls the protected no-argument constructor. 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.

Calls through this, super or a receiver of the subclass type are still direct, and A::new still selects such a constructor at run time.

An attribute assignment a.@f = v through a superclass receiver still fails verification in putfield. It is not changed here.

This and #3097 (GROOVY-12568) touch the same lines of StaticInvocationWriter, StaticTypesMethodReferenceExpressionWriter and ConstructorCallTransformer; whichever lands second keeps both. Once this is in, a map-style construction reaches the check of #3097 through writeInvokeConstructor, so its own call in ConstructorCallTransformer is not needed.

Compatibility

No working code is affected: this code failed verification or at link time. It now fails at compile time, under @CompileStatic only.

Tests

Groovy7325Bug.testProtectedMethodOfAnotherPackageThroughReceiverOfSuperclass, StaticCompileConstructorsTest.testProtectedConstructorOfAnotherPackage and testMapStyleConstructionWithProtectedConstructorOfAnotherPackage fail without the change. Groovy7325Bug.testProtectedMembersOfAnotherPackageThroughReceiverOfSubclass and the ConstructorsSTCTest additions pin what keeps working. 5,684 tests in org.codehaus.groovy.classgen.asm.sc, groovy.transform.stc and gls pass. The Checkstyle and CodeNarc gates pass.

…ckage instead of failing verification

Outside its package, a protected constructor can only be called by
super(...) or by creating an anonymous inner class (JLS 6.6.2.2). The type
checker accepts new A(...) of such a constructor in a subclass of A, as it
accepts other protected members there, and type-checked code creates the
object through the meta-class. Under static compilation, the call was
compiled to NEW and INVOKESPECIAL, and the class failed verification when
it was loaded: "VerifyError: Bad access to protected <init> method". The
same happened for a map-style construction such as new A([:]), which calls
the protected no-argument constructor.

The static compiler now reports "Cannot call protected constructor for A
from class B", as it reports a protected method called on a receiver that
is not the subclass (GROOVY-7325). super(...), this(...) and
new A(...) {} still call the constructor directly. A constructor reference
A::new to such a constructor still selects it at run time.
… through a superclass receiver

Outside its package, a protected instance method can be used only through
a receiver of the subclass (JLS 6.6.2.1, JVMS 4.10.1.8). The static
compiler meant to report such a call (GROOVY-7325), but its check compared
the types the wrong way round: it tested whether the current class is a
subtype of the receiver type instead of the reverse. So a.m() with a
receiver of the superclass type, in a subclass in another package, was
compiled to a direct call and the class failed verification: "VerifyError:
Bad access to protected data in invokevirtual". This also hit a protected
getter or setter used as a property (a.name, a.name = value). A method
reference such as a::m or A::m to such a method failed with
BootstrapMethodError.

The check now requires the receiver to be of the current class (or this,
super or a static method), so such a call or method reference is reported
as "Cannot access method", as GROOVY-7325 intended and as javac does. Calls
through this, super or a receiver of the subclass type are still direct.
@codecov-commenter

codecov-commenter commented Nov 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.96774% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.4627%. Comparing base (97e0f72) to head (0b62c0e).

Files with missing lines Patch % Lines
...groovy/classgen/asm/sc/StaticInvocationWriter.java 50.0000% 0 Missing and 3 partials ⚠️
...sc/StaticTypesMethodReferenceExpressionWriter.java 62.5000% 0 Missing and 3 partials ⚠️
...rm/sc/transformers/ConstructorCallTransformer.java 82.3529% 0 Missing and 3 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##               master      #3251        +/-   ##
==================================================
+ Coverage     74.4524%   74.4627%   +0.0103%     
- Complexity      44845      44862        +17     
==================================================
  Files            1702       1702                
  Lines          151995     152013        +18     
  Branches        29122      29131         +9     
==================================================
+ Hits           113164     113193        +29     
+ Misses          28422      28412        -10     
+ Partials        10409      10408         -1     
Files with missing lines Coverage Δ
...groovy/classgen/asm/sc/StaticInvocationWriter.java 77.5510% <50.0000%> (+0.0229%) ⬆️
...sc/StaticTypesMethodReferenceExpressionWriter.java 86.8201% <62.5000%> (-0.2287%) ⬇️
...rm/sc/transformers/ConstructorCallTransformer.java 94.0298% <82.3529%> (-0.5014%) ⬇️

... and 12 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@daniellansun daniellansun left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for GROOVY-12717.

Outside its package, a protected constructor may only be invoked via super(...) or an anonymous subclass (JLS 6.6.2.2), and a protected instance method needs a receiver of the calling subclass (JVMS 4.10.1.8). Reporting those at SC generation time — instead of a VerifyError / BootstrapMethodError at load — is the right UX, and leaving @TypeChecked on the meta-class path is consistent with dynamic Groovy.

Notable pieces: StaticInvocationWriter constructor accessibility error; protected instance-method receiver check excluding super / class-with-super; method-reference validation for protected cross-package receivers; map-style construction routed through the invocation writer so the same check applies. Tests cover ObjectInputStream/ObjectOutputStream, cross-package Groovy types, property accessors, bound and unbound references, and the allowed subclass-receiver / AIC / super() cases.

CI fully green. Companion to the GROOVY-12568 runtime-package work (#3097 and siblings); land with that family in mind. Soft adjacency to #3253 (indy refused constructor → meta-class) — complementary layers.

The tests were marked with GROOVY-12568, where they came from before the
change moved to its own issue.
…package

# Conflicts:
#	src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java
#	src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
#	src/main/java/org/codehaus/groovy/transform/sc/transformers/ConstructorCallTransformer.java
@testlens-app

testlens-app Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

🚨 All tests passed but jobs failed 🚨

Failed Jobs without Test Failures

❌ Lint gate / lint

🏷️ Commit: 0b62c0e
▶️ Tests: 128638 executed
⚪️ Checks: 26/26 completed

Rerun Controls

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app/docs.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants