diff --git a/VERSION b/VERSION index 57acaaeae89333..6f84a79fe4133f 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -go1.26.1 -time 2026-03-05T20:45:11Z +go1.26.2 +time 2026-03-27T21:58:29Z diff --git a/doc/godebug.md b/doc/godebug.md index dbec880c56c7ac..00da3d83efe54e 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -133,9 +133,7 @@ are also treated as invalid. The defaults that will be compiled into a main package are reported by the command: -{{raw ` go list -f '{{.DefaultGODEBUG}}' my/main/package -`}} Only differences from the base Go toolchain defaults are reported. diff --git a/src/archive/tar/format.go b/src/archive/tar/format.go index 9954b4d9f5548a..32e58a9d9b4e1e 100644 --- a/src/archive/tar/format.go +++ b/src/archive/tar/format.go @@ -147,6 +147,12 @@ const ( // Max length of a special file (PAX header, GNU long name or link). // This matches the limit used by libarchive. maxSpecialFileSize = 1 << 20 + + // Maximum number of sparse file entries. + // We should never actually hit this limit + // (every sparse encoding will first be limited by maxSpecialFileSize), + // but this adds an additional layer of defense. + maxSparseFileEntries = 1 << 20 ) // blockPadding computes the number of bytes needed to pad offset up to the diff --git a/src/archive/tar/reader.go b/src/archive/tar/reader.go index 16ac2f5b17c28b..f85b9986259528 100644 --- a/src/archive/tar/reader.go +++ b/src/archive/tar/reader.go @@ -490,7 +490,8 @@ func (tr *Reader) readOldGNUSparseMap(hdr *Header, blk *block) (sparseDatas, err } s := blk.toGNU().sparse() spd := make(sparseDatas, 0, s.maxEntries()) - for { + totalSize := len(s) + for totalSize < maxSpecialFileSize { for i := 0; i < s.maxEntries(); i++ { // This termination condition is identical to GNU and BSD tar. if s.entry(i).offset()[0] == 0x00 { @@ -501,7 +502,11 @@ func (tr *Reader) readOldGNUSparseMap(hdr *Header, blk *block) (sparseDatas, err if p.err != nil { return nil, p.err } - spd = append(spd, sparseEntry{Offset: offset, Length: length}) + var err error + spd, err = appendSparseEntry(spd, sparseEntry{Offset: offset, Length: length}) + if err != nil { + return nil, err + } } if s.isExtended()[0] > 0 { @@ -510,10 +515,12 @@ func (tr *Reader) readOldGNUSparseMap(hdr *Header, blk *block) (sparseDatas, err return nil, err } s = blk.toSparse() + totalSize += len(s) continue } return spd, nil // Done } + return nil, errSparseTooLong } // readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format @@ -586,7 +593,10 @@ func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) { if err1 != nil || err2 != nil { return nil, ErrHeader } - spd = append(spd, sparseEntry{Offset: offset, Length: length}) + spd, err = appendSparseEntry(spd, sparseEntry{Offset: offset, Length: length}) + if err != nil { + return nil, err + } } return spd, nil } @@ -620,12 +630,22 @@ func readGNUSparseMap0x1(paxHdrs map[string]string) (sparseDatas, error) { if err1 != nil || err2 != nil { return nil, ErrHeader } - spd = append(spd, sparseEntry{Offset: offset, Length: length}) + spd, err = appendSparseEntry(spd, sparseEntry{Offset: offset, Length: length}) + if err != nil { + return nil, err + } sparseMap = sparseMap[2:] } return spd, nil } +func appendSparseEntry(spd sparseDatas, ent sparseEntry) (sparseDatas, error) { + if len(spd) >= maxSparseFileEntries { + return nil, errSparseTooLong + } + return append(spd, ent), nil +} + // Read reads from the current file in the tar archive. // It returns (0, io.EOF) when it reaches the end of that file, // until [Next] is called to advance to the next file. diff --git a/src/archive/tar/reader_test.go b/src/archive/tar/reader_test.go index c7611ca044c3f5..a324674cb7bb19 100644 --- a/src/archive/tar/reader_test.go +++ b/src/archive/tar/reader_test.go @@ -1145,6 +1145,17 @@ func TestReadOldGNUSparseMap(t *testing.T) { input: makeInput(FormatGNU, "", makeSparseStrings(sparseDatas{{10 << 30, 512}, {20 << 30, 512}})...), wantMap: sparseDatas{{10 << 30, 512}, {20 << 30, 512}}, + }, { + input: makeInput(FormatGNU, "", + makeSparseStrings(func() sparseDatas { + var datas sparseDatas + // This is more than enough entries to exceed our limit. + for i := range int64(1 << 20) { + datas = append(datas, sparseEntry{i * 2, (i * 2) + 1}) + } + return datas + }())...), + wantErr: errSparseTooLong, }} for i, v := range vectors { diff --git a/src/builtin/builtin.go b/src/builtin/builtin.go index afa2a10f9091dc..be53077d9cb495 100644 --- a/src/builtin/builtin.go +++ b/src/builtin/builtin.go @@ -122,6 +122,10 @@ type Type int // invocation. type Type1 int +// TypeOrExpr is here for the purposes of documentation only. It is a stand-in +// for either a Go type or an expression. +type TypeOrExpr int + // IntegerType is here for the purposes of documentation only. It is a stand-in // for any integer type: int, uint, int8 etc. type IntegerType int @@ -220,10 +224,15 @@ func max[T cmp.Ordered](x T, y ...T) T // min will return NaN. func min[T cmp.Ordered](x T, y ...T) T -// The new built-in function allocates memory. The first argument is a type, -// not a value, and the value returned is a pointer to a newly -// allocated zero value of that type. -func new(Type) *Type +// The built-in function new allocates a new, initialized variable and returns +// a pointer to it. It accepts a single argument, which may be either a type +// or an expression. +// If the argument is a type T, then new(T) allocates a variable of type T +// initialized to its zero value. +// Otherwise, the argument is an expression x and new(x) allocates a variable +// of the type of x initialized to the value of x. If that value is an untyped +// constant, it is first implicitly converted to its default type. +func new(TypeOrExpr) *Type // The complex built-in function constructs a complex value from two // floating-point values. The real and imaginary parts must be of the same diff --git a/src/cmd/compile/internal/bloop/bloop.go b/src/cmd/compile/internal/bloop/bloop.go index 69c889e85803db..b8ff1f0fbdf168 100644 --- a/src/cmd/compile/internal/bloop/bloop.go +++ b/src/cmd/compile/internal/bloop/bloop.go @@ -144,7 +144,7 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { if name != nil { debugName(name, n.Pos()) ret = keepAliveAt([]ir.Node{name}, n) - } else if deref := n.X.(*ir.StarExpr); deref != nil { + } else if deref, ok := n.X.(*ir.StarExpr); ok && deref != nil { ret = keepAliveAt([]ir.Node{deref}, n) if base.Flag.LowerM > 1 { base.WarnfAt(n.Pos(), "dereference will be kept alive") @@ -159,7 +159,7 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { if name != nil { debugName(name, n.Pos()) ns = append(ns, name) - } else if deref := lhs.(*ir.StarExpr); deref != nil { + } else if deref, ok := lhs.(*ir.StarExpr); ok && deref != nil { ns = append(ns, deref) if base.Flag.LowerM > 1 { base.WarnfAt(n.Pos(), "dereference will be kept alive") @@ -174,7 +174,7 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { if name != nil { debugName(name, n.Pos()) ret = keepAliveAt([]ir.Node{name}, n) - } else if deref := n.X.(*ir.StarExpr); deref != nil { + } else if deref, ok := n.X.(*ir.StarExpr); ok && deref != nil { ret = keepAliveAt([]ir.Node{deref}, n) if base.Flag.LowerM > 1 { base.WarnfAt(n.Pos(), "dereference will be kept alive") diff --git a/src/cmd/compile/internal/ssa/_gen/dec.rules b/src/cmd/compile/internal/ssa/_gen/dec.rules index a04a7cd5f836e2..97bc2a59788fad 100644 --- a/src/cmd/compile/internal/ssa/_gen/dec.rules +++ b/src/cmd/compile/internal/ssa/_gen/dec.rules @@ -101,8 +101,7 @@ (IMake _typ (StructMake ___)) => imakeOfStructMake(v) (StructSelect (IData x)) && v.Type.Size() > 0 => (IData x) -(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsStruct() => (StructMake) -(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsArray() => (ArrayMake0) +(StructSelect (IData x)) && v.Type.Size() == 0 => (Empty) (StructSelect [i] x:(StructMake ___)) => x.Args[i] diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules index fe8fc5b2620b5f..37ce66238134c3 100644 --- a/src/cmd/compile/internal/ssa/_gen/generic.rules +++ b/src/cmd/compile/internal/ssa/_gen/generic.rules @@ -941,7 +941,7 @@ // struct operations (StructSelect [i] x:(StructMake ___)) => x.Args[i] -(Load _ _) && t.IsStruct() && CanSSA(t) && !t.IsSIMD() => rewriteStructLoad(v) +(Load _ _) && t.IsStruct() && t.Size() > 0 && CanSSA(t) && !t.IsSIMD() => rewriteStructLoad(v) (Store _ (StructMake ___) _) => rewriteStructStore(v) (StructSelect [i] x:(Load ptr mem)) && !CanSSA(t) => @@ -950,8 +950,7 @@ // Putting struct{*byte} and similar into direct interfaces. (IMake _typ (StructMake ___)) => imakeOfStructMake(v) (StructSelect (IData x)) && v.Type.Size() > 0 => (IData x) -(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsStruct() => (StructMake) -(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsArray() => (ArrayMake0) +(StructSelect (IData x)) && v.Type.Size() == 0 => (Empty) // un-SSAable values use mem->mem copies (Store {t} dst (Load src mem) mem) && !CanSSA(t) => @@ -962,19 +961,19 @@ // array ops (ArraySelect (ArrayMake1 x)) => x -(Load _ _) && t.IsArray() && t.NumElem() == 0 => - (ArrayMake0) - (Load ptr mem) && t.IsArray() && t.NumElem() == 1 && CanSSA(t) => (ArrayMake1 (Load ptr mem)) -(Store _ (ArrayMake0) mem) => mem (Store dst (ArrayMake1 e) mem) => (Store {e.Type} dst e mem) // Putting [1]*byte and similar into direct interfaces. (IMake _typ (ArrayMake1 val)) => (IMake _typ val) (ArraySelect [0] (IData x)) => (IData x) +// zero-sized values. +(Load _ _) && t.Size() == 0 => (Empty) +(Store _ (Empty) mem) => mem + // string ops // Decomposing StringMake and lowering of StringPtr and StringLen // happens in a later pass, dec, so that these operations are available diff --git a/src/cmd/compile/internal/ssa/_gen/genericOps.go b/src/cmd/compile/internal/ssa/_gen/genericOps.go index 85bde1aab27724..2f75656e0b7c78 100644 --- a/src/cmd/compile/internal/ssa/_gen/genericOps.go +++ b/src/cmd/compile/internal/ssa/_gen/genericOps.go @@ -537,12 +537,11 @@ var genericOps = []opData{ {name: "IData", argLength: 1}, // arg0=interface, returns data field // Structs - {name: "StructMake", argLength: -1}, // args...=field0..n-1. Returns struct with n fields. + {name: "StructMake", argLength: -1}, // args...=field0..n-1. Returns struct with n fields. Must have >0 size (use Empty otherwise). {name: "StructSelect", argLength: 1, aux: "Int64"}, // arg0=struct, auxint=field index. Returns the auxint'th field. // Arrays - {name: "ArrayMake0"}, // Returns array with 0 elements - {name: "ArrayMake1", argLength: 1}, // Returns array with 1 element + {name: "ArrayMake1", argLength: 1}, // Returns array with 1 element. Use Empty if the element is zero-sized. {name: "ArraySelect", argLength: 1, aux: "Int64"}, // arg0=array, auxint=index. Returns a[i]. // Spill&restore ops for the register allocator. These are @@ -682,6 +681,9 @@ var genericOps = []opData{ // Helper instruction which is semantically equivalent to calling runtime.memequal, but some targets may prefer to custom lower it later, e.g. for specific constant sizes. {name: "MemEq", argLength: 4, commutative: true, typ: "Bool"}, // arg0=ptr0, arg1=ptr1, arg2=size, arg3=memory. + // Value of a zero-sized type. + {name: "Empty", argLength: 0}, + // SIMD {name: "ZeroSIMD", argLength: 0}, // zero value of a vector diff --git a/src/cmd/compile/internal/ssa/check.go b/src/cmd/compile/internal/ssa/check.go index 4ea85613040b40..a41c110faa9b93 100644 --- a/src/cmd/compile/internal/ssa/check.go +++ b/src/cmd/compile/internal/ssa/check.go @@ -274,6 +274,10 @@ func checkFunc(f *Func) { } } + if (v.Op == OpStructMake || v.Op == OpArrayMake1) && v.Type.Size() == 0 { + f.Fatalf("zero-sized Make; use Empty instead %v", v) + } + if f.RegAlloc != nil && f.Config.SoftFloat && v.Type.IsFloat() { f.Fatalf("unexpected floating-point type %v", v.LongString()) } diff --git a/src/cmd/compile/internal/ssa/copyelim.go b/src/cmd/compile/internal/ssa/copyelim.go index 09df63565be29c..762ffe1bd2d84c 100644 --- a/src/cmd/compile/internal/ssa/copyelim.go +++ b/src/cmd/compile/internal/ssa/copyelim.go @@ -105,11 +105,7 @@ func phielim(f *Func) { // This is an early place in SSA where all values are examined. // Rewrite all 0-sized Go values to remove accessors, dereferences, loads, etc. if t := v.Type; (t.IsStruct() || t.IsArray()) && t.Size() == 0 { - if t.IsStruct() { - v.reset(OpStructMake) - } else { - v.reset(OpArrayMake0) - } + v.reset(OpEmpty) } // Modify all values so no arg (including args // of OpCopy) is a copy. diff --git a/src/cmd/compile/internal/ssa/decompose.go b/src/cmd/compile/internal/ssa/decompose.go index a3d0fbd4069760..6ea69da0169fa3 100644 --- a/src/cmd/compile/internal/ssa/decompose.go +++ b/src/cmd/compile/internal/ssa/decompose.go @@ -266,7 +266,7 @@ func decomposeUser(f *Func) { // returned. func decomposeUserArrayInto(f *Func, name *LocalSlot, slots []*LocalSlot) []*LocalSlot { t := name.Type - if t.NumElem() == 0 { + if t.Size() == 0 { // TODO(khr): Not sure what to do here. Probably nothing. // Names for empty arrays aren't important. return slots @@ -362,6 +362,10 @@ func decomposeUserPhi(v *Value) { // and then recursively decomposes the phis for each field. func decomposeStructPhi(v *Value) { t := v.Type + if t.Size() == 0 { + v.reset(OpEmpty) + return + } n := t.NumFields() fields := make([]*Value, 0, MaxStruct) for i := 0; i < n; i++ { @@ -385,8 +389,8 @@ func decomposeStructPhi(v *Value) { // and then recursively decomposes the element phi. func decomposeArrayPhi(v *Value) { t := v.Type - if t.NumElem() == 0 { - v.reset(OpArrayMake0) + if t.Size() == 0 { + v.reset(OpEmpty) return } if t.NumElem() != 1 { diff --git a/src/cmd/compile/internal/ssa/expand_calls.go b/src/cmd/compile/internal/ssa/expand_calls.go index ba2bedc65f8154..3b434e4791ad74 100644 --- a/src/cmd/compile/internal/ssa/expand_calls.go +++ b/src/cmd/compile/internal/ssa/expand_calls.go @@ -508,13 +508,7 @@ func (x *expandState) rewriteSelectOrArg(pos src.XPos, b *Block, container, a, m if at.Size() == 0 { // For consistency, create these values even though they'll ultimately be unused - if at.IsArray() { - return makeOf(a, OpArrayMake0, nil) - } - if at.IsStruct() { - return makeOf(a, OpStructMake, nil) - } - return a + return makeOf(a, OpEmpty, nil) } sk := selKey{from: container, size: 0, offsetOrIndex: rc.storeOffset, typ: at} diff --git a/src/cmd/compile/internal/ssa/loopbce.go b/src/cmd/compile/internal/ssa/loopbce.go index aa6cc48cacbbe7..a63a314043fe89 100644 --- a/src/cmd/compile/internal/ssa/loopbce.go +++ b/src/cmd/compile/internal/ssa/loopbce.go @@ -219,9 +219,11 @@ func findIndVar(f *Func) []indVar { if init.AuxInt > v { return false } + // TODO(1.27): investigate passing a smaller-magnitude overflow limit to addU + // for addWillOverflow. v = addU(init.AuxInt, diff(v, init.AuxInt)/uint64(step)*uint64(step)) } - if addWillOverflow(v, step) { + if addWillOverflow(v, step, maxSignedValue(ind.Type)) { return false } if inclusive && v != limit.AuxInt || !inclusive && v+1 != limit.AuxInt { @@ -250,7 +252,7 @@ func findIndVar(f *Func) []indVar { // ind < knn - k cannot overflow if step is at most k+1 return step <= k+1 && k != maxSignedValue(limit.Type) } else { // step < 0 - if limit.Op == OpConst64 { + if limit.isGenericIntConst() { // Figure out the actual smallest value. v := limit.AuxInt if !inclusive { @@ -264,9 +266,11 @@ func findIndVar(f *Func) []indVar { if init.AuxInt < v { return false } + // TODO(1.27): investigate passing a smaller-magnitude underflow limit to subU + // for subWillUnderflow. v = subU(init.AuxInt, diff(init.AuxInt, v)/uint64(-step)*uint64(-step)) } - if subWillUnderflow(v, -step) { + if subWillUnderflow(v, -step, minSignedValue(ind.Type)) { return false } if inclusive && v != limit.AuxInt || !inclusive && v-1 != limit.AuxInt { @@ -328,14 +332,22 @@ func findIndVar(f *Func) []indVar { return iv } -// addWillOverflow reports whether x+y would result in a value more than maxint. -func addWillOverflow(x, y int64) bool { - return x+y < x +// subWillUnderflow checks if x - y underflows the min value. +// y must be positive. +func subWillUnderflow(x, y int64, min int64) bool { + if y < 0 { + base.Fatalf("expecting positive value") + } + return x < min+y } -// subWillUnderflow reports whether x-y would result in a value less than minint. -func subWillUnderflow(x, y int64) bool { - return x-y > x +// addWillOverflow checks if x + y overflows the max value. +// y must be positive. +func addWillOverflow(x, y int64, max int64) bool { + if y < 0 { + base.Fatalf("expecting positive value") + } + return x > max-y } // diff returns x-y as a uint64. Requires x>=y. @@ -356,7 +368,8 @@ func addU(x int64, y uint64) int64 { x += 1 y -= 1 << 63 } - if addWillOverflow(x, int64(y)) { + // TODO(1.27): investigate passing a smaller-magnitude overflow limit in here. + if addWillOverflow(x, int64(y), maxSignedValue(types.Types[types.TINT64])) { base.Fatalf("addU overflowed %d + %d", x, y) } return x + int64(y) @@ -372,7 +385,8 @@ func subU(x int64, y uint64) int64 { x -= 1 y -= 1 << 63 } - if subWillUnderflow(x, int64(y)) { + // TODO(1.27): investigate passing a smaller-magnitude underflow limit in here. + if subWillUnderflow(x, int64(y), minSignedValue(types.Types[types.TINT64])) { base.Fatalf("subU underflowed %d - %d", x, y) } return x - int64(y) diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 816b0786052ac0..db0a152ae884d8 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -6034,7 +6034,6 @@ const ( OpIData OpStructMake OpStructSelect - OpArrayMake0 OpArrayMake1 OpArraySelect OpStoreReg @@ -6126,6 +6125,7 @@ const ( OpPrefetchCache OpPrefetchCacheStreamed OpMemEq + OpEmpty OpZeroSIMD OpCvt16toMask8x16 OpCvt32toMask8x32 @@ -88194,11 +88194,6 @@ var opcodeTable = [...]opInfo{ argLen: 1, generic: true, }, - { - name: "ArrayMake0", - argLen: 0, - generic: true, - }, { name: "ArrayMake1", argLen: 1, @@ -88719,6 +88714,11 @@ var opcodeTable = [...]opInfo{ commutative: true, generic: true, }, + { + name: "Empty", + argLen: 0, + generic: true, + }, { name: "ZeroSIMD", argLen: 0, diff --git a/src/cmd/compile/internal/ssa/rewritedec.go b/src/cmd/compile/internal/ssa/rewritedec.go index 1e5c19cd23dc20..1141d322bf0a59 100644 --- a/src/cmd/compile/internal/ssa/rewritedec.go +++ b/src/cmd/compile/internal/ssa/rewritedec.go @@ -868,29 +868,16 @@ func rewriteValuedec_OpStructSelect(v *Value) bool { return true } // match: (StructSelect (IData x)) - // cond: v.Type.Size() == 0 && v.Type.IsStruct() - // result: (StructMake) + // cond: v.Type.Size() == 0 + // result: (Empty) for { if v_0.Op != OpIData { break } - if !(v.Type.Size() == 0 && v.Type.IsStruct()) { + if !(v.Type.Size() == 0) { break } - v.reset(OpStructMake) - return true - } - // match: (StructSelect (IData x)) - // cond: v.Type.Size() == 0 && v.Type.IsArray() - // result: (ArrayMake0) - for { - if v_0.Op != OpIData { - break - } - if !(v.Type.Size() == 0 && v.Type.IsArray()) { - break - } - v.reset(OpArrayMake0) + v.reset(OpEmpty) return true } // match: (StructSelect [i] x:(StructMake ___)) diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index dbbb7105afa455..be944ef8546c0c 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -12420,27 +12420,16 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { return true } // match: (Load _ _) - // cond: t.IsStruct() && CanSSA(t) && !t.IsSIMD() + // cond: t.IsStruct() && t.Size() > 0 && CanSSA(t) && !t.IsSIMD() // result: rewriteStructLoad(v) for { t := v.Type - if !(t.IsStruct() && CanSSA(t) && !t.IsSIMD()) { + if !(t.IsStruct() && t.Size() > 0 && CanSSA(t) && !t.IsSIMD()) { break } v.copyOf(rewriteStructLoad(v)) return true } - // match: (Load _ _) - // cond: t.IsArray() && t.NumElem() == 0 - // result: (ArrayMake0) - for { - t := v.Type - if !(t.IsArray() && t.NumElem() == 0) { - break - } - v.reset(OpArrayMake0) - return true - } // match: (Load ptr mem) // cond: t.IsArray() && t.NumElem() == 1 && CanSSA(t) // result: (ArrayMake1 (Load ptr mem)) @@ -12457,6 +12446,17 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { v.AddArg(v0) return true } + // match: (Load _ _) + // cond: t.Size() == 0 + // result: (Empty) + for { + t := v.Type + if !(t.Size() == 0) { + break + } + v.reset(OpEmpty) + return true + } // match: (Load sptr:(Addr {scon} (SB)) mem) // cond: symIsRO(scon) // result: (Const8 [int8(read8(scon,0))]) @@ -32005,16 +32005,6 @@ func rewriteValuegeneric_OpStore(v *Value) bool { v.AddArg3(dst, src, v0) return true } - // match: (Store _ (ArrayMake0) mem) - // result: mem - for { - if v_1.Op != OpArrayMake0 { - break - } - mem := v_2 - v.copyOf(mem) - return true - } // match: (Store dst (ArrayMake1 e) mem) // result: (Store {e.Type} dst e mem) for { @@ -32029,6 +32019,16 @@ func rewriteValuegeneric_OpStore(v *Value) bool { v.AddArg3(dst, e, mem) return true } + // match: (Store _ (Empty) mem) + // result: mem + for { + if v_1.Op != OpEmpty { + break + } + mem := v_2 + v.copyOf(mem) + return true + } // match: (Store (SelectN [0] call:(StaticLECall ___)) x mem:(SelectN [1] call)) // cond: isConstZero(x) && isMalloc(call.Aux) // result: mem @@ -32520,29 +32520,16 @@ func rewriteValuegeneric_OpStructSelect(v *Value) bool { return true } // match: (StructSelect (IData x)) - // cond: v.Type.Size() == 0 && v.Type.IsStruct() - // result: (StructMake) - for { - if v_0.Op != OpIData { - break - } - if !(v.Type.Size() == 0 && v.Type.IsStruct()) { - break - } - v.reset(OpStructMake) - return true - } - // match: (StructSelect (IData x)) - // cond: v.Type.Size() == 0 && v.Type.IsArray() - // result: (ArrayMake0) + // cond: v.Type.Size() == 0 + // result: (Empty) for { if v_0.Op != OpIData { break } - if !(v.Type.Size() == 0 && v.Type.IsArray()) { + if !(v.Type.Size() == 0) { break } - v.reset(OpArrayMake0) + v.reset(OpEmpty) return true } return false diff --git a/src/cmd/compile/internal/ssa/rewritetern.go b/src/cmd/compile/internal/ssa/rewritetern.go index 5493e5f1092b08..766b3f898c0765 100644 --- a/src/cmd/compile/internal/ssa/rewritetern.go +++ b/src/cmd/compile/internal/ssa/rewritetern.go @@ -5,7 +5,6 @@ package ssa import ( - "fmt" "internal/goarch" "slices" ) @@ -175,7 +174,10 @@ func rewriteTern(f *Func) { imm := computeTT(a0, vars0) op := ternOpForLogical(a0.Op) if op == a0.Op { - panic(fmt.Errorf("should have mapped away from input op, a0 is %s", a0.LongString())) + if f.pass.debug > 0 { + f.Warnl(a0.Pos, "Skipping rewrite for %s, op=%v", a0.LongString(), op) + } + return } if f.pass.debug > 0 { f.Warnl(a0.Pos, "Rewriting %s into %v of 0b%b %v %v %v", a0.LongString(), op, imm, diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go index dba73de771da29..95710d9024bfeb 100644 --- a/src/cmd/compile/internal/ssa/value.go +++ b/src/cmd/compile/internal/ssa/value.go @@ -616,6 +616,9 @@ func CanSSA(t *types.Type) bool { if t.IsSIMD() { return true } + if t.Size() == 0 { + return true + } sizeLimit := int64(MaxStruct * types.PtrSize) if t.Size() > sizeLimit { // 4*Widthptr is an arbitrary constant. We want it diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 17feb90df7de1b..3da43e715e7df6 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -340,9 +340,9 @@ func buildssa(fn *ir.Func, worker int, isPgoHot bool) *ssa.Func { if base.Flag.Cfg.Instrumenting && fn.Pragma&ir.Norace == 0 && !fn.Linksym().ABIWrapper() { if !base.Flag.Race || !objabi.LookupPkgSpecial(fn.Sym().Pkg.Path).NoRaceFunc { s.instrumentMemory = true - } - if base.Flag.Race { - s.instrumentEnterExit = true + if base.Flag.Race { + s.instrumentEnterExit = true + } } } @@ -1653,6 +1653,16 @@ func (s *state) stmtList(l ir.Nodes) { } } +func peelConvNop(n ir.Node) ir.Node { + if n == nil { + return n + } + for n.Op() == ir.OCONVNOP { + n = n.(*ir.ConvExpr).X + } + return n +} + // stmt converts the statement n to SSA and adds it to s. func (s *state) stmt(n ir.Node) { s.pushLine(n.Pos()) @@ -1828,12 +1838,10 @@ func (s *state) stmt(n ir.Node) { // arrays referenced are strictly smaller parts of the same base array. // If one side of the assignment is a full array, then partial overlap // can't happen. (The arrays are either disjoint or identical.) - mayOverlap := n.X.Op() == ir.ODEREF && (n.Y != nil && n.Y.Op() == ir.ODEREF) - if n.Y != nil && n.Y.Op() == ir.ODEREF { - p := n.Y.(*ir.StarExpr).X - for p.Op() == ir.OCONVNOP { - p = p.(*ir.ConvExpr).X - } + ny := peelConvNop(n.Y) + mayOverlap := n.X.Op() == ir.ODEREF && (n.Y != nil && ny.Op() == ir.ODEREF) + if ny != nil && ny.Op() == ir.ODEREF { + p := peelConvNop(ny.(*ir.StarExpr).X) if p.Op() == ir.OSPTR && p.(*ir.UnaryExpr).X.Type().IsString() { // Pointer fields of strings point to unmodifiable memory. // That memory can't overlap with the memory being written. @@ -4488,6 +4496,11 @@ func (s *state) assignWhichMayOverlap(left ir.Node, right *ssa.Value, deref bool // Grab old value of structure. old := s.expr(left.X) + if left.Type().Size() == 0 { + // Nothing to do when assigning zero-sized things. + return + } + // Make new structure. new := s.newValue0(ssa.OpStructMake, t) @@ -4523,8 +4536,20 @@ func (s *state) assignWhichMayOverlap(left ir.Node, right *ssa.Value, deref bool return } if n != 1 { - s.Fatalf("assigning to non-1-length array") + // This can happen in weird, always-panics cases, like: + // var x [0][2]int + // x[i][j] = 5 + // We know it always panics because the LHS is ssa-able, + // and arrays of length > 1 can't be ssa-able unless + // they are somewhere inside an outer [0]. + // We can ignore the actual assignment, it is dynamically + // unreachable. See issue 77635. + return } + if t.Size() == 0 { + return + } + // Rewrite to a = [1]{v} len := s.constInt(types.Types[types.TINT], 1) s.boundsCheck(i, len, ssa.BoundsIndex, false) // checks i == 0 @@ -4570,6 +4595,9 @@ func (s *state) assignWhichMayOverlap(left ir.Node, right *ssa.Value, deref bool // zeroVal returns the zero value for type t. func (s *state) zeroVal(t *types.Type) *ssa.Value { + if t.Size() == 0 { + return s.entryNewValue0(ssa.OpEmpty, t) + } switch { case t.IsInteger(): switch t.Size() { @@ -4622,13 +4650,8 @@ func (s *state) zeroVal(t *types.Type) *ssa.Value { v.AddArg(s.zeroVal(t.FieldType(i))) } return v - case t.IsArray(): - switch t.NumElem() { - case 0: - return s.entryNewValue0(ssa.OpArrayMake0, t) - case 1: - return s.entryNewValue1(ssa.OpArrayMake1, t, s.zeroVal(t.Elem())) - } + case t.IsArray() && t.NumElem() == 1: + return s.entryNewValue1(ssa.OpArrayMake1, t, s.zeroVal(t.Elem())) case t.IsSIMD(): return s.newValue0(ssa.OpZeroSIMD, t) } @@ -5215,7 +5238,15 @@ func (s *state) addr(n ir.Node) *ssa.Value { } if s.canSSA(n) { - s.Fatalf("addr of canSSA expression: %+v", n) + // This happens in weird, always-panics cases, like: + // var x [0][2]int + // x[i][j] = 5 + // The outer assignment, ...[j] = 5, is a fine + // assignment to do, but requires computing the address + // &x[i], which will always panic when evaluated. + // We just return something reasonable in this case. + // It will be dynamically unreachable. See issue 77635. + return s.newValue1A(ssa.OpAddr, n.Type().PtrTo(), ir.Syms.Zerobase, s.sb) } t := types.NewPtr(n.Type()) @@ -5647,7 +5678,7 @@ func (s *state) storeTypeScalars(t *types.Type, left, right *ssa.Value, skip ski val := s.newValue1I(ssa.OpStructSelect, ft, int64(i), right) s.storeTypeScalars(ft, addr, val, 0) } - case t.IsArray() && t.NumElem() == 0: + case t.IsArray() && t.Size() == 0: // nothing case t.IsArray() && t.NumElem() == 1: s.storeTypeScalars(t.Elem(), left, s.newValue1I(ssa.OpArraySelect, t.Elem(), 0, right), 0) @@ -5687,7 +5718,7 @@ func (s *state) storeTypePtrs(t *types.Type, left, right *ssa.Value) { val := s.newValue1I(ssa.OpStructSelect, ft, int64(i), right) s.storeTypePtrs(ft, addr, val) } - case t.IsArray() && t.NumElem() == 0: + case t.IsArray() && t.Size() == 0: // nothing case t.IsArray() && t.NumElem() == 1: s.storeTypePtrs(t.Elem(), left, s.newValue1I(ssa.OpArraySelect, t.Elem(), 0, right)) diff --git a/src/cmd/compile/internal/test/fixedbugs_test.go b/src/cmd/compile/internal/test/fixedbugs_test.go index b6d3e248ad0c1d..49d4fbb4157d8c 100644 --- a/src/cmd/compile/internal/test/fixedbugs_test.go +++ b/src/cmd/compile/internal/test/fixedbugs_test.go @@ -5,9 +5,12 @@ package test import ( + "bytes" + "internal/platform" "internal/testenv" "os" "path/filepath" + "runtime" "strings" "testing" ) @@ -84,3 +87,46 @@ func Mod32(x uint32) uint32 { return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has unknown Pos } ` + +// Test that building and running a program with -race -gcflags='all=-N -l' +// does not crash. This is a regression test for #77597 where generic functions +// from NoRaceFunc packages (like internal/runtime/atomic) were incorrectly +// getting racefuncenter/racefuncexit instrumentation when instantiated in +// other packages with optimizations disabled. +func TestIssue77597(t *testing.T) { + if !platform.RaceDetectorSupported(runtime.GOOS, runtime.GOARCH) { + t.Skipf("race detector not supported on %s/%s", runtime.GOOS, runtime.GOARCH) + } + testenv.MustHaveGoBuild(t) + testenv.MustHaveGoRun(t) + testenv.MustHaveCGO(t) + + dir := t.TempDir() + src := filepath.Join(dir, "main.go") + err := os.WriteFile(src, []byte(issue77597src), 0644) + if err != nil { + t.Fatalf("could not write file: %v", err) + } + + cmd := testenv.Command(t, testenv.GoToolPath(t), "run", "-race", "-gcflags=all=-N -l", src) + out, err := cmd.CombinedOutput() + if err != nil { + // For details, please refer to CL 160919. + unsupportedVMA := []byte("unsupported VMA range") + if bytes.Contains(out, unsupportedVMA) { + t.Skipf("skipped due to unsupported VMA on %s/%s", runtime.GOOS, runtime.GOARCH) + } else { + t.Fatalf("program failed: %v\n%s", err, out) + } + } +} + +var issue77597src = ` +package main + +import "fmt" + +func main() { + fmt.Println("OK") +} +` diff --git a/src/cmd/compile/internal/test/memoverlap_test.go b/src/cmd/compile/internal/test/memoverlap_test.go new file mode 100644 index 00000000000000..c53288e6bb24ac --- /dev/null +++ b/src/cmd/compile/internal/test/memoverlap_test.go @@ -0,0 +1,41 @@ +// Copyright 2026 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 test + +import "testing" + +const arrFooSize = 96 + +type arrFoo [arrFooSize]int + +//go:noinline +func badCopy(dst, src []int) { + p := (*[arrFooSize]int)(dst[:arrFooSize]) + q := (*[arrFooSize]int)(src[:arrFooSize]) + *p = arrFoo(*q) +} + +//go:noinline +func goodCopy(dst, src []int) { + p := (*[arrFooSize]int)(dst[:arrFooSize]) + q := (*[arrFooSize]int)(src[:arrFooSize]) + *p = *q +} + +func TestOverlapedMoveWithNoopIConv(t *testing.T) { + h1 := make([]int, arrFooSize+1) + h2 := make([]int, arrFooSize+1) + for i := range arrFooSize + 1 { + h1[i] = i + h2[i] = i + } + badCopy(h1[1:], h1[:arrFooSize]) + goodCopy(h2[1:], h2[:arrFooSize]) + for i := range arrFooSize + 1 { + if h1[i] != h2[i] { + t.Errorf("h1 and h2 differ at index %d, expect them to be the same", i) + } + } +} diff --git a/src/cmd/compile/internal/typecheck/expr.go b/src/cmd/compile/internal/typecheck/expr.go index 44a69f03320efd..7dc29636cce76f 100644 --- a/src/cmd/compile/internal/typecheck/expr.go +++ b/src/cmd/compile/internal/typecheck/expr.go @@ -7,7 +7,6 @@ package typecheck import ( "fmt" "go/constant" - "go/token" "internal/types/errors" "strings" @@ -826,18 +825,6 @@ func tcSliceHeader(n *ir.SliceHeaderExpr) ir.Node { n.Len = DefaultLit(Expr(n.Len), types.Types[types.TINT]) n.Cap = DefaultLit(Expr(n.Cap), types.Types[types.TINT]) - if ir.IsConst(n.Len, constant.Int) && ir.Int64Val(n.Len) < 0 { - base.Fatalf("len for OSLICEHEADER must be non-negative") - } - - if ir.IsConst(n.Cap, constant.Int) && ir.Int64Val(n.Cap) < 0 { - base.Fatalf("cap for OSLICEHEADER must be non-negative") - } - - if ir.IsConst(n.Len, constant.Int) && ir.IsConst(n.Cap, constant.Int) && constant.Compare(n.Len.Val(), token.GTR, n.Cap.Val()) { - base.Fatalf("len larger than cap for OSLICEHEADER") - } - return n } diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index 62cd9376927e0d..2da9a17636aae6 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -92,6 +92,7 @@ var bootstrapDirs = []string{ "internal/race", "internal/runtime/gc", "internal/saferio", + "internal/strconv", "internal/syscall/unix", "internal/types/errors", "internal/unsafeheader", diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 023a63059c1fdb..14107c2d8ed9f3 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -11,7 +11,7 @@ require ( golang.org/x/sys v0.39.0 golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec golang.org/x/term v0.38.0 - golang.org/x/tools v0.39.1-0.20260302211140-642dd50cb7cc + golang.org/x/tools v0.39.1-0.20260323181443-4f499ecaa91d ) require ( diff --git a/src/cmd/go.sum b/src/cmd/go.sum index 269fbb17b04e97..c4920417b21b3d 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -22,7 +22,7 @@ golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= -golang.org/x/tools v0.39.1-0.20260302211140-642dd50cb7cc h1:RzEk8N4Q57niCI1HA49wovYfk90ufvZo8j3JA87GZH8= -golang.org/x/tools v0.39.1-0.20260302211140-642dd50cb7cc/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.39.1-0.20260323181443-4f499ecaa91d h1:d9RYG/Z8xQk+tFy5IyhSwUrt4HhSsqHw/uhwmn1KaJg= +golang.org/x/tools v0.39.1-0.20260323181443-4f499ecaa91d/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ= diff --git a/src/cmd/go/internal/cache/cache.go b/src/cmd/go/internal/cache/cache.go index 23cc531e697453..67f8202c06a55c 100644 --- a/src/cmd/go/internal/cache/cache.go +++ b/src/cmd/go/internal/cache/cache.go @@ -385,13 +385,42 @@ func (c *DiskCache) Trim() error { // trim time is too far in the future, attempt the trim anyway. It's possible that // the cache was full when the corruption happened. Attempting a trim on // an empty cache is cheap, so there wouldn't be a big performance hit in that case. - if data, err := lockedfile.Read(filepath.Join(c.dir, "trim.txt")); err == nil { + skipTrim := func(data []byte) bool { if t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64); err == nil { lastTrim := time.Unix(t, 0) if d := now.Sub(lastTrim); d < trimInterval && d > -mtimeInterval { - return nil + return true } } + return false + } + // Check to see if we need a trim. Do this check separately from the lockedfile.Transform + // so that we can skip getting an exclusive lock in the common case. + if data, err := lockedfile.Read(filepath.Join(c.dir, "trim.txt")); err == nil { + if skipTrim(data) { + return nil + } + } + + errFileChanged := errors.New("file changed") + + // Write the new timestamp before we start trimming to reduce the chance that multiple invocations + // try to trim at the same time, causing contention in CI (#76314). + err := lockedfile.Transform(filepath.Join(c.dir, "trim.txt"), func(data []byte) ([]byte, error) { + if skipTrim(data) { + // The timestamp in the file no longer meets the criteria for us to + // do a trim. It must have been updated by another go command invocation + // since we last read it. Skip the trim. + return nil, errFileChanged + } + return fmt.Appendf(nil, "%d", now.Unix()), nil + }) + if errors.Is(err, errors.ErrUnsupported) { + return err + } + if errors.Is(err, errFileChanged) { + // Skip the trim because we don't need it anymore. + return nil } // Trim each of the 256 subdirectories. @@ -403,14 +432,6 @@ func (c *DiskCache) Trim() error { c.trimSubdir(subdir, cutoff) } - // Ignore errors from here: if we don't write the complete timestamp, the - // cache will appear older than it is, and we'll trim it again next time. - var b bytes.Buffer - fmt.Fprintf(&b, "%d", now.Unix()) - if err := lockedfile.Write(filepath.Join(c.dir, "trim.txt"), &b, 0o666); err != nil { - return err - } - return nil } diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 8e302766711ea4..01591c9c9bbceb 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -3455,6 +3455,10 @@ func (b *Builder) swigIntSize(objdir string) (intsize string, err error) { // Run SWIG on one SWIG input file. func (b *Builder) swigOne(a *Action, file, objdir string, pcCFLAGS []string, cxx bool, intgosize string) error { + if strings.HasPrefix(file, "cgo") { + return errors.New("SWIG file must not use prefix 'cgo'") + } + p := a.Package sh := b.Shell(a) diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go index 7c4546fb179663..98645d1173d07a 100644 --- a/src/cmd/link/internal/arm64/asm.go +++ b/src/cmd/link/internal/arm64/asm.go @@ -1281,6 +1281,9 @@ func gensymlate(ctxt *ld.Link, ldr *loader.Loader) { // addLabelSyms adds "label" symbols at s+limit, s+2*limit, etc. addLabelSyms := func(s loader.Sym, limit, sz int64) { + if ldr.SymSect(s) == nil { + log.Fatalf("gensymlate: symbol %s has no section (type=%v)", ldr.SymName(s), ldr.SymType(s)) + } v := ldr.SymValue(s) for off := limit; off < sz; off += limit { p := ldr.LookupOrCreateSym(offsetLabelName(ldr, s, off), ldr.SymVersion(s)) @@ -1332,6 +1335,10 @@ func gensymlate(ctxt *ld.Link, ldr *loader.Loader) { if t >= sym.SDWARFSECT { continue // no need to add label for DWARF symbols } + if ldr.AttrSpecial(s) || !ldr.TopLevelSym(s) { + // no need to add label for special symbols and non-top-level symbols + continue + } sz := ldr.SymSize(s) if sz <= limit { continue diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go index 9049145e225247..c7ba56ad8328ba 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go @@ -315,12 +315,43 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) { curPath := a.pass.Pkg.Path() curFile := astutil.EnclosingFile(curId) id := curId.Node().(*ast.Ident) + + // Find the complete identifier, which may take any of these forms: + // Id + // Id[T] + // Id[K, V] + // pkg.Id + // pkg.Id[T] + // pkg.Id[K, V] + var expr ast.Expr = id + if astutil.IsChildOf(curId, edge.SelectorExpr_Sel) { + curId = curId.Parent() + expr = curId.Node().(ast.Expr) + } + // If expr is part of an IndexExpr or IndexListExpr, we'll need that node. + // Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated. + switch ek, _ := curId.ParentEdge(); ek { + case edge.IndexExpr_X: + expr = curId.Parent().Node().(*ast.IndexExpr) + case edge.IndexListExpr_X: + expr = curId.Parent().Node().(*ast.IndexListExpr) + } + t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier + if targs := t.TypeArgs(); targs.Len() > 0 { + // Instantiate the alias with the type args from this use. + // For example, given type A = M[K, V], compute the type of the use + // A[int, Foo] as M[int, Foo]. + // Don't validate instantiation: it can't panic unless we have a bug, + // in which case seeing the stack trace via telemetry would be helpful. + instAlias, _ := types.Instantiate(nil, alias, slices.Collect(targs.Types()), false) + rhs = instAlias.(*types.Alias).Rhs() + } + // We have an identifier A here (n), possibly qualified by a package // identifier (sel.n), and an inlinable "type A = rhs" elsewhere. // // We can replace A with rhs if no name in rhs is shadowed at n's position, // and every package in rhs is importable by the current package. - var ( importPrefixes = map[string]string{curPath: ""} // from pkg path to prefix edits []analysis.TextEdit @@ -349,6 +380,7 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) { return } else if _, ok := importPrefixes[pkgPath]; !ok { // Use AddImport to add pkgPath if it's not there already. Associate the prefix it assigns + // with the prefix it assigns // with the package path for use by the TypeString qualifier below. prefix, eds := refactor.AddImport( a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), id.Pos()) @@ -356,36 +388,7 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) { edits = append(edits, eds...) } } - // Find the complete identifier, which may take any of these forms: - // Id - // Id[T] - // Id[K, V] - // pkg.Id - // pkg.Id[T] - // pkg.Id[K, V] - var expr ast.Expr = id - if astutil.IsChildOf(curId, edge.SelectorExpr_Sel) { - curId = curId.Parent() - expr = curId.Node().(ast.Expr) - } - // If expr is part of an IndexExpr or IndexListExpr, we'll need that node. - // Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated. - switch ek, _ := curId.ParentEdge(); ek { - case edge.IndexExpr_X: - expr = curId.Parent().Node().(*ast.IndexExpr) - case edge.IndexListExpr_X: - expr = curId.Parent().Node().(*ast.IndexListExpr) - } - t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier - if targs := t.TypeArgs(); targs.Len() > 0 { - // Instantiate the alias with the type args from this use. - // For example, given type A = M[K, V], compute the type of the use - // A[int, Foo] as M[int, Foo]. - // Don't validate instantiation: it can't panic unless we have a bug, - // in which case seeing the stack trace via telemetry would be helpful. - instAlias, _ := types.Instantiate(nil, alias, slices.Collect(targs.Types()), false) - rhs = instAlias.(*types.Alias).Rhs() - } + // To get the replacement text, render the alias RHS using the package prefixes // we assigned above. newText := types.TypeString(rhs, func(p *types.Package) string { diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index cb5b826075bd17..4e2260af522911 100644 --- a/src/cmd/vendor/modules.txt +++ b/src/cmd/vendor/modules.txt @@ -73,7 +73,7 @@ golang.org/x/text/internal/tag golang.org/x/text/language golang.org/x/text/transform golang.org/x/text/unicode/norm -# golang.org/x/tools v0.39.1-0.20260302211140-642dd50cb7cc +# golang.org/x/tools v0.39.1-0.20260323181443-4f499ecaa91d ## explicit; go 1.24.0 golang.org/x/tools/cmd/bisect golang.org/x/tools/cover diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index 9c662ef8f67c0e..cfadec68f195b8 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -1363,7 +1363,7 @@ func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error { } newSecret := cipherSuite.nextTrafficSecret(c.in.trafficSecret) - if err := c.setReadTrafficSecret(cipherSuite, QUICEncryptionLevelInitial, newSecret); err != nil { + if err := c.setReadTrafficSecret(cipherSuite, QUICEncryptionLevelInitial, newSecret, keyUpdate.updateRequested); err != nil { return err } @@ -1683,12 +1683,16 @@ func (c *Conn) VerifyHostname(host string) error { // setReadTrafficSecret sets the read traffic secret for the given encryption level. If // being called at the same time as setWriteTrafficSecret, the caller must ensure the call // to setWriteTrafficSecret happens first so any alerts are sent at the write level. -func (c *Conn) setReadTrafficSecret(suite *cipherSuiteTLS13, level QUICEncryptionLevel, secret []byte) error { +func (c *Conn) setReadTrafficSecret(suite *cipherSuiteTLS13, level QUICEncryptionLevel, secret []byte, locked bool) error { // Ensure that there are no buffered handshake messages before changing the // read keys, since that can cause messages to be parsed that were encrypted // using old keys which are no longer appropriate. if c.hand.Len() != 0 { - c.sendAlert(alertUnexpectedMessage) + if locked { + c.sendAlertLocked(alertUnexpectedMessage) + } else { + c.sendAlert(alertUnexpectedMessage) + } return errors.New("tls: handshake buffer not empty before setting read traffic secret") } c.in.setTrafficSecret(suite, level, secret) diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go index 77a24b4a78d8fc..65177767a05b1f 100644 --- a/src/crypto/tls/handshake_client_tls13.go +++ b/src/crypto/tls/handshake_client_tls13.go @@ -492,7 +492,7 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { clientSecret := handshakeSecret.ClientHandshakeTrafficSecret(hs.transcript) c.setWriteTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret) serverSecret := handshakeSecret.ServerHandshakeTrafficSecret(hs.transcript) - if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret); err != nil { + if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret, false); err != nil { return err } @@ -711,7 +711,7 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error { hs.trafficSecret = hs.masterSecret.ClientApplicationTrafficSecret(hs.transcript) serverSecret := hs.masterSecret.ServerApplicationTrafficSecret(hs.transcript) - if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret); err != nil { + if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret, false); err != nil { return err } diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index bce94ed2d8c436..b45d7cbc537ffe 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -752,7 +752,7 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error { serverSecret := hs.handshakeSecret.ServerHandshakeTrafficSecret(hs.transcript) c.setWriteTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret) clientSecret := hs.handshakeSecret.ClientHandshakeTrafficSecret(hs.transcript) - if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret); err != nil { + if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret, false); err != nil { return err } @@ -1136,7 +1136,7 @@ func (hs *serverHandshakeStateTLS13) readClientFinished() error { return errors.New("tls: invalid client finished hash") } - if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret); err != nil { + if err := c.setReadTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret, false); err != nil { return err } diff --git a/src/crypto/tls/handshake_test.go b/src/crypto/tls/handshake_test.go index 9cea8182d04154..6e00b4348e6038 100644 --- a/src/crypto/tls/handshake_test.go +++ b/src/crypto/tls/handshake_test.go @@ -778,3 +778,51 @@ func concatHandshakeMessages(msgs ...handshakeMessage) ([]byte, error) { outBuf = append(outBuf, marshalled...) return outBuf, nil } + +func TestMultipleKeyUpdate(t *testing.T) { + for _, requestUpdate := range []bool{true, false} { + t.Run(fmt.Sprintf("requestUpdate=%t", requestUpdate), func(t *testing.T) { + + c, s := localPipe(t) + cfg := testConfig.Clone() + cfg.MinVersion = VersionTLS13 + cfg.MaxVersion = VersionTLS13 + client := Client(c, testConfig) + server := Server(s, testConfig) + + clientHandshakeDone := make(chan struct{}) + go func() { + if err := client.Handshake(); err != nil { + } + close(clientHandshakeDone) + io.Copy(io.Discard, server) + }() + + if err := server.Handshake(); err != nil { + t.Fatalf("server handshake failed: %v\n", err) + } + <-clientHandshakeDone + + c.SetReadDeadline(time.Now().Add(1 * time.Second)) + s.SetReadDeadline(time.Now().Add(1 * time.Second)) + + kuMsg, err := (&keyUpdateMsg{updateRequested: requestUpdate}).marshal() + if err != nil { + t.Fatalf("failed to marshal key update message: %v", err) + } + + client.out.Lock() + if _, err := client.writeRecordLocked(recordTypeHandshake, append(kuMsg, kuMsg...)); err != nil { + t.Fatalf("failed to write key update messages: %v", err) + } + client.out.Unlock() + + _, err = io.Copy(io.Discard, client) + if err == nil { + t.Fatal("expected multiple key update messages to cause an error, got nil") + } else if !strings.HasSuffix(err.Error(), "tls: unexpected message") { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} diff --git a/src/crypto/x509/constraints.go b/src/crypto/x509/constraints.go index 83bfbcb2ef2e7f..b7eb1e0f57d866 100644 --- a/src/crypto/x509/constraints.go +++ b/src/crypto/x509/constraints.go @@ -351,6 +351,7 @@ func newDNSConstraints(l []string, permitted bool) interface{ query(string) (str if !permitted { parentConstraints := map[string]string{} for _, name := range nc.constraints.set { + name = strings.ToLower(name) trimmedName := trimFirstLabel(name) if trimmedName == "" { continue @@ -376,6 +377,7 @@ func (dnc *dnsConstraints) query(s string) (string, bool) { } if !dnc.permitted && len(s) > 0 && s[0] == '*' { + s = strings.ToLower(s) trimmed := trimFirstLabel(s) if constraint, found := dnc.parentConstraints[trimmed]; found { return constraint, true diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go index 3e205e5caf44e9..97a2420a7f2e7b 100644 --- a/src/crypto/x509/name_constraints_test.go +++ b/src/crypto/x509/name_constraints_test.go @@ -1656,6 +1656,28 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:"}, }, }, + { + name: "subdomain exclusion blocks uppercase wildcard", + roots: []constraintsSpec{{ + bad: []string{"dns:sub.example.com"}, + }}, + intermediates: [][]constraintsSpec{{{}}}, + leaf: leafSpec{ + sans: []string{"dns:*.EXAMPLE.COM"}, + }, + expectedError: "\"*.EXAMPLE.COM\" is excluded by constraint \"sub.example.com\"", + }, + { + name: "uppercase subdomain exclusion blocks lowercase wildcard", + roots: []constraintsSpec{{ + bad: []string{"dns:SUB.EXAMPLE.COM"}, + }}, + intermediates: [][]constraintsSpec{{{}}}, + leaf: leafSpec{ + sans: []string{"dns:*.example.com"}, + }, + expectedError: "\"*.example.com\" is excluded by constraint \"sub.example.com\"", + }, } func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index 3d9c115dba9df3..8151a731253c67 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -720,6 +720,8 @@ func alreadyInChain(candidate *Certificate, chain []*Certificate) bool { // for failed checks due to different intermediates having the same Subject. const maxChainSignatureChecks = 100 +var errSignatureLimit = errors.New("x509: signature check attempts limit reached while verifying certificate chain") + func (c *Certificate) buildChains(currentChain []*Certificate, sigChecks *int, opts *VerifyOptions) (chains [][]*Certificate, err error) { var ( hintErr error @@ -727,16 +729,16 @@ func (c *Certificate) buildChains(currentChain []*Certificate, sigChecks *int, o ) considerCandidate := func(certType int, candidate potentialParent) { - if candidate.cert.PublicKey == nil || alreadyInChain(candidate.cert, currentChain) { - return - } - if sigChecks == nil { sigChecks = new(int) } *sigChecks++ if *sigChecks > maxChainSignatureChecks { - err = errors.New("x509: signature check attempts limit reached while verifying certificate chain") + err = errSignatureLimit + return + } + + if candidate.cert.PublicKey == nil || alreadyInChain(candidate.cert, currentChain) { return } @@ -777,11 +779,20 @@ func (c *Certificate) buildChains(currentChain []*Certificate, sigChecks *int, o } } - for _, root := range opts.Roots.findPotentialParents(c) { - considerCandidate(rootCertificate, root) - } - for _, intermediate := range opts.Intermediates.findPotentialParents(c) { - considerCandidate(intermediateCertificate, intermediate) +candidateLoop: + for _, parents := range []struct { + certType int + potentials []potentialParent + }{ + {rootCertificate, opts.Roots.findPotentialParents(c)}, + {intermediateCertificate, opts.Intermediates.findPotentialParents(c)}, + } { + for _, parent := range parents.potentials { + considerCandidate(parents.certType, parent) + if err == errSignatureLimit { + break candidateLoop + } + } } if len(chains) > 0 { @@ -1284,12 +1295,12 @@ func policiesValid(chain []*Certificate, opts VerifyOptions) bool { } else { // 6.1.4 (b) (3) (i) -- as updated by RFC 9618 pg.deleteLeaf(mapping.IssuerDomainPolicy) - - // 6.1.4 (b) (3) (ii) -- as updated by RFC 9618 - pg.prune() } } + // 6.1.4 (b) (3) (ii) -- as updated by RFC 9618 + pg.prune() + for issuerStr, subjectPolicies := range mappings { // 6.1.4 (b) (1) -- as updated by RFC 9618 if matching := pg.leafWithPolicy(OID{der: []byte(issuerStr)}); matching != nil { diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index f9bd313737e21b..1d3e845d0f0a9c 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -1529,10 +1529,13 @@ func TestValidHostname(t *testing.T) { } } -func generateCert(cn string, isCA bool, issuer *Certificate, issuerKey crypto.PrivateKey) (*Certificate, crypto.PrivateKey, error) { - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, nil, err +func generateCert(cn string, isCA bool, issuer *Certificate, issuerKey crypto.PrivateKey, priv crypto.PrivateKey) (*Certificate, crypto.PrivateKey, error) { + if priv == nil { + var err error + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, err + } } serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) @@ -1543,6 +1546,7 @@ func generateCert(cn string, isCA bool, issuer *Certificate, issuerKey crypto.Pr Subject: pkix.Name{CommonName: cn}, NotBefore: time.Now().Add(-1 * time.Hour), NotAfter: time.Now().Add(24 * time.Hour), + DNSNames: []string{rand.Text()}, KeyUsage: KeyUsageKeyEncipherment | KeyUsageDigitalSignature | KeyUsageCertSign, ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, @@ -1554,7 +1558,7 @@ func generateCert(cn string, isCA bool, issuer *Certificate, issuerKey crypto.Pr issuerKey = priv } - derBytes, err := CreateCertificate(rand.Reader, template, issuer, priv.Public(), issuerKey) + derBytes, err := CreateCertificate(rand.Reader, template, issuer, priv.(crypto.Signer).Public(), issuerKey) if err != nil { return nil, nil, err } @@ -1566,81 +1570,77 @@ func generateCert(cn string, isCA bool, issuer *Certificate, issuerKey crypto.Pr return cert, priv, nil } -func TestPathologicalChain(t *testing.T) { - if testing.Short() { - t.Skip("skipping generation of a long chain of certificates in short mode") - } - - // Build a chain where all intermediates share the same subject, to hit the - // path building worst behavior. - roots, intermediates := NewCertPool(), NewCertPool() - - parent, parentKey, err := generateCert("Root CA", true, nil, nil) - if err != nil { - t.Fatal(err) - } - roots.AddCert(parent) - - for i := 1; i < 100; i++ { - parent, parentKey, err = generateCert("Intermediate CA", true, parent, parentKey) - if err != nil { - t.Fatal(err) - } - intermediates.AddCert(parent) - } - - leaf, _, err := generateCert("Leaf", false, parent, parentKey) - if err != nil { - t.Fatal(err) - } - - start := time.Now() - _, err = leaf.Verify(VerifyOptions{ - Roots: roots, - Intermediates: intermediates, - }) - t.Logf("verification took %v", time.Since(start)) - - if err == nil || !strings.Contains(err.Error(), "signature check attempts limit") { - t.Errorf("expected verification to fail with a signature checks limit error; got %v", err) - } -} - -func TestLongChain(t *testing.T) { +func TestPathologicalChains(t *testing.T) { if testing.Short() { - t.Skip("skipping generation of a long chain of certificates in short mode") + t.Skip("skipping generation of a long chains of certificates in short mode") } - roots, intermediates := NewCertPool(), NewCertPool() + // Test four pathological cases, where the intermediates in the chain have + // the same/different subjects and the same/different keys. This covers a + // number of cases where the chain building algorithm might be inefficient, + // such as when there are many intermediates with the same subject but + // different keys, many intermediates with the same key but different + // subjects, many intermediates with the same subject and key, or many + // intermediates with different subjects and keys. + // + // The worst case for our algorithm is when all of the intermediates share + // both subject and key, in which case all of the intermediates appear to + // have signed each other, causing us to see a large number of potential + // parents for each intermediate. + // + // All of these cases, Certificate.Verify should return errSignatureLimit. + // + // In all cases, don't have a root in the pool, so a valid chain cannot actually be built. - parent, parentKey, err := generateCert("Root CA", true, nil, nil) - if err != nil { - t.Fatal(err) - } - roots.AddCert(parent) + for _, test := range []struct { + sameSubject bool + sameKey bool + }{ + {sameSubject: false, sameKey: false}, + {sameSubject: true, sameKey: false}, + {sameSubject: false, sameKey: true}, + {sameSubject: true, sameKey: true}, + } { + t.Run(fmt.Sprintf("sameSubject=%t,sameKey=%t", test.sameSubject, test.sameKey), func(t *testing.T) { + intermediates := NewCertPool() + + var intermediateKey crypto.PrivateKey + if test.sameKey { + var err error + intermediateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + } - for i := 1; i < 15; i++ { - name := fmt.Sprintf("Intermediate CA #%d", i) - parent, parentKey, err = generateCert(name, true, parent, parentKey) - if err != nil { - t.Fatal(err) - } - intermediates.AddCert(parent) - } + var leafSigner crypto.PrivateKey + var intermediate *Certificate + for i := range 100 { + cn := "Intermediate CA" + if !test.sameSubject { + cn += fmt.Sprintf(" #%d", i) + } + var err error + intermediate, leafSigner, err = generateCert(cn, true, intermediate, leafSigner, intermediateKey) + if err != nil { + t.Fatal(err) + } + intermediates.AddCert(intermediate) + } - leaf, _, err := generateCert("Leaf", false, parent, parentKey) - if err != nil { - t.Fatal(err) - } + leaf, _, err := generateCert("Leaf", false, intermediate, leafSigner, nil) + if err != nil { + t.Fatal(err) + } - start := time.Now() - if _, err := leaf.Verify(VerifyOptions{ - Roots: roots, - Intermediates: intermediates, - }); err != nil { - t.Error(err) + start := time.Now() + _, err = leaf.Verify(VerifyOptions{ + Roots: NewCertPool(), + Intermediates: intermediates, + }) + t.Logf("verification took %v", time.Since(start)) + }) } - t.Logf("verification took %v", time.Since(start)) } func TestSystemRootsError(t *testing.T) { diff --git a/src/html/template/context.go b/src/html/template/context.go index 8b3af2feabd8aa..132ae2d28dc980 100644 --- a/src/html/template/context.go +++ b/src/html/template/context.go @@ -6,6 +6,7 @@ package template import ( "fmt" + "slices" "text/template/parse" ) @@ -37,7 +38,7 @@ func (c context) String() string { if c.err != nil { err = c.err } - return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, err) + return fmt.Sprintf("{%v %v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.jsBraceDepth, c.attr, c.element, err) } // eq reports whether two contexts are equal. @@ -46,6 +47,7 @@ func (c context) eq(d context) bool { c.delim == d.delim && c.urlPart == d.urlPart && c.jsCtx == d.jsCtx && + slices.Equal(c.jsBraceDepth, d.jsBraceDepth) && c.attr == d.attr && c.element == d.element && c.err == d.err @@ -68,6 +70,9 @@ func (c context) mangle(templateName string) string { if c.jsCtx != jsCtxRegexp { s += "_" + c.jsCtx.String() } + if c.jsBraceDepth != nil { + s += fmt.Sprintf("_jsBraceDepth(%v)", c.jsBraceDepth) + } if c.attr != attrNone { s += "_" + c.attr.String() } @@ -77,6 +82,13 @@ func (c context) mangle(templateName string) string { return s } +// clone returns a copy of c with the same field values. +func (c context) clone() context { + clone := c + clone.jsBraceDepth = slices.Clone(c.jsBraceDepth) + return clone +} + // state describes a high-level HTML parser state. // // It bounds the top of the element stack, and by extension the HTML insertion diff --git a/src/html/template/escape.go b/src/html/template/escape.go index d8e1b8cb547db2..e18fa3aa7382bd 100644 --- a/src/html/template/escape.go +++ b/src/html/template/escape.go @@ -523,7 +523,7 @@ func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) if nodeName == "range" { e.rangeContext = &rangeContext{outer: e.rangeContext} } - c0 := e.escapeList(c, n.List) + c0 := e.escapeList(c.clone(), n.List) if nodeName == "range" { if c0.state != stateError { c0 = joinRange(c0, e.rangeContext) @@ -554,7 +554,7 @@ func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) return c0 } } - c1 := e.escapeList(c, n.ElseList) + c1 := e.escapeList(c.clone(), n.ElseList) return join(c0, c1, n, nodeName) } diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go index 49710c38b7265b..126dc22f330576 100644 --- a/src/html/template/escape_test.go +++ b/src/html/template/escape_test.go @@ -1185,6 +1185,18 @@ func TestErrors(t *testing.T) { // html is allowed since it is the last command in the pipeline, but urlquery is not. `predefined escaper "urlquery" disallowed in template`, }, + { + "", + ``, + }, } for _, test := range tests { buf := new(bytes.Buffer) @@ -1756,7 +1768,7 @@ func TestEscapeText(t *testing.T) { }, { "