From 4e610a0ee72eba476b5b4102df62385ef7b73e39 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 2 Jun 2021 14:39:43 +0200 Subject: [PATCH] main: escape commands while printing them with the -x flag This should make issues such as the one in https://github.com/tinygo-org/tinygo/issues/1910 more obvious. --- builder/build.go | 4 ++-- builder/cc.go | 6 +++--- compileopts/options.go | 2 +- main.go | 27 ++++++++++++++++++++++++--- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/builder/build.go b/builder/build.go index 9d26df81..bdb5a1e7 100644 --- a/builder/build.go +++ b/builder/build.go @@ -547,8 +547,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil } ldflags = append(ldflags, dependency.result) } - if config.Options.PrintCommands { - fmt.Printf("%s %s\n", config.Target.Linker, strings.Join(ldflags, " ")) + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(config.Target.Linker, ldflags...) } err = link(config.Target.Linker, ldflags...) if err != nil { diff --git a/builder/cc.go b/builder/cc.go index a450cc6e..7f0dad28 100644 --- a/builder/cc.go +++ b/builder/cc.go @@ -56,7 +56,7 @@ import ( // depfile but without invalidating its name. For this reason, the depfile is // written on each new compilation (even when it seems unnecessary). However, it // could in rare cases lead to a stale file fetched from the cache. -func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands bool) (string, error) { +func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) { // Hash input file. fileHash, err := hashFile(abspath) if err != nil { @@ -128,8 +128,8 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands // flags (for the assembler) is a compiler error. flags = append(flags, "-Qunused-arguments") } - if printCommands { - fmt.Printf("clang %s\n", strings.Join(flags, " ")) + if printCommands != nil { + printCommands("clang", flags...) } err = runCCompiler(flags...) if err != nil { diff --git a/compileopts/options.go b/compileopts/options.go index ee562100..10c143b8 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -25,7 +25,7 @@ type Options struct { PrintIR bool DumpSSA bool VerifyIR bool - PrintCommands bool + PrintCommands func(cmd string, args ...string) Debug bool PrintSizes string PrintAllocs *regexp.Regexp // regexp string diff --git a/main.go b/main.go index 1f34add3..88d7cfac 100644 --- a/main.go +++ b/main.go @@ -95,12 +95,31 @@ func copyFile(src, dst string) error { // executeCommand is a simple wrapper to exec.Cmd func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd { - if options.PrintCommands { - fmt.Printf("%s %s\n", name, strings.Join(arg, " ")) + if options.PrintCommands != nil { + options.PrintCommands(name, arg...) } return exec.Command(name, arg...) } +// printCommand prints a command to stdout while formatting it like a real +// command (escaping characters etc). The resulting command should be easy to +// run directly in a shell, although it is not guaranteed to be a safe shell +// escape. That's not a problem as the primary use case is printing the command, +// not running it. +func printCommand(cmd string, args ...string) { + command := append([]string{cmd}, args...) + for i, arg := range command { + // Source: https://www.oreilly.com/library/view/learning-the-bash/1565923472/ch01s09.html + const specialChars = "~`#$&*()\\|[]{};'\"<>?! " + if strings.ContainsAny(arg, specialChars) { + // See: https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash + arg = "'" + strings.ReplaceAll(arg, `'`, `'\''`) + "'" + command[i] = arg + } + } + fmt.Fprintln(os.Stderr, strings.Join(command, " ")) +} + // Build compiles and links the given package and writes it to outpath. func Build(pkgName, outpath string, options *compileopts.Options) error { config, err := builder.NewConfig(options) @@ -1008,7 +1027,6 @@ func main() { PrintSizes: *printSize, PrintStacks: *printStacks, PrintAllocs: printAllocs, - PrintCommands: *printCommands, Tags: *tags, GlobalValues: globalVarValues, WasmAbi: *wasmAbi, @@ -1016,6 +1034,9 @@ func main() { OpenOCDCommands: ocdCommands, LLVMFeatures: *llvmFeatures, } + if *printCommands { + options.PrintCommands = printCommand + } os.Setenv("CC", "clang -target="+*target)