cmd/cloner: ensure cloner gets re-run when structs change

If you change a struct and don't re-run cloner,
your Cloner method might be inaccurate, leading to bad things.

To prevent this, write out the struct as it is at the moment that
cloner is caller, and attempt a conversion from that type.
If the struct gets changed in any way, this conversion will fail.

This will yield false positives: If you change a non-pointer field,
you will be forced to re-run cloner, even though the actual generated
code won't change. I think this is an acceptable cost: It is a minor
annoyance, which will prevent real bugs.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
pull/738/head
Josh Bleecher Snyder 4 years ago committed by Josh Bleecher Snyder
parent 8ecee476f6
commit 2352690bde

@ -143,7 +143,19 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, name string, typ *types
switch t := typ.Underlying().(type) { switch t := typ.Underlying().(type) {
case *types.Struct: case *types.Struct:
_ = t // We generate two bits of code simultaneously while we walk the struct.
// One is the Clone method itself, which we write directly to buf.
// The other is a variable assignment that will fail if the struct
// changes without the Clone method getting regenerated.
// We write that to regenBuf, and then append it to buf at the end.
regenBuf := new(bytes.Buffer)
writeRegen := func(format string, args ...interface{}) {
fmt.Fprintf(regenBuf, format+"\n", args...)
}
writeRegen("// A compilation failure here means this code must be regenerated, with command:")
writeRegen("// tailscale.com/cmd/cloner -type %s", *flagTypes)
writeRegen("var _%sNeedsRegeneration = %s(struct {", name, name)
name := typ.Obj().Name() name := typ.Obj().Name()
fmt.Fprintf(buf, "// Clone makes a deep copy of %s.\n", name) fmt.Fprintf(buf, "// Clone makes a deep copy of %s.\n", name)
fmt.Fprintf(buf, "// The result aliases no memory with the original.\n") fmt.Fprintf(buf, "// The result aliases no memory with the original.\n")
@ -159,6 +171,9 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, name string, typ *types
for i := 0; i < t.NumFields(); i++ { for i := 0; i < t.NumFields(); i++ {
fname := t.Field(i).Name() fname := t.Field(i).Name()
ft := t.Field(i).Type() ft := t.Field(i).Type()
writeRegen("\t%s %s", fname, importedName(ft))
if !containsPointers(ft) { if !containsPointers(ft) {
continue continue
} }
@ -220,6 +235,10 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, name string, typ *types
} }
writef("return dst") writef("return dst")
fmt.Fprintf(buf, "}\n\n") fmt.Fprintf(buf, "}\n\n")
writeRegen("}{})\n")
buf.Write(regenBuf.Bytes())
} }
} }

Loading…
Cancel
Save