Skip to content

Commit 8d68091

Browse files
authored
chore: Restrict bitwise operations argument range to the safe-integer range. port google/go-jsonnet#859 (#641)
port google/go-jsonnet#859
1 parent 05f8dee commit 8d68091

10 files changed

Lines changed: 106 additions & 15 deletions

sjsonnet/src/sjsonnet/Evaluator.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -711,14 +711,14 @@ class Evaluator(
711711
case Expr.BinaryOp.OP_^ =>
712712
(l, r) match {
713713
case (l: Val.Num, r: Val.Num) =>
714-
Val.Num(pos, (l.asLong ^ r.asLong).toDouble)
714+
Val.Num(pos, (l.asSafeLong ^ r.asSafeLong).toDouble)
715715
case _ => fail()
716716
}
717717

718718
case Expr.BinaryOp.OP_| =>
719719
(l, r) match {
720720
case (l: Val.Num, r: Val.Num) =>
721-
Val.Num(pos, (l.asLong | r.asLong).toDouble)
721+
Val.Num(pos, (l.asSafeLong | r.asSafeLong).toDouble)
722722
case _ => fail()
723723
}
724724

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
4611686018427387904
1+
sjsonnet.Error: numeric value outside safe integer range for bitwise operation
2+
at [<root>].(bitwise_or9.jsonnet:1:11)
3+
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.5403023058681398
1+
0.54030230586813977
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
26881171418161356000000000000000000000000000
1+
26881171418161356094253400435962903554686976
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
80.5904782547916
1+
80.590478254791606
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
9.999999999999999E-31
1+
9.9999999999999991e-31
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
22
"x": 2
3-
}
3+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.84
1+
0.83999999999999997

sjsonnet/test/resources/test_suite/.sync_ignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@
55
# error.std_parseYaml1.jsonnet: sjsonnet uses SnakeYAML/scala-yaml which
66
# successfully parses 'a: b:' as {"a":"b:"}, unlike RapidYAML which errors.
77
error.std_parseYaml1.jsonnet
8+
# unparse.jsonnet: sjsonnet renders 1/3 as 0.3333333333333333 and 1e-14 as
9+
# 1.0E-14, differing from C++ jsonnet's 0.33333333333333331 and 1e-14.
10+
unparse.jsonnet

sync_test_suites.sh

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
# A .jsonnet file is only synced if it has a
1111
# corresponding valid .golden file (upstream or already present locally), so that
1212
# Scala tests always find a matching .jsonnet.golden for each .jsonnet file.
13+
#
14+
# Golden file sync strategy:
15+
# - Both success (JSON output): exact compare, update when different.
16+
# - Success<->error transition: always update (behavioral change).
17+
# - Both error: skip (sjsonnet and upstream have different error messages/formats).
18+
# - New golden files (no local copy): copied from upstream directly.
19+
#
1320
# For new .jsonnet files that have no golden file after syncing,
1421
# golden files are generated using sjsonnet via the per-suite refresh_golden.sh scripts.
1522
#
@@ -26,21 +33,43 @@ set -euo pipefail
2633
ROOT_DIR="$(git rev-parse --show-toplevel)"
2734
cd "$ROOT_DIR"
2835

36+
# Collect .jsonnet files whose golden files need regeneration via refresh_golden_outputs.sh
37+
GOLDEN_REFRESH_FILES=$(mktemp)
38+
trap_cleanup() { rm -rf "$TEMP_DIR" "$GOLDEN_REFRESH_FILES"; }
39+
2940
# --- Configuration ---
3041
CPP_TEST_SUITE_DIR="sjsonnet/test/resources/test_suite"
3142
GO_TEST_SUITE_DIR="sjsonnet/test/resources/go_test_suite"
3243

3344
# --- Step 1: Clone upstream repositories into a temporary directory ---
3445
echo "=== Cloning upstream repositories ==="
3546
TEMP_DIR=$(mktemp -d)
36-
trap 'rm -rf "$TEMP_DIR"' EXIT
47+
trap 'rm -rf "$TEMP_DIR"; rm -f "$GOLDEN_REFRESH_FILES"' EXIT
3748

3849
echo " Cloning google/jsonnet (depth=1)..."
3950
git clone --depth=1 --quiet https://github.com/google/jsonnet.git "$TEMP_DIR/jsonnet"
4051

4152
echo " Cloning google/go-jsonnet (depth=1)..."
4253
git clone --depth=1 --quiet https://github.com/google/go-jsonnet.git "$TEMP_DIR/go-jsonnet"
4354

