Update to go1.24.3
This commit is contained in:
4
VERSION
4
VERSION
@@ -1,2 +1,2 @@
|
||||
go1.24.2
|
||||
time 2025-03-26T19:09:39Z
|
||||
go1.24.3
|
||||
time 2025-04-30T18:13:34Z
|
||||
|
||||
@@ -27,6 +27,7 @@ func test42018(t *testing.T) {
|
||||
recurseHWND(400, hwnd, uintptr(unsafe.Pointer(&i)))
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func recurseHANDLE(n int, p C.HANDLE, v uintptr) {
|
||||
if n > 0 {
|
||||
recurseHANDLE(n-1, p, v)
|
||||
@@ -36,6 +37,7 @@ func recurseHANDLE(n int, p C.HANDLE, v uintptr) {
|
||||
}
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func recurseHWND(n int, p C.HWND, v uintptr) {
|
||||
if n > 0 {
|
||||
recurseHWND(n-1, p, v)
|
||||
|
||||
@@ -170,19 +170,8 @@ func CanInlineFuncs(funcs []*ir.Func, profile *pgoir.Profile) {
|
||||
}
|
||||
|
||||
ir.VisitFuncsBottomUp(funcs, func(funcs []*ir.Func, recursive bool) {
|
||||
numfns := numNonClosures(funcs)
|
||||
|
||||
for _, fn := range funcs {
|
||||
if !recursive || numfns > 1 {
|
||||
// We allow inlining if there is no
|
||||
// recursion, or the recursion cycle is
|
||||
// across more than one function.
|
||||
CanInline(fn, profile)
|
||||
} else {
|
||||
if base.Flag.LowerM > 1 && fn.OClosure == nil {
|
||||
fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(fn), fn.Nname)
|
||||
}
|
||||
}
|
||||
CanInline(fn, profile)
|
||||
if inlheur.Enabled() {
|
||||
analyzeFuncProps(fn, profile)
|
||||
}
|
||||
@@ -1023,68 +1012,6 @@ func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCa
|
||||
}
|
||||
}
|
||||
|
||||
if callee == callerfn {
|
||||
// Can't recursively inline a function into itself.
|
||||
if log && logopt.Enabled() {
|
||||
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(callerfn)))
|
||||
}
|
||||
return false, 0, false
|
||||
}
|
||||
|
||||
isClosureParent := func(closure, parent *ir.Func) bool {
|
||||
for p := closure.ClosureParent; p != nil; p = p.ClosureParent {
|
||||
if p == parent {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if isClosureParent(callerfn, callee) {
|
||||
// Can't recursively inline a parent of the closure into itself.
|
||||
if log && logopt.Enabled() {
|
||||
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to closure parent: %s, %s", ir.FuncName(callerfn), ir.FuncName(callee)))
|
||||
}
|
||||
return false, 0, false
|
||||
}
|
||||
if isClosureParent(callee, callerfn) {
|
||||
// Can't recursively inline a closure if there's a call to the parent in closure body.
|
||||
if ir.Any(callee, func(node ir.Node) bool {
|
||||
if call, ok := node.(*ir.CallExpr); ok {
|
||||
if name, ok := call.Fun.(*ir.Name); ok && isClosureParent(callerfn, name.Func) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
if log && logopt.Enabled() {
|
||||
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to closure parent: %s, %s", ir.FuncName(callerfn), ir.FuncName(callee)))
|
||||
}
|
||||
return false, 0, false
|
||||
}
|
||||
}
|
||||
do := func(fn *ir.Func) bool {
|
||||
// Can't recursively inline a function if the function body contains
|
||||
// a call to a function f, which the function f is one of the call arguments.
|
||||
return ir.Any(fn, func(node ir.Node) bool {
|
||||
if call, ok := node.(*ir.CallExpr); ok {
|
||||
for _, arg := range call.Args {
|
||||
if call.Fun == arg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
for _, fn := range []*ir.Func{callerfn, callee} {
|
||||
if do(fn) {
|
||||
if log && logopt.Enabled() {
|
||||
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to function: %s", ir.FuncName(fn)))
|
||||
}
|
||||
return false, 0, false
|
||||
}
|
||||
}
|
||||
|
||||
if base.Flag.Cfg.Instrumenting && types.IsNoInstrumentPkg(callee.Sym().Pkg) {
|
||||
// Runtime package must not be instrumented.
|
||||
// Instrument skips runtime package. However, some runtime code can be
|
||||
|
||||
@@ -2631,7 +2631,12 @@ func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) {
|
||||
vers := revInfo.Version
|
||||
if vers != "" {
|
||||
if st.Uncommitted {
|
||||
vers += "+dirty"
|
||||
// SemVer build metadata is dot-separated https://semver.org/#spec-item-10
|
||||
if strings.HasSuffix(vers, "+incompatible") {
|
||||
vers += ".dirty"
|
||||
} else {
|
||||
vers += "+dirty"
|
||||
}
|
||||
}
|
||||
info.Main.Version = vers
|
||||
}
|
||||
|
||||
@@ -108,6 +108,19 @@ go version -m example$GOEXE
|
||||
stdout '\s+mod\s+example\s+v1.0.3-0.20220719150703-2e239bf29c13\s+'
|
||||
rm example$GOEXE
|
||||
|
||||
# Create +incompatible module
|
||||
exec git checkout v1.0.4
|
||||
exec git rm go.mod
|
||||
exec git commit -m 'commit 6'
|
||||
exec git tag v2.0.0
|
||||
exec git checkout HEAD^ go.mod
|
||||
# And make the tree +dirty
|
||||
mv README4 README5
|
||||
go build
|
||||
go version -m example$GOEXE
|
||||
stdout '\s+mod\s+example\s+v2.0.0\+incompatible.dirty\s+'
|
||||
rm example$GOEXE
|
||||
|
||||
-- $WORK/repo/go.mod --
|
||||
module example
|
||||
|
||||
|
||||
@@ -1006,9 +1006,10 @@ func genWasmExportWrapper(s *obj.LSym, appendp func(p *obj.Prog, as obj.As, args
|
||||
// In the unwinding case, we call wasm_pc_f_loop_export to handle stack switch and rewinding,
|
||||
// until a normal return (non-unwinding) back to this function.
|
||||
p = appendp(p, AIf)
|
||||
p = appendp(p, AI32Const, retAddr)
|
||||
p = appendp(p, AI32Const, constAddr(16))
|
||||
p = appendp(p, AI32ShrU)
|
||||
p = appendp(p, AI64Const, retAddr)
|
||||
p = appendp(p, AI64Const, constAddr(16))
|
||||
p = appendp(p, AI64ShrU)
|
||||
p = appendp(p, AI32WrapI64)
|
||||
p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: wasm_pc_f_loop_export})
|
||||
p = appendp(p, AEnd)
|
||||
|
||||
|
||||
@@ -432,16 +432,16 @@ func (st *loadState) addSym(name string, ver int, r *oReader, li uint32, kind in
|
||||
return i
|
||||
}
|
||||
// symbol already exists
|
||||
// Fix for issue #47185 -- given two dupok or BSS symbols with
|
||||
// different sizes, favor symbol with larger size. See also
|
||||
// issue #46653 and #72032.
|
||||
oldsz := l.SymSize(oldi)
|
||||
sz := int64(r.Sym(li).Siz())
|
||||
if osym.Dupok() {
|
||||
if l.flags&FlagStrictDups != 0 {
|
||||
l.checkdup(name, r, li, oldi)
|
||||
}
|
||||
// Fix for issue #47185 -- given two dupok symbols with
|
||||
// different sizes, favor symbol with larger size. See
|
||||
// also issue #46653.
|
||||
szdup := l.SymSize(oldi)
|
||||
sz := int64(r.Sym(li).Siz())
|
||||
if szdup < sz {
|
||||
if oldsz < sz {
|
||||
// new symbol overwrites old symbol.
|
||||
l.objSyms[oldi] = objSym{r.objidx, li}
|
||||
}
|
||||
@@ -452,11 +452,24 @@ func (st *loadState) addSym(name string, ver int, r *oReader, li uint32, kind in
|
||||
if oldsym.Dupok() {
|
||||
return oldi
|
||||
}
|
||||
overwrite := r.DataSize(li) != 0
|
||||
// If one is a DATA symbol (i.e. has content, DataSize != 0)
|
||||
// and the other is BSS, the one with content wins.
|
||||
// If both are BSS, the one with larger size wins.
|
||||
// Specifically, the "overwrite" variable and the final result are
|
||||
//
|
||||
// new sym old sym overwrite
|
||||
// ---------------------------------------------
|
||||
// DATA DATA true => ERROR
|
||||
// DATA lg/eq BSS sm/eq true => new wins
|
||||
// DATA small BSS large true => ERROR
|
||||
// BSS large DATA small true => ERROR
|
||||
// BSS large BSS small true => new wins
|
||||
// BSS sm/eq D/B lg/eq false => old wins
|
||||
overwrite := r.DataSize(li) != 0 || oldsz < sz
|
||||
if overwrite {
|
||||
// new symbol overwrites old symbol.
|
||||
oldtyp := sym.AbiSymKindToSymKind[objabi.SymKind(oldsym.Type())]
|
||||
if !(oldtyp.IsData() && oldr.DataSize(oldli) == 0) {
|
||||
if !(oldtyp.IsData() && oldr.DataSize(oldli) == 0) || oldsz > sz {
|
||||
log.Fatalf("duplicated definition of symbol %s, from %s and %s", name, r.unit.Lib.Pkg, oldr.unit.Lib.Pkg)
|
||||
}
|
||||
l.objSyms[oldi] = objSym{r.objidx, li}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"testing"
|
||||
|
||||
imacho "cmd/internal/macho"
|
||||
"cmd/internal/objfile"
|
||||
"cmd/internal/sys"
|
||||
)
|
||||
|
||||
@@ -1541,3 +1542,53 @@ func TestCheckLinkname(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinknameBSS(t *testing.T) {
|
||||
// Test that the linker chooses the right one as the definition
|
||||
// for linknamed variables. See issue #72032.
|
||||
testenv.MustHaveGoBuild(t)
|
||||
t.Parallel()
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
src := filepath.Join("testdata", "linkname", "sched.go")
|
||||
exe := filepath.Join(tmpdir, "sched.exe")
|
||||
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("build failed unexpectedly: %v:\n%s", err, out)
|
||||
}
|
||||
|
||||
// Check the symbol size.
|
||||
f, err := objfile.Open(exe)
|
||||
if err != nil {
|
||||
t.Fatalf("fail to open executable: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
syms, err := f.Symbols()
|
||||
if err != nil {
|
||||
t.Fatalf("fail to get symbols: %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, s := range syms {
|
||||
if s.Name == "runtime.sched" || s.Name == "_runtime.sched" {
|
||||
found = true
|
||||
if s.Size < 100 {
|
||||
// As of Go 1.25 (Mar 2025), runtime.sched has 6848 bytes on
|
||||
// darwin/arm64. It should always be larger than 100 bytes on
|
||||
// all platforms.
|
||||
t.Errorf("runtime.sched symbol size too small: want > 100, got %d", s.Size)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("runtime.sched symbol not found")
|
||||
}
|
||||
|
||||
// Executable should run.
|
||||
cmd = testenv.Command(t, exe)
|
||||
out, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Errorf("executable failed to run: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
19
src/cmd/link/testdata/linkname/sched.go
vendored
Normal file
19
src/cmd/link/testdata/linkname/sched.go
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2025 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 main
|
||||
|
||||
import _ "unsafe"
|
||||
|
||||
type schedt struct{}
|
||||
|
||||
//go:linkname sched runtime.sched
|
||||
var sched schedt
|
||||
|
||||
func main() {
|
||||
select {
|
||||
default:
|
||||
println("hello")
|
||||
}
|
||||
}
|
||||
@@ -381,8 +381,28 @@ func decodeInnerClientHello(outer *clientHelloMsg, encoded []byte) (*clientHello
|
||||
return nil, errInvalidECHExt
|
||||
}
|
||||
|
||||
if len(inner.supportedVersions) != 1 || (len(inner.supportedVersions) >= 1 && inner.supportedVersions[0] != VersionTLS13) {
|
||||
return nil, errors.New("tls: client sent encrypted_client_hello extension and offered incompatible versions")
|
||||
hasTLS13 := false
|
||||
for _, v := range inner.supportedVersions {
|
||||
// Skip GREASE values (values of the form 0x?A0A).
|
||||
// GREASE (Generate Random Extensions And Sustain Extensibility) is a mechanism used by
|
||||
// browsers like Chrome to ensure TLS implementations correctly ignore unknown values.
|
||||
// GREASE values follow a specific pattern: 0x?A0A, where ? can be any hex digit.
|
||||
// These values should be ignored when processing supported TLS versions.
|
||||
if v&0x0F0F == 0x0A0A && v&0xff == v>>8 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure at least TLS 1.3 is offered.
|
||||
if v == VersionTLS13 {
|
||||
hasTLS13 = true
|
||||
} else if v < VersionTLS13 {
|
||||
// Reject if any non-GREASE value is below TLS 1.3, as ECH requires TLS 1.3+.
|
||||
return nil, errors.New("tls: client sent encrypted_client_hello extension with unsupported versions")
|
||||
}
|
||||
}
|
||||
|
||||
if !hasTLS13 {
|
||||
return nil, errors.New("tls: client sent encrypted_client_hello extension but did not offer TLS 1.3")
|
||||
}
|
||||
|
||||
return inner, nil
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
func runtime_mapaccess1_fast32(typ *abi.SwissMapType, m *Map, key uint32) unsafe.Pointer {
|
||||
if race.Enabled && m != nil {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess1)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess1_fast32)
|
||||
race.ReadPC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func runtime_mapaccess1_fast32(typ *abi.SwissMapType, m *Map, key uint32) unsafe
|
||||
func runtime_mapaccess2_fast32(typ *abi.SwissMapType, m *Map, key uint32) (unsafe.Pointer, bool) {
|
||||
if race.Enabled && m != nil {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess1)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess2_fast32)
|
||||
race.ReadPC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ func runtime_mapassign_fast32(typ *abi.SwissMapType, m *Map, key uint32) unsafe.
|
||||
}
|
||||
if race.Enabled {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign_fast32)
|
||||
race.WritePC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
if m.writing != 0 {
|
||||
@@ -332,7 +332,7 @@ func runtime_mapassign_fast32ptr(typ *abi.SwissMapType, m *Map, key unsafe.Point
|
||||
}
|
||||
if race.Enabled {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign_fast32ptr)
|
||||
race.WritePC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
if m.writing != 0 {
|
||||
@@ -458,7 +458,7 @@ outer:
|
||||
func runtime_mapdelete_fast32(typ *abi.SwissMapType, m *Map, key uint32) {
|
||||
if race.Enabled {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapdelete_fast32)
|
||||
race.WritePC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
func runtime_mapaccess1_fast64(typ *abi.SwissMapType, m *Map, key uint64) unsafe.Pointer {
|
||||
if race.Enabled && m != nil {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess1)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess1_fast64)
|
||||
race.ReadPC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func runtime_mapaccess1_fast64(typ *abi.SwissMapType, m *Map, key uint64) unsafe
|
||||
func runtime_mapaccess2_fast64(typ *abi.SwissMapType, m *Map, key uint64) (unsafe.Pointer, bool) {
|
||||
if race.Enabled && m != nil {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess1)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess2_fast64)
|
||||
race.ReadPC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ func runtime_mapassign_fast64(typ *abi.SwissMapType, m *Map, key uint64) unsafe.
|
||||
}
|
||||
if race.Enabled {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign_fast64)
|
||||
race.WritePC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
if m.writing != 0 {
|
||||
@@ -370,7 +370,7 @@ func runtime_mapassign_fast64ptr(typ *abi.SwissMapType, m *Map, key unsafe.Point
|
||||
}
|
||||
if race.Enabled {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign_fast64ptr)
|
||||
race.WritePC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
if m.writing != 0 {
|
||||
@@ -497,7 +497,7 @@ outer:
|
||||
func runtime_mapdelete_fast64(typ *abi.SwissMapType, m *Map, key uint64) {
|
||||
if race.Enabled {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapdelete_fast64)
|
||||
race.WritePC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ func stringPtr(s string) unsafe.Pointer {
|
||||
func runtime_mapaccess1_faststr(typ *abi.SwissMapType, m *Map, key string) unsafe.Pointer {
|
||||
if race.Enabled && m != nil {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess1)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess1_faststr)
|
||||
race.ReadPC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func runtime_mapaccess1_faststr(typ *abi.SwissMapType, m *Map, key string) unsaf
|
||||
func runtime_mapaccess2_faststr(typ *abi.SwissMapType, m *Map, key string) (unsafe.Pointer, bool) {
|
||||
if race.Enabled && m != nil {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess1)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapaccess2_faststr)
|
||||
race.ReadPC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ func runtime_mapassign_faststr(typ *abi.SwissMapType, m *Map, key string) unsafe
|
||||
}
|
||||
if race.Enabled {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign_faststr)
|
||||
race.WritePC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
if m.writing != 0 {
|
||||
@@ -396,7 +396,7 @@ outer:
|
||||
func runtime_mapdelete_faststr(typ *abi.SwissMapType, m *Map, key string) {
|
||||
if race.Enabled {
|
||||
callerpc := sys.GetCallerPC()
|
||||
pc := abi.FuncPCABIInternal(runtime_mapassign)
|
||||
pc := abi.FuncPCABIInternal(runtime_mapdelete_faststr)
|
||||
race.WritePC(unsafe.Pointer(m), callerpc, pc)
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ func Mkdirat(dirfd int, path string, mode uint32) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_mkdirat_trampoline), uintptr(dirfd), uintptr(unsafe.Pointer(p)), 0, 0, 0, 0)
|
||||
_, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_mkdirat_trampoline), uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(mode), 0, 0, 0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
|
||||
@@ -186,20 +186,20 @@ func (r *Root) logStat(name string) {
|
||||
//
|
||||
// "." components are removed, except in the last component.
|
||||
//
|
||||
// Path separators following the last component are preserved.
|
||||
func splitPathInRoot(s string, prefix, suffix []string) (_ []string, err error) {
|
||||
// Path separators following the last component are returned in suffixSep.
|
||||
func splitPathInRoot(s string, prefix, suffix []string) (_ []string, suffixSep string, err error) {
|
||||
if len(s) == 0 {
|
||||
return nil, errors.New("empty path")
|
||||
return nil, "", errors.New("empty path")
|
||||
}
|
||||
if IsPathSeparator(s[0]) {
|
||||
return nil, errPathEscapes
|
||||
return nil, "", errPathEscapes
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows cleans paths before opening them.
|
||||
s, err = rootCleanPath(s, prefix, suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
prefix = nil
|
||||
suffix = nil
|
||||
@@ -215,13 +215,14 @@ func splitPathInRoot(s string, prefix, suffix []string) (_ []string, err error)
|
||||
}
|
||||
parts = append(parts, s[i:j])
|
||||
// Advance to the next component, or end of the path.
|
||||
partEnd := j
|
||||
for j < len(s) && IsPathSeparator(s[j]) {
|
||||
j++
|
||||
}
|
||||
if j == len(s) {
|
||||
// If this is the last path component,
|
||||
// preserve any trailing path separators.
|
||||
parts[len(parts)-1] = s[i:]
|
||||
suffixSep = s[partEnd:]
|
||||
break
|
||||
}
|
||||
if parts[len(parts)-1] == "." {
|
||||
@@ -235,7 +236,7 @@ func splitPathInRoot(s string, prefix, suffix []string) (_ []string, err error)
|
||||
parts = parts[:len(parts)-1]
|
||||
}
|
||||
parts = append(parts, suffix...)
|
||||
return parts, nil
|
||||
return parts, suffixSep, nil
|
||||
}
|
||||
|
||||
// FS returns a file system (an fs.FS) for the tree of files in the root.
|
||||
|
||||
@@ -33,7 +33,7 @@ func checkPathEscapesInternal(r *Root, name string, lstat bool) error {
|
||||
if r.root.closed.Load() {
|
||||
return ErrClosed
|
||||
}
|
||||
parts, err := splitPathInRoot(name, nil, nil)
|
||||
parts, suffixSep, err := splitPathInRoot(name, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,11 +61,15 @@ func checkPathEscapesInternal(r *Root, name string, lstat bool) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if lstat && i == len(parts)-1 {
|
||||
break
|
||||
part := parts[i]
|
||||
if i == len(parts)-1 {
|
||||
if lstat {
|
||||
break
|
||||
}
|
||||
part += suffixSep
|
||||
}
|
||||
|
||||
next := joinPath(base, parts[i])
|
||||
next := joinPath(base, part)
|
||||
fi, err := Lstat(next)
|
||||
if err != nil {
|
||||
if IsNotExist(err) {
|
||||
@@ -82,10 +86,19 @@ func checkPathEscapesInternal(r *Root, name string, lstat bool) error {
|
||||
if symlinks > rootMaxSymlinks {
|
||||
return errors.New("too many symlinks")
|
||||
}
|
||||
newparts, err := splitPathInRoot(link, parts[:i], parts[i+1:])
|
||||
newparts, newSuffixSep, err := splitPathInRoot(link, parts[:i], parts[i+1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i == len(parts) {
|
||||
// suffixSep contains any trailing path separator characters
|
||||
// in the link target.
|
||||
// If we are replacing the remainder of the path, retain these.
|
||||
// If we're replacing some intermediate component of the path,
|
||||
// ignore them, since intermediate components must always be
|
||||
// directories.
|
||||
suffixSep = newSuffixSep
|
||||
}
|
||||
parts = newparts
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string)
|
||||
}
|
||||
defer r.root.decref()
|
||||
|
||||
parts, err := splitPathInRoot(name, nil, nil)
|
||||
parts, suffixSep, err := splitPathInRoot(name, nil, nil)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
@@ -146,6 +146,9 @@ func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string)
|
||||
return ret, errPathEscapes
|
||||
}
|
||||
parts = slices.Delete(parts, i-count, end)
|
||||
if len(parts) == 0 {
|
||||
parts = []string{"."}
|
||||
}
|
||||
i = 0
|
||||
if dirfd != rootfd {
|
||||
syscall.Close(dirfd)
|
||||
@@ -159,7 +162,9 @@ func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string)
|
||||
// Call f to decide what to do with it.
|
||||
// If f returns errSymlink, this element is a symlink
|
||||
// which should be followed.
|
||||
ret, err = f(dirfd, parts[i])
|
||||
// suffixSep contains any trailing separator characters
|
||||
// which we rejoin to the final part at this time.
|
||||
ret, err = f(dirfd, parts[i]+suffixSep)
|
||||
if _, ok := err.(errSymlink); !ok {
|
||||
return ret, err
|
||||
}
|
||||
@@ -181,10 +186,19 @@ func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string)
|
||||
if symlinks > rootMaxSymlinks {
|
||||
return ret, syscall.ELOOP
|
||||
}
|
||||
newparts, err := splitPathInRoot(string(e), parts[:i], parts[i+1:])
|
||||
newparts, newSuffixSep, err := splitPathInRoot(string(e), parts[:i], parts[i+1:])
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
if i == len(parts)-1 {
|
||||
// suffixSep contains any trailing path separator characters
|
||||
// in the link target.
|
||||
// If we are replacing the remainder of the path, retain these.
|
||||
// If we're replacing some intermediate component of the path,
|
||||
// ignore them, since intermediate components must always be
|
||||
// directories.
|
||||
suffixSep = newSuffixSep
|
||||
}
|
||||
if len(newparts) < i || !slices.Equal(parts[:i], newparts[:i]) {
|
||||
// Some component in the path which we have already traversed
|
||||
// has changed. We need to restart parsing from the root.
|
||||
|
||||
@@ -186,6 +186,30 @@ var rootTestCases = []rootTest{{
|
||||
open: "link",
|
||||
target: "target",
|
||||
ltarget: "link",
|
||||
}, {
|
||||
name: "symlink dotdot slash",
|
||||
fs: []string{
|
||||
"link => ../",
|
||||
},
|
||||
open: "link",
|
||||
ltarget: "link",
|
||||
wantError: true,
|
||||
}, {
|
||||
name: "symlink ending in slash",
|
||||
fs: []string{
|
||||
"dir/",
|
||||
"link => dir/",
|
||||
},
|
||||
open: "link/target",
|
||||
target: "dir/target",
|
||||
}, {
|
||||
name: "symlink dotdot dotdot slash",
|
||||
fs: []string{
|
||||
"dir/link => ../../",
|
||||
},
|
||||
open: "dir/link",
|
||||
ltarget: "dir/link",
|
||||
wantError: true,
|
||||
}, {
|
||||
name: "symlink chain",
|
||||
fs: []string{
|
||||
@@ -213,6 +237,16 @@ var rootTestCases = []rootTest{{
|
||||
},
|
||||
open: "a/../a/b/../../a/b/../b/target",
|
||||
target: "a/b/target",
|
||||
}, {
|
||||
name: "path with dotdot slash",
|
||||
fs: []string{},
|
||||
open: "../",
|
||||
wantError: true,
|
||||
}, {
|
||||
name: "path with dotdot dotdot slash",
|
||||
fs: []string{},
|
||||
open: "a/../../",
|
||||
wantError: true,
|
||||
}, {
|
||||
name: "dotdot no symlink",
|
||||
fs: []string{
|
||||
@@ -413,6 +447,12 @@ func TestRootMkdir(t *testing.T) {
|
||||
if !fi.IsDir() {
|
||||
t.Fatalf(`stat file created with Root.Mkdir(%q): not a directory`, test.open)
|
||||
}
|
||||
if mode := fi.Mode(); mode&0o777 == 0 {
|
||||
// Issue #73559: We're not going to worry about the exact
|
||||
// mode bits (which will have been modified by umask),
|
||||
// but there should be mode bits.
|
||||
t.Fatalf(`stat file created with Root.Mkdir(%q): mode=%v, want non-zero`, test.open, mode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1176,6 +1216,33 @@ func TestRootRaceRenameDir(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootSymlinkToRoot(t *testing.T) {
|
||||
dir := makefs(t, []string{
|
||||
"d/d => ..",
|
||||
})
|
||||
root, err := os.OpenRoot(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer root.Close()
|
||||
if err := root.Mkdir("d/d/new", 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := root.Open("d/d")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
names, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
slices.Sort(names)
|
||||
if got, want := names, []string{"d", "new"}; !slices.Equal(got, want) {
|
||||
t.Errorf("root contains: %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenInRoot(t *testing.T) {
|
||||
dir := makefs(t, []string{
|
||||
"file",
|
||||
|
||||
@@ -234,8 +234,11 @@ func unminit() {
|
||||
getg().m.procid = 0
|
||||
}
|
||||
|
||||
// Called from exitm, but not from drop, to undo the effect of thread-owned
|
||||
// Called from mexit, but not from dropm, to undo the effect of thread-owned
|
||||
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
|
||||
//
|
||||
// This always runs without a P, so //go:nowritebarrierrec is required.
|
||||
//go:nowritebarrierrec
|
||||
func mdestroy(mp *m) {
|
||||
}
|
||||
|
||||
|
||||
@@ -186,8 +186,11 @@ func unminit() {
|
||||
getg().m.procid = 0
|
||||
}
|
||||
|
||||
// Called from exitm, but not from drop, to undo the effect of thread-owned
|
||||
// Called from mexit, but not from dropm, to undo the effect of thread-owned
|
||||
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
|
||||
//
|
||||
// This always runs without a P, so //go:nowritebarrierrec is required.
|
||||
//go:nowritebarrierrec
|
||||
func mdestroy(mp *m) {
|
||||
}
|
||||
|
||||
|
||||
@@ -344,8 +344,11 @@ func unminit() {
|
||||
getg().m.procid = 0
|
||||
}
|
||||
|
||||
// Called from exitm, but not from drop, to undo the effect of thread-owned
|
||||
// Called from mexit, but not from dropm, to undo the effect of thread-owned
|
||||
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
|
||||
//
|
||||
// This always runs without a P, so //go:nowritebarrierrec is required.
|
||||
//go:nowritebarrierrec
|
||||
func mdestroy(mp *m) {
|
||||
}
|
||||
|
||||
|
||||
@@ -216,8 +216,11 @@ func unminit() {
|
||||
getg().m.procid = 0
|
||||
}
|
||||
|
||||
// Called from exitm, but not from drop, to undo the effect of thread-owned
|
||||
// Called from mexit, but not from dropm, to undo the effect of thread-owned
|
||||
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
|
||||
//
|
||||
// This always runs without a P, so //go:nowritebarrierrec is required.
|
||||
//go:nowritebarrierrec
|
||||
func mdestroy(mp *m) {
|
||||
}
|
||||
|
||||
|
||||
@@ -412,13 +412,12 @@ func unminit() {
|
||||
getg().m.procid = 0
|
||||
}
|
||||
|
||||
// Called from exitm, but not from drop, to undo the effect of thread-owned
|
||||
// Called from mexit, but not from dropm, to undo the effect of thread-owned
|
||||
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
|
||||
//
|
||||
// This always runs without a P, so //go:nowritebarrierrec is required.
|
||||
//go:nowritebarrierrec
|
||||
func mdestroy(mp *m) {
|
||||
if mp.vgetrandomState != 0 {
|
||||
vgetrandomPutState(mp.vgetrandomState)
|
||||
mp.vgetrandomState = 0
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef GOARCH_386
|
||||
|
||||
@@ -320,8 +320,11 @@ func unminit() {
|
||||
// must continue working after unminit.
|
||||
}
|
||||
|
||||
// Called from exitm, but not from drop, to undo the effect of thread-owned
|
||||
// Called from mexit, but not from dropm, to undo the effect of thread-owned
|
||||
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
|
||||
//
|
||||
// This always runs without a P, so //go:nowritebarrierrec is required.
|
||||
//go:nowritebarrierrec
|
||||
func mdestroy(mp *m) {
|
||||
}
|
||||
|
||||
|
||||
@@ -182,8 +182,11 @@ func unminit() {
|
||||
getg().m.procid = 0
|
||||
}
|
||||
|
||||
// Called from exitm, but not from drop, to undo the effect of thread-owned
|
||||
// Called from mexit, but not from dropm, to undo the effect of thread-owned
|
||||
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
|
||||
//
|
||||
// This always runs without a P, so //go:nowritebarrierrec is required.
|
||||
//go:nowritebarrierrec
|
||||
func mdestroy(mp *m) {
|
||||
}
|
||||
|
||||
|
||||
@@ -217,8 +217,11 @@ func minit() {
|
||||
func unminit() {
|
||||
}
|
||||
|
||||
// Called from exitm, but not from drop, to undo the effect of thread-owned
|
||||
// Called from mexit, but not from dropm, to undo the effect of thread-owned
|
||||
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
|
||||
//
|
||||
// This always runs without a P, so //go:nowritebarrierrec is required.
|
||||
//go:nowritebarrierrec
|
||||
func mdestroy(mp *m) {
|
||||
}
|
||||
|
||||
|
||||
@@ -970,9 +970,11 @@ func unminit() {
|
||||
mp.procid = 0
|
||||
}
|
||||
|
||||
// Called from exitm, but not from drop, to undo the effect of thread-owned
|
||||
// Called from mexit, but not from dropm, to undo the effect of thread-owned
|
||||
// resources in minit, semacreate, or elsewhere. Do not take locks after calling this.
|
||||
//
|
||||
// This always runs without a P, so //go:nowritebarrierrec is required.
|
||||
//go:nowritebarrierrec
|
||||
//go:nosplit
|
||||
func mdestroy(mp *m) {
|
||||
if mp.highResTimer != 0 {
|
||||
|
||||
@@ -73,7 +73,10 @@ func TestConvertCPUProfileNoSamples(t *testing.T) {
|
||||
checkProfile(t, p, 2000*1000, periodType, sampleType, nil, "")
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func f1() { f1() }
|
||||
|
||||
//go:noinline
|
||||
func f2() { f2() }
|
||||
|
||||
// testPCs returns two PCs and two corresponding memory mappings
|
||||
|
||||
@@ -1935,6 +1935,9 @@ func mexit(osStack bool) {
|
||||
mp.gsignal = nil
|
||||
}
|
||||
|
||||
// Free vgetrandom state.
|
||||
vgetrandomDestroy(mp)
|
||||
|
||||
// Remove m from allm.
|
||||
lock(&sched.lock)
|
||||
for pprev := &allm; *pprev != nil; pprev = &(*pprev).alllink {
|
||||
|
||||
@@ -745,7 +745,7 @@ ok:
|
||||
//
|
||||
// syscall9 expects a 32-bit result and tests for 32-bit -1
|
||||
// to decide there was an error.
|
||||
TEXT runtime·syscall9(SB),NOSPLIT,$16
|
||||
TEXT runtime·syscall9(SB),NOSPLIT,$32
|
||||
MOVQ (0*8)(DI), R13// fn
|
||||
MOVQ (2*8)(DI), SI // a2
|
||||
MOVQ (3*8)(DI), DX // a3
|
||||
@@ -753,15 +753,18 @@ TEXT runtime·syscall9(SB),NOSPLIT,$16
|
||||
MOVQ (5*8)(DI), R8 // a5
|
||||
MOVQ (6*8)(DI), R9 // a6
|
||||
MOVQ (7*8)(DI), R10 // a7
|
||||
MOVQ R10, 0(SP)
|
||||
MOVQ (8*8)(DI), R11 // a8
|
||||
MOVQ R11, 8(SP)
|
||||
MOVQ (9*8)(DI), R12 // a9
|
||||
MOVQ DI, (SP)
|
||||
MOVQ R12, 16(SP)
|
||||
MOVQ DI, 24(SP)
|
||||
MOVQ (1*8)(DI), DI // a1
|
||||
XORL AX, AX // vararg: say "no float args"
|
||||
|
||||
CALL R13
|
||||
|
||||
MOVQ (SP), DI
|
||||
MOVQ 24(SP), DI
|
||||
MOVQ AX, (10*8)(DI) // r1
|
||||
MOVQ DX, (11*8)(DI) // r2
|
||||
|
||||
@@ -770,7 +773,7 @@ TEXT runtime·syscall9(SB),NOSPLIT,$16
|
||||
|
||||
CALL libc_error(SB)
|
||||
MOVLQSX (AX), AX
|
||||
MOVQ (SP), DI
|
||||
MOVQ 24(SP), DI
|
||||
MOVQ AX, (12*8)(DI) // err
|
||||
|
||||
ok:
|
||||
|
||||
@@ -73,9 +73,16 @@ func vgetrandomGetState() uintptr {
|
||||
return state
|
||||
}
|
||||
|
||||
func vgetrandomPutState(state uintptr) {
|
||||
// Free vgetrandom state from the M (if any) prior to destroying the M.
|
||||
//
|
||||
// This may allocate, so it must have a P.
|
||||
func vgetrandomDestroy(mp *m) {
|
||||
if mp.vgetrandomState == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
lock(&vgetrandomAlloc.statesLock)
|
||||
vgetrandomAlloc.states = append(vgetrandomAlloc.states, state)
|
||||
vgetrandomAlloc.states = append(vgetrandomAlloc.states, mp.vgetrandomState)
|
||||
unlock(&vgetrandomAlloc.statesLock)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@ func vgetrandom(p []byte, flags uint32) (ret int, supported bool) {
|
||||
return -1, false
|
||||
}
|
||||
|
||||
func vgetrandomPutState(state uintptr) {}
|
||||
func vgetrandomDestroy(mp *m) {}
|
||||
|
||||
func vgetrandomInit() {}
|
||||
|
||||
@@ -30,6 +30,8 @@ func main() {
|
||||
// should not be adjusted when the stack is copied.
|
||||
recurse(100, p, v)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func recurse(n int, p *S, v uintptr) {
|
||||
if n > 0 {
|
||||
recurse(n-1, p, v)
|
||||
|
||||
@@ -11,14 +11,14 @@ package p
|
||||
|
||||
func f() { // ERROR "can inline f"
|
||||
var i interface{ m() } = T(0) // ERROR "T\(0\) does not escape"
|
||||
i.m() // ERROR "devirtualizing i.m" "inlining call to T.m"
|
||||
i.m() // ERROR "devirtualizing i.m" "inlining call to T.m" "inlining call to f" "T\(0\) does not escape"
|
||||
}
|
||||
|
||||
type T int
|
||||
|
||||
func (T) m() { // ERROR "can inline T.m"
|
||||
if never {
|
||||
f() // ERROR "inlining call to f" "devirtualizing i.m" "T\(0\) does not escape"
|
||||
f() // ERROR "inlining call to f" "devirtualizing i.m" "T\(0\) does not escape" "inlining call to T.m"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
package main
|
||||
|
||||
func run() { // ERROR "cannot inline run: recursive"
|
||||
//go:noinline
|
||||
func run() { // ERROR "cannot inline run: marked go:noinline"
|
||||
f := func() { // ERROR "can inline run.func1 with cost .* as:.*" "func literal does not escape"
|
||||
g() // ERROR "inlining call to g"
|
||||
}
|
||||
|
||||
@@ -280,13 +280,13 @@ func ff(x int) { // ERROR "can inline ff"
|
||||
if x < 0 {
|
||||
return
|
||||
}
|
||||
gg(x - 1) // ERROR "inlining call to gg" "inlining call to hh"
|
||||
gg(x - 1) // ERROR "inlining call to gg" "inlining call to hh" "inlining call to ff"
|
||||
}
|
||||
func gg(x int) { // ERROR "can inline gg"
|
||||
hh(x - 1) // ERROR "inlining call to hh" "inlining call to ff"
|
||||
hh(x - 1) // ERROR "inlining call to hh" "inlining call to ff" "inlining call to gg"
|
||||
}
|
||||
func hh(x int) { // ERROR "can inline hh"
|
||||
ff(x - 1) // ERROR "inlining call to ff" "inlining call to gg"
|
||||
ff(x - 1) // ERROR "inlining call to ff" "inlining call to gg" "inlining call to hh"
|
||||
}
|
||||
|
||||
// Issue #14768 - make sure we can inline for loops.
|
||||
@@ -332,9 +332,9 @@ func ii() { // ERROR "can inline ii"
|
||||
// Issue #42194 - make sure that functions evaluated in
|
||||
// go and defer statements can be inlined.
|
||||
func gd1(int) {
|
||||
defer gd1(gd2()) // ERROR "inlining call to gd2"
|
||||
defer gd1(gd2()) // ERROR "inlining call to gd2" "can inline gd1.deferwrap1"
|
||||
defer gd3()() // ERROR "inlining call to gd3"
|
||||
go gd1(gd2()) // ERROR "inlining call to gd2"
|
||||
go gd1(gd2()) // ERROR "inlining call to gd2" "can inline gd1.gowrap2"
|
||||
go gd3()() // ERROR "inlining call to gd3"
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ func f5_int8(a [10]int) int {
|
||||
return x
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func f6(a []int) {
|
||||
for i := range a { // ERROR "Induction variable: limits \[0,\?\), increment 1$"
|
||||
b := a[0:i] // ERROR "(\([0-9]+\) )?Proved IsSliceInBounds$"
|
||||
|
||||
@@ -280,13 +280,13 @@ func ff(x int) { // ERROR "can inline ff"
|
||||
if x < 0 {
|
||||
return
|
||||
}
|
||||
gg(x - 1) // ERROR "inlining call to gg" "inlining call to hh"
|
||||
gg(x - 1) // ERROR "inlining call to gg" "inlining call to hh" "inlining call to ff"
|
||||
}
|
||||
func gg(x int) { // ERROR "can inline gg"
|
||||
hh(x - 1) // ERROR "inlining call to hh" "inlining call to ff"
|
||||
hh(x - 1) // ERROR "inlining call to hh" "inlining call to ff" "inlining call to gg"
|
||||
}
|
||||
func hh(x int) { // ERROR "can inline hh"
|
||||
ff(x - 1) // ERROR "inlining call to ff" "inlining call to gg"
|
||||
ff(x - 1) // ERROR "inlining call to ff" "inlining call to gg" "inlining call to hh"
|
||||
}
|
||||
|
||||
// Issue #14768 - make sure we can inline for loops.
|
||||
|
||||
Reference in New Issue
Block a user