Skip to content

Commit d244bc7

Browse files
committed
fix: std.parseJson reports truncated input cleanly
1 parent 628e0c1 commit d244bc7

17 files changed

Lines changed: 28 additions & 227 deletions

bench/resources/go_suite/manifestTomlEx.jsonnet.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@
5555
}
5656
}
5757
},
58-
"nothing": "\n\n[prometheusOperator]\n\n\n [prometheusOperator.clusterRole]\n\n\n [[prometheusOperator.clusterRole.rules]]\n apiGroups = [\n \"authentication.k8s.io\"\n ]\n resources = [\n \"tokenreviews\"\n ]\n verbs = [\n \"create\"\n ]\n\n [[prometheusOperator.clusterRole.rules]]\n apiGroups = [\n \"authorization.k8s.io\"\n ]\n resources = [\n \"subjectaccessreviews\"\n ]\n verbs = [\n \"create\"\n ]\n\n [prometheusOperator.service]\n\n\n [prometheusOperator.service.spec]\n\n\n [[prometheusOperator.service.spec.ports]]\n name = \"https\"\n port = 8443\n targetPort = \"https\"\n\n [prometheusOperator.serviceMonitor]\n\n\n [prometheusOperator.serviceMonitor.spec]\n\n\n [[prometheusOperator.serviceMonitor.spec.endpoints]]\n bearerTokenFile = \"/var/run/secrets/kubernetes.io/serviceaccount/token\"\n honorLabels = true\n port = \"https\"\n scheme = \"https\"\n\n [prometheusOperator.serviceMonitor.spec.endpoints.tlsConfig]\n insecureSkipVerify = true"
58+
"nothing": "[prometheusOperator]\n\n [prometheusOperator.clusterRole]\n\n [[prometheusOperator.clusterRole.rules]]\n apiGroups = [\n \"authentication.k8s.io\"\n ]\n resources = [\n \"tokenreviews\"\n ]\n verbs = [\n \"create\"\n ]\n\n [[prometheusOperator.clusterRole.rules]]\n apiGroups = [\n \"authorization.k8s.io\"\n ]\n resources = [\n \"subjectaccessreviews\"\n ]\n verbs = [\n \"create\"\n ]\n\n [prometheusOperator.service]\n\n [prometheusOperator.service.spec]\n\n [[prometheusOperator.service.spec.ports]]\n name = \"https\"\n port = 8443\n targetPort = \"https\"\n\n [prometheusOperator.serviceMonitor]\n\n [prometheusOperator.serviceMonitor.spec]\n\n [[prometheusOperator.serviceMonitor.spec.endpoints]]\n bearerTokenFile = \"/var/run/secrets/kubernetes.io/serviceaccount/token\"\n honorLabels = true\n port = \"https\"\n scheme = \"https\"\n\n [prometheusOperator.serviceMonitor.spec.endpoints.tlsConfig]\n insecureSkipVerify = true"
5959
}

bench/resources/refresh_golden_outputs.sh

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,8 @@ set -euo pipefail
44
ROOT_DIR="$(git rev-parse --show-toplevel)"
55
pushd "$ROOT_DIR" || exit 1
66

7-
# Use the same Scala version the bench actually runs under (scalaVersions.head) so
8-
# goldens never drift from the runtime. Pinning a fixed version here previously broke
9-
# refreshes once that version stopped building.
10-
SCALA_VERSION="$(./mill show bench.scalaVersion 2>/dev/null | tr -d '"[:space:]')"
11-
echo "Using Scala $SCALA_VERSION (bench.scalaVersion)"
12-
./mill show "sjsonnet.jvm[$SCALA_VERSION].assembly"
13-
SJSONNET="out/sjsonnet/jvm/$SCALA_VERSION/assembly.dest/out.jar"
7+
./mill show "sjsonnet.jvm[3.3.7].assembly"
8+
SJSONNET="out/sjsonnet/jvm/3.3.7/assembly.dest/out.jar"
149

