From 2352690bde826aa46eeee01af61d7990dab3a5ea Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 4 Sep 2020 11:58:42 -0700 Subject: [PATCH] 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 --- cmd/cloner/cloner.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cmd/cloner/cloner.go b/cmd/cloner/cloner.go index c7463b0f8..d0b20c43e 100644 --- a/cmd/cloner/cloner.go +++ b/cmd/cloner/cloner.go @@ -143,7 +143,19 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, name string, typ *types switch t := typ.Underlying().(type) { 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() fmt.Fprintf(buf, "// Clone makes a deep copy of %s.\n", name) 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++ { fname := t.Field(i).Name() ft := t.Field(i).Type() + + writeRegen("\t%s %s", fname, importedName(ft)) + if !containsPointers(ft) { continue } @@ -220,6 +235,10 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, name string, typ *types } writef("return dst") fmt.Fprintf(buf, "}\n\n") + + writeRegen("}{})\n") + + buf.Write(regenBuf.Bytes()) } }