1// Copyright 2009 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
5package noder
6
7import (
8	"fmt"
9	"internal/buildcfg"
10	"strings"
11
12	"cmd/compile/internal/ir"
13	"cmd/compile/internal/syntax"
14)
15
16func isSpace(c rune) bool {
17	return c == ' ' || c == '\t' || c == '\n' || c == '\r'
18}
19
20func isQuoted(s string) bool {
21	return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"'
22}
23
24const (
25	funcPragmas = ir.Nointerface |
26		ir.Noescape |
27		ir.Norace |
28		ir.Nosplit |
29		ir.Noinline |
30		ir.NoCheckPtr |
31		ir.RegisterParams | // TODO(register args) remove after register abi is working
32		ir.CgoUnsafeArgs |
33		ir.UintptrKeepAlive |
34		ir.UintptrEscapes |
35		ir.Systemstack |
36		ir.Nowritebarrier |
37		ir.Nowritebarrierrec |
38		ir.Yeswritebarrierrec
39)
40
41func pragmaFlag(verb string) ir.PragmaFlag {
42	switch verb {
43	case "go:build":
44		return ir.GoBuildPragma
45	case "go:nointerface":
46		if buildcfg.Experiment.FieldTrack {
47			return ir.Nointerface
48		}
49	case "go:noescape":
50		return ir.Noescape
51	case "go:norace":
52		return ir.Norace
53	case "go:nosplit":
54		return ir.Nosplit | ir.NoCheckPtr // implies NoCheckPtr (see #34972)
55	case "go:noinline":
56		return ir.Noinline
57	case "go:nocheckptr":
58		return ir.NoCheckPtr
59	case "go:systemstack":
60		return ir.Systemstack
61	case "go:nowritebarrier":
62		return ir.Nowritebarrier
63	case "go:nowritebarrierrec":
64		return ir.Nowritebarrierrec | ir.Nowritebarrier // implies Nowritebarrier
65	case "go:yeswritebarrierrec":
66		return ir.Yeswritebarrierrec
67	case "go:cgo_unsafe_args":
68		return ir.CgoUnsafeArgs | ir.NoCheckPtr // implies NoCheckPtr (see #34968)
69	case "go:uintptrkeepalive":
70		return ir.UintptrKeepAlive
71	case "go:uintptrescapes":
72		// This directive extends //go:uintptrkeepalive by forcing
73		// uintptr arguments to escape to the heap, which makes stack
74		// growth safe.
75		return ir.UintptrEscapes | ir.UintptrKeepAlive // implies UintptrKeepAlive
76	case "go:registerparams": // TODO(register args) remove after register abi is working
77		return ir.RegisterParams
78	}
79	return 0
80}
81
82// pragcgo is called concurrently if files are parsed concurrently.
83func (p *noder) pragcgo(pos syntax.Pos, text string) {
84	f := pragmaFields(text)
85
86	verb := strings.TrimPrefix(f[0], "go:")
87	f[0] = verb
88
89	switch verb {
90	case "cgo_export_static", "cgo_export_dynamic":
91		switch {
92		case len(f) == 2 && !isQuoted(f[1]):
93		case len(f) == 3 && !isQuoted(f[1]) && !isQuoted(f[2]):
94		default:
95			p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf(`usage: //go:%s local [remote]`, verb)})
96			return
97		}
98	case "cgo_import_dynamic":
99		switch {
100		case len(f) == 2 && !isQuoted(f[1]):
101		case len(f) == 3 && !isQuoted(f[1]) && !isQuoted(f[2]):
102		case len(f) == 4 && !isQuoted(f[1]) && !isQuoted(f[2]) && isQuoted(f[3]):
103			f[3] = strings.Trim(f[3], `"`)
104			if buildcfg.GOOS == "aix" && f[3] != "" {
105				// On Aix, library pattern must be "lib.a/object.o"
106				// or "lib.a/libname.so.X"
107				n := strings.Split(f[3], "/")
108				if len(n) != 2 || !strings.HasSuffix(n[0], ".a") || (!strings.HasSuffix(n[1], ".o") && !strings.Contains(n[1], ".so.")) {
109					p.error(syntax.Error{Pos: pos, Msg: `usage: //go:cgo_import_dynamic local [remote ["lib.a/object.o"]]`})
110					return
111				}
112			}
113		default:
114			p.error(syntax.Error{Pos: pos, Msg: `usage: //go:cgo_import_dynamic local [remote ["library"]]`})
115			return
116		}
117	case "cgo_import_static":
118		switch {
119		case len(f) == 2 && !isQuoted(f[1]):
120		default:
121			p.error(syntax.Error{Pos: pos, Msg: `usage: //go:cgo_import_static local`})
122			return
123		}
124	case "cgo_dynamic_linker":
125		switch {
126		case len(f) == 2 && isQuoted(f[1]):
127			f[1] = strings.Trim(f[1], `"`)
128		default:
129			p.error(syntax.Error{Pos: pos, Msg: `usage: //go:cgo_dynamic_linker "path"`})
130			return
131		}
132	case "cgo_ldflag":
133		switch {
134		case len(f) == 2 && isQuoted(f[1]):
135			f[1] = strings.Trim(f[1], `"`)
136		default:
137			p.error(syntax.Error{Pos: pos, Msg: `usage: //go:cgo_ldflag "arg"`})
138			return
139		}
140	default:
141		return
142	}
143	p.pragcgobuf = append(p.pragcgobuf, f)
144}
145
146// pragmaFields is similar to strings.FieldsFunc(s, isSpace)
147// but does not split when inside double quoted regions and always
148// splits before the start and after the end of a double quoted region.
149// pragmaFields does not recognize escaped quotes. If a quote in s is not
150// closed the part after the opening quote will not be returned as a field.
151func pragmaFields(s string) []string {
152	var a []string
153	inQuote := false
154	fieldStart := -1 // Set to -1 when looking for start of field.
155	for i, c := range s {
156		switch {
157		case c == '"':
158			if inQuote {
159				inQuote = false
160				a = append(a, s[fieldStart:i+1])
161				fieldStart = -1
162			} else {
163				inQuote = true
164				if fieldStart >= 0 {
165					a = append(a, s[fieldStart:i])
166				}
167				fieldStart = i
168			}
169		case !inQuote && isSpace(c):
170			if fieldStart >= 0 {
171				a = append(a, s[fieldStart:i])
172				fieldStart = -1
173			}
174		default:
175			if fieldStart == -1 {
176				fieldStart = i
177			}
178		}
179	}
180	if !inQuote && fieldStart >= 0 { // Last field might end at the end of the string.
181		a = append(a, s[fieldStart:])
182	}
183	return a
184}
185