Source file src/cmd/compile/internal/staticdata/embed.go

     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  
     5  package staticdata
     6  
     7  import (
     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  
    19  const (
    20  	embedUnknown = iota
    21  	embedBytes
    22  	embedString
    23  	embedFiles
    24  )
    25  
    26  func 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.
    69  func 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  
    82  func 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.
    99  func 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.
   107  func 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  

View as plain text