Skip to content

Commit a01c405

Browse files
committed
Rewrite proc parameter execution
Fixes issue where variables in default parameter value expressions resolve to previous parameters instead of variables from the outer scope in runtime.
1 parent 9f0c6b5 commit a01c405

File tree

1 file changed

+99
-51
lines changed

1 file changed

+99
-51
lines changed

src/main/java/com/laytonsmith/core/functions/DataHandling.java

Lines changed: 99 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,9 +1554,8 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes)
15541554
public static Procedure getProcedure(Target t, Environment env, Script parent, ParseTree... nodes) {
15551555
String name = "";
15561556
List<IVariable> vars = new ArrayList<>();
1557-
ParseTree tree = null;
15581557
List<String> varNames = new ArrayList<>();
1559-
boolean usesAssign = false;
1558+
boolean procDefinitelyNotConstant = false;
15601559
CClassType returnType = Auto.TYPE;
15611560
NodeModifiers modifiers = null;
15621561
if(nodes[0].getData().equals(CVoid.VOID) || nodes[0].getData().isInstanceOf(CClassType.TYPE)) {
@@ -1575,71 +1574,120 @@ public static Procedure getProcedure(Target t, Environment env, Script parent, P
15751574
nodes[0].getNodeModifiers().merge(modifiers);
15761575
// We have to restore the variable list once we're done
15771576
IVariableList originalList = env.getEnv(GlobalEnv.class).GetVarList().clone();
1578-
for(int i = 0; i < nodes.length; i++) {
1579-
if(i == nodes.length - 1) {
1580-
tree = nodes[i];
1581-
} else {
1582-
boolean thisNodeIsAssign = false;
1583-
if(nodes[i].getData() instanceof CFunction) {
1584-
String funcName = nodes[i].getData().val();
1585-
if(funcName.equals(assign.NAME) || funcName.equals(__unsafe_assign__.NAME)) {
1586-
thisNodeIsAssign = true;
1587-
if((nodes[i].getChildren().size() == 3 && Construct.IsDynamicHelper(nodes[i].getChildAt(0).getData()))
1588-
|| Construct.IsDynamicHelper(nodes[i].getChildAt(1).getData())) {
1589-
usesAssign = true;
1590-
}
1591-
} else if(funcName.equals(__autoconcat__.NAME)) {
1592-
throw new CREInvalidProcedureException("Invalid arguments defined for procedure", t);
1577+
1578+
// Get code block node.
1579+
ParseTree code = nodes[nodes.length - 1];
1580+
1581+
// Execute default parameter values.
1582+
Mixed[] paramDefaultValues = new Mixed[nodes.length - 1];
1583+
for(int i = 1; i < nodes.length - 1; i++) { // Skip proc name and code block nodes.
1584+
ParseTree node = nodes[i];
1585+
if(node.getData() instanceof CFunction cf) {
1586+
if(cf.val().equals(assign.NAME) || cf.val().equals(__unsafe_assign__.NAME)) {
1587+
ParseTree paramDefaultValueNode = node.getChildAt(node.numberOfChildren() - 1);
1588+
env.getEnv(GlobalEnv.class).SetFlag(GlobalEnv.FLAG_VAR_ARGS_ALLOWED, true);
1589+
try {
1590+
paramDefaultValues[i] = parent.eval(paramDefaultValueNode, env);
1591+
} finally {
1592+
env.getEnv(GlobalEnv.class).ClearFlag(GlobalEnv.FLAG_VAR_ARGS_ALLOWED);
15931593
}
1594+
if(paramDefaultValues[i] instanceof IVariable ivar) {
1595+
paramDefaultValues[i] = env.getEnv(GlobalEnv.class)
1596+
.GetVarList().get(ivar.getVariableName(), t, true, env).ival();
1597+
}
1598+
1599+
// Mark proc as not constant if a default parameter is not a constant.
1600+
if(Construct.IsDynamicHelper(paramDefaultValueNode.getData())) {
1601+
procDefinitelyNotConstant = true;
1602+
}
1603+
continue;
1604+
} else if(cf.val().equals(__autoconcat__.NAME)) {
1605+
throw new CREInvalidProcedureException("Invalid arguments defined for procedure", t);
15941606
}
1607+
}
1608+
paramDefaultValues[i] = null;
1609+
}
1610+
1611+
// Collect parameter IVariable objects with default parameter value assigned.
1612+
for(int i = 0; i < nodes.length - 1; i++) {
1613+
IVariable ivar;
1614+
1615+
// Get IVariable from parameter with type and/or default value.
1616+
Mixed paramDefaultValue = paramDefaultValues[i];
1617+
if(paramDefaultValue != null) {
1618+
1619+
// Construct temporary assign node to assign resulting default parameter value.
1620+
ParseTree assignNode = nodes[i];
1621+
CFunction assignFunc = (CFunction) assignNode.getData();
1622+
ParseTree tempAssignNode = new ParseTree(new CFunction(assignFunc.val(),
1623+
assignNode.getTarget()), assignNode.getFileOptions());
1624+
if(assignNode.numberOfChildren() == 3) {
1625+
tempAssignNode.addChild(assignNode.getChildAt(0));
1626+
tempAssignNode.addChild(assignNode.getChildAt(1));
1627+
tempAssignNode.addChild(new ParseTree(paramDefaultValue, assignNode.getFileOptions()));
1628+
} else {
1629+
tempAssignNode.addChild(assignNode.getChildAt(0));
1630+
tempAssignNode.addChild(new ParseTree(paramDefaultValue, assignNode.getFileOptions()));
1631+
}
1632+
1633+
// Assign resulting default parameter value to IVariable.
15951634
env.getEnv(GlobalEnv.class).SetFlag(GlobalEnv.FLAG_NO_CHECK_DUPLICATE_ASSIGN, true);
15961635
env.getEnv(GlobalEnv.class).SetFlag(GlobalEnv.FLAG_VAR_ARGS_ALLOWED, true);
1597-
Mixed cons;
15981636
try {
1599-
cons = parent.eval(nodes[i], env);
1637+
ivar = (IVariable) parent.eval(tempAssignNode, env);
16001638
} finally {
16011639
env.getEnv(GlobalEnv.class).ClearFlag(GlobalEnv.FLAG_VAR_ARGS_ALLOWED);
16021640
env.getEnv(GlobalEnv.class).ClearFlag(GlobalEnv.FLAG_NO_CHECK_DUPLICATE_ASSIGN);
16031641
}
1642+
} else {
1643+
1644+
// Execute node to obtain proc name or IVariable object.
1645+
env.getEnv(GlobalEnv.class).SetFlag(GlobalEnv.FLAG_NO_CHECK_DUPLICATE_ASSIGN, true);
1646+
env.getEnv(GlobalEnv.class).SetFlag(GlobalEnv.FLAG_VAR_ARGS_ALLOWED, true);
1647+
Mixed val;
1648+
try {
1649+
val = parent.eval(nodes[i], env);
1650+
} finally {
1651+
env.getEnv(GlobalEnv.class).ClearFlag(GlobalEnv.FLAG_VAR_ARGS_ALLOWED);
1652+
env.getEnv(GlobalEnv.class).ClearFlag(GlobalEnv.FLAG_NO_CHECK_DUPLICATE_ASSIGN);
1653+
}
1654+
1655+
// Handle proc name node.
16041656
if(i == 0) {
1605-
if(cons instanceof IVariable) {
1657+
if(val instanceof IVariable) {
16061658
throw new CREInvalidProcedureException("Anonymous Procedures are not allowed", t);
16071659
}
1608-
name = cons.val();
1609-
} else {
1610-
if(!(cons instanceof IVariable)) {
1611-
throw new CREInvalidProcedureException("You must use IVariables as the arguments", t);
1612-
}
1613-
IVariable ivar = null;
1614-
try {
1615-
Mixed c = cons;
1616-
String varName = ((IVariable) c).getVariableName();
1617-
if(varNames.contains(varName)) {
1618-
throw new CREInvalidProcedureException("Same variable name defined twice in " + name, t);
1619-
}
1620-
varNames.add(varName);
1621-
while(c instanceof IVariable) {
1622-
c = env.getEnv(GlobalEnv.class).GetVarList().get(((IVariable) c).getVariableName(), t,
1623-
true, env).ival();
1624-
}
1625-
if(!thisNodeIsAssign) {
1626-
//This is required because otherwise a default value that's already in the environment
1627-
//would end up getting set to the existing value, thereby leaking in the global env
1628-
//into this proc, if the call to the proc didn't have a value in this slot.
1629-
c = new CString("", t);
1630-
}
1631-
ivar = new IVariable(((IVariable) cons).getDefinedType(),
1632-
((IVariable) cons).getVariableName(), c.clone(), t, env);
1633-
} catch (CloneNotSupportedException ex) {
1634-
//
1635-
}
1636-
vars.add(ivar);
1660+
name = val.val();
1661+
continue;
16371662
}
1663+
1664+
// Handle proc parameter node.
1665+
if(!(val instanceof IVariable)) {
1666+
throw new CREInvalidProcedureException("You must use IVariables as the arguments", t);
1667+
}
1668+
ivar = (IVariable) val;
1669+
}
1670+
1671+
// Check for duplicate parameter names.
1672+
String varName = ivar.getVariableName();
1673+
if(varNames.contains(varName)) {
1674+
throw new CREInvalidProcedureException("Same variable name defined twice in " + name, t);
16381675
}
1676+
varNames.add(varName);
1677+
1678+
// Get IVariable value.
1679+
Mixed ivarVal = (paramDefaultValue != null ? paramDefaultValue : new CString("", t));
1680+
1681+
// Store IVariable object clone.
1682+
vars.add(new IVariable(ivar.getDefinedType(), ivar.getVariableName(), ivarVal, ivar.getTarget()));
16391683
}
1684+
1685+
// Restore variable list.
16401686
env.getEnv(GlobalEnv.class).SetVarList(originalList);
1641-
Procedure myProc = new Procedure(name, returnType, vars, nodes[0].getNodeModifiers().getComment(), tree, t);
1642-
if(usesAssign) {
1687+
1688+
// Create and return procedure.
1689+
Procedure myProc = new Procedure(name, returnType, vars, nodes[0].getNodeModifiers().getComment(), code, t);
1690+
if(procDefinitelyNotConstant) {
16431691
myProc.definitelyNotConstant();
16441692
}
16451693
return myProc;

0 commit comments

Comments
 (0)