github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/tools/golang_patched_dce/ad1d54f.diff (about) 1 From ad1d54f498bd0f9de434773b6272d70f5d8ee980 Mon Sep 17 00:00:00 2001 2 From: Brad Fitzpatrick <bradfitz@golang.org> 3 Date: Fri, 06 Dec 2019 18:19:03 +0000 4 Subject: [PATCH] text/template: avoid a global map to help the linker's deadcode elimination 5 6 Fixes #36021 7 Updates #2559 8 Updates #26775 9 10 Change-Id: I2e6708691311035b63866f25d5b4b3977a118290 11 --- 12 13 diff --git a/src/text/template/deadcode_test.go b/src/text/template/deadcode_test.go 14 new file mode 100644 15 index 0000000..7495f51 16 --- /dev/null 17 +++ b/src/text/template/deadcode_test.go 18 @@ -0,0 +1,64 @@ 19 +// Copyright 2019 The Go Authors. All rights reserved. 20 +// Use of this source code is governed by a BSD-style 21 +// license that can be found in the LICENSE file. 22 + 23 +package template_test 24 + 25 +import ( 26 + "bytes" 27 + "internal/testenv" 28 + "io/ioutil" 29 + "os" 30 + "os/exec" 31 + "path/filepath" 32 + "testing" 33 +) 34 + 35 +// Issue 36021: verify that text/template doesn't prevent the linker from removing 36 +// unused methods. 37 +func TestDeadCodeElimination(t *testing.T) { 38 + if testing.Short() { 39 + t.Skip("skipping in short mode") 40 + } 41 + testenv.MustHaveGoBuild(t) 42 + const prog = `package main 43 + 44 +import ( 45 + _ "text/template" 46 +) 47 + 48 +type T struct{} 49 + 50 +func (t *T) Unused() { println("THIS SHOULD BE ELIMINATED") } 51 +func (t *T) Used() {} 52 + 53 +var sink *T 54 + 55 +func main() { 56 + var t T 57 + sink = &t 58 + t.Used() 59 +} 60 +` 61 + td, err := ioutil.TempDir("", "text_template_TestDeadCodeElimination") 62 + if err != nil { 63 + t.Fatal(err) 64 + } 65 + defer os.Remove(td) 66 + 67 + if err := ioutil.WriteFile(filepath.Join(td, "x.go"), []byte(prog), 0644); err != nil { 68 + t.Fatal(err) 69 + } 70 + cmd := exec.Command("go", "build", "-o", "x.exe", "x.go") 71 + cmd.Dir = td 72 + if out, err := cmd.CombinedOutput(); err != nil { 73 + t.Fatalf("go build: %v, %s", err, out) 74 + } 75 + slurp, err := ioutil.ReadFile(filepath.Join(td, "x.exe")) 76 + if err != nil { 77 + t.Fatal(err) 78 + } 79 + if bytes.Contains(slurp, []byte("THIS SHOULD BE ELIMINATED")) { 80 + t.Error("binary contains code that should be deadcode eliminated") 81 + } 82 +} 83 diff --git a/src/text/template/funcs.go b/src/text/template/funcs.go 84 index 0568c79..55470d6 100644 85 --- a/src/text/template/funcs.go 86 +++ b/src/text/template/funcs.go 87 @@ -12,6 +12,7 @@ 88 "net/url" 89 "reflect" 90 "strings" 91 + "sync" 92 "unicode" 93 "unicode/utf8" 94 ) 95 @@ -29,31 +30,49 @@ 96 // type can return interface{} or reflect.Value. 97 type FuncMap map[string]interface{} 98 99 -var builtins = FuncMap{ 100 - "and": and, 101 - "call": call, 102 - "html": HTMLEscaper, 103 - "index": index, 104 - "slice": slice, 105 - "js": JSEscaper, 106 - "len": length, 107 - "not": not, 108 - "or": or, 109 - "print": fmt.Sprint, 110 - "printf": fmt.Sprintf, 111 - "println": fmt.Sprintln, 112 - "urlquery": URLQueryEscaper, 113 +// builtins returns the FuncMap. 114 +// It is not a global variable so the linker can dead code eliminate 115 +// more when this isn't called. See golang.org/issue/36021. 116 +// TODO: revert this back to a global map once golang.org/issue/2559 is fixed. 117 +func builtins() FuncMap { 118 + return FuncMap{ 119 + "and": and, 120 + "call": call, 121 + "html": HTMLEscaper, 122 + "index": index, 123 + "slice": slice, 124 + "js": JSEscaper, 125 + "len": length, 126 + "not": not, 127 + "or": or, 128 + "print": fmt.Sprint, 129 + "printf": fmt.Sprintf, 130 + "println": fmt.Sprintln, 131 + "urlquery": URLQueryEscaper, 132 133 - // Comparisons 134 - "eq": eq, // == 135 - "ge": ge, // >= 136 - "gt": gt, // > 137 - "le": le, // <= 138 - "lt": lt, // < 139 - "ne": ne, // != 140 + // Comparisons 141 + "eq": eq, // == 142 + "ge": ge, // >= 143 + "gt": gt, // > 144 + "le": le, // <= 145 + "lt": lt, // < 146 + "ne": ne, // != 147 + } 148 } 149 150 -var builtinFuncs = createValueFuncs(builtins) 151 +var builtinFuncsOnce struct { 152 + sync.Once 153 + v map[string]reflect.Value 154 +} 155 + 156 +// builtinFuncsOnce lazily computes & caches the builtinFuncs map. 157 +// TODO: revert this back to a global map once golang.org/issue/2559 is fixed. 158 +func builtinFuncs() map[string]reflect.Value { 159 + builtinFuncsOnce.Do(func() { 160 + builtinFuncsOnce.v = createValueFuncs(builtins()) 161 + }) 162 + return builtinFuncsOnce.v 163 +} 164 165 // createValueFuncs turns a FuncMap into a map[string]reflect.Value 166 func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { 167 @@ -125,7 +144,7 @@ 168 return fn, true 169 } 170 } 171 - if fn := builtinFuncs[name]; fn.IsValid() { 172 + if fn := builtinFuncs()[name]; fn.IsValid() { 173 return fn, true 174 } 175 return reflect.Value{}, false 176 diff --git a/src/text/template/multi_test.go b/src/text/template/multi_test.go 177 index 5769470..bf1f1b2 100644 178 --- a/src/text/template/multi_test.go 179 +++ b/src/text/template/multi_test.go 180 @@ -242,7 +242,7 @@ 181 t.Fatal(err) 182 } 183 // Add a new parse tree. 184 - tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins) 185 + tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins()) 186 if err != nil { 187 t.Fatal(err) 188 } 189 diff --git a/src/text/template/template.go b/src/text/template/template.go 190 index 2c5ff01..e636907 100644 191 --- a/src/text/template/template.go 192 +++ b/src/text/template/template.go 193 @@ -198,7 +198,7 @@ 194 func (t *Template) Parse(text string) (*Template, error) { 195 t.init() 196 t.muFuncs.RLock() 197 - trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins) 198 + trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins()) 199 t.muFuncs.RUnlock() 200 if err != nil { 201 return nil, err