[dev.simd] all: merge master (adce7f1) into dev.simd

Conflicts:

- src/internal/goexperiment/flags.go
- src/runtime/export_test.go

Merge List:

+ 2025-10-03 adce7f196e cmd/link: support .def file with MSVC clang toolchain
+ 2025-10-03 d5b950399d cmd/cgo: fix unaligned arguments typedmemmove crash on iOS
+ 2025-10-02 53845004d6 net/http/httputil: deprecate ReverseProxy.Director
+ 2025-10-02 bbdff9e8e1 net/http: update bundled x/net/http2 and delete obsolete http2inTests
+ 2025-10-02 4008e07080 io/fs: move path name documentation up to the package doc comment
+ 2025-10-02 0e4e2e6832 runtime: skip TestGoroutineLeakProfile under mayMoreStackPreempt
+ 2025-10-02 f03c392295 runtime: fix aix/ppc64 library initialization
+ 2025-10-02 707454b41f cmd/go: update `go help mod edit` with the tool and ignore sections
+ 2025-10-02 8c68a1c1ab runtime,net/http/pprof: goroutine leak detection by using the garbage collector
+ 2025-10-02 84db201ae1 cmd/compile: propagate len([]T{}) to make builtin to allow stack allocation
+ 2025-10-02 5799c139a7 crypto/tls: rm marshalEncryptedClientHelloConfigList dead code
+ 2025-10-01 633dd1d475 encoding/json: fix Decoder.InputOffset regression in goexperiment.jsonv2
+ 2025-10-01 8ad27fb656 doc/go_spec.html: update date
+ 2025-10-01 3f451f2c54 testing/synctest: fix inverted test failure message in TestContextAfterFunc
+ 2025-10-01 be0fed8a5f cmd/go/testdata/script/test_fuzz_fuzztime.txt: disable
+ 2025-09-30 eb1c7f6e69 runtime: move loong64 library entry point to os-agnostic file
+ 2025-09-30 c9257151e5 runtime: unify ppc64/ppc64le library entry point
+ 2025-09-30 4ff8a457db test/codegen: codify handling of floating point constants on arm64
+ 2025-09-30 fcb893fc4b cmd/compile/internal/ssa: remove redundant "type:" prefix check
+ 2025-09-30 19cc1022ba mime: reduce allocs incurred by ParseMediaType
+ 2025-09-30 08afc50bea mime: extend "builtinTypes" to include a more complete list of common types
+ 2025-09-30 97da068774 cmd/compile: eliminate nil checks on .dict arg
+ 2025-09-30 300d9d2714 runtime: initialise debug settings much earlier in startup process
+ 2025-09-30 a846bb0aa5 errors: add AsType
+ 2025-09-30 7c8166d02d cmd/link/internal/arm64: support Mach-O ARM64_RELOC_SUBTRACTOR in internal linking
+ 2025-09-30 6e95748335 cmd/link/internal/arm64: support Mach-O ARM64_RELOC_POINTER_TO_GOT in internal linking
+ 2025-09-30 742f92063e cmd/compile, runtime: always enable Wasm signext and satconv features
+ 2025-09-30 db10db6be3 internal/poll: remove operation fields from FD
+ 2025-09-29 75c87df58e internal/poll: pass the I/O mode instead of an overlapped object in execIO
+ 2025-09-29 fc88e18b4a crypto/internal/fips140/entropy: add CPU jitter-based entropy source
+ 2025-09-29 db4fade759 crypto/internal/fips140/mlkem: make CAST conditional
+ 2025-09-29 db3cb3fd9a runtime: correct reference to getStackMap in comment
+ 2025-09-29 690fc2fb05 internal/poll: remove buf field from operation
+ 2025-09-29 eaf2345256 cmd/link: use a .def file to mark exported symbols on Windows
+ 2025-09-29 4b77733565 internal/syscall/windows: regenerate GetFileSizeEx
+ 2025-09-29 4e9006a716 crypto/tls: quote protocols in ALPN error message
+ 2025-09-29 047c2ab841 cmd/link: don't pass -Wl,-S on Solaris
+ 2025-09-29 ae8eba071b cmd/link: use correct length for pcln.cutab
+ 2025-09-29 fe3ba74b9e cmd/link: skip TestFlagW on platforms without DWARF symbol table
+ 2025-09-29 d42d56b764 encoding/xml: make use of reflect.TypeAssert
+ 2025-09-29 6d51f93257 runtime: jump instead of branch in netbsd/arm64 entry point
+ 2025-09-28 5500cbf0e4 debug/elf: prevent offset overflow
+ 2025-09-27 34e67623a8 all:  fix typos
+ 2025-09-27 af6999e60d cmd/compile: implement jump table on loong64
+ 2025-09-26 63cd912083 os/user: simplify go:build
+ 2025-09-26 53009b26dd runtime: use a smaller arena size on Wasm
+ 2025-09-26 3a5df9d2b2 net/http: add HTTP2Config.StrictMaxConcurrentRequests
+ 2025-09-26 16be34df02 net/http: add more tests of transport connection pool
+ 2025-09-26 3e4540b49d os/user: use getgrouplist on illumos && cgo
+ 2025-09-26 15fbe3480b internal/poll: simplify WriteMsg and ReadMsg on Windows
+ 2025-09-26 16ae11a9e1 runtime: move TestReadMetricsSched to testprog
+ 2025-09-26 459f3a3adc cmd/link: don't pass -Wl,-S on AIX
+ 2025-09-26 4631a2d3c6 cmd/link: skip TestFlagW on AIX
+ 2025-09-26 0f31d742cd cmd/compile: fix ICE with new(<untyped expr>)
+ 2025-09-26 7d7cd6e07b internal/poll: don't call SetFilePointerEx in Seek for overlapped handles
+ 2025-09-26 41cba31e66 mime/multipart: percent-encode CR and LF in header values to avoid CRLF injection
+ 2025-09-26 dd1d597c3a Revert "cmd/internal/obj/loong64: use the MOVVP instruction to optimize prologue"
+ 2025-09-26 45d6bc76af runtime: unify arm64 entry point code
+ 2025-09-25 fdea7da3e6 runtime: use common library entry point on windows amd64/386
+ 2025-09-25 e8a4f508d1 lib/fips140: re-seal v1.0.0
+ 2025-09-25 9b7a328089 crypto/internal/fips140: remove key import PCTs, make keygen PCTs fatal
+ 2025-09-25 7f9ab7203f crypto/internal/fips140: update frozen module version to "v1.0.0"
+ 2025-09-25 fb5719cbda crypto/internal/fips140/ecdsa: make TestingOnlyNewDRBG generic
+ 2025-09-25 56067e31f2 std: remove unused declarations

Change-Id: Iecb28fd62c69fbed59da557f46d31bae55889e2c
This commit is contained in:
Cherry Mui 2025-10-03 10:11:20 -04:00
commit fb1749a3fe
260 changed files with 15251 additions and 2505 deletions

1
api/next/51945.txt Normal file
View file

@ -0,0 +1 @@
pkg errors, func AsType[$0 error](error) ($0, bool) #51945

1
api/next/67813.txt Normal file
View file

@ -0,0 +1 @@
pkg net/http, type HTTP2Config struct, StrictMaxConcurrentRequests bool #67813

1
api/next/73161.txt Normal file
View file

@ -0,0 +1 @@
pkg net/http/httputil, type ReverseProxy struct, Director //deprecated #73161

View file

@ -1,6 +1,6 @@
<!--{
"Title": "The Go Programming Language Specification",
"Subtitle": "Language version go1.25 (Feb 25, 2025)",
"Subtitle": "Language version go1.26 (Oct 1, 2025)",
"Path": "/ref/spec"
}-->

View file

@ -0,0 +1,2 @@
The new [AsType] function is a generic version of [As]. It is type-safe, faster,
and, in most cases, easier to use.

View file

