]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template/html: avoid redundant escaping directives.
authorMike Samuel <mikesamuel@gmail.com>
Fri, 30 Sep 2011 04:31:41 +0000 (21:31 -0700)
committerMike Samuel <mikesamuel@gmail.com>
Fri, 30 Sep 2011 04:31:41 +0000 (21:31 -0700)
This is a possible optimization.  I'm not sure the complexity is worth it.
The new benchmark in escape_test is 46us without and 35us with the optimization.

R=nigeltao
CC=golang-dev
https://golang.org/cl/5168041

src/pkg/exp/template/html/escape.go
src/pkg/exp/template/html/escape_test.go

index 13a035f348b256d7685bb00e05368871b0231f88..74abccecddf332fa8db7e49c90bc360d18d4f846 100644 (file)
@@ -262,19 +262,54 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) {
                i := indexOfStr((id.Args[0].(*parse.IdentifierNode)).Ident, s, escFnsEq)
                if i != -1 {
                        for _, name := range s[:i] {
-                               newCmds = append(newCmds, newIdentCmd(name))
+                               newCmds = appendCmd(newCmds, newIdentCmd(name))
                        }
                        s = s[i+1:]
                }
-               newCmds = append(newCmds, id)
+               newCmds = appendCmd(newCmds, id)
        }
        // Create any remaining sanitizers.
        for _, name := range s {
-               newCmds = append(newCmds, newIdentCmd(name))
+               newCmds = appendCmd(newCmds, newIdentCmd(name))
        }
        p.Cmds = newCmds
 }
 
+// redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
+// for all x.
+var redundantFuncs = map[string]map[string]bool{
+       "exp_template_html_commentescaper": {
+               "exp_template_html_attrescaper":    true,
+               "exp_template_html_nospaceescaper": true,
+               "exp_template_html_htmlescaper":    true,
+       },
+       "exp_template_html_cssescaper": {
+               "exp_template_html_attrescaper": true,
+       },
+       "exp_template_html_jsregexpescaper": {
+               "exp_template_html_attrescaper": true,
+       },
+       "exp_template_html_jsstrescaper": {
+               "exp_template_html_attrescaper": true,
+       },
+       "exp_template_html_urlescaper": {
+               "exp_template_html_urlnormalizer": true,
+       },
+}
+
+// appendCmd appends the given command to the end of the command pipeline
+// unless it is redundant with the last command.
+func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode {
+       if n := len(cmds); n != 0 {
+               last, ok := cmds[n-1].Args[0].(*parse.IdentifierNode)
+               next, _ := cmd.Args[0].(*parse.IdentifierNode)
+               if ok && redundantFuncs[last.Ident][next.Ident] {
+                       return cmds
+               }
+       }
+       return append(cmds, cmd)
+}
+
 // indexOfStr is the first i such that eq(s, strs[i]) or -1 if s was not found.
 func indexOfStr(s string, strs []string, eq func(a, b string) bool) int {
        for i, t := range strs {
index a4ec25f363aa8833867895e2a42f1798405f21c9..9466cc1d5d61fe1bb63e6560bb40545761255c6e 100644 (file)
@@ -1577,3 +1577,47 @@ func TestEscapeSetErrorsNotIgnorable(t *testing.T) {
        defer expectExecuteFailure(t, &b)
        s.Execute(&b, "t", nil)
 }
+
+func TestRedundantFuncs(t *testing.T) {
+       inputs := []interface{}{
+               "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
+                       "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+                       ` !"#$%&'()*+,-./` +
+                       `0123456789:;<=>?` +
+                       `@ABCDEFGHIJKLMNO` +
+                       `PQRSTUVWXYZ[\]^_` +
+                       "`abcdefghijklmno" +
+                       "pqrstuvwxyz{|}~\x7f" +
+                       "\u00A0\u0100\u2028\u2029\ufeff\ufdec\ufffd\uffff\U0001D11E" +
+                       "&amp;%22\\",
+               CSS(`a[href =~ "//example.com"]#foo`),
+               HTML(`Hello, <b>World</b> &amp;tc!`),
+               HTMLAttr(` dir="ltr"`),
+               JS(`c && alert("Hello, World!");`),
+               JSStr(`Hello, World & O'Reilly\x21`),
+               URL(`greeting=H%69&addressee=(World)`),
+       }
+
+       for n0, m := range redundantFuncs {
+               f0 := funcMap[n0].(func(...interface{}) string)
+               for n1, _ := range m {
+                       f1 := funcMap[n1].(func(...interface{}) string)
+                       for _, input := range inputs {
+                               want := f0(input)
+                               if got := f1(want); want != got {
+                                       t.Errorf("%s %s with %T %q: want\n\t%q,\ngot\n\t%q", n0, n1, input, input, want, got)
+                               }
+                       }
+               }
+       }
+}
+
+func BenchmarkEscapedExecute(b *testing.B) {
+       tmpl := template.Must(Escape(template.Must(template.New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`))))
+       var buf bytes.Buffer
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               tmpl.Execute(&buf, "foo & 'bar' & baz")
+               buf.Reset()
+       }
+}