fix: resolve infinite recursion in Repository.UnmarshalJSON#1001
Merged
keegancsmith merged 1 commit intosourcegraph:mainfrom Dec 2, 2025
Merged
Conversation
This fixes a bug caused by jsonv2 json.Unmarshal() behavior that is not backward compatible. The infinite recursion occurs when UnmarshalJSON calls json.Unmarshal on itself. See: golang/go#75361
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I encountered an issue during the process of integrating zoekt with gitea go-gitea/gitea#33850:
runtime: goroutine stack exceeds 1000000000-byte limit runtime: sp=0x14025a6e350 stack=[0x14025a6e000, 0x14045a6e000] fatal error: stack overflow runtime stack: runtime.throw({0x1055f3893?, 0x103019b20?}) /Users/adl/sdk/go1.25.4/src/runtime/panic.go:1094 +0x34 fp=0x16cf2ada0 sp=0x16cf2ad70 pc=0x103062f14 runtime.newstack() /Users/adl/sdk/go1.25.4/src/runtime/stack.go:1159 +0x44c fp=0x16cf2aed0 sp=0x16cf2ada0 pc=0x103047b0c runtime.morestack() /Users/adl/sdk/go1.25.4/src/runtime/asm_arm64.s:392 +0x70 fp=0x16cf2aed0 sp=0x16cf2aed0 pc=0x103069350 goroutine 659 gp=0x14002e79dc0 m=2 mp=0x1400009a808 [running]: encoding/json/jsontext.(*decoderState).consumeValue(0x140152fb2c0, 0x14025a6e710, 0x60?, 0x4) /Users/adl/sdk/go1.25.4/src/encoding/json/jsontext/decode.go:835 +0x544 fp=0x14025a6e350 sp=0x14025a6e350 pc=0x1031e7f04 encoding/json/jsontext.(*decoderState).consumeObject(0x140152fb2c0, 0x14025a6e710, 0x14025a6e4c8?, 0x3) /Users/adl/sdk/go1.25.4/src/encoding/json/jsontext/decode.go:1026 +0x65c fp=0x14025a6e440 sp=0x14025a6e350 pc=0x1031e89cc encoding/json/jsontext.(*decoderState).consumeValue(0x140152fb2c0, 0x14025a6e710, 0x14025a6e5b8?, 0x3) /Users/adl/sdk/go1.25.4/src/encoding/json/jsontext/decode.go:863 +0x3c4 fp=0x14025a6e4b0 sp=0x14025a6e440 pc=0x1031e7d84 encoding/json/jsontext.(*decoderState).consumeArray(0x140152fb2c0, 0x14025a6e710, 0x0?, 0x0?) /Users/adl/sdk/go1.25.4/src/encoding/json/jsontext/decode.go:1084 +0x1b8 fp=0x14025a6e500 sp=0x14025a6e4b0 pc=0x1031e9198 encoding/json/jsontext.(*decoderState).consumeValue(0x140152fb2c0, 0x14025a6e710, 0x14025a6e678?, 0x2) /Users/adl/sdk/go1.25.4/src/encoding/json/jsontext/decode.go:865 +0x32c fp=0x14025a6e570 sp=0x14025a6e500 pc=0x1031e7cec encoding/json/jsontext.(*decoderState).consumeObject(0x140152fb2c0, 0x14025a6e710, 0x14025a6e798?, 0x1) /Users/adl/sdk/go1.25.4/src/encoding/json/jsontext/decode.go:1026 +0x65c fp=0x14025a6e660 sp=0x14025a6e570 pc=0x1031e89cc encoding/json/jsontext.(*decoderState).consumeValue(0x140152fb2c0, 0x14025a6e710, 0x0?, 0x1) /Users/adl/sdk/go1.25.4/src/encoding/json/jsontext/decode.go:863 +0x3c4 fp=0x14025a6e6d0 sp=0x14025a6e660 pc=0x1031e7d84 encoding/json/jsontext.(*decoderState).CheckNextValue(0x140152fb2c0) /Users/adl/sdk/go1.25.4/src/encoding/json/jsontext/decode.go:781 +0x64 fp=0x14025a6e720 sp=0x14025a6e6d0 pc=0x1031e7724 encoding/json/v2.unmarshalDecode(0x140152fb2c0, {0x106383fe0?, 0x1400029d4a0?}, 0x140152fb380) /Users/adl/sdk/go1.25.4/src/encoding/json/v2/arshal.go:484 +0x114 fp=0x14025a6e7b0 sp=0x14025a6e720 pc=0x103200794 encoding/json/v2.unmarshalFull(0x140152fb2c0, {0x106383fe0?, 0x1400029d4a0?}, 0x14025a6e8c8?) /Users/adl/sdk/go1.25.4/src/encoding/json/v2/arshal.go:437 +0x28 fp=0x14025a6e800 sp=0x14025a6e7b0 pc=0x103200588 encoding/json/v2.Unmarshal({0x12f9d5750?, 0x16d?, 0x14025a6e8d8?}, {0x106383fe0, 0x1400029d4a0}, {0x14025a6e8c8?, 0x197?, 0x1031e7cec?}) /Users/adl/sdk/go1.25.4/src/encoding/json/v2/arshal.go:412 +0x80 fp=0x14025a6e880 sp=0x14025a6e800 pc=0x103200290 encoding/json.Unmarshal(...) /Users/adl/sdk/go1.25.4/src/encoding/json/v2_decode.go:98 github.com/sourcegraph/zoekt.(*Repository).UnmarshalJSON(0x1400029d4a0, {0x12f9d5750?, 0x140152fb180?, 0x14025a6e9a0?}) /Users/adl/go/pkg/mod/github.com/sourcegraph/zoekt@v0.0.0-20251024135756-eab7bf421ca3/api.go:656 +0x60 fp=0x14025a6e8e0 sp=0x14025a6e880 pc=0x1047da750 encoding/json/v2.makeMethodArshaler.func6(0x140152fb180, {{0x1069ed0a0?, 0x1400029d4a0?, 0x0?}, 0x50?}, 0x140152fb240) /Users/adl/sdk/go1.25.4/src/encoding/json/v2/arshal_methods.go:264 +0x148 fp=0x14025a6e9b0 sp=0x14025a6e8e0 pc=0x1032108a8 encoding/json/v2.unmarshalDecode(0x140152fb180, {0x106383fe0?, 0x1400029d4a0?}, 0x140152fb240) /Users/adl/sdk/go1.25.4/src/encoding/json/v2/arshal.go:494 +0x18c fp=0x14025a6ea40 sp=0x14025a6e9b0 pc=0x10320080c encoding/json/v2.unmarshalFull(0x140152fb180, {0x106383fe0?, 0x1400029d4a0?}, 0x14025a6eb58?) /Users/adl/sdk/go1.25.4/src/encoding/json/v2/arshal.go:437 +0x28 fp=0x14025a6ea90 sp=0x14025a6ea40 pc=0x103200588 encoding/json/v2.Unmarshal({0x12f9d5750?, 0x16d?, 0x14025a6eb68?}, {0x106383fe0, 0x1400029d4a0}, {0x14025a6eb58?, 0x197?, 0x1031e7cec?}) /Users/adl/sdk/go1.25.4/src/encoding/json/v2/arshal.go:412 +0x80 fp=0x14025a6eb10 sp=0x14025a6ea90 pc=0x103200290Then I discovered that it was because the
encoding/jsonjsonv2 enabled in gitea has a bug:golang/go#75361
In short, jsonv2 causes an infinite loop when declaring a new type in a struct's
UnmarshalJSON()method and then unmarshaling this new type, while jsonv1 avoids this issue by default, resulting in incompatibility between v1/v2.Therefore, currently the simplest fix is to define type repository Repository instead of type repository *Repository in the Repository's UnmarshalJSON method to avoid infinite loop stack overflow.
How to reproduce this bug, use original code and