Update to go1.25.4

This commit is contained in:
Vorapol Rinsatitnon
2025-11-09 15:54:43 +07:00
parent 5abfd5beda
commit de7123beea
20 changed files with 368 additions and 446 deletions

View File

@@ -1,2 +1,2 @@
go1.25.3
time 2025-10-13T16:08:43Z
go1.25.4
time 2025-10-31T13:24:27Z

View File

@@ -49,9 +49,6 @@ type pkgReader struct {
// but bitwise inverted so we can detect if we're missing the entry
// or not.
newindex []index
// indicates whether the data is reading during reshaping.
reshaping bool
}
func newPkgReader(pr pkgbits.PkgDecoder) *pkgReader {
@@ -119,10 +116,6 @@ type reader struct {
// find parameters/results.
funarghack bool
// reshaping is used during reading exprReshape code, preventing
// the reader from shapifying the re-shaped type.
reshaping bool
// methodSym is the name of method's name, if reading a method.
// It's nil if reading a normal function or closure body.
methodSym *types.Sym
@@ -937,8 +930,19 @@ func shapify(targ *types.Type, basic bool) *types.Type {
// types, and discarding struct field names and tags. However, we'll
// need to start tracking how type parameters are actually used to
// implement some of these optimizations.
pointerShaping := basic && targ.IsPtr() && !targ.Elem().NotInHeap()
// The exception is when the type parameter is a pointer to a type
// which `Type.HasShape()` returns true, but `Type.IsShape()` returns
// false, like `*[]go.shape.T`. This is because the type parameter is
// used to instantiate a generic function inside another generic function.
// In this case, we want to keep the targ as-is, otherwise, we may lose the
// original type after `*[]go.shape.T` is shapified to `*go.shape.uint8`.
// See issue #54535, #71184.
if pointerShaping && !targ.Elem().IsShape() && targ.Elem().HasShape() {
return targ
}
under := targ.Underlying()
if basic && targ.IsPtr() && !targ.Elem().NotInHeap() {
if pointerShaping {
under = types.NewPtr(types.Types[types.TUINT8])
}
@@ -1014,25 +1018,7 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx index, implicits, explicits
// arguments.
for i, targ := range dict.targs {
basic := r.Bool()
isPointerShape := basic && targ.IsPtr() && !targ.Elem().NotInHeap()
// We should not do shapify during the reshaping process, see #71184.
// However, this only matters for shapify a pointer type, which will
// lose the original underlying type.
//
// Example with a pointer type:
//
// - First, shapifying *[]T -> *uint8
// - During the reshaping process, *uint8 is shapified to *go.shape.uint8
// - This ends up with a different type with the original *[]T
//
// For a non-pointer type:
//
// - int -> go.shape.int
// - go.shape.int -> go.shape.int
//
// We always end up with the identical type.
canShapify := !pr.reshaping || !isPointerShape
if dict.shaped && canShapify {
if dict.shaped {
dict.targs[i] = shapify(targ, basic)
}
}
@@ -2470,10 +2456,7 @@ func (r *reader) expr() (res ir.Node) {
case exprReshape:
typ := r.typ()
old := r.reshaping
r.reshaping = true
x := r.expr()
r.reshaping = old
if types.IdenticalStrict(x.Type(), typ) {
return x
@@ -2596,10 +2579,7 @@ func (r *reader) funcInst(pos src.XPos) (wrapperFn, baseFn, dictPtr ir.Node) {
info := r.dict.subdicts[idx]
explicits := r.p.typListIdx(info.explicits, r.dict)
old := r.p.reshaping
r.p.reshaping = r.reshaping
baseFn = r.p.objIdx(info.idx, implicits, explicits, true).(*ir.Name)
r.p.reshaping = old
// TODO(mdempsky): Is there a more robust way to get the
// dictionary pointer type here?

View File

@@ -2555,7 +2555,7 @@ func rewriteStructStore(v *Value) *Value {
// isDirectType reports whether v represents a type
// (a *runtime._type) whose value is stored directly in an
// interface (i.e., is pointer or pointer-like).
// interface (i.e., is pointer or pointer-like) and is comparable.
func isDirectType(v *Value) bool {
return isDirectType1(v)
}
@@ -2571,7 +2571,8 @@ func isDirectType1(v *Value) bool {
return false
}
if ti, ok := (*lsym.Extra).(*obj.TypeInfo); ok {
return types.IsDirectIface(ti.Type.(*types.Type))
t := ti.Type.(*types.Type)
return types.IsDirectIface(t) && types.IsComparable(t)
}
}
return false
@@ -2588,7 +2589,7 @@ func isDirectType2(v *Value) bool {
// isDirectIface reports whether v represents an itab
// (a *runtime._itab) for a type whose value is stored directly
// in an interface (i.e., is pointer or pointer-like).
// in an interface (i.e., is pointer or pointer-like) and is comparable.
func isDirectIface(v *Value) bool {
return isDirectIface1(v, 9)
}
@@ -2607,7 +2608,8 @@ func isDirectIface1(v *Value, depth int) bool {
return false
}
if ii, ok := (*lsym.Extra).(*obj.ItabInfo); ok {
return types.IsDirectIface(ii.Type.(*types.Type))
t := ii.Type.(*types.Type)
return types.IsDirectIface(t) && types.IsComparable(t)
}
case OpConstNil:
// We can treat this as direct, because if the itab is

View File

@@ -0,0 +1,78 @@
go build main.go
! stdout .
! stderr .
-- main.go --
package main
import (
"demo/registry"
)
func main() {
_ = registry.NewUserRegistry()
}
-- go.mod --
module demo
go 1.24
-- model/user.go --
package model
type User struct {
ID int
}
func (c *User) String() string {
return ""
}
-- ordered/map.go --
package ordered
type OrderedMap[K comparable, V any] struct {
m map[K]V
}
func New[K comparable, V any](options ...any) *OrderedMap[K, V] {
orderedMap := &OrderedMap[K, V]{}
return orderedMap
}
-- registry/user.go --
package registry
import (
"demo/model"
"demo/ordered"
)
type baseRegistry = Registry[model.User, *model.User]
type UserRegistry struct {
*baseRegistry
}
type Registry[T any, P PStringer[T]] struct {
m *ordered.OrderedMap[string, P]
}
type PStringer[T any] interface {
*T
String() string
}
func NewRegistry[T any, P PStringer[T]]() *Registry[T, P] {
r := &Registry[T, P]{
m: ordered.New[string, P](),
}
return r
}
func NewUserRegistry() *UserRegistry {
return &UserRegistry{
baseRegistry: NewRegistry[model.User](),
}
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (amd64 || arm64 || mips || mipsle || mips64 || mips64le || ppc64 || ppc64le || riscv64) && !purego
//go:build (amd64 || arm64 || ppc64 || ppc64le || riscv64) && !purego
package subtle

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (!amd64 && !arm64 && !loong64 && !mips && !mipsle && !mips64 && !mips64le && !ppc64 && !ppc64le && !riscv64) || purego
//go:build (!amd64 && !arm64 && !loong64 && !ppc64 && !ppc64le && !riscv64) || purego
package subtle

View File

@@ -1,153 +0,0 @@
// 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.
//go:build (mips64 || mips64le) && !purego
#include "textflag.h"
// func xorBytes(dst, a, b *byte, n int)
TEXT ·xorBytes(SB), NOSPLIT|NOFRAME, $0
MOVV dst+0(FP), R1
MOVV a+8(FP), R2
MOVV b+16(FP), R3
MOVV n+24(FP), R4
xor_64_check:
SGTU $64, R4, R5 // R5 = 1 if (64 > R4)
BNE R5, xor_32_check
xor_64:
MOVV (R2), R6
MOVV 8(R2), R7
MOVV 16(R2), R8
MOVV 24(R2), R9
MOVV (R3), R10
MOVV 8(R3), R11
MOVV 16(R3), R12
MOVV 24(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVV R10, (R1)
MOVV R11, 8(R1)
MOVV R12, 16(R1)
MOVV R13, 24(R1)
MOVV 32(R2), R6
MOVV 40(R2), R7
MOVV 48(R2), R8
MOVV 56(R2), R9
MOVV 32(R3), R10
MOVV 40(R3), R11
MOVV 48(R3), R12
MOVV 56(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVV R10, 32(R1)
MOVV R11, 40(R1)
MOVV R12, 48(R1)
MOVV R13, 56(R1)
ADDV $64, R2
ADDV $64, R3
ADDV $64, R1
SUBV $64, R4
SGTU $64, R4, R5
BEQ R0, R5, xor_64
BEQ R0, R4, end
xor_32_check:
SGTU $32, R4, R5
BNE R5, xor_16_check
xor_32:
MOVV (R2), R6
MOVV 8(R2), R7
MOVV 16(R2), R8
MOVV 24(R2), R9
MOVV (R3), R10
MOVV 8(R3), R11
MOVV 16(R3), R12
MOVV 24(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVV R10, (R1)
MOVV R11, 8(R1)
MOVV R12, 16(R1)
MOVV R13, 24(R1)
ADDV $32, R2
ADDV $32, R3
ADDV $32, R1
SUBV $32, R4
BEQ R0, R4, end
xor_16_check:
SGTU $16, R4, R5
BNE R5, xor_8_check
xor_16:
MOVV (R2), R6
MOVV 8(R2), R7
MOVV (R3), R8
MOVV 8(R3), R9
XOR R6, R8
XOR R7, R9
MOVV R8, (R1)
MOVV R9, 8(R1)
ADDV $16, R2
ADDV $16, R3
ADDV $16, R1
SUBV $16, R4
BEQ R0, R4, end
xor_8_check:
SGTU $8, R4, R5
BNE R5, xor_4_check
xor_8:
MOVV (R2), R6
MOVV (R3), R7
XOR R6, R7
MOVV R7, (R1)
ADDV $8, R1
ADDV $8, R2
ADDV $8, R3
SUBV $8, R4
BEQ R0, R4, end
xor_4_check:
SGTU $4, R4, R5
BNE R5, xor_2_check
xor_4:
MOVW (R2), R6
MOVW (R3), R7
XOR R6, R7
MOVW R7, (R1)
ADDV $4, R2
ADDV $4, R3
ADDV $4, R1
SUBV $4, R4
BEQ R0, R4, end
xor_2_check:
SGTU $2, R4, R5
BNE R5, xor_1
xor_2:
MOVH (R2), R6
MOVH (R3), R7
XOR R6, R7
MOVH R7, (R1)
ADDV $2, R2
ADDV $2, R3
ADDV $2, R1
SUBV $2, R4
BEQ R0, R4, end
xor_1:
MOVB (R2), R6
MOVB (R3), R7
XOR R6, R7
MOVB R7, (R1)
end:
RET

View File

@@ -1,212 +0,0 @@
// 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.
//go:build (mips || mipsle) && !purego
#include "textflag.h"
// func xorBytes(dst, a, b *byte, n int)
TEXT ·xorBytes(SB), NOSPLIT|NOFRAME, $0
MOVW dst+0(FP), R1
MOVW a+4(FP), R2
MOVW b+8(FP), R3
MOVW n+12(FP), R4
SGTU $64, R4, R5 // R5 = 1 if (64 > R4)
BNE R5, xor_32_check
xor_64:
MOVW (R2), R6
MOVW 4(R2), R7
MOVW 8(R2), R8
MOVW 12(R2), R9
MOVW (R3), R10
MOVW 4(R3), R11
MOVW 8(R3), R12
MOVW 12(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVW R10, (R1)
MOVW R11, 4(R1)
MOVW R12, 8(R1)
MOVW R13, 12(R1)
MOVW 16(R2), R6
MOVW 20(R2), R7
MOVW 24(R2), R8
MOVW 28(R2), R9
MOVW 16(R3), R10
MOVW 20(R3), R11
MOVW 24(R3), R12
MOVW 28(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVW R10, 16(R1)
MOVW R11, 20(R1)
MOVW R12, 24(R1)
MOVW R13, 28(R1)
MOVW 32(R2), R6
MOVW 36(R2), R7
MOVW 40(R2), R8
MOVW 44(R2), R9
MOVW 32(R3), R10
MOVW 36(R3), R11
MOVW 40(R3), R12
MOVW 44(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVW R10, 32(R1)
MOVW R11, 36(R1)
MOVW R12, 40(R1)
MOVW R13, 44(R1)
MOVW 48(R2), R6
MOVW 52(R2), R7
MOVW 56(R2), R8
MOVW 60(R2), R9
MOVW 48(R3), R10
MOVW 52(R3), R11
MOVW 56(R3), R12
MOVW 60(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVW R10, 48(R1)
MOVW R11, 52(R1)
MOVW R12, 56(R1)
MOVW R13, 60(R1)
ADD $64, R2
ADD $64, R3
ADD $64, R1
SUB $64, R4
SGTU $64, R4, R5
BEQ R0, R5, xor_64
BEQ R0, R4, end
xor_32_check:
SGTU $32, R4, R5
BNE R5, xor_16_check
xor_32:
MOVW (R2), R6
MOVW 4(R2), R7
MOVW 8(R2), R8
MOVW 12(R2), R9
MOVW (R3), R10
MOVW 4(R3), R11
MOVW 8(R3), R12
MOVW 12(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVW R10, (R1)
MOVW R11, 4(R1)
MOVW R12, 8(R1)
MOVW R13, 12(R1)
MOVW 16(R2), R6
MOVW 20(R2), R7
MOVW 24(R2), R8
MOVW 28(R2), R9
MOVW 16(R3), R10
MOVW 20(R3), R11
MOVW 24(R3), R12
MOVW 28(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVW R10, 16(R1)
MOVW R11, 20(R1)
MOVW R12, 24(R1)
MOVW R13, 28(R1)
ADD $32, R2
ADD $32, R3
ADD $32, R1
SUB $32, R4
BEQ R0, R4, end
xor_16_check:
SGTU $16, R4, R5
BNE R5, xor_8_check
xor_16:
MOVW (R2), R6
MOVW 4(R2), R7
MOVW 8(R2), R8
MOVW 12(R2), R9
MOVW (R3), R10
MOVW 4(R3), R11
MOVW 8(R3), R12
MOVW 12(R3), R13
XOR R6, R10
XOR R7, R11
XOR R8, R12
XOR R9, R13
MOVW R10, (R1)
MOVW R11, 4(R1)
MOVW R12, 8(R1)
MOVW R13, 12(R1)
ADD $16, R2
ADD $16, R3
ADD $16, R1
SUB $16, R4
BEQ R0, R4, end
xor_8_check:
SGTU $8, R4, R5
BNE R5, xor_4_check
xor_8:
MOVW (R2), R6
MOVW 4(R2), R7
MOVW (R3), R8
MOVW 4(R3), R9
XOR R6, R8
XOR R7, R9
MOVW R8, (R1)
MOVW R9, 4(R1)
ADD $8, R1
ADD $8, R2
ADD $8, R3
SUB $8, R4
BEQ R0, R4, end
xor_4_check:
SGTU $4, R4, R5
BNE R5, xor_2_check
xor_4:
MOVW (R2), R6
MOVW (R3), R7
XOR R6, R7
MOVW R7, (R1)
ADD $4, R2
ADD $4, R3
ADD $4, R1
SUB $4, R4
BEQ R0, R4, end
xor_2_check:
SGTU $2, R4, R5
BNE R5, xor_1
xor_2:
MOVH (R2), R6
MOVH (R3), R7
XOR R6, R7
MOVH R7, (R1)
ADD $2, R2
ADD $2, R3
ADD $2, R1
SUB $2, R4
BEQ R0, R4, end
xor_1:
MOVB (R2), R6
MOVB (R3), R7
XOR R6, R7
MOVB R7, (R1)
end:
RET

View File

@@ -91,7 +91,15 @@ func Decode(data []byte) (p *Block, rest []byte) {
// the byte array, we'll accept the start string without it.
rest = data
endTrailerIndex := 0
for {
// If we've already tried parsing a block, skip past the END we already
// saw.
if endTrailerIndex < 0 || endTrailerIndex > len(rest) {
return nil, data
}
rest = rest[endTrailerIndex:]
// Find the first END line, and then find the last BEGIN line before
// the end line. This lets us skip any repeated BEGIN lines that don't
// have a matching END.
@@ -99,10 +107,10 @@ func Decode(data []byte) (p *Block, rest []byte) {
if endIndex < 0 {
return nil, data
}
endTrailerIndex := endIndex + len(pemEnd)
endTrailerIndex = endIndex + len(pemEnd)
beginIndex := bytes.LastIndex(rest[:endIndex], pemStart[1:])
if beginIndex < 0 || beginIndex > 0 && rest[beginIndex-1] != '\n' {
return nil, data
if beginIndex < 0 || (beginIndex > 0 && rest[beginIndex-1] != '\n') {
continue
}
rest = rest[beginIndex+len(pemStart)-1:]
endIndex -= beginIndex + len(pemStart) - 1
@@ -111,11 +119,11 @@ func Decode(data []byte) (p *Block, rest []byte) {
var typeLine []byte
var consumed int
typeLine, rest, consumed = getLine(rest)
endIndex -= consumed
endTrailerIndex -= consumed
if !bytes.HasSuffix(typeLine, pemEndOfLine) {
continue
}
endIndex -= consumed
endTrailerIndex -= consumed
typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
p = &Block{

View File

@@ -639,3 +639,104 @@ func TestBadEncode(t *testing.T) {
}
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
func TestDecodeStrangeCases(t *testing.T) {
sentinelType := "TEST BLOCK"
sentinelBytes := []byte("hello")
for _, tc := range []struct {
name string
pem string
}{
{
name: "invalid section (not base64)",
pem: `-----BEGIN COMMENT-----
foo foo foo
-----END COMMENT-----
-----BEGIN TEST BLOCK-----
aGVsbG8=
-----END TEST BLOCK-----`,
},
{
name: "leading garbage on block",
pem: `foo foo foo-----BEGIN CERTIFICATE-----
MCowBQYDK2VwAyEApVjJeLW5MoP6uR3+OeITokM+rBDng6dgl1vvhcy+wws=
-----END PUBLIC KEY-----
-----BEGIN TEST BLOCK-----
aGVsbG8=
-----END TEST BLOCK-----`,
},
{
name: "leading garbage",
pem: `foo foo foo
-----BEGIN TEST BLOCK-----
aGVsbG8=
-----END TEST BLOCK-----`,
},
{
name: "leading partial block",
pem: `foo foo foo
-----END COMMENT-----
-----BEGIN TEST BLOCK-----
aGVsbG8=
-----END TEST BLOCK-----`,
},
{
name: "multiple BEGIN",
pem: `-----BEGIN TEST BLOCK-----
-----BEGIN TEST BLOCK-----
-----BEGIN TEST BLOCK-----
aGVsbG8=
-----END TEST BLOCK-----`,
},
{
name: "multiple END",
pem: `-----BEGIN TEST BLOCK-----
aGVsbG8=
-----END TEST BLOCK-----
-----END TEST BLOCK-----
-----END TEST BLOCK-----`,
},
{
name: "leading malformed BEGIN",
pem: `-----BEGIN PUBLIC KEY
aGVsbG8=
-----END PUBLIC KEY-----
-----BEGIN TEST BLOCK-----
aGVsbG8=
-----END TEST BLOCK-----`,
},
} {
t.Run(tc.name, func(t *testing.T) {
block, _ := Decode([]byte(tc.pem))
if block == nil {
t.Fatal("expected valid block")
}
if block.Type != sentinelType {
t.Fatalf("unexpected block returned, got type %q, want type %q", block.Type, sentinelType)
}
if !bytes.Equal(block.Bytes, sentinelBytes) {
t.Fatalf("unexpected block content, got %x, want %x", block.Bytes, sentinelBytes)
}
})
}
}
func TestJustEnd(t *testing.T) {
pemData := `
-----END PUBLIC KEY-----`
block, _ := Decode([]byte(pemData))
if block != nil {
t.Fatal("unexpected block")
}
}
func FuzzDecode(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
Decode(data)
})
}
func TestMissingEndTrailer(t *testing.T) {
Decode([]byte{0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xa, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20})
}

View File

@@ -204,7 +204,7 @@ func Deleteat(dirfd syscall.Handle, name string, options uint32) error {
var h syscall.Handle
err := NtOpenFile(
&h,
SYNCHRONIZE|DELETE,
SYNCHRONIZE|FILE_READ_ATTRIBUTES|DELETE,
objAttrs,
&IO_STATUS_BLOCK{},
FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
@@ -215,14 +215,22 @@ func Deleteat(dirfd syscall.Handle, name string, options uint32) error {
}
defer syscall.CloseHandle(h)
const (
FileDispositionInformation = 13
FileDispositionInformationEx = 64
)
if TestDeleteatFallback {
return deleteatFallback(h)
}
const FileDispositionInformationEx = 64
// First, attempt to delete the file using POSIX semantics
// (which permit a file to be deleted while it is still open).
// This matches the behavior of DeleteFileW.
//
// The following call uses features available on different Windows versions:
// - FILE_DISPOSITION_INFORMATION_EX: Windows 10, version 1607 (aka RS1)
// - FILE_DISPOSITION_POSIX_SEMANTICS: Windows 10, version 1607 (aka RS1)
// - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: Windows 10, version 1809 (aka RS5)
//
// Also, some file systems, like FAT32, don't support POSIX semantics.
err = NtSetInformationFile(
h,
&IO_STATUS_BLOCK{},
@@ -241,28 +249,57 @@ func Deleteat(dirfd syscall.Handle, name string, options uint32) error {
switch err {
case nil:
return nil
case STATUS_CANNOT_DELETE, STATUS_DIRECTORY_NOT_EMPTY:
case STATUS_INVALID_INFO_CLASS, // the operating system doesn't support FileDispositionInformationEx
STATUS_INVALID_PARAMETER, // the operating system doesn't support one of the flags
STATUS_NOT_SUPPORTED: // the file system doesn't support FILE_DISPOSITION_INFORMATION_EX or one of the flags
return deleteatFallback(h)
default:
return err.(NTStatus).Errno()
}
}
// If the prior deletion failed, the filesystem either doesn't support
// POSIX semantics (for example, FAT), or hasn't implemented
// FILE_DISPOSITION_INFORMATION_EX.
//
// Try again.
err = NtSetInformationFile(
// TestDeleteatFallback should only be used for testing purposes.
// When set, [Deleteat] uses the fallback path unconditionally.
var TestDeleteatFallback bool
// deleteatFallback is a deleteat implementation that strives
// for compatibility with older Windows versions and file systems
// over performance.
func deleteatFallback(h syscall.Handle) error {
var data syscall.ByHandleFileInformation
if err := syscall.GetFileInformationByHandle(h, &data); err == nil && data.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
// Remove read-only attribute. Reopen the file, as it was previously open without FILE_WRITE_ATTRIBUTES access
// in order to maximize compatibility in the happy path.
wh, err := ReOpenFile(h,
FILE_WRITE_ATTRIBUTES,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS,
)
if err != nil {
return err
}
err = SetFileInformationByHandle(
wh,
FileBasicInfo,
unsafe.Pointer(&FILE_BASIC_INFO{
FileAttributes: data.FileAttributes &^ FILE_ATTRIBUTE_READONLY,
}),
uint32(unsafe.Sizeof(FILE_BASIC_INFO{})),
)
syscall.CloseHandle(wh)
if err != nil {
return err
}
}
return SetFileInformationByHandle(
h,
&IO_STATUS_BLOCK{},
unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{
FileDispositionInfo,
unsafe.Pointer(&FILE_DISPOSITION_INFO{
DeleteFile: true,
}),
uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})),
FileDispositionInformation,
uint32(unsafe.Sizeof(FILE_DISPOSITION_INFO{})),
)
if st, ok := err.(NTStatus); ok {
return st.Errno()
}
return err
}
func Renameat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error {

View File

@@ -19,6 +19,7 @@ const (
FileBasicInfo = 0 // FILE_BASIC_INFO
FileStandardInfo = 1 // FILE_STANDARD_INFO
FileNameInfo = 2 // FILE_NAME_INFO
FileDispositionInfo = 4 // FILE_DISPOSITION_INFO
FileStreamInfo = 7 // FILE_STREAM_INFO
FileCompressionInfo = 8 // FILE_COMPRESSION_INFO
FileAttributeTagInfo = 9 // FILE_ATTRIBUTE_TAG_INFO

View File

@@ -529,6 +529,8 @@ const (
//sys GetOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, done *uint32, wait bool) (err error)
//sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
//sys ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error)
// NTStatus corresponds with NTSTATUS, error values returned by ntdll.dll and
// other native functions.
type NTStatus uint32
@@ -554,6 +556,9 @@ const (
STATUS_NOT_A_DIRECTORY NTStatus = 0xC0000103
STATUS_CANNOT_DELETE NTStatus = 0xC0000121
STATUS_REPARSE_POINT_ENCOUNTERED NTStatus = 0xC000050B
STATUS_NOT_SUPPORTED NTStatus = 0xC00000BB
STATUS_INVALID_PARAMETER NTStatus = 0xC000000D
STATUS_INVALID_INFO_CLASS NTStatus = 0xC0000003
)
const (

View File

@@ -199,6 +199,11 @@ const (
FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000
)
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_disposition_info
type FILE_DISPOSITION_INFO struct {
DeleteFile bool
}
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_disposition_information
type FILE_DISPOSITION_INFORMATION struct {
DeleteFile bool

View File

@@ -84,6 +84,7 @@ var (
procModule32NextW = modkernel32.NewProc("Module32NextW")
procMoveFileExW = modkernel32.NewProc("MoveFileExW")
procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar")
procReOpenFile = modkernel32.NewProc("ReOpenFile")
procRtlLookupFunctionEntry = modkernel32.NewProc("RtlLookupFunctionEntry")
procRtlVirtualUnwind = modkernel32.NewProc("RtlVirtualUnwind")
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
@@ -430,6 +431,15 @@ func MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32,
return
}
func ReOpenFile(filehandle syscall.Handle, desiredAccess uint32, shareMode uint32, flagAndAttributes uint32) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procReOpenFile.Addr(), 4, uintptr(filehandle), uintptr(desiredAccess), uintptr(shareMode), uintptr(flagAndAttributes), 0, 0)
handle = syscall.Handle(r0)
if handle == 0 {
err = errnoErr(e1)
}
return
}
func RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table unsafe.Pointer) (ret *RUNTIME_FUNCTION) {
r0, _, _ := syscall.Syscall(procRtlLookupFunctionEntry.Addr(), 3, uintptr(pc), uintptr(unsafe.Pointer(baseAddress)), uintptr(table))
ret = (*RUNTIME_FUNCTION)(unsafe.Pointer(r0))

View File

@@ -673,13 +673,13 @@ func parseHost(host string) (string, error) {
// Per RFC 3986, only a host identified by a valid
// IPv6 address can be enclosed by square brackets.
// This excludes any IPv4 or IPv4-mapped addresses.
// This excludes any IPv4, but notably not IPv4-mapped addresses.
addr, err := netip.ParseAddr(unescapedHostname)
if err != nil {
return "", fmt.Errorf("invalid host: %w", err)
}
if addr.Is4() || addr.Is4In6() {
return "", errors.New("invalid IPv6 host")
if addr.Is4() {
return "", errors.New("invalid IP-literal")
}
return "[" + unescapedHostname + "]" + unescapedColonPort, nil
} else if i := strings.LastIndex(host, ":"); i != -1 {

View File

@@ -726,7 +726,7 @@ var parseRequestURLTests = []struct {
{"https://[2001:db8::1]/path", true}, // compressed IPv6 address with path
{"https://[fe80::1%25eth0]/path?query=1", true}, // link-local with zone, path, and query
{"https://[::ffff:192.0.2.1]", false},
{"https://[::ffff:192.0.2.1]", true},
{"https://[:1] ", false},
{"https://[1:2:3:4:5:6:7:8:9]", false},
{"https://[1::1::1]", false},
@@ -1672,16 +1672,17 @@ func TestParseErrors(t *testing.T) {
{"cache_object:foo/bar", true},
{"cache_object/:foo/bar", false},
{"http://[192.168.0.1]/", true}, // IPv4 in brackets
{"http://[192.168.0.1]:8080/", true}, // IPv4 in brackets with port
{"http://[::ffff:192.168.0.1]/", true}, // IPv4-mapped IPv6 in brackets
{"http://[::ffff:192.168.0.1]:8080/", true}, // IPv4-mapped IPv6 in brackets with port
{"http://[::ffff:c0a8:1]/", true}, // IPv4-mapped IPv6 in brackets (hex)
{"http://[not-an-ip]/", true}, // invalid IP string in brackets
{"http://[fe80::1%foo]/", true}, // invalid zone format in brackets
{"http://[fe80::1", true}, // missing closing bracket
{"http://fe80::1]/", true}, // missing opening bracket
{"http://[test.com]/", true}, // domain name in brackets
{"http://[192.168.0.1]/", true}, // IPv4 in brackets
{"http://[192.168.0.1]:8080/", true}, // IPv4 in brackets with port
{"http://[::ffff:192.168.0.1]/", false}, // IPv4-mapped IPv6 in brackets
{"http://[::ffff:192.168.0.1000]/", true}, // Out of range IPv4-mapped IPv6 in brackets
{"http://[::ffff:192.168.0.1]:8080/", false}, // IPv4-mapped IPv6 in brackets with port
{"http://[::ffff:c0a8:1]/", false}, // IPv4-mapped IPv6 in brackets (hex)
{"http://[not-an-ip]/", true}, // invalid IP string in brackets
{"http://[fe80::1%foo]/", true}, // invalid zone format in brackets
{"http://[fe80::1", true}, // missing closing bracket
{"http://fe80::1]/", true}, // missing opening bracket
{"http://[test.com]/", true}, // domain name in brackets
}
for _, tt := range tests {
u, err := Parse(tt.in)

View File

@@ -236,6 +236,23 @@ func TestRemoveAllLongPathRelative(t *testing.T) {
}
}
func TestRemoveAllFallback(t *testing.T) {
windows.TestDeleteatFallback = true
t.Cleanup(func() { windows.TestDeleteatFallback = false })
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "file1"), []byte{}, 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "file2"), []byte{}, 0400); err != nil { // read-only file
t.Fatal(err)
}
if err := os.RemoveAll(dir); err != nil {
t.Fatal(err)
}
}
func testLongPathAbs(t *testing.T, target string) {
t.Helper()
testWalkFn := func(path string, info os.FileInfo, err error) error {

View File

@@ -22,10 +22,17 @@ const (
// On AMD64, virtual addresses are 48-bit (or 57-bit) sign-extended.
// Other archs are 48-bit zero-extended.
//
// We use one extra bit to placate systems which simulate amd64 binaries on
// an arm64 host. Allocated arm64 addresses could be as high as 1<<48-1,
// which would be invalid if we assumed 48-bit sign-extended addresses.
// See issue 69255.
// (Note that this does not help the other way around, simluating arm64
// on amd64, but we don't have that problem at the moment.)
//
// On s390x, virtual addresses are 64-bit. There's not much we
// can do about this, so we just hope that the kernel doesn't
// get to really high addresses and panic if it does.
defaultAddrBits = 48
defaultAddrBits = 48 + 1
// On AIX, 64-bit addresses are split into 36-bit segment number and 28-bit
// offset in segment. Segment numbers in the range 0x0A0000000-0x0AFFFFFFF(LSA)

View File

@@ -0,0 +1,35 @@
// run
// 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 "runtime"
func main() {
shouldPanic(func() {
g = any(func() {}) == any(func() {})
})
shouldPanic(func() {
g = any(map[int]int{}) == any(map[int]int{})
})
shouldPanic(func() {
g = any([]int{}) == any([]int{})
})
}
var g bool
func shouldPanic(f func()) {
defer func() {
err := recover()
if err == nil {
_, _, line, _ := runtime.Caller(2)
println("did not panic at line", line+1)
}
}()
f()
}