]> git.feebdaed.xyz Git - 0xmirror/go.git/commitdiff
go/types, types2: add check for finite size at value observance
authorMark Freeman <mark@golang.org>
Wed, 3 Dec 2025 20:14:22 +0000 (15:14 -0500)
committerGopher Robot <gobot@golang.org>
Tue, 9 Dec 2025 16:51:02 +0000 (08:51 -0800)
Each type must be representable by a finite amount of Go source code
after replacing all alias type names, value names, and embedded
interfaces (per #56103) with the RHS from their respective declarations
("expansion"); otherwise the type is invalid.

Furthermore, each type must have a finite size.

Checking for finite source after expansion is handled in decl.go.
Checking for finite size is handled in validtype.go and is delayed to
the end of type checking (unless used in unsafe.Sizeof, in which case
it is computed eagerly).

We can only construct values of valid types. Thus, while a type is
pending (on the object path and thus not yet valid), it cannot construct
a value of its own type (directly or indirectly).

This change enforces the indirect case by validating each type at value
observance (and hence upholding the invariant that values of only valid
types are created). Validation is cached on Named types to avoid
duplicate work.

As an example, consider:

  type A [unsafe.Sizeof(B{})]int
  type B A

Clearly, since there are no aliases, A and B have finite source. At the
site of B{}, B will be checked for finite size, recursing down the
values of B, at which point A is seen. Since A is on the object path,
there is a cycle preventing B from being proven valid before B{},
violating our invariant.

Note that this change also works for generic types.

Fixes #75918
Fixes #76478

Change-Id: I76d493b5da9571780fed4b3c76803750ec184818
Reviewed-on: https://go-review.googlesource.com/c/go/+/726580
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Mark Freeman <markfreeman@google.com>

src/cmd/compile/internal/types2/cycles.go
src/cmd/compile/internal/types2/expr.go
src/cmd/compile/internal/types2/named.go
src/cmd/compile/internal/types2/sizeof_test.go
src/go/types/cycles.go
src/go/types/expr.go
src/go/types/named.go
src/go/types/sizeof_test.go
src/internal/types/testdata/fixedbugs/issue52915.go
src/internal/types/testdata/fixedbugs/issue75918.go [new file with mode: 0644]
src/internal/types/testdata/fixedbugs/issue76478.go [new file with mode: 0644]

index b916219c979801d6d0a5b56b3ea7e75b7eca2e0f..f6721fa593f2efc273b5b1e5b617907c3716513a 100644 (file)
@@ -102,3 +102,43 @@ func (check *Checker) directCycle(tname *TypeName, pathIdx map[*TypeName]int) {
                }
        }
 }
