Skip to content

LDEV-6226 fix cross-classloader method resolution#2772

Merged
michaeloffner merged 3 commits intolucee:6.2from
zspitzer:LDEV-6226
Apr 12, 2026
Merged

LDEV-6226 fix cross-classloader method resolution#2772
michaeloffner merged 3 commits 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

@michaeloffner michaeloffner merged commit 15b0d10 into lucee:6.2 Apr 12, 2026
2 checks passed
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.

2 participants