@ -0,0 +1,4 @@
The new
[HTTP2Config.StrictMaxConcurrentRequests](/pkg/net/http#HTTP2Config.StrictMaxConcurrentRequests)
field controls whether a new connection should be opened
if an existing HTTP/2 connection has exceeded its stream limit.

View file

@ -0,0 +1,11 @@
The [ReverseProxy.Director] configuration field is deprecated
in favor of [ReverseProxy.Rewrite].
A malicious client can remove headers added by a `Director` function
by designating those headers as hop-by-hop. Since there is no way to address
this problem within the scope of the `Director` API, we added a new
`Rewrite` hook in Go 1.20. `Rewrite` hooks are provided with both the
unmodified inbound request received by the proxy and the outbound request
which will be sent by the proxy.
Since the `Director` hook is fundamentally unsafe, we are now deprecating it.

View file

@ -9,4 +9,4 @@
#
# go test cmd/go/internal/fips140 -update
#
v1.0.0.zip b50508feaeff05d22516b21e1fd210bbf5d6a1e422eaf2cfa23fe379342713b8
v1.0.0-c2097c7c.zip daf3614e0406f67ae6323c902db3f953a1effb199142362a039e7526dfb9368b

View file

@ -1 +1 @@
v1.0.0
v1.0.0-c2097c7c

1
lib/fips140/v1.0.0.txt Normal file
View file

@ -0,0 +1 @@
v1.0.0-c2097c7c

View file

@ -8,6 +8,7 @@ import (
"bufio"
"bytes"
"cmd/cgo/internal/cgotest"
"cmp"
"debug/elf"
"debug/pe"
"encoding/binary"
@ -272,7 +273,7 @@ func createHeaders() error {
// which results in the linkers output implib getting overwritten at each step. So instead build the
// import library the traditional way, using a def file.
err = os.WriteFile("libgo.def",
[]byte("LIBRARY libgo.dll\nEXPORTS\n\tDidInitRun\n\tDidMainRun\n\tDivu\n\tFromPkg\n\t_cgo_dummy_export\n"),
[]byte("LIBRARY libgo.dll\nEXPORTS\n\tDidInitRun\n\tDidMainRun\n\tDivu\n\tFromPkg\n"),
0644)
if err != nil {
return fmt.Errorf("unable to write def file: %v", err)
@ -375,9 +376,23 @@ func TestExportedSymbols(t *testing.T) {
}
}
func checkNumberOfExportedFunctionsWindows(t *testing.T, prog string, exportedFunctions int, wantAll bool) {
func checkNumberOfExportedSymbolsWindows(t *testing.T, exportedSymbols int, wantAll bool) {
t.Parallel()
tmpdir := t.TempDir()
prog := `
package main
import "C"
func main() {}
`
for i := range exportedSymbols {
prog += fmt.Sprintf(`
//export GoFunc%d
func GoFunc%d() {}
`, i, i)
}
srcfile := filepath.Join(tmpdir, "test.go")
objfile := filepath.Join(tmpdir, "test.dll")
if err := os.WriteFile(srcfile, []byte(prog), 0666); err != nil {
@ -443,18 +458,19 @@ func checkNumberOfExportedFunctionsWindows(t *testing.T, prog string, exportedFu
t.Fatalf("binary.Read failed: %v", err)
}
// Only the two exported functions and _cgo_dummy_export should be exported.
exportedSymbols = cmp.Or(exportedSymbols, 1) // _cgo_stub_export is exported if there are no other symbols exported
// NumberOfNames is the number of functions exported with a unique name.
// NumberOfFunctions can be higher than that because it also counts
// functions exported only by ordinal, a unique number asigned by the linker,
// and linkers might add an unknown number of their own ordinal-only functions.
if wantAll {
if e.NumberOfNames <= uint32(exportedFunctions) {
t.Errorf("got %d exported names, want > %d", e.NumberOfNames, exportedFunctions)
if e.NumberOfNames <= uint32(exportedSymbols) {
t.Errorf("got %d exported names, want > %d", e.NumberOfNames, exportedSymbols)
}
} else {
if e.NumberOfNames > uint32(exportedFunctions) {
t.Errorf("got %d exported names, want <= %d", e.NumberOfNames, exportedFunctions)
if e.NumberOfNames != uint32(exportedSymbols) {
t.Errorf("got %d exported names, want %d", e.NumberOfNames, exportedSymbols)
}
}
}
@ -470,43 +486,14 @@ func TestNumberOfExportedFunctions(t *testing.T) {
t.Parallel()
const prog0 = `
package main
import "C"
func main() {
}
`
const prog2 = `
package main
import "C"
//export GoFunc
func GoFunc() {
println(42)
}
//export GoFunc2
func GoFunc2() {
println(24)
}
func main() {
}
`
// All programs export _cgo_dummy_export, so add 1 to the expected counts.
t.Run("OnlyExported/0", func(t *testing.T) {
checkNumberOfExportedFunctionsWindows(t, prog0, 0+1, false)
})
t.Run("OnlyExported/2", func(t *testing.T) {
checkNumberOfExportedFunctionsWindows(t, prog2, 2+1, false)
})
t.Run("All", func(t *testing.T) {
checkNumberOfExportedFunctionsWindows(t, prog2, 2+1, true)
})
for i := range 3 {
t.Run(fmt.Sprintf("OnlyExported/%d", i), func(t *testing.T) {
checkNumberOfExportedSymbolsWindows(t, i, false)
})
t.Run(fmt.Sprintf("All/%d", i), func(t *testing.T) {
checkNumberOfExportedSymbolsWindows(t, i, true)
})
}
}
// test1: shared library can be dynamically loaded and exported symbols are accessible.

View file

@ -0,0 +1,144 @@
// 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 out_test
import (
"bufio"
"bytes"
"fmt"
"internal/testenv"
"internal/goarch"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
)
type methodAlign struct {
Method string
Align int
}
var wantAligns = map[string]int{
"ReturnEmpty": 1,
"ReturnOnlyUint8": 1,
"ReturnOnlyUint16": 2,
"ReturnOnlyUint32": 4,
"ReturnOnlyUint64": goarch.PtrSize,
"ReturnOnlyInt": goarch.PtrSize,
"ReturnOnlyPtr": goarch.PtrSize,
"ReturnByteSlice": goarch.PtrSize,
"ReturnString": goarch.PtrSize,
"InputAndReturnUint8": 1,
"MixedTypes": goarch.PtrSize,
}
// TestAligned tests that the generated _cgo_export.c file has the wanted
// align attributes for struct types used as arguments or results of
// //exported functions.
func TestAligned(t *testing.T) {
testenv.MustHaveGoRun(t)
testenv.MustHaveCGO(t)
testdata, err := filepath.Abs("testdata")
if err != nil {
t.Fatal(err)
}
objDir := t.TempDir()
cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "cgo",
"-objdir", objDir,
filepath.Join(testdata, "aligned.go"))
cmd.Stderr = new(bytes.Buffer)
err = cmd.Run()
if err != nil {
t.Fatalf("%#q: %v\n%s", cmd, err, cmd.Stderr)
}
haveAligns, err := parseAlign(filepath.Join(objDir, "_cgo_export.c"))
if err != nil {
t.Fatal(err)
}
// Check that we have all the wanted methods
if len(haveAligns) != len(wantAligns) {
t.Fatalf("have %d methods with aligned, want %d", len(haveAligns), len(wantAligns))
}
for i := range haveAligns {
method := haveAligns[i].Method
haveAlign := haveAligns[i].Align
wantAlign, ok := wantAligns[method]
if !ok {
t.Errorf("method %s: have aligned %d, want missing entry", method, haveAlign)
} else if haveAlign != wantAlign {
t.Errorf("method %s: have aligned %d, want %d", method, haveAlign, wantAlign)
}
}
}
func parseAlign(filename string) ([]methodAlign, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
var results []methodAlign
scanner := bufio.NewScanner(file)
// Regex to match function declarations like "struct MethodName_return MethodName("
funcRegex := regexp.MustCompile(`^struct\s+(\w+)_return\s+(\w+)\(`)
// Regex to match simple function declarations like "GoSlice MethodName("
simpleFuncRegex := regexp.MustCompile(`^Go\w+\s+(\w+)\(`)
// Regex to match void-returning exported functions like "void ReturnEmpty("
voidFuncRegex := regexp.MustCompile(`^void\s+(\w+)\(`)
// Regex to match align attributes like "__attribute__((aligned(8)))"
alignRegex := regexp.MustCompile(`__attribute__\(\(aligned\((\d+)\)\)\)`)
var currentMethod string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// Check if this line declares a function with struct return type
if matches := funcRegex.FindStringSubmatch(line); matches != nil {
currentMethod = matches[2] // Extract the method name
} else if matches := simpleFuncRegex.FindStringSubmatch(line); matches != nil {
// Check if this line declares a function with simple return type (like GoSlice)
currentMethod = matches[1] // Extract the method name
} else if matches := voidFuncRegex.FindStringSubmatch(line); matches != nil {
// Check if this line declares a void-returning function
currentMethod = matches[1] // Extract the method name
}
// Check if this line contains align information
if alignMatches := alignRegex.FindStringSubmatch(line); alignMatches != nil && currentMethod != "" {
alignStr := alignMatches[1]
align, err := strconv.Atoi(alignStr)
if err != nil {
// Skip this entry if we can't parse the align as integer
currentMethod = ""
continue
}
results = append(results, methodAlign{
Method: currentMethod,
Align: align,
})
currentMethod = "" // Reset for next method
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error reading file: %w", err)
}
return results, nil
}

View file

@ -0,0 +1,63 @@
// 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 "C"
//export ReturnEmpty
func ReturnEmpty() {
return
}
//export ReturnOnlyUint8
func ReturnOnlyUint8() (uint8, uint8, uint8) {
return 1, 2, 3
}
//export ReturnOnlyUint16
func ReturnOnlyUint16() (uint16, uint16, uint16) {
return 1, 2, 3
}
//export ReturnOnlyUint32
func ReturnOnlyUint32() (uint32, uint32, uint32) {
return 1, 2, 3
}
//export ReturnOnlyUint64
func ReturnOnlyUint64() (uint64, uint64, uint64) {
return 1, 2, 3
}
//export ReturnOnlyInt
func ReturnOnlyInt() (int, int, int) {
return 1, 2, 3
}
//export ReturnOnlyPtr
func ReturnOnlyPtr() (*int, *int, *int) {
a, b, c := 1, 2, 3
return &a, &b, &c
}
//export ReturnString
func ReturnString() string {
return "hello"
}
//export ReturnByteSlice
func ReturnByteSlice() []byte {
return []byte{1, 2, 3}
}
//export InputAndReturnUint8
func InputAndReturnUint8(a, b, c uint8) (uint8, uint8, uint8) {
return a, b, c
}
//export MixedTypes
func MixedTypes(a uint8, b uint16, c uint32, d uint64, e int, f *int) (uint8, uint16, uint32, uint64, int, *int) {
return a, b, c, d, e, f
}

View file

@ -949,6 +949,8 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
fmt.Fprintf(gotype, "struct {\n")
off := int64(0)
npad := 0
// the align is at least 1 (for char)
maxAlign := int64(1)
argField := func(typ ast.Expr, namePat string, args ...interface{}) {
name := fmt.Sprintf(namePat, args...)
t := p.cgoType(typ)
@ -963,6 +965,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
noSourceConf.Fprint(gotype, fset, typ)
fmt.Fprintf(gotype, "\n")
off += t.Size
// keep track of the maximum alignment among all fields
// so that we can align the struct correctly
if t.Align > maxAlign {
maxAlign = t.Align
}
}
if fn.Recv != nil {
argField(fn.Recv.List[0].Type, "recv")
@ -1005,12 +1012,8 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
}
// Build the wrapper function compiled by gcc.
gccExport := ""
if goos == "windows" {
gccExport = "__declspec(dllexport) "
}
var s strings.Builder
fmt.Fprintf(&s, "%s%s %s(", gccExport, gccResult, exp.ExpName)
fmt.Fprintf(&s, "%s %s(", gccResult, exp.ExpName)
if fn.Recv != nil {
s.WriteString(p.cgoType(fn.Recv.List[0].Type).C.String())
s.WriteString(" recv")
@ -1051,7 +1054,11 @@ func (p *Package) writeExports(fgo2, fm, fgcc, fgcch io.Writer) {
// string.h for memset, and is also robust to C++
// types with constructors. Both GCC and LLVM optimize
// this into just zeroing _cgo_a.
fmt.Fprintf(fgcc, "\ttypedef %s %v _cgo_argtype;\n", ctype.String(), p.packedAttribute())
//
// The struct should be aligned to the maximum alignment
// of any of its fields. This to avoid alignment
// issues.
fmt.Fprintf(fgcc, "\ttypedef %s %v __attribute__((aligned(%d))) _cgo_argtype;\n", ctype.String(), p.packedAttribute(), maxAlign)
fmt.Fprintf(fgcc, "\tstatic _cgo_argtype _cgo_zero;\n")
fmt.Fprintf(fgcc, "\t_cgo_argtype _cgo_a = _cgo_zero;\n")
if gccResult != "void" && (len(fntype.Results.List) > 1 || len(fntype.Results.List[0].Names) > 1) {

View file

@ -563,7 +563,10 @@ func (b *batch) rewriteWithLiterals(n ir.Node, fn *ir.Func) {
if ro == nil {
base.Fatalf("no ReassignOracle for function %v with closure parent %v", fn, fn.ClosureParent)
}
if s := ro.StaticValue(*r); s.Op() == ir.OLITERAL {
s := ro.StaticValue(*r)
switch s.Op() {
case ir.OLITERAL:
lit, ok := s.(*ir.BasicLit)
if !ok || lit.Val().Kind() != constant.Int {
base.Fatalf("unexpected BasicLit Kind")
@ -577,6 +580,14 @@ func (b *batch) rewriteWithLiterals(n ir.Node, fn *ir.Func) {
assignTemp(n.Pos(), *r, n.PtrInit())
*r = ir.NewBasicLit(n.Pos(), (*r).Type(), lit.Val())
}
case ir.OLEN:
x := ro.StaticValue(s.(*ir.UnaryExpr).X)
if x.Op() == ir.OSLICELIT {
x := x.(*ir.CompLitExpr)
// Preserve any side effects of the original expression, then update the value.
assignTemp(n.Pos(), *r, n.PtrInit())
*r = ir.NewBasicLit(n.Pos(), types.Types[types.TINT], constant.MakeInt64(x.Len))
}
}
}
case ir.OCONVIFACE:

View file

@ -1266,6 +1266,29 @@ func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
p.From.Reg = b.Controls[0].Reg()
}
}
case ssa.BlockLOONG64JUMPTABLE:
// ALSLV $3, Rarg0, Rarg1, REGTMP
// MOVV (REGTMP), REGTMP
// JMP (REGTMP)
p := s.Prog(loong64.AALSLV)
p.From.Type = obj.TYPE_CONST
p.From.Offset = 3 // idx*8
p.Reg = b.Controls[0].Reg()
p.AddRestSourceReg(b.Controls[1].Reg())
p.To.Type = obj.TYPE_REG
p.To.Reg = loong64.REGTMP
p1 := s.Prog(loong64.AMOVV)
p1.From.Type = obj.TYPE_MEM
p1.From.Reg = loong64.REGTMP
p1.From.Offset = 0
p1.To.Type = obj.TYPE_REG
p1.To.Reg = loong64.REGTMP
p2 := s.Prog(obj.AJMP)
p2.To.Type = obj.TYPE_MEM
p2.To.Reg = loong64.REGTMP
// Save jump tables for later resolution of the target blocks.
s.JumpTables = append(s.JumpTables, b)
default:
b.Fatalf("branch not implemented: %s", b.LongString())
}

View file

@ -2434,6 +2434,7 @@ func (r *reader) expr() (res ir.Node) {
if r.Bool() {
// new(expr) -> tmp := expr; &tmp
x := r.expr()
x = typecheck.DefaultLit(x, nil) // See TODO in exprConvert case.
var init ir.Nodes
addr := ir.NewAddrExpr(pos, r.tempCopy(pos, x, &init))
addr.SetInit(init)

View file

@ -504,6 +504,8 @@
(MOVBUreg x:((SGT|SGTU) _ _)) => x
(MOVBUreg x:(XOR (MOVVconst [1]) ((SGT|SGTU) _ _))) => x
(JumpTable idx) => (JUMPTABLE {makeJumpTableSym(b)} idx (MOVVaddr <typ.Uintptr> {makeJumpTableSym(b)} (SB)))
// Write barrier.
(WB ...) => (LoweredWB ...)

View file

@ -577,6 +577,12 @@ func init() {
{name: "BLT", controls: 2}, // controls[0] < controls[1]
{name: "BGEU", controls: 2}, // controls[0] >= controls[1], unsigned
{name: "BLTU", controls: 2}, // controls[0] < controls[1], unsigned
// JUMPTABLE implements jump tables.
// Aux is the symbol (an *obj.LSym) for the jump table.
// control[0] is the index into the jump table.
// control[1] is the address of the jump table (the address of the symbol stored in Aux).
{name: "JUMPTABLE", controls: 2, aux: "Sym"},
}
archs = append(archs, arch{

View file

@ -55,12 +55,9 @@
(ZeroExt32to64 x:(I64Load32U _ _)) => x
(ZeroExt16to(64|32) x:(I64Load16U _ _)) => x
(ZeroExt8to(64|32|16) x:(I64Load8U _ _)) => x
(SignExt32to64 x) && buildcfg.GOWASM.SignExt => (I64Extend32S x)
(SignExt8to(64|32|16) x) && buildcfg.GOWASM.SignExt => (I64Extend8S x)
(SignExt16to(64|32) x) && buildcfg.GOWASM.SignExt => (I64Extend16S x)
(SignExt32to64 x) => (I64ShrS (I64Shl x (I64Const [32])) (I64Const [32]))
(SignExt16to(64|32) x) => (I64ShrS (I64Shl x (I64Const [48])) (I64Const [48]))
(SignExt8to(64|32|16) x) => (I64ShrS (I64Shl x (I64Const [56])) (I64Const [56]))
(SignExt32to64 x) => (I64Extend32S x)
(SignExt8to(64|32|16) x) => (I64Extend8S x)
(SignExt16to(64|32) x) => (I64Extend16S x)
(ZeroExt32to64 x) => (I64And x (I64Const [0xffffffff]))
(ZeroExt16to(64|32) x) => (I64And x (I64Const [0xffff]))
(ZeroExt8to(64|32|16) x) => (I64And x (I64Const [0xff]))

View file

@ -2083,6 +2083,9 @@
&& warnRule(fe.Debug_checknil(), v, "removed nil check")
=> ptr
// .dict args are always non-nil.
(NilCheck ptr:(Arg {sym}) _) && isDictArgSym(sym) => ptr
// Nil checks of nil checks are redundant.
// See comment at the end of https://go-review.googlesource.com/c/go/+/537775.
(NilCheck ptr:(NilCheck _ _) _ ) => ptr

View file

@ -108,6 +108,7 @@ const (
BlockLOONG64BLT
BlockLOONG64BGEU
BlockLOONG64BLTU
BlockLOONG64JUMPTABLE
BlockMIPSEQ
BlockMIPSNE
@ -250,20 +251,21 @@ var blockString = [...]string{
BlockARM64GEnoov: "GEnoov",
BlockARM64JUMPTABLE: "JUMPTABLE",
BlockLOONG64EQZ: "EQZ",
BlockLOONG64NEZ: "NEZ",
BlockLOONG64LTZ: "LTZ",
BlockLOONG64LEZ: "LEZ",
BlockLOONG64GTZ: "GTZ",
BlockLOONG64GEZ: "GEZ",
BlockLOONG64FPT: "FPT",
BlockLOONG64FPF: "FPF",
BlockLOONG64BEQ: "BEQ",
BlockLOONG64BNE: "BNE",
BlockLOONG64BGE: "BGE",
BlockLOONG64BLT: "BLT",
BlockLOONG64BGEU: "BGEU",
BlockLOONG64BLTU: "BLTU",
BlockLOONG64EQZ: "EQZ",
BlockLOONG64NEZ: "NEZ",
BlockLOONG64LTZ: "LTZ",
BlockLOONG64LEZ: "LEZ",
BlockLOONG64GTZ: "GTZ",
BlockLOONG64GEZ: "GEZ",
BlockLOONG64FPT: "FPT",
BlockLOONG64FPF: "FPF",
BlockLOONG64BEQ: "BEQ",
BlockLOONG64BNE: "BNE",
BlockLOONG64BGE: "BGE",
BlockLOONG64BLT: "BLT",
BlockLOONG64BGEU: "BGEU",
BlockLOONG64BLTU: "BLTU",
BlockLOONG64JUMPTABLE: "JUMPTABLE",
BlockMIPSEQ: "EQ",
BlockMIPSNE: "NE",

View file

@ -6,9 +6,11 @@ package ssa
import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/logopt"
"cmd/compile/internal/reflectdata"
"cmd/compile/internal/rttype"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/obj/s390x"
@ -2057,12 +2059,12 @@ func isFixedLoad(v *Value, sym Sym, off int64) bool {
return false
}
if strings.HasPrefix(lsym.Name, "type:") {
if ti := lsym.TypeInfo(); ti != nil {
// Type symbols do not contain information about their fields, unlike the cases above.
// Hand-implement field accesses.
// TODO: can this be replaced with reflectdata.writeType and just use the code above?
t := (*lsym.Extra).(*obj.TypeInfo).Type.(*types.Type)
t := ti.Type.(*types.Type)
for _, f := range rttype.Type.Fields() {
if f.Offset == off && copyCompatibleType(v.Type, f.Type) {
@ -2116,12 +2118,12 @@ func rewriteFixedLoad(v *Value, sym Sym, sb *Value, off int64) *Value {
base.Fatalf("fixedLoad data not known for %s:%d", sym, off)
}
if strings.HasPrefix(lsym.Name, "type:") {
if ti := lsym.TypeInfo(); ti != nil {
// Type symbols do not contain information about their fields, unlike the cases above.
// Hand-implement field accesses.
// TODO: can this be replaced with reflectdata.writeType and just use the code above?
t := (*lsym.Extra).(*obj.TypeInfo).Type.(*types.Type)
t := ti.Type.(*types.Type)
ptrSizedOpConst := OpConst64
if f.Config.PtrSize == 4 {
@ -2611,10 +2613,7 @@ func isDirectType1(v *Value) bool {
return isDirectType2(v.Args[0])
case OpAddr:
lsym := v.Aux.(*obj.LSym)
if lsym.Extra == nil {
return false
}
if ti, ok := (*lsym.Extra).(*obj.TypeInfo); ok {
if ti := lsym.TypeInfo(); ti != nil {
return types.IsDirectIface(ti.Type.(*types.Type))
}
}
@ -2647,10 +2646,7 @@ func isDirectIface1(v *Value, depth int) bool {
return isDirectIface2(v.Args[0], depth-1)
case OpAddr:
lsym := v.Aux.(*obj.LSym)
if lsym.Extra == nil {
return false
}
if ii, ok := (*lsym.Extra).(*obj.ItabInfo); ok {
if ii := lsym.ItabInfo(); ii != nil {
return types.IsDirectIface(ii.Type.(*types.Type))
}
case OpConstNil:
@ -2744,3 +2740,7 @@ func panicBoundsCToAux(p PanicBoundsC) Aux {
func panicBoundsCCToAux(p PanicBoundsCC) Aux {
return p
}
func isDictArgSym(sym Sym) bool {
return sym.(*ir.Name).Sym().Name == typecheck.LocalDictName
}

View file

@ -12148,6 +12148,19 @@ func rewriteBlockLOONG64(b *Block) bool {
b.resetWithControl(BlockLOONG64NEZ, v0)
return true
}
case BlockJumpTable:
// match: (JumpTable idx)
// result: (JUMPTABLE {makeJumpTableSym(b)} idx (MOVVaddr <typ.Uintptr> {makeJumpTableSym(b)} (SB)))
for {
idx := b.Controls[0]
v0 := b.NewValue0(b.Pos, OpLOONG64MOVVaddr, typ.Uintptr)
v0.Aux = symToAux(makeJumpTableSym(b))
v1 := b.NewValue0(b.Pos, OpSB, typ.Uintptr)
v0.AddArg(v1)
b.resetWithControl2(BlockLOONG64JUMPTABLE, idx, v0)
b.Aux = symToAux(makeJumpTableSym(b))
return true
}
case BlockLOONG64LEZ:
// match: (LEZ (MOVVconst [c]) yes no)
// cond: c <= 0

View file

@ -2,7 +2,6 @@
package ssa
import "internal/buildcfg"
import "math"
import "cmd/compile/internal/types"
@ -3202,8 +3201,6 @@ func rewriteValueWasm_OpRsh8x8(v *Value) bool {
}
func rewriteValueWasm_OpSignExt16to32(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (SignExt16to32 x:(I64Load16S _ _))
// result: x
for {
@ -3215,34 +3212,16 @@ func rewriteValueWasm_OpSignExt16to32(v *Value) bool {
return true
}
// match: (SignExt16to32 x)
// cond: buildcfg.GOWASM.SignExt
// result: (I64Extend16S x)
for {
x := v_0
if !(buildcfg.GOWASM.SignExt) {
break
}
v.reset(OpWasmI64Extend16S)
v.AddArg(x)
return true
}
// match: (SignExt16to32 x)
// result: (I64ShrS (I64Shl x (I64Const [48])) (I64Const [48]))
for {
x := v_0
v.reset(OpWasmI64ShrS)
v0 := b.NewValue0(v.Pos, OpWasmI64Shl, typ.Int64)
v1 := b.NewValue0(v.Pos, OpWasmI64Const, typ.Int64)
v1.AuxInt = int64ToAuxInt(48)
v0.AddArg2(x, v1)
v.AddArg2(v0, v1)
return true
}
}
func rewriteValueWasm_OpSignExt16to64(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (SignExt16to64 x:(I64Load16S _ _))
// result: x
for {
@ -3254,34 +3233,16 @@ func rewriteValueWasm_OpSignExt16to64(v *Value) bool {
return true
}
// match: (SignExt16to64 x)
// cond: buildcfg.GOWASM.SignExt
// result: (I64Extend16S x)
for {
x := v_0
if !(buildcfg.GOWASM.SignExt) {
break
}
v.reset(OpWasmI64Extend16S)
v.AddArg(x)
return true
}
// match: (SignExt16to64 x)
// result: (I64ShrS (I64Shl x (I64Const [48])) (I64Const [48]))
for {
x := v_0
v.reset(OpWasmI64ShrS)
v0 := b.NewValue0(v.Pos, OpWasmI64Shl, typ.Int64)
v1 := b.NewValue0(v.Pos, OpWasmI64Const, typ.Int64)
v1.AuxInt = int64ToAuxInt(48)
v0.AddArg2(x, v1)
v.AddArg2(v0, v1)
return true
}
}
func rewriteValueWasm_OpSignExt32to64(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (SignExt32to64 x:(I64Load32S _ _))
// result: x
for {
@ -3293,34 +3254,16 @@ func rewriteValueWasm_OpSignExt32to64(v *Value) bool {
return true
}
// match: (SignExt32to64 x)
// cond: buildcfg.GOWASM.SignExt
// result: (I64Extend32S x)
for {
x := v_0
if !(buildcfg.GOWASM.SignExt) {
break
}
v.reset(OpWasmI64Extend32S)
v.AddArg(x)
return true
}
// match: (SignExt32to64 x)
// result: (I64ShrS (I64Shl x (I64Const [32])) (I64Const [32]))
for {
x := v_0
v.reset(OpWasmI64ShrS)
v0 := b.NewValue0(v.Pos, OpWasmI64Shl, typ.Int64)
v1 := b.NewValue0(v.Pos, OpWasmI64Const, typ.Int64)
v1.AuxInt = int64ToAuxInt(32)
v0.AddArg2(x, v1)
v.AddArg2(v0, v1)
return true
}
}
func rewriteValueWasm_OpSignExt8to16(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (SignExt8to16 x:(I64Load8S _ _))
// result: x
for {
@ -3332,34 +3275,16 @@ func rewriteValueWasm_OpSignExt8to16(v *Value) bool {
return true
}
// match: (SignExt8to16 x)
// cond: buildcfg.GOWASM.SignExt
// result: (I64Extend8S x)
for {
x := v_0
if !(buildcfg.GOWASM.SignExt) {
break
}
v.reset(OpWasmI64Extend8S)
v.AddArg(x)
return true
}
// match: (SignExt8to16 x)
// result: (I64ShrS (I64Shl x (I64Const [56])) (I64Const [56]))
for {
x := v_0
v.reset(OpWasmI64ShrS)
v0 := b.NewValue0(v.Pos, OpWasmI64Shl, typ.Int64)
v1 := b.NewValue0(v.Pos, OpWasmI64Const, typ.Int64)
v1.AuxInt = int64ToAuxInt(56)
v0.AddArg2(x, v1)
v.AddArg2(v0, v1)
return true
}
}
func rewriteValueWasm_OpSignExt8to32(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (SignExt8to32 x:(I64Load8S _ _))
// result: x
for {
@ -3371,34 +3296,16 @@ func rewriteValueWasm_OpSignExt8to32(v *Value) bool {
return true
}
// match: (SignExt8to32 x)
// cond: buildcfg.GOWASM.SignExt
// result: (I64Extend8S x)
for {
x := v_0
if !(buildcfg.GOWASM.SignExt) {
break
}
v.reset(OpWasmI64Extend8S)
v.AddArg(x)
return true
}
// match: (SignExt8to32 x)
// result: (I64ShrS (I64Shl x (I64Const [56])) (I64Const [56]))
for {
x := v_0
v.reset(OpWasmI64ShrS)
v0 := b.NewValue0(v.Pos, OpWasmI64Shl, typ.Int64)
v1 := b.NewValue0(v.Pos, OpWasmI64Const, typ.Int64)
v1.AuxInt = int64ToAuxInt(56)
v0.AddArg2(x, v1)
v.AddArg2(v0, v1)
return true
}
}
func rewriteValueWasm_OpSignExt8to64(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (SignExt8to64 x:(I64Load8S _ _))
// result: x
for {
@ -3410,29 +3317,13 @@ func rewriteValueWasm_OpSignExt8to64(v *Value) bool {
return true
}
// match: (SignExt8to64 x)
// cond: buildcfg.GOWASM.SignExt
// result: (I64Extend8S x)
for {
x := v_0
if !(buildcfg.GOWASM.SignExt) {
break
}
v.reset(OpWasmI64Extend8S)
v.AddArg(x)
return true
}
// match: (SignExt8to64 x)
// result: (I64ShrS (I64Shl x (I64Const [56])) (I64Const [56]))
for {
x := v_0
v.reset(OpWasmI64ShrS)
v0 := b.NewValue0(v.Pos, OpWasmI64Shl, typ.Int64)
v1 := b.NewValue0(v.Pos, OpWasmI64Const, typ.Int64)
v1.AuxInt = int64ToAuxInt(56)
v0.AddArg2(x, v1)
v.AddArg2(v0, v1)
return true
}
}
func rewriteValueWasm_OpSlicemask(v *Value) bool {
v_0 := v.Args[0]

View file

@ -21395,6 +21395,21 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool {
v.copyOf(ptr)
return true
}
// match: (NilCheck ptr:(Arg {sym}) _)
// cond: isDictArgSym(sym)
// result: ptr
for {
ptr := v_0
if ptr.Op != OpArg {
break
}
sym := auxToSym(ptr.Aux)
if !(isDictArgSym(sym)) {
break
}
v.copyOf(ptr)
return true
}
// match: (NilCheck ptr:(NilCheck _ _) _ )
// result: ptr
for {

View file

@ -14,7 +14,6 @@ import (
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/obj/wasm"
"internal/buildcfg"
)
/*
@ -425,27 +424,11 @@ func ssaGenValueOnStack(s *ssagen.State, v *ssa.Value, extend bool) {
case ssa.OpWasmI64TruncSatF32S, ssa.OpWasmI64TruncSatF64S:
getValue64(s, v.Args[0])
if buildcfg.GOWASM.SatConv {
s.Prog(v.Op.Asm())
} else {
if v.Op == ssa.OpWasmI64TruncSatF32S {
s.Prog(wasm.AF64PromoteF32)
}
p := s.Prog(wasm.ACall)
p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmTruncS}
}
s.Prog(v.Op.Asm())
case ssa.OpWasmI64TruncSatF32U, ssa.OpWasmI64TruncSatF64U:
getValue64(s, v.Args[0])
if buildcfg.GOWASM.SatConv {
s.Prog(v.Op.Asm())
} else {
if v.Op == ssa.OpWasmI64TruncSatF32U {
s.Prog(wasm.AF64PromoteF32)
}
p := s.Prog(wasm.ACall)
p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: ir.Syms.WasmTruncU}
}
s.Prog(v.Op.Asm())
case ssa.OpWasmF32DemoteF64:
getValue64(s, v.Args[0])

View file

@ -1280,11 +1280,6 @@
// The -json flag prints the final go.mod file in JSON format instead of
// writing it back to go.mod. The JSON output corresponds to these Go types:
//
// type Module struct {
// Path string
// Version string
// }
//
// type GoMod struct {
// Module ModPath
// Go string
@ -1294,6 +1289,13 @@
// Exclude []Module
// Replace []Replace
// Retract []Retract
// Tool []Tool
// Ignore []Ignore
// }
//
// type Module struct {
// Path string
// Version string
// }
//
// type ModPath struct {

View file

@ -27,10 +27,10 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"strings"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
modzip "golang.org/x/mod/zip"
)
@ -61,7 +61,7 @@ func main() {
// Must have valid version, and must not overwrite existing file.
version := flag.Arg(0)
if !regexp.MustCompile(`^v\d+\.\d+\.\d+$`).MatchString(version) {
if semver.Canonical(version) != version {
log.Fatalf("invalid version %q; must be vX.Y.Z", version)
}
if _, err := os.Stat(version + ".zip"); err == nil {
@ -117,7 +117,9 @@ func main() {
if !bytes.Contains(contents, []byte(returnLine)) {
log.Fatalf("did not find %q in fips140.go", returnLine)
}
newLine := `return "` + version + `"`
// Use only the vX.Y.Z part of a possible vX.Y.Z-hash version.
v, _, _ := strings.Cut(version, "-")
newLine := `return "` + v + `"`
contents = bytes.ReplaceAll(contents, []byte(returnLine), []byte(newLine))
wf, err := zw.Create(f.Name)
if err != nil {

View file

@ -104,11 +104,6 @@ writing it back to go.mod.
The -json flag prints the final go.mod file in JSON format instead of
writing it back to go.mod. The JSON output corresponds to these Go types:
type Module struct {
Path string
Version string
}
type GoMod struct {
Module ModPath
Go string
@ -118,6 +113,13 @@ writing it back to go.mod. The JSON output corresponds to these Go types:
Exclude []Module
Replace []Replace
Retract []Retract
Tool []Tool
Ignore []Ignore
}
type Module struct {
Path string
Version string
}
type ModPath struct {

View file

@ -1,4 +1,4 @@
env snap=v1.0.0
env snap=v1.0.0-c2097c7c
env alias=inprocess
env GOFIPS140=$snap
@ -23,8 +23,7 @@ stdout crypto/internal/fips140/$snap/sha256
! stdout crypto/internal/fips140/check
# again with GOFIPS140=$alias
# TODO: enable when we add inprocess.txt
# env GOFIPS140=$alias
env GOFIPS140=$alias
# default GODEBUG includes fips140=on
go list -f '{{.DefaultGODEBUG}}'

View file

@ -1,3 +1,5 @@
skip # a 5s timeout is never going to be reliable (go.dev/issue/72140)
[!fuzz] skip
[short] skip
env GOCACHE=$WORK/cache

View file

@ -464,7 +464,7 @@ type LSym struct {
P []byte
R []Reloc
Extra *interface{} // *FuncInfo, *VarInfo, *FileInfo, or *TypeInfo, if present
Extra *interface{} // *FuncInfo, *VarInfo, *FileInfo, *TypeInfo, or *ItabInfo, if present
Pkg string
PkgIdx int32
@ -604,6 +604,15 @@ func (s *LSym) NewTypeInfo() *TypeInfo {
return t
}
// TypeInfo returns the *TypeInfo associated with s, or else nil.
func (s *LSym) TypeInfo() *TypeInfo {
if s.Extra == nil {
return nil
}
t, _ := (*s.Extra).(*TypeInfo)
return t
}
// An ItabInfo contains information for a symbol
// that contains a runtime.itab.
type ItabInfo struct {
@ -620,6 +629,15 @@ func (s *LSym) NewItabInfo() *ItabInfo {
return t
}
// ItabInfo returns the *ItabInfo associated with s, or else nil.
func (s *LSym) ItabInfo() *ItabInfo {
if s.Extra == nil {
return nil
}
i, _ := (*s.Extra).(*ItabInfo)
return i
}
// WasmImport represents a WebAssembly (WASM) imported function with
// parameters and results translated into WASM types based on the Go function
// declaration.

View file

@ -707,6 +707,15 @@ func span0(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
// so instruction sequences that use REGTMP are unsafe to
// preempt asynchronously.
obj.MarkUnsafePoints(c.ctxt, c.cursym.Func().Text, c.newprog, c.isUnsafePoint, c.isRestartable)
// Now that we know byte offsets, we can generate jump table entries.
for _, jt := range cursym.Func().JumpTables {
for i, p := range jt.Targets {
// The ith jumptable entry points to the p.Pc'th
// byte in the function symbol s.
jt.Sym.WriteAddr(ctxt, int64(i)*8, 8, cursym, p.Pc)
}
}
}
// isUnsafePoint returns whether p is an unsafe point.

View file

@ -324,7 +324,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
q = c.ctxt.StartUnsafePoint(q, c.newprog)
q = obj.Appendp(q, newprog)
q.As = AMOVVP
q.As = mov
q.Pos = p.Pos
q.From.Type = obj.TYPE_REG
q.From.Reg = REGLINK

View file

@ -145,6 +145,7 @@ var ArchLoong64 = &Arch{
MinLC: 4,
Alignment: 8, // Unaligned accesses are not guaranteed to be fast
CanMergeLoads: true,
CanJumpTable: true,
HasLR: true,
FixedFrameSize: 8, // LR
}

View file

@ -361,6 +361,13 @@ func TestDWARFLocationList(t *testing.T) {
func TestFlagW(t *testing.T) {
testenv.MustHaveGoBuild(t)
if runtime.GOOS == "aix" {
t.Skip("internal/xcoff cannot parse file without symbol table")
}
if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
}
t.Parallel()
tmpdir := t.TempDir()
@ -379,7 +386,7 @@ func TestFlagW(t *testing.T) {
{"-s", false}, // -s implies -w
{"-s -w=0", true}, // -w=0 negates the implied -w
}
if testenv.HasCGO() {
if testenv.HasCGO() && runtime.GOOS != "solaris" { // Solaris linker doesn't support the -S flag
tests = append(tests,
testCase{"-w -linkmode=external", false},
testCase{"-s -linkmode=external", false},

View file

@ -224,6 +224,28 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
}
return true
case objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_SUBTRACTOR*2:
// ARM64_RELOC_SUBTRACTOR must be followed by ARM64_RELOC_UNSIGNED.
// The pair of relocations resolves to the difference between two
// symbol addresses (each relocation specifies a symbol).
outer, off := ld.FoldSubSymbolOffset(ldr, targ)
if outer != s {
// TODO: support subtracted symbol in different section.
ldr.Errorf(s, "unsupported ARM64_RELOC_SUBTRACTOR reloc: target %s, outer %s", ldr.SymName(targ), ldr.SymName(outer))
break
}
su := ldr.MakeSymbolUpdater(s)
relocs := su.Relocs()
if rIdx+1 >= relocs.Count() || relocs.At(rIdx+1).Type() != objabi.MachoRelocOffset+ld.MACHO_ARM64_RELOC_UNSIGNED*2 || relocs.At(rIdx+1).Off() != r.Off() {
ldr.Errorf(s, "unexpected ARM64_RELOC_SUBTRACTOR reloc, must be followed by ARM64_RELOC_UNSIGNED at same offset")
break
}
su.SetRelocType(rIdx+1, objabi.R_PCREL)
su.SetRelocAdd(rIdx+1, r.Add()+int64(r.Off())+int64(r.Siz())-off)
// Remove the other relocation
su.SetRelocSiz(rIdx, 0)
return true
case objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_BRANCH26*2 + pcrel:
su := ldr.MakeSymbolUpdater(s)
su.SetRelocType(rIdx, objabi.R_CALLARM64)
@ -277,6 +299,17 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
su.SetRelocSym(rIdx, syms.GOT)
su.SetRelocAdd(rIdx, int64(ldr.SymGot(targ)))
return true
case objabi.MachoRelocOffset + ld.MACHO_ARM64_RELOC_POINTER_TO_GOT*2 + pcrel:
if targType != sym.SDYNIMPORT {
ldr.Errorf(s, "unexpected GOT reloc for non-dynamic symbol %s", ldr.SymName(targ))
}
ld.AddGotSym(target, ldr, syms, targ, 0)
su := ldr.MakeSymbolUpdater(s)
su.SetRelocType(rIdx, objabi.R_PCREL)
su.SetRelocSym(rIdx, syms.GOT)
su.SetRelocAdd(rIdx, r.Add()+int64(r.Siz())+int64(ldr.SymGot(targ)))
return true
}
// Reread the reloc to incorporate any changes in type above.

View file

@ -1452,7 +1452,9 @@ func (ctxt *Link) hostlink() {
argv = append(argv, "-s")
}
} else if *FlagW {
argv = append(argv, "-Wl,-S") // suppress debugging symbols
if !ctxt.IsAIX() && !ctxt.IsSolaris() { // The AIX and Solaris linkers' -S has different meaning
argv = append(argv, "-Wl,-S") // suppress debugging symbols
}
}
// On darwin, whether to combine DWARF into executable.
@ -1770,7 +1772,8 @@ func (ctxt *Link) hostlink() {
}
// Force global symbols to be exported for dlopen, etc.
if ctxt.IsELF {
switch {
case ctxt.IsELF:
if ctxt.DynlinkingGo() || ctxt.BuildMode == BuildModeCShared || !linkerFlagSupported(ctxt.Arch, argv[0], altLinker, "-Wl,--export-dynamic-symbol=main") {
argv = append(argv, "-rdynamic")
} else {
@ -1781,10 +1784,16 @@ func (ctxt *Link) hostlink() {
sort.Strings(exports)
argv = append(argv, exports...)
}
}
if ctxt.HeadType == objabi.Haix {
case ctxt.IsAIX():
fileName := xcoffCreateExportFile(ctxt)
argv = append(argv, "-Wl,-bE:"+fileName)
case ctxt.IsWindows() && !slices.Contains(flagExtldflags, wlPrefix+"export-all-symbols"):
fileName := peCreateExportFile(ctxt, filepath.Base(outopt))
prefix := ""
if isMSVC {
prefix = "-Wl,-def:"
}
argv = append(argv, prefix+fileName)
}
const unusedArguments = "-Qunused-arguments"

View file

@ -106,11 +106,13 @@ const (
MACHO_ARM_RELOC_SECTDIFF = 2
MACHO_ARM_RELOC_BR24 = 5
MACHO_ARM64_RELOC_UNSIGNED = 0
MACHO_ARM64_RELOC_SUBTRACTOR = 1
MACHO_ARM64_RELOC_BRANCH26 = 2
MACHO_ARM64_RELOC_PAGE21 = 3
MACHO_ARM64_RELOC_PAGEOFF12 = 4
MACHO_ARM64_RELOC_GOT_LOAD_PAGE21 = 5
MACHO_ARM64_RELOC_GOT_LOAD_PAGEOFF12 = 6
MACHO_ARM64_RELOC_POINTER_TO_GOT = 7
MACHO_ARM64_RELOC_ADDEND = 10
MACHO_GENERIC_RELOC_VANILLA = 0
MACHO_FAKE_GOTPCREL = 100

View file

@ -8,6 +8,7 @@
package ld
import (
"bytes"
"cmd/internal/objabi"
"cmd/internal/sys"
"cmd/link/internal/loader"
@ -17,6 +18,8 @@ import (
"fmt"
"internal/buildcfg"
"math"
"os"
"path/filepath"
"slices"
"sort"
"strconv"
@ -1748,3 +1751,44 @@ func asmbPe(ctxt *Link) {
pewrite(ctxt)
}
// peCreateExportFile creates a file with exported symbols for Windows .def files.
// ld will export all symbols, even those not marked for export, unless a .def file is provided.
func peCreateExportFile(ctxt *Link, libName string) (fname string) {
fname = filepath.Join(*flagTmpdir, "export_file.def")
var buf bytes.Buffer
fmt.Fprintf(&buf, "LIBRARY %s\n", libName)
buf.WriteString("EXPORTS\n")
ldr := ctxt.loader
var exports []string
for s := range ldr.ForAllCgoExportStatic() {
extname := ldr.SymExtname(s)
if !strings.HasPrefix(extname, "_cgoexp_") {
continue
}
if ldr.IsFileLocal(s) {
continue // Only export non-static symbols
}
// Retrieve the name of the initial symbol
// exported by cgo.
// The corresponding Go symbol is:
// _cgoexp_hashcode_symname.
name := strings.SplitN(extname, "_", 4)[3]
exports = append(exports, name)
}
if len(exports) == 0 {
// See runtime/cgo/windows.go for details.
exports = append(exports, "_cgo_stub_export")
}
sort.Strings(exports)
buf.WriteString(strings.Join(exports, "\n"))
err := os.WriteFile(fname, buf.Bytes(), 0666)
if err != nil {
Errorf("WriteFile %s failed: %v", fname, err)
}
return fname
}

View file

@ -645,7 +645,7 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
sliceSym(pcln.funcnametab)
// The cutab slice
sliceSym(pcln.cutab)
slice(pcln.cutab, uint64(ldr.SymSize(pcln.cutab))/4)
// The filetab slice
sliceSym(pcln.filetab)
@ -654,7 +654,7 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
sliceSym(pcln.pctab)
// The pclntab slice
slice(pcln.pclntab, uint64(ldr.SymSize(pcln.pclntab)))
sliceSym(pcln.pclntab)
// The ftab slice
slice(pcln.pclntab, uint64(pcln.nfunc+1))

View file

@ -1779,10 +1779,7 @@ func xcoffCreateExportFile(ctxt *Link) (fname string) {
var buf bytes.Buffer
ldr := ctxt.loader
for s, nsym := loader.Sym(1), loader.Sym(ldr.NSym()); s < nsym; s++ {
if !ldr.AttrCgoExport(s) {
continue
}
for s := range ldr.ForAllCgoExportStatic() {
extname := ldr.SymExtname(s)
if !strings.HasPrefix(extname, "._cgoexp_") {
continue

View file

@ -16,6 +16,7 @@ import (
"fmt"
"internal/abi"
"io"
"iter"
"log"
"math/bits"
"os"
@ -1109,6 +1110,18 @@ func (l *Loader) SetAttrCgoExportStatic(i Sym, v bool) {
}
}
// ForAllCgoExportStatic returns an iterator over all symbols
// marked with the "cgo_export_static" compiler directive.
func (l *Loader) ForAllCgoExportStatic() iter.Seq[Sym] {
return func(yield func(Sym) bool) {
for s := range l.attrCgoExportStatic {
if !yield(s) {
break
}
}
}
}
// IsGeneratedSym returns true if a symbol's been previously marked as a
// generator symbol through the SetIsGeneratedSym. The functions for generator
// symbols are kept in the Link context.
@ -2437,6 +2450,9 @@ var blockedLinknames = map[string][]string{
"sync_test.runtime_blockUntilEmptyCleanupQueue": {"sync_test"},
"time.runtimeIsBubbled": {"time"},
"unique.runtime_blockUntilEmptyCleanupQueue": {"unique"},
// Experimental features
"runtime.goroutineLeakGC": {"runtime/pprof"},
"runtime.goroutineleakcount": {"runtime/pprof"},
// Others
"net.newWindowsFile": {"net"}, // pushed from os
"testing/synctest.testingSynctestTest": {"testing/synctest"}, // pushed from testing

View file

@ -20,7 +20,7 @@ type MakeHash func() hash.Hash
// TestHash performs a set of tests on hash.Hash implementations, checking the
// documented requirements of Write, Sum, Reset, Size, and BlockSize.
func TestHash(t *testing.T, mh MakeHash) {
if boring.Enabled || fips140.Version() == "v1.0" {
if boring.Enabled || fips140.Version() == "v1.0.0" {
testhash.TestHashWithoutClone(t, testhash.MakeHash(mh))
return
}

View file

@ -3,9 +3,11 @@
// license that can be found in the LICENSE file.
// Package entropy provides the passive entropy source for the FIPS 140-3
// module. It is only used in FIPS mode by [crypto/internal/fips140/drbg.Read].
// module. It is only used in FIPS mode by [crypto/internal/fips140/drbg.Read]
// from the FIPS 140-3 Go Cryptographic Module v1.0.0. Later versions of the
// module have an internal CPU jitter-based entropy source.
//
// This complies with IG 9.3.A, Additional Comment 12, which until January 1,
// This complied with IG 9.3.A, Additional Comment 12, which until January 1,
// 2026 allows new modules to meet an [earlier version] of Resolution 2(b):
// "A software module that contains an approved DRBG that receives a LOAD
// command (or its logical equivalent) with entropy obtained from [...] inside

View file

@ -56,9 +56,10 @@ func CAST(name string, f func() error) {
}
// PCT runs the named Pairwise Consistency Test (if operated in FIPS mode) and
// returns any errors. If an error is returned, the key must not be used.
// aborts the program (stopping the module input/output and entering the "error
// state") if the test fails.
//
// PCTs are mandatory for every key pair that is generated/imported, including
// PCTs are mandatory for every generated (but not imported) key pair, including
// ephemeral keys (which effectively doubles the cost of key establishment). See
// Implementation Guidance 10.3.A Additional Comment 1.
//
@ -66,17 +67,23 @@ func CAST(name string, f func() error) {
//
// If a package p calls PCT during key generation, an invocation of that
// function should be added to fipstest.TestConditionals.
func PCT(name string, f func() error) error {
func PCT(name string, f func() error) {
if strings.ContainsAny(name, ",#=:") {
panic("fips: invalid self-test name: " + name)
}
if !Enabled {
return nil
return
}
err := f()
if name == failfipscast {
err = errors.New("simulated PCT failure")
}
return err
if err != nil {
fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
panic("unreachable")
}
if debug {
println("FIPS 140-3 PCT passed:", name)
}
}

View file

@ -9,21 +9,53 @@
package drbg
import (
"crypto/internal/entropy"
"crypto/internal/fips140"
"crypto/internal/fips140/entropy"
"crypto/internal/randutil"
"crypto/internal/sysrand"
"io"
"sync"
"sync/atomic"
)
var drbgs = sync.Pool{
// memory is a scratch buffer that is accessed between samples by the entropy
// source to expose it to memory access timings.
//
// We reuse it and share it between Seed calls to avoid the significant (~500µs)
// cost of zeroing a new allocation every time. The entropy source accesses it
// using atomics (and doesn't care about its contents).
//
// It should end up in the .noptrbss section, and become backed by physical pages
// at first use. This ensures that programs that do not use the FIPS 140-3 module
// do not incur any memory use or initialization penalties.
var memory entropy.ScratchBuffer
func getEntropy() *[SeedSize]byte {
var retries int
seed, err := entropy.Seed(&memory)
for err != nil {
// The CPU jitter-based SP 800-90B entropy source has a non-negligible
// chance of failing the startup health tests.
//
// Each time it does, it enters a permanent failure state, and we
// restart it anew. This is not expected to happen more than a few times
// in a row.
if retries++; retries > 100 {
panic("fips140/drbg: failed to obtain initial entropy")
}
seed, err = entropy.Seed(&memory)
}
return &seed
}
// getEntropy is very slow (~500µs), so we don't want it on the hot path.
// We keep both a persistent DRBG instance and a pool of additional instances.
// Occasional uses will use drbgInstance, even if the pool was emptied since the
// last use. Frequent concurrent uses will fill the pool and use it.
var drbgInstance atomic.Pointer[Counter]
var drbgPool = sync.Pool{
New: func() any {
var c *Counter
entropy.Depleted(func(seed *[48]byte) {
c = NewCounter(seed)
})
return c
return NewCounter(getEntropy())
},
}
@ -44,8 +76,15 @@ func Read(b []byte) {
additionalInput := new([SeedSize]byte)
sysrand.Read(additionalInput[:16])
drbg := drbgs.Get().(*Counter)
defer drbgs.Put(drbg)
drbg := drbgInstance.Swap(nil)
if drbg == nil {
drbg = drbgPool.Get().(*Counter)
}
defer func() {
if !drbgInstance.CompareAndSwap(nil, drbg) {
drbgPool.Put(drbg)
}
}()
for len(b) > 0 {
size := min(len(b), maxRequestSize)
@ -54,9 +93,7 @@ func Read(b []byte) {
// Section 9.3.2: if Generate reports a reseed is required, the
// additional input is passed to Reseed along with the entropy and
// then nulled before the next Generate call.
entropy.Depleted(func(seed *[48]byte) {
drbg.Reseed(seed, additionalInput)
})
drbg.Reseed(getEntropy(), additionalInput)
additionalInput = nil
continue
}

View file

@ -161,6 +161,27 @@ func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
if err != nil {
continue
}
// A "Pairwise Consistency Test" makes no sense if we just generated the
// public key from an ephemeral private key. Moreover, there is no way to
// check it aside from redoing the exact same computation again. SP 800-56A
// Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it.
// However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a
// PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional
// Comment 1 goes out of its way to say that "the PCT shall be performed
// consistent [...], even if the underlying standard does not require a
// PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode.
fips140.PCT("ECDH PCT", func() error {
p1, err := c.newPoint().ScalarBaseMult(privateKey.d)
if err != nil {
return err
}
if !bytes.Equal(p1.Bytes(), privateKey.pub.q) {
return errors.New("crypto/ecdh: public key does not match private key")
}
return nil
})
return privateKey, nil
}
}
@ -188,28 +209,6 @@ func NewPrivateKey[P Point[P]](c *Curve[P], key []byte) (*PrivateKey, error) {
panic("crypto/ecdh: internal error: public key is the identity element")
}
// A "Pairwise Consistency Test" makes no sense if we just generated the
// public key from an ephemeral private key. Moreover, there is no way to
// check it aside from redoing the exact same computation again. SP 800-56A
// Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it.
// However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a
// PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional
// Comment 1 goes out of its way to say that "the PCT shall be performed
// consistent [...], even if the underlying standard does not require a
// PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode.
if err := fips140.PCT("ECDH PCT", func() error {
p1, err := c.newPoint().ScalarBaseMult(key)
if err != nil {
return err
}
if !bytes.Equal(p1.Bytes(), publicKey) {
return errors.New("crypto/ecdh: public key does not match private key")
}
return nil
}); err != nil {
panic(err)
}
k := &PrivateKey{d: bytes.Clone(key), pub: PublicKey{curve: c.curve, q: publicKey}}
return k, nil
}

View file

@ -51,8 +51,8 @@ func testHash() []byte {
}
}
func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error {
return fips140.PCT("ECDSA PCT", func() error {
func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) {
fips140.PCT("ECDSA PCT", func() error {
hash := testHash()
drbg := newDRBG(sha512.New, k.d, bits2octets(P256(), hash), nil)
sig, err := sign(c, k, drbg, hash)

View file

@ -167,11 +167,6 @@ func NewPrivateKey[P Point[P]](c *Curve[P], D, Q []byte) (*PrivateKey, error) {
return nil, err
}
priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)}
if err := fipsPCT(c, priv); err != nil {
// This can happen if the application went out of its way to make an
// ecdsa.PrivateKey with a mismatching PublicKey.
return nil, err
}
return priv, nil
}
@ -204,10 +199,7 @@ func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
},
d: k.Bytes(c.N),
}
if err := fipsPCT(c, priv); err != nil {
// This clearly can't happen, but FIPS 140-3 mandates that we check it.
panic(err)
}
fipsPCT(c, priv)
return priv, nil
}

View file

@ -122,7 +122,7 @@ func newDRBG[H hash.Hash](hash func() H, entropy, nonce []byte, s personalizatio
//
// This should only be used for ACVP testing. hmacDRBG is not intended to be
// used directly.
func TestingOnlyNewDRBG(hash func() hash.Hash, entropy, nonce []byte, s []byte) *hmacDRBG {
func TestingOnlyNewDRBG[H hash.Hash](hash func() H, entropy, nonce []byte, s []byte) *hmacDRBG {
return newDRBG(hash, entropy, nonce, plainPersonalizationString(s))
}

View file

@ -12,8 +12,8 @@ import (
"sync"
)
func fipsPCT(k *PrivateKey) error {
return fips140.PCT("Ed25519 sign and verify PCT", func() error {
func fipsPCT(k *PrivateKey) {
fips140.PCT("Ed25519 sign and verify PCT", func() error {
return pairwiseTest(k)
})
}

View file

@ -69,10 +69,7 @@ func generateKey(priv *PrivateKey) (*PrivateKey, error) {
fips140.RecordApproved()
drbg.Read(priv.seed[:])
precomputePrivateKey(priv)
if err := fipsPCT(priv); err != nil {
// This clearly can't happen, but FIPS 140-3 requires that we check.
panic(err)
}
fipsPCT(priv)
return priv, nil
}
@ -88,10 +85,6 @@ func newPrivateKeyFromSeed(priv *PrivateKey, seed []byte) (*PrivateKey, error) {
}
copy(priv.seed[:], seed)
precomputePrivateKey(priv)
if err := fipsPCT(priv); err != nil {
// This clearly can't happen, but FIPS 140-3 requires that we check.
panic(err)
}
return priv, nil
}
@ -137,12 +130,6 @@ func newPrivateKey(priv *PrivateKey, privBytes []byte) (*PrivateKey, error) {
copy(priv.prefix[:], h[32:])
if err := fipsPCT(priv); err != nil {
// This can happen if the application messed with the private key
// encoding, and the public key doesn't match the seed anymore.
return nil, err
}
return priv, nil
}

View file

@ -0,0 +1,202 @@
// 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 entropy implements a CPU jitter-based SP 800-90B entropy source.
package entropy
import (
"crypto/internal/fips140deps/time"
"errors"
"sync/atomic"
"unsafe"
)
// Version returns the version of the entropy source.
//
// This is independent of the FIPS 140-3 module version, in order to reuse the
// ESV certificate across module versions.
func Version() string {
return "v1.0.0"
}
// ScratchBuffer is a large buffer that will be written to using atomics, to
// generate noise from memory access timings. Its contents do not matter.
type ScratchBuffer [1 << 25]byte
// Seed returns a 384-bit seed with full entropy.
//
// memory is passed in to allow changing the allocation strategy without
// modifying the frozen and certified entropy source in this package.
//
// Seed returns an error if the entropy source startup health tests fail, which
// has a non-negligible chance of happening.
func Seed(memory *ScratchBuffer) ([48]byte, error) {
// Collect w = 1024 samples, each certified to provide no less than h = 0.5
// bits of entropy, for a total of hᵢₙ = w × h = 512 bits of entropy, over
// nᵢₙ = w × n = 8192 bits of input data.
var samples [1024]byte
if err := Samples(samples[:], memory); err != nil {
return [48]byte{}, err
}
// Use a vetted unkeyed conditioning component, SHA-384, with nw = 384 and
// nₒᵤₜ = 384. Per the formula in SP 800-90B Section 3.1.5.1.2, the output
// entropy hₒᵤₜ is:
//
// sage: n_in = 8192
// sage: n_out = 384
// sage: nw = 384
// sage: h_in = 512
// sage: P_high = 2^(-h_in)
// sage: P_low = (1 - P_high) / (2^n_in - 1)
// sage: n = min(n_out, nw)
// sage: ψ = 2^(n_in - n) * P_low + P_high
// sage: U = 2^(n_in - n) + sqrt(2 * n * 2^(n_in - n) * ln(2))
// sage: ω = U * P_low
// sage: h_out = -log(max(ψ, ω), 2)
// sage: h_out.n()
// 384.000000000000
//
// According to Implementation Guidance D.K, Resolution 19, since
//
// - the conditioning component is vetted,
// - hᵢₙ = 512 ≥ nₒᵤₜ + 64 = 448, and
// - nₒᵤₜ ≤ security strength of SHA-384 = 384 (per SP 800-107 Rev. 1, Table 1),
//
// we can claim the output has full entropy.
return SHA384(&samples), nil
}
// Samples starts a new entropy source, collects the requested number of
// samples, conducts startup health tests, and returns the samples or an error
// if the health tests fail.
//
// The health tests have a non-negligible chance of failing.
func Samples(samples []uint8, memory *ScratchBuffer) error {
if len(samples) < 1024 {
return errors.New("entropy: at least 1024 samples are required for startup health tests")
}
s := newSource(memory)
for range 4 {
// Warm up the source to avoid any initial bias.
_ = s.Sample()
}
for i := range samples {
samples[i] = s.Sample()
}
if err := RepetitionCountTest(samples); err != nil {
return err
}
if err := AdaptiveProportionTest(samples); err != nil {
return err
}
return nil
}
type source struct {
memory *ScratchBuffer
lcgState uint32
previous int64
}
func newSource(memory *ScratchBuffer) *source {
return &source{
memory: memory,
lcgState: uint32(time.HighPrecisionNow()),
previous: time.HighPrecisionNow(),
}
}
// touchMemory performs a write to memory at the given index.
//
// The memory slice is passed in and may be shared across sources e.g. to avoid
// the significant (~500µs) cost of zeroing a new allocation on every [Seed] call.
func touchMemory(memory *ScratchBuffer, idx uint32) {
idx = idx / 4 * 4 // align to 32 bits
u32 := (*uint32)(unsafe.Pointer(&memory[idx]))
last := atomic.LoadUint32(u32)
atomic.SwapUint32(u32, last+13)
}
func (s *source) Sample() uint8 {
// Perform a few memory accesses in an unpredictable pattern to expose the
// next measurement to as much system noise as possible.
memory, lcgState := s.memory, s.lcgState
_ = memory[0] // hoist the nil check out of touchMemory
for range 64 {
lcgState = 1664525*lcgState + 1013904223
// Discard the lower bits, which tend to fall into short cycles.
idx := (lcgState >> 6) & (1<<25 - 1)
touchMemory(memory, idx)
}
s.lcgState = lcgState
t := time.HighPrecisionNow()
sample := t - s.previous
s.previous = t
// Reduce the symbol space to 256 values, assuming most of the entropy is in
// the least-significant bits, which represent the highest-resolution timing
// differences.
return uint8(sample)
}
// RepetitionCountTest implements the repetition count test from SP 800-90B
// Section 4.4.1. It returns an error if any symbol is repeated C = 41 or more
// times in a row.
//
// This C value is calculated from a target failure probability α = 2⁻²⁰ and a
// claimed min-entropy per symbol h = 0.5 bits, using the formula in SP 800-90B
// Section 4.4.1.
//
// sage: α = 2^-20
// sage: H = 0.5
// sage: 1 + ceil(-log(α, 2) / H)
// 41
func RepetitionCountTest(samples []uint8) error {
x := samples[0]
count := 1
for _, y := range samples[1:] {
if y == x {
count++
if count >= 41 {
return errors.New("entropy: repetition count health test failed")
}
} else {
x = y
count = 1
}
}
return nil
}
// AdaptiveProportionTest implements the adaptive proportion test from SP 800-90B
// Section 4.4.2. It returns an error if any symbol appears C = 410 or more
// times in the last W = 512 samples.
//
// This C value is calculated from a target failure probability α = 2⁻²⁰, a
// window size W = 512, and a claimed min-entropy per symbol h = 0.5 bits, using
// the formula in SP 800-90B Section 4.4.2, equivalent to the Microsoft Excel
// formula 1+CRITBINOM(W, power(2,(H)),1α).
//
// sage: from scipy.stats import binom
// sage: α = 2^-20
// sage: H = 0.5
// sage: W = 512
// sage: C = 1 + binom.ppf(1 - α, W, 2**(-H))
// sage: ceil(C)
// 410
func AdaptiveProportionTest(samples []uint8) error {
var counts [256]int
for i, x := range samples {
counts[x]++
if i >= 512 {
counts[samples[i-512]]--
}
if counts[x] >= 410 {
return errors.New("entropy: adaptive proportion health test failed")
}
}
return nil
}

View file

@ -0,0 +1,191 @@
// 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 entropy
import "math/bits"
// This file includes a SHA-384 implementation to insulate the entropy source
// from any changes in the FIPS 140-3 module's crypto/internal/fips140/sha512
// package. We only support 1024-byte inputs.
func SHA384(p *[1024]byte) [48]byte {
h := [8]uint64{
0xcbbb9d5dc1059ed8,
0x629a292a367cd507,
0x9159015a3070dd17,
0x152fecd8f70e5939,
0x67332667ffc00b31,
0x8eb44a8768581511,
0xdb0c2e0d64f98fa7,
0x47b5481dbefa4fa4,
}
sha384Block(&h, (*[128]byte)(p[0:128]))
sha384Block(&h, (*[128]byte)(p[128:256]))
sha384Block(&h, (*[128]byte)(p[256:384]))
sha384Block(&h, (*[128]byte)(p[384:512]))
sha384Block(&h, (*[128]byte)(p[512:640]))
sha384Block(&h, (*[128]byte)(p[640:768]))
sha384Block(&h, (*[128]byte)(p[768:896]))
sha384Block(&h, (*[128]byte)(p[896:1024]))
var padlen [128]byte
padlen[0] = 0x80
bePutUint64(padlen[112+8:], 1024*8)
sha384Block(&h, &padlen)
var digest [48]byte
bePutUint64(digest[0:], h[0])
bePutUint64(digest[8:], h[1])
bePutUint64(digest[16:], h[2])
bePutUint64(digest[24:], h[3])
bePutUint64(digest[32:], h[4])
bePutUint64(digest[40:], h[5])
return digest
}
var _K = [...]uint64{
0x428a2f98d728ae22,
0x7137449123ef65cd,
0xb5c0fbcfec4d3b2f,
0xe9b5dba58189dbbc,
0x3956c25bf348b538,
0x59f111f1b605d019,
0x923f82a4af194f9b,
0xab1c5ed5da6d8118,
0xd807aa98a3030242,
0x12835b0145706fbe,
0x243185be4ee4b28c,
0x550c7dc3d5ffb4e2,
0x72be5d74f27b896f,
0x80deb1fe3b1696b1,
0x9bdc06a725c71235,
0xc19bf174cf692694,
0xe49b69c19ef14ad2,
0xefbe4786384f25e3,
0x0fc19dc68b8cd5b5,
0x240ca1cc77ac9c65,
0x2de92c6f592b0275,
0x4a7484aa6ea6e483,
0x5cb0a9dcbd41fbd4,
0x76f988da831153b5,
0x983e5152ee66dfab,
0xa831c66d2db43210,
0xb00327c898fb213f,
0xbf597fc7beef0ee4,
0xc6e00bf33da88fc2,
0xd5a79147930aa725,
0x06ca6351e003826f,
0x142929670a0e6e70,
0x27b70a8546d22ffc,
0x2e1b21385c26c926,
0x4d2c6dfc5ac42aed,
0x53380d139d95b3df,
0x650a73548baf63de,
0x766a0abb3c77b2a8,
0x81c2c92e47edaee6,
0x92722c851482353b,
0xa2bfe8a14cf10364,
0xa81a664bbc423001,
0xc24b8b70d0f89791,
0xc76c51a30654be30,
0xd192e819d6ef5218,
0xd69906245565a910,
0xf40e35855771202a,
0x106aa07032bbd1b8,
0x19a4c116b8d2d0c8,
0x1e376c085141ab53,
0x2748774cdf8eeb99,
0x34b0bcb5e19b48a8,
0x391c0cb3c5c95a63,
0x4ed8aa4ae3418acb,
0x5b9cca4f7763e373,
0x682e6ff3d6b2b8a3,
0x748f82ee5defb2fc,
0x78a5636f43172f60,
0x84c87814a1f0ab72,
0x8cc702081a6439ec,
0x90befffa23631e28,
0xa4506cebde82bde9,
0xbef9a3f7b2c67915,
0xc67178f2e372532b,
0xca273eceea26619c,
0xd186b8c721c0c207,
0xeada7dd6cde0eb1e,
0xf57d4f7fee6ed178,
0x06f067aa72176fba,
0x0a637dc5a2c898a6,
0x113f9804bef90dae,
0x1b710b35131c471b,
0x28db77f523047d84,
0x32caab7b40c72493,
0x3c9ebe0a15c9bebc,
0x431d67c49c100d4c,
0x4cc5d4becb3e42b6,
0x597f299cfc657e2a,
0x5fcb6fab3ad6faec,
0x6c44198c4a475817,
}
func sha384Block(dh *[8]uint64, p *[128]byte) {
var w [80]uint64
for i := range 80 {
if i < 16 {
w[i] = beUint64(p[i*8:])
} else {
v1 := w[i-2]
t1 := bits.RotateLeft64(v1, -19) ^ bits.RotateLeft64(v1, -61) ^ (v1 >> 6)
v2 := w[i-15]
t2 := bits.RotateLeft64(v2, -1) ^ bits.RotateLeft64(v2, -8) ^ (v2 >> 7)
w[i] = t1 + w[i-7] + t2 + w[i-16]
}
}
a, b, c, d, e, f, g, h := dh[0], dh[1], dh[2], dh[3], dh[4], dh[5], dh[6], dh[7]
for i := range 80 {
t1 := h + (bits.RotateLeft64(e, -14) ^ bits.RotateLeft64(e, -18) ^
bits.RotateLeft64(e, -41)) + ((e & f) ^ (^e & g)) + _K[i] + w[i]
t2 := (bits.RotateLeft64(a, -28) ^ bits.RotateLeft64(a, -34) ^
bits.RotateLeft64(a, -39)) + ((a & b) ^ (a & c) ^ (b & c))
h = g
g = f
f = e
e = d + t1
d = c
c = b
b = a
a = t1 + t2
}
dh[0] += a
dh[1] += b
dh[2] += c
dh[3] += d
dh[4] += e
dh[5] += f
dh[6] += g
dh[7] += h
}
func beUint64(b []byte) uint64 {
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
}
func bePutUint64(b []byte, v uint64) {
_ = b[7] // early bounds check to guarantee safety of writes below
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
}

View file

@ -7,7 +7,6 @@ package fips140
import (
"crypto/internal/fips140deps/godebug"
"errors"
"hash"
"runtime"
)
@ -49,6 +48,8 @@ func Supported() error {
}
// See EnableFIPS in cmd/internal/obj/fips.go for commentary.
// Also, js/wasm and windows/386 don't have good enough timers
// for the CPU jitter entropy source.
switch {
case runtime.GOARCH == "wasm",
runtime.GOOS == "windows" && runtime.GOARCH == "386",
@ -68,16 +69,10 @@ func Name() string {
return "Go Cryptographic Module"
}
// Version returns the formal version (such as "v1.0") if building against a
// Version returns the formal version (such as "v1.0.0") if building against a
// frozen module with GOFIPS140. Otherwise, it returns "latest".
func Version() string {
// This return value is replaced by mkzip.go, it must not be changed or
// moved to a different file.
return "latest" //mkzip:version
}
// Hash is a legacy compatibility alias for hash.Hash.
//
// It's only here because [crypto/internal/fips140/ecdsa.TestingOnlyNewDRBG]
// takes a "func() fips140.Hash" in v1.0.0, instead of being generic.
type Hash = hash.Hash

View file

@ -9,9 +9,10 @@ import (
"crypto/internal/fips140"
_ "crypto/internal/fips140/check"
"errors"
"sync"
)
func init() {
var fipsSelfTest = sync.OnceFunc(func() {
fips140.CAST("ML-KEM-768", func() error {
var d = &[32]byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
@ -40,14 +41,12 @@ func init() {
dk := &DecapsulationKey768{}
kemKeyGen(dk, d, z)
ek := dk.EncapsulationKey()
Ke, c := ek.EncapsulateInternal(m)
Kd, err := dk.Decapsulate(c)
if err != nil {
return err
}
var cc [CiphertextSize768]byte
Ke, _ := kemEncaps(&cc, ek, m)
Kd := kemDecaps(dk, &cc)
if !bytes.Equal(Ke, K) || !bytes.Equal(Kd, K) {
return errors.New("unexpected result")
}
return nil
})
}
})

View file

@ -113,15 +113,13 @@ func GenerateKey1024() (*DecapsulationKey1024, error) {
}
func generateKey1024(dk *DecapsulationKey1024) (*DecapsulationKey1024, error) {
fipsSelfTest()
var d [32]byte
drbg.Read(d[:])
var z [32]byte
drbg.Read(z[:])
kemKeyGen1024(dk, &d, &z)
if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil {
// This clearly can't happen, but FIPS 140-3 requires us to check.
panic(err)
}
fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) })
fips140.RecordApproved()
return dk, nil
}
@ -129,6 +127,7 @@ func generateKey1024(dk *DecapsulationKey1024) (*DecapsulationKey1024, error) {
// GenerateKeyInternal1024 is a derandomized version of GenerateKey1024,
// exclusively for use in tests.
func GenerateKeyInternal1024(d, z *[32]byte) *DecapsulationKey1024 {
fipsSelfTest()
dk := &DecapsulationKey1024{}
kemKeyGen1024(dk, d, z)
return dk
@ -149,10 +148,6 @@ func newKeyFromSeed1024(dk *DecapsulationKey1024, seed []byte) (*DecapsulationKe
d := (*[32]byte)(seed[:32])
z := (*[32]byte)(seed[32:])
kemKeyGen1024(dk, d, z)
if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil {
// This clearly can't happen, but FIPS 140-3 requires us to check.
panic(err)
}
fips140.RecordApproved()
return dk, nil
}
@ -285,6 +280,7 @@ func (ek *EncapsulationKey1024) Encapsulate() (sharedKey, ciphertext []byte) {
}
func (ek *EncapsulationKey1024) encapsulate(cc *[CiphertextSize1024]byte) (sharedKey, ciphertext []byte) {
fipsSelfTest()
var m [messageSize]byte
drbg.Read(m[:])
// Note that the modulus check (step 2 of the encapsulation key check from
@ -296,6 +292,7 @@ func (ek *EncapsulationKey1024) encapsulate(cc *[CiphertextSize1024]byte) (share
// EncapsulateInternal is a derandomized version of Encapsulate, exclusively for
// use in tests.
func (ek *EncapsulationKey1024) EncapsulateInternal(m *[32]byte) (sharedKey, ciphertext []byte) {
fipsSelfTest()
cc := &[CiphertextSize1024]byte{}
return kemEncaps1024(cc, ek, m)
}
@ -401,6 +398,7 @@ func pkeEncrypt1024(cc *[CiphertextSize1024]byte, ex *encryptionKey1024, m *[mes
//
// The shared key must be kept secret.
func (dk *DecapsulationKey1024) Decapsulate(ciphertext []byte) (sharedKey []byte, err error) {
fipsSelfTest()
if len(ciphertext) != CiphertextSize1024 {
return nil, errors.New("mlkem: invalid ciphertext length")
}

View file

@ -172,15 +172,13 @@ func GenerateKey768() (*DecapsulationKey768, error) {
}
func generateKey(dk *DecapsulationKey768) (*DecapsulationKey768, error) {
fipsSelfTest()
var d [32]byte
drbg.Read(d[:])
var z [32]byte
drbg.Read(z[:])
kemKeyGen(dk, &d, &z)
if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil {
// This clearly can't happen, but FIPS 140-3 requires us to check.
panic(err)
}
fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) })
fips140.RecordApproved()
return dk, nil
}
@ -188,6 +186,7 @@ func generateKey(dk *DecapsulationKey768) (*DecapsulationKey768, error) {
// GenerateKeyInternal768 is a derandomized version of GenerateKey768,
// exclusively for use in tests.
func GenerateKeyInternal768(d, z *[32]byte) *DecapsulationKey768 {
fipsSelfTest()
dk := &DecapsulationKey768{}
kemKeyGen(dk, d, z)
return dk
@ -208,10 +207,6 @@ func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768,
d := (*[32]byte)(seed[:32])
z := (*[32]byte)(seed[32:])
kemKeyGen(dk, d, z)
if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil {
// This clearly can't happen, but FIPS 140-3 requires us to check.
panic(err)
}
fips140.RecordApproved()
return dk, nil
}
@ -344,6 +339,7 @@ func (ek *EncapsulationKey768) Encapsulate() (sharedKey, ciphertext []byte) {
}
func (ek *EncapsulationKey768) encapsulate(cc *[CiphertextSize768]byte) (sharedKey, ciphertext []byte) {
fipsSelfTest()
var m [messageSize]byte
drbg.Read(m[:])
// Note that the modulus check (step 2 of the encapsulation key check from
@ -355,6 +351,7 @@ func (ek *EncapsulationKey768) encapsulate(cc *[CiphertextSize768]byte) (sharedK
// EncapsulateInternal is a derandomized version of Encapsulate, exclusively for
// use in tests.
func (ek *EncapsulationKey768) EncapsulateInternal(m *[32]byte) (sharedKey, ciphertext []byte) {
fipsSelfTest()
cc := &[CiphertextSize768]byte{}
return kemEncaps(cc, ek, m)
}
@ -460,6 +457,7 @@ func pkeEncrypt(cc *[CiphertextSize768]byte, ex *encryptionKey, m *[messageSize]
//
// The shared key must be kept secret.
func (dk *DecapsulationKey768) Decapsulate(ciphertext []byte) (sharedKey []byte, err error) {
fipsSelfTest()
if len(ciphertext) != CiphertextSize768 {
return nil, errors.New("mlkem: invalid ciphertext length")
}

View file

@ -105,7 +105,28 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
// negligible chance of failure we can defer the check to the end of key
// generation and return an error if it fails. See [checkPrivateKey].
return newPrivateKey(N, 65537, d, P, Q)
k, err := newPrivateKey(N, 65537, d, P, Q)
if err != nil {
return nil, err
}
if k.fipsApproved {
fips140.PCT("RSA sign and verify PCT", func() error {
hash := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
}
sig, err := signPKCS1v15(k, "SHA-256", hash)
if err != nil {
return err
}
return verifyPKCS1v15(k.PublicKey(), "SHA-256", hash, sig)
})
}
return k, nil
}
}

View file

@ -320,26 +320,6 @@ func checkPrivateKey(priv *PrivateKey) error {
return errors.New("crypto/rsa: d too small")
}
// If the key is still in scope for FIPS mode, perform a Pairwise
// Consistency Test.
if priv.fipsApproved {
if err := fips140.PCT("RSA sign and verify PCT", func() error {
hash := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
}
sig, err := signPKCS1v15(priv, "SHA-256", hash)
if err != nil {
return err
}
return verifyPKCS1v15(priv.PublicKey(), "SHA-256", hash, sig)
}); err != nil {
return err
}
}
return nil
}

View file

@ -88,7 +88,8 @@ func TestImports(t *testing.T) {
}
}
// Ensure that all packages except check and check's dependencies import check.
// Ensure that all packages except check, check's dependencies, and the
// entropy source (which is used only from .../fips140/drbg) import check.
for pkg := range allPackages {
switch pkg {
case "crypto/internal/fips140/check":
@ -99,6 +100,7 @@ func TestImports(t *testing.T) {
case "crypto/internal/fips140/sha3":
case "crypto/internal/fips140/sha256":
case "crypto/internal/fips140/sha512":
case "crypto/internal/fips140/entropy":
default:
if !importCheck[pkg] {
t.Errorf("package %s does not import crypto/internal/fips140/check", pkg)

View file

@ -0,0 +1,21 @@
// 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 !windows
package time
import "time"
var start = time.Now()
// HighPrecisionNow returns a high-resolution timestamp suitable for measuring
// small time differences. It uses the time package's monotonic clock.
//
// Its unit, epoch, and resolution are unspecified, and may change, but can be
// assumed to be sufficiently precise to measure time differences on the order
// of tens to hundreds of nanoseconds.
func HighPrecisionNow() int64 {
return int64(time.Since(start))
}

View file

@ -0,0 +1,17 @@
// 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 time
import "internal/syscall/windows"
// HighPrecisionNow returns a high-resolution timestamp suitable for measuring
// small time differences. It uses Windows' QueryPerformanceCounter.
//
// Its unit, epoch, and resolution are unspecified, and may change, but can be
// assumed to be sufficiently precise to measure time differences on the order
// of tens to hundreds of nanoseconds.
func HighPrecisionNow() int64 {
return windows.QueryPerformanceCounter()
}

View file

@ -1624,7 +1624,7 @@ func cmdHmacDrbgAft(h func() hash.Hash) command {
// * Uninstantiate
// See Table 7 in draft-vassilev-acvp-drbg
out := make([]byte, outLen)
drbg := ecdsa.TestingOnlyNewDRBG(func() fips140.Hash { return h() }, entropy, nonce, personalization)
drbg := ecdsa.TestingOnlyNewDRBG(h, entropy, nonce, personalization)
drbg.Generate(out)
drbg.Generate(out)

View file

@ -5,9 +5,8 @@
package fipstest
import (
"crypto"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"internal/testenv"
"io/fs"
@ -18,6 +17,7 @@ import (
"testing"
// Import packages that define CASTs to test them.
"crypto/internal/cryptotest"
_ "crypto/internal/fips140/aes"
_ "crypto/internal/fips140/aes/gcm"
_ "crypto/internal/fips140/drbg"
@ -48,10 +48,8 @@ var allCASTs = []string{
"HKDF-SHA2-256",
"HMAC-SHA2-256",
"KAS-ECC-SSC P-256",
"ML-KEM PCT",
"ML-KEM PCT",
"ML-KEM PCT",
"ML-KEM PCT",
"ML-KEM PCT", // -768
"ML-KEM PCT", // -1024
"ML-KEM-768",
"PBKDF2",
"RSA sign and verify PCT",
@ -106,61 +104,77 @@ func TestAllCASTs(t *testing.T) {
// TestConditionals causes the conditional CASTs and PCTs to be invoked.
func TestConditionals(t *testing.T) {
mlkem.GenerateKey768()
k, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader)
// ML-KEM PCT
kMLKEM, err := mlkem.GenerateKey768()
if err != nil {
t.Fatal(err)
t.Error(err)
} else {
// ML-KEM-768
kMLKEM.EncapsulationKey().Encapsulate()
}
ecdh.ECDH(ecdh.P256(), k, k.PublicKey())
// ECDH PCT
kDH, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader)
if err != nil {
t.Error(err)
} else {
// KAS-ECC-SSC P-256
ecdh.ECDH(ecdh.P256(), kDH, kDH.PublicKey())
}
// ECDSA PCT
kDSA, err := ecdsa.GenerateKey(ecdsa.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
t.Error(err)
} else {
// ECDSA P-256 SHA2-512 sign and verify
ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32))
}
ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32))
// Ed25519 sign and verify PCT
k25519, err := ed25519.GenerateKey()
if err != nil {
t.Fatal(err)
t.Error(err)
} else {
// Ed25519 sign and verify
ed25519.Sign(k25519, make([]byte, 32))
}
ed25519.Sign(k25519, make([]byte, 32))
rsa.VerifyPKCS1v15(&rsa.PublicKey{}, "", nil, nil)
// Parse an RSA key to hit the PCT rather than generating one (which is slow).
block, _ := pem.Decode([]byte(strings.ReplaceAll(
`-----BEGIN RSA TESTING KEY-----
MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso
tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE
89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU
l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s
B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59
3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+
dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI
FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J
aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2
BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx
IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/
fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u
pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT
Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl
u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD
fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X
Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE
k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo
qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS
CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ
XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw
AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r
UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0
2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5
7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3
-----END RSA TESTING KEY-----`, "TESTING KEY", "PRIVATE KEY")))
if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
t.Fatal(err)
// RSA sign and verify PCT
kRSA, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Error(err)
} else {
// RSASSA-PKCS-v1.5 2048-bit sign and verify
rsa.SignPKCS1v15(kRSA, crypto.SHA256.String(), make([]byte, 32))
}
t.Log("completed successfully")
}
func TestCASTPasses(t *testing.T) {
moduleStatus(t)
testenv.MustHaveExec(t)
cryptotest.MustSupportFIPS140(t)
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v")
cmd.Env = append(cmd.Env, "GODEBUG=fips140=debug")
out, err := cmd.CombinedOutput()
t.Logf("%s", out)
if err != nil || !strings.Contains(string(out), "completed successfully") {
t.Errorf("TestConditionals did not complete successfully")
}
for _, name := range allCASTs {
t.Run(name, func(t *testing.T) {
if !strings.Contains(string(out), fmt.Sprintf("passed: %s\n", name)) {
t.Errorf("CAST/PCT %s success was not logged", name)
} else {
t.Logf("CAST/PCT succeeded: %s", name)
}
})
}
}
func TestCASTFailures(t *testing.T) {
moduleStatus(t)
testenv.MustHaveExec(t)
cryptotest.MustSupportFIPS140(t)
for _, name := range allCASTs {
t.Run(name, func(t *testing.T) {
@ -169,7 +183,6 @@ func TestCASTFailures(t *testing.T) {
if !testing.Verbose() {
t.Parallel()
}
t.Logf("CAST/PCT succeeded: %s", name)
t.Logf("Testing CAST/PCT failure...")
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v")
cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=failfipscast=%s,fips140=on", name))
@ -180,6 +193,8 @@ func TestCASTFailures(t *testing.T) {
}
if strings.Contains(string(out), "completed successfully") {
t.Errorf("CAST/PCT %s failure did not stop the program", name)
} else if !strings.Contains(string(out), "self-test failed: "+name) {
t.Errorf("CAST/PCT %s failure did not log the expected message", name)
} else {
t.Logf("CAST/PCT %s failed as expected and caused the program to exit", name)
}

View file

@ -0,0 +1,264 @@
// 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 !fips140v1.0
package fipstest
import (
"bytes"
"crypto/internal/cryptotest"
"crypto/internal/fips140/drbg"
"crypto/internal/fips140/entropy"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"flag"
"fmt"
"internal/testenv"
"io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
var flagEntropySamples = flag.String("entropy-samples", "", "store entropy samples with the provided `suffix`")
var flagNISTSP80090B = flag.Bool("nist-sp800-90b", false, "run NIST SP 800-90B tests (requires docker)")
func TestEntropySamples(t *testing.T) {
cryptotest.MustSupportFIPS140(t)
var seqSamples [1_000_000]uint8
samplesOrTryAgain(t, seqSamples[:])
seqSamplesName := fmt.Sprintf("entropy_samples_sequential_%s_%s_%s_%s_%s.bin", entropy.Version(),
runtime.GOOS, runtime.GOARCH, *flagEntropySamples, time.Now().Format("20060102T150405Z"))
if *flagEntropySamples != "" {
if err := os.WriteFile(seqSamplesName, seqSamples[:], 0644); err != nil {
t.Fatalf("failed to write samples to %q: %v", seqSamplesName, err)
}
t.Logf("wrote %s", seqSamplesName)
}
var restartSamples [1000][1000]uint8
for i := range restartSamples {
var samples [1024]uint8
samplesOrTryAgain(t, samples[:])
copy(restartSamples[i][:], samples[:])
}
restartSamplesName := fmt.Sprintf("entropy_samples_restart_%s_%s_%s_%s_%s.bin", entropy.Version(),
runtime.GOOS, runtime.GOARCH, *flagEntropySamples, time.Now().Format("20060102T150405Z"))
if *flagEntropySamples != "" {
f, err := os.Create(restartSamplesName)
if err != nil {
t.Fatalf("failed to create %q: %v", restartSamplesName, err)
}
for i := range restartSamples {
if _, err := f.Write(restartSamples[i][:]); err != nil {
t.Fatalf("failed to write samples to %q: %v", restartSamplesName, err)
}
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close %q: %v", restartSamplesName, err)
}
t.Logf("wrote %s", restartSamplesName)
}
if *flagNISTSP80090B {
if *flagEntropySamples == "" {
t.Fatalf("-nist-sp800-90b requires -entropy-samples to be set too")
}
// Check if the nist-sp800-90b docker image is already present,
// and build it otherwise.
if err := testenv.Command(t,
"docker", "image", "inspect", "nist-sp800-90b",
).Run(); err != nil {
t.Logf("building nist-sp800-90b docker image")
dockerfile := filepath.Join(t.TempDir(), "Dockerfile.SP800-90B_EntropyAssessment")
if err := os.WriteFile(dockerfile, []byte(NISTSP80090BDockerfile), 0644); err != nil {
t.Fatalf("failed to write Dockerfile: %v", err)
}
out, err := testenv.Command(t,
"docker", "build", "-t", "nist-sp800-90b", "-f", dockerfile, "/var/empty",
).CombinedOutput()
if err != nil {
t.Fatalf("failed to build nist-sp800-90b docker image: %v\n%s", err, out)
}
}
pwd, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get current working directory: %v", err)
}
t.Logf("running ea_non_iid analysis")
out, err := testenv.Command(t,
"docker", "run", "--rm", "-v", fmt.Sprintf("%s:%s", pwd, pwd), "-w", pwd,
"nist-sp800-90b", "ea_non_iid", seqSamplesName, "8",
).CombinedOutput()
if err != nil {
t.Fatalf("ea_non_iid failed: %v\n%s", err, out)
}
t.Logf("\n%s", out)
H_I := string(out)
H_I = strings.TrimSpace(H_I[strings.LastIndexByte(H_I, ' ')+1:])
t.Logf("running ea_restart analysis with H_I = %s", H_I)
out, err = testenv.Command(t,
"docker", "run", "--rm", "-v", fmt.Sprintf("%s:%s", pwd, pwd), "-w", pwd,
"nist-sp800-90b", "ea_restart", restartSamplesName, "8", H_I,
).CombinedOutput()
if err != nil {
t.Fatalf("ea_restart failed: %v\n%s", err, out)
}
t.Logf("\n%s", out)
}
}
var NISTSP80090BDockerfile = `
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y build-essential git \
libbz2-dev libdivsufsort-dev libjsoncpp-dev libgmp-dev libmpfr-dev libssl-dev \
&& rm -rf /var/lib/apt/lists/*
RUN git clone --depth 1 https://github.com/usnistgov/SP800-90B_EntropyAssessment.git
RUN cd SP800-90B_EntropyAssessment && git checkout 8924f158c97e7b805e0f95247403ad4c44b9cd6f
WORKDIR ./SP800-90B_EntropyAssessment/cpp/
RUN make all
RUN cd selftest && ./selftest
RUN cp ea_non_iid ea_restart /usr/local/bin/
`
var memory entropy.ScratchBuffer
// samplesOrTryAgain calls entropy.Samples up to 10 times until it succeeds.
// Samples has a non-negligible chance of failing the health tests, as required
// by SP 800-90B.
func samplesOrTryAgain(t *testing.T, samples []uint8) {
t.Helper()
for range 10 {
if err := entropy.Samples(samples, &memory); err != nil {
t.Logf("entropy.Samples() failed: %v", err)
continue
}
return
}
t.Fatal("entropy.Samples() failed 10 times in a row")
}
func TestEntropySHA384(t *testing.T) {
var input [1024]uint8
for i := range input {
input[i] = uint8(i)
}
want := sha512.Sum384(input[:])
got := entropy.SHA384(&input)
if got != want {
t.Errorf("SHA384() = %x, want %x", got, want)
}
}
func TestEntropyRepetitionCountTest(t *testing.T) {
good := bytes.Repeat(append(bytes.Repeat([]uint8{42}, 40), 1), 100)
if err := entropy.RepetitionCountTest(good); err != nil {
t.Errorf("RepetitionCountTest(good) = %v, want nil", err)
}
bad := bytes.Repeat([]uint8{0}, 40)
bad = append(bad, bytes.Repeat([]uint8{1}, 40)...)
bad = append(bad, bytes.Repeat([]uint8{42}, 41)...)
bad = append(bad, bytes.Repeat([]uint8{2}, 40)...)
if err := entropy.RepetitionCountTest(bad); err == nil {
t.Error("RepetitionCountTest(bad) = nil, want error")
}
bad = bytes.Repeat([]uint8{42}, 41)
if err := entropy.RepetitionCountTest(bad); err == nil {
t.Error("RepetitionCountTest(bad) = nil, want error")
}
}
func TestEntropyAdaptiveProportionTest(t *testing.T) {
good := bytes.Repeat([]uint8{0}, 409)
good = append(good, bytes.Repeat([]uint8{1}, 512-409)...)
good = append(good, bytes.Repeat([]uint8{0}, 409)...)
if err := entropy.AdaptiveProportionTest(good); err != nil {
t.Errorf("AdaptiveProportionTest(good) = %v, want nil", err)
}
// These fall out of the window.
bad := bytes.Repeat([]uint8{1}, 100)
bad = append(bad, bytes.Repeat([]uint8{1, 2, 3, 4, 5, 6}, 100)...)
// These are in the window.
bad = append(bad, bytes.Repeat([]uint8{42}, 410)...)
if err := entropy.AdaptiveProportionTest(bad[:len(bad)-1]); err != nil {
t.Errorf("AdaptiveProportionTest(bad[:len(bad)-1]) = %v, want nil", err)
}
if err := entropy.AdaptiveProportionTest(bad); err == nil {
t.Error("AdaptiveProportionTest(bad) = nil, want error")
}
}
func TestEntropyUnchanged(t *testing.T) {
testenv.MustHaveSource(t)
h := sha256.New()
root := os.DirFS("../fips140/entropy")
if err := fs.WalkDir(root, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
data, err := fs.ReadFile(root, path)
if err != nil {
return err
}
t.Logf("Hashing %s (%d bytes)", path, len(data))
fmt.Fprintf(h, "%s %d\n", path, len(data))
h.Write(data)
return nil
}); err != nil {
t.Fatalf("WalkDir: %v", err)
}
// The crypto/internal/fips140/entropy package is certified as a FIPS 140-3
// entropy source through the Entropy Source Validation program,
// independently of the FIPS 140-3 module. It must not change even across
// FIPS 140-3 module versions, in order to reuse the ESV certificate.
exp := "35976eb8a11678c79777da07aaab5511d4325701f837777df205f6e7b20c6821"
if got := hex.EncodeToString(h.Sum(nil)); got != exp {
t.Errorf("hash of crypto/internal/fips140/entropy = %s, want %s", got, exp)
}
}
func TestEntropyRace(t *testing.T) {
// Check that concurrent calls to Seed don't trigger the race detector.
for range 2 {
go func() {
_, _ = entropy.Seed(&memory)
}()
}
// Same, with the higher-level DRBG. More concurrent calls to hit the Pool.
for range 16 {
go func() {
var b [64]byte
drbg.Read(b[:])
}()
}
}
var sink byte
func BenchmarkEntropySeed(b *testing.B) {
for b.Loop() {
seed, err := entropy.Seed(&memory)
if err != nil {
b.Fatalf("entropy.Seed() failed: %v", err)
}
sink ^= seed[0]
}
}

View file

@ -74,11 +74,9 @@ func TestVersion(t *testing.T) {
continue
}
exp := setting.Value
if exp == "v1.0.0" {
// Unfortunately we enshrined the version of the first module as
// v1.0 before deciding to go for full versions.
exp = "v1.0"
}
// Remove the -hash suffix, if any.
// The version from fips140.Version omits it.
exp, _, _ = strings.Cut(exp, "-")
if v := fips140.Version(); v != exp {
t.Errorf("Version is %q, expected %q", v, exp)
}

View file

@ -1246,7 +1246,7 @@ func TestModifiedPrivateKey(t *testing.T) {
})
t.Run("D+2", func(t *testing.T) {
if fips140.Version() == "v1.0" {
if fips140.Version() == "v1.0.0" {
t.Skip("This was fixed after v1.0.0")
}
testModifiedPrivateKey(t, func(k *PrivateKey) {

View file

@ -568,16 +568,6 @@ func parseECHExt(ext []byte) (echType echExtType, cs echCipher, configID uint8,
return echType, cs, configID, bytes.Clone(encap), bytes.Clone(payload), nil
}
func marshalEncryptedClientHelloConfigList(configs []EncryptedClientHelloKey) ([]byte, error) {
builder := cryptobyte.NewBuilder(nil)
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
for _, c := range configs {
builder.AddBytes(c.Config)
}
})
return builder.Bytes()
}
func (c *Conn) processECHClientHello(outer *clientHelloMsg, echKeys []EncryptedClientHelloKey) (*clientHelloMsg, *echServerContext, error) {
echType, echCiphersuite, configID, encap, payload, err := parseECHExt(outer.encryptedClientHello)
if err != nil {

View file

@ -357,7 +357,7 @@ func negotiateALPN(serverProtos, clientProtos []string, quic bool) (string, erro
if http11fallback {
return "", nil
}
return "", fmt.Errorf("tls: client requested unsupported application protocols (%s)", clientProtos)
return "", fmt.Errorf("tls: client requested unsupported application protocols (%q)", clientProtos)
}
// supportsECDHE returns whether ECDHE key exchanges can be used with this

View file

@ -35,7 +35,7 @@ type echServerContext struct {
configID uint8
ciphersuite echCipher
transcript hash.Hash
// inner indicates that the initial client_hello we recieved contained an
// inner indicates that the initial client_hello we received contained an
// encrypted_client_hello extension that indicated it was an "inner" hello.
// We don't do any additional processing of the hello in this case, so all
// fields above are unset.

View file

@ -25,6 +25,7 @@ import (
"internal/saferio"
"internal/zstd"
"io"
"math"
"os"
"strings"
"unsafe"
@ -830,17 +831,9 @@ func (f *File) applyRelocationsAMD64(dst []byte, rels []byte) error {
switch t {
case R_X86_64_64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val64 := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
putUint(f.ByteOrder, dst, rela.Off, 8, sym.Value, rela.Addend, false)
case R_X86_64_32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val32 := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val32)
putUint(f.ByteOrder, dst, rela.Off, 4, sym.Value, rela.Addend, false)
}
}
@ -872,12 +865,7 @@ func (f *File) applyRelocations386(dst []byte, rels []byte) error {
sym := &symbols[symNo-1]
if t == R_386_32 {
if rel.Off+4 >= uint32(len(dst)) {
continue
}
val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
val += uint32(sym.Value)
f.ByteOrder.PutUint32(dst[rel.Off:rel.Off+4], val)
putUint(f.ByteOrder, dst, uint64(rel.Off), 4, sym.Value, 0, true)
}
}
@ -910,12 +898,7 @@ func (f *File) applyRelocationsARM(dst []byte, rels []byte) error {
switch t {
case R_ARM_ABS32:
if rel.Off+4 >= uint32(len(dst)) {
continue
}
val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
val += uint32(sym.Value)
f.ByteOrder.PutUint32(dst[rel.Off:rel.Off+4], val)
putUint(f.ByteOrder, dst, uint64(rel.Off), 4, sym.Value, 0, true)
}
}
@ -955,17 +938,9 @@ func (f *File) applyRelocationsARM64(dst []byte, rels []byte) error {
switch t {
case R_AARCH64_ABS64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val64 := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
putUint(f.ByteOrder, dst, rela.Off, 8, sym.Value, rela.Addend, false)
case R_AARCH64_ABS32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val32 := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val32)
putUint(f.ByteOrder, dst, rela.Off, 4, sym.Value, rela.Addend, false)
}
}
@ -1001,11 +976,7 @@ func (f *File) applyRelocationsPPC(dst []byte, rels []byte) error {
switch t {
case R_PPC_ADDR32:
if rela.Off+4 >= uint32(len(dst)) || rela.Addend < 0 {
continue
}
val32 := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val32)
putUint(f.ByteOrder, dst, uint64(rela.Off), 4, sym.Value, 0, false)
}
}
@ -1041,17 +1012,9 @@ func (f *File) applyRelocationsPPC64(dst []byte, rels []byte) error {
switch t {
case R_PPC64_ADDR64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val64 := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
putUint(f.ByteOrder, dst, rela.Off, 8, sym.Value, rela.Addend, false)
case R_PPC64_ADDR32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val32 := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val32)
putUint(f.ByteOrder, dst, rela.Off, 4, sym.Value, rela.Addend, false)
}
}
@ -1084,12 +1047,7 @@ func (f *File) applyRelocationsMIPS(dst []byte, rels []byte) error {
switch t {
case R_MIPS_32:
if rel.Off+4 >= uint32(len(dst)) {
continue
}
val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
val += uint32(sym.Value)
f.ByteOrder.PutUint32(dst[rel.Off:rel.Off+4], val)
putUint(f.ByteOrder, dst, uint64(rel.Off), 4, sym.Value, 0, true)
}
}
@ -1132,17 +1090,9 @@ func (f *File) applyRelocationsMIPS64(dst []byte, rels []byte) error {
switch t {
case R_MIPS_64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val64 := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
putUint(f.ByteOrder, dst, rela.Off, 8, sym.Value, rela.Addend, false)
case R_MIPS_32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val32 := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val32)
putUint(f.ByteOrder, dst, rela.Off, 4, sym.Value, rela.Addend, false)
}
}
@ -1180,17 +1130,9 @@ func (f *File) applyRelocationsLOONG64(dst []byte, rels []byte) error {
switch t {
case R_LARCH_64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val64 := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
putUint(f.ByteOrder, dst, rela.Off, 8, sym.Value, rela.Addend, false)
case R_LARCH_32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val32 := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val32)
putUint(f.ByteOrder, dst, rela.Off, 4, sym.Value, rela.Addend, false)
}
}
@ -1226,17 +1168,9 @@ func (f *File) applyRelocationsRISCV64(dst []byte, rels []byte) error {
switch t {
case R_RISCV_64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val64 := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
putUint(f.ByteOrder, dst, rela.Off, 8, sym.Value, rela.Addend, false)
case R_RISCV_32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val32 := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val32)
putUint(f.ByteOrder, dst, rela.Off, 4, sym.Value, rela.Addend, false)
}
}
@ -1272,17 +1206,9 @@ func (f *File) applyRelocationss390x(dst []byte, rels []byte) error {
switch t {
case R_390_64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val64 := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
putUint(f.ByteOrder, dst, rela.Off, 8, sym.Value, rela.Addend, false)
case R_390_32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val32 := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val32)
putUint(f.ByteOrder, dst, rela.Off, 4, sym.Value, rela.Addend, false)
}
}
@ -1318,17 +1244,10 @@ func (f *File) applyRelocationsSPARC64(dst []byte, rels []byte) error {
switch t {
case R_SPARC_64, R_SPARC_UA64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val64 := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val64)
putUint(f.ByteOrder, dst, rela.Off, 8, sym.Value, rela.Addend, false)
case R_SPARC_32, R_SPARC_UA32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val32 := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val32)
putUint(f.ByteOrder, dst, rela.Off, 4, sym.Value, rela.Addend, false)
}
}
@ -1903,3 +1822,38 @@ type nobitsSectionReader struct{}
func (*nobitsSectionReader) ReadAt(p []byte, off int64) (n int, err error) {
return 0, errors.New("unexpected read from SHT_NOBITS section")
}
// putUint writes a relocation to slice
// at offset start of length length (4 or 8 bytes),
// adding sym+addend to the existing value if readUint is true,
// or just writing sym+addend if readUint is false.
// If the write would extend beyond the end of slice, putUint does nothing.
// If the addend is negative, putUint does nothing.
// If the addition would overflow, putUint does nothing.
func putUint(byteOrder binary.ByteOrder, slice []byte, start, length, sym uint64, addend int64, readUint bool) {
if start+length > uint64(len(slice)) || math.MaxUint64-start < length {
return
}
if addend < 0 {
return
}
s := slice[start : start+length]
switch length {
case 4:
ae := uint32(addend)
if readUint {
ae += byteOrder.Uint32(s)
}
byteOrder.PutUint32(s, uint32(sym)+ae)
case 8:
ae := uint64(addend)
if readUint {
ae += byteOrder.Uint64(s)
}
byteOrder.PutUint64(s, sym+ae)
default:
panic("can't happen")
}
}

View file

@ -557,3 +557,67 @@ func TestTokenTruncation(t *testing.T) {
}
}
}
func TestDecoderInputOffset(t *testing.T) {
const input = ` [
[ ] , [ "one" ] , [ "one" , "two" ] ,
{ } , { "alpha" : "bravo" } , { "alpha" : "bravo" , "fizz" : "buzz" }
] `
wantOffsets := []int64{
0, 1, 2, 5, 6, 7, 8, 9, 12, 13, 18, 19, 20, 21, 24, 25, 30, 31,
38, 39, 40, 41, 46, 47, 48, 49, 52, 53, 60, 61, 70, 71, 72, 73,
76, 77, 84, 85, 94, 95, 103, 104, 112, 113, 114, 116, 117, 117,
117, 117,
}
wantMores := []bool{
true, true, false, true, true, false, true, true, true, false,
true, false, true, true, true, false, true, true, true, true,
true, false, false, false, false,
}
d := NewDecoder(strings.NewReader(input))
checkOffset := func() {
t.Helper()
got := d.InputOffset()
if len(wantOffsets) == 0 {
t.Fatalf("InputOffset = %d, want nil", got)
}
want := wantOffsets[0]
if got != want {
t.Fatalf("InputOffset = %d, want %d", got, want)
}
wantOffsets = wantOffsets[1:]
}
checkMore := func() {
t.Helper()
got := d.More()
if len(wantMores) == 0 {
t.Fatalf("More = %v, want nil", got)
}
want := wantMores[0]
if got != want {
t.Fatalf("More = %v, want %v", got, want)
}
wantMores = wantMores[1:]
}
checkOffset()
checkMore()
checkOffset()
for {
if _, err := d.Token(); err == io.EOF {
break
} else if err != nil {
t.Fatalf("Token error: %v", err)
}
checkOffset()
checkMore()
checkOffset()
}
checkOffset()
checkMore()
checkOffset()
if len(wantOffsets)+len(wantMores) > 0 {
t.Fatal("unconsumed testdata")
}
}

View file

@ -20,6 +20,10 @@ type Decoder struct {
dec *jsontext.Decoder
opts jsonv2.Options
err error
// hadPeeked reports whether [Decoder.More] was called.
// It is reset by [Decoder.Decode] and [Decoder.Token].
hadPeeked bool
}
// NewDecoder returns a new decoder that reads from r.
@ -76,6 +80,7 @@ func (dec *Decoder) Decode(v any) error {
}
return dec.err
}
dec.hadPeeked = false
return jsonv2.Unmarshal(b, v, dec.opts)
}
@ -206,6 +211,7 @@ func (dec *Decoder) Token() (Token, error) {
}
return nil, transformSyntacticError(err)
}
dec.hadPeeked = false
switch k := tok.Kind(); k {
case 'n':
return nil, nil
@ -230,6 +236,7 @@ func (dec *Decoder) Token() (Token, error) {
// More reports whether there is another element in the
// current array or object being parsed.
func (dec *Decoder) More() bool {
dec.hadPeeked = true
k := dec.dec.PeekKind()
return k > 0 && k != ']' && k != '}'
}
@ -238,5 +245,18 @@ func (dec *Decoder) More() bool {
// The offset gives the location of the end of the most recently returned token
// and the beginning of the next token.
func (dec *Decoder) InputOffset() int64 {
return dec.dec.InputOffset()
offset := dec.dec.InputOffset()
if dec.hadPeeked {
// Historically, InputOffset reported the location of
// the end of the most recently returned token
// unless [Decoder.More] is called, in which case, it reported
// the beginning of the next token.
unreadBuffer := dec.dec.UnreadBuffer()
trailingTokens := bytes.TrimLeft(unreadBuffer, " \n\r\t")
if len(trailingTokens) > 0 {
leadingWhitespace := len(unreadBuffer) - len(trailingTokens)
offset += int64(leadingWhitespace)
}
}
return offset
}

View file

@ -537,3 +537,67 @@ func TestTokenTruncation(t *testing.T) {
}
}
}
func TestDecoderInputOffset(t *testing.T) {
const input = ` [
[ ] , [ "one" ] , [ "one" , "two" ] ,
{ } , { "alpha" : "bravo" } , { "alpha" : "bravo" , "fizz" : "buzz" }
] `
wantOffsets := []int64{
0, 1, 2, 5, 6, 7, 8, 9, 12, 13, 18, 19, 20, 21, 24, 25, 30, 31,
38, 39, 40, 41, 46, 47, 48, 49, 52, 53, 60, 61, 70, 71, 72, 73,
76, 77, 84, 85, 94, 95, 103, 104, 112, 113, 114, 116, 117, 117,
117, 117,
}
wantMores := []bool{
true, true, false, true, true, false, true, true, true, false,
true, false, true, true, true, false, true, true, true, true,
true, false, false, false, false,
}
d := NewDecoder(strings.NewReader(input))
checkOffset := func() {
t.Helper()
got := d.InputOffset()
if len(wantOffsets) == 0 {
t.Fatalf("InputOffset = %d, want nil", got)
}
want := wantOffsets[0]
if got != want {
t.Fatalf("InputOffset = %d, want %d", got, want)
}
wantOffsets = wantOffsets[1:]
}
checkMore := func() {
t.Helper()
got := d.More()
if len(wantMores) == 0 {
t.Fatalf("More = %v, want nil", got)
}
want := wantMores[0]
if got != want {
t.Fatalf("More = %v, want %v", got, want)
}
wantMores = wantMores[1:]
}
checkOffset()
checkMore()
checkOffset()
for {
if _, err := d.Token(); err == io.EOF {
break
} else if err != nil {
t.Fatalf("Token error: %v", err)
}
checkOffset()
checkMore()
checkOffset()
}
checkOffset()
checkMore()
checkOffset()
if len(wantOffsets)+len(wantMores) > 0 {
t.Fatal("unconsumed testdata")
}
}

View file

@ -416,12 +416,6 @@ func (p *printer) popPrefix() {
}
}
var (
marshalerType = reflect.TypeFor[Marshaler]()
marshalerAttrType = reflect.TypeFor[MarshalerAttr]()
textMarshalerType = reflect.TypeFor[encoding.TextMarshaler]()
)
// marshalValue writes one or more XML elements representing val.
// If val was obtained from a struct field, finfo must have its details.
func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplate *StartElement) error {
@ -450,24 +444,32 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
typ := val.Type()
// Check for marshaler.
if val.CanInterface() && typ.Implements(marshalerType) {
return p.marshalInterface(val.Interface().(Marshaler), defaultStart(typ, finfo, startTemplate))
if val.CanInterface() {
if marshaler, ok := reflect.TypeAssert[Marshaler](val); ok {
return p.marshalInterface(marshaler, defaultStart(typ, finfo, startTemplate))
}
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(marshalerType) {
return p.marshalInterface(pv.Interface().(Marshaler), defaultStart(pv.Type(), finfo, startTemplate))
if pv.CanInterface() {
if marshaler, ok := reflect.TypeAssert[Marshaler](pv); ok {
return p.marshalInterface(marshaler, defaultStart(pv.Type(), finfo, startTemplate))
}
}
}
// Check for text marshaler.
if val.CanInterface() && typ.Implements(textMarshalerType) {
return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler), defaultStart(typ, finfo, startTemplate))
if val.CanInterface() {
if textMarshaler, ok := reflect.TypeAssert[encoding.TextMarshaler](val); ok {
return p.marshalTextInterface(textMarshaler, defaultStart(typ, finfo, startTemplate))
}
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler), defaultStart(pv.Type(), finfo, startTemplate))
if pv.CanInterface() {
if textMarshaler, ok := reflect.TypeAssert[encoding.TextMarshaler](pv); ok {
return p.marshalTextInterface(textMarshaler, defaultStart(pv.Type(), finfo, startTemplate))
}
}
}
@ -503,7 +505,7 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name
} else {
fv := xmlname.value(val, dontInitNilPointers)
if v, ok := fv.Interface().(Name); ok && v.Local != "" {
if v, ok := reflect.TypeAssert[Name](fv); ok && v.Local != "" {
start.Name = v
}
}
@ -580,21 +582,9 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
// marshalAttr marshals an attribute with the given name and value, adding to start.Attr.
func (p *printer) marshalAttr(start *StartElement, name Name, val reflect.Value) error {
if val.CanInterface() && val.Type().Implements(marshalerAttrType) {
attr, err := val.Interface().(MarshalerAttr).MarshalXMLAttr(name)
if err != nil {
return err
}
if attr.Name.Local != "" {
start.Attr = append(start.Attr, attr)
}
return nil
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(marshalerAttrType) {
attr, err := pv.Interface().(MarshalerAttr).MarshalXMLAttr(name)
if val.CanInterface() {
if marshaler, ok := reflect.TypeAssert[MarshalerAttr](val); ok {
attr, err := marshaler.MarshalXMLAttr(name)
if err != nil {
return err
}
@ -605,19 +595,25 @@ func (p *printer) marshalAttr(start *StartElement, name Name, val reflect.Value)
}
}
if val.CanInterface() && val.Type().Implements(textMarshalerType) {
text, err := val.Interface().(encoding.TextMarshaler).MarshalText()
if err != nil {
return err
}
start.Attr = append(start.Attr, Attr{name, string(text)})
return nil
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
text, err := pv.Interface().(encoding.TextMarshaler).MarshalText()
if pv.CanInterface() {
if marshaler, ok := reflect.TypeAssert[MarshalerAttr](pv); ok {
attr, err := marshaler.MarshalXMLAttr(name)
if err != nil {
return err
}
if attr.Name.Local != "" {
start.Attr = append(start.Attr, attr)
}
return nil
}
}
}
if val.CanInterface() {
if textMarshaler, ok := reflect.TypeAssert[encoding.TextMarshaler](val); ok {
text, err := textMarshaler.MarshalText()
if err != nil {
return err
}
@ -626,6 +622,20 @@ func (p *printer) marshalAttr(start *StartElement, name Name, val reflect.Value)
}
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() {
if textMarshaler, ok := reflect.TypeAssert[encoding.TextMarshaler](pv); ok {
text, err := textMarshaler.MarshalText()
if err != nil {
return err
}
start.Attr = append(start.Attr, Attr{name, string(text)})
return nil
}
}
}
// Dereference or skip nil pointer, interface values.
switch val.Kind() {
case reflect.Pointer, reflect.Interface:
@ -647,7 +657,8 @@ func (p *printer) marshalAttr(start *StartElement, name Name, val reflect.Value)
}
if val.Type() == attrType {
start.Attr = append(start.Attr, val.Interface().(Attr))
attr, _ := reflect.TypeAssert[Attr](val)
start.Attr = append(start.Attr, attr)
return nil
}
@ -854,20 +865,9 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
if err := s.trim(finfo.parents); err != nil {
return err
}
if vf.CanInterface() && vf.Type().Implements(textMarshalerType) {
data, err := vf.Interface().(encoding.TextMarshaler).MarshalText()
if err != nil {
return err
}
if err := emit(p, data); err != nil {
return err
}
continue
}
if vf.CanAddr() {
pv := vf.Addr()
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
data, err := pv.Interface().(encoding.TextMarshaler).MarshalText()
if vf.CanInterface() {
if textMarshaler, ok := reflect.TypeAssert[encoding.TextMarshaler](vf); ok {
data, err := textMarshaler.MarshalText()
if err != nil {
return err
}
@ -877,6 +877,21 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
continue
}
}
if vf.CanAddr() {
pv := vf.Addr()
if pv.CanInterface() {
if textMarshaler, ok := reflect.TypeAssert[encoding.TextMarshaler](pv); ok {
data, err := textMarshaler.MarshalText()
if err != nil {
return err
}
if err := emit(p, data); err != nil {
return err
}
continue
}
}
}
var scratch [64]byte
vf = indirect(vf)
@ -902,7 +917,7 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
return err
}
case reflect.Slice:
if elem, ok := vf.Interface().([]byte); ok {
if elem, ok := reflect.TypeAssert[[]byte](vf); ok {
if err := emit(p, elem); err != nil {
return err
}

View file

@ -255,28 +255,36 @@ func (d *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error {
}
val = val.Elem()
}
if val.CanInterface() && val.Type().Implements(unmarshalerAttrType) {
if val.CanInterface() {
// This is an unmarshaler with a non-pointer receiver,
// so it's likely to be incorrect, but we do what we're told.
return val.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr)
if unmarshaler, ok := reflect.TypeAssert[UnmarshalerAttr](val); ok {
return unmarshaler.UnmarshalXMLAttr(attr)
}
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(unmarshalerAttrType) {
return pv.Interface().(UnmarshalerAttr).UnmarshalXMLAttr(attr)
if pv.CanInterface() {
if unmarshaler, ok := reflect.TypeAssert[UnmarshalerAttr](pv); ok {
return unmarshaler.UnmarshalXMLAttr(attr)
}
}
}
// Not an UnmarshalerAttr; try encoding.TextUnmarshaler.
if val.CanInterface() && val.Type().Implements(textUnmarshalerType) {
if val.CanInterface() {
// This is an unmarshaler with a non-pointer receiver,
// so it's likely to be incorrect, but we do what we're told.
return val.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value))
if textUnmarshaler, ok := reflect.TypeAssert[encoding.TextUnmarshaler](val); ok {
return textUnmarshaler.UnmarshalText([]byte(attr.Value))
}
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
return pv.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(attr.Value))
if pv.CanInterface() {
if textUnmarshaler, ok := reflect.TypeAssert[encoding.TextUnmarshaler](pv); ok {
return textUnmarshaler.UnmarshalText([]byte(attr.Value))
}
}
}
@ -303,12 +311,7 @@ func (d *Decoder) unmarshalAttr(val reflect.Value, attr Attr) error {
return copyValue(val, []byte(attr.Value))
}
var (
attrType = reflect.TypeFor[Attr]()
unmarshalerType = reflect.TypeFor[Unmarshaler]()
unmarshalerAttrType = reflect.TypeFor[UnmarshalerAttr]()
textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]()
)
var attrType = reflect.TypeFor[Attr]()
const (
maxUnmarshalDepth = 10000
@ -352,27 +355,35 @@ func (d *Decoder) unmarshal(val reflect.Value, start *StartElement, depth int) e
val = val.Elem()
}
if val.CanInterface() && val.Type().Implements(unmarshalerType) {
if val.CanInterface() {
// This is an unmarshaler with a non-pointer receiver,
// so it's likely to be incorrect, but we do what we're told.
return d.unmarshalInterface(val.Interface().(Unmarshaler), start)
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(unmarshalerType) {
return d.unmarshalInterface(pv.Interface().(Unmarshaler), start)
if unmarshaler, ok := reflect.TypeAssert[Unmarshaler](val); ok {
return d.unmarshalInterface(unmarshaler, start)
}
}
if val.CanInterface() && val.Type().Implements(textUnmarshalerType) {
return d.unmarshalTextInterface(val.Interface().(encoding.TextUnmarshaler))
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() {
if unmarshaler, ok := reflect.TypeAssert[Unmarshaler](pv); ok {
return d.unmarshalInterface(unmarshaler, start)
}
}
}
if val.CanInterface() {
if textUnmarshaler, ok := reflect.TypeAssert[encoding.TextUnmarshaler](val); ok {
return d.unmarshalTextInterface(textUnmarshaler)
}
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
return d.unmarshalTextInterface(pv.Interface().(encoding.TextUnmarshaler))
if pv.CanInterface() {
if textUnmarshaler, ok := reflect.TypeAssert[encoding.TextUnmarshaler](pv); ok {
return d.unmarshalTextInterface(textUnmarshaler)
}
}
}
@ -453,7 +464,7 @@ func (d *Decoder) unmarshal(val reflect.Value, start *StartElement, depth int) e
return UnmarshalError(e)
}
fv := finfo.value(sv, initNilPointers)
if _, ok := fv.Interface().(Name); ok {
if _, ok := reflect.TypeAssert[Name](fv); ok {
fv.Set(reflect.ValueOf(start.Name))
}
}
@ -578,20 +589,24 @@ Loop:
}
}
if saveData.IsValid() && saveData.CanInterface() && saveData.Type().Implements(textUnmarshalerType) {
if err := saveData.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {
return err
if saveData.IsValid() && saveData.CanInterface() {
if textUnmarshaler, ok := reflect.TypeAssert[encoding.TextUnmarshaler](saveData); ok {
if err := textUnmarshaler.UnmarshalText(data); err != nil {
return err
}
saveData = reflect.Value{}
}
saveData = reflect.Value{}
}
if saveData.IsValid() && saveData.CanAddr() {
pv := saveData.Addr()
if pv.CanInterface() && pv.Type().Implements(textUnmarshalerType) {
if err := pv.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil {
return err
if pv.CanInterface() {
if textUnmarshaler, ok := reflect.TypeAssert[encoding.TextUnmarshaler](pv); ok {
if err := textUnmarshaler.UnmarshalText(data); err != nil {
return err
}
saveData = reflect.Value{}
}
saveData = reflect.Value{}
}
}

View file

@ -41,12 +41,12 @@
//
// because the former will succeed if err wraps [io/fs.ErrExist].
//
// [As] examines the tree of its first argument looking for an error that can be
// assigned to its second argument, which must be a pointer. If it succeeds, it
// performs the assignment and returns true. Otherwise, it returns false. The form
// [AsType] examines the tree of its argument looking for an error whose
// type matches its type argument. If it succeeds, it returns the
// corresponding value of that type and true. Otherwise, it returns the
// zero value of that type and false. The form
//
// var perr *fs.PathError
// if errors.As(err, &perr) {
// if perr, ok := errors.AsType[*fs.PathError](err); ok {
// fmt.Println(perr.Path)
// }
//

View file

@ -102,6 +102,18 @@ func ExampleAs() {
// Failed at path: non-existing
}
func ExampleAsType() {
if _, err := os.Open("non-existing"); err != nil {
if pathError, ok := errors.AsType[*fs.PathError](err); ok {
fmt.Println("Failed at path:", pathError.Path)
} else {
fmt.Println(err)
}
}
// Output:
// Failed at path: non-existing
}
func ExampleUnwrap() {
err1 := errors.New("error1")
err2 := fmt.Errorf("error2: [%w]", err1)

View file

@ -80,6 +80,10 @@ func is(err, target error, targetComparable bool) bool {
// As finds the first error in err's tree that matches target, and if one is found, sets
// target to that error value and returns true. Otherwise, it returns false.
//
// For most uses, prefer [AsType]. As is equivalent to [AsType] but sets its target
// argument rather than returning the matching error and doesn't require its target
// argument to implement error.
//
// The tree consists of err itself, followed by the errors obtained by repeatedly
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple
// errors, As examines err followed by a depth-first traversal of its children.
@ -145,3 +149,60 @@ func as(err error, target any, targetVal reflectlite.Value, targetType reflectli
}
var errorType = reflectlite.TypeOf((*error)(nil)).Elem()
// AsType finds the first error in err's tree that matches the type E, and
// if one is found, returns that error value and true. Otherwise, it
// returns the zero value of E and false.
//
// The tree consists of err itself, followed by the errors obtained by
// repeatedly calling its Unwrap() error or Unwrap() []error method. When
// err wraps multiple errors, AsType examines err followed by a
// depth-first traversal of its children.
//
// An error err matches the type E if the type assertion err.(E) holds,
// or if the error has a method As(any) bool such that err.As(target)
// returns true when target is a non-nil *E. In the latter case, the As
// method is responsible for setting target.
func AsType[E error](err error) (E, bool) {
if err == nil {
var zero E
return zero, false
}
var pe *E // lazily initialized
return asType(err, &pe)
}
func asType[E error](err error, ppe **E) (_ E, _ bool) {
for {
if e, ok := err.(E); ok {
return e, true
}
if x, ok := err.(interface{ As(any) bool }); ok {
if *ppe == nil {
*ppe = new(E)
}
if x.As(*ppe) {
return **ppe, true
}
}
switch x := err.(type) {
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
return
}
case interface{ Unwrap() []error }:
for _, err := range x.Unwrap() {
if err == nil {
continue
}
if x, ok := asType(err, ppe); ok {
return x, true
}
}
return
default:
return
}
}
}

View file

@ -239,6 +239,123 @@ func TestAsValidation(t *testing.T) {
}
}
func TestAsType(t *testing.T) {
var errT errorT
var errP *fs.PathError
type timeout interface {
Timeout() bool
error
}
_, errF := os.Open("non-existing")
poserErr := &poser{"oh no", nil}
testAsType(t,
nil,
errP,
false,
)
testAsType(t,
wrapped{"pitied the fool", errorT{"T"}},
errorT{"T"},
true,
)
testAsType(t,
errF,
errF,
true,
)
testAsType(t,
errT,
errP,
false,
)
testAsType(t,
wrapped{"wrapped", nil},
errT,
false,
)
testAsType(t,
&poser{"error", nil},
errorT{"poser"},
true,
)
testAsType(t,
&poser{"path", nil},
poserPathErr,
true,
)
testAsType(t,
poserErr,
poserErr,
true,
)
testAsType(t,
errors.New("err"),
timeout(nil),
false,
)
testAsType(t,
errF,
errF.(timeout),
true)
testAsType(t,
wrapped{"path error", errF},
errF.(timeout),
true,
)
testAsType(t,
multiErr{},
errT,
false,
)
testAsType(t,
multiErr{errors.New("a"), errorT{"T"}},
errorT{"T"},
true,
)
testAsType(t,
multiErr{errorT{"T"}, errors.New("a")},
errorT{"T"},
true,
)
testAsType(t,
multiErr{errorT{"a"}, errorT{"b"}},
errorT{"a"},
true,
)
testAsType(t,
multiErr{multiErr{errors.New("a"), errorT{"a"}}, errorT{"b"}},
errorT{"a"},
true,
)
testAsType(t,
multiErr{wrapped{"path error", errF}},
errF.(timeout),
true,
)
testAsType(t,
multiErr{nil},
errT,
false,
)
}
type compError interface {
comparable
error
}
func testAsType[E compError](t *testing.T, err error, want E, wantOK bool) {
t.Helper()
name := fmt.Sprintf("AsType[%T](Errorf(..., %v))", want, err)
t.Run(name, func(t *testing.T) {
got, gotOK := errors.AsType[E](err)
if gotOK != wantOK || got != want {
t.Fatalf("got %v, %t; want %v, %t", got, gotOK, want, wantOK)
}
})
}
func BenchmarkIs(b *testing.B) {
err1 := errors.New("1")
err2 := multiErr{multiErr{multiErr{err1, errorT{"a"}}, errorT{"b"}}}
@ -260,6 +377,15 @@ func BenchmarkAs(b *testing.B) {
}
}
func BenchmarkAsType(b *testing.B) {
err := multiErr{multiErr{multiErr{errors.New("a"), errorT{"a"}}, errorT{"b"}}}
for range b.N {
if _, ok := errors.AsType[errorT](err); !ok {
b.Fatal("AsType failed")
}
}
}
func TestUnwrap(t *testing.T) {
err1 := errors.New("1")
erra := wrapped{"wrap 2", err1}

View file

@ -4,7 +4,7 @@ go 1.26
require (
golang.org/x/crypto v0.42.0
golang.org/x/net v0.44.0
golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f
)
require (

View file

@ -1,7 +1,7 @@
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f h1:vNklv+oJQSYNGsWXHoCPi2MHMcpj9/Q7aBhvvfnJvGg=
golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=

View file

@ -487,11 +487,16 @@ var depsRules = `
internal/byteorder < crypto/internal/fips140deps/byteorder;
internal/cpu, internal/goarch < crypto/internal/fips140deps/cpu;
internal/godebug < crypto/internal/fips140deps/godebug;
time, internal/syscall/windows < crypto/internal/fips140deps/time;
crypto/internal/fips140deps/time, errors, math/bits, sync/atomic, unsafe
< crypto/internal/fips140/entropy;
STR, hash,
crypto/internal/impl,
crypto/internal/entropy,
crypto/internal/randutil,
crypto/internal/fips140/entropy,
crypto/internal/fips140deps/byteorder,
crypto/internal/fips140deps/cpu,
crypto/internal/fips140deps/godebug

View file

@ -85,7 +85,7 @@ func gofips140() string {
}
// isFIPSVersion reports whether v is a valid FIPS version,
// of the form vX.Y.Z.
// of the form vX.Y.Z or vX.Y.Z-hash.
func isFIPSVersion(v string) bool {
if !strings.HasPrefix(v, "v") {
return false
@ -99,7 +99,8 @@ func isFIPSVersion(v string) bool {
return false
}
v, ok = skipNum(v[len("."):])
return ok && v == ""
hasHash := strings.HasPrefix(v, "-") && len(v) == len("-")+8
return ok && (v == "" || hasHash)
}
// skipNum skips the leading text matching [0-9]+
@ -320,18 +321,13 @@ func goriscv64() int {
}
type gowasmFeatures struct {
SatConv bool
SignExt bool
// Legacy features, now always enabled
//SatConv bool
//SignExt bool
}
func (f gowasmFeatures) String() string {
var flags []string
if f.SatConv {
flags = append(flags, "satconv")
}
if f.SignExt {
flags = append(flags, "signext")
}
return strings.Join(flags, ",")
}
@ -339,9 +335,9 @@ func gowasm() (f gowasmFeatures) {
for opt := range strings.SplitSeq(envOr("GOWASM", ""), ",") {
switch opt {
case "satconv":
f.SatConv = true
// ignore, always enabled
case "signext":
f.SignExt = true
// ignore, always enabled
case "":
// ignore
default:
@ -451,12 +447,10 @@ func gogoarchTags() []string {
return list
case "wasm":
var list []string
if GOWASM.SatConv {
list = append(list, GOARCH+".satconv")
}
if GOWASM.SignExt {
list = append(list, GOARCH+".signext")
}
// SatConv is always enabled
list = append(list, GOARCH+".satconv")
// SignExt is always enabled
list = append(list, GOARCH+".signext")
return list
}
return nil

View file

@ -0,0 +1,8 @@
// Code generated by mkconsts.go. DO NOT EDIT.
//go:build !goexperiment.goroutineleakprofile
package goexperiment
const GoroutineLeakProfile = false
const GoroutineLeakProfileInt = 0

View file

@ -0,0 +1,8 @@
// Code generated by mkconsts.go. DO NOT EDIT.
//go:build goexperiment.goroutineleakprofile
package goexperiment
const GoroutineLeakProfile = true
const GoroutineLeakProfileInt = 1

View file

@ -119,6 +119,9 @@ type Flags struct {
// SizeSpecializedMalloc enables malloc implementations that are specialized per size class.
SizeSpecializedMalloc bool
// GoroutineLeakProfile enables the collection of goroutine leak profiles.
GoroutineLeakProfile bool
// SIMD enables the simd package and the compiler's handling
// of SIMD intrinsics.
SIMD bool

View file

@ -9,6 +9,7 @@ import (
"internal/race"
"internal/syscall/windows"
"io"
"runtime"
"sync"
"sync/atomic"
"syscall"
@ -75,25 +76,6 @@ type operation struct {
// fields used by runtime.netpoll
runtimeCtx uintptr
mode int32
// fields used only by net package
buf syscall.WSABuf
}
func (o *operation) setEvent() {
h, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
// This shouldn't happen when all CreateEvent arguments are zero.
panic(err)
}
// Set the low bit so that the external IOCP doesn't receive the completion packet.
o.o.HEvent = h | 1
}
func (o *operation) close() {
if o.o.HEvent != 0 {
syscall.CloseHandle(o.o.HEvent)
}
}
func (fd *FD) overlapped(o *operation) *syscall.Overlapped {
@ -107,9 +89,8 @@ func (fd *FD) overlapped(o *operation) *syscall.Overlapped {
return &o.o
}
func (o *operation) InitBuf(buf []byte) {
o.buf.Len = uint32(len(buf))
o.buf.Buf = unsafe.SliceData(buf)
func newWsaBuf(b []byte) *syscall.WSABuf {
return &syscall.WSABuf{Buf: unsafe.SliceData(b), Len: uint32(len(b))}
}
var wsaBufsPool = sync.Pool{
@ -168,7 +149,7 @@ var wsaMsgPool = sync.Pool{
// newWSAMsg creates a new WSAMsg with the provided parameters.
// Use [freeWSAMsg] to free it.
func newWSAMsg(p []byte, oob []byte, flags int, rsa *syscall.RawSockaddrAny) *windows.WSAMsg {
func newWSAMsg(p []byte, oob []byte, flags int, unconnected bool) *windows.WSAMsg {
// The returned object can't be allocated in the stack because it is accessed asynchronously
// by Windows in between several system calls. If the stack frame is moved while that happens,
// then Windows may access invalid memory.
@ -183,11 +164,9 @@ func newWSAMsg(p []byte, oob []byte, flags int, rsa *syscall.RawSockaddrAny) *wi
Buf: unsafe.SliceData(oob),
}
msg.Flags = uint32(flags)
msg.Name = syscall.Pointer(unsafe.Pointer(rsa))
if rsa != nil {
msg.Namelen = int32(unsafe.Sizeof(*rsa))
} else {
msg.Namelen = 0
if unconnected {
msg.Name = wsaRsaPool.Get().(*syscall.RawSockaddrAny)
msg.Namelen = int32(unsafe.Sizeof(syscall.RawSockaddrAny{}))
}
return msg
}
@ -198,6 +177,12 @@ func freeWSAMsg(msg *windows.WSAMsg) {
msg.Buffers.Buf = nil
msg.Control.Len = 0
msg.Control.Buf = nil
if msg.Name != nil {
*msg.Name = syscall.RawSockaddrAny{}
wsaRsaPool.Put(msg.Name)
msg.Name = nil
msg.Namelen = 0
}
wsaMsgPool.Put(msg)
}
@ -207,6 +192,12 @@ var wsaRsaPool = sync.Pool{
},
}
var operationPool = sync.Pool{
New: func() any {
return new(operation)
},
}
// waitIO waits for the IO operation o to complete.
func (fd *FD) waitIO(o *operation) error {
if fd.isBlocking {
@ -245,23 +236,57 @@ func (fd *FD) cancelIO(o *operation) {
fd.pd.waitCanceled(int(o.mode))
}
// pin pins ptr for the duration of the IO operation.
// If fd is in blocking mode, pin does nothing.
func (fd *FD) pin(mode int, ptr any) {
if fd.isBlocking {
return
}
if mode == 'r' {
fd.readPinner.Pin(ptr)
} else {
fd.writePinner.Pin(ptr)
}
}
// execIO executes a single IO operation o.
// It supports both synchronous and asynchronous IO.
// o.qty and o.flags are set to zero before calling submit
// to avoid reusing the values from a previous call.
func (fd *FD) execIO(o *operation, submit func(o *operation) (uint32, error)) (int, error) {
func (fd *FD) execIO(mode int, submit func(o *operation) (uint32, error)) (int, error) {
if mode == 'r' {
defer fd.readPinner.Unpin()
} else {
defer fd.writePinner.Unpin()
}
// Notify runtime netpoll about starting IO.
err := fd.pd.prepare(int(o.mode), fd.isFile)
err := fd.pd.prepare(mode, fd.isFile)
if err != nil {
return 0, err
}
o := operationPool.Get().(*operation)
defer operationPool.Put(o)
*o = operation{
o: syscall.Overlapped{
OffsetHigh: uint32(fd.offset >> 32),
Offset: uint32(fd.offset),
},
runtimeCtx: fd.pd.runtimeCtx,
mode: int32(mode),
}
// Start IO.
if !fd.isBlocking && o.o.HEvent == 0 && !fd.pollable() {
if !fd.isBlocking && !fd.pollable() {
// If the handle is opened for overlapped IO but we can't
// use the runtime poller, then we need to use an
// event to wait for the IO to complete.
o.setEvent()
h, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
// This shouldn't happen when all CreateEvent arguments are zero.
panic(err)
}
// Set the low bit so that the external IOCP doesn't receive the completion packet.
o.o.HEvent = h | 1
defer syscall.CloseHandle(h)
}
fd.pin(mode, o)
qty, err := submit(o)
var waitErr error
// Blocking operations shouldn't return ERROR_IO_PENDING.
@ -316,11 +341,6 @@ type FD struct {
// System file descriptor. Immutable until Close.
Sysfd syscall.Handle
// Read operation.
rop operation
// Write operation.
wop operation
// I/O poller.
pd pollDesc
@ -358,6 +378,11 @@ type FD struct {
isBlocking bool
disassociated atomic.Bool
// readPinner and writePinner are automatically unpinned
// before execIO returns.
readPinner runtime.Pinner
writePinner runtime.Pinner
}
// setOffset sets the offset fields of the overlapped object
@ -375,8 +400,6 @@ type FD struct {
// using an external mechanism.
func (fd *FD) setOffset(off int64) {
fd.offset = off
fd.rop.o.OffsetHigh, fd.rop.o.Offset = uint32(off>>32), uint32(off)
fd.wop.o.OffsetHigh, fd.wop.o.Offset = uint32(off>>32), uint32(off)
}
// addOffset adds the given offset to the current offset.
@ -427,8 +450,6 @@ func (fd *FD) Init(net string, pollable bool) error {
}
fd.isFile = fd.kind != kindNet
fd.isBlocking = !pollable
fd.rop.mode = 'r'
fd.wop.mode = 'w'
// It is safe to add overlapped handles that also perform I/O
// outside of the runtime poller. The runtime poller will ignore
@ -437,8 +458,6 @@ func (fd *FD) Init(net string, pollable bool) error {
if err != nil {
return err
}
fd.rop.runtimeCtx = fd.pd.runtimeCtx
fd.wop.runtimeCtx = fd.pd.runtimeCtx
if fd.kind != kindNet || socketCanUseSetFileCompletionNotificationModes {
// Non-socket handles can use SetFileCompletionNotificationModes without problems.
err := syscall.SetFileCompletionNotificationModes(fd.Sysfd,
@ -477,8 +496,6 @@ func (fd *FD) destroy() error {
if fd.Sysfd == syscall.InvalidHandle {
return syscall.EINVAL
}
fd.rop.close()
fd.wop.close()
// Poller may want to unregister fd in readiness notification mechanism,
// so this must be executed before fd.CloseFunc.
fd.pd.close()
@ -533,6 +550,10 @@ func (fd *FD) Read(buf []byte) (int, error) {
defer fd.readUnlock()
}
if len(buf) > 0 {
fd.pin('r', &buf[0])
}
if len(buf) > maxRW {
buf = buf[:maxRW]
}
@ -543,10 +564,8 @@ func (fd *FD) Read(buf []byte) (int, error) {
case kindConsole:
n, err = fd.readConsole(buf)
case kindFile, kindPipe:
o := &fd.rop
o.InitBuf(buf)
n, err = fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = syscall.ReadFile(fd.Sysfd, unsafe.Slice(o.buf.Buf, o.buf.Len), &qty, fd.overlapped(o))
n, err = fd.execIO('r', func(o *operation) (qty uint32, err error) {
err = syscall.ReadFile(fd.Sysfd, buf, &qty, fd.overlapped(o))
return qty, err
})
fd.addOffset(n)
@ -560,11 +579,9 @@ func (fd *FD) Read(buf []byte) (int, error) {
}
}
case kindNet:
o := &fd.rop
o.InitBuf(buf)
n, err = fd.execIO(o, func(o *operation) (qty uint32, err error) {
n, err = fd.execIO('r', func(o *operation) (qty uint32, err error) {
var flags uint32
err = syscall.WSARecv(fd.Sysfd, &o.buf, 1, &qty, &flags, &o.o, nil)
err = syscall.WSARecv(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &o.o, nil)
return qty, err
})
if race.Enabled {
@ -652,7 +669,7 @@ func (fd *FD) readConsole(b []byte) (int, error) {
}
// Pread emulates the Unix pread system call.
func (fd *FD) Pread(b []byte, off int64) (int, error) {
func (fd *FD) Pread(buf []byte, off int64) (int, error) {
if fd.kind == kindPipe {
// Pread does not work with pipes
return 0, syscall.ESPIPE
@ -663,8 +680,12 @@ func (fd *FD) Pread(b []byte, off int64) (int, error) {
}
defer fd.readWriteUnlock()
if len(b) > maxRW {
b = b[:maxRW]
if len(buf) > 0 {
fd.pin('r', &buf[0])
}
if len(buf) > maxRW {
buf = buf[:maxRW]
}
if fd.isBlocking {
@ -683,17 +704,15 @@ func (fd *FD) Pread(b []byte, off int64) (int, error) {
curoffset := fd.offset
defer fd.setOffset(curoffset)
}
o := &fd.rop
o.InitBuf(b)
fd.setOffset(off)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = syscall.ReadFile(fd.Sysfd, unsafe.Slice(o.buf.Buf, o.buf.Len), &qty, &o.o)
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
err = syscall.ReadFile(fd.Sysfd, buf, &qty, &o.o)
return qty, err
})
if err == syscall.ERROR_HANDLE_EOF {
err = io.EOF
}
if len(b) != 0 {
if len(buf) != 0 {
err = fd.eofError(n, err)
}
return n, err
@ -711,14 +730,15 @@ func (fd *FD) ReadFrom(buf []byte) (int, syscall.Sockaddr, error) {
return 0, nil, err
}
defer fd.readUnlock()
o := &fd.rop
o.InitBuf(buf)
fd.pin('r', &buf[0])
rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
defer wsaRsaPool.Put(rsa)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
rsan := int32(unsafe.Sizeof(*rsa))
var flags uint32
err = syscall.WSARecvFrom(fd.Sysfd, &o.buf, 1, &qty, &flags, rsa, &rsan, &o.o, nil)
err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
return qty, err
})
err = fd.eofError(n, err)
@ -741,14 +761,15 @@ func (fd *FD) ReadFromInet4(buf []byte, sa4 *syscall.SockaddrInet4) (int, error)
return 0, err
}
defer fd.readUnlock()
o := &fd.rop
o.InitBuf(buf)
fd.pin('r', &buf[0])
rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
defer wsaRsaPool.Put(rsa)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
rsan := int32(unsafe.Sizeof(*rsa))
var flags uint32
err = syscall.WSARecvFrom(fd.Sysfd, &o.buf, 1, &qty, &flags, rsa, &rsan, &o.o, nil)
err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
return qty, err
})
err = fd.eofError(n, err)
@ -771,14 +792,15 @@ func (fd *FD) ReadFromInet6(buf []byte, sa6 *syscall.SockaddrInet6) (int, error)
return 0, err
}
defer fd.readUnlock()
o := &fd.rop
o.InitBuf(buf)
fd.pin('r', &buf[0])
rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
defer wsaRsaPool.Put(rsa)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
rsan := int32(unsafe.Sizeof(*rsa))
var flags uint32
err = syscall.WSARecvFrom(fd.Sysfd, &o.buf, 1, &qty, &flags, rsa, &rsan, &o.o, nil)
err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
return qty, err
})
err = fd.eofError(n, err)
@ -803,6 +825,9 @@ func (fd *FD) Write(buf []byte) (int, error) {
defer fd.writeUnlock()
}
if len(buf) > 0 {
fd.pin('w', &buf[0])
}
var ntotal int
for {
max := len(buf)
@ -816,10 +841,8 @@ func (fd *FD) Write(buf []byte) (int, error) {
case kindConsole:
n, err = fd.writeConsole(b)
case kindPipe, kindFile:
o := &fd.wop
o.InitBuf(b)
n, err = fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = syscall.WriteFile(fd.Sysfd, unsafe.Slice(o.buf.Buf, o.buf.Len), &qty, fd.overlapped(o))
n, err = fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = syscall.WriteFile(fd.Sysfd, b, &qty, fd.overlapped(o))
return qty, err
})
fd.addOffset(n)
@ -827,10 +850,8 @@ func (fd *FD) Write(buf []byte) (int, error) {
if race.Enabled {
race.ReleaseMerge(unsafe.Pointer(&ioSync))
}
o := &fd.wop
o.InitBuf(b)
n, err = fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = syscall.WSASend(fd.Sysfd, &o.buf, 1, &qty, 0, &o.o, nil)
n, err = fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = syscall.WSASend(fd.Sysfd, newWsaBuf(b), 1, &qty, 0, &o.o, nil)
return qty, err
})
}
@ -899,6 +920,10 @@ func (fd *FD) Pwrite(buf []byte, off int64) (int, error) {
}
defer fd.readWriteUnlock()
if len(buf) > 0 {
fd.pin('w', &buf[0])
}
if fd.isBlocking {
curoffset, err := syscall.Seek(fd.Sysfd, 0, io.SeekCurrent)
if err != nil {
@ -922,12 +947,9 @@ func (fd *FD) Pwrite(buf []byte, off int64) (int, error) {
if max-ntotal > maxRW {
max = ntotal + maxRW
}
b := buf[ntotal:max]
o := &fd.wop
o.InitBuf(b)
fd.setOffset(off + int64(ntotal))
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = syscall.WriteFile(fd.Sysfd, unsafe.Slice(o.buf.Buf, o.buf.Len), &qty, &o.o)
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = syscall.WriteFile(fd.Sysfd, buf[ntotal:max], &qty, &o.o)
return qty, err
})
if n > 0 {
@ -956,7 +978,7 @@ func (fd *FD) Writev(buf *[][]byte) (int64, error) {
}
bufs := newWSABufs(buf)
defer freeWSABufs(bufs)
n, err := fd.execIO(&fd.wop, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = syscall.WSASend(fd.Sysfd, &(*bufs)[0], uint32(len(*bufs)), &qty, 0, &o.o, nil)
return qty, err
})
@ -974,25 +996,23 @@ func (fd *FD) WriteTo(buf []byte, sa syscall.Sockaddr) (int, error) {
if len(buf) == 0 {
// handle zero-byte payload
o := &fd.wop
o.InitBuf(buf)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = syscall.WSASendto(fd.Sysfd, &o.buf, 1, &qty, 0, sa, &o.o, nil)
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = syscall.WSASendto(fd.Sysfd, &syscall.WSABuf{}, 1, &qty, 0, sa, &o.o, nil)
return qty, err
})
return n, err
}
fd.pin('w', &buf[0])
ntotal := 0
for len(buf) > 0 {
b := buf
if len(b) > maxRW {
b = b[:maxRW]
}
o := &fd.wop
o.InitBuf(b)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = syscall.WSASendto(fd.Sysfd, &o.buf, 1, &qty, 0, sa, &o.o, nil)
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = syscall.WSASendto(fd.Sysfd, newWsaBuf(b), 1, &qty, 0, sa, &o.o, nil)
return qty, err
})
ntotal += int(n)
@ -1013,25 +1033,23 @@ func (fd *FD) WriteToInet4(buf []byte, sa4 *syscall.SockaddrInet4) (int, error)
if len(buf) == 0 {
// handle zero-byte payload
o := &fd.wop
o.InitBuf(buf)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = windows.WSASendtoInet4(fd.Sysfd, &o.buf, 1, &qty, 0, sa4, &o.o, nil)
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = windows.WSASendtoInet4(fd.Sysfd, &syscall.WSABuf{}, 1, &qty, 0, sa4, &o.o, nil)
return qty, err
})
return n, err
}
fd.pin('w', &buf[0])
ntotal := 0
for len(buf) > 0 {
b := buf
if len(b) > maxRW {
b = b[:maxRW]
}
o := &fd.wop
o.InitBuf(b)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = windows.WSASendtoInet4(fd.Sysfd, &o.buf, 1, &qty, 0, sa4, &o.o, nil)
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = windows.WSASendtoInet4(fd.Sysfd, newWsaBuf(b), 1, &qty, 0, sa4, &o.o, nil)
return qty, err
})
ntotal += int(n)
@ -1052,25 +1070,23 @@ func (fd *FD) WriteToInet6(buf []byte, sa6 *syscall.SockaddrInet6) (int, error)
if len(buf) == 0 {
// handle zero-byte payload
o := &fd.wop
o.InitBuf(buf)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = windows.WSASendtoInet6(fd.Sysfd, &o.buf, 1, &qty, 0, sa6, &o.o, nil)
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = windows.WSASendtoInet6(fd.Sysfd, &syscall.WSABuf{}, 1, &qty, 0, sa6, &o.o, nil)
return qty, err
})
return n, err
}
fd.pin('w', &buf[0])
ntotal := 0
for len(buf) > 0 {
b := buf
if len(b) > maxRW {
b = b[:maxRW]
}
o := &fd.wop
o.InitBuf(b)
n, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
err = windows.WSASendtoInet6(fd.Sysfd, &o.buf, 1, &qty, 0, sa6, &o.o, nil)
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = windows.WSASendtoInet6(fd.Sysfd, newWsaBuf(b), 1, &qty, 0, sa6, &o.o, nil)
return qty, err
})
ntotal += int(n)
@ -1086,17 +1102,16 @@ func (fd *FD) WriteToInet6(buf []byte, sa6 *syscall.SockaddrInet6) (int, error)
// called when the descriptor is first created. This is here rather
// than in the net package so that it can use fd.wop.
func (fd *FD) ConnectEx(ra syscall.Sockaddr) error {
o := &fd.wop
_, err := fd.execIO(o, func(o *operation) (uint32, error) {
_, err := fd.execIO('w', func(o *operation) (uint32, error) {
return 0, ConnectExFunc(fd.Sysfd, ra, nil, 0, nil, &o.o)
})
return err
}
func (fd *FD) acceptOne(s syscall.Handle, rawsa []syscall.RawSockaddrAny, o *operation) (string, error) {
func (fd *FD) acceptOne(s syscall.Handle, rawsa []syscall.RawSockaddrAny) (string, error) {
// Submit accept request.
rsan := uint32(unsafe.Sizeof(rawsa[0]))
_, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
_, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
err = AcceptFunc(fd.Sysfd, s, (*byte)(unsafe.Pointer(&rawsa[0])), 0, rsan, rsan, &qty, &o.o)
return qty, err
@ -1124,7 +1139,6 @@ func (fd *FD) Accept(sysSocket func() (syscall.Handle, error)) (syscall.Handle,
}
defer fd.readUnlock()
o := &fd.rop
var rawsa [2]syscall.RawSockaddrAny
for {
s, err := sysSocket()
@ -1132,7 +1146,7 @@ func (fd *FD) Accept(sysSocket func() (syscall.Handle, error)) (syscall.Handle,
return syscall.InvalidHandle, nil, 0, "", err
}
errcall, err := fd.acceptOne(s, rawsa[:], o)
errcall, err := fd.acceptOne(s, rawsa[:])
if err == nil {
return s, rawsa[:], uint32(unsafe.Sizeof(rawsa[0])), "", nil
}
@ -1165,11 +1179,29 @@ func (fd *FD) Seek(offset int64, whence int) (int64, error) {
}
defer fd.readWriteUnlock()
if !fd.isBlocking && whence == io.SeekCurrent {
// Windows doesn't keep the file pointer for overlapped file handles.
// We do it ourselves in case to account for any read or write
// operations that may have occurred.
offset += fd.offset
if !fd.isBlocking {
// Windows doesn't use the file pointer for overlapped file handles,
// there is no point on calling syscall.Seek.
var newOffset int64
switch whence {
case io.SeekStart:
newOffset = offset
case io.SeekCurrent:
newOffset = fd.offset + offset
case io.SeekEnd:
var size int64
if err := windows.GetFileSizeEx(fd.Sysfd, &size); err != nil {
return 0, err
}
newOffset = size + offset
default:
return 0, windows.ERROR_INVALID_PARAMETER
}
if newOffset < 0 {
return 0, windows.ERROR_NEGATIVE_SEEK
}
fd.setOffset(newOffset)
return newOffset, nil
}
n, err := syscall.Seek(fd.Sysfd, offset, whence)
fd.setOffset(n)
@ -1242,14 +1274,12 @@ func (fd *FD) RawRead(f func(uintptr) bool) error {
// Use a zero-byte read as a way to get notified when this
// socket is readable. h/t https://stackoverflow.com/a/42019668/332798
o := &fd.rop
o.InitBuf(nil)
_, err := fd.execIO(o, func(o *operation) (qty uint32, err error) {
_, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
var flags uint32
if !fd.IsStream {
flags |= windows.MSG_PEEK
}
err = syscall.WSARecv(fd.Sysfd, &o.buf, 1, &qty, &flags, &o.o, nil)
err = syscall.WSARecv(fd.Sysfd, &syscall.WSABuf{}, 1, &qty, &flags, &o.o, nil)
return qty, err
})
if err == windows.WSAEMSGSIZE {
@ -1337,18 +1367,16 @@ func (fd *FD) ReadMsg(p []byte, oob []byte, flags int) (int, int, int, syscall.S
p = p[:maxRW]
}
rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
defer wsaRsaPool.Put(rsa)
msg := newWSAMsg(p, oob, flags, rsa)
msg := newWSAMsg(p, oob, flags, true)
defer freeWSAMsg(msg)
n, err := fd.execIO(&fd.rop, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
return qty, err
})
err = fd.eofError(n, err)
var sa syscall.Sockaddr
if err == nil {
sa, err = rsa.Sockaddr()
sa, err = msg.Name.Sockaddr()
}
return n, int(msg.Control.Len), int(msg.Flags), sa, err
}
@ -1364,17 +1392,15 @@ func (fd *FD) ReadMsgInet4(p []byte, oob []byte, flags int, sa4 *syscall.Sockadd
p = p[:maxRW]
}
rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
defer wsaRsaPool.Put(rsa)
msg := newWSAMsg(p, oob, flags, rsa)
msg := newWSAMsg(p, oob, flags, true)
defer freeWSAMsg(msg)
n, err := fd.execIO(&fd.rop, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
return qty, err
})
err = fd.eofError(n, err)
if err == nil {
rawToSockaddrInet4(rsa, sa4)
rawToSockaddrInet4(msg.Name, sa4)
}
return n, int(msg.Control.Len), int(msg.Flags), err
}
@ -1390,17 +1416,15 @@ func (fd *FD) ReadMsgInet6(p []byte, oob []byte, flags int, sa6 *syscall.Sockadd
p = p[:maxRW]
}
rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
defer wsaRsaPool.Put(rsa)
msg := newWSAMsg(p, oob, flags, rsa)
msg := newWSAMsg(p, oob, flags, true)
defer freeWSAMsg(msg)
n, err := fd.execIO(&fd.rop, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
return qty, err
})
err = fd.eofError(n, err)
if err == nil {
rawToSockaddrInet6(rsa, sa6)
rawToSockaddrInet6(msg.Name, sa6)
}
return n, int(msg.Control.Len), int(msg.Flags), err
}
@ -1416,21 +1440,16 @@ func (fd *FD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (int, int, err
}
defer fd.writeUnlock()
var rsa *syscall.RawSockaddrAny
if sa != nil {
rsa = wsaRsaPool.Get().(*syscall.RawSockaddrAny)
defer wsaRsaPool.Put(rsa)
}
msg := newWSAMsg(p, oob, 0, rsa)
msg := newWSAMsg(p, oob, 0, sa != nil)
defer freeWSAMsg(msg)
if sa != nil {
var err error
msg.Namelen, err = sockaddrToRaw(rsa, sa)
msg.Namelen, err = sockaddrToRaw(msg.Name, sa)
if err != nil {
return 0, 0, err
}
}
n, err := fd.execIO(&fd.wop, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
return qty, err
})
@ -1448,17 +1467,12 @@ func (fd *FD) WriteMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (in
}
defer fd.writeUnlock()
var rsa *syscall.RawSockaddrAny
if sa != nil {
rsa = wsaRsaPool.Get().(*syscall.RawSockaddrAny)
defer wsaRsaPool.Put(rsa)
}
msg := newWSAMsg(p, oob, 0, rsa)
msg := newWSAMsg(p, oob, 0, sa != nil)
defer freeWSAMsg(msg)
if sa != nil {
msg.Namelen = sockaddrInet4ToRaw(rsa, sa)
msg.Namelen = sockaddrInet4ToRaw(msg.Name, sa)
}
n, err := fd.execIO(&fd.wop, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
return qty, err
})
@ -1476,17 +1490,12 @@ func (fd *FD) WriteMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (in
}
defer fd.writeUnlock()
var rsa *syscall.RawSockaddrAny
if sa != nil {
rsa = wsaRsaPool.Get().(*syscall.RawSockaddrAny)
defer wsaRsaPool.Put(rsa)
}
msg := newWSAMsg(p, oob, 0, rsa)
msg := newWSAMsg(p, oob, 0, sa != nil)
defer freeWSAMsg(msg)
if sa != nil {
msg.Namelen = sockaddrInet6ToRaw(rsa, sa)
msg.Namelen = sockaddrInet6ToRaw(msg.Name, sa)
}
n, err := fd.execIO(&fd.wop, func(o *operation) (qty uint32, err error) {
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
return qty, err
})

View file

@ -62,18 +62,14 @@ func SendFile(fd *FD, src uintptr, size int64) (written int64, err error, handle
// See https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-transmitfile
const maxChunkSizePerCall = int64(0x7fffffff - 1)
o := &fd.wop
for size > 0 {
chunkSize := maxChunkSizePerCall
if chunkSize > size {
chunkSize = size
}
off := startpos + written
o.o.Offset = uint32(off)
o.o.OffsetHigh = uint32(off >> 32)
n, err := fd.execIO(o, func(o *operation) (uint32, error) {
fd.setOffset(startpos + written)
n, err := fd.execIO('w', func(o *operation) (uint32, error) {
err := syscall.TransmitFile(fd.Sysfd, hsrc, uint32(chunkSize), 0, &o.o, nil, syscall.TF_WRITE_BEHIND)
if err != nil {
return 0, err

View file

@ -8,7 +8,7 @@ import "internal/goarch"
// ObjMask is a bitmap where each bit corresponds to an object in a span.
//
// It is sized to accomodate all size classes.
// It is sized to accommodate all size classes.
type ObjMask [MaxObjsPerSpan / (goarch.PtrSize * 8)]uintptr
// PtrMask is a bitmap where each bit represents a pointer-word in a single runtime page.

View file

@ -16,7 +16,7 @@ func ExpandReference(sizeClass int, packed *gc.ObjMask, unpacked *gc.PtrMask) {
// Look up the size and derive the number of objects in a span.
// We're only concerned with small objects in single-page spans,
// and gc.PtrMask enforces this by being statically sized to
// accomodate only such spans.
// accommodate only such spans.
size := uintptr(gc.SizeClassToSize[sizeClass])
nObj := uintptr(gc.SizeClassToNPages[sizeClass]) * gc.PageSize / size

View file

@ -39,6 +39,7 @@ const (
ERROR_NOT_SUPPORTED syscall.Errno = 50
ERROR_CALL_NOT_IMPLEMENTED syscall.Errno = 120
ERROR_INVALID_NAME syscall.Errno = 123
ERROR_NEGATIVE_SEEK syscall.Errno = 131
ERROR_LOCK_FAILED syscall.Errno = 167
ERROR_IO_INCOMPLETE syscall.Errno = 996
ERROR_NO_TOKEN syscall.Errno = 1008
@ -195,6 +196,7 @@ const (
//sys SetFileInformationByHandle(handle syscall.Handle, fileInformationClass uint32, buf unsafe.Pointer, bufsize uint32) (err error) = kernel32.SetFileInformationByHandle
//sys VirtualQuery(address uintptr, buffer *MemoryBasicInformation, length uintptr) (err error) = kernel32.VirtualQuery
//sys GetTempPath2(buflen uint32, buf *uint16) (n uint32, err error) = GetTempPath2W
//sys GetFileSizeEx(handle syscall.Handle, size *int64) (err error) = kernel32.GetFileSizeEx
const (
// flags for CreateToolhelp32Snapshot
@ -259,7 +261,7 @@ var sendRecvMsgFunc struct {
}
type WSAMsg struct {
Name syscall.Pointer
Name *syscall.RawSockaddrAny
Namelen int32
Buffers *syscall.WSABuf
BufferCount uint32

View file

@ -73,6 +73,7 @@ var (
procGetConsoleCP = modkernel32.NewProc("GetConsoleCP")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
procGetFileSizeEx = modkernel32.NewProc("GetFileSizeEx")
procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW")
procGetModuleHandleW = modkernel32.NewProc("GetModuleHandleW")
@ -326,6 +327,14 @@ func GetFileInformationByHandleEx(handle syscall.Handle, class uint32, info *byt
return
}
func GetFileSizeEx(handle syscall.Handle, size *int64) (err error) {
r1, _, e1 := syscall.SyscallN(procGetFileSizeEx.Addr(), uintptr(handle), uintptr(unsafe.Pointer(size)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetFinalPathNameByHandle(file syscall.Handle, filePath *uint16, filePathSize uint32, flags uint32) (n uint32, err error) {
r0, _, e1 := syscall.SyscallN(procGetFinalPathNameByHandleW.Addr(), uintptr(file), uintptr(unsafe.Pointer(filePath)), uintptr(filePathSize), uintptr(flags))
n = uint32(r0)

View file

@ -6,6 +6,19 @@
// A file system can be provided by the host operating system
// but also by other packages.
//
// # Path Names
//
// The interfaces in this package all operate on the same
// path name syntax, regardless of the host operating system.
//
// Path names are UTF-8-encoded,
// unrooted, slash-separated sequences of path elements, like “x/y/z”.
// Path names must not contain an element that is “.” or “..” or the empty string,
// except for the special case that the name "." may be used for the root directory.
// Paths must not start or end with a slash: “/x” and “x/” are invalid.
//
// # Testing
//
// See the [testing/fstest] package for support with testing
// implementations of file systems.
package fs
@ -41,16 +54,13 @@ type FS interface {
// ValidPath reports whether the given path name
// is valid for use in a call to Open.
//
// Path names passed to open are UTF-8-encoded,
// unrooted, slash-separated sequences of path elements, like “x/y/z”.
// Path names must not contain an element that is “.” or “..” or the empty string,
// except for the special case that the name "." may be used for the root directory.
// Paths must not start or end with a slash: “/x” and “x/” are invalid.
//
// Note that paths are slash-separated on all systems, even Windows.
// Paths containing other characters such as backslash and colon
// are accepted as valid, but those characters must never be
// interpreted by an [FS] implementation as path element separators.
// See the [Path Names] section for more details.
//
// [Path Names]: https://pkg.go.dev/io/fs#hdr-Path_Names
func ValidPath(name string) bool {
if !utf8.ValidString(name) {
return false

View file

@ -98,24 +98,32 @@ func FormatMediaType(t string, param map[string]string) string {
func checkMediaTypeDisposition(s string) error {
typ, rest := consumeToken(s)
if typ == "" {
return errors.New("mime: no media type")
return errNoMediaType
}
if rest == "" {
return nil
}
if !strings.HasPrefix(rest, "/") {
return errors.New("mime: expected slash after first token")
var ok bool
if rest, ok = strings.CutPrefix(rest, "/"); !ok {
return errNoSlashAfterFirstToken
}
subtype, rest := consumeToken(rest[1:])
subtype, rest := consumeToken(rest)
if subtype == "" {
return errors.New("mime: expected token after slash")
return errNoTokenAfterSlash
}
if rest != "" {
return errors.New("mime: unexpected content after media subtype")
return errUnexpectedContentAfterMediaSubtype
}
return nil
}
var (
errNoMediaType = errors.New("mime: no media type")
errNoSlashAfterFirstToken = errors.New("mime: expected slash after first token")
errNoTokenAfterSlash = errors.New("mime: expected token after slash")
errUnexpectedContentAfterMediaSubtype = errors.New("mime: unexpected content after media subtype")
)
// ErrInvalidMediaParameter is returned by [ParseMediaType] if
// the media type value was found but there was an error parsing
// the optional parameters
@ -169,7 +177,6 @@ func ParseMediaType(v string) (mediatype string, params map[string]string, err e
if continuation == nil {
continuation = make(map[string]map[string]string)
}
var ok bool
if pmap, ok = continuation[baseName]; !ok {
continuation[baseName] = make(map[string]string)
pmap = continuation[baseName]
@ -177,7 +184,7 @@ func ParseMediaType(v string) (mediatype string, params map[string]string, err e
}
if v, exists := pmap[key]; exists && v != value {
// Duplicate parameter names are incorrect, but we allow them if they are equal.
return "", nil, errors.New("mime: duplicate parameter name")
return "", nil, errDuplicateParamName
}
pmap[key] = value
v = rest
@ -227,27 +234,28 @@ func ParseMediaType(v string) (mediatype string, params map[string]string, err e
return
}
var errDuplicateParamName = errors.New("mime: duplicate parameter name")
func decode2231Enc(v string) (string, bool) {
sv := strings.SplitN(v, "'", 3)
if len(sv) != 3 {
charset, v, ok := strings.Cut(v, "'")
if !ok {
return "", false
}
// TODO: ignoring lang in sv[1] for now. If anybody needs it we'll
// TODO: ignoring the language part for now. If anybody needs it, we'll
// need to decide how to expose it in the API. But I'm not sure
// anybody uses it in practice.
charset := strings.ToLower(sv[0])
if len(charset) == 0 {
_, extOtherVals, ok := strings.Cut(v, "'")
if !ok {
return "", false
}
if charset != "us-ascii" && charset != "utf-8" {
// TODO: unsupported encoding
charset = strings.ToLower(charset)
switch charset {
case "us-ascii", "utf-8":
default:
// Empty or unsupported encoding.
return "", false
}
encv, err := percentHexUnescape(sv[2])
if err != nil {
return "", false
}
return encv, true
return percentHexUnescape(extOtherVals)
}
// consumeToken consumes a token from the beginning of provided
@ -309,11 +317,11 @@ func consumeValue(v string) (value, rest string) {
func consumeMediaParam(v string) (param, value, rest string) {
rest = strings.TrimLeftFunc(v, unicode.IsSpace)
if !strings.HasPrefix(rest, ";") {
var ok bool
if rest, ok = strings.CutPrefix(rest, ";"); !ok {
return "", "", v
}
rest = rest[1:] // consume semicolon
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
param, rest = consumeToken(rest)
param = strings.ToLower(param)
@ -322,10 +330,9 @@ func consumeMediaParam(v string) (param, value, rest string) {
}
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
if !strings.HasPrefix(rest, "=") {
if rest, ok = strings.CutPrefix(rest, "="); !ok {
return "", "", v
}
rest = rest[1:] // consume equals sign
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
value, rest2 := consumeValue(rest)
if value == "" && rest2 == rest {
@ -335,7 +342,7 @@ func consumeMediaParam(v string) (param, value, rest string) {
return param, value, rest
}
func percentHexUnescape(s string) (string, error) {
func percentHexUnescape(s string) (string, bool) {
// Count %, check that they're well-formed.
percents := 0
for i := 0; i < len(s); {
@ -345,16 +352,12 @@ func percentHexUnescape(s string) (string, error) {
}
percents++
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
s = s[i:]
if len(s) > 3 {
s = s[0:3]
}
return "", fmt.Errorf("mime: bogus characters after %%: %q", s)
return "", false
}
i += 3
}
if percents == 0 {
return s, nil
return s, true
}
t := make([]byte, len(s)-2*percents)
@ -371,7 +374,7 @@ func percentHexUnescape(s string) (string, error) {
i++
}
}
return string(t), nil
return string(t), true
}
func ishex(c byte) bool {

View file

@ -125,8 +125,20 @@ func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) {
return p, nil
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"", "\r", "%0D", "\n", "%0A")
// escapeQuotes escapes special characters in field parameter values.
//
// For historical reasons, this uses \ escaping for " and \ characters,
// and percent encoding for CR and LF.
//
// The WhatWG specification for form data encoding suggests that we should
// use percent encoding for " (%22), and should not escape \.
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm
//
// Empirically, as of the time this comment was written, it is necessary
// to escape \ characters or else Chrome (and possibly other browsers) will
// interpet the unescaped \ as an escape.
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}

Some files were not shown because too many files have changed in this diff Show more