55+
# --- Helper: Check if a golden file contains successful (non-error) output ---
56+
# Success tests output valid JSON: objects, arrays, strings, numbers, booleans, null.
57+
# Returns 0 (true) if the output looks like JSON success, 1 (false) if it looks like an error.
58+
is_success_golden() {
59+
local file="$1"
60+
local first_line
61+
first_line=$(head -1 "$file")
62+
case "$first_line" in
63+
"{"*|"["*|'"'*|"true"|"false"|"null")
64+
return 0 ;;
65+
esac
66+
# Numbers (including negative, decimal, scientific notation)
67+
if [[ "$first_line" =~ ^-?[0-9] ]]; then
68+
return 0
69+
fi
70+
return 1
71+
}
72+
4473
# --- Step 2: Sync .jsonnet and .golden files (excluding lint-related golden) ---
4574
echo ""
4675
echo "=== Syncing test files ==="
@@ -62,7 +91,7 @@ sync_test_files() {
6291
ignore_stems_file=$(mktemp)
6392
if [ -f "$ignore_file" ]; then
6493
# Strip comments and blank lines, extract stems (remove .jsonnet extension)
65-
grep -v '^\s*#' "$ignore_file" | grep -v '^\s*$' | sed 's/\.jsonnet$//' > "$ignore_stems_file"
94+
{ grep -v '^\s*#' "$ignore_file" | grep -v '^\s*$' | sed 's/\.jsonnet$//' || true; } > "$ignore_stems_file"
6695
local ignore_count
6796
ignore_count=$(wc -l < "$ignore_stems_file" | tr -d ' ')
6897
echo " Syncing $suite_name... ($ignore_count file(s) in .sync_ignore)"
@@ -182,8 +211,10 @@ sync_test_files() {
182211
done
183212

184213
# --- Phase 3: Sync golden files ---
185-
# Never overwrite existing golden files — sjsonnet golden files use a different error
186-
# format than upstream C++/Go implementations, so existing files must be preserved.
214+
# Golden files are synced based on error/success classification:
215+
# - Both non-error: exact compare, update when different.
216+
# - One error + one non-error (success<->error change): always update.
217+
# - Both error: skip (sjsonnet and upstream have different error messages/formats).
187218

188219
# 1) Sync *.jsonnet.golden files (already in correct naming format, skip directories)
189220
for src_file in "$source_dir"/*.jsonnet.golden; do
@@ -208,10 +239,29 @@ sync_test_files() {
208239

209240
local dest_file="$target_dir/$basename"
210241

211-
# Only copy new golden files, never overwrite existing ones
212242
if [ ! -e "$dest_file" ]; then
213243
cp -r "$src_file" "$dest_file"
214244
new_golden=$((new_golden + 1))
245+
elif ! diff -q "$src_file" "$dest_file" > /dev/null 2>&1; then
246+
local src_ok=0 dest_ok=0
247+
is_success_golden "$src_file" && src_ok=1
248+
is_success_golden "$dest_file" && dest_ok=1
249+
250+
if [ "$src_ok" -eq 0 ] && [ "$dest_ok" -eq 0 ]; then
251+
# Both are error tests — keep sjsonnet's version (different error formats)
252+
true
253+
elif [ "$src_ok" -eq 1 ] && [ "$dest_ok" -eq 1 ]; then
254+
# Both success with different content — copy upstream golden
255+
cp -r "$src_file" "$dest_file"
256+
updated_golden=$((updated_golden + 1))
257+
else
258+
# Success<->error transition — regenerate golden with sjsonnet
259+
local jsonnet_file="$target_dir/${stem}.jsonnet"
260+
if [ -f "$jsonnet_file" ]; then
261+
echo "$jsonnet_file" >> "$GOLDEN_REFRESH_FILES"
262+
updated_golden=$((updated_golden + 1))
263+
fi
264+
fi
215265
fi
216266
done
217267

@@ -248,10 +298,28 @@ sync_test_files() {
248298
if [ -f "$jsonnet_file" ]; then
249299
local dest_file="$target_dir/${stem}.jsonnet.golden"
250300

251-
# Only copy new golden files, never overwrite existing ones
252301
if [ ! -e "$dest_file" ]; then
253302
cp -r "$src_entry" "$dest_file"
254303
new_golden=$((new_golden + 1))
304+
elif ! diff -q "$src_entry" "$dest_file" > /dev/null 2>&1; then
305+
local src_ok=0 dest_ok=0
306+
is_success_golden "$src_entry" && src_ok=1
307+
is_success_golden "$dest_file" && dest_ok=1
308+
309+
if [ "$src_ok" -eq 0 ] && [ "$dest_ok" -eq 0 ]; then
310+
true
311+
elif [ "$src_ok" -eq 1 ] && [ "$dest_ok" -eq 1 ]; then
312+
# Both success with different content — copy upstream golden
313+
cp -r "$src_entry" "$dest_file"
314+
updated_golden=$((updated_golden + 1))
315+
else
316+
# Success<->error transition — regenerate golden with sjsonnet
317+
local local_jsonnet="$target_dir/${stem}.jsonnet"
318+
if [ -f "$local_jsonnet" ]; then
319+
echo "$local_jsonnet" >> "$GOLDEN_REFRESH_FILES"
320+
updated_golden=$((updated_golden + 1))
321+
fi
322+
fi
255323
fi
256324
fi
257325
done
@@ -326,6 +394,24 @@ generate_missing_golden() {
326394
generate_missing_golden "$CPP_TEST_SUITE_DIR" "C++ test suite"
327395
generate_missing_golden "$GO_TEST_SUITE_DIR" "Go test suite"
328396

397+
# --- Step 3b: Refresh golden files for success<->error transitions ---
398+
if [ -s "$GOLDEN_REFRESH_FILES" ]; then
399+
# Deduplicate
400+
sort -u "$GOLDEN_REFRESH_FILES" -o "$GOLDEN_REFRESH_FILES"
401+
local_refresh_count=$(wc -l < "$GOLDEN_REFRESH_FILES" | tr -d ' ')
402+
echo ""
403+
echo "=== Refreshing $local_refresh_count golden file(s) for success<->error transitions ==="
404+
REFRESH_SCRIPT="$ROOT_DIR/sjsonnet/test/resources/refresh_golden_outputs.sh"
405+
if [ -x "$REFRESH_SCRIPT" ]; then
406+
# shellcheck disable=SC2046
407+
"$REFRESH_SCRIPT" $(cat "$GOLDEN_REFRESH_FILES")
408+
else
409+
echo " WARNING: refresh_golden_outputs.sh not found or not executable at $REFRESH_SCRIPT"
410+
echo " Files needing refresh:"
411+
cat "$GOLDEN_REFRESH_FILES" | while read -r f; do echo " $f"; done
412+
fi
413+
fi
414+
329415
# --- Step 4: Final summary ---
330416
echo ""
331417
echo "=== Sync complete ==="

0 commit comments

Comments
 (0)