v0.297.0: burn-down round 2 — 24 small rows (R-591 R-568 R-567 R-363 R-547 R-10 R-552 R-251 R-104 R-619 R-362 R-675 R-256 R-257 R-240 R-365 R-425 R-565 R-564 R-603 R-454 R-208 R-457-swept) + the banner countdown and deepCopyStack twins; MinAgent 0.131.0
gates / gates (push) Failing after 50s
gates / gates (push) Failing after 50s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-591 follow-up: deepCopyStack's contract is "a snapshot the caller may mutate", and the I18n hole
|
||||
// was not the only one — DataPaths, AfterLoad (and every other reference added to Metadata after the
|
||||
// copy was written) stayed shared. Instead of one assertion per field, this fills EVERY pointer, slice
|
||||
// and map reachable from Stack.Meta with a non-empty value and then walks the copy beside the original:
|
||||
// any reference the two share is a field a caller's write would leak through. A field added to
|
||||
// Metadata tomorrow is covered the day it is added.
|
||||
func TestDeepCopyStackMetaSharesNoReference(t *testing.T) {
|
||||
orig := &Stack{Name: "demo"}
|
||||
fillAll(reflect.ValueOf(&orig.Meta).Elem(), 0)
|
||||
cp := deepCopyStack(orig)
|
||||
var shared []string
|
||||
findAliases(reflect.ValueOf(orig.Meta), reflect.ValueOf(cp.Meta), "Meta", &shared)
|
||||
for _, p := range shared {
|
||||
t.Errorf("R-591: deepCopyStack leaves %s shared with the original — a write through the copy changes the stack", p)
|
||||
}
|
||||
}
|
||||
|
||||
// fillAll gives every pointer, slice (one element) and map (one entry) under v a non-nil value.
|
||||
func fillAll(v reflect.Value, depth int) {
|
||||
if depth > 6 || !v.CanSet() && v.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
fillAll(v.Elem(), depth+1)
|
||||
case reflect.Slice:
|
||||
if v.Len() == 0 {
|
||||
v.Set(reflect.MakeSlice(v.Type(), 1, 1))
|
||||
}
|
||||
fillAll(v.Index(0), depth+1)
|
||||
case reflect.Map:
|
||||
if v.Len() == 0 {
|
||||
m := reflect.MakeMap(v.Type())
|
||||
k := reflect.New(v.Type().Key()).Elem()
|
||||
if k.Kind() == reflect.String {
|
||||
k.SetString("k")
|
||||
}
|
||||
e := reflect.New(v.Type().Elem()).Elem()
|
||||
fillAll(e, depth+1)
|
||||
m.SetMapIndex(k, e)
|
||||
v.Set(m)
|
||||
}
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if v.Type().Field(i).IsExported() {
|
||||
fillAll(v.Field(i), depth+1)
|
||||
}
|
||||
}
|
||||
case reflect.String:
|
||||
if v.String() == "" {
|
||||
v.SetString("x")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findAliases records every pointer/slice/map under a and b that points at the same memory.
|
||||
func findAliases(a, b reflect.Value, path string, out *[]string) {
|
||||
switch a.Kind() {
|
||||
case reflect.Ptr:
|
||||
if a.IsNil() || b.IsNil() {
|
||||
return
|
||||
}
|
||||
if a.Pointer() == b.Pointer() {
|
||||
*out = append(*out, path)
|
||||
return
|
||||
}
|
||||
findAliases(a.Elem(), b.Elem(), path, out)
|
||||
case reflect.Slice:
|
||||
if a.Len() == 0 || b.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if a.Pointer() == b.Pointer() {
|
||||
*out = append(*out, path)
|
||||
return
|
||||
}
|
||||
for i := 0; i < a.Len() && i < b.Len(); i++ {
|
||||
findAliases(a.Index(i), b.Index(i), path+"[]", out)
|
||||
}
|
||||
case reflect.Map:
|
||||
if a.Len() == 0 || b.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if a.Pointer() == b.Pointer() {
|
||||
*out = append(*out, path)
|
||||
return
|
||||
}
|
||||
for _, k := range a.MapKeys() {
|
||||
if bv := b.MapIndex(k); bv.IsValid() {
|
||||
findAliases(a.MapIndex(k), bv, path+"[k]", out)
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
for i := 0; i < a.NumField(); i++ {
|
||||
if a.Type().Field(i).IsExported() {
|
||||
findAliases(a.Field(i), b.Field(i), path+"."+a.Type().Field(i).Name, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user