Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions lib/tag/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func hasNonNilTag(tags []any) bool {
return false
}

func expand(depth int, tagValue any, result []any, active []any) ([]any, error) {
func expand(depth int, tagValue any, result []any, active []any, inGetter *bool) ([]any, error) {
if depth > maxTagDepth || len(result) > maxTagCount {
return result, ErrTooManyTags
}
Expand All @@ -160,7 +160,10 @@ func expand(depth int, tagValue any, result []any, active []any) ([]any, error)
if idx := findActiveIndex(active, data); idx >= 0 {
return addActiveTags(result, active[idx:])
}
return expand(depth+1, data.JawsGetTag(), result, append(active, data))
*inGetter = true
value := data.JawsGetTag()
*inGetter = false
return expand(depth+1, value, result, append(active, data), inGetter)
case []any:
if !hasNonNilTag(data) {
return result, nil
Expand All @@ -174,7 +177,7 @@ func expand(depth int, tagValue any, result []any, active []any) ([]any, error)
active = append(active, data)
var err error
for _, v := range data {
if result, err = expand(depth+1, v, result, active); err != nil {
if result, err = expand(depth+1, v, result, active, inGetter); err != nil {
return result, err
}
}
Expand Down Expand Up @@ -234,14 +237,18 @@ func TagExpand(tagValue any) (result []any, err error) {
// stays as a defense-in-depth net: should a non-comparable value ever reach
// that comparison, recoverComparabilityPanic turns the specific "comparing
// uncomparable type" runtime panic into [ErrNotUsableAsTag] and re-raises
// anything else.
// anything else. A panic raised inside JawsGetTag must pass through unchanged.
var inGetter bool
defer func() {
if r := recover(); r != nil {
if inGetter {
panic(r)
}
result, err = recoverComparabilityPanic(r, tagValue)
}
}()
var activeArr [12]any
return expand(0, tagValue, nil, activeArr[:0])
return expand(0, tagValue, nil, activeArr[:0], &inGetter)
}

// recoverComparabilityPanic maps a panic recovered from tag expansion to a
Expand Down
23 changes: 20 additions & 3 deletions lib/tag/tag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,24 @@ type testPanicTagGetter struct{}

func (testPanicTagGetter) JawsGetTag() any { panic("boom") }

type testComparabilityPanicTagGetter struct{}

func (testComparabilityPanicTagGetter) JawsGetTag() any {
var a, b any = []int{1}, []int{1}
_ = a == b
return nil
}

func TestTagExpand_RepanicsComparabilityPanicFromGetter(t *testing.T) {
defer func() {
r, ok := recover().(runtime.Error)
if !ok || !strings.Contains(r.Error(), "comparing uncomparable type") {
t.Fatalf("expected getter's comparability panic, got %v", r)
}
}()
_, _ = TagExpand(testComparabilityPanicTagGetter{})
}

// uncomparablePanic returns a real "comparing uncomparable type" runtime panic
// value by comparing two non-comparable interface values.
func uncomparablePanic() (r any) {
Expand All @@ -530,9 +548,8 @@ func uncomparablePanic() (r any) {
return
}

// Test_recoverComparabilityPanic exercises the recovery helper directly, since the
// comparability panic it handles only occurs in production builds (debug builds
// reject such tags in ensureUsableTag before the dedup compares them).
// Test_recoverComparabilityPanic exercises the defense-in-depth recovery helper
// directly; ensureUsableTag normally rejects unusable tags before deduplication.
func Test_recoverComparabilityPanic(t *testing.T) {
rerr := uncomparablePanic()
if _, ok := rerr.(runtime.Error); !ok {
Expand Down
Loading