fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: resolve infinite recursion in Repository.UnmarshalJSON (#1001)

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: https://github.com/golang/go/issues/75361

author
ZheNing Hu
committer
GitHub
date (Dec 2, 2025, 4:14 PM +0200) commit 886b229d parent 8ec72f9a
+105 -2
+2 -2
api.go
··· 650 650 func (r *Repository) UnmarshalJSON(data []byte) error { 651 651 // We define a new type so that we can use json.Unmarshal 652 652 // without recursing into this same method. 653 - type repository *Repository 654 - repo := repository(r) 653 + type repository Repository 654 + repo := (*repository)(r) 655 655 656 656 err := json.Unmarshal(data, repo) 657 657 if err != nil {
+103
api_test.go
··· 17 17 import ( 18 18 "bytes" 19 19 "encoding/gob" 20 + "encoding/json" 20 21 "reflect" 21 22 "strings" 22 23 "testing" ··· 388 389 }) 389 390 } 390 391 } 392 + 393 + // TestRepositoryUnmarshalJSONStackOverflowFix tests that the UnmarshalJSON method 394 + // for Repository doesn't cause a stack overflow due to infinite recursion. 395 + // This test creates a scenario that would trigger the bug where the type alias 396 + // was incorrectly defined as a pointer type, causing recursive calls during 397 + // JSON unmarshaling of nested SubRepoMap structures. 398 + func TestRepositoryUnmarshalJSONStackOverflowFix(t *testing.T) { 399 + // Create a JSON with nested SubRepoMap containing Repository objects 400 + // This specifically tests the recursive scenario that causes stack overflow 401 + jsonData := `{ 402 + "id": 12345, 403 + "name": "test-repo", 404 + "url": "https://example.com/test-repo", 405 + "branches": [ 406 + { 407 + "name": "main", 408 + "version": "abc123" 409 + } 410 + ], 411 + "rawconfig": { 412 + "repoid": "12345" 413 + }, 414 + "subrepomap": { 415 + "submodule1": { 416 + "id": 11111, 417 + "name": "submodule1", 418 + "url": "https://example.com/submodule1", 419 + "rawconfig": { 420 + "repoid": "11111" 421 + }, 422 + "subrepomap": { 423 + "nested-submodule": { 424 + "id": 33333, 425 + "name": "nested-submodule", 426 + "rawconfig": { 427 + "repoid": "33333" 428 + } 429 + } 430 + } 431 + }, 432 + "submodule2": { 433 + "id": 22222, 434 + "name": "submodule2", 435 + "rawconfig": { 436 + "repoid": "22222" 437 + } 438 + } 439 + } 440 + }` 441 + 442 + // Attempt to unmarshal the JSON data into a Repository struct 443 + // This would previously cause a stack overflow due to incorrect type alias 444 + var repo Repository 445 + err := json.Unmarshal([]byte(jsonData), &repo) 446 + 447 + // Verify that unmarshaling succeeds without stack overflow 448 + if err != nil { 449 + t.Fatalf("Failed to unmarshal Repository JSON: %v", err) 450 + } 451 + 452 + // Basic verification 453 + if repo.ID != 12345 { 454 + t.Errorf("Expected ID 12345, got %d", repo.ID) 455 + } 456 + 457 + if repo.Name != "test-repo" { 458 + t.Errorf("Expected Name 'test-repo', got '%s'", repo.Name) 459 + } 460 + 461 + // Verify nested SubRepoMap handling doesn't cause stack overflow 462 + if repo.SubRepoMap == nil { 463 + t.Fatalf("Expected SubRepoMap to be non-nil") 464 + } 465 + 466 + if len(repo.SubRepoMap) != 2 { 467 + t.Fatalf("Expected 2 subrepos in SubRepoMap, got %d", len(repo.SubRepoMap)) 468 + } 469 + 470 + // Verify nested repository unmarshaling worked correctly 471 + submodule1, exists := repo.SubRepoMap["submodule1"] 472 + if !exists { 473 + t.Fatalf("Expected submodule1 to exist in SubRepoMap") 474 + } 475 + 476 + if submodule1.ID != 11111 { 477 + t.Errorf("Expected submodule1 ID 11111, got %d", submodule1.ID) 478 + } 479 + 480 + // Verify deeply nested SubRepoMap works 481 + if submodule1.SubRepoMap == nil { 482 + t.Fatalf("Expected submodule1 SubRepoMap to be non-nil") 483 + } 484 + 485 + nestedSubmodule, exists := submodule1.SubRepoMap["nested-submodule"] 486 + if !exists { 487 + t.Fatalf("Expected nested-submodule to exist") 488 + } 489 + 490 + if nestedSubmodule.ID != 33333 { 491 + t.Errorf("Expected nested-submodule ID 33333, got %d", nestedSubmodule.ID) 492 + } 493 + }