Skip to content

Commit 5617f10

Browse files
Properly escape filenames in #line directives
In some cases, backslashes in a filenames were already escaped when emitting a #line directive, but sometimes no escaping happened. In any case, quotes in a filename should also be escaped. This commit introduces a helper function `QuoteCppString()` to handle quoting and escaping strings in a generic way. This new helper function is used in the code itself, but not in testcases yet. Note that ctags does not quite properly handle special characters and escaping in filenames. When parsing line markers, it just ignores escapes, so any escapes from the #line marker will be present in the ctags output as well. Before this commit, they would be copied unmodified into the #line directives generated from the ctags filenames. Now that these line directives have escaping applied, the filenames read from ctags need to be unescaped, to prevent double escapes. This unescaping already happened in CollectCTagsFromSketchFiles, but is now moved to parseTag where it is more appropriate and applies to all users. Since ctags does not handle escapes in line markers, it cuts the filename off at the first double quote. If a filename contains a double quote, the filename as output by ctags will be cut off before it, leaving the escaping \ at the end. Before this commit, this trailing \ would be output directly into the generated #line marker, which confused gcc and made the build fail. With this commit, this trailing \ is escaped itself, making the output syntactically valid, so the build now works with double quotes in a filename. However, due to this filename cut off, arduino-builder will no longer generate prototypes for functions in files that have a double quote in them (since it can no longer detect them as sketch functions), so double quotes (and probably tabs too) in filenames are still not completely supported yet. Signed-off-by: Matthijs Kooijman <matthijs@stdin.nl>
1 parent 6183f3b commit 5617f10

File tree

6 files changed

+37
-8
lines changed

6 files changed

+37
-8
lines changed

src/arduino.cc/builder/collect_ctags_from_sketch_files.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ package builder
3232
import (
3333
"arduino.cc/builder/types"
3434
"arduino.cc/builder/utils"
35-
"strings"
3635
)
3736

3837
type CollectCTagsFromSketchFiles struct{}
@@ -43,7 +42,7 @@ func (s *CollectCTagsFromSketchFiles) Run(ctx *types.Context) error {
4342
allCtags := ctx.CTagsOfPreprocessedSource
4443
ctagsOfSketch := []*types.CTag{}
4544
for _, ctag := range allCtags {
46-
if utils.SliceContains(sketchFileNames, strings.Replace(ctag.Filename, "\\\\", "\\", -1)) {
45+
if utils.SliceContains(sketchFileNames, ctag.Filename) {
4746
ctagsOfSketch = append(ctagsOfSketch, ctag)
4847
}
4948
}

src/arduino.cc/builder/ctags/ctags_parser.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,14 @@ func parseTag(row string) *types.CTag {
229229
parts := strings.Split(row, "\t")
230230

231231
tag.FunctionName = parts[0]
232-
tag.Filename = parts[1]
232+
// This unescapes any backslashes in the filename. These
233+
// filenames that ctags outputs originate from the line markers
234+
// in the source, as generated by gcc. gcc escapes both
235+
// backslashes and double quotes, but ctags ignores any escaping
236+
// and just cuts off the filename at the first double quote it
237+
// sees. This means any backslashes are still escaped, and need
238+
// to be unescape, and any quotes will just break the build.
239+
tag.Filename = strings.Replace(parts[1], "\\\\", "\\", -1)
233240

234241
parts = parts[2:]
235242

src/arduino.cc/builder/prototypes_adder.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ package builder
3232
import (
3333
"arduino.cc/builder/constants"
3434
"arduino.cc/builder/types"
35+
"arduino.cc/builder/utils"
3536
"fmt"
3637
"strconv"
3738
"strings"
@@ -87,7 +88,7 @@ func composePrototypeSection(line int, prototypes []*types.Prototype) string {
8788
str := joinPrototypes(prototypes)
8889
str += "\n#line "
8990
str += strconv.Itoa(line)
90-
str += " \"" + prototypes[0].File + "\""
91+
str += " " + utils.QuoteCppString(prototypes[0].File)
9192
str += "\n"
9293

9394
return str
@@ -99,7 +100,7 @@ func joinPrototypes(prototypes []*types.Prototype) string {
99100
if signatureContainsaDefaultArg(proto) {
100101
continue
101102
}
102-
prototypesSlice = append(prototypesSlice, "#line "+strconv.Itoa(proto.Line)+" \""+proto.File+"\"")
103+
prototypesSlice = append(prototypesSlice, "#line "+strconv.Itoa(proto.Line)+" "+utils.QuoteCppString(proto.File))
103104
prototypeParts := []string{}
104105
if proto.Modifiers != "" {
105106
prototypeParts = append(prototypeParts, proto.Modifiers)

src/arduino.cc/builder/sketch_source_merger.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ package builder
3232

3333
import (
3434
"arduino.cc/builder/types"
35+
"arduino.cc/builder/utils"
3536
"regexp"
36-
"strings"
3737
)
3838

3939
type SketchSourceMerger struct{}
@@ -47,7 +47,7 @@ func (s *SketchSourceMerger) Run(ctx *types.Context) error {
4747
includeSection += "#include <Arduino.h>\n"
4848
lineOffset++
4949
}
50-
includeSection += "#line 1 \"" + strings.Replace((&sketch.MainFile).Name, "\\", "\\\\", -1) + "\"\n"
50+
includeSection += "#line 1 " + utils.QuoteCppString(sketch.MainFile.Name) + "\n"
5151
lineOffset++
5252
ctx.IncludeSection = includeSection
5353

@@ -73,7 +73,7 @@ func sketchIncludesArduinoH(sketch *types.SketchFile) bool {
7373
}
7474

7575
func addSourceWrappedWithLineDirective(sketch *types.SketchFile) string {
76-
source := "#line 1 \"" + strings.Replace(sketch.Name, "\\", "\\\\", -1) + "\"\n"
76+
source := "#line 1 " + utils.QuoteCppString(sketch.Name) + "\n"
7777
source += sketch.Source
7878
source += "\n"
7979

src/arduino.cc/builder/test/utils_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,16 @@ func TestMapTrimSpace(t *testing.T) {
8787
require.Equal(t, "how are", parts[2])
8888
require.Equal(t, "you?", parts[3])
8989
}
90+
91+
func TestQuoteCppString(t *testing.T) {
92+
cases := map[string]string {
93+
`foo`: `"foo"`,
94+
`foo\bar`: `"foo\\bar"`,
95+
`foo "is" quoted and \\bar"" escaped\`: `"foo \"is\" quoted and \\\\bar\"\" escaped\\"`,
96+
// ASCII 0x20 - 0x7e, excluding `
97+
` !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_abcdefghijklmnopqrstuvwxyz{|}~`: `" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~"`,
98+
}
99+
for input, expected := range cases {
100+
require.Equal(t, expected, utils.QuoteCppString(input))
101+
}
102+
}

src/arduino.cc/builder/utils/utils.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,12 @@ func LogIfVerbose(level string, format string, args ...interface{}) types.Comman
407407
func LogThis(level string, format string, args ...interface{}) types.Command {
408408
return &loggerAction{false, level, format, args}
409409
}
410+
411+
// Returns the given string as a quoted string for use with the C
412+
// preprocessor. This adds double quotes around it and escapes any
413+
// double quotes and backslashes in the string.
414+
func QuoteCppString(str string) string {
415+
str = strings.Replace(str, "\\", "\\\\", -1)
416+
str = strings.Replace(str, "\"", "\\\"", -1)
417+
return "\"" + str + "\""
418+
}

0 commit comments

Comments
 (0)