diff --git a/.github/workflows/compatibility_test-windows.yml b/.github/workflows/compatibility_test-windows.yml index bc6b1ef81..04445be08 100644 --- a/.github/workflows/compatibility_test-windows.yml +++ b/.github/workflows/compatibility_test-windows.yml @@ -6,7 +6,7 @@ jobs: build: strategy: matrix: - go-version: [1.18.x, 1.21.x, 1.25.x] + go-version: [1.18.x, 1.21.x, 1.25.x, '1.27.0-rc.1'] runs-on: windows-latest steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/compatibility_test.yml b/.github/workflows/compatibility_test.yml index da19b7e05..1242886df 100644 --- a/.github/workflows/compatibility_test.yml +++ b/.github/workflows/compatibility_test.yml @@ -6,8 +6,8 @@ jobs: build: strategy: matrix: - go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x, 1.23.x, 1.24.x, 1.25.x] - os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest] + go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x, 1.23.x, 1.24.x, 1.25.x, '1.27.0-rc.1'] + os: [ubuntu-latest, ubuntu-24.04-arm, macos-15] runs-on: ${{ matrix.os }} steps: - name: Clear repository diff --git a/.github/workflows/test-arm64.yml b/.github/workflows/test-arm64.yml index 254f9e9b1..24cd0836f 100644 --- a/.github/workflows/test-arm64.yml +++ b/.github/workflows/test-arm64.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - go-version: [1.20.x, 1.22.x, 1.25.x] + go-version: [1.20.x, 1.22.x, 1.25.x, '1.27.0-rc.1'] runner_arch: [ubuntu-24.04-arm, macos-latest] runs-on: ${{ matrix.runner_arch }} diff --git a/.github/workflows/test-x86.yml b/.github/workflows/test-x86.yml index 7835bfd3b..b320ab5ca 100644 --- a/.github/workflows/test-x86.yml +++ b/.github/workflows/test-x86.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - go-version: [1.18.x, 1.21.x, 1.25.x] + go-version: [1.18.x, 1.21.x, 1.25.x, '1.27.0-rc.1'] runner_arch: [ubuntu-latest] runs-on: ${{ matrix.runner_arch }} diff --git a/README.md b/README.md index 56b6f5cfd..8ded7ca65 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A blazingly fast JSON serializing & deserializing library, accelerated by JI ## Requirement -- Go: 1.18~1.26 +- Go: 1.18~1.27 - Notice: Go1.24.0 is not supported due to the [issue](https://github.com/golang/go/issues/71672); please use a higher Go version or pass the build flag `-ldflags="-checklinkname=0"`. - OS: Linux / MacOS / Windows - CPU: AMD64 / (ARM64, need go1.20 above) diff --git a/README_ZH_CN.md b/README_ZH_CN.md index 0fa1c7042..8ffbefd03 100644 --- a/README_ZH_CN.md +++ b/README_ZH_CN.md @@ -6,7 +6,7 @@ ## 依赖 -- Go: 1.18~1.26 +- Go: 1.18~1.27 - 注意:Go1.24.0 由于 [issue](https://github.com/golang/go/issues/71672) 不可用,请升级到更高 Go 版本,或添加编译选项 `--ldflags="-checklinkname=0"` - OS: Linux / MacOS / Windows - CPU: AMD64 / (ARM64, 需要 Go1.20 以上) diff --git a/ast/api.go b/ast/api.go index e28815114..4d6de961b 100644 --- a/ast/api.go +++ b/ast/api.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2022 ByteDance Inc. diff --git a/ast/api_compat.go b/ast/api_compat.go index e88591bd0..834c7d215 100644 --- a/ast/api_compat.go +++ b/ast/api_compat.go @@ -1,5 +1,5 @@ -//go:build (!amd64 && !arm64) || go1.27 || !go1.17 || (arm64 && !go1.20) -// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20 +//go:build (!amd64 && !arm64) || go1.28 || !go1.17 || (arm64 && !go1.20) +// +build !amd64,!arm64 go1.28 !go1.17 arm64,!go1.20 /* * Copyright 2022 ByteDance Inc. diff --git a/ast/api_native_test.go b/ast/api_native_test.go index 193d7d0bc..c742396c0 100644 --- a/ast/api_native_test.go +++ b/ast/api_native_test.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2022 ByteDance Inc. diff --git a/compat.go b/compat.go index dcf9ef4ea..83cc6c210 100644 --- a/compat.go +++ b/compat.go @@ -1,5 +1,5 @@ -//go:build (!amd64 && !arm64) || go1.27 || !go1.17 || (arm64 && !go1.20) -// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20 +//go:build (!amd64 && !arm64) || go1.28 || !go1.17 || (arm64 && !go1.20) +// +build !amd64,!arm64 go1.28 !go1.17 arm64,!go1.20 /* * Copyright 2021 ByteDance Inc. diff --git a/decode_test.go b/decode_test.go index 08a1d6f99..d00fc2fd7 100644 --- a/decode_test.go +++ b/decode_test.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/decoder/decoder_compat.go b/decoder/decoder_compat.go index 12a7cede4..98b5ce620 100644 --- a/decoder/decoder_compat.go +++ b/decoder/decoder_compat.go @@ -1,5 +1,5 @@ -//go:build (!amd64 && !arm64) || go1.27 || !go1.17 || (arm64 && !go1.20) -// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20 +//go:build (!amd64 && !arm64) || go1.28 || !go1.17 || (arm64 && !go1.20) +// +build !amd64,!arm64 go1.28 !go1.17 arm64,!go1.20 /* * Copyright 2023 ByteDance Inc. diff --git a/decoder/decoder_native.go b/decoder/decoder_native.go index 8b51623b6..d8010f9ad 100644 --- a/decoder/decoder_native.go +++ b/decoder/decoder_native.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2023 ByteDance Inc. diff --git a/decoder/decoder_native_test.go b/decoder/decoder_native_test.go index 07ffb0cee..58a9177d1 100644 --- a/decoder/decoder_native_test.go +++ b/decoder/decoder_native_test.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/docs/json-v2-alignment.md b/docs/json-v2-alignment.md new file mode 100644 index 000000000..87cab4fb8 --- /dev/null +++ b/docs/json-v2-alignment.md @@ -0,0 +1,80 @@ +# Sonic and `encoding/json` v2 (Go 1.27+) + +## Background + +Starting with **Go 1.27**, the standard library `encoding/json` v1 package is +implemented on top of the new **json v2** implementation (`encoding/json/v2` + +`encoding/json/jsontext`) with v1 compatibility options. The v1 API remains +supported, but some edge cases still produce different errors or values when the +json v2 backend is active. Builds can temporarily opt out with +`GOEXPERIMENT=nojsonv2` (this opt-out is expected to be removed in a future Go +release). + +Sonic is a drop-in replacement for `encoding/json` and currently implements +**v1 semantics**. On Go 1.27 with the default json v2 backend, a small set of +legacy parity tests still diverge between `sonic` and the standard library. This +document tracks those divergences. + +> Sonic still **builds and runs correctly** on Go 1.27 (native JIT path). These +> are *semantic* differences against the new std reference, not crashes or +> miscompilations in sonic. + +## How tests handle this + +`issue_test` contains cases that assert byte-for-byte parity or exact error/value +parity against the *live* `encoding/json`. Those cases are gated on a +build-tag-derived constant: + +- `issue_test/jsonv2_compat_test.go` (`//go:build goexperiment.jsonv2`) → `stdUsesJSONV2 = true` +- `issue_test/jsonv1_compat_test.go` (`//go:build !goexperiment.jsonv2`) → `stdUsesJSONV2 = false` + +The `goexperiment.jsonv2` tag is set automatically when the json v2 backend is +active (default on Go 1.27+, unset under `GOEXPERIMENT=nojsonv2` and on Go ≤1.26). +Tests keep Sonic-only assertions where they have stable expected values and skip +only the std parity check or whole parity-only test when `stdUsesJSONV2` is true, +so: + +- Go ≤ 1.26, or Go 1.27 with `GOEXPERIMENT=nojsonv2`: full parity coverage runs. +- Go 1.27 default (v2): the divergent std parity assertions are skipped. + +## Known divergences (surfaced by issue_test) + +| Test | Area | sonic / legacy behavior | Go 1.27 std jsonv2-backend behavior | +|------|------|---------------------|---------------------------| +| Issue460 | number → float32/float64 overflow | returns error, leaves dest at 0 | saturates dest to `±Inf` (still returns error) | +| Issue491 | marshal/unmarshal unsupported types | `map[*T]*T`, `[]chan T`, etc. error as unsupported | some pointer-keyed maps now marshal successfully; unsupported unmarshal error handling differs | +| Issue747 | map key float32/float64 | v1 parsing | v2 number/key parsing differs | +| Issue144 | `,string` option on pointer/number fields | v1 string-option semantics | v2 string-option semantics | +| Issue379 | unmarshal into `MyPtr`/`**T` with Unmarshaler | v1 pointer-allocation/Unmarshaler dispatch | v2 dispatch differs | +| Issue670 | custom `UnmarshalJSON` error cases | v1 error/typing | v2 error/typing | +| Issue758 | self-referential `interface{}` (`v = &v`) | sonic handles it | **v2 std recurses to stack overflow** (sonic is fine) | +| Issue772 | skipping iface-typed fields (non-ptr / nil) | v1 skip behavior | v2 skip behavior differs | +| Issue824 (`TestFloatMap`) | marshal `map[float64]string` | **errors** (`json: unsupported type: map[float64]string`) | **marshals successfully** (`{"1":""}`) | +| Issue827 | `TextMarshaler` types (e.g. `string3`) | v1 TextMarshaler handling | v2 TextMarshaler handling differs | + +Additional documented json v2 behavior changes are likely to affect parity if +Sonic later chooses to expose a v2-compatible mode (not all currently covered by +a dedicated test): + +- **Stricter UTF-8**: invalid UTF-8 bytes in JSON strings are rejected. +- **Duplicate object keys**: objects with duplicate member names are rejected. +- Error message text differs (sonic already differs here, generally tolerated). + +## Decision / status + +- **Supporting Go 1.27 (build + run): done.** Native path validated; full test + suite green under `GOEXPERIMENT=nojsonv2` and with the v2 parity gates above. +- **Aligning Sonic to json v2 behavior: not started — separate workstream.** + This is a larger design question (whether/when Sonic offers a v2-compatible + API). Track here as the list of behaviors to reconcile if/when that work is + scheduled. + +## Reproducing locally + +```sh +# v2 default (skips parity tests) +env -u GOROOT GOTOOLCHAIN=local /path/to/go1.27/bin/go test ./issue_test/... + +# force v1 semantics (full parity coverage) +env -u GOROOT GOEXPERIMENT=nojsonv2 GOTOOLCHAIN=local /path/to/go1.27/bin/go test ./issue_test/... +``` diff --git a/encode_test.go b/encode_test.go index fb39c68e9..8a4ff4da8 100644 --- a/encode_test.go +++ b/encode_test.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/encoder/encoder_compat.go b/encoder/encoder_compat.go index ae4a76e54..3fd8468d1 100644 --- a/encoder/encoder_compat.go +++ b/encoder/encoder_compat.go @@ -1,5 +1,5 @@ -//go:build (!amd64 && !arm64) || go1.27 || !go1.17 || (arm64 && !go1.20) -// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20 +//go:build (!amd64 && !arm64) || go1.28 || !go1.17 || (arm64 && !go1.20) +// +build !amd64,!arm64 go1.28 !go1.17 arm64,!go1.20 /* * Copyright 2023 ByteDance Inc. diff --git a/encoder/encoder_native.go b/encoder/encoder_native.go index d62fb37d4..acbfaaf49 100644 --- a/encoder/encoder_native.go +++ b/encoder/encoder_native.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2023 ByteDance Inc. diff --git a/encoder/encoder_native_test.go b/encoder/encoder_native_test.go index c891a2cca..63d4218c0 100644 --- a/encoder/encoder_native_test.go +++ b/encoder/encoder_native_test.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/internal/compat/warn.go b/internal/compat/warn.go index 7b2ecb0b9..67c023a34 100644 --- a/internal/compat/warn.go +++ b/internal/compat/warn.go @@ -1,12 +1,13 @@ -// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20 +//go:build (!amd64 && !arm64) || go1.28 || !go1.17 || (arm64 && !go1.20) +// +build !amd64,!arm64 go1.28 !go1.17 arm64,!go1.20 package compat import ( - "fmt" - "os" + "fmt" + "os" ) func Warn(prefix string) { - fmt.Fprintf(os.Stderr, "WARNING: %s only supports (go1.17~1.26 and amd64 CPU) or (go1.20~1.26 and arm64 CPU), but your environment is not suitable and will fallback to encoding/json\n", prefix) + fmt.Fprintf(os.Stderr, "WARNING: %s only supports (go1.17~1.27 and amd64 CPU) or (go1.20~1.27 and arm64 CPU), but your environment is not suitable and will fallback to encoding/json\n", prefix) } diff --git a/internal/decoder/api/decoder_amd64.go b/internal/decoder/api/decoder_amd64.go index aea6bfc46..b5161a121 100644 --- a/internal/decoder/api/decoder_amd64.go +++ b/internal/decoder/api/decoder_amd64.go @@ -1,5 +1,5 @@ -//go:build go1.17 && !go1.27 -// +build go1.17,!go1.27 +//go:build go1.17 && !go1.28 +// +build go1.17,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/internal/decoder/api/decoder_arm64.go b/internal/decoder/api/decoder_arm64.go index 42f6d68d8..b1e751005 100644 --- a/internal/decoder/api/decoder_arm64.go +++ b/internal/decoder/api/decoder_arm64.go @@ -1,5 +1,5 @@ -//go:build go1.17 && !go1.27 -// +build go1.17,!go1.27 +//go:build go1.17 && !go1.28 +// +build go1.17,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/internal/decoder/jitdec/asm_stubs_amd64_go121.go b/internal/decoder/jitdec/asm_stubs_amd64_go121.go index 7908c1d6d..38a04b53f 100644 --- a/internal/decoder/jitdec/asm_stubs_amd64_go121.go +++ b/internal/decoder/jitdec/asm_stubs_amd64_go121.go @@ -1,5 +1,5 @@ -//go:build go1.21 && !go1.27 -// +build go1.21,!go1.27 +//go:build go1.21 && !go1.28 +// +build go1.21,!go1.28 // Copyright 2023 CloudWeGo Authors // diff --git a/internal/decoder/jitdec/assembler_regabi_amd64.go b/internal/decoder/jitdec/assembler_regabi_amd64.go index 366e8beb4..4c6bdcfb3 100644 --- a/internal/decoder/jitdec/assembler_regabi_amd64.go +++ b/internal/decoder/jitdec/assembler_regabi_amd64.go @@ -1,5 +1,5 @@ -//go:build go1.17 && !go1.27 -// +build go1.17,!go1.27 +//go:build go1.17 && !go1.28 +// +build go1.17,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/internal/decoder/jitdec/generic_regabi_amd64.go b/internal/decoder/jitdec/generic_regabi_amd64.go index 7ddf631d6..1ebc8afbe 100644 --- a/internal/decoder/jitdec/generic_regabi_amd64.go +++ b/internal/decoder/jitdec/generic_regabi_amd64.go @@ -1,5 +1,5 @@ -//go:build go1.17 && !go1.27 -// +build go1.17,!go1.27 +//go:build go1.17 && !go1.28 +// +build go1.17,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/internal/encoder/alg/spec.go b/internal/encoder/alg/spec.go index 3a3241c11..f58ca0ca4 100644 --- a/internal/encoder/alg/spec.go +++ b/internal/encoder/alg/spec.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.16 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.16,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.16 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.16,!go1.28 arm64,go1.20,!go1.28 /** * Copyright 2024 ByteDance Inc. diff --git a/internal/encoder/alg/spec_compat.go b/internal/encoder/alg/spec_compat.go index b25308228..9f2bbf51a 100644 --- a/internal/encoder/alg/spec_compat.go +++ b/internal/encoder/alg/spec_compat.go @@ -1,5 +1,5 @@ -//go:build (!amd64 && !arm64) || go1.27 || !go1.16 || (arm64 && !go1.20) -// +build !amd64,!arm64 go1.27 !go1.16 arm64,!go1.20 +//go:build (!amd64 && !arm64) || go1.28 || !go1.16 || (arm64 && !go1.20) +// +build !amd64,!arm64 go1.28 !go1.16 arm64,!go1.20 /** * Copyright 2024 ByteDance Inc. diff --git a/internal/encoder/x86/asm_stubs_amd64_go121.go b/internal/encoder/x86/asm_stubs_amd64_go121.go index 818e98307..4748a6385 100644 --- a/internal/encoder/x86/asm_stubs_amd64_go121.go +++ b/internal/encoder/x86/asm_stubs_amd64_go121.go @@ -1,5 +1,5 @@ -//go:build go1.21 && !go1.27 -// +build go1.21,!go1.27 +//go:build go1.21 && !go1.28 +// +build go1.21,!go1.28 // Copyright 2023 CloudWeGo Authors // diff --git a/internal/encoder/x86/assembler_export_amd64.go b/internal/encoder/x86/assembler_export_amd64.go index 6df604286..69d81f3de 100644 --- a/internal/encoder/x86/assembler_export_amd64.go +++ b/internal/encoder/x86/assembler_export_amd64.go @@ -1,5 +1,5 @@ -//go:build go1.17 && !go1.27 -// +build go1.17,!go1.27 +//go:build go1.17 && !go1.28 +// +build go1.17,!go1.28 package x86 diff --git a/internal/encoder/x86/assembler_regabi_amd64.go b/internal/encoder/x86/assembler_regabi_amd64.go index a62579b7c..b1c84eb10 100644 --- a/internal/encoder/x86/assembler_regabi_amd64.go +++ b/internal/encoder/x86/assembler_regabi_amd64.go @@ -1,5 +1,5 @@ -//go:build go1.17 && !go1.27 -// +build go1.17,!go1.27 +//go:build go1.17 && !go1.28 +// +build go1.17,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/internal/encoder/x86/debug_go117.go b/internal/encoder/x86/debug_go117.go index 79a86f423..aa4a599bf 100644 --- a/internal/encoder/x86/debug_go117.go +++ b/internal/encoder/x86/debug_go117.go @@ -1,5 +1,5 @@ -//go:build go1.17 && !go1.27 -// +build go1.17,!go1.27 +//go:build go1.17 && !go1.28 +// +build go1.17,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/internal/rt/base64_amd64.go b/internal/rt/base64_amd64.go index f2f1ad8fb..59ab11ce8 100644 --- a/internal/rt/base64_amd64.go +++ b/internal/rt/base64_amd64.go @@ -1,5 +1,5 @@ -//go:build amd64 && go1.17 && !go1.27 -// +build amd64,go1.17,!go1.27 +//go:build amd64 && go1.17 && !go1.28 +// +build amd64,go1.17,!go1.28 package rt diff --git a/internal/rt/base64_compat.go b/internal/rt/base64_compat.go index 88e28ca3a..4e23722da 100644 --- a/internal/rt/base64_compat.go +++ b/internal/rt/base64_compat.go @@ -1,5 +1,5 @@ -//go:build !amd64 || !go1.17 || go1.27 -// +build !amd64 !go1.17 go1.27 +//go:build !amd64 || !go1.17 || go1.28 +// +build !amd64 !go1.17 go1.28 package rt diff --git a/internal/rt/gcwb.go b/internal/rt/gcwb.go index fb7e1cc37..eafef90a0 100644 --- a/internal/rt/gcwb.go +++ b/internal/rt/gcwb.go @@ -1,5 +1,5 @@ -//go:build go1.21 && !go1.27 -// +build go1.21,!go1.27 +//go:build go1.21 && !go1.28 +// +build go1.21,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/internal/rt/map.go b/internal/rt/map.go index 61a99dfec..7f2c6cba6 100644 --- a/internal/rt/map.go +++ b/internal/rt/map.go @@ -22,4 +22,6 @@ type GoMapIterator struct { I uint8 Bucket uintptr CheckBucket uintptr + // different from go1.23, noswissmap + ClearSeq uint64 } diff --git a/issue_test/issue144_test.go b/issue_test/issue144_test.go index 398ba8e33..8c7b67608 100644 --- a/issue_test/issue144_test.go +++ b/issue_test/issue144_test.go @@ -12,33 +12,36 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. -*/ + */ package issue_test import ( - `encoding/json` - `testing` + "encoding/json" + "testing" - `github.com/stretchr/testify/require` - `github.com/davecgh/go-spew/spew` - `github.com/bytedance/sonic` + "github.com/bytedance/sonic" + "github.com/davecgh/go-spew/spew" + "github.com/stretchr/testify/require" ) type Issue144_StringOption struct { - S1 *string `json:"s1,string"` - S2 *string `json:"s2,string"` - S3 string `json:"s3,string"` - J1 json.Number `json:"j1,string"` - J2 *json.Number `json:"j2,string"` - J3 *json.Number `json:"j3,string"` - I1 int `json:"i1,string"` - I2 *int `json:"i2,string"` - I3 *int `json:"i3,string"` + S1 *string `json:"s1,string"` + S2 *string `json:"s2,string"` + S3 string `json:"s3,string"` + J1 json.Number `json:"j1,string"` + J2 *json.Number `json:"j2,string"` + J3 *json.Number `json:"j3,string"` + I1 int `json:"i1,string"` + I2 *int `json:"i2,string"` + I3 *int `json:"i3,string"` } func TestIssue144_StringOption(t *testing.T) { - data := []byte(`{ + if stdUsesJSONV2 { + t.Skip("std encoding/json jsonv2 backend differs for this legacy parity case; see docs/json-v2-alignment.md") + } + data := []byte(`{ "s1":"\"null\"", "s2":"null", "s3":"null", @@ -50,23 +53,23 @@ func TestIssue144_StringOption(t *testing.T) { "i3":"-123" }`) - var v1, v2 Issue144_StringOption - e1 := json.Unmarshal(data, &v1) - e2 := sonic.Unmarshal(data, &v2) - require.NoError(t, e1) - require.NoError(t, e2) - require.Equal(t, v1, v2) - spew.Dump(v1) + var v1, v2 Issue144_StringOption + e1 := json.Unmarshal(data, &v1) + e2 := sonic.Unmarshal(data, &v2) + require.NoError(t, e1) + require.NoError(t, e2) + require.Equal(t, v1, v2) + spew.Dump(v1) - i, j, s := int(1), json.Number("1"), "null" - v1.I2, v2.I2 = &i, &i - v1.J2, v2.J2 = &j, &j - v1.S2, v2.S2 = &s, &s + i, j, s := int(1), json.Number("1"), "null" + v1.I2, v2.I2 = &i, &i + v1.J2, v2.J2 = &j, &j + v1.S2, v2.S2 = &s, &s - e1 = json.Unmarshal(data, &v1) - e2 = sonic.Unmarshal(data, &v2) - require.NoError(t, e1) - require.NoError(t, e2) - require.Equal(t, v1, v2) - spew.Dump(v1) -} \ No newline at end of file + e1 = json.Unmarshal(data, &v1) + e2 = sonic.Unmarshal(data, &v2) + require.NoError(t, e1) + require.NoError(t, e2) + require.Equal(t, v1, v2) + spew.Dump(v1) +} diff --git a/issue_test/issue379_test.go b/issue_test/issue379_test.go index 109e26cbc..5d2ab67bf 100644 --- a/issue_test/issue379_test.go +++ b/issue_test/issue379_test.go @@ -26,97 +26,100 @@ import ( ) type Foo struct { - Name string + Name string } func (f *Foo) UnmarshalJSON(data []byte) error { - println("UnmarshalJSON called!!!") - f.Name = "Unmarshaler" - return nil + println("UnmarshalJSON called!!!") + f.Name = "Unmarshaler" + return nil } type MyPtr *Foo func TestIssue379(t *testing.T) { - tests := []struct{ - data string - newf func() interface{} - equal func(exp, act interface{}) bool - } { - { - data: `{"Name":"MyPtr"}`, - newf: func() interface{} { return &Foo{} }, - }, - { - data: `{"Name":"MyPtr"}`, - newf: func() interface{} { ptr := &Foo{}; return &ptr }, - }, - { - data: `{"Name":"MyPtr"}`, - newf: func() interface{} { return MyPtr(&Foo{}) }, - }, - { - data: `{"Name":"MyPtr"}`, - newf: func() interface{} { ptr := MyPtr(&Foo{}); return &ptr }, - }, + if stdUsesJSONV2 { + t.Skip("std encoding/json jsonv2 backend differs for this legacy parity case; see docs/json-v2-alignment.md") + } + tests := []struct { + data string + newf func() interface{} + equal func(exp, act interface{}) bool + }{ + { + data: `{"Name":"MyPtr"}`, + newf: func() interface{} { return &Foo{} }, + }, + { + data: `{"Name":"MyPtr"}`, + newf: func() interface{} { ptr := &Foo{}; return &ptr }, + }, + { + data: `{"Name":"MyPtr"}`, + newf: func() interface{} { return MyPtr(&Foo{}) }, + }, + { + data: `{"Name":"MyPtr"}`, + newf: func() interface{} { ptr := MyPtr(&Foo{}); return &ptr }, + }, - // TODO: fix jit tests - // { - // data: `null`, - // newf: func() interface{} { return MyPtr(&Foo{}) }, - // }, - // { - // data: `null`, - // newf: func() interface{} { ptr := MyPtr(&Foo{}); return &ptr }, - // }, - // { - // data: `null`, - // newf: func() interface{} { - // x := &Foo{Name: "mock"} - // return x }, - // }, - // { - // data: `null`, - // newf: func() interface{} { ptr := &Foo{}; return &ptr }, - // }, - { - data: `{"map":{"Name":"MyPtr"}}`, - newf: func() interface{} { return new(map[string]MyPtr) }, - }, - { - data: `{"map":{"Name":"MyPtr"}}`, - newf: func() interface{} { return new(map[string]*Foo) }, - }, - { - data: `{"map":{"Name":"MyPtr"}}`, - newf: func() interface{} { return new(map[string]*MyPtr) }, - }, - { - data: `[{"Name":"MyPtr"}]`, - newf: func() interface{} { return new([]MyPtr) }, - }, - { - data: `[{"Name":"MyPtr"}]`, - newf: func() interface{} { return new([]*MyPtr) }, - }, - { - data: `[{"Name":"MyPtr"}]`, - newf: func() interface{} { return new([]*Foo) }, - }, - } + // TODO: fix jit tests + // { + // data: `null`, + // newf: func() interface{} { return MyPtr(&Foo{}) }, + // }, + // { + // data: `null`, + // newf: func() interface{} { ptr := MyPtr(&Foo{}); return &ptr }, + // }, + // { + // data: `null`, + // newf: func() interface{} { + // x := &Foo{Name: "mock"} + // return x }, + // }, + // { + // data: `null`, + // newf: func() interface{} { ptr := &Foo{}; return &ptr }, + // }, + { + data: `{"map":{"Name":"MyPtr"}}`, + newf: func() interface{} { return new(map[string]MyPtr) }, + }, + { + data: `{"map":{"Name":"MyPtr"}}`, + newf: func() interface{} { return new(map[string]*Foo) }, + }, + { + data: `{"map":{"Name":"MyPtr"}}`, + newf: func() interface{} { return new(map[string]*MyPtr) }, + }, + { + data: `[{"Name":"MyPtr"}]`, + newf: func() interface{} { return new([]MyPtr) }, + }, + { + data: `[{"Name":"MyPtr"}]`, + newf: func() interface{} { return new([]*MyPtr) }, + }, + { + data: `[{"Name":"MyPtr"}]`, + newf: func() interface{} { return new([]*Foo) }, + }, + } - for i, tt := range tests { - println(i) - jv, sv := tt.newf(), tt.newf() - jerr := json.Unmarshal([]byte(tt.data), jv) - serr := sonic.Unmarshal([]byte(tt.data), sv) - require.Equal(t, jv, sv) - require.Equal(t, jerr, serr) + for i, tt := range tests { + println(i) + jv, sv := tt.newf(), tt.newf() + jerr := json.Unmarshal([]byte(tt.data), jv) + serr := sonic.Unmarshal([]byte(tt.data), sv) + require.Equal(t, jv, sv) + require.Equal(t, jerr, serr) - jv, sv = tt.newf(), tt.newf() - jerr = json.Unmarshal([]byte(tt.data), &jv) - serr = sonic.Unmarshal([]byte(tt.data), &sv) - require.Equal(t, jv, sv, spew.Sdump(jv, sv)) - require.Equal(t, jerr, serr) - } + jv, sv = tt.newf(), tt.newf() + jerr = json.Unmarshal([]byte(tt.data), &jv) + serr = sonic.Unmarshal([]byte(tt.data), &sv) + require.Equal(t, jv, sv, spew.Sdump(jv, sv)) + require.Equal(t, jerr, serr) + } } diff --git a/issue_test/issue460_test.go b/issue_test/issue460_test.go index d76a69d8d..dba23b3f4 100644 --- a/issue_test/issue460_test.go +++ b/issue_test/issue460_test.go @@ -12,54 +12,59 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. -*/ + */ package issue_test + import ( - `encoding/json` - `testing` - `github.com/bytedance/sonic` - `github.com/stretchr/testify/require` + "encoding/json" + "testing" + + "github.com/bytedance/sonic" + "github.com/stretchr/testify/require" ) // TODO: fix float32 func TestIssue460_UnmarshalMaxFloat32(t *testing.T) { - tests := []string { - // max.MaxFloat32 - "3.40282346638528859811704183484516925440e+38", + if stdUsesJSONV2 { + t.Skip("std encoding/json jsonv2 backend differs for this legacy parity case; see docs/json-v2-alignment.md") + } + tests := []string{ + // max.MaxFloat32 + "3.40282346638528859811704183484516925440e+38", - // round up to max.MaxFloat32 - // "3.402823469e+38", - "3.40282346e+38", - // "3.40282347e+38", - // "3.40282348e+38", - // "3.4028235e+38", - // TODO: fix this case - // "3.4028235677973366e+38", // Bits: 1000111111011111111111111111111111_10000000000000000000000000000 + // round up to max.MaxFloat32 + // "3.402823469e+38", + "3.40282346e+38", + // "3.40282347e+38", + // "3.40282348e+38", + // "3.4028235e+38", + // TODO: fix this case + // "3.4028235677973366e+38", // Bits: 1000111111011111111111111111111111_10000000000000000000000000000 - // overflow for float32, round up to max.MaxFloat32 + 1 - "3.402823567797337e+38", // Bits: 1000111111011111111111111111111111_10000000000000000000000000001 - "3.402823567797338e+38", - // "3.4028236e+38", - } + // overflow for float32, round up to max.MaxFloat32 + 1 + "3.402823567797337e+38", // Bits: 1000111111011111111111111111111111_10000000000000000000000000001 + "3.402823567797338e+38", + // "3.4028236e+38", + } - t.Run("max float32", func(t *testing.T) { - for _, data := range(tests) { - var f1, f2 float32 - se := sonic.UnmarshalString(data, &f1) - je := json.Unmarshal([]byte(data), &f2) - require.Equal(t, se != nil, je != nil, data, se, je) - require.Equal(t, f2, f1, data) - } - }) + t.Run("max float32", func(t *testing.T) { + for _, data := range tests { + var f1, f2 float32 + se := sonic.UnmarshalString(data, &f1) + je := json.Unmarshal([]byte(data), &f2) + require.Equal(t, se != nil, je != nil, data, se, je) + require.Equal(t, f2, f1, data) + } + }) - t.Run("min float32", func(t *testing.T) { - for _, data := range(tests) { - data = string("-") + data - var f1, f2 float32 - se := sonic.UnmarshalString(data, &f1) - je := json.Unmarshal([]byte(data), &f2) - require.Equal(t, se != nil, je != nil, data, se, je) - require.Equal(t, f2, f1, data) - } - }) + t.Run("min float32", func(t *testing.T) { + for _, data := range tests { + data = string("-") + data + var f1, f2 float32 + se := sonic.UnmarshalString(data, &f1) + je := json.Unmarshal([]byte(data), &f2) + require.Equal(t, se != nil, je != nil, data, se, je) + require.Equal(t, f2, f1, data) + } + }) } diff --git a/issue_test/issue491_test.go b/issue_test/issue491_test.go index 3d1fb65ac..8d9ec2564 100644 --- a/issue_test/issue491_test.go +++ b/issue_test/issue491_test.go @@ -13,8 +13,8 @@ type Unsupported struct { } type StructWithUnsupported struct { - Foo *Unsupported `json:"foo"` - Bar *Unsupported `json:"bar,omitempty"` + Foo *Unsupported `json:"foo"` + Bar *Unsupported `json:"bar,omitempty"` } type Foo2 struct { @@ -27,8 +27,11 @@ type MockContext struct { } func TestIssue491_MarshalUnsupportedType(t *testing.T) { + if stdUsesJSONV2 { + t.Skip("std encoding/json jsonv2 backend differs for this legacy parity case; see docs/json-v2-alignment.md") + } // Wrapper a unbale serde type - tests := []interface{} { + tests := []interface{}{ map[string]*Function{}, map[*Function]*Function{}, map[string]Function{}, @@ -43,32 +46,36 @@ func TestIssue491_MarshalUnsupportedType(t *testing.T) { chan int(nil), new(MockContext), } - for _, v := range(tests) { + for _, v := range tests { assertMarshal(t, sonic.ConfigDefault, v) } } - func TestIssue491_UnmarshalUnsupported(t *testing.T) { +func TestIssue491_UnmarshalUnsupported(t *testing.T) { + if stdUsesJSONV2 { + t.Skip("std encoding/json jsonv2 backend differs for this legacy parity case; see docs/json-v2-alignment.md") + } + type Test struct { - data string + data string value interface{} } tests := []unmTestCase{ { - name: "unsupported type slice", - data: []byte("null"), - newfn: func() interface{} { return new([]Function)}, + name: "unsupported type slice", + data: []byte("null"), + newfn: func() interface{} { return new([]Function) }, }, - { - name: "unsupported type", - data: []byte("[null, null]"), + { + name: "unsupported type", + data: []byte("[null, null]"), newfn: func() interface{} { return new([]chan int) }, }, { name: "unsupported type in struct", data: []byte("{\"foo\": null}"), - newfn: func() interface{} { + newfn: func() interface{} { return new(struct { Foo Function }) @@ -77,13 +84,12 @@ func TestIssue491_MarshalUnsupportedType(t *testing.T) { { name: "unsupported type in map key should be error", data: []byte("null"), - newfn: func() interface{} { + newfn: func() interface{} { return map[chan int]Function{} }, }, } - for _, v := range(tests) { + for _, v := range tests { assertUnmarshal(t, sonic.ConfigDefault, v) } - } - \ No newline at end of file +} diff --git a/issue_test/issue670_test.go b/issue_test/issue670_test.go index 15f554994..9e3d9fbd3 100644 --- a/issue_test/issue670_test.go +++ b/issue_test/issue670_test.go @@ -26,13 +26,16 @@ import ( ) func TestIssue670_JSONMarshaler(t *testing.T) { - var obj = Issue670JSONMarshaler{ D: Date(time.Now().Unix()) } + var obj = Issue670JSONMarshaler{D: Date(time.Now().Unix())} so, _ := sonic.MarshalString(obj) eo, _ := json.Marshal(obj) assert.Equal(t, string(eo), so) } func TestIssue670_JSONUnmarshaler(t *testing.T) { + if stdUsesJSONV2 { + t.Skip("std encoding/json jsonv2 backend differs for this legacy parity case; see docs/json-v2-alignment.md") + } // match eo := []byte(`{"D":"2021-08-26","E":1}`) et := reflect.TypeOf(Issue670JSONMarshaler{}) @@ -52,7 +55,7 @@ func testUnmarshal(t *testing.T, eo []byte, rt reflect.Type, checkobj bool) { es := sonic.Unmarshal(eo, obj) obj2 := reflect.New(rt).Interface() ee := json.Unmarshal(eo, obj2) - assert.Equal(t, ee ==nil, es == nil, es) + assert.Equal(t, ee == nil, es == nil, es) if checkobj { assert.Equal(t, obj2, obj) } @@ -61,7 +64,7 @@ func testUnmarshal(t *testing.T, eo []byte, rt reflect.Type, checkobj bool) { } func TestIssue670_TextMarshaler(t *testing.T) { - var obj = Issue670TextMarshaler{ D: int(time.Now().Unix()) } + var obj = Issue670TextMarshaler{D: int(time.Now().Unix())} so, _ := sonic.MarshalString(obj) eo, _ := json.Marshal(obj) assert.Equal(t, string(eo), so) @@ -84,8 +87,8 @@ func TestIssue670_TextUnmarshaler(t *testing.T) { } type Issue670JSONMarshaler struct { - D Date `form:"D" json:"D,string" query:"D"` - E int + D Date `form:"D" json:"D,string" query:"D"` + E int } type Date int64 @@ -102,7 +105,7 @@ func (d *Date) UnmarshalJSON(in []byte) error { *d = 0 return nil } - + t, err := time.Parse("2006-01-02", string(in)) if err != nil { return err @@ -112,11 +115,10 @@ func (d *Date) UnmarshalJSON(in []byte) error { } type Issue670TextMarshaler struct { - D int `form:"D" json:"D,string" query:"D"` - E int + D int `form:"D" json:"D,string" query:"D"` + E int } - type Date2 int64 func (d Date2) MarshalText() ([]byte, error) { @@ -137,4 +139,4 @@ func (d *Date2) UnmarshalText(in []byte) error { } *d = Date2(t.Unix()) return nil -} \ No newline at end of file +} diff --git a/issue_test/issue747_test.go b/issue_test/issue747_test.go index 9f45a5d2f..62111bdca 100644 --- a/issue_test/issue747_test.go +++ b/issue_test/issue747_test.go @@ -24,24 +24,24 @@ import ( func TestIssue747(t *testing.T) { tests := []struct { - name string - input string + name string + input string expected interface{} - newfn func() interface{} + newfn func() interface{} }{ { - name: "unmarshal map key float64", + name: "unmarshal map key float64", input: `{"1.2":1.8}`, expected: &map[float64]float64{ - 1.2: 1.8, + 1.2: 1.8, }, newfn: func() interface{} { return new(map[float64]float64) }, }, { - name: "unmarshal map key float32", + name: "unmarshal map key float32", input: `{"1.2":1.8}`, expected: &map[float32]float32{ - 1.2: 1.8, + 1.2: 1.8, }, newfn: func() interface{} { return new(map[float32]float32) }, }, @@ -54,7 +54,11 @@ func TestIssue747(t *testing.T) { assert.Equal(t, tt.expected, sv) assert.NoError(t, serr) - // Note: it is different from encoding/json + if stdUsesJSONV2 { + return + } + + // Note: it is different from legacy encoding/json. jerr := json.Unmarshal([]byte(tt.input), &jv) assert.NotEqual(t, jerr == nil, serr == nil) assert.NotEqual(t, jv, sv) diff --git a/issue_test/issue758_test.go b/issue_test/issue758_test.go index f753c9329..0db7fce27 100644 --- a/issue_test/issue758_test.go +++ b/issue_test/issue758_test.go @@ -6,9 +6,15 @@ import ( "github.com/bytedance/sonic" ) - func TestIssue758_UnmarshalIntoAnyPointer(t *testing.T) { - for _, cas := range []unmTestCase { + // The "recursive" self-referential eface cases make the std encoding/json + // jsonv2 backend recurse until stack overflow, which aborts the whole test + // binary. Sonic handles them, but the test compares against the live std + // reference, so skip under v2. See docs/json-v2-alignment.md. + if stdUsesJSONV2 { + t.Skip("std encoding/json jsonv2 backend stack-overflows on self-referential eface; sonic follows v1") + } + for _, cas := range []unmTestCase{ { name: "non-nil typed pointer", data: []byte(`["one","2"]`), @@ -52,10 +58,10 @@ func TestIssue758_UnmarshalIntoAnyPointer(t *testing.T) { name: "non-nil eface pointer", data: []byte(`{"a": "b"}`), newfn: func() interface{} { - var v1 interface{} = & struct { + var v1 interface{} = &struct { A string `json:"a"` B string `json:"b"` - } { + }{ A: "c", B: "d", } @@ -113,8 +119,7 @@ func TestIssue758_UnmarshalIntoAnyPointer(t *testing.T) { name: "non-nil iface type as struct field", data: []byte(`{"id": {"id": "2"},"name": "name"}`), newfn: func() interface{} { - obj := WrapperEface { - } + obj := WrapperEface{} foo := fooEface{} obj.Id = &foo return &obj @@ -124,8 +129,7 @@ func TestIssue758_UnmarshalIntoAnyPointer(t *testing.T) { name: "non-nil iface type as struct field", data: []byte(`{"name": "name", "id": null}`), newfn: func() interface{} { - obj := WrapperEface { - } + obj := WrapperEface{} a := "123" foo := fooEface{ Id: &a, @@ -142,8 +146,8 @@ func TestIssue758_UnmarshalIntoAnyPointer(t *testing.T) { } type WrapperEface struct { - Name string `json:"name"` - Id MockEface `json:"id"` + Name string `json:"name"` + Id MockEface `json:"id"` } type MockEface interface { @@ -165,5 +169,3 @@ type fooEface3 struct { func (self fooEface3) MyMock() { } - - diff --git a/issue_test/issue772_test.go b/issue_test/issue772_test.go index 43a1f6441..d71662826 100644 --- a/issue_test/issue772_test.go +++ b/issue_test/issue772_test.go @@ -1,4 +1,3 @@ - package issue_test import ( @@ -8,13 +7,15 @@ import ( ) func TestIssue772_SkipIfaceType(t *testing.T) { - for _, cas := range []unmTestCase { + if stdUsesJSONV2 { + t.Skip("std encoding/json jsonv2 backend differs for this legacy parity case; see docs/json-v2-alignment.md") + } + for _, cas := range []unmTestCase{ { name: "should skip non-ptr iface type", data: []byte(`{"id": {"id": "2"},"name": "name"}`), newfn: func() interface{} { - obj := WrapperEface { - } + obj := WrapperEface{} obj.Id = fooEface3{} return &obj }, @@ -23,8 +24,7 @@ func TestIssue772_SkipIfaceType(t *testing.T) { name: "should skip nil iface type", data: []byte(`{"id": {"id": "2"},"name": "name"}`), newfn: func() interface{} { - obj := WrapperEface { - } + obj := WrapperEface{} obj.Id = (*fooEface)(nil) return &obj }, @@ -35,6 +35,3 @@ func TestIssue772_SkipIfaceType(t *testing.T) { }) } } - - - diff --git a/issue_test/issue824_test.go b/issue_test/issue824_test.go index 3b94778c5..55c5222c0 100644 --- a/issue_test/issue824_test.go +++ b/issue_test/issue824_test.go @@ -26,6 +26,12 @@ import ( func TestFloatMap(t *testing.T) { js, err := sonic.ConfigStd.Marshal(map[float64]string{1: ""}) + + if stdUsesJSONV2 { + require.Error(t, err) + return + } + js0, err0 := json.Marshal(map[float64]string{1: ""}) require.Equal(t, err0 == nil, err == nil) require.Equal(t, string(js0), string(js)) diff --git a/issue_test/issue827_test.go b/issue_test/issue827_test.go index bcb70766d..f54781a22 100644 --- a/issue_test/issue827_test.go +++ b/issue_test/issue827_test.go @@ -144,27 +144,35 @@ func TestIssue827_AllTextMarshalerTypes(t *testing.T) { {"string3", map[Str2TM]int{s1: 1, s2: 2}}, {"custom", map[CustomTM]int{nil: 1, make(chan int): 2}}, } - + for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - jsonData, err := json.Marshal(tc.data) - require.NoError(t, err) - expected := string(jsonData) - println(tc.name, "expected:", expected) - sonicData, err := sonic.Marshal(tc.data) require.NoError(t, err) - + sonicStdData, err := sonic.ConfigStd.Marshal(tc.data) require.NoError(t, err) - require.Equal(t, expected, string(sonicStdData)) - // compare the output of sonic.Marshal - var sonicMap, jsonMap map[string]int + var sonicMap, sonicStdMap map[string]int require.NoError(t, json.Unmarshal(sonicData, &sonicMap)) + require.NoError(t, json.Unmarshal(sonicStdData, &sonicStdMap)) + require.Equal(t, sonicStdMap, sonicMap) + + if stdUsesJSONV2 { + return + } + + jsonData, err := json.Marshal(tc.data) + require.NoError(t, err) + expected := string(jsonData) + println(tc.name, "expected:", expected) + + require.Equal(t, expected, string(sonicStdData)) + + var jsonMap map[string]int require.NoError(t, json.Unmarshal(jsonData, &jsonMap)) require.Equal(t, jsonMap, sonicMap) - + }) } } diff --git a/issue_test/jsonv1_compat_test.go b/issue_test/jsonv1_compat_test.go new file mode 100644 index 000000000..1c51e8151 --- /dev/null +++ b/issue_test/jsonv1_compat_test.go @@ -0,0 +1,26 @@ +//go:build !goexperiment.jsonv2 +// +build !goexperiment.jsonv2 + +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package issue_test + +// stdUsesJSONV2 reports whether the standard library encoding/json package is +// using the json v2 backend. It is false here: either Go < 1.27, or the +// experiment was disabled with GOEXPERIMENT=nojsonv2. See +// docs/json-v2-alignment.md. +const stdUsesJSONV2 = false diff --git a/issue_test/jsonv2_compat_test.go b/issue_test/jsonv2_compat_test.go new file mode 100644 index 000000000..a1cedec50 --- /dev/null +++ b/issue_test/jsonv2_compat_test.go @@ -0,0 +1,27 @@ +//go:build goexperiment.jsonv2 +// +build goexperiment.jsonv2 + +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package issue_test + +// stdUsesJSONV2 reports whether the standard library encoding/json package is +// using the json v2 backend. This is the default since Go 1.27 (opt out with +// GOEXPERIMENT=nojsonv2). Sonic still implements v1 semantics, so tests that +// assert exact std parity gate those assertions in this mode. See +// docs/json-v2-alignment.md for the divergence list. +const stdUsesJSONV2 = true diff --git a/loader/funcdata_compat.go b/loader/funcdata_compat.go index 68d50a663..a2764866e 100644 --- a/loader/funcdata_compat.go +++ b/loader/funcdata_compat.go @@ -1,5 +1,5 @@ -//go:build !go1.17 || go1.27 -// +build !go1.17 go1.27 +//go:build !go1.17 || go1.28 +// +build !go1.17 go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/loader/funcdata_go127.go b/loader/funcdata_go127.go new file mode 100644 index 000000000..5fb6e8799 --- /dev/null +++ b/loader/funcdata_go127.go @@ -0,0 +1,129 @@ +//go:build go1.27 && !go1.28 +// +build go1.27,!go1.28 + +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package loader + +import ( + "github.com/bytedance/sonic/loader/internal/rt" + "unsafe" +) + +const ( + _Magic uint32 = 0xFFFFFFF1 +) + +// In Go 1.27 the moduledata layout changed (CL around the type/itab linker rework): +// - `types, etypes` gained a `typedesclen` field in between; +// - `itaboffset, itabsize` were added after etypes; +// - the `typelinks []int32` and `itablinks []*itab` slices were removed; +// - `typemap` was retyped from map[typeOff]*_type to map[*_type]*_type. +// +// See runtime/symtab.go in the go1.27 release branch. +type moduledata struct { + pcHeader *pcHeader + funcnametab []byte + cutab []uint32 + filetab []byte + pctab []byte + pclntable []byte + ftab []funcTab + findfunctab uintptr + minpc, maxpc uintptr // first func address, last func address + last func size + + text, etext uintptr // start/end of text, (etext-text) must be greater than MIN_FUNC + noptrdata, enoptrdata uintptr + data, edata uintptr + bss, ebss uintptr + noptrbss, enoptrbss uintptr + covctrs, ecovctrs uintptr + end, gcdata, gcbss uintptr + types, typedesclen, etypes uintptr + itaboffset, itabsize uintptr + rodata uintptr + gofunc uintptr // go.func.* is actual funcinfo object in image + epclntab uintptr + + textsectmap []textSection // see runtime/symtab.go: textAddr() + + ptab []ptabEntry + + pluginpath string + pkghashes []modulehash + + // This slice records the initializing tasks that need to be + // done to start up the program. It is built by the linker. + inittasks []unsafe.Pointer + + modulename string + modulehashes []modulehash + + hasmain uint8 // 1 if module contains the main function, 0 otherwise + bad bool // module failed to load and should be ignored + + gcdatamask, gcbssmask bitVector + + typemap map[*rt.GoType]*rt.GoType // *_type to use from previous module + + next *moduledata +} + +type _func struct { + entryOff uint32 // start pc, as offset from moduledata.text/pcHeader.textStart + nameOff int32 // function name, as index into moduledata.funcnametab. + + args int32 // in/out args size + deferreturn uint32 // offset of start of a deferreturn call instruction from entry, if any. + + pcsp uint32 + pcfile uint32 + pcln uint32 + npcdata uint32 + cuOffset uint32 // runtime.cutab offset of this function's CU + startLine int32 // line number of start of function (func keyword/TEXT directive) + funcID uint8 // set for certain special runtime functions + flag uint8 + _ [1]byte // pad + nfuncdata uint8 // + + // The end of the struct is followed immediately by two variable-length + // arrays that reference the pcdata and funcdata locations for this + // function. + + // pcdata contains the offset into moduledata.pctab for the start of + // that index's table. e.g., + // &moduledata.pctab[_func.pcdata[_PCDATA_UnsafePoint]] is the start of + // the unsafe point table. + // + // An offset of 0 indicates that there is no table. + // + // pcdata [npcdata]uint32 + + // funcdata contains the offset past moduledata.gofunc which contains a + // pointer to that index's funcdata. e.g., + // *(moduledata.gofunc + _func.funcdata[_FUNCDATA_ArgsPointerMaps]) is + // the argument pointer map. + // + // An offset of ^uint32(0) indicates that there is no entry. + // + // funcdata [nfuncdata]uint32 +} + +func setEpclntab(mod *moduledata, val uintptr) { + mod.epclntab = val +} diff --git a/loader/moduledata.go b/loader/moduledata.go index 7a22c04a3..98597eb51 100644 --- a/loader/moduledata.go +++ b/loader/moduledata.go @@ -1,5 +1,5 @@ -// go:build go1.18 && !go1.27 -// +build go1.18,!go1.27 +//go:build go1.18 && !go1.28 +// +build go1.18,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/rfc_test.go b/rfc_test.go index 82cc704c0..0210fbbfe 100644 --- a/rfc_test.go +++ b/rfc_test.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 package sonic_test diff --git a/scripts/test_pcsp.py b/scripts/test_pcsp.py index 04c2baa81..26fa38faf 100755 --- a/scripts/test_pcsp.py +++ b/scripts/test_pcsp.py @@ -63,7 +63,7 @@ def check_ge_go_1_23() -> bool: if ver is None: print("Cannot get go version") exit(1) - major, minor, _ = ver.split('.') + major, minor = ver.split('.')[:2] return int(major) > 1 or int(minor) >= 23 def main(): diff --git a/search_test.go b/search_test.go index c9248b917..3ad240d80 100644 --- a/search_test.go +++ b/search_test.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/sonic.go b/sonic.go index c6a825ab2..95a1c69c5 100644 --- a/sonic.go +++ b/sonic.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/unquote/unquote.go b/unquote/unquote.go index fa18b631e..644e793f4 100644 --- a/unquote/unquote.go +++ b/unquote/unquote.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2021 ByteDance Inc. diff --git a/unquote/unquote_fallback.go b/unquote/unquote_fallback.go index 81a5bfca4..f9fcaf59b 100644 --- a/unquote/unquote_fallback.go +++ b/unquote/unquote_fallback.go @@ -1,5 +1,5 @@ -//go:build (!amd64 && !arm64) || go1.27 || !go1.17 || (arm64 && !go1.20) -// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20 +//go:build (!amd64 && !arm64) || go1.28 || !go1.17 || (arm64 && !go1.20) +// +build !amd64,!arm64 go1.28 !go1.17 arm64,!go1.20 /* * Copyright 2021 ByteDance Inc. diff --git a/utf8/utf8.go b/utf8/utf8.go index 3ed307168..b48c5cb6e 100644 --- a/utf8/utf8.go +++ b/utf8/utf8.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2022 ByteDance Inc. diff --git a/utf8/utf8_fallback.go b/utf8/utf8_fallback.go index b3d0ddee0..25c337e87 100644 --- a/utf8/utf8_fallback.go +++ b/utf8/utf8_fallback.go @@ -1,5 +1,5 @@ -//go:build (!amd64 && !arm64) || go1.27 || !go1.17 || (arm64 && !go1.20) -// +build !amd64,!arm64 go1.27 !go1.17 arm64,!go1.20 +//go:build (!amd64 && !arm64) || go1.28 || !go1.17 || (arm64 && !go1.20) +// +build !amd64,!arm64 go1.28 !go1.17 arm64,!go1.20 /* * Copyright 2021 ByteDance Inc. diff --git a/utf8/utf8_native_test.go b/utf8/utf8_native_test.go index 1d7c7a326..4fd6bc673 100644 --- a/utf8/utf8_native_test.go +++ b/utf8/utf8_native_test.go @@ -1,5 +1,5 @@ -//go:build (amd64 && go1.17 && !go1.27) || (arm64 && go1.20 && !go1.27) -// +build amd64,go1.17,!go1.27 arm64,go1.20,!go1.27 +//go:build (amd64 && go1.17 && !go1.28) || (arm64 && go1.20 && !go1.28) +// +build amd64,go1.17,!go1.28 arm64,go1.20,!go1.28 /* * Copyright 2022 ByteDance Inc.