Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions core/src/main/java/lucee/runtime/ComponentImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,8 @@
import lucee.runtime.type.Struct;
import lucee.runtime.type.StructImpl;
import lucee.runtime.type.UDF;
import lucee.runtime.type.BoundUDF;
import lucee.runtime.type.UDFGSProperty;
import lucee.runtime.type.UDFGetterProperty;
import lucee.runtime.type.UDFSetterProperty;
import lucee.runtime.type.UDFImpl;
import lucee.runtime.type.UDFPlus;
import lucee.runtime.type.UDFProperties;
Expand Down Expand Up @@ -450,6 +449,18 @@ public static Map<Key, UDF> duplicateUTFMap(ComponentImpl src, ComponentImpl trg
for (Entry<Key, UDF> e: srcMap.entrySet()) {
udf = e.getValue();

// LDEV-6298 v2: share the flyweight accessor across original + duplicate.
// Slow-path dispatch is now bound at extraction time via BoundUDF, so it no longer
// trusts srcComponent — sharing is safe. pageSource compare so chained duplicates match.
// LDEV-3335: null owner means a stateless class-level flyweight (pool entry) — share unconditionally.
if (udf instanceof UDFGSProperty) {
Component owner = udf.getOwnerComponent();
if (owner == null || owner.getPageSource() == src.getPageSource()) {
trgMap.put(e.getKey(), udf);
}
continue;
}

if (udf.getOwnerComponent() == src) {
UDF clone = e.getValue().duplicate();
if (clone instanceof UDFPlus) {
Expand Down Expand Up @@ -770,14 +781,13 @@ else if (_namedArgs != null) {

Object _call(PageContext pc, Collection.Key calledName, UDF udf, Struct namedArgs, Object[] args) throws PageException {

// LDEV-6236 accessor bypass — skip full UDF dispatch for generated getters/setters
if (!((PageContextImpl) pc).hasDebugOptions(ConfigPro.DEBUG_TEMPLATE)) {
if (udf instanceof UDFGetterProperty) {
return ((UDFGetterProperty) udf).callDirect( this, pc );
}
if (udf instanceof UDFSetterProperty && args != null) {
return ((UDFSetterProperty) udf).callDirect( this, pc, args );
}
// LDEV-6236 accessor bypass — skip full UDF dispatch for generated getters/setters.
// Guard both args paths: setter named-arg dispatch via UDFUtil.argumentCollection NPEs on null values.
if (!((PageContextImpl) pc).hasDebugOptions(ConfigPro.DEBUG_TEMPLATE) && udf instanceof UDFGSProperty) {
UDFGSProperty gs = (UDFGSProperty) udf;
if (args != null) return gs._call(pc, this, args);
if (namedArgs != null) return gs._callWithNamedValues(pc, this, namedArgs);
// both null — fall through to the slow path which has its own arg handling
}

Object rtn = null;
Expand Down Expand Up @@ -1988,6 +1998,8 @@ private Object _set(PageContext pc, Collection.Key key, Object value, int access
Member m = (Member) value;
if (m instanceof UDF) {
UDF udf = (UDF) m;
// LDEV-1962: unwrap BoundUDF on mixin assign — host component becomes the receiver.
if (udf instanceof BoundUDF) udf = ((BoundUDF) udf).getInner();
if (udf.getAccess() > Component.ACCESS_PUBLIC && udf instanceof UDFPlus) ((UDFPlus) udf).setAccess(Component.ACCESS_PUBLIC);
_data.put(key, udf);
_udfs.put(key, udf);
Expand Down Expand Up @@ -2143,7 +2155,7 @@ public final Object put(Object key, Object value) {
@Override
public Object get(PageContext pc, Collection.Key key) throws PageException {
Member member = getMember(pc, key, true, false);
if (member != null) return member.getValue();
if (member != null) return accessorOrValue(member);

// trigger
if (triggerDataMember(pc) && !isPrivate(pc)) {
Expand All @@ -2155,6 +2167,12 @@ public Object get(PageContext pc, Collection.Key key) throws PageException {
// ["+name+"]");
}

// LDEV-6298 v2: bind shared accessor flyweight to this instance for slow-path extraction.
private Object accessorOrValue(Member member) {
if (member instanceof UDFGSProperty) return new BoundUDF((UDFGSProperty) member, this);
return member.getValue();
}

private Object callGetter(PageContext pc, Collection.Key key) throws PageException {
Key getterName = KeyImpl.init("get" + key.getLowerString());
Member member = getMember(pc, getterName, false, false);
Expand Down Expand Up @@ -2211,7 +2229,7 @@ public Object get(int access, String name) throws PageException {
@Override
public Object get(int access, Collection.Key key) throws PageException {
Member member = getMember(access, key, true, false);
if (member != null) return member.getValue();
if (member != null) return accessorOrValue(member);

// Trigger
PageContext pc = ThreadLocalPageContext.get();
Expand All @@ -2224,7 +2242,7 @@ public Object get(int access, Collection.Key key) throws PageException {
@Override
public Object get(PageContext pc, Collection.Key key, Object defaultValue) {
Member member = getMember(pc, key, true, false);
if (member != null) return member.getValue();
if (member != null) return accessorOrValue(member);

// trigger
if (triggerDataMember(pc) && !isPrivate(pc)) {
Expand Down Expand Up @@ -2253,7 +2271,7 @@ protected Object get(int access, String name, Object defaultValue) {
@Override
public Object get(int access, Collection.Key key, Object defaultValue) {
Member member = getMember(access, key, true, false);
if (member != null) return member.getValue();
if (member != null) return accessorOrValue(member);

// trigger
PageContext pc = ThreadLocalPageContext.get();
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/lucee/runtime/ComponentScopeShadow.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import lucee.runtime.op.Duplicator;
import lucee.runtime.type.Collection;
import lucee.runtime.type.Struct;
import lucee.runtime.type.BoundUDF;
import lucee.runtime.type.StructImpl;
import lucee.runtime.type.UDF;
import lucee.runtime.type.dt.DateTime;
Expand Down Expand Up @@ -215,6 +216,9 @@ public Object removeEL(Key key) {
public Object set(Collection.Key key, Object value) throws ApplicationException {
if (key.equalsIgnoreCase(KeyConstants._this) || key.equalsIgnoreCase(KeyConstants._super) || key.equalsIgnoreCase(KeyConstants._static)) return value;

// LDEV-1962: mirror ComponentImpl._set — unwrap BoundUDF on assign so mixin rebind applies.
if (value instanceof BoundUDF) value = ((BoundUDF) value).getInner();

if (!component.afterConstructor && value instanceof UDF) {
component.addConstructorUDF(key, (UDF) value);
}
Expand Down
Loading
Loading