Skip to content

Commit 7583c01

Browse files
committed
LDEV-6303 ExpressionDefault wrapper and duplicate re-fire
1 parent 3c2e677 commit 7583c01

14 files changed

Lines changed: 824 additions & 3 deletions

File tree

core/src/main/java/lucee/runtime/ComponentImpl.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@
115115
import lucee.runtime.type.scope.ArgumentImpl;
116116
import lucee.runtime.type.scope.ArgumentIntKey;
117117
import lucee.runtime.type.scope.Variables;
118+
119+
import java.lang.reflect.Method;
120+
import java.util.concurrent.ConcurrentHashMap;
118121
import lucee.runtime.type.util.ArrayUtil;
119122
import lucee.runtime.type.util.ComponentUtil;
120123
import lucee.runtime.type.util.KeyConstants;
@@ -367,6 +370,8 @@ public ComponentImpl _duplicate(boolean deepCopy, boolean isTop) {
367370
if (useShadow) {
368371
addUDFS(trg, ((ComponentScopeShadow) scope).getShadow(), ((ComponentScopeShadow) trg.scope).getShadow());
369372
}
373+
374+
reFireExpressionDefaults(trg);
370375
}
371376
}
372377
finally {
@@ -376,6 +381,69 @@ public ComponentImpl _duplicate(boolean deepCopy, boolean isTop) {
376381
return trg;
377382
}
378383

384+
// SEED_METHOD_NONE flags "Page class compiled by pre-LDEV-6303 Lucee" (cross-version compat).
385+
private static final java.util.Map<Class<?>, Method> SEED_METHOD_CACHE = new ConcurrentHashMap<Class<?>, Method>();
386+
private static final Method SEED_METHOD_NONE;
387+
static {
388+
Method tmp = null;
389+
try {
390+
tmp = Object.class.getDeclaredMethod("hashCode");
391+
}
392+
catch (NoSuchMethodException e) {}
393+
SEED_METHOD_NONE = tmp;
394+
}
395+
396+
private static Method lookupSeedMethod(Class<?> pageClass) {
397+
Method m = SEED_METHOD_CACHE.get(pageClass);
398+
if (m != null) return m == SEED_METHOD_NONE ? null : m;
399+
try {
400+
m = pageClass.getDeclaredMethod("_seedExpressionDefaults", PageContext.class);
401+
SEED_METHOD_CACHE.put(pageClass, m);
402+
return m;
403+
}
404+
catch (NoSuchMethodException e) {
405+
SEED_METHOD_CACHE.put(pageClass, SEED_METHOD_NONE);
406+
return null;
407+
}
408+
}
409+
410+
private static void reFireExpressionDefaults(ComponentImpl trg) {
411+
PageContext pc = ThreadLocalPageContext.get();
412+
if (pc == null) return;
413+
414+
Variables prevVars = pc.variablesScope();
415+
try {
416+
if (trg.scope instanceof Variables) pc.setVariablesScope((Variables) trg.scope);
417+
418+
ComponentImpl walker = trg;
419+
while (walker != null) {
420+
if (walker.pageSource != null) {
421+
try {
422+
lucee.runtime.Page page = walker.pageSource.loadPage(pc, false);
423+
if (page != null) {
424+
Method seed = lookupSeedMethod(page.getClass());
425+
if (seed != null) {
426+
try {
427+
seed.invoke(null, pc);
428+
}
429+
catch (Throwable t) {
430+
ExceptionUtil.rethrowIfNecessary(t);
431+
}
432+
}
433+
}
434+
}
435+
catch (PageException pe) {
436+
break;
437+
}
438+
}
439+
walker = (ComponentImpl) walker.base;
440+
}
441+
}
442+
finally {
443+
if (prevVars != null) pc.setVariablesScope(prevVars);
444+
}
445+
}
446+
379447
private static void addUDFS(ComponentImpl trgComp, Map src, Map trg) {
380448
if (!src.isEmpty()) {
381449
Iterator it = src.entrySet().iterator();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package lucee.runtime.component;
2+
3+
/** Immutable source-string wrapper for non-foldable cfproperty expression-form defaults. */
4+
public final class ExpressionDefault {
5+
6+
private final String source;
7+
8+
public ExpressionDefault(String source) {
9+
this.source = source == null ? "" : source;
10+
}
11+
12+
public String getSource() {
13+
return source;
14+
}
15+
16+
@Override
17+
public String toString() {
18+
return source;
19+
}
20+
21+
@Override
22+
public boolean equals(Object obj) {
23+
if (this == obj) return true;
24+
if (!(obj instanceof ExpressionDefault)) return false;
25+
return source.equals(((ExpressionDefault) obj).source);
26+
}
27+
28+
@Override
29+
public int hashCode() {
30+
return source.hashCode();
31+
}
32+
}

core/src/main/java/lucee/runtime/component/PropertyImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ protected final int hash() {
8181
@Override
8282
public String getDefault() {
8383
if (_default == null) return null;
84+
// expression-form: source string would corrupt callers piping it into scope.set
85+
if (_default instanceof ExpressionDefault) return null;
8486
try {
8587
return Caster.toString(_default);
8688
}

core/src/main/java/lucee/transformer/bytecode/PageImpl.java

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,8 @@ else if (functions.length <= 10) {
765765
// newInstance/initComponent/call
766766
writeOutStatic(optionalPS, constr, keys, cw, comp, className);
767767

768+
writeOutSeedExpressionDefaults(optionalPS, constr, keys, cw, comp, className);
769+
768770
// set field subs
769771
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE, "subs", "[Llucee/runtime/CIPage;", null, null);
770772
fv.visitEnd();
@@ -1229,10 +1231,9 @@ private void writeOutStatic(PageSource optionalPS, ConstrBytecodeContext constr,
12291231
// prop.setDefault(value) if it's a simple literal
12301232
// Only handle simple literals (Literal interface) - complex expressions like now()
12311233
// or #myVar# need PageContext and must be evaluated at runtime, not class-load time
1232-
if (propDefaultAttr != null && propDefaultAttr.getValue() instanceof Literal) {
1234+
if (propDefaultAttr != null && propDefaultAttr.getValue() != null) {
12331235
Expression defaultExpr = propDefaultAttr.getValue();
12341236

1235-
// Handle simple literals only - complex expressions handled at runtime
12361237
if (defaultExpr instanceof LitStringImpl) {
12371238
String value = ((LitStringImpl) defaultExpr).getString();
12381239
ga.loadLocal(propLocal);
@@ -1253,7 +1254,31 @@ else if (defaultExpr instanceof LitBooleanImpl) {
12531254
ga.box(Type.BOOLEAN_TYPE);
12541255
ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setDefault", Type.VOID_TYPE, new Type[] { Types.OBJECT }));
12551256
}
1256-
// else: complex expression - will be handled at runtime in TagProperty
1257+
else {
1258+
String source;
1259+
try {
1260+
int start = defaultExpr.getStart().pos;
1261+
int end = defaultExpr.getEnd().pos;
1262+
// json() consumes opening { or [ before recording position; back up
1263+
if (start > 0) {
1264+
String peek = sourceCode.subCFMLString(start - 1, 1).toString();
1265+
if (peek.length() == 1 && (peek.charAt(0) == '{' || peek.charAt(0) == '[')) {
1266+
start--;
1267+
}
1268+
}
1269+
source = sourceCode.subCFMLString(start, end - start).toString();
1270+
}
1271+
catch (Exception e) {
1272+
source = "";
1273+
}
1274+
Type EXPRESSION_DEFAULT = Type.getType("Llucee/runtime/component/ExpressionDefault;");
1275+
ga.loadLocal(propLocal);
1276+
ga.newInstance(EXPRESSION_DEFAULT);
1277+
ga.dup();
1278+
ga.push(source);
1279+
ga.invokeConstructor(EXPRESSION_DEFAULT, new Method("<init>", Type.VOID_TYPE, new Type[] { Types.STRING }));
1280+
ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setDefault", Type.VOID_TYPE, new Type[] { Types.OBJECT }));
1281+
}
12571282
}
12581283

12591284
// Collect dynamic attributes (non-standard attributes)
@@ -1540,6 +1565,59 @@ else if (defaultExpr instanceof LitBooleanImpl) {
15401565

15411566
}
15421567

1568+
private void writeOutSeedExpressionDefaults(PageSource optionalPS, ConstrBytecodeContext constr, Map<LitString, Integer> keys, ClassWriter cw, TagCIObject component,
1569+
String name) throws TransformerException {
1570+
if (component == null || component.getBody() == null) return;
1571+
List<Statement> statements = component.getBody().getStatements();
1572+
if (statements == null) return;
1573+
1574+
List<TagProperty> exprDefProps = new ArrayList<TagProperty>();
1575+
for (Statement stmt: statements) {
1576+
if (!(stmt instanceof TagProperty)) continue;
1577+
TagProperty tagProp = (TagProperty) stmt;
1578+
Attribute defaultAttr = tagProp.getAttribute("default");
1579+
if (defaultAttr == null || defaultAttr.getValue() == null) continue;
1580+
Expression defaultExpr = defaultAttr.getValue();
1581+
if (defaultExpr instanceof LitStringImpl || defaultExpr instanceof LitNumberImpl || defaultExpr instanceof LitBooleanImpl) continue;
1582+
Attribute nameAttr = tagProp.getAttribute("name");
1583+
if (nameAttr == null || nameAttr.getValue() == null) continue;
1584+
exprDefProps.add(tagProp);
1585+
}
1586+
1587+
// Method shell always emitted on component classes — empty body is JIT no-op; ensures
1588+
// presence-of-method is stable for ComponentImpl._duplicate's reflection lookup.
1589+
Method seedMethod = new Method("_seedExpressionDefaults", Types.VOID, new Type[] { Types.PAGE_CONTEXT });
1590+
GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, seedMethod, null, new Type[] { Types.THROWABLE }, cw);
1591+
BytecodeContext bc = new BytecodeContext(config, optionalPS, constr, this, keys, cw, name, ga, seedMethod, writeLog(), suppressWSbeforeArg, output, returnValue,
1592+
sourceCode.getSourceOffset());
1593+
1594+
Method METHOD_VARIABLES_SCOPE = new Method("variablesScope", Types.VARIABLES, new Type[] {});
1595+
Method METHOD_SET_EL = new Method("setEL", Types.OBJECT, new Type[] { Types.COLLECTION_KEY, Types.OBJECT });
1596+
1597+
for (TagProperty tagProp: exprDefProps) {
1598+
Attribute nameAttr = tagProp.getAttribute("name");
1599+
Attribute defaultAttr = tagProp.getAttribute("default");
1600+
String propName = nameAttr.getValue() instanceof Literal ? ((Literal) nameAttr.getValue()).getString() : null;
1601+
if (propName == null) continue;
1602+
Expression defaultExpr = defaultAttr.getValue();
1603+
1604+
defaultExpr.writeOut(bc, Expression.MODE_REF);
1605+
int defaultLocal = ga.newLocal(Types.OBJECT);
1606+
ga.storeLocal(defaultLocal);
1607+
1608+
ga.loadArg(0);
1609+
ga.invokeVirtual(Types.PAGE_CONTEXT, METHOD_VARIABLES_SCOPE);
1610+
ga.push(propName);
1611+
ga.invokeStatic(KEY_IMPL, KEY_INIT);
1612+
ga.loadLocal(defaultLocal);
1613+
ga.invokeInterface(Types.SCOPE, METHOD_SET_EL);
1614+
ga.pop();
1615+
}
1616+
1617+
ga.returnValue();
1618+
ga.endMethod();
1619+
}
1620+
15431621
private String getTagAttributeValue(Tag tag, String attrName) {
15441622
Attribute attr = tag.getAttribute(attrName);
15451623
if (attr != null && attr.getValue() != null) {

0 commit comments

Comments
 (0)