diff --git a/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java b/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java index 8e810e72b..bf1655b23 100644 --- a/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java +++ b/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java @@ -1579,7 +1579,7 @@ public static ParseTree compile(TokenStream stream, Environment environment, environment.getEnv(CompilerEnvironment.class).addCompilerWarning(fileOptions, warning); f = new ParseTree(new CFunction(Compiler.__autoconcat__.NAME, unknown), fileOptions); } else { - f = new ParseTree(new CFunction(Compiler.p.NAME, unknown), fileOptions); + f = new ParseTree(new CFunction(Compiler.p.NAME, t.getTarget()), fileOptions); } constructCount.push(new AtomicInteger(0)); tree.addChild(f); diff --git a/src/main/java/com/laytonsmith/core/constructs/IVariable.java b/src/main/java/com/laytonsmith/core/constructs/IVariable.java index 6460bed0d..e1a30dd1c 100644 --- a/src/main/java/com/laytonsmith/core/constructs/IVariable.java +++ b/src/main/java/com/laytonsmith/core/constructs/IVariable.java @@ -96,6 +96,14 @@ public IVariable clone() throws CloneNotSupportedException { return clone; } + /** + * Create a clone of this {@link IVariable} using the same variable value reference. + * @return The clone. + */ + public IVariable shallowClone() { + return new IVariable(type, name, varValue, definedTarget); + } + @Override public boolean isDynamic() { return true; diff --git a/src/main/java/com/laytonsmith/core/constructs/IVariableList.java b/src/main/java/com/laytonsmith/core/constructs/IVariableList.java index ee9cc597a..4a7e32bfe 100644 --- a/src/main/java/com/laytonsmith/core/constructs/IVariableList.java +++ b/src/main/java/com/laytonsmith/core/constructs/IVariableList.java @@ -79,6 +79,10 @@ public void set(IVariable v) { varList.put(v.getVariableName(), v); } + public IVariable get(String name) { + return varList.get(name); + } + public IVariable get(String name, Target t, boolean bypassAssignedCheck, Environment env) { IVariable v = varList.get(name); if(v == null) { @@ -133,7 +137,9 @@ public String toString() { @Override public IVariableList clone() { IVariableList clone = new IVariableList(this); - clone.varList = new HashMap<>(varList); + for(IVariable var : varList.values()) { + clone.set(var.shallowClone()); + } return clone; } diff --git a/src/main/java/com/laytonsmith/core/functions/Compiler.java b/src/main/java/com/laytonsmith/core/functions/Compiler.java index 8e553e1f7..70e4c03a8 100644 --- a/src/main/java/com/laytonsmith/core/functions/Compiler.java +++ b/src/main/java/com/laytonsmith/core/functions/Compiler.java @@ -6,13 +6,19 @@ import com.laytonsmith.annotations.hide; import com.laytonsmith.annotations.noboilerplate; import com.laytonsmith.annotations.noprofile; +import com.laytonsmith.core.ArgumentValidation; import com.laytonsmith.core.FullyQualifiedClassName; import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Script; +import com.laytonsmith.core.Optimizable.OptimizationOption; +import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.compiler.CompilerWarning; import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.compiler.analysis.StaticAnalysis; +import com.laytonsmith.core.compiler.signature.FunctionSignatures; +import com.laytonsmith.core.compiler.signature.SignatureBuilder; import com.laytonsmith.core.constructs.CBareString; import com.laytonsmith.core.constructs.CBracket; import com.laytonsmith.core.constructs.CClassType; @@ -26,9 +32,13 @@ import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.IVariable; +import com.laytonsmith.core.constructs.IVariableList; +import com.laytonsmith.core.constructs.InstanceofUtil; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.constructs.Token; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.Environment.EnvironmentImpl; import com.laytonsmith.core.exceptions.CRE.CRECastException; import com.laytonsmith.core.exceptions.CRE.CRENotFoundException; import com.laytonsmith.core.exceptions.CRE.CREThrowable; @@ -187,27 +197,35 @@ public static ParseTree rewrite(List list, boolean returnSConcat, //If any of our nodes are CSymbols, we have different behavior boolean inSymbolMode = false; //caching this can save Xn + // Rewrite execute operator. rewriteParenthesis(list); - //Assignment - //Note that we are walking the array in reverse, because multiple assignments, - //say @a = @b = 1 will break if they look like assign(assign(@a, @b), 1), - //they need to be assign(@a, assign(@b, 1)). As a variation, we also have - //to support something like 1 + @a = 2, which will turn into add(1, assign(@a, 2), - //and 1 + @a = @b + 3 would turn into add(1, assign(@a, add(@b, 3))). + // Rewrite assignment operator. + /* + * Note that we are walking the array in reverse, because multiple assignments, + * say @a = @b = 1 will break if they look like assign(assign(@a, @b), 1), + * they need to be assign(@a, assign(@b, 1)). As a variation, we also have + * to support something like 1 + @a = 2, which will turn into add(1, assign(@a, 2), + * and 1 + @a = @b + 3 would turn into add(1, assign(@a, add(@b, 3))). + */ for(int i = list.size() - 2; i >= 0; i--) { ParseTree node = list.get(i + 1); if(node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isAssignment()) { + + // Get assign left hand side and autoconcat assign right hand side if necessary. ParseTree lhs = list.get(i); - ParseTree assignNode = new ParseTree( - new CFunction(assign.NAME, node.getTarget()), node.getFileOptions()); - ParseTree rhs; if(i < list.size() - 3) { - //Need to autoconcat List valChildren = new ArrayList<>(); int index = i + 2; // add all preceding symbols - while(list.size() > index + 1 && list.get(index).getData() instanceof CSymbol) { + while(list.size() > index + 1 && (list.get(index).getData() instanceof CSymbol + || (list.get(index).getData() instanceof CFunction cf + && cf.hasFunction() && cf.getFunction() != null + && cf.getFunction().getName().equals(Compiler.p.NAME) + && list.get(index).numberOfChildren() == 1 + && (list.get(index).getChildAt(0).getData() instanceof CClassType + || __type_ref__.createFromBareStringOrConcats( + list.get(index).getChildAt(0)) != null)))) { valChildren.add(list.get(index)); list.remove(index); } @@ -237,26 +255,31 @@ public static ParseTree rewrite(List list, boolean returnSConcat, if(list.size() <= i + 2) { throw new ConfigCompileException("Unexpected end of statement", list.get(i).getTarget()); } + ParseTree rhs = list.get(i + 2); - // Additive assignment + // Wrap additive assignment in right hand side (e.g. convert @a += 1 to @a = @a + 1). CSymbol sy = (CSymbol) node.getData(); String conversionFunction = sy.convertAssignment(); if(conversionFunction != null) { - ParseTree conversion = new ParseTree(new CFunction(conversionFunction, node.getTarget()), node.getFileOptions()); - conversion.addChild(lhs); - conversion.addChild(list.get(i + 2)); - list.set(i + 2, conversion); + ParseTree rhsReplacement = new ParseTree( + new CFunction(conversionFunction, node.getTarget()), node.getFileOptions()); + rhsReplacement.addChild(lhs); + rhsReplacement.addChild(rhs); + rhs = rhsReplacement; } - rhs = list.get(i + 2); + // Rewrite to assign node. + ParseTree assignNode = new ParseTree( + new CFunction(assign.NAME, node.getTarget()), node.getFileOptions()); assignNode.addChild(lhs); assignNode.addChild(rhs); - list.set(i, assignNode); - list.remove(i + 1); - list.remove(i + 1); + list.set(i, assignNode); // Overwrite lhs with assign node. + list.remove(i + 1); // Remove "=" node. + list.remove(i + 1); // Remove rhs node. } } - //postfix + + // Rewrite postfix operators. for(int i = 0; i < list.size(); i++) { ParseTree node = list.get(i); if(node.getData() instanceof CSymbol) { @@ -280,9 +303,10 @@ public static ParseTree rewrite(List list, boolean returnSConcat, } } } + + // Rewrite unary operators. if(inSymbolMode) { try { - //look for unary operators for(int i = 0; i < list.size() - 1; i++) { ParseTree node = list.get(i); if(node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isUnary()) { @@ -326,6 +350,44 @@ public static ParseTree rewrite(List list, boolean returnSConcat, conversion.addChild(rewrite(ac, returnSConcat, envs)); } } + } catch (IndexOutOfBoundsException e) { + throw new ConfigCompileException("Unexpected symbol (" + list.get(list.size() - 1).getData().val() + ")", + list.get(list.size() - 1).getTarget()); + } + } + + // Rewrite cast operator. + for(int i = list.size() - 2; i >= 0; i--) { + ParseTree node = list.get(i); + if(node.getData() instanceof CFunction cf && cf.hasFunction() && cf.getFunction() != null + && cf.getFunction().getName().equals(Compiler.p.NAME) && node.numberOfChildren() == 1) { + + // Convert bare string or concat() to type reference if needed. + ParseTree typeNode = node.getChildAt(0); + if(!typeNode.getData().isInstanceOf(CClassType.TYPE)) { + ParseTree convertedTypeNode = __type_ref__.createFromBareStringOrConcats(typeNode); + if(convertedTypeNode != null) { + typeNode = convertedTypeNode; + } else { + + // This is not a "(classtype)" format. Skip node. + continue; + } + } + + // Rewrite p(A) and the next list entry B to __cast__(B, A). + ParseTree castNode = new ParseTree( + new CFunction(__cast__.NAME, node.getTarget()), node.getFileOptions()); + castNode.addChild(list.get(i + 1)); + castNode.addChild(typeNode); + list.set(i, castNode); + list.remove(i + 1); + } + } + + // Rewrite binary operators. + if(inSymbolMode) { + try { //Exponential for(int i = 0; i < list.size() - 1; i++) { @@ -586,18 +648,30 @@ private static void rewriteParenthesis(List list) throws ConfigCompil for(int listInd = list.size() - 1; listInd >= 1; listInd--) { Stack executes = new Stack<>(); while(listInd > 0) { - ParseTree lastNode = list.get(listInd); + ParseTree node = list.get(listInd); try { - if(lastNode.getData() instanceof CFunction cf + if(node.getData() instanceof CFunction cf && cf.hasFunction() && cf.getFunction() != null && cf.getFunction().getName().equals(Compiler.p.NAME)) { - Mixed prevNode = list.get(listInd - 1).getData(); - if(prevNode instanceof CSymbol || prevNode instanceof CLabel || prevNode instanceof CString) { - // It's just a parenthesis like @a = (1); or key: (value), so we should leave it alone. + ParseTree prevNode = list.get(listInd - 1); + Mixed prevNodeVal = prevNode.getData(); + + // Do not rewrite parenthesis like "@a = (1);" or "key: (value)" to execute(). + if(prevNodeVal instanceof CSymbol + || prevNodeVal instanceof CLabel || prevNodeVal instanceof CString) { + break; + } + + // Do not rewrite casts to execute() if the callable is the cast (i.e. "(type) (val)"). + if(prevNodeVal instanceof CFunction cfunc && cfunc.hasFunction() && cfunc.getFunction() != null + && cfunc.getFunction().getName().equals(Compiler.p.NAME) && prevNode.numberOfChildren() == 1 + && (prevNode.getChildAt(0).getData().isInstanceOf(CClassType.TYPE) + || __type_ref__.createFromBareStringOrConcats(prevNode.getChildAt(0)) != null)) { break; } - executes.push(lastNode); + + executes.push(node); list.remove(listInd--); } else { break; @@ -1136,4 +1210,175 @@ public ParseTree postParseRewrite(ParseTree ast, Environment env, } } } + + @api + @noprofile + @hide("This is only used internally by the compiler.") + public static class __cast__ extends DummyFunction implements Optimizable { + + public static final String NAME = "__cast__"; + + @Override + public String getName() { + return NAME; + } + + @Override + public FunctionSignatures getSignatures() { + return new SignatureBuilder(CClassType.AUTO) + .param(Mixed.TYPE, "value", "The value.") + .param(CClassType.TYPE, "type", "The type.") + .throwsEx(CRECastException.class, "When value cannot be cast to type.") + .build(); + } + + @SuppressWarnings("unchecked") + @Override + public Class[] thrown() { + return new Class[] {CRECastException.class}; + } + + @Override + public Integer[] numArgs() { + return new Integer[] {2}; + } + + @Override + public String docs() { + return "mixed {mixed value, ClassType type} Used internally by the compiler. You shouldn't use it."; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + Mixed value = args[0]; + CClassType type = ArgumentValidation.getClassType(args[1], t); + if(!InstanceofUtil.isInstanceof(value, type, env)) { + throw new CRECastException( + "Cannot cast from " + value.typeof().getSimpleName() + " to " + type.getSimpleName() + ".", t); + } + return value; + } + + @Override + public CClassType typecheck(StaticAnalysis analysis, + ParseTree ast, Environment env, Set exceptions) { + + // Fall back to default behavior for invalid usage. + if(ast.numberOfChildren() != 2) { + return super.typecheck(analysis, ast, env, exceptions); + } + + // Typecheck value and type nodes. + ParseTree valNode = ast.getChildAt(0); + CClassType valType = analysis.typecheck(valNode, env, exceptions); + StaticAnalysis.requireType(valType, Mixed.TYPE, valType.getTarget(), env, exceptions); + ParseTree typeNode = ast.getChildAt(1); + CClassType typeType = analysis.typecheck(typeNode, env, exceptions); + StaticAnalysis.requireType(typeType, CClassType.TYPE, typeNode.getTarget(), env, exceptions); + + // Get cast-to type. + if(!(typeNode.getData() instanceof CClassType)) { + assert !exceptions.isEmpty() : "Missing compile-time type error for cast type argument."; + return CClassType.AUTO; + } + CClassType castToType = (CClassType) typeNode.getData(); + + // Generate redundancy warning for casts to the value type. + if(castToType.equals(valType)) { + env.getEnv(CompilerEnvironment.class).addCompilerWarning(ast.getFileOptions(), + new CompilerWarning("Redundant cast to " + castToType.getSimpleName(), ast.getTarget(), + FileOptions.SuppressWarning.UselessCode)); + } + + // Generate compile error for impossible casts. + if(!InstanceofUtil.isInstanceof(valType, castToType, env) + && !InstanceofUtil.isInstanceof(castToType, valType, env)) { + exceptions.add(new ConfigCompileException("Cannot cast from " + + valType.getSimpleName() + " to " + castToType.getSimpleName() + ".", ast.getTarget())); + } + + // Return type that is being cast to. + return castToType; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.OPTIMIZE_DYNAMIC, + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException, ConfigCompileGroupException { + + // Optimize __cast__(__cast__(val, type1), type2) to __cast__(val, type1) if the cast to type2 will always + // pass given that the cast to type1 has passed. + ParseTree valNode = children.get(0); + if(valNode.getData() instanceof CFunction cf && cf.getFunction() != null + && cf.getFunction().getName().equals(__cast__.NAME) && valNode.numberOfChildren() == 2) { + ParseTree typeNode = children.get(1); + ParseTree childTypeNode = valNode.getChildAt(1); + if(typeNode.getData() instanceof CClassType type + && childTypeNode.getData() instanceof CClassType childType + && InstanceofUtil.isInstanceof(childType, type, env)) { + return valNode; + } + } + return null; + } + } + + @api + @noprofile + @noboilerplate + @hide("This is only used internally by the compiler.") + public static class __unsafe_assign__ extends assign { + + public static final String NAME = "__unsafe_assign__"; + + @Override + public String getName() { + return NAME; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { + + // Get arguments. + CClassType type; + String varName; + Mixed val; + if(args.length == 3) { + type = (CClassType) args[0]; + varName = ((IVariable) args[1]).getVariableName(); + val = args[2]; + } else { + type = null; + varName = ((IVariable) args[0]).getVariableName(); + val = args[1]; + } + + // Get variable list. + IVariableList list = env.getEnv(GlobalEnv.class).GetVarList(); + + // Unwrap assigned value from IVariable if an IVariable is passed. + if(val instanceof IVariable ivar) { + val = list.get(ivar.getVariableName(), ivar.getTarget(), env).ival(); + } + + // Assign value to variable. + IVariable var = list.get(varName); + if(var == null) { + var = new IVariable(type, varName, val, t); + list.set(var); + } else { + var.setIval(val); + } + return var; + } + } } diff --git a/src/main/java/com/laytonsmith/core/functions/DataHandling.java b/src/main/java/com/laytonsmith/core/functions/DataHandling.java index dd13b020a..e393be34b 100644 --- a/src/main/java/com/laytonsmith/core/functions/DataHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/DataHandling.java @@ -96,8 +96,10 @@ import com.laytonsmith.core.functions.ArrayHandling.array_push; import com.laytonsmith.core.functions.ArrayHandling.array_set; import com.laytonsmith.core.functions.Compiler.__autoconcat__; +import com.laytonsmith.core.functions.Compiler.__cast__; import com.laytonsmith.core.functions.Compiler.__statements__; import com.laytonsmith.core.functions.Compiler.__type_ref__; +import com.laytonsmith.core.functions.Compiler.__unsafe_assign__; import com.laytonsmith.core.functions.Compiler.centry; import com.laytonsmith.core.natives.interfaces.Mixed; import com.laytonsmith.tools.docgen.templates.ArrayIteration; @@ -340,26 +342,27 @@ public Integer[] numArgs() { @Override public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { IVariableList list = env.getEnv(GlobalEnv.class).GetVarList(); - int offset; - CClassType type; - String name; + IVariable var; if(args.length == 3) { - offset = 1; - if(!(args[offset] instanceof IVariable)) { + + // Get and validate variable name. + if(!(args[1] instanceof IVariable)) { throw new CRECastException(getName() + " with 3 arguments only accepts an ivariable as the second argument.", t); } - name = ((IVariable) args[offset]).getVariableName(); - if(list.has(name) && env.getEnv(GlobalEnv.class).GetFlag(GlobalEnv.FLAG_NO_CHECK_DUPLICATE_ASSIGN) == null) { + String varName = ((IVariable) args[1]).getVariableName(); + if(list.has(varName) && env.getEnv(GlobalEnv.class).GetFlag(GlobalEnv.FLAG_NO_CHECK_DUPLICATE_ASSIGN) == null) { if(env.getEnv(GlobalEnv.class).GetFlag(GlobalEnv.FLAG_CLOSURE_WARN_OVERWRITE) != null) { MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, LogLevel.WARNING, - "The variable " + name + " is hiding another value of the" + "The variable " + varName + " is hiding another value of the" + " same name in the main scope.", t); - } else if(!StaticAnalysis.enabled() && t != list.get(name, t, true, env).getDefinedTarget()) { - MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, LogLevel.ERROR, name + " was already defined at " - + list.get(name, t, true, env).getDefinedTarget() + " but is being redefined.", t); + } else if(!StaticAnalysis.enabled() && t != list.get(varName, t, true, env).getDefinedTarget()) { + MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, LogLevel.ERROR, varName + " was already defined at " + + list.get(varName, t, true, env).getDefinedTarget() + " but is being redefined.", t); } } - type = ArgumentValidation.getClassType(args[0], t); + + // Get and validate variable type. + CClassType type = ArgumentValidation.getClassType(args[0], t); Boolean varArgsAllowed = env.getEnv(GlobalEnv.class).GetFlag(GlobalEnv.FLAG_VAR_ARGS_ALLOWED); if(varArgsAllowed == null) { varArgsAllowed = false; @@ -367,24 +370,64 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommand if(type.isVarargs() && !varArgsAllowed) { throw new CRECastException("Cannot use varargs type in this context", t); } + if(type.equals(CVoid.TYPE)) { + throw new CRECastException("Variables may not be of type void", t); + } + + // Get assigned value. + Mixed val = args[2]; + + // Unwrap assigned value from IVariable if an IVariable is passed. + if(val instanceof IVariable ivar) { + val = list.get(ivar.getVariableName(), ivar.getTarget(), env).ival(); + } + + // Validate assigned value. + if(val instanceof CVoid) { + throw new CRECastException("Void may not be assigned to a variable", t); + } + if(!InstanceofUtil.isInstanceof(val.typeof(), type, env)) { + throw new CRECastException(varName + " is of type " + type.val() + ", but a value of type " + + val.typeof() + " was assigned to it.", t); + } + + // Set variable in variable list. + var = new IVariable(type, varName, val, t); + list.set(var); + } else { - offset = 0; - if(!(args[offset] instanceof IVariable)) { + + // Get and validate variable name. + if(!(args[0] instanceof IVariable)) { throw new CRECastException(getName() + " with 2 arguments only accepts an ivariable as the first argument.", t); } - name = ((IVariable) args[offset]).getVariableName(); - IVariable listVar = list.get(name, t, true, env); - t = listVar.getDefinedTarget(); - type = listVar.getDefinedType(); - } - Mixed c = args[offset + 1]; - while(c instanceof IVariable) { - IVariable cur = (IVariable) c; - c = list.get(cur.getVariableName(), cur.getTarget(), env).ival(); + String varName = ((IVariable) args[0]).getVariableName(); + + // Get assigned value. + Mixed val = args[1]; + + // Unwrap assigned value from IVariable if an IVariable is passed. + if(val instanceof IVariable ivar) { + val = list.get(ivar.getVariableName(), ivar.getTarget(), env).ival(); + } + + // Validate assigned value and set variable in variable list. + if(val instanceof CVoid) { + throw new CRECastException("Void may not be assigned to a variable", t); + } + var = list.get(varName); + if(var == null) { + var = new IVariable(Auto.TYPE, varName, val, t); + list.set(var); + } else { + if(!InstanceofUtil.isInstanceof(val.typeof(), var.getDefinedType(), env)) { + throw new CRECastException(varName + " is of type " + var.getDefinedType() + + ", but a value of type " + val.typeof() + " was assigned to it.", t); + } + var.setIval(val); + } } - IVariable v = new IVariable(type, name, c, t, env); - list.set(v); - return v; + return var; } @Override @@ -637,6 +680,8 @@ public ParseTree optimizeDynamic(Target t, Environment env, if(children.size() < 2) { return null; } + + // Generate warning for assigning a variable to itself. if(children.get(0).getData() instanceof IVariable && children.get(1).getData() instanceof IVariable) { if(((IVariable) children.get(0).getData()).getVariableName().equals( @@ -646,18 +691,49 @@ public ParseTree optimizeDynamic(Target t, Environment env, new CompilerWarning(msg, t, null)); } } - { - // Check for declaration of variables named "pass" or "password" and see if it's defined as a - // secure_string. If not, warn. - if(children.get(0).getData() instanceof CClassType && children.get(1).getData() instanceof IVariable) { - boolean isString - = ((CClassType) children.get(0).getData()).getNativeType() == CString.class; - String varName = ((IVariable) children.get(1).getData()).getVariableName(); - if((varName.equalsIgnoreCase("pass") || varName.equalsIgnoreCase("password")) - && isString) { - String msg = ""; - env.getEnv(CompilerEnvironment.class).addCompilerWarning(fileOptions, - new CompilerWarning(msg, t, FileOptions.SuppressWarning.CodeUpgradeNotices)); + + // Check for declaration of variables named "pass" or "password" and see if it's defined as a + // secure_string. If not, warn. + if(children.get(0).getData() instanceof CClassType && children.get(1).getData() instanceof IVariable) { + boolean isString + = ((CClassType) children.get(0).getData()).getNativeType() == CString.class; + String varName = ((IVariable) children.get(1).getData()).getVariableName(); + if((varName.equalsIgnoreCase("pass") || varName.equalsIgnoreCase("password")) + && isString) { + String msg = ""; + env.getEnv(CompilerEnvironment.class).addCompilerWarning(fileOptions, + new CompilerWarning(msg, t, FileOptions.SuppressWarning.CodeUpgradeNotices)); + } + } + + // Rewrite "assign(type, var, val)" to "__unsafe_assign__(type, var, val)" if the typecheck cannot fail. + if(children.size() == 3) { + ParseTree typeNode = children.get(0); + ParseTree varNode = children.get(1); + ParseTree valNode = children.get(2); + if(typeNode.getData() instanceof CClassType declaredType + && varNode.getData() instanceof IVariable) { + if(valNode.isConst()) { + CClassType valType = valNode.getData().typeof(); + if((valType != CClassType.AUTO || declaredType == CClassType.AUTO) + && InstanceofUtil.isInstanceof(valType, declaredType, env)) { + ParseTree newAssignNode = new ParseTree(new CFunction(__unsafe_assign__.NAME, t), fileOptions); + newAssignNode.addChild(typeNode); + newAssignNode.addChild(varNode); + newAssignNode.addChild(valNode); + return newAssignNode; + } + } + if(valNode.getData() instanceof CFunction cf && cf.getCachedFunction() != null + && cf.getCachedFunction().getName().equals(__cast__.NAME) && valNode.numberOfChildren() == 2 + && valNode.getChildAt(1).getData() instanceof CClassType castType + && (castType != CClassType.AUTO || declaredType == CClassType.AUTO) + && InstanceofUtil.isInstanceof(castType, declaredType, env)) { + ParseTree newAssignNode = new ParseTree(new CFunction(__unsafe_assign__.NAME, t), fileOptions); + newAssignNode.addChild(typeNode); + newAssignNode.addChild(varNode); + newAssignNode.addChild(valNode); + return newAssignNode; } } } @@ -1505,13 +1581,14 @@ public static Procedure getProcedure(Target t, Environment env, Script parent, P } else { boolean thisNodeIsAssign = false; if(nodes[i].getData() instanceof CFunction) { - if((nodes[i].getData()).val().equals(assign.NAME)) { + String funcName = nodes[i].getData().val(); + if(funcName.equals(assign.NAME) || funcName.equals(__unsafe_assign__.NAME)) { thisNodeIsAssign = true; if((nodes[i].getChildren().size() == 3 && Construct.IsDynamicHelper(nodes[i].getChildAt(0).getData())) || Construct.IsDynamicHelper(nodes[i].getChildAt(1).getData())) { usesAssign = true; } - } else if((nodes[i].getData()).val().equals(__autoconcat__.NAME)) { + } else if(funcName.equals(__autoconcat__.NAME)) { throw new CREInvalidProcedureException("Invalid arguments defined for procedure", t); } } diff --git a/src/main/java/com/laytonsmith/core/functions/Exceptions.java b/src/main/java/com/laytonsmith/core/functions/Exceptions.java index 83036c832..ac638a2a5 100644 --- a/src/main/java/com/laytonsmith/core/functions/Exceptions.java +++ b/src/main/java/com/laytonsmith/core/functions/Exceptions.java @@ -618,7 +618,10 @@ public ParseTree optimizeDynamic(Target t, Environment env, } // Validate that the node is an assign with 3 arguments. - if(!CFunction.IsFunction(assign, DataHandling.assign.class) || assign.numberOfChildren() != 3) { + if(!(assign.getData() instanceof CFunction cf && cf.getFunction() != null + && (cf.getFunction().getName().equals(DataHandling.assign.NAME) + || cf.getFunction().getName().equals(Compiler.__unsafe_assign__.NAME))) + || assign.numberOfChildren() != 3) { throw new ConfigCompileException("Expecting a variable declaration, but instead " + assign.getData().val() + " was found", t); } diff --git a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java index 0be64ce14..1a34c8796 100644 --- a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java +++ b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java @@ -1422,4 +1422,26 @@ public void testInvalidFQCNTypingCompileFailsStrict() throws Exception { sa.setLocalEnable(true); MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, null, true), env, env.getEnvClasses(), sa); } + + @Test + public void testSoftCastSyntaxCompiles() throws Exception { + String script = """ + + int @a = (int) 1; + number @b = (int) 1; + mixed @c = (ms.lang.int) 1; + int @d = (int) (1); + int @e = (ms.lang.int) 1; + int @f = (int) (1 + 2); + int @g = (int) 1 + 2; + msg((string) 'Hello World!'); + msg((string) 'Hello '.(string) 'World!'); + mixed @h = (number) (int) 1; + mixed @i = (mixed) (number) (int) (int) (int) 1; + """; + Environment env = Static.GenerateStandaloneEnvironment(); + StaticAnalysis sa = new StaticAnalysis(true); + sa.setLocalEnable(true); + MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, null, true), env, env.getEnvClasses(), sa); + } } diff --git a/src/test/java/com/laytonsmith/core/NewExceptionHandlingTest.java b/src/test/java/com/laytonsmith/core/NewExceptionHandlingTest.java index a1fee4116..dbd524100 100644 --- a/src/test/java/com/laytonsmith/core/NewExceptionHandlingTest.java +++ b/src/test/java/com/laytonsmith/core/NewExceptionHandlingTest.java @@ -45,8 +45,8 @@ public void setUp() { @Test public void testBasicKeywordUsage() throws Exception { - assertEquals("__statements__(complex_try(__statements__(noop()),assign(ms.lang.IOException,@e,null),__statements__(noop())," - + "assign(ms.lang.Exception,@e,null),__statements__(noop()),__statements__(noop())))", + assertEquals("__statements__(complex_try(__statements__(noop()),__unsafe_assign__(ms.lang.IOException,@e,null),__statements__(noop())," + + "__unsafe_assign__(ms.lang.Exception,@e,null),__statements__(noop()),__statements__(noop())))", optimize("try { } catch (IOException @e){ } catch (Exception @e){ } finally { }")); } diff --git a/src/test/java/com/laytonsmith/core/OptimizationTest.java b/src/test/java/com/laytonsmith/core/OptimizationTest.java index 3d5edf465..bab80532f 100644 --- a/src/test/java/com/laytonsmith/core/OptimizationTest.java +++ b/src/test/java/com/laytonsmith/core/OptimizationTest.java @@ -264,7 +264,7 @@ public void testAssignWithComplexSymbols() throws Exception { @Test public void testMultipleAdjacentAssignment() throws Exception { - assertEquals("__statements__(assign(@one,inc(@two)),assign(ms.lang.int,@three,0),assign(@four,'test'))", + assertEquals("__statements__(assign(@one,inc(@two)),__unsafe_assign__(ms.lang.int,@three,0),assign(@four,'test'))", optimize("@one = ++@two; int @three = 0; @four = 'test';")); } @@ -584,9 +584,9 @@ public void testIfStatementWithMultipleInvalidParameters() throws Exception { @Test public void testSconcatWithNonStatement() throws Exception { - assertEquals("__statements__(assign(ms.lang.int,@i,0),assign(ms.lang.int,@j,0))", + assertEquals("__statements__(__unsafe_assign__(ms.lang.int,@i,0),__unsafe_assign__(ms.lang.int,@j,0))", optimize("int @i = 0; int @j = 0;")); - assertEquals("sconcat(__statements__(assign(ms.lang.int,@i,0)),assign(ms.lang.int,@j,0))", + assertEquals("sconcat(__statements__(__unsafe_assign__(ms.lang.int,@i,0)),__unsafe_assign__(ms.lang.int,@j,0))", optimize("int @i = 0; int @j = 0")); } @@ -673,13 +673,13 @@ public void testParenthesisRewritesCorrectly1() throws Exception { @Test public void testParenthesisRewritesCorrectly2() throws Exception { - assertEquals("__statements__(assign(ms.lang.closure,@c,closure(assign(ms.lang.int,@a,null),__statements__(msg(@a)))),execute(10,@c))", + assertEquals("__statements__(assign(ms.lang.closure,@c,closure(__unsafe_assign__(ms.lang.int,@a,null),__statements__(msg(@a)))),execute(10,@c))", optimize("closure @c = closure(int @a) {msg(@a);};\n@c(10);")); } @Test public void testParenthesisRewritesCorrectly3() throws Exception { - assertEquals("__statements__(assign(ms.lang.closure,@c,closure(assign(ms.lang.int,@a,null),assign(ms.lang.int,@b,null),__statements__(msg(@a)))),execute(10,11,@c))", + assertEquals("__statements__(assign(ms.lang.closure,@c,closure(__unsafe_assign__(ms.lang.int,@a,null),__unsafe_assign__(ms.lang.int,@b,null),__statements__(msg(@a)))),execute(10,11,@c))", optimize("closure @c = closure(int @a, int @b) {msg(@a);};\n@c(10,11);")); } @@ -728,7 +728,7 @@ public void testNoErrorWithParenthesisAfterSymbol() throws Exception { @Test public void testProcReference() throws Exception { - assertEquals("__statements__(proc(ms.lang.int,'_asdf',assign(ms.lang.int,@i,null),__statements__(return(@i)))," + assertEquals("__statements__(proc(ms.lang.int,'_asdf',__unsafe_assign__(ms.lang.int,@i,null),__statements__(return(@i)))," + "assign(ms.lang.Callable,@c,get_proc('_asdf')))", optimize("int proc _asdf(int @i) { return @i; } Callable @c = proc _asdf;")); } @@ -761,9 +761,9 @@ public void testSmartStringToDumbStringRewriteWithEscapes() throws Exception { @Test public void testForIsSelfStatement() throws Exception { - assertEquals("__statements__(for(assign(ms.lang.int,@i,0),lt(@i,10),inc(@i),__statements__(msg(@i))))", + assertEquals("__statements__(for(__unsafe_assign__(ms.lang.int,@i,0),lt(@i,10),inc(@i),__statements__(msg(@i))))", optimize("for(int @i = 0, @i < 10, @i++) { msg(@i); }")); - assertEquals("__statements__(while(true,__statements__(msg(''),for(assign(ms.lang.int,@i,0),lt(@i,10),inc(@i),__statements__(msg(@i))))))", + assertEquals("__statements__(while(true,__statements__(msg(''),for(__unsafe_assign__(ms.lang.int,@i,0),lt(@i,10),inc(@i),__statements__(msg(@i))))))", optimize("while(true) { msg('') for(int @i = 0, @i < 10, @i++) { msg(@i); }}")); }