mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
Currently, for hidden closures, we always push them to compile queue during typechecking. If the hidden closure is discarded from the outer function body during deadcode, any desugaring phase after deadcode won't be applied to the closure. Thus, some un-expected OPs are passed to downstream passes, which they can't handle, the compiler goes boom! To fix this, we keep track of discarded hidden closures during deadcode pass, and won't compile them then. Fixes #47712 Change-Id: I078717d5d1f4f2fa39cbaf610cfffbb042e70ceb Reviewed-on: https://go-review.googlesource.com/c/go/+/342350 Trust: Cuong Manh Le <cuong.manhle.vn@gmail.com> Run-TryBot: Cuong Manh Le <cuong.manhle.vn@gmail.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
166 lines
3.4 KiB
Go
166 lines
3.4 KiB
Go
// Copyright 2009 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 deadcode
|
|
|
|
import (
|
|
"go/constant"
|
|
|
|
"cmd/compile/internal/base"
|
|
"cmd/compile/internal/ir"
|
|
)
|
|
|
|
func Func(fn *ir.Func) {
|
|
stmts(&fn.Body)
|
|
|
|
if len(fn.Body) == 0 {
|
|
return
|
|
}
|
|
|
|
for _, n := range fn.Body {
|
|
if len(n.Init()) > 0 {
|
|
return
|
|
}
|
|
switch n.Op() {
|
|
case ir.OIF:
|
|
n := n.(*ir.IfStmt)
|
|
if !ir.IsConst(n.Cond, constant.Bool) || len(n.Body) > 0 || len(n.Else) > 0 {
|
|
return
|
|
}
|
|
case ir.OFOR:
|
|
n := n.(*ir.ForStmt)
|
|
if !ir.IsConst(n.Cond, constant.Bool) || ir.BoolVal(n.Cond) {
|
|
return
|
|
}
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
|
|
ir.VisitList(fn.Body, markHiddenClosureDead)
|
|
fn.Body = []ir.Node{ir.NewBlockStmt(base.Pos, nil)}
|
|
}
|
|
|
|
func stmts(nn *ir.Nodes) {
|
|
var lastLabel = -1
|
|
for i, n := range *nn {
|
|
if n != nil && n.Op() == ir.OLABEL {
|
|
lastLabel = i
|
|
}
|
|
}
|
|
for i, n := range *nn {
|
|
// Cut is set to true when all nodes after i'th position
|
|
// should be removed.
|
|
// In other words, it marks whole slice "tail" as dead.
|
|
cut := false
|
|
if n == nil {
|
|
continue
|
|
}
|
|
if n.Op() == ir.OIF {
|
|
n := n.(*ir.IfStmt)
|
|
n.Cond = expr(n.Cond)
|
|
if ir.IsConst(n.Cond, constant.Bool) {
|
|
var body ir.Nodes
|
|
if ir.BoolVal(n.Cond) {
|
|
ir.VisitList(n.Else, markHiddenClosureDead)
|
|
n.Else = ir.Nodes{}
|
|
body = n.Body
|
|
} else {
|
|
ir.VisitList(n.Body, markHiddenClosureDead)
|
|
n.Body = ir.Nodes{}
|
|
body = n.Else
|
|
}
|
|
// If "then" or "else" branch ends with panic or return statement,
|
|
// it is safe to remove all statements after this node.
|
|
// isterminating is not used to avoid goto-related complications.
|
|
// We must be careful not to deadcode-remove labels, as they
|
|
// might be the target of a goto. See issue 28616.
|
|
if body := body; len(body) != 0 {
|
|
switch body[(len(body) - 1)].Op() {
|
|
case ir.ORETURN, ir.OTAILCALL, ir.OPANIC:
|
|
if i > lastLabel {
|
|
cut = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(n.Init()) != 0 {
|
|
stmts(n.(ir.InitNode).PtrInit())
|
|
}
|
|
switch n.Op() {
|
|
case ir.OBLOCK:
|
|
n := n.(*ir.BlockStmt)
|
|
stmts(&n.List)
|
|
case ir.OFOR:
|
|
n := n.(*ir.ForStmt)
|
|
stmts(&n.Body)
|
|
case ir.OIF:
|
|
n := n.(*ir.IfStmt)
|
|
stmts(&n.Body)
|
|
stmts(&n.Else)
|
|
case ir.ORANGE:
|
|
n := n.(*ir.RangeStmt)
|
|
stmts(&n.Body)
|
|
case ir.OSELECT:
|
|
n := n.(*ir.SelectStmt)
|
|
for _, cas := range n.Cases {
|
|
stmts(&cas.Body)
|
|
}
|
|
case ir.OSWITCH:
|
|
n := n.(*ir.SwitchStmt)
|
|
for _, cas := range n.Cases {
|
|
stmts(&cas.Body)
|
|
}
|
|
}
|
|
|
|
if cut {
|
|
*nn = (*nn)[:i+1]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func expr(n ir.Node) ir.Node {
|
|
// Perform dead-code elimination on short-circuited boolean
|
|
// expressions involving constants with the intent of
|
|
// producing a constant 'if' condition.
|
|
switch n.Op() {
|
|
case ir.OANDAND:
|
|
n := n.(*ir.LogicalExpr)
|
|
n.X = expr(n.X)
|
|
n.Y = expr(n.Y)
|
|
if ir.IsConst(n.X, constant.Bool) {
|
|
if ir.BoolVal(n.X) {
|
|
return n.Y // true && x => x
|
|
} else {
|
|
return n.X // false && x => false
|
|
}
|
|
}
|
|
case ir.OOROR:
|
|
n := n.(*ir.LogicalExpr)
|
|
n.X = expr(n.X)
|
|
n.Y = expr(n.Y)
|
|
if ir.IsConst(n.X, constant.Bool) {
|
|
if ir.BoolVal(n.X) {
|
|
return n.X // true || x => true
|
|
} else {
|
|
return n.Y // false || x => x
|
|
}
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func markHiddenClosureDead(n ir.Node) {
|
|
if n.Op() != ir.OCLOSURE {
|
|
return
|
|
}
|
|
clo := n.(*ir.ClosureExpr)
|
|
if clo.Func.IsHiddenClosure() {
|
|
clo.Func.SetIsDeadcodeClosure(true)
|
|
}
|
|
}
|