Skip to content

LDEV-6226 fix cross-classloader method resolution#2772

Open
zspitzer wants to merge 1 commit intolucee:6.2from
zspitzer:LDEV-6226
Open

LDEV-6226 fix cross-classloader method resolution#2772
zspitzer wants to merge 1 commit intolucee:6.2from
zspitzer:LDEV-6226

Conversation

@zspitzer
Copy link
Copy Markdown
Member

@zspitzer zspitzer commented Apr 2, 2026

Summary

Fixes intermittent NoSuchMethodException when calling Java methods from CFML after OSGi bundle refresh.

Root Cause

Clazz.getMethod() and Clazz.getConstructor() Phase 1 matching uses Class.isAssignableFrom() which does strict class identity comparison. When a Java object is persisted in a long-lived scope (e.g. variables.sfnClient) and an OSGi bundle refreshes overnight, the persisted object's class is from the old classloader while newly created arguments are from the new classloader. Same class name, different classloader = isAssignableFrom returns false = method not found.

ClazzDynamic caches by Class object (not name), so a new classloader gets a fresh cache entry — the cache itself is not the problem. The issue is purely in the type comparison during method resolution.

Fix

In Clazz.java, both getMethod() and getConstructor() Phase 1: try isAssignableFrom first (zero cost for the common same-classloader case), fall back to Reflector.isInstaneOf(argClass, paramClass, false) which does name-based comparison with superclass/interface hierarchy walking. This method already exists and is used elsewhere in Lucee.

Known Limitation

Method resolution is fixed, but the invocation path still has issues for this scenario:

  1. ASM-generated wrapper emits CHECKCAST using old classloader types → ClassCastException
  2. MethodInstance catches this and falls back to Method.invoke(), which also rejects cross-classloader args → IllegalArgumentException
  3. This is a hard JVM constraint — class identity is enforced at every invocation level

User-level mitigation: don't persist Java objects across bundle refreshes (recreate clients periodically or on error).

Test plan

  • LDEV6226.cfc — compiles a Java class to two separate dirs, loads each from an isolated URLClassLoader, verifies:
    • isAssignableFrom fails across classloaders but Reflector.isInstaneOf with exactMatch=false works
    • Clazz.getMethod() finds methods despite cross-classloader argument types

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.

1 participant