github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/work/shell_test.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build unix 6 7 package work 8 9 import ( 10 "bytes" 11 "strings" 12 "testing" 13 "unicode" 14 15 "github.com/go-asm/go/testenv" 16 ) 17 18 func FuzzSplitPkgConfigOutput(f *testing.F) { 19 testenv.MustHaveExecPath(f, "/bin/sh") 20 21 f.Add([]byte(`$FOO`)) 22 f.Add([]byte(`\$FOO`)) 23 f.Add([]byte(`${FOO}`)) 24 f.Add([]byte(`\${FOO}`)) 25 f.Add([]byte(`$(/bin/false)`)) 26 f.Add([]byte(`\$(/bin/false)`)) 27 f.Add([]byte(`$((0))`)) 28 f.Add([]byte(`\$((0))`)) 29 f.Add([]byte(`unescaped space`)) 30 f.Add([]byte(`escaped\ space`)) 31 f.Add([]byte(`"unterminated quote`)) 32 f.Add([]byte(`'unterminated quote`)) 33 f.Add([]byte(`unterminated escape\`)) 34 f.Add([]byte(`"quote with unterminated escape\`)) 35 f.Add([]byte(`'quoted "double quotes"'`)) 36 f.Add([]byte(`"quoted 'single quotes'"`)) 37 f.Add([]byte(`"\$0"`)) 38 f.Add([]byte(`"\$\0"`)) 39 f.Add([]byte(`"\$"`)) 40 f.Add([]byte(`"\$ "`)) 41 42 // Example positive inputs from TestSplitPkgConfigOutput. 43 // Some bare newlines have been removed so that the inputs 44 // are valid in the shell script we use for comparison. 45 f.Add([]byte(`-r:foo -L/usr/white\ space/lib -lfoo\ bar -lbar\ baz`)) 46 f.Add([]byte(`-lextra\ fun\ arg\\`)) 47 f.Add([]byte("\textra whitespace\r")) 48 f.Add([]byte(" \r ")) 49 f.Add([]byte(`"-r:foo" "-L/usr/white space/lib" "-lfoo bar" "-lbar baz"`)) 50 f.Add([]byte(`"-lextra fun arg\\"`)) 51 f.Add([]byte(`" \r\n\ "`)) 52 f.Add([]byte(`""`)) 53 f.Add([]byte(``)) 54 f.Add([]byte(`"\\"`)) 55 f.Add([]byte(`"\x"`)) 56 f.Add([]byte(`"\\x"`)) 57 f.Add([]byte(`'\\'`)) 58 f.Add([]byte(`'\x'`)) 59 f.Add([]byte(`"\\x"`)) 60 f.Add([]byte("\\\n")) 61 f.Add([]byte(`-fPIC -I/test/include/foo -DQUOTED='"/test/share/doc"'`)) 62 f.Add([]byte(`-fPIC -I/test/include/foo -DQUOTED="/test/share/doc"`)) 63 f.Add([]byte(`-fPIC -I/test/include/foo -DQUOTED=\"/test/share/doc\"`)) 64 f.Add([]byte(`-fPIC -I/test/include/foo -DQUOTED='/test/share/doc'`)) 65 f.Add([]byte(`-DQUOTED='/te\st/share/d\oc'`)) 66 f.Add([]byte(`-Dhello=10 -Dworld=+32 -DDEFINED_FROM_PKG_CONFIG=hello\ world`)) 67 f.Add([]byte(`"broken\"" \\\a "a"`)) 68 69 // Example negative inputs from TestSplitPkgConfigOutput. 70 f.Add([]byte(`" \r\n `)) 71 f.Add([]byte(`"-r:foo" "-L/usr/white space/lib "-lfoo bar" "-lbar baz"`)) 72 f.Add([]byte(`"-lextra fun arg\\`)) 73 f.Add([]byte(`broken flag\`)) 74 f.Add([]byte(`extra broken flag \`)) 75 f.Add([]byte(`\`)) 76 f.Add([]byte(`"broken\"" "extra" \`)) 77 78 f.Fuzz(func(t *testing.T, b []byte) { 79 t.Parallel() 80 81 if bytes.ContainsAny(b, "*?[#~%\x00{}!") { 82 t.Skipf("skipping %#q: contains a sometimes-quoted character", b) 83 } 84 // splitPkgConfigOutput itself rejects inputs that contain unquoted 85 // shell operator characters. (Quoted shell characters are fine.) 86 87 for _, c := range b { 88 if c > unicode.MaxASCII { 89 t.Skipf("skipping %#q: contains a non-ASCII character %q", b, c) 90 } 91 if !unicode.IsGraphic(rune(c)) && !unicode.IsSpace(rune(c)) { 92 t.Skipf("skipping %#q: contains non-graphic character %q", b, c) 93 } 94 } 95 96 args, err := splitPkgConfigOutput(b) 97 if err != nil { 98 // We haven't checked that the shell would actually reject this input too, 99 // but if splitPkgConfigOutput rejected it it's probably too dangerous to 100 // run in the script. 101 t.Logf("%#q: %v", b, err) 102 return 103 } 104 t.Logf("splitPkgConfigOutput(%#q) = %#q", b, args) 105 if len(args) == 0 { 106 t.Skipf("skipping %#q: contains no arguments", b) 107 } 108 109 var buf strings.Builder 110 for _, arg := range args { 111 buf.WriteString(arg) 112 buf.WriteString("\n") 113 } 114 wantOut := buf.String() 115 116 if strings.Count(wantOut, "\n") != len(args)+bytes.Count(b, []byte("\n")) { 117 // One of the newlines in b was treated as a delimiter and not part of an 118 // argument. Our bash test script would interpret that as a syntax error. 119 t.Skipf("skipping %#q: contains a bare newline", b) 120 } 121 122 // We use the printf shell command to echo the arguments because, per 123 // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16: 124 // “It is not possible to use echo portably across all POSIX systems unless 125 // both -n (as the first argument) and escape sequences are omitted.” 126 cmd := testenv.Command(t, "/bin/sh", "-c", "printf '%s\n' "+string(b)) 127 cmd.Env = append(cmd.Environ(), "LC_ALL=POSIX", "POSIXLY_CORRECT=1") 128 cmd.Stderr = new(strings.Builder) 129 out, err := cmd.Output() 130 if err != nil { 131 t.Fatalf("%#q: %v\n%s", cmd.Args, err, cmd.Stderr) 132 } 133 134 if string(out) != wantOut { 135 t.Logf("%#q:\n%#q", cmd.Args, out) 136 t.Logf("want:\n%#q", wantOut) 137 t.Errorf("parsed args do not match") 138 } 139 }) 140 }