// 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) } }