1510
for suite in bench/resources/*_suite; do
1611
suite_name=$(basename "$suite")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2000002000000.5
1+
2.0000020000005E12

sjsonnet/src-js/sjsonnet/Platform.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -523,9 +523,6 @@ object Platform {
523523
throw new Exception("SHA3 not implemented in Scala.js")
524524
}
525525

526-
def hashBytes(bytes: Array[Byte]): String =
527-
scala.util.hashing.MurmurHash3.bytesHash(bytes).toHexString
528-
529526
private val regexCache = new util.concurrent.ConcurrentHashMap[String, Pattern]
530527
private val namedGroupPattern = Pattern.compile("\\(\\?<(.+?)>.*?\\)")
531528
private val namedGroupPatternReplace = Pattern.compile("(\\(\\?P<)(.+?>.*?\\))")

sjsonnet/src/sjsonnet/Importer.scala

Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -186,55 +186,15 @@ final case class StaticResolvedFile(content: String) extends ResolvedFile {
186186
override def readRawBytes(): Array[Byte] = content.getBytes(StandardCharsets.UTF_8)
187187
}
188188

189-
final class StaticBinaryResolvedFile(content0: Array[Byte])
190-
extends ResolvedFile
191-
with Product
192-
with Serializable {
193-
private val bytes: Array[Byte] = content0.clone()
194-
189+
final case class StaticBinaryResolvedFile(content: Array[Byte]) extends ResolvedFile {
195190
def getParserInput(): ParserInput = throw new NotImplementedError("Not used for binary imports")
196191

197192
def readString(): String = throw new NotImplementedError("Not used for binary imports")
198193

199-
def content: Array[Byte] = bytes
200-
201-
private lazy val contentHashValue: String = Platform.hashBytes(bytes)
202-
203-
def contentHash(): String = contentHashValue
204-
205-
override def readRawBytes(): Array[Byte] = bytes
206-
207-
def copy(content: Array[Byte] = this.content): StaticBinaryResolvedFile =
208-
new StaticBinaryResolvedFile(content)
209-
210-
def productArity: Int = 1
211-
212-
def productElement(n: Int): Any =
213-
if (n == 0) content else throw new IndexOutOfBoundsException(n.toString)
214-
215-
def canEqual(that: Any): Boolean = that.isInstanceOf[StaticBinaryResolvedFile]
216-
217-
override def productPrefix: String = "StaticBinaryResolvedFile"
218-
219-
override def equals(that: Any): Boolean = that match {
220-
case other: StaticBinaryResolvedFile =>
221-
(this eq other) || (other.canEqual(this) && java.util.Arrays.equals(bytes, other.bytes))
222-
case _ => false
223-
}
224-
225-
override def hashCode(): Int = java.util.Arrays.hashCode(bytes)
226-
227-
override def toString: String = productIterator.mkString(productPrefix + "(", ",", ")")
228-
}
229-
230-
object StaticBinaryResolvedFile
231-
extends scala.runtime.AbstractFunction1[Array[Byte], StaticBinaryResolvedFile]
232-
with Serializable {
233-
def apply(content: Array[Byte]): StaticBinaryResolvedFile =
234-
new StaticBinaryResolvedFile(content)
194+
// We just cheat, the content hash can be the content itself for static imports
195+
def contentHash(): String = content.hashCode().toString
235196

236-
def unapply(file: StaticBinaryResolvedFile): Option[Array[Byte]] =
237-
if (file == null) None else Some(file.content)
197+
override def readRawBytes(): Array[Byte] = content
238198
}
239199

240200
class CachedImporter(parent: Importer) extends Importer {

sjsonnet/src/sjsonnet/StaticOptimizer.scala

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,11 @@ class StaticOptimizer(
138138
case e @ UnaryOp(pos, op, v: Val) => tryFoldUnaryOp(pos, op, v, e)
139139

140140
// Branch elimination: constant condition in if-else
141-
case IfElse(_, _: Val.True, thenExpr, _) =>
142-
thenExpr
143-
case IfElse(_, _: Val.False, _, elseExpr) =>
144-
if (elseExpr == null) Val.staticNull
145-
else elseExpr
141+
case IfElse(pos, _: Val.True, thenExpr, _) =>
142+
thenExpr.pos = pos; thenExpr
143+
case IfElse(pos, _: Val.False, _, elseExpr) =>
144+
if (elseExpr == null) Val.Null(pos)
145+
else { elseExpr.pos = pos; elseExpr }
146146

147147
// Short-circuit elimination for And/Or with constant lhs.
148148
//
@@ -152,12 +152,10 @@ class StaticOptimizer(
152152
// into just `rhs` without the Bool guard, we silently remove that runtime type check,
153153
// causing programs like `true && "hello"` to return "hello" instead of erroring.
154154
// See: Evaluator.visitAnd / Evaluator.visitOr for the authoritative runtime semantics.
155-
// The lhs-result cases return lhs itself to keep the selected boolean's position and avoid
156-
// turning the already-created literal/folded lhs into garbage.
157-
case And(_, _: Val.True, rhs: Val.Bool) => rhs
158-
case And(_, lhs: Val.False, _) => lhs
159-
case Or(_, lhs: Val.True, _) => lhs
160-
case Or(_, _: Val.False, rhs: Val.Bool) => rhs
155+
case And(pos, _: Val.True, rhs: Val.Bool) => rhs.pos = pos; rhs
156+
case And(pos, _: Val.False, _) => Val.False(pos)
157+
case Or(pos, _: Val.True, _) => Val.True(pos)
158+
case Or(pos, _: Val.False, rhs: Val.Bool) => rhs.pos = pos; rhs
161159

162160
// Identity-equivalent function recognition. Cheap pattern match here keeps the runtime
163161
// fast path (`Val.Func.isEffectivelyIdentity`) at single-field cost.

sjsonnet/src/sjsonnet/Val.scala

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,16 +1815,6 @@ object Val {
18151815
/** Package-private ref to DebugStats, set by Evaluator when --debug-stats is active. */
18161816
private[sjsonnet] var currentDebugStats: DebugStats = _
18171817