+
+// TODO(markfreeman): Can the value cached on Named be used in validType / hasVarSize?
+
+// finiteSize returns whether a type has finite size.
+func (check *Checker) finiteSize(t Type) (b bool) {
+       switch t := Unalias(t).(type) {
+       case *Named:
+               if t.finite != nil {
+                       return *t.finite
+               }
+
+               defer func() {
+                       t.finite = &b
+               }()
+
+               if i, ok := check.objPathIdx[t.obj]; ok {
+                       cycle := check.objPath[i:]
+                       check.cycleError(cycle, firstInSrc(cycle))
+                       return false
+               }
+               check.push(t.obj)
+               defer check.pop()
+
+               return check.finiteSize(t.fromRHS)
+
+       case *Array:
+               // The array length is already computed. If it was a valid length, it
+               // is finite; else, an error was reported in the computation.
+               return check.finiteSize(t.elem)
+
+       case *Struct:
+               for _, f := range t.fields {
+                       if !check.finiteSize(f.typ) {
+                               return false
+                       }
+               }
+       }
+
+       return true
+}
index 637cbaee5d3ee63a5f926c9d868932199e90ca19..e3ef1af1ce35ca33f1aedab4bbffbb1a576653d4 100644 (file)
@@ -1041,12 +1041,9 @@ func (check *Checker) pendingType(x *operand) {
        if x.mode == invalid || x.mode == novalue {
                return
        }
-       if n, ok := Unalias(x.typ).(*Named); ok {
-               if i, ok := check.objPathIdx[n.obj]; ok {
-                       check.cycleError(check.objPath, i)
-                       x.mode = invalid
-                       x.typ = Typ[Invalid]
-               }
+       if !check.finiteSize(x.typ) {
+               x.mode = invalid
+               x.typ = Typ[Invalid]
        }
 }
 
index edd6357248ec792a48be782ba1764805549a9647..6d5ff976c35a157615cbc5e4cddf283147d08689 100644 (file)
@@ -110,7 +110,8 @@ type Named struct {
        allowNilRHS        bool // same as below, as well as briefly during checking of a type declaration
        allowNilUnderlying bool // may be true from creation via [NewNamed] until [Named.SetUnderlying]
 
-       inst *instance // information for instantiated types; nil otherwise
+       inst   *instance // information for instantiated types; nil otherwise
+       finite *bool     // whether the type has finite size, or nil
 
        mu         sync.Mutex     // guards all fields below
        state_     uint32         // the current state of this type; must only be accessed atomically or when mu is held
index f206c12fc3d7a99212519049e43757a7e6432b30..a3697b666c6f8557a9d40d929e64c0b2be20f15a 100644 (file)
@@ -31,7 +31,7 @@ func TestSizeof(t *testing.T) {
                {Interface{}, 40, 80},
                {Map{}, 16, 32},
                {Chan{}, 12, 24},
-               {Named{}, 64, 120},
+               {Named{}, 68, 128},
                {TypeParam{}, 28, 48},
                {term{}, 12, 24},
 
index bd894258b121902c90a1fa1dfcc54d62e8c9c493..ca4d5f7744ce577c97083f300f55bb42c4f50ed7 100644 (file)
@@ -105,3 +105,43 @@ func (check *Checker) directCycle(tname *TypeName, pathIdx map[*TypeName]int) {
                }
        }
 }
+
+// TODO(markfreeman): Can the value cached on Named be used in validType / hasVarSize?
+
+// finiteSize returns whether a type has finite size.
+func (check *Checker) finiteSize(t Type) (b bool) {
+       switch t := Unalias(t).(type) {
+       case *Named:
+               if t.finite != nil {
+                       return *t.finite
+               }
+
+               defer func() {
+                       t.finite = &b
+               }()
+
+               if i, ok := check.objPathIdx[t.obj]; ok {
+                       cycle := check.objPath[i:]
+                       check.cycleError(cycle, firstInSrc(cycle))
+                       return false
+               }
+               check.push(t.obj)
+               defer check.pop()
+
+               return check.finiteSize(t.fromRHS)
+
+       case *Array:
+               // The array length is already computed. If it was a valid length, it
+               // is finite; else, an error was reported in the computation.
+               return check.finiteSize(t.elem)
+
+       case *Struct:
+               for _, f := range t.fields {
+                       if !check.finiteSize(f.typ) {
+                               return false
+                       }
+               }
+       }
+
+       return true
+}
index 09f7cdda802bad365d68f3556ce2b67ec8e9c68e..6ff5695390d1c549922c55b04ac64b235749d85d 100644 (file)
@@ -1033,12 +1033,9 @@ func (check *Checker) pendingType(x *operand) {
        if x.mode == invalid || x.mode == novalue {
                return
        }
-       if n, ok := Unalias(x.typ).(*Named); ok {
-               if i, ok := check.objPathIdx[n.obj]; ok {
-                       check.cycleError(check.objPath, i)
-                       x.mode = invalid
-                       x.typ = Typ[Invalid]
-               }
+       if !check.finiteSize(x.typ) {
+               x.mode = invalid
+               x.typ = Typ[Invalid]
        }
 }
 
index be6a0f54267c0268a6aa2d1132541109f0f813f4..ba0f92015d4715aa7fc46f4af51f0165849b55c9 100644 (file)
@@ -113,7 +113,8 @@ type Named struct {
        allowNilRHS        bool // same as below, as well as briefly during checking of a type declaration
        allowNilUnderlying bool // may be true from creation via [NewNamed] until [Named.SetUnderlying]
 
-       inst *instance // information for instantiated types; nil otherwise
+       inst   *instance // information for instantiated types; nil otherwise
+       finite *bool     // whether the type has finite size, or nil
 
        mu         sync.Mutex     // guards all fields below
        state_     uint32         // the current state of this type; must only be accessed atomically or when mu is held
index 694ab32462238ee6d50569a795a3dbdfa91b97a2..2f1859551709df987509625097b2740dcd99878e 100644 (file)
@@ -30,7 +30,7 @@ func TestSizeof(t *testing.T) {
                {Interface{}, 40, 80},
                {Map{}, 16, 32},
                {Chan{}, 12, 24},
-               {Named{}, 64, 120},
+               {Named{}, 68, 128},
                {TypeParam{}, 28, 48},
                {term{}, 12, 24},
 
index 70dc6643759d54688088098161b57b98201c3cd6..e60c1767e98aefc6a8831913eac08d0dc60f9332 100644 (file)
@@ -18,6 +18,4 @@ func _[P any]() {
        _ = unsafe.Sizeof(struct{ T[P] }{})
 }
 
-// TODO(gri) This is a follow-on error due to T[int] being invalid.
-//           We should try to avoid it.
-const _ = unsafe /* ERROR "not constant" */ .Sizeof(T[int]{})
+const _ = unsafe.Sizeof(T /* ERROR "invalid recursive type" */ [int]{})
diff --git a/src/internal/types/testdata/fixedbugs/issue75918.go b/src/internal/types/testdata/fixedbugs/issue75918.go
new file mode 100644 (file)
index 0000000..373db4a
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p
+
+import "unsafe"
+
+type A /* ERROR "invalid recursive type" */ [unsafe.Sizeof(S{})]byte
+
+type S struct {
+       a A
+}
diff --git a/src/internal/types/testdata/fixedbugs/issue76478.go b/src/internal/types/testdata/fixedbugs/issue76478.go
new file mode 100644 (file)
index 0000000..f16b40e
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p
+
+import "unsafe"
+
+type A /* ERROR "invalid recursive type" */ [unsafe.Sizeof(B{})]int
+type B A
+
+type C /* ERROR "invalid recursive type" */ [unsafe.Sizeof(f())]int
+func f() D {
+       return D{}
+}
+type D C
+
+type E /* ERROR "invalid recursive type" */ [unsafe.Sizeof(g[F]())]int
+func g[P any]() P {
+       panic(0)
+}
+type F struct {
+       f E
+}
+