[dev.boringcrypto.go1.17] all: merge go1.17.12 into dev.boringcrypto.go1.17

Change-Id: I652dbbb5721fa0c7be09be34b9ac1a987c331f6d
This commit is contained in:
Michael Anthony Knyszek 2022-07-13 15:35:44 +00:00
commit 7d5078e3bf
32 changed files with 661 additions and 120 deletions

View File

@ -78,6 +78,8 @@ func storeByType(t *types.Type) obj.As {
return x86.AMOVL return x86.AMOVL
case 8: case 8:
return x86.AMOVQ return x86.AMOVQ
case 16:
return x86.AMOVUPS
} }
} }
panic(fmt.Sprintf("bad store type %v", t)) panic(fmt.Sprintf("bad store type %v", t))

View File

@ -91,6 +91,11 @@ func Info(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope,
continue continue
} }
apdecls = append(apdecls, n) apdecls = append(apdecls, n)
if n.Type().Kind() == types.TSSA {
// Can happen for TypeInt128 types. This only happens for
// spill locations, so not a huge deal.
continue
}
fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type())) fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type()))
} }
} }

View File

@ -625,6 +625,12 @@ func PtrDataSize(t *Type) int64 {
} }
return lastPtrField.Offset + PtrDataSize(lastPtrField.Type) return lastPtrField.Offset + PtrDataSize(lastPtrField.Type)
case TSSA:
if t != TypeInt128 {
base.Fatalf("PtrDataSize: unexpected ssa type %v", t)
}
return 0
default: default:
base.Fatalf("PtrDataSize: unexpected type, %v", t) base.Fatalf("PtrDataSize: unexpected type, %v", t)
return 0 return 0

View File

@ -1661,6 +1661,11 @@ var (
TypeResultMem = newResults([]*Type{TypeMem}) TypeResultMem = newResults([]*Type{TypeMem})
) )
func init() {
TypeInt128.Width = 16
TypeInt128.Align = 8
}
// NewNamed returns a new named type for the given type name. obj should be an // NewNamed returns a new named type for the given type name. obj should be an
// ir.Name. The new type is incomplete (marked as TFORW kind), and the underlying // ir.Name. The new type is incomplete (marked as TFORW kind), and the underlying
// type should be set later via SetUnderlying(). References to the type are // type should be set later via SetUnderlying(). References to the type are

View File

