1// Copyright 2020 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 staticdata
6
7import (
8	"path"
9	"sort"
10	"strings"
11
12	"cmd/compile/internal/base"
13	"cmd/compile/internal/ir"
14	"cmd/compile/internal/objw"
15	"cmd/compile/internal/types"
16	"cmd/internal/obj"
17)
18
19const (
20	embedUnknown = iota
21	embedBytes
22	embedString
23	embedFiles
24)
25
26func embedFileList(v *ir.Name, kind int) []string {
27	// Build list of files to store.
28	have := make(map[string]bool)
29	var list []string
30	for _, e := range *v.Embed {
31		for _, pattern := range e.Patterns {
32			files, ok := base.Flag.Cfg.Embed.Patterns[pattern]
33			if !ok {
34				base.ErrorfAt(e.Pos, 0, "invalid go:embed: build system did not map pattern: %s", pattern)
35			}
36			for _, file := range files {
37				if base.Flag.Cfg.Embed.Files[file] == "" {
38					base.ErrorfAt(e.Pos, 0, "invalid go:embed: build system did not map file: %s", file)
39					continue
40				}
41				if !have[file] {
42					have[file] = true
43					list = append(list, file)
44				}
45				if kind == embedFiles {
46					for dir := path.Dir(file); dir != "." && !have[dir]; dir = path.Dir(dir) {
47						have[dir] = true
48						list = append(list, dir+"/")
49					}
50				}
51			}
52		}
53	}
54	sort.Slice(list, func(i, j int) bool {
55		return embedFileLess(list[i], list[j])
56	})
57
58	if kind == embedString || kind == embedBytes {
59		if len(list) > 1 {
60			base.ErrorfAt(v.Pos(), 0, "invalid go:embed: multiple files for type %v", v.Type())
61			return nil
62		}
63	}
64
65	return list
66}
67
68// embedKind determines the kind of embedding variable.
69func embedKind(typ *types.Type) int {
70	if typ.Sym() != nil && typ.Sym().Name == "FS" && typ.Sym().Pkg.Path == "embed" {
71		return embedFiles
72	}
73	if typ.Kind() == types.TSTRING {
74		return embedString
75	}
76	if typ.IsSlice() && typ.Elem().Kind() == types.TUINT8 {
77		return embedBytes
78	}
79	return embedUnknown
80}
81
82func embedFileNameSplit(name string) (dir, elem string, isDir bool) {
83	if name[len(name)-1] == '/' {
84		isDir = true
85		name = name[:len(name)-1]
86	}
87	i := len(name) - 1
88	for i >= 0 && name[i] != '/' {
89		i--
90	}
91	if i < 0 {
92		return ".", name, isDir
93	}
94	return name[:i], name[i+1:], isDir
95}
96
97// embedFileLess implements the sort order for a list of embedded files.
98// See the comment inside ../../../../embed/embed.go's Files struct for rationale.
99func embedFileLess(x, y string) bool {
100	xdir, xelem, _ := embedFileNameSplit(x)
101	ydir, yelem, _ := embedFileNameSplit(y)
102	return xdir < ydir || xdir == ydir && xelem < yelem
103}
104
105// WriteEmbed emits the init data for a //go:embed variable,
106// which is either a string, a []byte, or an embed.FS.
107func WriteEmbed(v *ir.Name) {
108	// TODO(mdempsky): User errors should be reported by the frontend.
109
110	commentPos := (*v.Embed)[0].Pos
111	if base.Flag.Cfg.Embed.Patterns == nil {
112		base.ErrorfAt(commentPos, 0, "invalid go:embed: build system did not supply embed configuration")
113		return
114	}
115	kind := embedKind(v.Type())
116	if kind == embedUnknown {
117		base.ErrorfAt(v.Pos(), 0, "go:embed cannot apply to var of type %v", v.Type())
118		return
119	}
120
121	files := embedFileList(v, kind)
122	switch kind {
123	case embedString, embedBytes:
124		file := files[0]
125		fsym, size, err := fileStringSym(v.Pos(), base.Flag.Cfg.Embed.Files[file], kind == embedString, nil)
126		if err != nil {
127			base.ErrorfAt(v.Pos(), 0, "embed %s: %v", file, err)
128		}
129		sym := v.Linksym()
130		off := 0
131		off = objw.SymPtr(sym, off, fsym, 0)       // data string
132		off = objw.Uintptr(sym, off, uint64(size)) // len
133		if kind == embedBytes {
134			objw.Uintptr(sym, off, uint64(size)) // cap for slice
135		}
136
137	case embedFiles:
138		slicedata := v.Sym().Pkg.Lookup(v.Sym().Name + `.files`).Linksym()
139		off := 0
140		// []files pointed at by Files
141		off = objw.SymPtr(slicedata, off, slicedata, 3*types.PtrSize) // []file, pointing just past slice
142		off = objw.Uintptr(slicedata, off, uint64(len(files)))
143		off = objw.Uintptr(slicedata, off, uint64(len(files)))
144
145		// embed/embed.go type file is:
146		//	name string
147		//	data string
148		//	hash [16]byte
149		// Emit one of these per file in the set.
150		const hashSize = 16
151		hash := make([]byte, hashSize)
152		for _, file := range files {
153			off = objw.SymPtr(slicedata, off, StringSym(v.Pos(), file), 0) // file string
154			off = objw.Uintptr(slicedata, off, uint64(len(file)))
155			if strings.HasSuffix(file, "/") {
156				// entry for directory - no data
157				off = objw.Uintptr(slicedata, off, 0)
158				off = objw.Uintptr(slicedata, off, 0)
159				off += hashSize
160			} else {
161				fsym, size, err := fileStringSym(v.Pos(), base.Flag.Cfg.Embed.Files[file], true, hash)
162				if err != nil {
163					base.ErrorfAt(v.Pos(), 0, "embed %s: %v", file, err)
164				}
165				off = objw.SymPtr(slicedata, off, fsym, 0) // data string
166				off = objw.Uintptr(slicedata, off, uint64(size))
167				off = int(slicedata.WriteBytes(base.Ctxt, int64(off), hash))
168			}
169		}
170		objw.Global(slicedata, int32(off), obj.RODATA|obj.LOCAL)
171		sym := v.Linksym()
172		objw.SymPtr(sym, 0, slicedata, 0)
173	}
174}
175