Skip to content

Commit ac56108

Browse files
committed
fix: improve base64 and decodeUTF8 error messages
Motivation: std.base64Decode leaked Java-specific IllegalArgumentException text ("Last unit does not have enough valid bits") instead of a clear error. std.decodeUTF8 reported only the type name ("got number") instead of the actual value, making debugging harder. Modification: - base64Decode/base64DecodeBytes: distinguish wrong-length input ("input string appears not to be a base64 encoded string. Wrong length found (N)") from invalid-character input ("failed to decode: <detail>"). - decodeUTF8: split error into three paths — non-integer floats ("Expected an integer, but got 17.5"), out-of-range integers ("Bytes must be integers in range [0, 255], got -1"), and non-numeric types ("Unexpected type string, expected number"). Result: - No more Java exception text leaking from base64 decode errors. - decodeUTF8 errors report actual values, not just types. - All tests pass on JVM 2.12, 2.13, 3.3.
1 parent ff45a2f commit ac56108

9 files changed

Lines changed: 35 additions & 23 deletions

sjsonnet/src/sjsonnet/stdlib/EncodingModule.scala

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,12 @@ object EncodingModule extends AbstractFunctionModule {
118118
else Val.Str(pos, value)): Val
119119
} catch {
120120
case e: IllegalArgumentException =>
121-
Error.fail("Invalid base64 string: " + e.getMessage)
121+
if (str.length % 4 != 0)
122+
Error.fail(
123+
s"input string appears not to be a base64 encoded string. Wrong length found (${str.length})"
124+
)
125+
else
126+
Error.fail("failed to decode: " + e.getMessage)
122127
}
123128
},
124129
/**
@@ -135,7 +140,12 @@ object EncodingModule extends AbstractFunctionModule {
135140
Val.Arr.fromBytes(pos, PlatformBase64.decode(str))
136141
} catch {
137142
case e: IllegalArgumentException =>
138-
Error.fail("Invalid base64 string: " + e.getMessage)
143+
if (str.length % 4 != 0)
144+
Error.fail(
145+
s"input string appears not to be a base64 encoded string. Wrong length found (${str.length})"
146+
)
147+
else
148+
Error.fail("failed to decode: " + e.getMessage)
139149
}
140150
},
141151
/**

sjsonnet/src/sjsonnet/stdlib/StringModule.scala

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,11 +1225,17 @@ object StringModule extends AbstractFunctionModule {
12251225
*/
12261226
private object DecodeUTF8 extends Val.Builtin1("decodeUTF8", "arr") {
12271227
def evalRhs(arr: Eval, ev: EvalScope, pos: Position): Val = {
1228-
for ((v, idx) <- arr.value.asArr.iterator.zipWithIndex) {
1229-
if (!v.isInstanceOf[Val.Num] || !v.asDouble.isWhole || v.asInt < 0 || v.asInt > 255) {
1230-
throw Error.fail(
1231-
"std.decodeUTF8: element " + idx + " is not an integer in range [0,255], got " + v.value.prettyName
1232-
)
1228+
for (v <- arr.value.asArr.iterator) {
1229+
v match {
1230+
case n: Val.Num =>
1231+
val d = n.asDouble
1232+
if (!d.isWhole)
1233+
throw Error.fail("Expected an integer, but got " + d)
1234+
val i = d.toInt
1235+
if (i < 0 || i > 255)
1236+
throw Error.fail("Bytes must be integers in range [0, 255], got " + i)
1237+
case _ =>
1238+
throw Error.fail("Unexpected type " + v.value.prettyName + ", expected number")
12331239
}
12341240
}
12351241
Val.Str(
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
sjsonnet.Error: [std.base64DecodeBytes] Invalid base64 string: Illegal base64 character 3f
1+
sjsonnet.Error: [std.base64DecodeBytes] input string appears not to be a base64 encoded string. Wrong length found (3)
22
at [<root>].(builtinBase64DecodeBytes_high_codepoint.jsonnet:1:22)
3-
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
sjsonnet.Error: [std.base64DecodeBytes] Invalid base64 string: Last unit does not have enough valid bits
1+
sjsonnet.Error: [std.base64DecodeBytes] input string appears not to be a base64 encoded string. Wrong length found (5)
22
at [<root>].(builtinBase64DecodeBytes_invalid_base64_data.jsonnet:1:22)
3-
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
sjsonnet.Error: [std.base64Decode] Invalid base64 string: Illegal base64 character 3f
1+
sjsonnet.Error: [std.base64Decode] input string appears not to be a base64 encoded string. Wrong length found (3)
22
at [<root>].(builtinBase64Decode_high_codepoint.jsonnet:1:17)
3-
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
sjsonnet.Error: [std.base64Decode] Invalid base64 string: Last unit does not have enough valid bits
1+
sjsonnet.Error: [std.base64Decode] input string appears not to be a base64 encoded string. Wrong length found (5)
22
at [<root>].(builtinBase64Decode_invalid_base64_data.jsonnet:1:17)
3-
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
sjsonnet.Error: [std.decodeUTF8] element 0 is not an integer in range [0,255], got number
1+
sjsonnet.Error: [std.decodeUTF8] Expected an integer, but got 17.5
22
at [<root>].(error.decodeUTF8_float.jsonnet:1:15)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
sjsonnet.Error: [std.decodeUTF8] element 0 is not an integer in range [0,255], got string
1+
sjsonnet.Error: [std.decodeUTF8] Unexpected type string, expected number
22
at [<root>].(error.decodeUTF8_nan.jsonnet:1:15)

sjsonnet/test/src/sjsonnet/Base64Tests.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ object Base64Tests extends TestSuite {
463463
test("errors") {
464464
test("invalidBase64Char") {
465465
val err = evalErr("""std.base64Decode("!!!!")""")
466-
assert(err.contains("Invalid base64"))
466+
assert(err.contains("failed to decode"))
467467
}
468468
test("invalidByteArrayValue") {
469469
val err = evalErr("""std.base64([256])""")
@@ -502,22 +502,22 @@ object Base64Tests extends TestSuite {
502502
test("missingTwoPads") {
503503
// "YQ" should be "YQ==" — both go-jsonnet and C++ jsonnet reject this
504504
val err = evalErr("""std.base64Decode("YQ")""")
505-
assert(err.contains("Invalid base64"))
505+
assert(err.contains("appears not to be a base64 encoded string"))
506506
}
507507
test("missingOnePad") {
508508
// "YWI" should be "YWI=" — rejected by both official implementations
509509
val err = evalErr("""std.base64Decode("YWI")""")
510-
assert(err.contains("Invalid base64"))
510+
assert(err.contains("appears not to be a base64 encoded string"))
511511
}
512512
test("singleChar") {
513513
// Single character is never valid base64
514514
val err = evalErr("""std.base64Decode("A")""")
515-
assert(err.contains("Invalid base64"))
515+
assert(err.contains("appears not to be a base64 encoded string"))
516516
}
517517
test("fiveChars") {
518518
// "wrong" (5 chars) — the go_test_suite canonical test case
519519
val err = evalErr("""std.base64Decode("wrong")""")
520-
assert(err.contains("Invalid base64"))
520+
assert(err.contains("appears not to be a base64 encoded string"))
521521
}
522522
test("validPaddedStillWorks") {
523523
// Properly padded input must still work
@@ -536,7 +536,7 @@ object Base64Tests extends TestSuite {
536536
// DecodeBytes also enforces strict padding
537537
test("decodeBytesUnpadded") {
538538
val err = evalErr("""std.base64DecodeBytes("YQ")""")
539-
assert(err.contains("Invalid base64"))
539+
assert(err.contains("appears not to be a base64 encoded string"))
540540
}
541541
}
542542

0 commit comments

Comments
 (0)