Source file src/internal/coverage/cfile/emit.go

     1  // Copyright 2022 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 cfile implements management of coverage files.
     6  // It provides functionality exported in runtime/coverage as well as
     7  // additional functionality used directly by package testing
     8  // through testing/internal/testdeps.
     9  package cfile
    10  
    11  import (
    12  	"crypto/md5"
    13  	"fmt"
    14  	"internal/coverage"
    15  	"internal/coverage/encodecounter"
    16  	"internal/coverage/encodemeta"
    17  	"internal/coverage/rtcov"
    18  	"io"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime"
    22  	"strconv"
    23  	"sync/atomic"
    24  	"time"
    25  	"unsafe"
    26  )
    27  
    28  // This file contains functions that support the writing of data files
    29  // emitted at the end of code coverage testing runs, from instrumented
    30  // executables.
    31  
    32  // getCovCounterList returns a list of counter-data blobs registered
    33  // for the currently executing instrumented program. It is defined in the
    34  // runtime.
    35  //
    36  //go:linkname getCovCounterList
    37  func getCovCounterList() []rtcov.CovCounterBlob
    38  
    39  // emitState holds useful state information during the emit process.
    40  //
    41  // When an instrumented program finishes execution and starts the
    42  // process of writing out coverage data, it's possible that an
    43  // existing meta-data file already exists in the output directory. In
    44  // this case openOutputFiles() below will leave the 'mf' field below
    45  // as nil. If a new meta-data file is needed, field 'mfname' will be
    46  // the final desired path of the meta file, 'mftmp' will be a
    47  // temporary file, and 'mf' will be an open os.File pointer for
    48  // 'mftmp'. The meta-data file payload will be written to 'mf', the
    49  // temp file will be then closed and renamed (from 'mftmp' to
    50  // 'mfname'), so as to insure that the meta-data file is created
    51  // atomically; we want this so that things work smoothly in cases
    52  // where there are several instances of a given instrumented program
    53  // all terminating at the same time and trying to create meta-data
    54  // files simultaneously.
    55  //
    56  // For counter data files there is less chance of a collision, hence
    57  // the openOutputFiles() stores the counter data file in 'cfname' and
    58  // then places the *io.File into 'cf'.
    59  type emitState struct {
    60  	mfname string   // path of final meta-data output file
    61  	mftmp  string   // path to meta-data temp file (if needed)
    62  	mf     *os.File // open os.File for meta-data temp file
    63  	cfname string   // path of final counter data file
    64  	cftmp  string   // path to counter data temp file
    65  	cf     *os.File // open os.File for counter data file
    66  	outdir string   // output directory
    67  
    68  	// List of meta-data symbols obtained from the runtime
    69  	metalist []rtcov.CovMetaBlob
    70  
    71  	// List of counter-data symbols obtained from the runtime
    72  	counterlist []rtcov.CovCounterBlob
    73  
    74  	// Table to use for remapping hard-coded pkg ids.
    75  	pkgmap map[int]int
    76  
    77  	// emit debug trace output
    78  	debug bool
    79  }
    80  
    81  var (
    82  	// finalHash is computed at init time from the list of meta-data
    83  	// symbols registered during init. It is used both for writing the
    84  	// meta-data file and counter-data files.
    85  	finalHash [16]byte
    86  	// Set to true when we've computed finalHash + finalMetaLen.
    87  	finalHashComputed bool
    88  	// Total meta-data length.
    89  	finalMetaLen uint64
    90  	// Records whether we've already attempted to write meta-data.
    91  	metaDataEmitAttempted bool
    92  	// Counter mode for this instrumented program run.
    93  	cmode coverage.CounterMode
    94  	// Counter granularity for this instrumented program run.
    95  	cgran coverage.CounterGranularity
    96  	// Cached value of GOCOVERDIR environment variable.
    97  	goCoverDir string
    98  	// Copy of os.Args made at init time, converted into map format.
    99  	capturedOsArgs map[string]string
   100  	// Flag used in tests to signal that coverage data already written.
   101  	covProfileAlreadyEmitted bool
   102  )
   103  
   104  // fileType is used to select between counter-data files and
   105  // meta-data files.
   106  type fileType int
   107  
   108  const (
   109  	noFile = 1 << iota
   110  	metaDataFile
   111  	counterDataFile
   112  )
   113  
   114  // emitMetaData emits the meta-data output file for this coverage run.
   115  // This entry point is intended to be invoked by the compiler from
   116  // an instrumented program's main package init func.
   117  func emitMetaData() {
   118  	if covProfileAlreadyEmitted {
   119  		return
   120  	}
   121  	ml, err := prepareForMetaEmit()
   122  	if err != nil {
   123  		fmt.Fprintf(os.Stderr, "error: coverage meta-data prep failed: %v\n", err)
   124  		if os.Getenv("GOCOVERDEBUG") != "" {
   125  			panic("meta-data write failure")
   126  		}
   127  	}
   128  	if len(ml) == 0 {
   129  		fmt.Fprintf(os.Stderr, "program not built with -cover\n")
   130  		return
   131  	}
   132  
   133  	goCoverDir = os.Getenv("GOCOVERDIR")
   134  	if goCoverDir == "" {
   135  		fmt.Fprintf(os.Stderr, "warning: GOCOVERDIR not set, no coverage data emitted\n")
   136  		return
   137  	}
   138  
   139  	if err := emitMetaDataToDirectory(goCoverDir, ml); err != nil {
   140  		fmt.Fprintf(os.Stderr, "error: coverage meta-data emit failed: %v\n", err)
   141  		if os.Getenv("GOCOVERDEBUG") != "" {
   142  			panic("meta-data write failure")
   143  		}
   144  	}
   145  }
   146  
   147  func modeClash(m coverage.CounterMode) bool {
   148  	if m == coverage.CtrModeRegOnly || m == coverage.CtrModeTestMain {
   149  		return false
   150  	}
   151  	if cmode == coverage.CtrModeInvalid {
   152  		cmode = m
   153  		return false
   154  	}
   155  	return cmode != m
   156  }
   157  
   158  func granClash(g coverage.CounterGranularity) bool {
   159  	if cgran == coverage.CtrGranularityInvalid {
   160  		cgran = g
   161  		return false
   162  	}
   163  	return cgran != g
   164  }
   165  
   166  // prepareForMetaEmit performs preparatory steps needed prior to
   167  // emitting a meta-data file, notably computing a final hash of
   168  // all meta-data blobs and capturing os args.
   169  func prepareForMetaEmit() ([]rtcov.CovMetaBlob, error) {
   170  	// Ask the runtime for the list of coverage meta-data symbols.
   171  	ml := rtcov.Meta.List
   172  
   173  	// In the normal case (go build -o prog.exe ... ; ./prog.exe)
   174  	// len(ml) will always be non-zero, but we check here since at
   175  	// some point this function will be reachable via user-callable
   176  	// APIs (for example, to write out coverage data from a server
   177  	// program that doesn't ever call os.Exit).
   178  	if len(ml) == 0 {
   179  		return nil, nil
   180  	}
   181  
   182  	s := &emitState{
   183  		metalist: ml,
   184  		debug:    os.Getenv("GOCOVERDEBUG") != "",
   185  	}
   186  
   187  	// Capture os.Args() now so as to avoid issues if args
   188  	// are rewritten during program execution.
   189  	capturedOsArgs = captureOsArgs()
   190  
   191  	if s.debug {
   192  		fmt.Fprintf(os.Stderr, "=+= GOCOVERDIR is %s\n", os.Getenv("GOCOVERDIR"))
   193  		fmt.Fprintf(os.Stderr, "=+= contents of covmetalist:\n")
   194  		for k, b := range ml {
   195  			fmt.Fprintf(os.Stderr, "=+= slot: %d path: %s ", k, b.PkgPath)
   196  			if b.PkgID != -1 {
   197  				fmt.Fprintf(os.Stderr, " hcid: %d", b.PkgID)
   198  			}
   199  			fmt.Fprintf(os.Stderr, "\n")
   200  		}
   201  		pm := rtcov.Meta.PkgMap
   202  		fmt.Fprintf(os.Stderr, "=+= remap table:\n")
   203  		for from, to := range pm {
   204  			fmt.Fprintf(os.Stderr, "=+= from %d to %d\n",
   205  				uint32(from), uint32(to))
   206  		}
   207  	}
   208  
   209  	h := md5.New()
   210  	tlen := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
   211  	for _, entry := range ml {
   212  		if _, err := h.Write(entry.Hash[:]); err != nil {
   213  			return nil, err
   214  		}
   215  		tlen += uint64(entry.Len)
   216  		ecm := coverage.CounterMode(entry.CounterMode)
   217  		if modeClash(ecm) {
   218  			return nil, fmt.Errorf("coverage counter mode clash: package %s uses mode=%d, but package %s uses mode=%s\n", ml[0].PkgPath, cmode, entry.PkgPath, ecm)
   219  		}
   220  		ecg := coverage.CounterGranularity(entry.CounterGranularity)
   221  		if granClash(ecg) {
   222  			return nil, fmt.Errorf("coverage counter granularity clash: package %s uses gran=%d, but package %s uses gran=%s\n", ml[0].PkgPath, cgran, entry.PkgPath, ecg)
   223  		}
   224  	}
   225  
   226  	// Hash mode and granularity as well.
   227  	h.Write([]byte(cmode.String()))
   228  	h.Write([]byte(cgran.String()))
   229  
   230  	// Compute final digest.
   231  	fh := h.Sum(nil)
   232  	copy(finalHash[:], fh)
   233  	finalHashComputed = true
   234  	finalMetaLen = tlen
   235  
   236  	return ml, nil
   237  }
   238  
   239  // emitMetaDataToDirectory emits the meta-data output file to the specified
   240  // directory, returning an error if something went wrong.
   241  func emitMetaDataToDirectory(outdir string, ml []rtcov.CovMetaBlob) error {
   242  	ml, err := prepareForMetaEmit()
   243  	if err != nil {
   244  		return err
   245  	}
   246  	if len(ml) == 0 {
   247  		return nil
   248  	}
   249  
   250  	metaDataEmitAttempted = true
   251  
   252  	s := &emitState{
   253  		metalist: ml,
   254  		debug:    os.Getenv("GOCOVERDEBUG") != "",
   255  		outdir:   outdir,
   256  	}
   257  
   258  	// Open output files.
   259  	if err := s.openOutputFiles(finalHash, finalMetaLen, metaDataFile); err != nil {
   260  		return err
   261  	}
   262  
   263  	// Emit meta-data file only if needed (may already be present).
   264  	if s.needMetaDataFile() {
   265  		if err := s.emitMetaDataFile(finalHash, finalMetaLen); err != nil {
   266  			return err
   267  		}
   268  	}
   269  	return nil
   270  }
   271  
   272  // emitCounterData emits the counter data output file for this coverage run.
   273  // This entry point is intended to be invoked by the runtime when an
   274  // instrumented program is terminating or calling os.Exit().
   275  func emitCounterData() {
   276  	if goCoverDir == "" || !finalHashComputed || covProfileAlreadyEmitted {
   277  		return
   278  	}
   279  	if err := emitCounterDataToDirectory(goCoverDir); err != nil {
   280  		fmt.Fprintf(os.Stderr, "error: coverage counter data emit failed: %v\n", err)
   281  		if os.Getenv("GOCOVERDEBUG") != "" {
   282  			panic("counter-data write failure")
   283  		}
   284  	}
   285  }
   286  
   287  // emitCounterDataToDirectory emits the counter-data output file for this coverage run.
   288  func emitCounterDataToDirectory(outdir string) error {
   289  	// Ask the runtime for the list of coverage counter symbols.
   290  	cl := getCovCounterList()
   291  	if len(cl) == 0 {
   292  		// no work to do here.
   293  		return nil
   294  	}
   295  
   296  	if !finalHashComputed {
   297  		return fmt.Errorf("error: meta-data not available (binary not built with -cover?)")
   298  	}
   299  
   300  	// Ask the runtime for the list of coverage counter symbols.
   301  	pm := rtcov.Meta.PkgMap
   302  	s := &emitState{
   303  		counterlist: cl,
   304  		pkgmap:      pm,
   305  		outdir:      outdir,
   306  		debug:       os.Getenv("GOCOVERDEBUG") != "",
   307  	}
   308  
   309  	// Open output file.
   310  	if err := s.openOutputFiles(finalHash, finalMetaLen, counterDataFile); err != nil {
   311  		return err
   312  	}
   313  	if s.cf == nil {
   314  		return fmt.Errorf("counter data output file open failed (no additional info")
   315  	}
   316  
   317  	// Emit counter data file.
   318  	if err := s.emitCounterDataFile(finalHash, s.cf); err != nil {
   319  		return err
   320  	}
   321  	if err := s.cf.Close(); err != nil {
   322  		return fmt.Errorf("closing counter data file: %v", err)
   323  	}
   324  
   325  	// Counter file has now been closed. Rename the temp to the
   326  	// final desired path.
   327  	if err := os.Rename(s.cftmp, s.cfname); err != nil {
   328  		return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.cfname, s.cftmp, err)
   329  	}
   330  
   331  	return nil
   332  }
   333  
   334  // emitCounterDataToWriter emits counter data for this coverage run to an io.Writer.
   335  func (s *emitState) emitCounterDataToWriter(w io.Writer) error {
   336  	if err := s.emitCounterDataFile(finalHash, w); err != nil {
   337  		return err
   338  	}
   339  	return nil
   340  }
   341  
   342  // openMetaFile determines whether we need to emit a meta-data output
   343  // file, or whether we can reuse the existing file in the coverage out
   344  // dir. It updates mfname/mftmp/mf fields in 's', returning an error
   345  // if something went wrong. See the comment on the emitState type
   346  // definition above for more on how file opening is managed.
   347  func (s *emitState) openMetaFile(metaHash [16]byte, metaLen uint64) error {
   348  
   349  	// Open meta-outfile for reading to see if it exists.
   350  	fn := fmt.Sprintf("%s.%x", coverage.MetaFilePref, metaHash)
   351  	s.mfname = filepath.Join(s.outdir, fn)
   352  	fi, err := os.Stat(s.mfname)
   353  	if err != nil || fi.Size() != int64(metaLen) {
   354  		// We need a new meta-file.
   355  		tname := "tmp." + fn + strconv.FormatInt(time.Now().UnixNano(), 10)
   356  		s.mftmp = filepath.Join(s.outdir, tname)
   357  		s.mf, err = os.Create(s.mftmp)
   358  		if err != nil {
   359  			return fmt.Errorf("creating meta-data file %s: %v", s.mftmp, err)
   360  		}
   361  	}
   362  	return nil
   363  }
   364  
   365  // openCounterFile opens an output file for the counter data portion
   366  // of a test coverage run. If updates the 'cfname' and 'cf' fields in
   367  // 's', returning an error if something went wrong.
   368  func (s *emitState) openCounterFile(metaHash [16]byte) error {
   369  	processID := os.Getpid()
   370  	fn := fmt.Sprintf(coverage.CounterFileTempl, coverage.CounterFilePref, metaHash, processID, time.Now().UnixNano())
   371  	s.cfname = filepath.Join(s.outdir, fn)
   372  	s.cftmp = filepath.Join(s.outdir, "tmp."+fn)
   373  	var err error
   374  	s.cf, err = os.Create(s.cftmp)
   375  	if err != nil {
   376  		return fmt.Errorf("creating counter data file %s: %v", s.cftmp, err)
   377  	}
   378  	return nil
   379  }
   380  
   381  // openOutputFiles opens output files in preparation for emitting
   382  // coverage data. In the case of the meta-data file, openOutputFiles
   383  // may determine that we can reuse an existing meta-data file in the
   384  // outdir, in which case it will leave the 'mf' field in the state
   385  // struct as nil. If a new meta-file is needed, the field 'mfname'
   386  // will be the final desired path of the meta file, 'mftmp' will be a
   387  // temporary file, and 'mf' will be an open os.File pointer for
   388  // 'mftmp'. The idea is that the client/caller will write content into
   389  // 'mf', close it, and then rename 'mftmp' to 'mfname'. This function
   390  // also opens the counter data output file, setting 'cf' and 'cfname'
   391  // in the state struct.
   392  func (s *emitState) openOutputFiles(metaHash [16]byte, metaLen uint64, which fileType) error {
   393  	fi, err := os.Stat(s.outdir)
   394  	if err != nil {
   395  		return fmt.Errorf("output directory %q inaccessible (err: %v); no coverage data written", s.outdir, err)
   396  	}
   397  	if !fi.IsDir() {
   398  		return fmt.Errorf("output directory %q not a directory; no coverage data written", s.outdir)
   399  	}
   400  
   401  	if (which & metaDataFile) != 0 {
   402  		if err := s.openMetaFile(metaHash, metaLen); err != nil {
   403  			return err
   404  		}
   405  	}
   406  	if (which & counterDataFile) != 0 {
   407  		if err := s.openCounterFile(metaHash); err != nil {
   408  			return err
   409  		}
   410  	}
   411  	return nil
   412  }
   413  
   414  // emitMetaDataFile emits coverage meta-data to a previously opened
   415  // temporary file (s.mftmp), then renames the generated file to the
   416  // final path (s.mfname).
   417  func (s *emitState) emitMetaDataFile(finalHash [16]byte, tlen uint64) error {
   418  	if err := writeMetaData(s.mf, s.metalist, cmode, cgran, finalHash); err != nil {
   419  		return fmt.Errorf("writing %s: %v\n", s.mftmp, err)
   420  	}
   421  	if err := s.mf.Close(); err != nil {
   422  		return fmt.Errorf("closing meta data temp file: %v", err)
   423  	}
   424  
   425  	// Temp file has now been flushed and closed. Rename the temp to the
   426  	// final desired path.
   427  	if err := os.Rename(s.mftmp, s.mfname); err != nil {
   428  		return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.mfname, s.mftmp, err)
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  // needMetaDataFile returns TRUE if we need to emit a meta-data file
   435  // for this program run. It should be used only after
   436  // openOutputFiles() has been invoked.
   437  func (s *emitState) needMetaDataFile() bool {
   438  	return s.mf != nil
   439  }
   440  
   441  func writeMetaData(w io.Writer, metalist []rtcov.CovMetaBlob, cmode coverage.CounterMode, gran coverage.CounterGranularity, finalHash [16]byte) error {
   442  	mfw := encodemeta.NewCoverageMetaFileWriter("<io.Writer>", w)
   443  
   444  	var blobs [][]byte
   445  	for _, e := range metalist {
   446  		sd := unsafe.Slice(e.P, int(e.Len))
   447  		blobs = append(blobs, sd)
   448  	}
   449  	return mfw.Write(finalHash, blobs, cmode, gran)
   450  }
   451  
   452  func (s *emitState) VisitFuncs(f encodecounter.CounterVisitorFn) error {
   453  	var tcounters []uint32
   454  
   455  	rdCounters := func(actrs []atomic.Uint32, ctrs []uint32) []uint32 {
   456  		ctrs = ctrs[:0]
   457  		for i := range actrs {
   458  			ctrs = append(ctrs, actrs[i].Load())
   459  		}
   460  		return ctrs
   461  	}
   462  
   463  	dpkg := uint32(0)
   464  	for _, c := range s.counterlist {
   465  		sd := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(c.Counters)), int(c.Len))
   466  		for i := 0; i < len(sd); i++ {
   467  			// Skip ahead until the next non-zero value.
   468  			sdi := sd[i].Load()
   469  			if sdi == 0 {
   470  				continue
   471  			}
   472  
   473  			// We found a function that was executed.
   474  			nCtrs := sd[i+coverage.NumCtrsOffset].Load()
   475  			pkgId := sd[i+coverage.PkgIdOffset].Load()
   476  			funcId := sd[i+coverage.FuncIdOffset].Load()
   477  			cst := i + coverage.FirstCtrOffset
   478  			counters := sd[cst : cst+int(nCtrs)]
   479  
   480  			// Check to make sure that we have at least one live
   481  			// counter. See the implementation note in ClearCoverageCounters
   482  			// for a description of why this is needed.
   483  			isLive := false
   484  			for i := 0; i < len(counters); i++ {
   485  				if counters[i].Load() != 0 {
   486  					isLive = true
   487  					break
   488  				}
   489  			}
   490  			if !isLive {
   491  				// Skip this function.
   492  				i += coverage.FirstCtrOffset + int(nCtrs) - 1
   493  				continue
   494  			}
   495  
   496  			if s.debug {
   497  				if pkgId != dpkg {
   498  					dpkg = pkgId
   499  					fmt.Fprintf(os.Stderr, "\n=+= %d: pk=%d visit live fcn",
   500  						i, pkgId)
   501  				}
   502  				fmt.Fprintf(os.Stderr, " {i=%d F%d NC%d}", i, funcId, nCtrs)
   503  			}
   504  
   505  			// Vet and/or fix up package ID. A package ID of zero
   506  			// indicates that there is some new package X that is a
   507  			// runtime dependency, and this package has code that
   508  			// executes before its corresponding init package runs.
   509  			// This is a fatal error that we should only see during
   510  			// Go development (e.g. tip).
   511  			ipk := int32(pkgId)
   512  			if ipk == 0 {
   513  				fmt.Fprintf(os.Stderr, "\n")
   514  				reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
   515  			} else if ipk < 0 {
   516  				if newId, ok := s.pkgmap[int(ipk)]; ok {
   517  					pkgId = uint32(newId)
   518  				} else {
   519  					fmt.Fprintf(os.Stderr, "\n")
   520  					reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
   521  				}
   522  			} else {
   523  				// The package ID value stored in the counter array
   524  				// has 1 added to it (so as to preclude the
   525  				// possibility of a zero value ; see
   526  				// runtime.addCovMeta), so subtract off 1 here to form
   527  				// the real package ID.
   528  				pkgId--
   529  			}
   530  
   531  			tcounters = rdCounters(counters, tcounters)
   532  			if err := f(pkgId, funcId, tcounters); err != nil {
   533  				return err
   534  			}
   535  
   536  			// Skip over this function.
   537  			i += coverage.FirstCtrOffset + int(nCtrs) - 1
   538  		}
   539  		if s.debug {
   540  			fmt.Fprintf(os.Stderr, "\n")
   541  		}
   542  	}
   543  	return nil
   544  }
   545  
   546  // captureOsArgs converts os.Args() into the format we use to store
   547  // this info in the counter data file (counter data file "args"
   548  // section is a generic key-value collection). See the 'args' section
   549  // in internal/coverage/defs.go for more info. The args map
   550  // is also used to capture GOOS + GOARCH values as well.
   551  func captureOsArgs() map[string]string {
   552  	m := make(map[string]string)
   553  	m["argc"] = strconv.Itoa(len(os.Args))
   554  	for k, a := range os.Args {
   555  		m[fmt.Sprintf("argv%d", k)] = a
   556  	}
   557  	m["GOOS"] = runtime.GOOS
   558  	m["GOARCH"] = runtime.GOARCH
   559  	return m
   560  }
   561  
   562  // emitCounterDataFile emits the counter data portion of a
   563  // coverage output file (to the file 's.cf').
   564  func (s *emitState) emitCounterDataFile(finalHash [16]byte, w io.Writer) error {
   565  	cfw := encodecounter.NewCoverageDataWriter(w, coverage.CtrULeb128)
   566  	if err := cfw.Write(finalHash, capturedOsArgs, s); err != nil {
   567  		return err
   568  	}
   569  	return nil
   570  }
   571  
   572  // MarkProfileEmitted signals the coverage machinery that
   573  // coverage data output files have already been written out, and there
   574  // is no need to take any additional action at exit time. This
   575  // function is called from the coverage-related boilerplate code in _testmain.go
   576  // emitted for go unit tests.
   577  func MarkProfileEmitted(val bool) {
   578  	covProfileAlreadyEmitted = val
   579  }
   580  
   581  func reportErrorInHardcodedList(slot, pkgID int32, fnID, nCtrs uint32) {
   582  	metaList := rtcov.Meta.List
   583  	pkgMap := rtcov.Meta.PkgMap
   584  
   585  	println("internal error in coverage meta-data tracking:")
   586  	println("encountered bad pkgID:", pkgID, " at slot:", slot,
   587  		" fnID:", fnID, " numCtrs:", nCtrs)
   588  	println("list of hard-coded runtime package IDs needs revising.")
   589  	println("[see the comment on the 'rtPkgs' var in ")
   590  	println(" <goroot>/src/internal/coverage/pkid.go]")
   591  	println("registered list:")
   592  	for k, b := range metaList {
   593  		print("slot: ", k, " path='", b.PkgPath, "' ")
   594  		if b.PkgID != -1 {
   595  			print(" hard-coded id: ", b.PkgID)
   596  		}
   597  		println("")
   598  	}
   599  	println("remap table:")
   600  	for from, to := range pkgMap {
   601  		println("from ", from, " to ", to)
   602  	}
   603  }
   604  

View as plain text