1818-
/**
1819-
* Field count at/below which `valueRaw` resolves an inline-array object by linear scan. Above
1820-
* it (e.g. large imported strict-JSON objects, whose inline arrays are uncapped — see
1821-
* Importer), `valueRaw` instead lazily builds the `value0` LinkedHashMap via `getValue0` for
1822-
* O(1) lookups. This keeps the scan for small objects (and never builds a map for the
1823-
* import-and-emit path, which materializes via direct inline iteration) while avoiding O(N^2)
1824-
* when a large imported object gains a `super` and must be resolved per-key.
1825-
*/
1826-
private[sjsonnet] final val InlineScanMax = 8
1827-
18281818
/**
18291819
* @param add
18301820
* whether this field was defined the "+:", "+::" or "+:::" separators, corresponding to the
@@ -2489,10 +2479,8 @@ object Val {
24892479
} else {
24902480
if (s == null) null else s.valueRaw(k, self, pos, cacheOwner, cacheKey)
24912481
}
2492-
} else if (inlineFieldKeys != null && inlineFieldKeys.length <= Obj.InlineScanMax) {
2493-
// Inline multi-field fast path: linear scan over small arrays. Large inline objects
2494-
// (uncapped imported strict-JSON) fall through to the getValue0 path below, which lazily
2495-
// builds an O(1) lookup map instead of scanning O(N) per key.
2482+
} else if (inlineFieldKeys != null) {
2483+
// Inline multi-field fast path: linear scan over small arrays
24962484
val keys = inlineFieldKeys
24972485
val members = inlineFieldMembers
24982486
val n = keys.length

sjsonnet/src/sjsonnet/functions/FunctionModule.scala

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ trait FunctionModule extends FunctionBuilder {
3434
* the module object
3535
*/
3636
def moduleFromFunctions(functions: (String, Val.Func)*): Val.Obj = {
37-
Val.Obj.mkWithConstCache(
38-
dummyPos,
39-
functions.size,
40-
functions.map { case (k, v) => (k, memberOf(v)) }
41-
)
37+
Val.Obj.mk(dummyPos, functions.map { case (k, v) => (k, memberOf(v)) }: _*)
4238
}
4339

4440
/**
@@ -50,11 +46,7 @@ trait FunctionModule extends FunctionBuilder {
5046
* the module object
5147
*/
5248
def moduleFromModules(subModules: FunctionModule*): Val.Obj = {
53-
Val.Obj.mkWithConstCache(
54-
dummyPos,
55-
subModules.size,
56-
subModules.map { module => (module.name, memberOf(module.module)) }
57-
)
49+
Val.Obj.mk(dummyPos, subModules.map { module => (module.name, memberOf(module.module)) }: _*)
5850
}
5951

6052
/**

sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -616,11 +616,6 @@ object ArrayModule extends AbstractFunctionModule {
616616
}
617617

618618
private object Range extends Val.Builtin2("range", "from", "to") {
619-
// Not static-safe: constant folding would call asStrictArray on the result, eagerly
620-
// materializing the lazy RangeArr at optimization time. That defeats the lazy RangeArr /
621-
// ConcatView / sharedConcatPrefixLength fast path (e.g. `std.range(1, N) + [x] < ... + [y]`
622-
// collapses from O(1) to O(N) materialization on every evaluation).
623-
override def staticSafe: Boolean = false
624619
def evalRhs(from: Eval, to: Eval, ev: EvalScope, pos: Position): Val = {
625620
val fromInt = requireInt(from.value, "from", pos)(ev)
626621
val toInt = requireInt(to.value, "to", pos)(ev)

sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ object ManifestModule extends AbstractFunctionModule {
130130
} catch {
131131
case e: ujson.ParseException =>
132132
throw Error.fail("Invalid JSON: " + e.getMessage, pos)(ev)
133+
case _: ujson.IncompleteParseException =>
134+
throw Error.fail("Invalid JSON: unexpected end of JSON input", pos)(ev)
133135
}
134136
}
135137
}

0 commit comments

Comments
 (0)