diff options
author | Cherry Zhang <cherryyz@google.com> | 2020-10-28 09:12:20 -0400 |
---|---|---|
committer | Cherry Zhang <cherryyz@google.com> | 2020-10-28 09:12:20 -0400 |
commit | a16e30d162c1c7408db7821e7b9513cefa09c6ca (patch) | |
tree | af752ba9ba44c547df39bb0af9bff79f610ba9d5 /src/cmd/compile/internal/gc/embed.go | |
parent | 91e4d2d57bc341dd82c98247117114c851380aef (diff) | |
parent | cf6cfba4d5358404dd890f6025e573a4b2156543 (diff) | |
download | go-git-dev.link.tar.gz |
[dev.link] all: merge branch 'master' into dev.linkdev.link
Clean merge.
Change-Id: Ia7b2808bc649790198d34c226a61d9e569084dc5
Diffstat (limited to 'src/cmd/compile/internal/gc/embed.go')
-rw-r--r-- | src/cmd/compile/internal/gc/embed.go | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/gc/embed.go b/src/cmd/compile/internal/gc/embed.go new file mode 100644 index 0000000000..103949c1f9 --- /dev/null +++ b/src/cmd/compile/internal/gc/embed.go @@ -0,0 +1,273 @@ +// 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" + "encoding/json" + "io/ioutil" + "log" + "path" + "sort" + "strconv" + "strings" +) + +var embedlist []*Node + +var embedCfg struct { + Patterns map[string][]string + Files map[string]string +} + +func readEmbedCfg(file string) { + data, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalf("-embedcfg: %v", err) + } + if err := json.Unmarshal(data, &embedCfg); err != nil { + log.Fatalf("%s: %v", file, err) + } + if embedCfg.Patterns == nil { + log.Fatalf("%s: invalid embedcfg: missing Patterns", file) + } + if embedCfg.Files == nil { + log.Fatalf("%s: invalid embedcfg: missing Files", file) + } +} + +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 + } + if embedCfg.Patterns == nil { + 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 { + files, ok := embedCfg.Patterns[pattern] + if !ok { + p.yyerrorpos(e.Pos, "invalid go:embed: build system did not map pattern: %s", pattern) + } + for _, file := range files { + if embedCfg.Files[file] == "" { + 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 { + if typ.Sym != nil && typ.Sym.Name == "FS" && (typ.Sym.Pkg.Path == "embed" || (typ.Sym.Pkg == localpkg && myimportpath == "embed")) { + 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 { + if typ.Sym != nil && typ.Sym.Name == "FS" && (typ.Sym.Pkg.Path == "embed" || (typ.Sym.Pkg == localpkg && myimportpath == "embed")) { + 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] + fsym, size, err := fileStringSym(v.Pos, embedCfg.Files[file], kind == embedString, nil) + 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 { + fsym, size, err := fileStringSym(v.Pos, embedCfg.Files[file], true, hash) + 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) + } +} |