cmd/compile: fix mis-compilation when switching over channels
CL 418101 changes Unified IR writer to force mixed tag/case to have
common type, emitting the implicit conversion if any of the case values
are not assignble to the tag value's type.
However, the Go spec definition of equality is non-transitive for
channels stored in interfaces, causing incorrect behavior with channel
values comparison.
To fix it, just stripping these implicit conversions.
Fixes #67190
diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go
index 97865bb..987488b 100644
--- a/src/cmd/compile/internal/noder/reader.go
+++ b/src/cmd/compile/internal/noder/reader.go
@@ -2012,6 +2012,21 @@
rtypes = append(rtypes, reflectdata.TypePtrAt(cas.Pos(), types.Types[types.TBOOL]))
}
}
+ } else {
+ // Unified IR writer ensures mixed tag/case always have common type, rely on the fact
+ // that `any(x) == any(y)` yields the same value as `x == y`, if they are not nil.
+ //
+ // However, the Go spec definition of equality is non-transitive for channels stored
+ // in interfaces (see issue #44051). So undoing the implicit conversions emitted by
+ // the writer to ensure the correct channel comparison (see issue #44051).
+ if conv, ok := tag.(*ir.ConvExpr); ok && conv.Implicit() && conv.Type().IsEmptyInterface() {
+ if x := conv.X; x.Type().IsChan() {
+ tag = x
+ for i, cas := range cases {
+ cases[i] = cas.(*ir.ConvExpr).X
+ }
+ }
+ }
}
}
diff --git a/test/fixedbugs/issue67190.go b/test/fixedbugs/issue67190.go
new file mode 100644
index 0000000..c19b248
--- /dev/null
+++ b/test/fixedbugs/issue67190.go
@@ -0,0 +1,24 @@
+// run
+
+// Copyright 2024 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
+
+func main() {
+ ch1 := make(chan struct{})
+ var ch2 <-chan struct{} = ch1
+
+ switch ch1 {
+ case ch2:
+ default:
+ panic("bad narrow case")
+ }
+
+ switch ch2 {
+ case ch1:
+ default:
+ panic("bad narrow switch")
+ }
+}
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Commit-Queue | +1 |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Code-Review | +2 |
are not assignble to the tag value's type.
assignable
// in interfaces (see issue #44051). So undoing the implicit conversions emitted by
undo
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
thx, fixedbugs files looks good, idk about noder enough to have an opinion.
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Auto-Submit | +1 |
Commit-Queue | +1 |
are not assignble to the tag value's type.
Cuong Manh Leassignable
Done
// in interfaces (see issue #44051). So undoing the implicit conversions emitted by
Cuong Manh Leundo
Done
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
// Unified IR writer ensures mixed tag/case always have common type, rely on the fact
I think I'd rather fix this in the writer than undo its work in the reader.
The writer might just need (line 1662)
// We need to keep comparisons of channel values from
// being wrapped in any(). See issue 67190.
if _, ok := tagType.Underlying().(*types2.Chan); ok { continue }
cases[i] = cas.(*ir.ConvExpr).X
In particular, I'm worried about here. How do we know there isn't some other reason why the tag has an implicit conversion, but doesn't make each case also be an implicit conversion? (I couldn't find a case where that could happen, but still makes me nervous.)
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
// Unified IR writer ensures mixed tag/case always have common type, rely on the fact
I think I'd rather fix this in the writer than undo its work in the reader.
The writer might just need (line 1662)
// We need to keep comparisons of channel values from
// being wrapped in any(). See issue 67190.
if _, ok := tagType.Underlying().(*types2.Chan); ok { continue }
Hmm, might not enough, since we still do the implicit conversion at L1669, causing the fatal at L2268.
cases[i] = cas.(*ir.ConvExpr).X
In particular, I'm worried about here. How do we know there isn't some other reason why the tag has an implicit conversion, but doesn't make each case also be an implicit conversion? (I couldn't find a case where that could happen, but still makes me nervous.)
Hmm,the writer always make the implicit comversion, so that mismatch won't happen.
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
// Unified IR writer ensures mixed tag/case always have common type, rely on the fact
Cuong Manh LeI think I'd rather fix this in the writer than undo its work in the reader.
The writer might just need (line 1662)
// We need to keep comparisons of channel values from
// being wrapped in any(). See issue 67190.
if _, ok := tagType.Underlying().(*types2.Chan); ok { continue }
Hmm, might not enough, since we still do the implicit conversion at L1669, causing the fatal at L2268.
If we skip the assignment to tagType, then it will always be the type of tag, so the implicit conversion would always work (or could be skipped, that would be fine also).
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Auto-Submit | +1 |
Commit-Queue | +1 |
// Unified IR writer ensures mixed tag/case always have common type, rely on the fact
Cuong Manh LeI think I'd rather fix this in the writer than undo its work in the reader.
The writer might just need (line 1662)
// We need to keep comparisons of channel values from
// being wrapped in any(). See issue 67190.
if _, ok := tagType.Underlying().(*types2.Chan); ok { continue }
Keith RandallHmm, might not enough, since we still do the implicit conversion at L1669, causing the fatal at L2268.
If we skip the assignment to tagType, then it will always be the type of tag, so the implicit conversion would always work (or could be skipped, that would be fine also).
Done
cases[i] = cas.(*ir.ConvExpr).X
Cuong Manh LeIn particular, I'm worried about here. How do we know there isn't some other reason why the tag has an implicit conversion, but doesn't make each case also be an implicit conversion? (I couldn't find a case where that could happen, but still makes me nervous.)
Hmm,the writer always make the implicit comversion, so that mismatch won't happen.
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
if casType := w.p.typeOf(cas); !types2.AssignableTo(casType, tagType) {
I think the wrapping in "any" is only needed if either casType or tagType is an interface. And I think it is more clearly ok to do so in that situation, as == in that situation is implemented by upconverting to an interface anyway.
So maybe we can just add && (types2.IsInterface(casType) || types2.IsInterface(tagType)) here.
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
if casType := w.p.typeOf(cas); !types2.AssignableTo(casType, tagType) {
I think the wrapping in "any" is only needed if either casType or tagType is an interface. And I think it is more clearly ok to do so in that situation, as == in that situation is implemented by upconverting to an interface anyway.
So maybe we can just add && (types2.IsInterface(casType) || types2.IsInterface(tagType)) here.
Passes all.bash for me.
if casType := w.p.typeOf(cas); !types2.AssignableTo(casType, tagType) {
Keith RandallI think the wrapping in "any" is only needed if either casType or tagType is an interface. And I think it is more clearly ok to do so in that situation, as == in that situation is implemented by upconverting to an interface anyway.
So maybe we can just add && (types2.IsInterface(casType) || types2.IsInterface(tagType)) here.
Passes all.bash for me.
Should we add this change in this CL or a follow up? A follow up CL seems to be better in case we need to revert.
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Commit-Queue | +1 |
if casType := w.p.typeOf(cas); !types2.AssignableTo(casType, tagType) {
Keith RandallI think the wrapping in "any" is only needed if either casType or tagType is an interface. And I think it is more clearly ok to do so in that situation, as == in that situation is implemented by upconverting to an interface anyway.
So maybe we can just add && (types2.IsInterface(casType) || types2.IsInterface(tagType)) here.
Cuong Manh LePasses all.bash for me.
Should we add this change in this CL or a follow up? A follow up CL seems to be better in case we need to revert.
Looking back at https://go-review.googlesource.com/c/go/+/418101, do we still have to worry that "unified IR can't wire up appropriate RTTI operands for the conversion" if the conversion is emitted by walk?
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
if casType := w.p.typeOf(cas); !types2.AssignableTo(casType, tagType) {
Keith RandallI think the wrapping in "any" is only needed if either casType or tagType is an interface. And I think it is more clearly ok to do so in that situation, as == in that situation is implemented by upconverting to an interface anyway.
So maybe we can just add && (types2.IsInterface(casType) || types2.IsInterface(tagType)) here.
Cuong Manh LePasses all.bash for me.
Should we add this change in this CL or a follow up? A follow up CL seems to be better in case we need to revert.
Sure, a followup is fine. The plan would then be this CL for 1.23 and the followup for 1.24?
if tagTypeIsChan {
I don't think we need these guards - implictConvExpr will be a no-op because tagType will have never been modified.
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Auto-Submit | +1 |
Commit-Queue | +1 |
if casType := w.p.typeOf(cas); !types2.AssignableTo(casType, tagType) {
Keith RandallI think the wrapping in "any" is only needed if either casType or tagType is an interface. And I think it is more clearly ok to do so in that situation, as == in that situation is implemented by upconverting to an interface anyway.
So maybe we can just add && (types2.IsInterface(casType) || types2.IsInterface(tagType)) here.
Cuong Manh LePasses all.bash for me.
Keith RandallShould we add this change in this CL or a follow up? A follow up CL seems to be better in case we need to revert.
Sure, a followup is fine. The plan would then be this CL for 1.23 and the followup for 1.24?
The plan would then be this CL for 1.23 and the followup for 1.24?
Sounds good to me.
I don't think we need these guards - implictConvExpr will be a no-op because tagType will have never been modified.
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Code-Review | +1 |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
cmd/compile: fix mis-compilation when switching over channels
CL 418101 changes Unified IR writer to force mixed tag/case to have
common type, emitting the implicit conversion if any of the case values
are not assignable to the tag value's type.
However, the Go spec definition of equality is non-transitive for
channels stored in interfaces, causing incorrect behavior with channel
values comparison.
To fix it, don't emit the implicit conversions if tag type is channel.
Fixes #67190
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Cuong Manh Le would like Keith Randall, Than McIntosh and Keith Randall to review this change.
cmd/compile: fix mis-compilation when switching over channels
CL 418101 changes Unified IR writer to force mixed tag/case to have
common type, emitting the implicit conversion if any of the case values
are not assignable to the tag value's type.
However, the Go spec definition of equality is non-transitive for
channels stored in interfaces, causing incorrect behavior with channel
values comparison.
To fix it, don't emit the implicit conversions if tag type is channel.
Fixes #67190
diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go
index fe8f8f2..8fed138 100644
--- a/src/cmd/compile/internal/noder/writer.go
+++ b/src/cmd/compile/internal/noder/writer.go
@@ -1582,6 +1582,7 @@
w.stmt(stmt.Init)
var iface, tagType types2.Type
+ var tagTypeIsChan bool
if guard, ok := stmt.Tag.(*syntax.TypeSwitchGuard); w.Bool(ok) {
iface = w.p.typeOf(guard.X)
@@ -1603,6 +1604,7 @@
tv := w.p.typeAndValue(tag)
tagType = tv.Type
tagValue = tv.Value
+ _, tagTypeIsChan = tagType.Underlying().(*types2.Chan)
} else {
tagType = types2.Typ[types2.Bool]
tagValue = constant.MakeBool(true)
@@ -1655,12 +1657,18 @@
// have the same type. If there are any case values that can't be
// converted to the tag value's type, then convert everything to
// `any` instead.
- Outer:
- for _, clause := range stmt.Body {
- for _, cas := range syntax.UnpackListExpr(clause.Cases) {
- if casType := w.p.typeOf(cas); !types2.AssignableTo(casType, tagType) {
- tagType = types2.NewInterfaceType(nil, nil)
- break Outer
+ //
+ // Except that we need to keep comparisons of channel values from
+ // being wrapped in any(). See issue #67190.
+
+ if !tagTypeIsChan {
+ Outer:
+ for _, clause := range stmt.Body {
+ for _, cas := range syntax.UnpackListExpr(clause.Cases) {
+ if casType := w.p.typeOf(cas); !types2.AssignableTo(casType, tagType) {
+ tagType = types2.NewInterfaceType(nil, nil)
+ break Outer
+ }
}
}
}
@@ -1696,7 +1704,11 @@
w.Sync(pkgbits.SyncExprs)
w.Len(len(cases))
for _, cas := range cases {
- w.implicitConvExpr(tagType, cas)
+ typ := tagType
+ if tagTypeIsChan {
+ typ = nil
+ }
+ w.implicitConvExpr(typ, cas)
}
}
diff --git a/test/fixedbugs/issue67190.go b/test/fixedbugs/issue67190.go
new file mode 100644
index 0000000..c19b248
--- /dev/null
+++ b/test/fixedbugs/issue67190.go
@@ -0,0 +1,24 @@
+// run
+
+// Copyright 2024 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
+
+func main() {
+ ch1 := make(chan struct{})
+ var ch2 <-chan struct{} = ch1
+
+ switch ch1 {
+ case ch2:
+ default:
+ panic("bad narrow case")
+ }
+
+ switch ch2 {
+ case ch1:
+ default:
+ panic("bad narrow switch")
+ }
+}
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
I don't think we need to move the CL over to the release branch. We haven't forked from main yet, so the release team will just bring everything over from main for the next rc/release.
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Commit-Queue | +1 |
I don't think we need to move the CL over to the release branch. We haven't forked from main yet, so the release team will just bring everything over from main for the next rc/release.
Ops, thanks for letting me know.
I thought that after the freeze begun, we have to cherry pick CL to release branch.
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. | Gerrit |