diff --git a/sjsonnet/src/sjsonnet/Evaluator.scala b/sjsonnet/src/sjsonnet/Evaluator.scala index 0cdffcfe..d0eeb080 100644 --- a/sjsonnet/src/sjsonnet/Evaluator.scala +++ b/sjsonnet/src/sjsonnet/Evaluator.scala @@ -553,6 +553,9 @@ class Evaluator( case (l: Val.Num, r: Val.Num) => val ll = l.asSafeLong(pos) val rr = r.asSafeLong(pos) + if (rr < 0) { + Error.fail("shift by negative exponent", pos) + } if (rr >= 1 && ll >= (1L << (63 - rr))) Error.fail("numeric value outside safe integer range for bitwise operation", pos) else @@ -563,7 +566,12 @@ class Evaluator( case Expr.BinaryOp.OP_>> => (l, r) match { case (l: Val.Num, r: Val.Num) => - Val.Num(pos, (l.asSafeLong(pos) >> r.asSafeLong(pos)).toDouble) + val ll = l.asSafeLong(pos) + val rr = r.asSafeLong(pos) + if (rr < 0) { + Error.fail("shift by negative exponent", pos) + } + Val.Num(pos, (ll >> rr).toDouble) case _ => fail() } diff --git a/sjsonnet/src/sjsonnet/Parser.scala b/sjsonnet/src/sjsonnet/Parser.scala index e27915f7..f416de1b 100644 --- a/sjsonnet/src/sjsonnet/Parser.scala +++ b/sjsonnet/src/sjsonnet/Parser.scala @@ -97,16 +97,17 @@ class Parser( ).map(s => Val.Num(s._1, s._2.toDouble)) def escape[$: P]: P[String] = P(escape0 | escape1) - def escape0[$: P]: P[String] = P("\\" ~~ !"u" ~~ AnyChar.!).map { - case "\"" => "\"" - case "'" => "\'" - case "\\" => "\\" - case "/" => "/" - case "b" => "\b" - case "f" => "\f" - case "n" => "\n" - case "r" => "\r" - case "t" => "\t" + def escape0[$: P]: P[String] = P("\\" ~~ !"u" ~~ AnyChar.!).flatMapX { + case "\"" => Pass("\"") + case "'" => Pass("\'") + case "\\" => Pass("\\") + case "/" => Pass("/") + case "b" => Pass("\b") + case "f" => Pass("\f") + case "n" => Pass("\n") + case "r" => Pass("\r") + case "t" => Pass("\t") + case s => Fail.opaque(f"Unknown escape sequence in string literal: $s") } def escape1[$: P]: P[String] = P("\\u" ~~ CharIn("0-9a-fA-F").repX(min = 4, max = 4).!).map { s => Integer.parseInt(s, 16).toChar.toString diff --git a/sjsonnet/src/sjsonnet/Std.scala b/sjsonnet/src/sjsonnet/Std.scala index e3da75d6..f0942507 100644 --- a/sjsonnet/src/sjsonnet/Std.scala +++ b/sjsonnet/src/sjsonnet/Std.scala @@ -534,11 +534,19 @@ class Std( } private object DecodeUTF8 extends Val.Builtin1("decodeUTF8", "arr") { - def evalRhs(arr: Lazy, ev: EvalScope, pos: Position): Val = - new Val.Str( + def evalRhs(arr: Lazy, ev: EvalScope, pos: Position): Val = { + for ((v, idx) <- arr.force.asArr.iterator.zipWithIndex) { + if (!v.isInstanceOf[Val.Num] || !v.asDouble.isWhole || v.asInt < 0 || v.asInt > 255) { + throw Error.fail( + f"Element $idx of the provided array was not an integer in range [0,255]" + ) + } + } + Val.Str( pos, - new String(arr.force.asArr.iterator.map(_.cast[Val.Num].value.toByte).toArray, UTF_8) + new String(arr.force.asArr.iterator.map(_.asInt.toByte).toArray, UTF_8) ) + } } private object Substr extends Val.Builtin3("substr", "str", "from", "len") { @@ -1004,8 +1012,14 @@ class Std( } private object ParseJson extends Val.Builtin1("parseJson", "str") { - def evalRhs(str: Lazy, ev: EvalScope, pos: Position): Val = - ujson.StringParser.transform(str.force.asString, new ValVisitor(pos)) + def evalRhs(str: Lazy, ev: EvalScope, pos: Position): Val = { + try { + ujson.StringParser.transform(str.force.asString, new ValVisitor(pos)) + } catch { + case e: ujson.ParseException => + throw Error.fail("Invalid JSON: " + e.getMessage, pos)(ev) + } + } } private object ParseYaml extends Val.Builtin1("parseYaml", "str") { @@ -1276,6 +1290,9 @@ class Std( Util.slice(pos, ev, indexable, index, _end, _step) }, builtin("makeArray", "sz", "func") { (pos, ev, sz: Int, func: Val.Func) => + if (sz < 0) { + Error.fail(f"std.makeArray requires size >= 0, got $sz") + } Val.Arr( pos, { val a = new Array[Lazy](sz) diff --git a/sjsonnet/src/sjsonnet/Val.scala b/sjsonnet/src/sjsonnet/Val.scala index e4237290..55fe11e6 100644 --- a/sjsonnet/src/sjsonnet/Val.scala +++ b/sjsonnet/src/sjsonnet/Val.scala @@ -109,6 +109,9 @@ object Val { override def asString: String = value } final case class Num(pos: Position, value: Double) extends Literal { + if (value.isInfinite) { + Error.fail("overflow") + } def prettyName = "number" override def asInt: Int = value.toInt override def asLong: Long = value.toLong diff --git a/sjsonnet/test/resources/test_suite/error.decodeUTF8_float.jsonnet.golden b/sjsonnet/test/resources/test_suite/error.decodeUTF8_float.jsonnet.golden index c1359b65..523b848a 100644 --- a/sjsonnet/test/resources/test_suite/error.decodeUTF8_float.jsonnet.golden +++ b/sjsonnet/test/resources/test_suite/error.decodeUTF8_float.jsonnet.golden @@ -1 +1,3 @@ -"\u0011" +sjsonnet.Error: Element 0 of the provided array was not an integer in range [0,255] + at [std.decodeUTF8].(sjsonnet/test/resources/test_suite/error.decodeUTF8_float.jsonnet:1:15) + diff --git a/sjsonnet/test/resources/test_suite/error.decodeUTF8_nan.jsonnet.golden b/sjsonnet/test/resources/test_suite/error.decodeUTF8_nan.jsonnet.golden index f0c5de75..839199d1 100644 --- a/sjsonnet/test/resources/test_suite/error.decodeUTF8_nan.jsonnet.golden +++ b/sjsonnet/test/resources/test_suite/error.decodeUTF8_nan.jsonnet.golden @@ -1,3 +1,3 @@ -sjsonnet.Error: Expected number, found string +sjsonnet.Error: Element 0 of the provided array was not an integer in range [0,255] at [std.decodeUTF8].(sjsonnet/test/resources/test_suite/error.decodeUTF8_nan.jsonnet:1:15) diff --git a/sjsonnet/test/resources/test_suite/error.negative_shfit.jsonnet.golden b/sjsonnet/test/resources/test_suite/error.negative_shfit.jsonnet.golden index 573541ac..a6c24a59 100644 --- a/sjsonnet/test/resources/test_suite/error.negative_shfit.jsonnet.golden +++ b/sjsonnet/test/resources/test_suite/error.negative_shfit.jsonnet.golden @@ -1 +1,3 @@ -0 +sjsonnet.Error: shift by negative exponent + at [BinaryOp >>].(error.negative_shfit.jsonnet:1:8) + diff --git a/sjsonnet/test/resources/test_suite/error.overflow.jsonnet.golden b/sjsonnet/test/resources/test_suite/error.overflow.jsonnet.golden index 3c62151d..f4eb3695 100644 --- a/sjsonnet/test/resources/test_suite/error.overflow.jsonnet.golden +++ b/sjsonnet/test/resources/test_suite/error.overflow.jsonnet.golden @@ -1 +1,3 @@ -Infinity +sjsonnet.Error: overflow + at [BinaryOp *].(error.overflow2.jsonnet:17:7) + diff --git a/sjsonnet/test/resources/test_suite/error.overflow2.jsonnet.golden b/sjsonnet/test/resources/test_suite/error.overflow2.jsonnet.golden index 3c62151d..f4eb3695 100644 --- a/sjsonnet/test/resources/test_suite/error.overflow2.jsonnet.golden +++ b/sjsonnet/test/resources/test_suite/error.overflow2.jsonnet.golden @@ -1 +1,3 @@ -Infinity +sjsonnet.Error: overflow + at [BinaryOp *].(error.overflow2.jsonnet:17:7) + diff --git a/sjsonnet/test/resources/test_suite/error.parse.string.invalid_escape.jsonnet.golden b/sjsonnet/test/resources/test_suite/error.parse.string.invalid_escape.jsonnet.golden index 64e5a9b2..8ab10ae0 100644 --- a/sjsonnet/test/resources/test_suite/error.parse.string.invalid_escape.jsonnet.golden +++ b/sjsonnet/test/resources/test_suite/error.parse.string.invalid_escape.jsonnet.golden @@ -1,35 +1,3 @@ -Exception in thread "main" scala.MatchError: o (of class java.lang.String) - at sjsonnet.Parser.escape0(Parser.scala:109) - at sjsonnet.Parser.escape(Parser.scala:99) - at sjsonnet.Parser.rec$2(Parser.scala:115) - at sjsonnet.Parser.doubleString(Parser.scala:115) - at sjsonnet.Parser.expr2(Parser.scala:349) - at sjsonnet.Parser.expr1(Parser.scala:275) - at sjsonnet.Parser.expr(Parser.scala:211) - at sjsonnet.Parser.document(Parser.scala:567) - at sjsonnet.CachedResolver.$anonfun$2(Importer.scala:218) - at fastparse.SharedPackageDefs.parseInputRaw(SharedPackageDefs.scala:69) - at fastparse.SharedPackageDefs.parseInputRaw$(SharedPackageDefs.scala:6) - at fastparse.package$.parseInputRaw(package.scala:5) - at fastparse.SharedPackageDefs.parse$$anonfun$1(SharedPackageDefs.scala:35) - at fastparse.ParserInputSource$fromParserInput.parseThrough(ParserInput.scala:25) - at fastparse.SharedPackageDefs.parse(SharedPackageDefs.scala:42) - at fastparse.SharedPackageDefs.parse$(SharedPackageDefs.scala:6) - at fastparse.package$.parse(package.scala:5) - at sjsonnet.CachedResolver.parse$$anonfun$1(Importer.scala:216) - at scala.collection.mutable.HashMap.getOrElseUpdate(HashMap.scala:469) - at sjsonnet.DefaultParseCache.getOrElseUpdate(ParseCache.scala:21) - at sjsonnet.CachedResolver.parse(Importer.scala:227) - at sjsonnet.Interpreter.evaluate(Interpreter.scala:175) - at sjsonnet.Interpreter.interpret0(Interpreter.scala:158) - at sjsonnet.SjsonnetMain$.renderNormal$$anonfun$1(SjsonnetMain.scala:157) - at sjsonnet.SjsonnetMain$.writeToFile(SjsonnetMain.scala:131) - at sjsonnet.SjsonnetMain$.renderNormal(SjsonnetMain.scala:155) - at sjsonnet.SjsonnetMain$.mainConfigured(SjsonnetMain.scala:332) - at sjsonnet.SjsonnetMain$.$anonfun$30$$anonfun$1(SjsonnetMain.scala:85) - at scala.util.Either.flatMap(Either.scala:360) - at sjsonnet.SjsonnetMain$.$anonfun$30(SjsonnetMain.scala:84) - at scala.util.Either.flatMap(Either.scala:360) - at sjsonnet.SjsonnetMain$.main0(SjsonnetMain.scala:77) - at sjsonnet.SjsonnetMain$.main(SjsonnetMain.scala:44) - at sjsonnet.SjsonnetMain.main(SjsonnetMain.scala) +sjsonnet.ParseError: Expected "\"":17:2, found "\\o\"\n" + at .(error.parse.string.invalid_escape.jsonnet:17:2) + diff --git a/sjsonnet/test/resources/test_suite/error.parse_json.jsonnet.golden b/sjsonnet/test/resources/test_suite/error.parse_json.jsonnet.golden index 651a33ff..5bd3a91f 100644 --- a/sjsonnet/test/resources/test_suite/error.parse_json.jsonnet.golden +++ b/sjsonnet/test/resources/test_suite/error.parse_json.jsonnet.golden @@ -1,30 +1,3 @@ -sjsonnet.Error: Internal Error - at [std.parseJson].(sjsonnet/test/resources/test_suite/error.parse_json.jsonnet:1:14) -Caused by: ujson.ParseException: expected json value got "b" at index 0 - at ujson.ParseException$.apply(Exceptions.scala:6) - at ujson.CharParser.die(CharParser.scala:98) - at ujson.CharParser.parseTopLevel0(CharParser.scala:353) - at ujson.CharParser.parseTopLevel(CharParser.scala:323) - at ujson.CharParser.parse(CharParser.scala:72) - at ujson.StringParser$.transform(StringParser.scala:28) - at sjsonnet.Std$ParseJson$.evalRhs(Std.scala:989) - at sjsonnet.Evaluator.visitApplyBuiltin1(Evaluator.scala:270) - at sjsonnet.Evaluator.visitExpr(Evaluator.scala:40) - at sjsonnet.Interpreter.evaluate$$anonfun$2$$anonfun$1(Interpreter.scala:177) - at sjsonnet.Interpreter.sjsonnet$Interpreter$$handleException(Interpreter.scala:163) - at sjsonnet.Interpreter.evaluate$$anonfun$2(Interpreter.scala:177) - at scala.util.Either.flatMap(Either.scala:360) - at sjsonnet.Interpreter.evaluate(Interpreter.scala:175) - at sjsonnet.Interpreter.interpret0(Interpreter.scala:158) - at sjsonnet.SjsonnetMain$.renderNormal$$anonfun$1(SjsonnetMain.scala:157) - at sjsonnet.SjsonnetMain$.writeToFile(SjsonnetMain.scala:131) - at sjsonnet.SjsonnetMain$.renderNormal(SjsonnetMain.scala:155) - at sjsonnet.SjsonnetMain$.mainConfigured(SjsonnetMain.scala:332) - at sjsonnet.SjsonnetMain$.$anonfun$30$$anonfun$1(SjsonnetMain.scala:85) - at scala.util.Either.flatMap(Either.scala:360) - at sjsonnet.SjsonnetMain$.$anonfun$30(SjsonnetMain.scala:84) - at scala.util.Either.flatMap(Either.scala:360) - at sjsonnet.SjsonnetMain$.main0(SjsonnetMain.scala:77) - at sjsonnet.SjsonnetMain$.main(SjsonnetMain.scala:44) - at sjsonnet.SjsonnetMain.main(SjsonnetMain.scala) +sjsonnet.Error: Invalid JSON: expected json value got "b" at index 0 + at [std.parseJson].(error.parse_json.jsonnet:1:14) diff --git a/sjsonnet/test/resources/test_suite/error.std_makeArray_negative.jsonnet.golden b/sjsonnet/test/resources/test_suite/error.std_makeArray_negative.jsonnet.golden index 0ad5489a..92100393 100644 --- a/sjsonnet/test/resources/test_suite/error.std_makeArray_negative.jsonnet.golden +++ b/sjsonnet/test/resources/test_suite/error.std_makeArray_negative.jsonnet.golden @@ -1,26 +1,3 @@ -sjsonnet.Error: Internal Error +sjsonnet.Error: std.makeArray requires size >= 0, got -10 at [std.makeArray].(sjsonnet/test/resources/test_suite/error.std_makeArray_negative.jsonnet:17:14) -Caused by: java.lang.NegativeArraySizeException: -10 - at sjsonnet.Std.$init$$$anonfun$13(Std.scala:1262) - at sjsonnet.Std.$init$$$anonfun$adapted$7(Std.scala:1259) - at sjsonnet.functions.FunctionBuilder$$anon$3.evalRhs(FunctionBuilder.scala:47) - at sjsonnet.Evaluator.visitApplyBuiltin2(Evaluator.scala:278) - at sjsonnet.Evaluator.visitExpr(Evaluator.scala:41) - at sjsonnet.Interpreter.evaluate$$anonfun$2$$anonfun$1(Interpreter.scala:177) - at sjsonnet.Interpreter.sjsonnet$Interpreter$$handleException(Interpreter.scala:163) - at sjsonnet.Interpreter.evaluate$$anonfun$2(Interpreter.scala:177) - at scala.util.Either.flatMap(Either.scala:360) - at sjsonnet.Interpreter.evaluate(Interpreter.scala:175) - at sjsonnet.Interpreter.interpret0(Interpreter.scala:158) - at sjsonnet.SjsonnetMain$.renderNormal$$anonfun$1(SjsonnetMain.scala:157) - at sjsonnet.SjsonnetMain$.writeToFile(SjsonnetMain.scala:131) - at sjsonnet.SjsonnetMain$.renderNormal(SjsonnetMain.scala:155) - at sjsonnet.SjsonnetMain$.mainConfigured(SjsonnetMain.scala:332) - at sjsonnet.SjsonnetMain$.$anonfun$30$$anonfun$1(SjsonnetMain.scala:85) - at scala.util.Either.flatMap(Either.scala:360) - at sjsonnet.SjsonnetMain$.$anonfun$30(SjsonnetMain.scala:84) - at scala.util.Either.flatMap(Either.scala:360) - at sjsonnet.SjsonnetMain$.main0(SjsonnetMain.scala:77) - at sjsonnet.SjsonnetMain$.main(SjsonnetMain.scala:44) - at sjsonnet.SjsonnetMain.main(SjsonnetMain.scala) diff --git a/sjsonnet/test/src-js/sjsonnet/ErrorTests.scala b/sjsonnet/test/src-js/sjsonnet/ErrorTests.scala index 5c42ac38..6bbb84ed 100644 --- a/sjsonnet/test/src-js/sjsonnet/ErrorTests.scala +++ b/sjsonnet/test/src-js/sjsonnet/ErrorTests.scala @@ -4,15 +4,6 @@ import utest._ object ErrorTests extends BaseFileTests { val skippedTests = Set( - "test_suite/error.decodeUTF8_float.jsonnet", - "test_suite/error.function_no_default_arg.jsonnet", - "test_suite/error.negative_shfit.jsonnet", - "test_suite/error.overflow.jsonnet", - "test_suite/error.overflow2.jsonnet", - "test_suite/error.parse.string.invalid_escape.jsonnet", - "test_suite/error.parse_json.jsonnet", - "test_suite/error.std_makeArray_negative.jsonnet", - // Stack size issues with the JS runner "test_suite/error.array_recursive_manifest.jsonnet", "test_suite/error.function_infinite_default.jsonnet", diff --git a/sjsonnet/test/src-jvm-native/sjsonnet/BaseFileTests.scala b/sjsonnet/test/src-jvm-native/sjsonnet/BaseFileTests.scala index 89dba1f4..5b602017 100644 --- a/sjsonnet/test/src-jvm-native/sjsonnet/BaseFileTests.scala +++ b/sjsonnet/test/src-jvm-native/sjsonnet/BaseFileTests.scala @@ -55,13 +55,18 @@ abstract class BaseFileTests extends TestSuite { var res: Either[String, Value] = Right(null) try { res = eval(fileName) - assert(res == Left(expected.stripLineEnd)) + assert(res.isLeft) + val actual = res.left.getOrElse("") + assert(actual == expected.stripLineEnd) } catch { case _: java.lang.StackOverflowError => assert(expected.contains("StackOverflowError")) case e: sjsonnet.Error => - assert(expected.stripLineEnd.contains(e.getMessage)) - case _: Throwable => + assert(expected.stripLineEnd.contains(e.getMessage.stripLineEnd)) + case e: AssertionError => + throw e + case e: Throwable => + println(s"Unexpected error: ${e}") assert(false) } } diff --git a/sjsonnet/test/src-jvm-native/sjsonnet/ErrorTests.scala b/sjsonnet/test/src-jvm-native/sjsonnet/ErrorTests.scala index 283f6ca3..c862871b 100644 --- a/sjsonnet/test/src-jvm-native/sjsonnet/ErrorTests.scala +++ b/sjsonnet/test/src-jvm-native/sjsonnet/ErrorTests.scala @@ -12,22 +12,11 @@ object ErrorTests extends BaseFileTests { "error.recursive_function_nonterm.jsonnet" ) - val skippedTests = Set( - "error.decodeUTF8_float.jsonnet", - "error.function_no_default_arg.jsonnet", - "error.negative_shfit.jsonnet", - "error.overflow.jsonnet", - "error.overflow2.jsonnet", - "error.parse.string.invalid_escape.jsonnet", - "error.parse_json.jsonnet", - "error.std_makeArray_negative.jsonnet" - ) ++ ( - if (isScalaNative) { - skippedTestInScalaNative - } else { - Set() - } - ) + val skippedTests = if (isScalaNative) { + skippedTestInScalaNative + } else { + Set.empty[String] + } val tests: Tests = Tests { test("error") - {