2020-07-19 00:32:02 -04:00
|
|
|
// Copyright 2020 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package gc
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"cmd/compile/internal/syntax"
|
|
|
|
|
"cmd/compile/internal/types"
|
|
|
|
|
"cmd/internal/obj"
|
2020-11-16 00:59:30 -05:00
|
|
|
|
2020-07-19 00:32:02 -04:00
|
|
|
"path"
|
|
|
|
|
"sort"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var embedlist []*Node
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
embedUnknown = iota
|
|
|
|
|
embedBytes
|
|
|
|
|
embedString
|
|
|
|
|
embedFiles
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var numLocalEmbed int
|
|
|
|
|
|
|
|
|
|
func varEmbed(p *noder, names []*Node, typ *Node, exprs []*Node, embeds []PragmaEmbed) (newExprs []*Node) {
|
|
|
|
|
haveEmbed := false
|
|
|
|
|
for _, decl := range p.file.DeclList {
|
|
|
|
|
imp, ok := decl.(*syntax.ImportDecl)
|
|
|
|
|
if !ok {
|
|
|
|
|
// imports always come first
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
path, _ := strconv.Unquote(imp.Path.Value)
|
|
|
|
|
if path == "embed" {
|
|
|
|
|
haveEmbed = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pos := embeds[0].Pos
|
|
|
|
|
if !haveEmbed {
|
|
|
|
|
p.yyerrorpos(pos, "invalid go:embed: missing import \"embed\"")
|
|
|
|
|
return exprs
|
|
|
|
|
}
|
2020-11-16 00:59:30 -05:00
|
|
|
if Flag.Cfg.Embed.Patterns == nil {
|
2020-07-19 00:32:02 -04:00
|
|
|
p.yyerrorpos(pos, "invalid go:embed: build system did not supply embed configuration")
|
|
|
|
|
return exprs
|
|
|
|
|
}
|
|
|
|
|
if len(names) > 1 {
|
|
|
|
|
p.yyerrorpos(pos, "go:embed cannot apply to multiple vars")
|
|
|
|
|
return exprs
|
|
|
|
|
}
|
|
|
|
|
if len(exprs) > 0 {
|
|
|
|
|
p.yyerrorpos(pos, "go:embed cannot apply to var with initializer")
|
|
|
|
|
return exprs
|
|
|
|
|
}
|
|
|
|
|
if typ == nil {
|
|
|
|
|
// Should not happen, since len(exprs) == 0 now.
|
|
|
|
|
p.yyerrorpos(pos, "go:embed cannot apply to var without type")
|
|
|
|
|
return exprs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kind := embedKindApprox(typ)
|
|
|
|
|
if kind == embedUnknown {
|
|
|
|
|
p.yyerrorpos(pos, "go:embed cannot apply to var of type %v", typ)
|
|
|
|
|
return exprs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build list of files to store.
|
|
|
|
|
have := make(map[string]bool)
|
|
|
|
|
var list []string
|
|
|
|
|
for _, e := range embeds {
|
|
|
|
|
for _, pattern := range e.Patterns {
|
2020-11-16 00:59:30 -05:00
|
|
|
files, ok := Flag.Cfg.Embed.Patterns[pattern]
|
2020-07-19 00:32:02 -04:00
|
|
|
if !ok {
|
|
|
|
|
p.yyerrorpos(e.Pos, "invalid go:embed: build system did not map pattern: %s", pattern)
|
|
|
|
|
}
|
|
|
|
|
for _, file := range files {
|
2020-11-16 00:59:30 -05:00
|
|
|
if Flag.Cfg.Embed.Files[file] == "" {
|
2020-07-19 00:32:02 -04:00
|
|
|
p.yyerrorpos(e.Pos, "invalid go:embed: build system did not map file: %s", file)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if !have[file] {
|
|
|
|
|
have[file] = true
|
|
|
|
|
list = append(list, file)
|
|
|
|
|
}
|
|
|
|
|
if kind == embedFiles {
|
|
|
|
|
for dir := path.Dir(file); dir != "." && !have[dir]; dir = path.Dir(dir) {
|
|
|
|
|
have[dir] = true
|
|
|
|
|
list = append(list, dir+"/")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(list, func(i, j int) bool {
|
|
|
|
|
return embedFileLess(list[i], list[j])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if kind == embedString || kind == embedBytes {
|
|
|
|
|
if len(list) > 1 {
|
|
|
|
|
p.yyerrorpos(pos, "invalid go:embed: multiple files for type %v", typ)
|
|
|
|
|
return exprs
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v := names[0]
|
|
|
|
|
if dclcontext != PEXTERN {
|
|
|
|
|
numLocalEmbed++
|
|
|
|
|
v = newnamel(v.Pos, lookupN("embed.", numLocalEmbed))
|
|
|
|
|
v.Sym.Def = asTypesNode(v)
|
|
|
|
|
v.Name.Param.Ntype = typ
|
|
|
|
|
v.SetClass(PEXTERN)
|
|
|
|
|
externdcl = append(externdcl, v)
|
|
|
|
|
exprs = []*Node{v}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v.Name.Param.SetEmbedFiles(list)
|
|
|
|
|
embedlist = append(embedlist, v)
|
|
|
|
|
return exprs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// embedKindApprox determines the kind of embedding variable, approximately.
|
|
|
|
|
// The match is approximate because we haven't done scope resolution yet and
|
|
|
|
|
// can't tell whether "string" and "byte" really mean "string" and "byte".
|
|
|
|
|
// The result must be confirmed later, after type checking, using embedKind.
|
|
|
|
|
func embedKindApprox(typ *Node) int {
|
2020-11-16 00:59:30 -05:00
|
|
|
if typ.Sym != nil && typ.Sym.Name == "FS" && (typ.Sym.Pkg.Path == "embed" || (typ.Sym.Pkg == localpkg && Ctxt.Pkgpath == "embed")) {
|
2020-07-19 00:32:02 -04:00
|
|
|
return embedFiles
|
|
|
|
|
}
|
|
|
|
|
// These are not guaranteed to match only string and []byte -
|
|
|
|
|
// maybe the local package has redefined one of those words.
|
|
|
|
|
// But it's the best we can do now during the noder.
|
|
|
|
|
// The stricter check happens later, in initEmbed calling embedKind.
|
|
|
|
|
if typ.Sym != nil && typ.Sym.Name == "string" && typ.Sym.Pkg == localpkg {
|
|
|
|
|
return embedString
|
|
|
|
|
}
|
|
|
|
|
if typ.Op == OTARRAY && typ.Left == nil && typ.Right.Sym != nil && typ.Right.Sym.Name == "byte" && typ.Right.Sym.Pkg == localpkg {
|
|
|
|
|
return embedBytes
|
|
|
|
|
}
|
|
|
|
|
return embedUnknown
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// embedKind determines the kind of embedding variable.
|
|
|
|
|
func embedKind(typ *types.Type) int {
|
2020-11-16 00:59:30 -05:00
|
|
|
if typ.Sym != nil && typ.Sym.Name == "FS" && (typ.Sym.Pkg.Path == "embed" || (typ.Sym.Pkg == localpkg && Ctxt.Pkgpath == "embed")) {
|
2020-07-19 00:32:02 -04:00
|
|
|
return embedFiles
|
|
|
|
|
}
|
|
|
|
|
if typ == types.Types[TSTRING] {
|
|
|
|
|
return embedString
|
|
|
|
|
}
|
|
|
|
|
if typ.Sym == nil && typ.IsSlice() && typ.Elem() == types.Bytetype {
|
|
|
|
|
return embedBytes
|
|
|
|
|
}
|
|
|
|
|
return embedUnknown
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func embedFileNameSplit(name string) (dir, elem string, isDir bool) {
|
|
|
|
|
if name[len(name)-1] == '/' {
|
|
|
|
|
isDir = true
|
|
|
|
|
name = name[:len(name)-1]
|
|
|
|
|
}
|
|
|
|
|
i := len(name) - 1
|
|
|
|
|
for i >= 0 && name[i] != '/' {
|
|
|
|
|
i--
|
|
|
|
|
}
|
|
|
|
|
if i < 0 {
|
|
|
|
|
return ".", name, isDir
|
|
|
|
|
}
|
|
|
|
|
return name[:i], name[i+1:], isDir
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// embedFileLess implements the sort order for a list of embedded files.
|
|
|
|
|
// See the comment inside ../../../../embed/embed.go's Files struct for rationale.
|
|
|
|
|
func embedFileLess(x, y string) bool {
|
|
|
|
|
xdir, xelem, _ := embedFileNameSplit(x)
|
|
|
|
|
ydir, yelem, _ := embedFileNameSplit(y)
|
|
|
|
|
return xdir < ydir || xdir == ydir && xelem < yelem
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func dumpembeds() {
|
|
|
|
|
for _, v := range embedlist {
|
|
|
|
|
initEmbed(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// initEmbed emits the init data for a //go:embed variable,
|
|
|
|
|
// which is either a string, a []byte, or an embed.FS.
|
|
|
|
|
func initEmbed(v *Node) {
|
|
|
|
|
files := v.Name.Param.EmbedFiles()
|
|
|
|
|
switch kind := embedKind(v.Type); kind {
|
|
|
|
|
case embedUnknown:
|
|
|
|
|
yyerrorl(v.Pos, "go:embed cannot apply to var of type %v", v.Type)
|
|
|
|
|
|
|
|
|
|
case embedString, embedBytes:
|
|
|
|
|
file := files[0]
|
2020-11-16 00:59:30 -05:00
|
|
|
fsym, size, err := fileStringSym(v.Pos, Flag.Cfg.Embed.Files[file], kind == embedString, nil)
|
2020-07-19 00:32:02 -04:00
|
|
|
if err != nil {
|
|
|
|
|
yyerrorl(v.Pos, "embed %s: %v", file, err)
|
|
|
|
|
}
|
|
|
|
|
sym := v.Sym.Linksym()
|
|
|
|
|
off := 0
|
|
|
|
|
off = dsymptr(sym, off, fsym, 0) // data string
|
|
|
|
|
off = duintptr(sym, off, uint64(size)) // len
|
|
|
|
|
if kind == embedBytes {
|
|
|
|
|
duintptr(sym, off, uint64(size)) // cap for slice
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case embedFiles:
|
|
|
|
|
slicedata := Ctxt.Lookup(`"".` + v.Sym.Name + `.files`)
|
|
|
|
|
off := 0
|
|
|
|
|
// []files pointed at by Files
|
|
|
|
|
off = dsymptr(slicedata, off, slicedata, 3*Widthptr) // []file, pointing just past slice
|
|
|
|
|
off = duintptr(slicedata, off, uint64(len(files)))
|
|
|
|
|
off = duintptr(slicedata, off, uint64(len(files)))
|
|
|
|
|
|
|
|
|
|
// embed/embed.go type file is:
|
|
|
|
|
// name string
|
|
|
|
|
// data string
|
|
|
|
|
// hash [16]byte
|
|
|
|
|
// Emit one of these per file in the set.
|
|
|
|
|
const hashSize = 16
|
|
|
|
|
hash := make([]byte, hashSize)
|
|
|
|
|
for _, file := range files {
|
|
|
|
|
off = dsymptr(slicedata, off, stringsym(v.Pos, file), 0) // file string
|
|
|
|
|
off = duintptr(slicedata, off, uint64(len(file)))
|
|
|
|
|
if strings.HasSuffix(file, "/") {
|
|
|
|
|
// entry for directory - no data
|
|
|
|
|
off = duintptr(slicedata, off, 0)
|
|
|
|
|
off = duintptr(slicedata, off, 0)
|
|
|
|
|
off += hashSize
|
|
|
|
|
} else {
|
2020-11-16 00:59:30 -05:00
|
|
|
fsym, size, err := fileStringSym(v.Pos, Flag.Cfg.Embed.Files[file], true, hash)
|
2020-07-19 00:32:02 -04:00
|
|
|
if err != nil {
|
|
|
|
|
yyerrorl(v.Pos, "embed %s: %v", file, err)
|
|
|
|
|
}
|
|
|
|
|
off = dsymptr(slicedata, off, fsym, 0) // data string
|
|
|
|
|
off = duintptr(slicedata, off, uint64(size))
|
|
|
|
|
off = int(slicedata.WriteBytes(Ctxt, int64(off), hash))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ggloblsym(slicedata, int32(off), obj.RODATA|obj.LOCAL)
|
|
|
|
|
sym := v.Sym.Linksym()
|
|
|
|
|
dsymptr(sym, 0, slicedata, 0)
|
|
|
|
|
}
|
|
|
|
|
}
|