lsym.SymIdx = int32(idx)
lsym.Set(obj.AttrIndexed, true)
} else {
- name.Sym().Linkname = r.String()
+ linkname := r.String()
+ sym := name.Sym()
+ sym.Linkname = linkname
+ if sym.Pkg == types.LocalPkg && linkname != "" {
+ // Mark linkname in the current package. We don't mark the
+ // ones that are imported and propagated (e.g. through
+ // inlining or instantiation, which are marked in their
+ // corresponding packages). So we can tell in which package
+ // the linkname is used (pulled), and the linker can
+ // make a decision for allowing or disallowing it.
+ sym.Linksym().Set(obj.AttrLinkname, true)
+ }
}
}
SymFlagItab
SymFlagDict
SymFlagPkgInit
+ SymFlagLinkname
)
// Returns the length of the name of the symbol.
func (s *Sym) IsItab() bool { return s.Flag2()&SymFlagItab != 0 }
func (s *Sym) IsDict() bool { return s.Flag2()&SymFlagDict != 0 }
func (s *Sym) IsPkgInit() bool { return s.Flag2()&SymFlagPkgInit != 0 }
+func (s *Sym) IsLinkname() bool { return s.Flag2()&SymFlagLinkname != 0 }
func (s *Sym) SetName(x string, w *Writer) {
binary.LittleEndian.PutUint32(s[:], uint32(len(x)))
// PkgInit indicates this is a compiler-generated package init func.
AttrPkgInit
+ // Linkname indicates this is a go:linkname'd symbol.
+ AttrLinkname
+
// attrABIBase is the value at which the ABI is encoded in
// Attribute. This must be last; all bits after this are
// assumed to be an ABI value.
func (a *Attribute) ABIWrapper() bool { return a.load()&AttrABIWrapper != 0 }
func (a *Attribute) IsPcdata() bool { return a.load()&AttrPcdata != 0 }
func (a *Attribute) IsPkgInit() bool { return a.load()&AttrPkgInit != 0 }
+func (a *Attribute) IsLinkname() bool { return a.load()&AttrLinkname != 0 }
func (a *Attribute) Set(flag Attribute, value bool) {
for {
{bit: AttrContentAddressable, s: ""},
{bit: AttrABIWrapper, s: "ABIWRAPPER"},
{bit: AttrPkgInit, s: "PKGINIT"},
+ {bit: AttrLinkname, s: "LINKNAME"},
}
// String formats a for printing in as part of a TEXT prog.
if s.IsPkgInit() {
flag2 |= goobj.SymFlagPkgInit
}
+ if s.IsLinkname() || w.ctxt.IsAsm { // assembly reference is treated the same as linkname
+ flag2 |= goobj.SymFlagLinkname
+ }
name := s.Name
if strings.HasPrefix(name, "gofile..") {
name = filepath.ToSlash(name)
return i
}
- // Non-package (named) symbol. Check if it already exists.
+ // Non-package (named) symbol.
+ if osym.IsLinkname() && r.DataSize(li) == 0 {
+ // This is a linknamed "var" "reference" (var x T with no data and //go:linkname x).
+ // Check if a linkname reference is allowed.
+ // Only check references (pull), not definitions (push, with non-zero size),
+ // so push is always allowed.
+ // Linkname is always a non-package reference.
+ checkLinkname(r.unit.Lib.Pkg, name)
+ }
+ // Check if it already exists.
oldi, existed := l.symsByName[ver][name]
if !existed {
l.symsByName[ver][name] = i
osym := r.Sym(ndef + i)
name := osym.Name(r.Reader)
v := abiToVer(osym.ABI(), r.version)
+ if osym.IsLinkname() {
+ // Check if a linkname reference is allowed.
+ // Only check references (pull), not definitions (push),
+ // so push is always allowed.
+ // Linkname is always a non-package reference.
+ checkLinkname(r.unit.Lib.Pkg, name)
+ }
r.syms[ndef+i] = l.LookupOrCreateSym(name, v)
gi := r.syms[ndef+i]
if osym.Local() {
return v
}
+// A list of blocked linknames. Some linknames are allowed only
+// in specific packages. This maps symbol names to allowed packages.
+// If a name is not in this map, and not with a blocked prefix (see
+// blockedLinknamePrefixes), it is allowed everywhere.
+// If a name is in this map, it is allowed only in listed packages.
+var blockedLinknames = map[string][]string{
+ // coroutines
+ "runtime.coroexit": nil,
+ "runtime.corostart": nil,
+ "runtime.coroswitch": {"iter"},
+ "runtime.newcoro": {"iter"},
+ // weak references
+ "internal/weak.runtime_registerWeakPointer": {"internal/weak"},
+ "internal/weak.runtime_makeStrongFromWeak": {"internal/weak"},
+ "runtime.getOrAddWeakHandle": nil,
+}
+
+// A list of blocked linkname prefixes (packages).
+var blockedLinknamePrefixes = []string{
+ "internal/weak.",
+ "internal/concurrent.",
+}
+
+func checkLinkname(pkg, name string) {
+ error := func() {
+ log.Fatalf("linkname or assembly reference of %s is not allowed in package %s", name, pkg)
+ }
+ pkgs, ok := blockedLinknames[name]
+ if ok {
+ for _, p := range pkgs {
+ if pkg == p {
+ return // pkg is allowed
+ }
+ }
+ error()
+ }
+ for _, p := range blockedLinknamePrefixes {
+ if strings.HasPrefix(name, p) {
+ error()
+ }
+ }
+}
+
// TopLevelSym tests a symbol (by name and kind) to determine whether
// the symbol first class sym (participating in the link) or is an
// anonymous aux or sub-symbol containing some sub-part or payload of
t.Errorf("randlayout with different seeds produced same layout:\n%s\n===\n\n%s", syms[0], syms[1])
}
}
+
+func TestBlockedLinkname(t *testing.T) {
+ // Test that code containing blocked linknames does not build.
+ testenv.MustHaveGoBuild(t)
+ t.Parallel()
+
+ tmpdir := t.TempDir()
+
+ tests := []struct {
+ src string
+ ok bool
+ }{
+ // use (instantiation) of public API is ok
+ {"ok.go", true},
+ // push linkname is ok
+ {"push.go", true},
+ // pull linkname of blocked symbol is not ok
+ {"coro.go", false},
+ {"weak.go", false},
+ {"coro_var.go", false},
+ // assembly reference is not ok
+ {"coro_asm", false},
+ }
+ for _, test := range tests {
+ test := test
+ t.Run(test.src, func(t *testing.T) {
+ t.Parallel()
+ src := filepath.Join("testdata", "linkname", test.src)
+ exe := filepath.Join(tmpdir, test.src+".exe")
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src)
+ out, err := cmd.CombinedOutput()
+ if test.ok && err != nil {
+ t.Errorf("build failed unexpectedly: %v:\n%s", err, out)
+ }
+ if !test.ok && err == nil {
+ t.Errorf("build succeeded unexpectedly: %v:\n%s", err, out)
+ }
+ })
+ }
+}
--- /dev/null
+// Copyright 2024 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.
+
+// Linkname coroswitch is not allowed, even if iter.Pull
+// is instantiated in the same package.
+
+package main
+
+import (
+ "iter"
+ "unsafe"
+)
+
+func seq(yield func(int) bool) {
+ yield(123)
+}
+
+func main() {
+ next, stop := iter.Pull(seq)
+ next()
+ stop()
+ coroswitch(nil)
+}
+
+//go:linkname coroswitch runtime.coroswitch
+func coroswitch(unsafe.Pointer)
--- /dev/null
+// Copyright 2024 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.
+
+TEXT ·newcoro(SB),0,$0-0
+ CALL runtime·newcoro(SB)
+ RET
--- /dev/null
+// Copyright 2024 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.
+
+// Assembly reference of newcoro is not allowed.
+
+package main
+
+func main() {
+ newcoro()
+}
+
+func newcoro()
--- /dev/null
+// Copyright 2024 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.
+
+// Linkname "var" to reference newcoro is not allowed.
+
+package main
+
+import "unsafe"
+
+func main() {
+ call(&newcoro)
+}
+
+//go:linkname newcoro runtime.newcoro
+var newcoro unsafe.Pointer
+
+//go:noinline
+func call(*unsafe.Pointer) {
+ // not implemented
+}
--- /dev/null
+// Copyright 2024 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.
+
+// Use of public API is ok.
+
+package main
+
+import (
+ "iter"
+ "unique"
+)
+
+func seq(yield func(int) bool) {
+ yield(123)
+}
+
+var s = "hello"
+
+func main() {
+ h := unique.Make(s)
+ next, stop := iter.Pull(seq)
+ defer stop()
+ println(h.Value())
+ println(next())
+ println(next())
+}
--- /dev/null
+// Copyright 2024 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 p
+
+import _ "unsafe"
+
+// f1 is pushed from main.
+//
+//go:linkname f1
+func f1()
+
+// Push f2 to main.
+//
+//go:linkname f2 main.f2
+func f2() {}
+
+func F() { f1() }
--- /dev/null
+// Copyright 2024 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.
+
+// "Push" linknames are ok.
+
+package main
+
+import (
+ "cmd/link/testdata/linkname/p"
+ _ "unsafe"
+)
+
+// Push f1 to p.
+//
+//go:linkname f1 cmd/link/testdata/linkname/p.f1
+func f1() { f2() }
+
+// f2 is pushed from p.
+//
+//go:linkname f2
+func f2()
+
+func main() {
+ p.F()
+}
--- /dev/null
+// Copyright 2024 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.
+
+// Linkname generic functions in internal/weak is not
+// allowed; legitimate instantiation is ok.
+
+package main
+
+import (
+ "unique"
+ "unsafe"
+)
+
+//go:linkname weakMake internal/weak.Make[string]
+func weakMake(string) unsafe.Pointer
+
+func main() {
+ h := unique.Make("xxx")
+ println(h.Value())
+ weakMake("xxx")
+}