@ -1140,9 +1140,9 @@ func (t *tester) cgoTest(dt *distTest) error {
cmd := t.addCmd(dt, "misc/cgo/test", t.goTest()) cmd := t.addCmd(dt, "misc/cgo/test", t.goTest())
setEnv(cmd, "GOFLAGS", "-ldflags=-linkmode=auto") setEnv(cmd, "GOFLAGS", "-ldflags=-linkmode=auto")
// Skip internal linking cases on linux/arm64 to support GCC-9.4 and above. // Skip internal linking cases on arm64 to support GCC-9.4 and above.
// See issue #39466. // See issue #39466.
skipInternalLink := goarch == "arm64" && goos == "linux" skipInternalLink := goarch == "arm64" && goos != "darwin"
if t.internalLink() && !skipInternalLink { if t.internalLink() && !skipInternalLink {
cmd := t.addCmd(dt, "misc/cgo/test", t.goTest(), "-tags=internal") cmd := t.addCmd(dt, "misc/cgo/test", t.goTest(), "-tags=internal")

View File

@ -440,7 +440,7 @@ func (r *gitRepo) fetchRefsLocked() error {
// statLocal returns a RevInfo describing rev in the local git repository. // statLocal returns a RevInfo describing rev in the local git repository.
// It uses version as info.Version. // It uses version as info.Version.
func (r *gitRepo) statLocal(version, rev string) (*RevInfo, error) { func (r *gitRepo) statLocal(version, rev string) (*RevInfo, error) {
out, err := Run(r.dir, "git", "-c", "log.showsignature=false", "log", "-n1", "--format=format:%H %ct %D", rev, "--") out, err := Run(r.dir, "git", "-c", "log.showsignature=false", "log", "--no-decorate", "-n1", "--format=format:%H %ct %D", rev, "--")
if err != nil { if err != nil {
return nil, &UnknownRevisionError{Rev: rev} return nil, &UnknownRevisionError{Rev: rev}
} }

View File

@ -0,0 +1,28 @@
env GO111MODULE=on
[!net] skip
[!exec:git] skip
env GOPROXY=direct
env HOME=$WORK/home/gopher
go env GOPROXY
stdout 'direct'
exec git config --get log.decorate
stdout 'full'
# Test that Git log with user's global config '~/gitconfig' has log.decorate=full
# go mod download has an error 'v1.x.y is not a tag'
# with go1.16.14:
# `go1.16.14 list -m vcs-test.golang.org/git/gitrepo1.git@v1.2.3`
# will output with error:
# go list -m: vcs-test.golang.org/git/gitrepo1.git@v1.2.3: invalid version: unknown revision v1.2.3
# See golang/go#51312.
go list -m vcs-test.golang.org/git/gitrepo1.git@v1.2.3
stdout 'vcs-test.golang.org/git/gitrepo1.git v1.2.3'
-- $WORK/home/gopher/.gitconfig --
[log]
decorate = full

View File

@ -248,42 +248,40 @@ func (z *Reader) Read(p []byte) (n int, err error) {
return 0, z.err return 0, z.err
} }
n, z.err = z.decompressor.Read(p) for n == 0 {
z.digest = crc32.Update(z.digest, crc32.IEEETable, p[:n]) n, z.err = z.decompressor.Read(p)
z.size += uint32(n) z.digest = crc32.Update(z.digest, crc32.IEEETable, p[:n])
if z.err != io.EOF { z.size += uint32(n)
// In the normal case we return here. if z.err != io.EOF {
return n, z.err // In the normal case we return here.
return n, z.err
}
// Finished file; check checksum and size.
if _, err := io.ReadFull(z.r, z.buf[:8]); err != nil {
z.err = noEOF(err)
return n, z.err
}
digest := le.Uint32(z.buf[:4])
size := le.Uint32(z.buf[4:8])
if digest != z.digest || size != z.size {
z.err = ErrChecksum
return n, z.err
}
z.digest, z.size = 0, 0
// File is ok; check if there is another.
if !z.multistream {
return n, io.EOF
}
z.err = nil // Remove io.EOF
if _, z.err = z.readHeader(); z.err != nil {
return n, z.err
}
} }
// Finished file; check checksum and size. return n, nil
if _, err := io.ReadFull(z.r, z.buf[:8]); err != nil {
z.err = noEOF(err)
return n, z.err
}
digest := le.Uint32(z.buf[:4])
size := le.Uint32(z.buf[4:8])
if digest != z.digest || size != z.size {
z.err = ErrChecksum
return n, z.err
}
z.digest, z.size = 0, 0
// File is ok; check if there is another.
if !z.multistream {
return n, io.EOF
}
z.err = nil // Remove io.EOF
if _, z.err = z.readHeader(); z.err != nil {
return n, z.err
}
// Read from next file, if necessary.
if n > 0 {
return n, nil
}
return z.Read(p)
} }
// Close closes the Reader. It does not close the underlying io.Reader. // Close closes the Reader. It does not close the underlying io.Reader.

View File

@ -515,3 +515,19 @@ func TestTruncatedStreams(t *testing.T) {
} }
} }
} }
func TestCVE202230631(t *testing.T) {
var empty = []byte{0x1f, 0x8b, 0x08, 0x00, 0xa7, 0x8f, 0x43, 0x62, 0x00,
0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
r := bytes.NewReader(bytes.Repeat(empty, 4e6))
z, err := NewReader(r)
if err != nil {
t.Fatalf("NewReader: got %v, want nil", err)
}
// Prior to CVE-2022-30631 fix, this would cause an unrecoverable panic due
// to stack exhaustion.
_, err = z.Read(make([]byte, 10))
if err != io.EOF {
t.Errorf("Reader.Read: got %v, want %v", err, io.EOF)
}
}

View File

@ -871,8 +871,13 @@ func (dec *Decoder) decOpFor(wireId typeId, rt reflect.Type, name string, inProg
return &op return &op
} }
var maxIgnoreNestingDepth = 10000
// decIgnoreOpFor returns the decoding op for a field that has no destination. // decIgnoreOpFor returns the decoding op for a field that has no destination.
func (dec *Decoder) decIgnoreOpFor(wireId typeId, inProgress map[typeId]*decOp) *decOp { func (dec *Decoder) decIgnoreOpFor(wireId typeId, inProgress map[typeId]*decOp, depth int) *decOp {
if depth > maxIgnoreNestingDepth {
error_(errors.New("invalid nesting depth"))
}
// If this type is already in progress, it's a recursive type (e.g. map[string]*T). // If this type is already in progress, it's a recursive type (e.g. map[string]*T).
// Return the pointer to the op we're already building. // Return the pointer to the op we're already building.
if opPtr := inProgress[wireId]; opPtr != nil { if opPtr := inProgress[wireId]; opPtr != nil {
@ -896,7 +901,7 @@ func (dec *Decoder) decIgnoreOpFor(wireId typeId, inProgress map[typeId]*decOp)
errorf("bad data: undefined type %s", wireId.string()) errorf("bad data: undefined type %s", wireId.string())
case wire.ArrayT != nil: case wire.ArrayT != nil:
elemId := wire.ArrayT.Elem elemId := wire.ArrayT.Elem
elemOp := dec.decIgnoreOpFor(elemId, inProgress) elemOp := dec.decIgnoreOpFor(elemId, inProgress, depth+1)
op = func(i *decInstr, state *decoderState, value reflect.Value) { op = func(i *decInstr, state *decoderState, value reflect.Value) {
state.dec.ignoreArray(state, *elemOp, wire.ArrayT.Len) state.dec.ignoreArray(state, *elemOp, wire.ArrayT.Len)
} }
@ -904,15 +909,15 @@ func (dec *Decoder) decIgnoreOpFor(wireId typeId, inProgress map[typeId]*decOp)
case wire.MapT != nil: case wire.MapT != nil:
keyId := dec.wireType[wireId].MapT.Key keyId := dec.wireType[wireId].MapT.Key
elemId := dec.wireType[wireId].MapT.Elem elemId := dec.wireType[wireId].MapT.Elem
keyOp := dec.decIgnoreOpFor(keyId, inProgress) keyOp := dec.decIgnoreOpFor(keyId, inProgress, depth+1)
elemOp := dec.decIgnoreOpFor(elemId, inProgress) elemOp := dec.decIgnoreOpFor(elemId, inProgress, depth+1)
op = func(i *decInstr, state *decoderState, value reflect.Value) { op = func(i *decInstr, state *decoderState, value reflect.Value) {
state.dec.ignoreMap(state, *keyOp, *elemOp) state.dec.ignoreMap(state, *keyOp, *elemOp)
} }
case wire.SliceT != nil: case wire.SliceT != nil:
elemId := wire.SliceT.Elem elemId := wire.SliceT.Elem
elemOp := dec.decIgnoreOpFor(elemId, inProgress) elemOp := dec.decIgnoreOpFor(elemId, inProgress, depth+1)
op = func(i *decInstr, state *decoderState, value reflect.Value) { op = func(i *decInstr, state *decoderState, value reflect.Value) {
state.dec.ignoreSlice(state, *elemOp) state.dec.ignoreSlice(state, *elemOp)
} }
@ -1073,7 +1078,7 @@ func (dec *Decoder) compileSingle(remoteId typeId, ut *userTypeInfo) (engine *de
func (dec *Decoder) compileIgnoreSingle(remoteId typeId) *decEngine { func (dec *Decoder) compileIgnoreSingle(remoteId typeId) *decEngine {
engine := new(decEngine) engine := new(decEngine)
engine.instr = make([]decInstr, 1) // one item engine.instr = make([]decInstr, 1) // one item
op := dec.decIgnoreOpFor(remoteId, make(map[typeId]*decOp)) op := dec.decIgnoreOpFor(remoteId, make(map[typeId]*decOp), 0)
ovfl := overflow(dec.typeString(remoteId)) ovfl := overflow(dec.typeString(remoteId))
engine.instr[0] = decInstr{*op, 0, nil, ovfl} engine.instr[0] = decInstr{*op, 0, nil, ovfl}
engine.numInstr = 1 engine.numInstr = 1
@ -1118,7 +1123,7 @@ func (dec *Decoder) compileDec(remoteId typeId, ut *userTypeInfo) (engine *decEn
localField, present := srt.FieldByName(wireField.Name) localField, present := srt.FieldByName(wireField.Name)
// TODO(r): anonymous names // TODO(r): anonymous names
if !present || !isExported(wireField.Name) { if !present || !isExported(wireField.Name) {
op := dec.decIgnoreOpFor(wireField.Id, make(map[typeId]*decOp)) op := dec.decIgnoreOpFor(wireField.Id, make(map[typeId]*decOp), 0)
engine.instr[fieldnum] = decInstr{*op, fieldnum, nil, ovfl} engine.instr[fieldnum] = decInstr{*op, fieldnum, nil, ovfl}
continue continue
} }

View File

@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -796,3 +797,26 @@ func TestNetIP(t *testing.T) {
t.Errorf("decoded to %v, want 1.2.3.4", ip.String()) t.Errorf("decoded to %v, want 1.2.3.4", ip.String())
} }
} }
func TestIngoreDepthLimit(t *testing.T) {
// We don't test the actual depth limit because it requires building an
// extremely large message, which takes quite a while.
oldNestingDepth := maxIgnoreNestingDepth
maxIgnoreNestingDepth = 100
defer func() { maxIgnoreNestingDepth = oldNestingDepth }()
b := new(bytes.Buffer)
enc := NewEncoder(b)
typ := reflect.TypeOf(int(0))
nested := reflect.ArrayOf(1, typ)
for i := 0; i < 100; i++ {
nested = reflect.ArrayOf(1, nested)
}
badStruct := reflect.New(reflect.StructOf([]reflect.StructField{{Name: "F", Type: nested}}))
enc.Encode(badStruct.Interface())
dec := NewDecoder(b)
var output struct{ Hello int }
expectedErr := "invalid nesting depth"
if err := dec.Decode(&output); err == nil || err.Error() != expectedErr {
t.Errorf("Decode didn't fail with depth limit of 100: want %q, got %q", expectedErr, err)
}
}

View File

@ -148,7 +148,7 @@ func (d *Decoder) DecodeElement(v interface{}, start *StartElement) error {
if val.Kind() != reflect.Ptr { if val.Kind() != reflect.Ptr {
return errors.New("non-pointer passed to Unmarshal") return errors.New("non-pointer passed to Unmarshal")
} }
return d.unmarshal(val.Elem(), start) return d.unmarshal(val.Elem(), start, 0)
} }
// An UnmarshalError represents an error in the unmarshaling process. // An UnmarshalError represents an error in the unmarshaling process.
@ -304,8 +304,15 @@ var (
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
) )
const maxUnmarshalDepth = 10000
var errExeceededMaxUnmarshalDepth = errors.New("exceeded max depth")
// Unmarshal a single XML element into val. // Unmarshal a single XML element into val.
func (d *Decoder) unmarshal(val reflect.Value, start *StartElement) error { func (d *Decoder) unmarshal(val reflect.Value, start *StartElement, depth int) error {
if depth >= maxUnmarshalDepth {
return errExeceededMaxUnmarshalDepth
}
// Find start element if we need it. // Find start element if we need it.
if start == nil { if start == nil {
for { for {
@ -398,7 +405,7 @@ func (d *Decoder) unmarshal(val reflect.Value, start *StartElement) error {
v.Set(reflect.Append(val, reflect.Zero(v.Type().Elem()))) v.Set(reflect.Append(val, reflect.Zero(v.Type().Elem())))
// Recur to read element into slice. // Recur to read element into slice.
if err := d.unmarshal(v.Index(n), start); err != nil { if err := d.unmarshal(v.Index(n), start, depth+1); err != nil {
v.SetLen(n) v.SetLen(n)
return err return err
} }
@ -521,13 +528,15 @@ Loop:
case StartElement: case StartElement:
consumed := false consumed := false
if sv.IsValid() { if sv.IsValid() {
consumed, err = d.unmarshalPath(tinfo, sv, nil, &t) // unmarshalPath can call unmarshal, so we need to pass the depth through so that
// we can continue to enforce the maximum recusion limit.
consumed, err = d.unmarshalPath(tinfo, sv, nil, &t, depth)
if err != nil { if err != nil {
return err return err
} }
if !consumed && saveAny.IsValid() { if !consumed && saveAny.IsValid() {
consumed = true consumed = true
if err := d.unmarshal(saveAny, &t); err != nil { if err := d.unmarshal(saveAny, &t, depth+1); err != nil {
return err return err
} }
} }
@ -672,7 +681,7 @@ func copyValue(dst reflect.Value, src []byte) (err error) {
// The consumed result tells whether XML elements have been consumed // The consumed result tells whether XML elements have been consumed
// from the Decoder until start's matching end element, or if it's // from the Decoder until start's matching end element, or if it's
// still untouched because start is uninteresting for sv's fields. // still untouched because start is uninteresting for sv's fields.
func (d *Decoder) unmarshalPath(tinfo *typeInfo, sv reflect.Value, parents []string, start *StartElement) (consumed bool, err error) { func (d *Decoder) unmarshalPath(tinfo *typeInfo, sv reflect.Value, parents []string, start *StartElement, depth int) (consumed bool, err error) {
recurse := false recurse := false
Loop: Loop:
for i := range tinfo.fields { for i := range tinfo.fields {
@ -687,7 +696,7 @@ Loop:
} }
if len(finfo.parents) == len(parents) && finfo.name == start.Name.Local { if len(finfo.parents) == len(parents) && finfo.name == start.Name.Local {
// It's a perfect match, unmarshal the field. // It's a perfect match, unmarshal the field.
return true, d.unmarshal(finfo.value(sv, initNilPointers), start) return true, d.unmarshal(finfo.value(sv, initNilPointers), start, depth+1)
} }
if len(finfo.parents) > len(parents) && finfo.parents[len(parents)] == start.Name.Local { if len(finfo.parents) > len(parents) && finfo.parents[len(parents)] == start.Name.Local {
// It's a prefix for the field. Break and recurse // It's a prefix for the field. Break and recurse
@ -716,7 +725,9 @@ Loop:
} }
switch t := tok.(type) { switch t := tok.(type) {
case StartElement: case StartElement:
consumed2, err := d.unmarshalPath(tinfo, sv, parents, &t) // the recursion depth of unmarshalPath is limited to the path length specified
// by the struct field tag, so we don't increment the depth here.
consumed2, err := d.unmarshalPath(tinfo, sv, parents, &t, depth)
if err != nil { if err != nil {
return true, err return true, err
} }
@ -732,12 +743,12 @@ Loop:
} }
// Skip reads tokens until it has consumed the end element // Skip reads tokens until it has consumed the end element
// matching the most recent start element already consumed. // matching the most recent start element already consumed,
// It recurs if it encounters a start element, so it can be used to // skipping nested structures.
// skip nested structures.
// It returns nil if it finds an end element matching the start // It returns nil if it finds an end element matching the start
// element; otherwise it returns an error describing the problem. // element; otherwise it returns an error describing the problem.
func (d *Decoder) Skip() error { func (d *Decoder) Skip() error {
var depth int64
for { for {
tok, err := d.Token() tok, err := d.Token()
if err != nil { if err != nil {
@ -745,11 +756,12 @@ func (d *Decoder) Skip() error {
} }
switch tok.(type) { switch tok.(type) {
case StartElement: case StartElement:
if err := d.Skip(); err != nil { depth++
return err
}
case EndElement: case EndElement:
return nil if depth == 0 {
return nil
}
depth--
} }
} }
} }

View File

@ -5,8 +5,11 @@
package xml package xml
import ( import (
"bytes"
"errors"
"io" "io"
"reflect" "reflect"
"runtime"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -1079,3 +1082,32 @@ func TestUnmarshalWhitespaceAttrs(t *testing.T) {
t.Fatalf("whitespace attrs: Unmarshal:\nhave: %#+v\nwant: %#+v", v, want) t.Fatalf("whitespace attrs: Unmarshal:\nhave: %#+v\nwant: %#+v", v, want)
} }
} }
func TestCVE202230633(t *testing.T) {
if runtime.GOARCH == "wasm" {
t.Skip("causes memory exhaustion on js/wasm")
}
defer func() {
p := recover()
if p != nil {
t.Fatal("Unmarshal panicked")
}
}()
var example struct {
Things []string
}
Unmarshal(bytes.Repeat([]byte("<a>"), 17_000_000), &example)
}
func TestCVE202228131(t *testing.T) {
type nested struct {
Parent *nested `xml:",any"`
}
var n nested
err := Unmarshal(bytes.Repeat([]byte("<a>"), maxUnmarshalDepth+1), &n)
if err == nil {
t.Fatal("Unmarshal did not fail")
} else if !errors.Is(err, errExeceededMaxUnmarshalDepth) {
t.Fatalf("Unmarshal unexpected error: got %q, want %q", err, errExeceededMaxUnmarshalDepth)
}
}

View File

@ -97,8 +97,11 @@ func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode)
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
// resume same panic if it's not a bailout // resume same panic if it's not a bailout
if _, ok := e.(bailout); !ok { bail, ok := e.(bailout)
if !ok {
panic(e) panic(e)
} else if bail.msg != "" {
p.errors.Add(p.file.Position(bail.pos), bail.msg)
} }
} }
@ -203,8 +206,11 @@ func ParseExprFrom(fset *token.FileSet, filename string, src interface{}, mode M
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
// resume same panic if it's not a bailout // resume same panic if it's not a bailout
if _, ok := e.(bailout); !ok { bail, ok := e.(bailout)
if !ok {
panic(e) panic(e)
} else if bail.msg != "" {
p.errors.Add(p.file.Position(bail.pos), bail.msg)
} }
} }
p.errors.Sort() p.errors.Sort()

View File

@ -60,6 +60,10 @@ type parser struct {
inRhs bool // if set, the parser is parsing a rhs expression inRhs bool // if set, the parser is parsing a rhs expression
imports []*ast.ImportSpec // list of imports imports []*ast.ImportSpec // list of imports
// nestLev is used to track and limit the recursion depth
// during parsing.
nestLev int
} }
func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) { func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) {
@ -110,6 +114,24 @@ func un(p *parser) {
p.printTrace(")") p.printTrace(")")
} }
// maxNestLev is the deepest we're willing to recurse during parsing
const maxNestLev int = 1e5
func incNestLev(p *parser) *parser {
p.nestLev++
if p.nestLev > maxNestLev {
p.error(p.pos, "exceeded max nesting depth")
panic(bailout{})
}
return p
}
// decNestLev is used to track nesting depth during parsing to prevent stack exhaustion.
// It is used along with incNestLev in a similar fashion to how un and trace are used.
func decNestLev(p *parser) {
p.nestLev--
}
// Advance to the next token. // Advance to the next token.
func (p *parser) next0() { func (p *parser) next0() {
// Because of one-token look-ahead, print the previous token // Because of one-token look-ahead, print the previous token
@ -222,8 +244,12 @@ func (p *parser) next() {
} }
} }
// A bailout panic is raised to indicate early termination. // A bailout panic is raised to indicate early termination. pos and msg are
type bailout struct{} // only populated when bailing out of object resolution.
type bailout struct {
pos token.Pos
msg string
}
func (p *parser) error(pos token.Pos, msg string) { func (p *parser) error(pos token.Pos, msg string) {
if p.trace { if p.trace {
@ -1119,6 +1145,8 @@ func (p *parser) parseTypeInstance(typ ast.Expr) ast.Expr {
} }
func (p *parser) tryIdentOrType() ast.Expr { func (p *parser) tryIdentOrType() ast.Expr {
defer decNestLev(incNestLev(p))
switch p.tok { switch p.tok {
case token.IDENT: case token.IDENT:
typ := p.parseTypeName(nil) typ := p.parseTypeName(nil)
@ -1531,7 +1559,13 @@ func (p *parser) parsePrimaryExpr() (x ast.Expr) {
} }
x = p.parseOperand() x = p.parseOperand()
for { // We track the nesting here rather than at the entry for the function,
// since it can iteratively produce a nested output, and we want to
// limit how deep a structure we generate.
var n int
defer func() { p.nestLev -= n }()
for n = 1; ; n++ {
incNestLev(p)
switch p.tok { switch p.tok {
case token.PERIOD: case token.PERIOD:
p.next() p.next()
@ -1591,6 +1625,8 @@ func (p *parser) parsePrimaryExpr() (x ast.Expr) {
} }
func (p *parser) parseUnaryExpr() ast.Expr { func (p *parser) parseUnaryExpr() ast.Expr {
defer decNestLev(incNestLev(p))
if p.trace { if p.trace {
defer un(trace(p, "UnaryExpr")) defer un(trace(p, "UnaryExpr"))
} }
@ -1673,7 +1709,13 @@ func (p *parser) parseBinaryExpr(prec1 int) ast.Expr {
} }
x := p.parseUnaryExpr() x := p.parseUnaryExpr()
for { // We track the nesting here rather than at the entry for the function,
// since it can iteratively produce a nested output, and we want to
// limit how deep a structure we generate.
var n int
defer func() { p.nestLev -= n }()
for n = 1; ; n++ {
incNestLev(p)
op, oprec := p.tokPrec() op, oprec := p.tokPrec()
if oprec < prec1 { if oprec < prec1 {
return x return x
@ -1962,6 +2004,8 @@ func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) {
} }
func (p *parser) parseIfStmt() *ast.IfStmt { func (p *parser) parseIfStmt() *ast.IfStmt {
defer decNestLev(incNestLev(p))
if p.trace { if p.trace {
defer un(trace(p, "IfStmt")) defer un(trace(p, "IfStmt"))
} }
@ -2265,6 +2309,8 @@ func (p *parser) parseForStmt() ast.Stmt {
} }
func (p *parser) parseStmt() (s ast.Stmt) { func (p *parser) parseStmt() (s ast.Stmt) {
defer decNestLev(incNestLev(p))
if p.trace { if p.trace {
defer un(trace(p, "Statement")) defer un(trace(p, "Statement"))
} }

View File

@ -10,6 +10,7 @@ import (
"go/ast" "go/ast"
"go/token" "go/token"
"io/fs" "io/fs"
"runtime"
"strings" "strings"
"testing" "testing"
) )
@ -577,3 +578,171 @@ type x int // comment
t.Errorf("got %q, want %q", comment, "// comment") t.Errorf("got %q, want %q", comment, "// comment")
} }
} }
var parseDepthTests = []struct {
name string
format string
// multipler is used when a single statement may result in more than one
// change in the depth level, for instance "1+(..." produces a BinaryExpr
// followed by a UnaryExpr, which increments the depth twice. The test
// case comment explains which nodes are triggering the multiple depth
// changes.
parseMultiplier int
// scope is true if we should also test the statement for the resolver scope
// depth limit.
scope bool
// scopeMultiplier does the same as parseMultiplier, but for the scope
// depths.
scopeMultiplier int
}{
// The format expands the part inside « » many times.
// A second set of brackets nested inside the first stops the repetition,
// so that for example «(«1»)» expands to (((...((((1))))...))).
{name: "array", format: "package main; var x «[1]»int"},
{name: "slice", format: "package main; var x «[]»int"},
{name: "struct", format: "package main; var x «struct { X «int» }»", scope: true},
{name: "pointer", format: "package main; var x «*»int"},
{name: "func", format: "package main; var x «func()»int", scope: true},
{name: "chan", format: "package main; var x «chan »int"},
{name: "chan2", format: "package main; var x «<-chan »int"},
{name: "interface", format: "package main; var x «interface { M() «int» }»", scope: true, scopeMultiplier: 2}, // Scopes: InterfaceType, FuncType
{name: "map", format: "package main; var x «map[int]»int"},
{name: "slicelit", format: "package main; var x = «[]any{«»}»", parseMultiplier: 2}, // Parser nodes: UnaryExpr, CompositeLit
{name: "arraylit", format: "package main; var x = «[1]any{«nil»}»", parseMultiplier: 2}, // Parser nodes: UnaryExpr, CompositeLit
{name: "structlit", format: "package main; var x = «struct{x any}{«nil»}»", parseMultiplier: 2}, // Parser nodes: UnaryExpr, CompositeLit
{name: "maplit", format: "package main; var x = «map[int]any{1:«nil»}»", parseMultiplier: 2}, // Parser nodes: CompositeLit, KeyValueExpr
{name: "dot", format: "package main; var x = «x.»x"},
{name: "index", format: "package main; var x = x«[1]»"},
{name: "slice", format: "package main; var x = x«[1:2]»"},
{name: "slice3", format: "package main; var x = x«[1:2:3]»"},
{name: "dottype", format: "package main; var x = x«.(any)»"},
{name: "callseq", format: "package main; var x = x«()»"},
{name: "methseq", format: "package main; var x = x«.m()»", parseMultiplier: 2}, // Parser nodes: SelectorExpr, CallExpr
{name: "binary", format: "package main; var x = «1+»1"},
{name: "binaryparen", format: "package main; var x = «1+(«1»)»", parseMultiplier: 2}, // Parser nodes: BinaryExpr, ParenExpr
{name: "unary", format: "package main; var x = «^»1"},
{name: "addr", format: "package main; var x = «& »x"},
{name: "star", format: "package main; var x = «*»x"},
{name: "recv", format: "package main; var x = «<-»x"},
{name: "call", format: "package main; var x = «f(«1»)»", parseMultiplier: 2}, // Parser nodes: Ident, CallExpr
{name: "conv", format: "package main; var x = «(*T)(«1»)»", parseMultiplier: 2}, // Parser nodes: ParenExpr, CallExpr
{name: "label", format: "package main; func main() { «Label:» }"},
{name: "if", format: "package main; func main() { «if true { «» }»}", parseMultiplier: 2, scope: true, scopeMultiplier: 2}, // Parser nodes: IfStmt, BlockStmt. Scopes: IfStmt, BlockStmt
{name: "ifelse", format: "package main; func main() { «if true {} else » {} }", scope: true},
{name: "switch", format: "package main; func main() { «switch { default: «» }»}", scope: true, scopeMultiplier: 2}, // Scopes: TypeSwitchStmt, CaseClause
{name: "typeswitch", format: "package main; func main() { «switch x.(type) { default: «» }» }", scope: true, scopeMultiplier: 2}, // Scopes: TypeSwitchStmt, CaseClause
{name: "for0", format: "package main; func main() { «for { «» }» }", scope: true, scopeMultiplier: 2}, // Scopes: ForStmt, BlockStmt
{name: "for1", format: "package main; func main() { «for x { «» }» }", scope: true, scopeMultiplier: 2}, // Scopes: ForStmt, BlockStmt
{name: "for3", format: "package main; func main() { «for f(); g(); h() { «» }» }", scope: true, scopeMultiplier: 2}, // Scopes: ForStmt, BlockStmt
{name: "forrange0", format: "package main; func main() { «for range x { «» }» }", scope: true, scopeMultiplier: 2}, // Scopes: RangeStmt, BlockStmt
{name: "forrange1", format: "package main; func main() { «for x = range z { «» }» }", scope: true, scopeMultiplier: 2}, // Scopes: RangeStmt, BlockStmt
{name: "forrange2", format: "package main; func main() { «for x, y = range z { «» }» }", scope: true, scopeMultiplier: 2}, // Scopes: RangeStmt, BlockStmt
{name: "go", format: "package main; func main() { «go func() { «» }()» }", parseMultiplier: 2, scope: true}, // Parser nodes: GoStmt, FuncLit
{name: "defer", format: "package main; func main() { «defer func() { «» }()» }", parseMultiplier: 2, scope: true}, // Parser nodes: DeferStmt, FuncLit
{name: "select", format: "package main; func main() { «select { default: «» }» }", scope: true},
}
// split splits pre«mid»post into pre, mid, post.
// If the string does not have that form, split returns x, "", "".
func split(x string) (pre, mid, post string) {
start, end := strings.Index(x, "«"), strings.LastIndex(x, "»")
if start < 0 || end < 0 {
return x, "", ""
}
return x[:start], x[start+len("«") : end], x[end+len("»"):]
}
func TestParseDepthLimit(t *testing.T) {
if runtime.GOARCH == "wasm" {
t.Skip("causes call stack exhaustion on js/wasm")
}
for _, tt := range parseDepthTests {
for _, size := range []string{"small", "big"} {
t.Run(tt.name+"/"+size, func(t *testing.T) {
n := maxNestLev + 1
if tt.parseMultiplier > 0 {
n /= tt.parseMultiplier
}
if size == "small" {
// Decrease the number of statements by 10, in order to check
// that we do not fail when under the limit. 10 is used to
// provide some wiggle room for cases where the surrounding
// scaffolding syntax adds some noise to the depth that changes
// on a per testcase basis.
n -= 10
}
pre, mid, post := split(tt.format)
if strings.Contains(mid, "«") {
left, base, right := split(mid)
mid = strings.Repeat(left, n) + base + strings.Repeat(right, n)
} else {
mid = strings.Repeat(mid, n)
}
input := pre + mid + post
fset := token.NewFileSet()
_, err := ParseFile(fset, "", input, ParseComments|SkipObjectResolution)
if size == "small" {
if err != nil {
t.Errorf("ParseFile(...): %v (want success)", err)
}
} else {
expected := "exceeded max nesting depth"
if err == nil || !strings.HasSuffix(err.Error(), expected) {
t.Errorf("ParseFile(...) = _, %v, want %q", err, expected)
}
}
})
}
}
}
func TestScopeDepthLimit(t *testing.T) {
if runtime.GOARCH == "wasm" {
t.Skip("causes call stack exhaustion on js/wasm")
}
for _, tt := range parseDepthTests {
if !tt.scope {
continue
}
for _, size := range []string{"small", "big"} {
t.Run(tt.name+"/"+size, func(t *testing.T) {
n := maxScopeDepth + 1
if tt.scopeMultiplier > 0 {
n /= tt.scopeMultiplier
}
if size == "small" {
// Decrease the number of statements by 10, in order to check
// that we do not fail when under the limit. 10 is used to
// provide some wiggle room for cases where the surrounding
// scaffolding syntax adds some noise to the depth that changes
// on a per testcase basis.
n -= 10
}
pre, mid, post := split(tt.format)
if strings.Contains(mid, "«") {
left, base, right := split(mid)
mid = strings.Repeat(left, n) + base + strings.Repeat(right, n)
} else {
mid = strings.Repeat(mid, n)
}
input := pre + mid + post
fset := token.NewFileSet()
_, err := ParseFile(fset, "", input, DeclarationErrors)
if size == "small" {
if err != nil {
t.Errorf("ParseFile(...): %v (want success)", err)
}
} else {
expected := "exceeded max scope depth during object resolution"
if err == nil || !strings.HasSuffix(err.Error(), expected) {
t.Errorf("ParseFile(...) = _, %v, want %q", err, expected)
}
}
})
}
}
}

View File

@ -25,6 +25,7 @@ func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, str
declErr: declErr, declErr: declErr,
topScope: pkgScope, topScope: pkgScope,
pkgScope: pkgScope, pkgScope: pkgScope,
depth: 1,
} }
for _, decl := range file.Decls { for _, decl := range file.Decls {
@ -53,6 +54,8 @@ func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, str
file.Unresolved = r.unresolved[0:i] file.Unresolved = r.unresolved[0:i]
} }
const maxScopeDepth int = 1e3
type resolver struct { type resolver struct {
handle *token.File handle *token.File
declErr func(token.Pos, string) declErr func(token.Pos, string)
@ -61,6 +64,7 @@ type resolver struct {
pkgScope *ast.Scope // pkgScope.Outer == nil pkgScope *ast.Scope // pkgScope.Outer == nil
topScope *ast.Scope // top-most scope; may be pkgScope topScope *ast.Scope // top-most scope; may be pkgScope
unresolved []*ast.Ident // unresolved identifiers unresolved []*ast.Ident // unresolved identifiers
depth int // scope depth
// Label scopes // Label scopes
// (maintained by open/close LabelScope) // (maintained by open/close LabelScope)
@ -83,6 +87,10 @@ func (r *resolver) sprintf(format string, args ...interface{}) string {
} }
func (r *resolver) openScope(pos token.Pos) { func (r *resolver) openScope(pos token.Pos) {
r.depth++
if r.depth > maxScopeDepth {
panic(bailout{pos: pos, msg: "exceeded max scope depth during object resolution"})
}
if debugResolve { if debugResolve {
r.dump("opening scope @%v", pos) r.dump("opening scope @%v", pos)
} }
@ -90,6 +98,7 @@ func (r *resolver) openScope(pos token.Pos) {
} }
func (r *resolver) closeScope() { func (r *resolver) closeScope() {
r.depth--
if debugResolve { if debugResolve {
r.dump("closing scope") r.dump("closing scope")
} }

View File

@ -31,6 +31,16 @@ type GlobFS interface {
// Otherwise, Glob uses ReadDir to traverse the directory tree // Otherwise, Glob uses ReadDir to traverse the directory tree
// and look for matches for the pattern. // and look for matches for the pattern.
func Glob(fsys FS, pattern string) (matches []string, err error) { func Glob(fsys FS, pattern string) (matches []string, err error) {
return globWithLimit(fsys, pattern, 0)
}
func globWithLimit(fsys FS, pattern string, depth int) (matches []string, err error) {
// This limit is added to prevent stack exhaustion issues. See
// CVE-2022-30630.
const pathSeparatorsLimit = 10000
if depth > pathSeparatorsLimit {
return nil, path.ErrBadPattern
}
if fsys, ok := fsys.(GlobFS); ok { if fsys, ok := fsys.(GlobFS); ok {
return fsys.Glob(pattern) return fsys.Glob(pattern)
} }
@ -59,9 +69,9 @@ func Glob(fsys FS, pattern string) (matches []string, err error) {
} }
var m []string var m []string
m, err = Glob(fsys, dir) m, err = globWithLimit(fsys, dir, depth+1)
if err != nil { if err != nil {
return return nil, err
} }
for _, d := range m { for _, d := range m {
matches, err = glob(fsys, d, file, matches) matches, err = glob(fsys, d, file, matches)

View File

@ -8,6 +8,7 @@ import (
. "io/fs" . "io/fs"
"os" "os"
"path" "path"
"strings"
"testing" "testing"
) )
@ -55,6 +56,15 @@ func TestGlobError(t *testing.T) {
} }
} }
func TestCVE202230630(t *testing.T) {
// Prior to CVE-2022-30630, a stack exhaustion would occur given a large
// number of separators. There is now a limit of 10,000.
_, err := Glob(os.DirFS("."), "/*"+strings.Repeat("/", 10001))
if err != path.ErrBadPattern {
t.Fatalf("Glob returned err=%v, want %v", err, path.ErrBadPattern)
}
}
// contains reports whether vector contains the string s. // contains reports whether vector contains the string s.
func contains(vector []string, s string) bool { func contains(vector []string, s string) bool {
for _, elem := range vector { for _, elem := range vector {

View File

@ -101,6 +101,12 @@ func (h Header) Clone() Header {
sv := make([]string, nv) // shared backing array for headers' values sv := make([]string, nv) // shared backing array for headers' values
h2 := make(Header, len(h)) h2 := make(Header, len(h))
for k, vv := range h { for k, vv := range h {
if vv == nil {
// Preserve nil values. ReverseProxy distinguishes
// between nil and zero-length header values.
h2[k] = nil
continue
}
n := copy(sv, vv) n := copy(sv, vv)
h2[k] = sv[:n:n] h2[k] = sv[:n:n]
sv = sv[n:] sv = sv[n:]

View File

@ -235,6 +235,11 @@ func TestCloneOrMakeHeader(t *testing.T) {
in: Header{"foo": {"bar"}}, in: Header{"foo": {"bar"}},
want: Header{"foo": {"bar"}}, want: Header{"foo": {"bar"}},
}, },
{
name: "nil value",
in: Header{"foo": nil},
want: Header{"foo": nil},
},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@ -6189,6 +6189,7 @@ func TestUnsupportedTransferEncodingsReturn501(t *testing.T) {
"fugazi", "fugazi",
"foo-bar", "foo-bar",
"unknown", "unknown",
"\rchunked",
} }
for _, badTE := range unsupportedTEs { for _, badTE := range unsupportedTEs {

View File

@ -639,7 +639,7 @@ func (t *transferReader) parseTransferEncoding() error {
if len(raw) != 1 { if len(raw) != 1 {
return &unsupportedTEError{fmt.Sprintf("too many transfer encodings: %q", raw)} return &unsupportedTEError{fmt.Sprintf("too many transfer encodings: %q", raw)}
} }
if !ascii.EqualFold(textproto.TrimString(raw[0]), "chunked") { if !ascii.EqualFold(raw[0], "chunked") {
return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", raw[0])} return &unsupportedTEError{fmt.Sprintf("unsupported transfer encoding: %q", raw[0])}
} }

View File

@ -241,6 +241,16 @@ func getEsc(chunk string) (r rune, nchunk string, err error) {
// The only possible returned error is ErrBadPattern, when pattern // The only possible returned error is ErrBadPattern, when pattern
// is malformed. // is malformed.
func Glob(pattern string) (matches []string, err error) { func Glob(pattern string) (matches []string, err error) {
return globWithLimit(pattern, 0)
}
func globWithLimit(pattern string, depth int) (matches []string, err error) {
// This limit is used prevent stack exhaustion issues. See CVE-2022-30632.
const pathSeparatorsLimit = 10000
if depth == pathSeparatorsLimit {
return nil, ErrBadPattern
}
// Check pattern is well-formed. // Check pattern is well-formed.
if _, err := Match(pattern, ""); err != nil { if _, err := Match(pattern, ""); err != nil {
return nil, err return nil, err
@ -270,7 +280,7 @@ func Glob(pattern string) (matches []string, err error) {
} }
var m []string var m []string
m, err = Glob(dir) m, err = globWithLimit(dir, depth+1)
if err != nil { if err != nil {
return return
} }

View File

@ -155,6 +155,16 @@ func TestGlob(t *testing.T) {
} }
} }
func TestCVE202230632(t *testing.T) {
// Prior to CVE-2022-30632, this would cause a stack exhaustion given a
// large number of separators (more than 4,000,000). There is now a limit
// of 10,000.
_, err := Glob("/*" + strings.Repeat("/", 10001))
if err != ErrBadPattern {
t.Fatalf("Glob returned err=%v, want ErrBadPattern", err)
}
}
func TestGlobError(t *testing.T) { func TestGlobError(t *testing.T) {
bad := []string{`[]`, `nonexist/[]`} bad := []string{`[]`, `nonexist/[]`}
for _, pattern := range bad { for _, pattern := range bad {

View File

@ -321,9 +321,9 @@ func ReadMetricsSlow(memStats *MemStats, samplesp unsafe.Pointer, len, cap int)
// Initialize the metrics beforehand because this could // Initialize the metrics beforehand because this could
// allocate and skew the stats. // allocate and skew the stats.
semacquire(&metricsSema) metricsLock()
initMetrics() initMetrics()
semrelease(&metricsSema) metricsUnlock()
systemstack(func() { systemstack(func() {
// Read memstats first. It's going to flush // Read memstats first. It's going to flush

View File

@ -175,11 +175,11 @@ func (c *mcache) refill(spc spanClass) {
// Assume all objects from this span will be allocated in the // Assume all objects from this span will be allocated in the
// mcache. If it gets uncached, we'll adjust this. // mcache. If it gets uncached, we'll adjust this.
stats := memstats.heapStats.acquire() stats := memstats.heapStats.acquire()
atomic.Xadduintptr(&stats.smallAllocCount[spc.sizeclass()], uintptr(s.nelems)-uintptr(s.allocCount)) atomic.Xadd64(&stats.smallAllocCount[spc.sizeclass()], int64(s.nelems)-int64(s.allocCount))
// Flush tinyAllocs. // Flush tinyAllocs.
if spc == tinySpanClass { if spc == tinySpanClass {
atomic.Xadduintptr(&stats.tinyAllocCount, c.tinyAllocs) atomic.Xadd64(&stats.tinyAllocCount, int64(c.tinyAllocs))
c.tinyAllocs = 0 c.tinyAllocs = 0
} }
memstats.heapStats.release() memstats.heapStats.release()
@ -229,8 +229,8 @@ func (c *mcache) allocLarge(size uintptr, needzero bool, noscan bool) (*mspan, b
throw("out of memory") throw("out of memory")
} }
stats := memstats.heapStats.acquire() stats := memstats.heapStats.acquire()
atomic.Xadduintptr(&stats.largeAlloc, npages*pageSize) atomic.Xadd64(&stats.largeAlloc, int64(npages*pageSize))
atomic.Xadduintptr(&stats.largeAllocCount, 1) atomic.Xadd64(&stats.largeAllocCount, 1)
memstats.heapStats.release() memstats.heapStats.release()
// Update gcController.heapLive and revise pacing if needed. // Update gcController.heapLive and revise pacing if needed.
@ -261,9 +261,9 @@ func (c *mcache) releaseAll() {
s := c.alloc[i] s := c.alloc[i]
if s != &emptymspan { if s != &emptymspan {
// Adjust nsmallalloc in case the span wasn't fully allocated. // Adjust nsmallalloc in case the span wasn't fully allocated.
n := uintptr(s.nelems) - uintptr(s.allocCount) n := int64(s.nelems) - int64(s.allocCount)
stats := memstats.heapStats.acquire() stats := memstats.heapStats.acquire()
atomic.Xadduintptr(&stats.smallAllocCount[spanClass(i).sizeclass()], -n) atomic.Xadd64(&stats.smallAllocCount[spanClass(i).sizeclass()], -n)
memstats.heapStats.release() memstats.heapStats.release()
if s.sweepgen != sg+1 { if s.sweepgen != sg+1 {
// refill conservatively counted unallocated slots in gcController.heapLive. // refill conservatively counted unallocated slots in gcController.heapLive.
@ -273,7 +273,7 @@ func (c *mcache) releaseAll() {
// gcController.heapLive was totally recomputed since // gcController.heapLive was totally recomputed since
// caching this span, so we don't do this for // caching this span, so we don't do this for
// stale spans. // stale spans.
atomic.Xadd64(&gcController.heapLive, -int64(n)*int64(s.elemsize)) atomic.Xadd64(&gcController.heapLive, -n*int64(s.elemsize))
} }
// Release the span to the mcentral. // Release the span to the mcentral.
mheap_.central[i].mcentral.uncacheSpan(s) mheap_.central[i].mcentral.uncacheSpan(s)
@ -286,7 +286,7 @@ func (c *mcache) releaseAll() {
// Flush tinyAllocs. // Flush tinyAllocs.
stats := memstats.heapStats.acquire() stats := memstats.heapStats.acquire()
atomic.Xadduintptr(&stats.tinyAllocCount, c.tinyAllocs) atomic.Xadd64(&stats.tinyAllocCount, int64(c.tinyAllocs))
c.tinyAllocs = 0 c.tinyAllocs = 0
memstats.heapStats.release() memstats.heapStats.release()

View File

@ -12,9 +12,12 @@ import (
) )
var ( var (
// metrics is a map of runtime/metrics keys to // metrics is a map of runtime/metrics keys to data used by the runtime
// data used by the runtime to sample each metric's // to sample each metric's value. metricsInit indicates it has been
// value. // initialized.
//
// These fields are protected by metricsSema which should be
// locked/unlocked with metricsLock() / metricsUnlock().
metricsSema uint32 = 1 metricsSema uint32 = 1
metricsInit bool metricsInit bool
metrics map[string]metricData metrics map[string]metricData
@ -34,6 +37,23 @@ type metricData struct {
compute func(in *statAggregate, out *metricValue) compute func(in *statAggregate, out *metricValue)
} }
func metricsLock() {
// Acquire the metricsSema but with handoff. Operations are typically
// expensive enough that queueing up goroutines and handing off between
// them will be noticeably better-behaved.
semacquire1(&metricsSema, true, 0, 0)
if raceenabled {
raceacquire(unsafe.Pointer(&metricsSema))
}
}
func metricsUnlock() {
if raceenabled {
racerelease(unsafe.Pointer(&metricsSema))
}
semrelease(&metricsSema)
}
// initMetrics initializes the metrics map if it hasn't been yet. // initMetrics initializes the metrics map if it hasn't been yet.
// //
// metricsSema must be held. // metricsSema must be held.
@ -388,13 +408,13 @@ func (a *heapStatsAggregate) compute() {
memstats.heapStats.read(&a.heapStatsDelta) memstats.heapStats.read(&a.heapStatsDelta)
// Calculate derived stats. // Calculate derived stats.
a.totalAllocs = uint64(a.largeAllocCount) a.totalAllocs = a.largeAllocCount
a.totalFrees = uint64(a.largeFreeCount) a.totalFrees = a.largeFreeCount
a.totalAllocated = uint64(a.largeAlloc) a.totalAllocated = a.largeAlloc
a.totalFreed = uint64(a.largeFree) a.totalFreed = a.largeFree
for i := range a.smallAllocCount { for i := range a.smallAllocCount {
na := uint64(a.smallAllocCount[i]) na := a.smallAllocCount[i]
nf := uint64(a.smallFreeCount[i]) nf := a.smallFreeCount[i]
a.totalAllocs += na a.totalAllocs += na
a.totalFrees += nf a.totalFrees += nf
a.totalAllocated += na * uint64(class_to_size[i]) a.totalAllocated += na * uint64(class_to_size[i])
@ -546,10 +566,7 @@ func readMetrics(samplesp unsafe.Pointer, len int, cap int) {
sl := slice{samplesp, len, cap} sl := slice{samplesp, len, cap}
samples := *(*[]metricSample)(unsafe.Pointer(&sl)) samples := *(*[]metricSample)(unsafe.Pointer(&sl))
// Acquire the metricsSema but with handoff. This operation metricsLock()
// is expensive enough that queueing up goroutines and handing
// off between them will be noticeably better-behaved.
semacquire1(&metricsSema, true, 0, 0)
// Ensure the map is initialized. // Ensure the map is initialized.
initMetrics() initMetrics()
@ -573,5 +590,5 @@ func readMetrics(samplesp unsafe.Pointer, len int, cap int) {
data.compute(&agg, &sample.value) data.compute(&agg, &sample.value)
} }
semrelease(&metricsSema) metricsUnlock()
} }

View File

@ -581,7 +581,7 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
// free slots zeroed. // free slots zeroed.
s.needzero = 1 s.needzero = 1
stats := memstats.heapStats.acquire() stats := memstats.heapStats.acquire()
atomic.Xadduintptr(&stats.smallFreeCount[spc.sizeclass()], uintptr(nfreed)) atomic.Xadd64(&stats.smallFreeCount[spc.sizeclass()], int64(nfreed))
memstats.heapStats.release() memstats.heapStats.release()
} }
if !preserve { if !preserve {
@ -628,8 +628,8 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
mheap_.freeSpan(s) mheap_.freeSpan(s)
} }
stats := memstats.heapStats.acquire() stats := memstats.heapStats.acquire()
atomic.Xadduintptr(&stats.largeFreeCount, 1) atomic.Xadd64(&stats.largeFreeCount, 1)
atomic.Xadduintptr(&stats.largeFree, size) atomic.Xadd64(&stats.largeFree, int64(size))
memstats.heapStats.release() memstats.heapStats.release()
return true return true
} }

View File

@ -8,7 +8,6 @@ package runtime
import ( import (
"runtime/internal/atomic" "runtime/internal/atomic"
"runtime/internal/sys"
"unsafe" "unsafe"
) )
@ -565,29 +564,29 @@ func updatememstats() {
memstats.heapStats.unsafeRead(&consStats) memstats.heapStats.unsafeRead(&consStats)
// Collect large allocation stats. // Collect large allocation stats.
totalAlloc := uint64(consStats.largeAlloc) totalAlloc := consStats.largeAlloc
memstats.nmalloc += uint64(consStats.largeAllocCount) memstats.nmalloc += consStats.largeAllocCount
totalFree := uint64(consStats.largeFree) totalFree := consStats.largeFree
memstats.nfree += uint64(consStats.largeFreeCount) memstats.nfree += consStats.largeFreeCount
// Collect per-sizeclass stats. // Collect per-sizeclass stats.
for i := 0; i < _NumSizeClasses; i++ { for i := 0; i < _NumSizeClasses; i++ {
// Malloc stats. // Malloc stats.
a := uint64(consStats.smallAllocCount[i]) a := consStats.smallAllocCount[i]
totalAlloc += a * uint64(class_to_size[i]) totalAlloc += a * uint64(class_to_size[i])
memstats.nmalloc += a memstats.nmalloc += a
memstats.by_size[i].nmalloc = a memstats.by_size[i].nmalloc = a
// Free stats. // Free stats.
f := uint64(consStats.smallFreeCount[i]) f := consStats.smallFreeCount[i]
totalFree += f * uint64(class_to_size[i]) totalFree += f * uint64(class_to_size[i])
memstats.nfree += f memstats.nfree += f
memstats.by_size[i].nfree = f memstats.by_size[i].nfree = f
} }
// Account for tiny allocations. // Account for tiny allocations.
memstats.nfree += uint64(consStats.tinyAllocCount) memstats.nfree += consStats.tinyAllocCount
memstats.nmalloc += uint64(consStats.tinyAllocCount) memstats.nmalloc += consStats.tinyAllocCount
// Calculate derived stats. // Calculate derived stats.
memstats.total_alloc = totalAlloc memstats.total_alloc = totalAlloc
@ -703,17 +702,20 @@ type heapStatsDelta struct {
inPtrScalarBits int64 // byte delta of memory reserved for unrolled GC prog bits inPtrScalarBits int64 // byte delta of memory reserved for unrolled GC prog bits
// Allocator stats. // Allocator stats.
tinyAllocCount uintptr // number of tiny allocations //
largeAlloc uintptr // bytes allocated for large objects // These are all uint64 because they're cumulative, and could quickly wrap
largeAllocCount uintptr // number of large object allocations // around otherwise.
smallAllocCount [_NumSizeClasses]uintptr // number of allocs for small objects tinyAllocCount uint64 // number of tiny allocations
largeFree uintptr // bytes freed for large objects (>maxSmallSize) largeAlloc uint64 // bytes allocated for large objects
largeFreeCount uintptr // number of frees for large objects (>maxSmallSize) largeAllocCount uint64 // number of large object allocations
smallFreeCount [_NumSizeClasses]uintptr // number of frees for small objects (<=maxSmallSize) smallAllocCount [_NumSizeClasses]uint64 // number of allocs for small objects
largeFree uint64 // bytes freed for large objects (>maxSmallSize)
largeFreeCount uint64 // number of frees for large objects (>maxSmallSize)
smallFreeCount [_NumSizeClasses]uint64 // number of frees for small objects (<=maxSmallSize)
// Add a uint32 to ensure this struct is a multiple of 8 bytes in size. // NOTE: This struct must be a multiple of 8 bytes in size because it
// Only necessary on 32-bit platforms. // is stored in an array. If it's not, atomic accesses to the above
_ [(sys.PtrSize / 4) % 2]uint32 // fields may be unaligned and fail on 32-bit platforms.
} }
// merge adds in the deltas from b into a. // merge adds in the deltas from b into a.

View File

@ -12,12 +12,30 @@ import (
// cbs stores all registered Go callbacks. // cbs stores all registered Go callbacks.
var cbs struct { var cbs struct {
lock mutex lock mutex // use cbsLock / cbsUnlock for race instrumentation.
ctxt [cb_max]winCallback ctxt [cb_max]winCallback
index map[winCallbackKey]int index map[winCallbackKey]int
n int n int
} }
func cbsLock() {
lock(&cbs.lock)
// compileCallback is used by goenvs prior to completion of schedinit.
// raceacquire involves a racecallback to get the proc, which is not
// safe prior to scheduler initialization. Thus avoid instrumentation
// until then.
if raceenabled && mainStarted {
raceacquire(unsafe.Pointer(&cbs.lock))
}
}
func cbsUnlock() {
if raceenabled && mainStarted {
racerelease(unsafe.Pointer(&cbs.lock))
}
unlock(&cbs.lock)
}
// winCallback records information about a registered Go callback. // winCallback records information about a registered Go callback.
type winCallback struct { type winCallback struct {
fn *funcval // Go function fn *funcval // Go function
@ -302,11 +320,11 @@ func compileCallback(fn eface, cdecl bool) (code uintptr) {
key := winCallbackKey{(*funcval)(fn.data), cdecl} key := winCallbackKey{(*funcval)(fn.data), cdecl}
lock(&cbs.lock) // We don't unlock this in a defer because this is used from the system stack. cbsLock()
// Check if this callback is already registered. // Check if this callback is already registered.
if n, ok := cbs.index[key]; ok { if n, ok := cbs.index[key]; ok {
unlock(&cbs.lock) cbsUnlock()
return callbackasmAddr(n) return callbackasmAddr(n)
} }
@ -316,7 +334,7 @@ func compileCallback(fn eface, cdecl bool) (code uintptr) {
} }
n := cbs.n n := cbs.n
if n >= len(cbs.ctxt) { if n >= len(cbs.ctxt) {
unlock(&cbs.lock) cbsUnlock()
throw("too many callback functions") throw("too many callback functions")
} }
c := winCallback{key.fn, retPop, abiMap} c := winCallback{key.fn, retPop, abiMap}
@ -324,7 +342,7 @@ func compileCallback(fn eface, cdecl bool) (code uintptr) {
cbs.index[key] = n cbs.index[key] = n
cbs.n++ cbs.n++
unlock(&cbs.lock) cbsUnlock()
return callbackasmAddr(n) return callbackasmAddr(n)
} }

View File

@ -0,0 +1,89 @@
// compile
// Copyright 2022 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
type T1 struct {
A T5
B T2
C T7
D T4
}
type T2 struct {
T3
A float64
E float64
C float64
}
type T3 struct {
F float64
G float64
H float64
I float64
J float64
K float64
L float64
}
type T4 struct {
M float64
N float64
O float64
P float64
}
type T5 struct {
Q float64
R float64
S float64
T float64
U float64
V float64
}
type T6 struct {
T9
C T10
}
type T7 struct {
T10
T11
}
type T8 struct {
T9
C T7
}
type T9 struct {
A T5
B T3
D T4
}
type T10 struct {
W float64
}
type T11 struct {
X float64
Y float64
}
func MainTest(x T1, y T8, z T6) float64 {
return Test(x.B, x.A, x.D, x.C, y.B, y.A, y.D, y.C, z.B, z.A, z.D,
T7{
T10: T10{
W: z.C.W,
},
T11: T11{},
},
)
}
func Test(a T2, b T5, c T4, d T7, e T3, f T5, g T4, h T7, i T3, j T5, k T4, l T7) float64