Skip to content

Fix ConstructorUtils.getMatchingAccessibleConstructor on non-public classes - #1793

Open
Alwaysgaurav1 wants to merge 2 commits into
apache:masterfrom
Alwaysgaurav1:fix/constructor-utils-accessible-check
Open

Alwaysgaurav1 wants to merge 2 commits into
apache:masterfrom
Alwaysgaurav1:fix/constructor-utils-accessible-check

Conversation

@Alwaysgaurav1

Copy link
Copy Markdown
Contributor

Summary

Fixes ConstructorUtils.getMatchingAccessibleConstructor and ConstructorUtils.invokeConstructor to properly enforce class accessibility checks on non-public classes (e.g. package-private classes or public inner classes declared inside non-public outer classes), achieving consistency across exact matching, inexact matching, and MethodUtils (following PR #1783).

Problem

In ConstructorUtils.getMatchingAccessibleConstructor(Class<T> cls, Class<?>... parameterTypes):

  1. The exact-match fast-path attempted to resolve cls.getConstructor(parameterTypes) directly.
  2. In Java reflection, Class.getConstructor(...) returns any constructor with the public modifier, even if declaring class cls is package-private or enclosed in a non-public outer class.
  3. The fast-path called MemberUtils.setAccessibleWorkaround(...) directly and returned the constructor without verifying whether cls is accessible.
  4. Conversely, the fallback loop for inexact assignment-compatible matching filtered each candidate through getAccessibleConstructor(ctor), which checks MemberUtils.isAccessible(ctor) && isAccessible(ctor.getDeclaringClass()).

This created inconsistent behavior:

  • ConstructorUtils.getAccessibleConstructor(PackagePrivateBean.class, exactTypes) returned null.
  • ConstructorUtils.getMatchingAccessibleConstructor(PackagePrivateBean.class, inexactTypes) returned null.
  • But ConstructorUtils.getMatchingAccessibleConstructor(PackagePrivateBean.class, exactTypes) bypassed the check and returned the constructor.
  • Consequently, ConstructorUtils.invokeConstructor(PackagePrivateBean.class, exactArgs) attempted reflective instantiation on non-public classes (violating access control and throwing IllegalAccessException or InaccessibleObjectException under JPMS), whereas inexact arguments threw NoSuchMethodException("No such accessible constructor on object: ...").

Changes

  1. ConstructorUtils.java:

    • In getMatchingAccessibleConstructor:
      • Added an early return if (!isAccessible(cls)) return null;. Since constructors are never inherited and always belong to their declaring class, an inaccessible class can never have an accessible constructor.
      • In the exact-match try block, verified the candidate using getAccessibleConstructor(cls.getConstructor(parameterTypes)) before returning.
    • In getAccessibleConstructor(Class<T> cls, Class<?>... parameterTypes):
      • Added early return if (!isAccessible(cls)) return null; for consistent fast-path checking.
  2. ConstructorUtilsTest.java:

    • Added testGetMatchingAccessibleConstructorOnNonPublicClass asserting that getMatchingAccessibleConstructor returns null for non-public classes and public inner classes of non-public outer classes (for both exact and inexact signatures).
    • Added testInvokeConstructorOnNonPublicClass and testInvokeExactConstructorOnNonPublicClass asserting that NoSuchMethodException is consistently thrown.

@Alwaysgaurav1

Copy link
Copy Markdown
Contributor Author

Hi @garydgregory,

This is a companion fix to #1783 (MethodUtils.invokeMethod on instances of non-public classes).

While #1783 addressed method lookup on non-public classes, ConstructorUtils.getMatchingAccessibleConstructor had the identical issue in its exact-match fast path, bypassing getAccessibleConstructor checks on non-public classes. This PR brings ConstructorUtils into full alignment.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

Accessibility handling is consistently fixed and covered by regression tests.

Pull request overview

Fixes constructor lookup and invocation accessibility handling for non-public classes and nested classes.

Changes:

  • Enforces class-level accessibility checks.
  • Validates exact constructor matches consistently.
  • Adds regression tests for matching and invocation behavior.
File summaries
File Description
src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java Adds coverage for non-public class scenarios.
src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java Enforces accessibility consistently for constructors.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@garydgregory

Copy link
Copy Markdown
Member

@Alwaysgaurav1
These change now cause the documentation to no longer match the behavior. Specifically, you should update the class-level Javadoc to match the new behavior. The new accessibility guard rejects package-private classes immediately, but the "Known Limitations" section still promises a setAccessible(true) workaround for their public constructors. Remove or revise that explanation and document that these calls now return null or throw NoSuchMethodException.

@garydgregory garydgregory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See my comment.

@garydgregory
garydgregory marked this pull request as draft September 18, 2026 20:48
@Alwaysgaurav1

Copy link
Copy Markdown
Contributor Author

Thanks for the review, @garydgregory! I have updated the class-level Javadoc in ConstructorUtils to remove the obsolete setAccessible(true) workaround description and explicitly document that constructor lookup methods return null and invocation methods throw NoSuchMethodException for non-public classes.

@Alwaysgaurav1
Alwaysgaurav1 marked this pull request as ready for review September 19, 2026 05:42
@Alwaysgaurav1
Alwaysgaurav1 requested review from garydgregory and a lite review from Copilot September 19, 2026 05:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Early returns can bypass documented SecurityManager package-access checks.

Review effort: Lite
Findings: None

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