mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
[dev.regabi] cmd/compile: split out package dwarfgen [generated]
[git-generate]
cd src/cmd/compile/internal/gc
rf '
# Inline and remove ngotype.
ex {
import "cmd/compile/internal/ir"
import "cmd/compile/internal/reflectdata"
var n ir.Node
ngotype(n) -> reflectdata.TypeSym(n.Type())
}
rm ngotype
mv recordFlags RecordFlags
mv recordPackageName RecordPackageName
mv RecordFlags RecordPackageName dwarf.go
mv debuginfo Info
mv genAbstractFunc AbstractFunc
mv scope.go scope_test.go dwarf.go dwinl.go cmd/compile/internal/dwarfgen
'
Change-Id: I31fa982900dbba2066ca4c7a706af922e5481c70
Reviewed-on: https://go-review.googlesource.com/c/go/+/279477
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
parent
6c34d2f420
commit
01fd2d05c8
7 changed files with 94 additions and 94 deletions
483
src/cmd/compile/internal/dwarfgen/dwarf.go
Normal file
483
src/cmd/compile/internal/dwarfgen/dwarf.go
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
// Copyright 2011 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 dwarfgen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"cmd/compile/internal/base"
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/compile/internal/reflectdata"
|
||||
"cmd/compile/internal/ssa"
|
||||
"cmd/compile/internal/ssagen"
|
||||
"cmd/compile/internal/types"
|
||||
"cmd/internal/dwarf"
|
||||
"cmd/internal/obj"
|
||||
"cmd/internal/objabi"
|
||||
"cmd/internal/src"
|
||||
)
|
||||
|
||||
func Info(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) {
|
||||
fn := curfn.(*ir.Func)
|
||||
|
||||
if fn.Nname != nil {
|
||||
expect := fn.Sym().Linksym()
|
||||
if fnsym.ABI() == obj.ABI0 {
|
||||
expect = fn.Sym().LinksymABI0()
|
||||
}
|
||||
if fnsym != expect {
|
||||
base.Fatalf("unexpected fnsym: %v != %v", fnsym, expect)
|
||||
}
|
||||
}
|
||||
|
||||
// Back when there were two different *Funcs for a function, this code
|
||||
// was not consistent about whether a particular *Node being processed
|
||||
// was an ODCLFUNC or ONAME node. Partly this is because inlined function
|
||||
// bodies have no ODCLFUNC node, which was it's own inconsistency.
|
||||
// In any event, the handling of the two different nodes for DWARF purposes
|
||||
// was subtly different, likely in unintended ways. CL 272253 merged the
|
||||
// two nodes' Func fields, so that code sees the same *Func whether it is
|
||||
// holding the ODCLFUNC or the ONAME. This resulted in changes in the
|
||||
// DWARF output. To preserve the existing DWARF output and leave an
|
||||
// intentional change for a future CL, this code does the following when
|
||||
// fn.Op == ONAME:
|
||||
//
|
||||
// 1. Disallow use of createComplexVars in createDwarfVars.
|
||||
// It was not possible to reach that code for an ONAME before,
|
||||
// because the DebugInfo was set only on the ODCLFUNC Func.
|
||||
// Calling into it in the ONAME case causes an index out of bounds panic.
|
||||
//
|
||||
// 2. Do not populate apdecls. fn.Func.Dcl was in the ODCLFUNC Func,
|
||||
// not the ONAME Func. Populating apdecls for the ONAME case results
|
||||
// in selected being populated after createSimpleVars is called in
|
||||
// createDwarfVars, and then that causes the loop to skip all the entries
|
||||
// in dcl, meaning that the RecordAutoType calls don't happen.
|
||||
//
|
||||
// These two adjustments keep toolstash -cmp working for now.
|
||||
// Deciding the right answer is, as they say, future work.
|
||||
//
|
||||
// We can tell the difference between the old ODCLFUNC and ONAME
|
||||
// cases by looking at the infosym.Name. If it's empty, DebugInfo is
|
||||
// being called from (*obj.Link).populateDWARF, which used to use
|
||||
// the ODCLFUNC. If it's non-empty (the name will end in $abstract),
|
||||
// DebugInfo is being called from (*obj.Link).DwarfAbstractFunc,
|
||||
// which used to use the ONAME form.
|
||||
isODCLFUNC := infosym.Name == ""
|
||||
|
||||
var apdecls []*ir.Name
|
||||
// Populate decls for fn.
|
||||
if isODCLFUNC {
|
||||
for _, n := range fn.Dcl {
|
||||
if n.Op() != ir.ONAME { // might be OTYPE or OLITERAL
|
||||
continue
|
||||
}
|
||||
switch n.Class_ {
|
||||
case ir.PAUTO:
|
||||
if !n.Used() {
|
||||
// Text == nil -> generating abstract function
|
||||
if fnsym.Func().Text != nil {
|
||||
base.Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)")
|
||||
}
|
||||
continue
|
||||
}
|
||||
case ir.PPARAM, ir.PPARAMOUT:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
apdecls = append(apdecls, n)
|
||||
fnsym.Func().RecordAutoType(reflectdata.TypeSym(n.Type()).Linksym())
|
||||
}
|
||||
}
|
||||
|
||||
decls, dwarfVars := createDwarfVars(fnsym, isODCLFUNC, fn, apdecls)
|
||||
|
||||
// For each type referenced by the functions auto vars but not
|
||||
// already referenced by a dwarf var, attach an R_USETYPE relocation to
|
||||
// the function symbol to insure that the type included in DWARF
|
||||
// processing during linking.
|
||||
typesyms := []*obj.LSym{}
|
||||
for t, _ := range fnsym.Func().Autot {
|
||||
typesyms = append(typesyms, t)
|
||||
}
|
||||
sort.Sort(obj.BySymName(typesyms))
|
||||
for _, sym := range typesyms {
|
||||
r := obj.Addrel(infosym)
|
||||
r.Sym = sym
|
||||
r.Type = objabi.R_USETYPE
|
||||
}
|
||||
fnsym.Func().Autot = nil
|
||||
|
||||
var varScopes []ir.ScopeID
|
||||
for _, decl := range decls {
|
||||
pos := declPos(decl)
|
||||
varScopes = append(varScopes, findScope(fn.Marks, pos))
|
||||
}
|
||||
|
||||
scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes)
|
||||
var inlcalls dwarf.InlCalls
|
||||
if base.Flag.GenDwarfInl > 0 {
|
||||
inlcalls = assembleInlines(fnsym, dwarfVars)
|
||||
}
|
||||
return scopes, inlcalls
|
||||
}
|
||||
|
||||
func declPos(decl *ir.Name) src.XPos {
|
||||
if decl.Name().Defn != nil && (decl.Name().Captured() || decl.Name().Byval()) {
|
||||
// It's not clear which position is correct for captured variables here:
|
||||
// * decl.Pos is the wrong position for captured variables, in the inner
|
||||
// function, but it is the right position in the outer function.
|
||||
// * decl.Name.Defn is nil for captured variables that were arguments
|
||||
// on the outer function, however the decl.Pos for those seems to be
|
||||
// correct.
|
||||
// * decl.Name.Defn is the "wrong" thing for variables declared in the
|
||||
// header of a type switch, it's their position in the header, rather
|
||||
// than the position of the case statement. In principle this is the
|
||||
// right thing, but here we prefer the latter because it makes each
|
||||
// instance of the header variable local to the lexical block of its
|
||||
// case statement.
|
||||
// This code is probably wrong for type switch variables that are also
|
||||
// captured.
|
||||
return decl.Name().Defn.Pos()
|
||||
}
|
||||
return decl.Pos()
|
||||
}
|
||||
|
||||
// createDwarfVars process fn, returning a list of DWARF variables and the
|
||||
// Nodes they represent.
|
||||
func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var) {
|
||||
// Collect a raw list of DWARF vars.
|
||||
var vars []*dwarf.Var
|
||||
var decls []*ir.Name
|
||||
var selected map[*ir.Name]bool
|
||||
if base.Ctxt.Flag_locationlists && base.Ctxt.Flag_optimize && fn.DebugInfo != nil && complexOK {
|
||||
decls, vars, selected = createComplexVars(fnsym, fn)
|
||||
} else {
|
||||
decls, vars, selected = createSimpleVars(fnsym, apDecls)
|
||||
}
|
||||
|
||||
dcl := apDecls
|
||||
if fnsym.WasInlined() {
|
||||
dcl = preInliningDcls(fnsym)
|
||||
}
|
||||
|
||||
// If optimization is enabled, the list above will typically be
|
||||
// missing some of the original pre-optimization variables in the
|
||||
// function (they may have been promoted to registers, folded into
|
||||
// constants, dead-coded away, etc). Input arguments not eligible
|
||||
// for SSA optimization are also missing. Here we add back in entries
|
||||
// for selected missing vars. Note that the recipe below creates a
|
||||
// conservative location. The idea here is that we want to
|
||||
// communicate to the user that "yes, there is a variable named X
|
||||
// in this function, but no, I don't have enough information to
|
||||
// reliably report its contents."
|
||||
// For non-SSA-able arguments, however, the correct information
|
||||
// is known -- they have a single home on the stack.
|
||||
for _, n := range dcl {
|
||||
if _, found := selected[n]; found {
|
||||
continue
|
||||
}
|
||||
c := n.Sym().Name[0]
|
||||
if c == '.' || n.Type().IsUntyped() {
|
||||
continue
|
||||
}
|
||||
if n.Class_ == ir.PPARAM && !ssagen.TypeOK(n.Type()) {
|
||||
// SSA-able args get location lists, and may move in and
|
||||
// out of registers, so those are handled elsewhere.
|
||||
// Autos and named output params seem to get handled
|
||||
// with VARDEF, which creates location lists.
|
||||
// Args not of SSA-able type are treated here; they
|
||||
// are homed on the stack in a single place for the
|
||||
// entire call.
|
||||
vars = append(vars, createSimpleVar(fnsym, n))
|
||||
decls = append(decls, n)
|
||||
continue
|
||||
}
|
||||
typename := dwarf.InfoPrefix + types.TypeSymName(n.Type())
|
||||
decls = append(decls, n)
|
||||
abbrev := dwarf.DW_ABRV_AUTO_LOCLIST
|
||||
isReturnValue := (n.Class_ == ir.PPARAMOUT)
|
||||
if n.Class_ == ir.PPARAM || n.Class_ == ir.PPARAMOUT {
|
||||
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
|
||||
} else if n.Class_ == ir.PAUTOHEAP {
|
||||
// If dcl in question has been promoted to heap, do a bit
|
||||
// of extra work to recover original class (auto or param);
|
||||
// see issue 30908. This insures that we get the proper
|
||||
// signature in the abstract function DIE, but leaves a
|
||||
// misleading location for the param (we want pointer-to-heap
|
||||
// and not stack).
|
||||
// TODO(thanm): generate a better location expression
|
||||
stackcopy := n.Name().Stackcopy
|
||||
if stackcopy != nil && (stackcopy.Class_ == ir.PPARAM || stackcopy.Class_ == ir.PPARAMOUT) {
|
||||
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
|
||||
isReturnValue = (stackcopy.Class_ == ir.PPARAMOUT)
|
||||
}
|
||||
}
|
||||
inlIndex := 0
|
||||
if base.Flag.GenDwarfInl > 1 {
|
||||
if n.Name().InlFormal() || n.Name().InlLocal() {
|
||||
inlIndex = posInlIndex(n.Pos()) + 1
|
||||
if n.Name().InlFormal() {
|
||||
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
|
||||
}
|
||||
}
|
||||
}
|
||||
declpos := base.Ctxt.InnermostPos(n.Pos())
|
||||
vars = append(vars, &dwarf.Var{
|
||||
Name: n.Sym().Name,
|
||||
IsReturnValue: isReturnValue,
|
||||
Abbrev: abbrev,
|
||||
StackOffset: int32(n.FrameOffset()),
|
||||
Type: base.Ctxt.Lookup(typename),
|
||||
DeclFile: declpos.RelFilename(),
|
||||
DeclLine: declpos.RelLine(),
|
||||
DeclCol: declpos.Col(),
|
||||
InlIndex: int32(inlIndex),
|
||||
ChildIndex: -1,
|
||||
})
|
||||
// Record go type of to insure that it gets emitted by the linker.
|
||||
fnsym.Func().RecordAutoType(reflectdata.TypeSym(n.Type()).Linksym())
|
||||
}
|
||||
|
||||
return decls, vars
|
||||
}
|
||||
|
||||
// Given a function that was inlined at some point during the
|
||||
// compilation, return a sorted list of nodes corresponding to the
|
||||
// autos/locals in that function prior to inlining. If this is a
|
||||
// function that is not local to the package being compiled, then the
|
||||
// names of the variables may have been "versioned" to avoid conflicts
|
||||
// with local vars; disregard this versioning when sorting.
|
||||
func preInliningDcls(fnsym *obj.LSym) []*ir.Name {
|
||||
fn := base.Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*ir.Func)
|
||||
var rdcl []*ir.Name
|
||||
for _, n := range fn.Inl.Dcl {
|
||||
c := n.Sym().Name[0]
|
||||
// Avoid reporting "_" parameters, since if there are more than
|
||||
// one, it can result in a collision later on, as in #23179.
|
||||
if unversion(n.Sym().Name) == "_" || c == '.' || n.Type().IsUntyped() {
|
||||
continue
|
||||
}
|
||||
rdcl = append(rdcl, n)
|
||||
}
|
||||
return rdcl
|
||||
}
|
||||
|
||||
// createSimpleVars creates a DWARF entry for every variable declared in the
|
||||
// function, claiming that they are permanently on the stack.
|
||||
func createSimpleVars(fnsym *obj.LSym, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, map[*ir.Name]bool) {
|
||||
var vars []*dwarf.Var
|
||||
var decls []*ir.Name
|
||||
selected := make(map[*ir.Name]bool)
|
||||
for _, n := range apDecls {
|
||||
if ir.IsAutoTmp(n) {
|
||||
continue
|
||||
}
|
||||
|
||||
decls = append(decls, n)
|
||||
vars = append(vars, createSimpleVar(fnsym, n))
|
||||
selected[n] = true
|
||||
}
|
||||
return decls, vars, selected
|
||||
}
|
||||
|
||||
func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
|
||||
var abbrev int
|
||||
var offs int64
|
||||
|
||||
switch n.Class_ {
|
||||
case ir.PAUTO:
|
||||
offs = n.FrameOffset()
|
||||
abbrev = dwarf.DW_ABRV_AUTO
|
||||
if base.Ctxt.FixedFrameSize() == 0 {
|
||||
offs -= int64(types.PtrSize)
|
||||
}
|
||||
if objabi.Framepointer_enabled || objabi.GOARCH == "arm64" {
|
||||
// There is a word space for FP on ARM64 even if the frame pointer is disabled
|
||||
offs -= int64(types.PtrSize)
|
||||
}
|
||||
|
||||
case ir.PPARAM, ir.PPARAMOUT:
|
||||
abbrev = dwarf.DW_ABRV_PARAM
|
||||
offs = n.FrameOffset() + base.Ctxt.FixedFrameSize()
|
||||
default:
|
||||
base.Fatalf("createSimpleVar unexpected class %v for node %v", n.Class_, n)
|
||||
}
|
||||
|
||||
typename := dwarf.InfoPrefix + types.TypeSymName(n.Type())
|
||||
delete(fnsym.Func().Autot, reflectdata.TypeSym(n.Type()).Linksym())
|
||||
inlIndex := 0
|
||||
if base.Flag.GenDwarfInl > 1 {
|
||||
if n.Name().InlFormal() || n.Name().InlLocal() {
|
||||
inlIndex = posInlIndex(n.Pos()) + 1
|
||||
if n.Name().InlFormal() {
|
||||
abbrev = dwarf.DW_ABRV_PARAM
|
||||
}
|
||||
}
|
||||
}
|
||||
declpos := base.Ctxt.InnermostPos(declPos(n))
|
||||
return &dwarf.Var{
|
||||
Name: n.Sym().Name,
|
||||
IsReturnValue: n.Class_ == ir.PPARAMOUT,
|
||||
IsInlFormal: n.Name().InlFormal(),
|
||||
Abbrev: abbrev,
|
||||
StackOffset: int32(offs),
|
||||
Type: base.Ctxt.Lookup(typename),
|
||||
DeclFile: declpos.RelFilename(),
|
||||
DeclLine: declpos.RelLine(),
|
||||
DeclCol: declpos.Col(),
|
||||
InlIndex: int32(inlIndex),
|
||||
ChildIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// createComplexVars creates recomposed DWARF vars with location lists,
|
||||
// suitable for describing optimized code.
|
||||
func createComplexVars(fnsym *obj.LSym, fn *ir.Func) ([]*ir.Name, []*dwarf.Var, map[*ir.Name]bool) {
|
||||
debugInfo := fn.DebugInfo.(*ssa.FuncDebug)
|
||||
|
||||
// Produce a DWARF variable entry for each user variable.
|
||||
var decls []*ir.Name
|
||||
var vars []*dwarf.Var
|
||||
ssaVars := make(map[*ir.Name]bool)
|
||||
|
||||
for varID, dvar := range debugInfo.Vars {
|
||||
n := dvar
|
||||
ssaVars[n] = true
|
||||
for _, slot := range debugInfo.VarSlots[varID] {
|
||||
ssaVars[debugInfo.Slots[slot].N] = true
|
||||
}
|
||||
|
||||
if dvar := createComplexVar(fnsym, fn, ssa.VarID(varID)); dvar != nil {
|
||||
decls = append(decls, n)
|
||||
vars = append(vars, dvar)
|
||||
}
|
||||
}
|
||||
|
||||
return decls, vars, ssaVars
|
||||
}
|
||||
|
||||
// createComplexVar builds a single DWARF variable entry and location list.
|
||||
func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var {
|
||||
debug := fn.DebugInfo.(*ssa.FuncDebug)
|
||||
n := debug.Vars[varID]
|
||||
|
||||
var abbrev int
|
||||
switch n.Class_ {
|
||||
case ir.PAUTO:
|
||||
abbrev = dwarf.DW_ABRV_AUTO_LOCLIST
|
||||
case ir.PPARAM, ir.PPARAMOUT:
|
||||
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
gotype := reflectdata.TypeSym(n.Type()).Linksym()
|
||||
delete(fnsym.Func().Autot, gotype)
|
||||
typename := dwarf.InfoPrefix + gotype.Name[len("type."):]
|
||||
inlIndex := 0
|
||||
if base.Flag.GenDwarfInl > 1 {
|
||||
if n.Name().InlFormal() || n.Name().InlLocal() {
|
||||
inlIndex = posInlIndex(n.Pos()) + 1
|
||||
if n.Name().InlFormal() {
|
||||
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
|
||||
}
|
||||
}
|
||||
}
|
||||
declpos := base.Ctxt.InnermostPos(n.Pos())
|
||||
dvar := &dwarf.Var{
|
||||
Name: n.Sym().Name,
|
||||
IsReturnValue: n.Class_ == ir.PPARAMOUT,
|
||||
IsInlFormal: n.Name().InlFormal(),
|
||||
Abbrev: abbrev,
|
||||
Type: base.Ctxt.Lookup(typename),
|
||||
// The stack offset is used as a sorting key, so for decomposed
|
||||
// variables just give it the first one. It's not used otherwise.
|
||||
// This won't work well if the first slot hasn't been assigned a stack
|
||||
// location, but it's not obvious how to do better.
|
||||
StackOffset: ssagen.StackOffset(debug.Slots[debug.VarSlots[varID][0]]),
|
||||
DeclFile: declpos.RelFilename(),
|
||||
DeclLine: declpos.RelLine(),
|
||||
DeclCol: declpos.Col(),
|
||||
InlIndex: int32(inlIndex),
|
||||
ChildIndex: -1,
|
||||
}
|
||||
list := debug.LocationLists[varID]
|
||||
if len(list) != 0 {
|
||||
dvar.PutLocationList = func(listSym, startPC dwarf.Sym) {
|
||||
debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym))
|
||||
}
|
||||
}
|
||||
return dvar
|
||||
}
|
||||
|
||||
// RecordFlags records the specified command-line flags to be placed
|
||||
// in the DWARF info.
|
||||
func RecordFlags(flags ...string) {
|
||||
if base.Ctxt.Pkgpath == "" {
|
||||
// We can't record the flags if we don't know what the
|
||||
// package name is.
|
||||
return
|
||||
}
|
||||
|
||||
type BoolFlag interface {
|
||||
IsBoolFlag() bool
|
||||
}
|
||||
type CountFlag interface {
|
||||
IsCountFlag() bool
|
||||
}
|
||||
var cmd bytes.Buffer
|
||||
for _, name := range flags {
|
||||
f := flag.Lookup(name)
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
getter := f.Value.(flag.Getter)
|
||||
if getter.String() == f.DefValue {
|
||||
// Flag has default value, so omit it.
|
||||
continue
|
||||
}
|
||||
if bf, ok := f.Value.(BoolFlag); ok && bf.IsBoolFlag() {
|
||||
val, ok := getter.Get().(bool)
|
||||
if ok && val {
|
||||
fmt.Fprintf(&cmd, " -%s", f.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if cf, ok := f.Value.(CountFlag); ok && cf.IsCountFlag() {
|
||||
val, ok := getter.Get().(int)
|
||||
if ok && val == 1 {
|
||||
fmt.Fprintf(&cmd, " -%s", f.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get())
|
||||
}
|
||||
|
||||
if cmd.Len() == 0 {
|
||||
return
|
||||
}
|
||||
s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "producer." + base.Ctxt.Pkgpath)
|
||||
s.Type = objabi.SDWARFCUINFO
|
||||
// Sometimes (for example when building tests) we can link
|
||||
// together two package main archives. So allow dups.
|
||||
s.Set(obj.AttrDuplicateOK, true)
|
||||
base.Ctxt.Data = append(base.Ctxt.Data, s)
|
||||
s.P = cmd.Bytes()[1:]
|
||||
}
|
||||
|
||||
// RecordPackageName records the name of the package being
|
||||
// compiled, so that the linker can save it in the compile unit's DIE.
|
||||
func RecordPackageName() {
|
||||
s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "packagename." + base.Ctxt.Pkgpath)
|
||||
s.Type = objabi.SDWARFCUINFO
|
||||
// Sometimes (for example when building tests) we can link
|
||||
// together two package main archives. So allow dups.
|
||||
s.Set(obj.AttrDuplicateOK, true)
|
||||
base.Ctxt.Data = append(base.Ctxt.Data, s)
|
||||
s.P = []byte(types.LocalPkg.Name)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue