shader: Check unused local variables

Fixes #1328
This commit is contained in:
Hajime Hoshi 2020-09-13 20:13:30 +09:00
parent e543d4f107
commit 154f86e6c1
11 changed files with 90 additions and 52 deletions

View File

@ -31,7 +31,7 @@ func canTruncateToInteger(v gconstant.Value) bool {
var textureVariableRe = regexp.MustCompile(`\A__t(\d+)\z`) var textureVariableRe = regexp.MustCompile(`\A__t(\d+)\z`)
func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr, []shaderir.Type, []shaderir.Stmt, bool) { func (cs *compileState) parseExpr(block *block, expr ast.Expr, markLocalVariableUsed bool) ([]shaderir.Expr, []shaderir.Type, []shaderir.Stmt, bool) {
switch e := expr.(type) { switch e := expr.(type) {
case *ast.BasicLit: case *ast.BasicLit:
switch e.Kind { switch e.Kind {
@ -57,7 +57,7 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
var stmts []shaderir.Stmt var stmts []shaderir.Stmt
// Prase LHS first for the order of the statements. // Prase LHS first for the order of the statements.
lhs, ts, ss, ok := cs.parseExpr(block, e.X) lhs, ts, ss, ok := cs.parseExpr(block, e.X, markLocalVariableUsed)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -68,7 +68,7 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
stmts = append(stmts, ss...) stmts = append(stmts, ss...)
lhst := ts[0] lhst := ts[0]
rhs, ts, ss, ok := cs.parseExpr(block, e.Y) rhs, ts, ss, ok := cs.parseExpr(block, e.Y, markLocalVariableUsed)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -192,7 +192,7 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
// Parse the argument first for the order of the statements. // Parse the argument first for the order of the statements.
for _, a := range e.Args { for _, a := range e.Args {
es, ts, ss, ok := cs.parseExpr(block, a) es, ts, ss, ok := cs.parseExpr(block, a, markLocalVariableUsed)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -206,7 +206,7 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
} }
// TODO: When len(ss) is not 0? // TODO: When len(ss) is not 0?
es, _, ss, ok := cs.parseExpr(block, e.Fun) es, _, ss, ok := cs.parseExpr(block, e.Fun, markLocalVariableUsed)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -376,7 +376,7 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
}, },
}, nil, nil, true }, nil, nil, true
} }
if i, t, ok := block.findLocalVariable(e.Name); ok { if i, t, ok := block.findLocalVariable(e.Name, markLocalVariableUsed); ok {
return []shaderir.Expr{ return []shaderir.Expr{
{ {
Type: shaderir.LocalVariable, Type: shaderir.LocalVariable,
@ -428,10 +428,10 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
cs.addError(e.Pos(), fmt.Sprintf("unexpected identifier: %s", e.Name)) cs.addError(e.Pos(), fmt.Sprintf("unexpected identifier: %s", e.Name))
case *ast.ParenExpr: case *ast.ParenExpr:
return cs.parseExpr(block, e.X) return cs.parseExpr(block, e.X, markLocalVariableUsed)
case *ast.SelectorExpr: case *ast.SelectorExpr:
exprs, _, stmts, ok := cs.parseExpr(block, e.X) exprs, _, stmts, ok := cs.parseExpr(block, e.X, markLocalVariableUsed)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -467,7 +467,7 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
}, []shaderir.Type{t}, stmts, true }, []shaderir.Type{t}, stmts, true
case *ast.UnaryExpr: case *ast.UnaryExpr:
exprs, t, stmts, ok := cs.parseExpr(block, e.X) exprs, t, stmts, ok := cs.parseExpr(block, e.X, markLocalVariableUsed)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -526,7 +526,7 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
var stmts []shaderir.Stmt var stmts []shaderir.Stmt
for i, e := range e.Elts { for i, e := range e.Elts {
exprs, _, ss, ok := cs.parseExpr(block, e) exprs, _, ss, ok := cs.parseExpr(block, e, markLocalVariableUsed)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -568,7 +568,7 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
var stmts []shaderir.Stmt var stmts []shaderir.Stmt
// Parse the index first // Parse the index first
exprs, _, ss, ok := cs.parseExpr(block, e.Index) exprs, _, ss, ok := cs.parseExpr(block, e.Index, markLocalVariableUsed)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -587,7 +587,7 @@ func (cs *compileState) parseExpr(block *block, expr ast.Expr) ([]shaderir.Expr,
idx.ConstType = shaderir.ConstTypeInt idx.ConstType = shaderir.ConstTypeInt
} }
exprs, ts, ss, ok := cs.parseExpr(block, e.X) exprs, ts, ss, ok := cs.parseExpr(block, e.X, markLocalVariableUsed)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }

View File

@ -83,11 +83,12 @@ type typ struct {
} }
type block struct { type block struct {
types []typ types []typ
vars []variable vars []variable
consts []constant unusedVars map[int]token.Pos
pos token.Pos consts []constant
outer *block pos token.Pos
outer *block
ir *shaderir.Block ir *shaderir.Block
} }
@ -100,7 +101,22 @@ func (b *block) totalLocalVariableNum() int {
return c return c
} }
func (b *block) findLocalVariable(name string) (int, shaderir.Type, bool) { func (b *block) addNamedLocalVariable(name string, typ shaderir.Type, pos token.Pos) {
b.vars = append(b.vars, variable{
name: name,
typ: typ,
})
if name == "_" {
return
}
idx := len(b.vars) - 1
if b.unusedVars == nil {
b.unusedVars = map[int]token.Pos{}
}
b.unusedVars[idx] = pos
}
func (b *block) findLocalVariable(name string, markLocalVariableUsed bool) (int, shaderir.Type, bool) {
if name == "" || name == "_" { if name == "" || name == "_" {
panic("shader: variable name must be non-empty and non-underscore") panic("shader: variable name must be non-empty and non-underscore")
} }
@ -111,11 +127,14 @@ func (b *block) findLocalVariable(name string) (int, shaderir.Type, bool) {
} }
for i, v := range b.vars { for i, v := range b.vars {
if v.name == name { if v.name == name {
if markLocalVariableUsed {
delete(b.unusedVars, i)
}
return idx + i, v.typ, true return idx + i, v.typ, true
} }
} }
if b.outer != nil { if b.outer != nil {
return b.outer.findLocalVariable(name) return b.outer.findLocalVariable(name, markLocalVariableUsed)
} }
return 0, shaderir.Type{}, false return 0, shaderir.Type{}, false
} }
@ -422,7 +441,7 @@ func (s *compileState) parseVariable(block *block, vs *ast.ValueSpec) ([]variabl
init := vs.Values[i] init := vs.Values[i]
es, origts, ss, ok := s.parseExpr(block, init) es, origts, ss, ok := s.parseExpr(block, init, true)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -458,7 +477,7 @@ func (s *compileState) parseVariable(block *block, vs *ast.ValueSpec) ([]variabl
var ss []shaderir.Stmt var ss []shaderir.Stmt
var ok bool var ok bool
initexprs, inittypes, ss, ok = s.parseExpr(block, init) initexprs, inittypes, ss, ok = s.parseExpr(block, init, true)
if !ok { if !ok {
return nil, nil, nil, false return nil, nil, nil, false
} }
@ -644,7 +663,7 @@ func (cs *compileState) parseFunc(block *block, d *ast.FuncDecl) (function, bool
} }
} }
b, ok := cs.parseBlock(block, d.Name.Name, d.Body.List, inParams, outParams) b, ok := cs.parseBlock(block, d.Name.Name, d.Body.List, inParams, outParams, true)
if !ok { if !ok {
return function{}, false return function{}, false
} }
@ -690,7 +709,7 @@ func (cs *compileState) parseFunc(block *block, d *ast.FuncDecl) (function, bool
}, true }, true
} }
func (cs *compileState) parseBlock(outer *block, fname string, stmts []ast.Stmt, inParams, outParams []variable) (*block, bool) { func (cs *compileState) parseBlock(outer *block, fname string, stmts []ast.Stmt, inParams, outParams []variable, checkLocalVariableUsage bool) (*block, bool) {
var vars []variable var vars []variable
if outer == &cs.global { if outer == &cs.global {
vars = make([]variable, 0, len(inParams)+len(outParams)) vars = make([]variable, 0, len(inParams)+len(outParams))
@ -745,5 +764,12 @@ func (cs *compileState) parseBlock(outer *block, fname string, stmts []ast.Stmt,
block.ir.Stmts = append(block.ir.Stmts, ss...) block.ir.Stmts = append(block.ir.Stmts, ss...)
} }
if checkLocalVariableUsage && len(block.unusedVars) > 0 {
for idx, pos := range block.unusedVars {
cs.addError(pos, fmt.Sprintf("local variable %s is not used", block.vars[idx].name))
}
return nil, false
}
return block, true return block, true
} }

View File

@ -75,13 +75,13 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
op = shaderir.ModOp op = shaderir.ModOp
} }
rhs, _, ss, ok := cs.parseExpr(block, stmt.Rhs[0]) rhs, _, ss, ok := cs.parseExpr(block, stmt.Rhs[0], true)
if !ok { if !ok {
return nil, false return nil, false
} }
stmts = append(stmts, ss...) stmts = append(stmts, ss...)
lhs, ts, ss, ok := cs.parseExpr(block, stmt.Lhs[0]) lhs, ts, ss, ok := cs.parseExpr(block, stmt.Lhs[0], false)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -111,7 +111,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
cs.addError(stmt.Pos(), fmt.Sprintf("unexpected token: %s", stmt.Tok)) cs.addError(stmt.Pos(), fmt.Sprintf("unexpected token: %s", stmt.Tok))
} }
case *ast.BlockStmt: case *ast.BlockStmt:
b, ok := cs.parseBlock(block, fname, stmt.List, inParams, outParams) b, ok := cs.parseBlock(block, fname, stmt.List, inParams, outParams, true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -146,7 +146,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
// Create a new pseudo block for the initial statement, so that the counter variable belongs to the // Create a new pseudo block for the initial statement, so that the counter variable belongs to the
// new pseudo block for each for-loop. Without this, the samely named counter variables in different // new pseudo block for each for-loop. Without this, the samely named counter variables in different
// for-loops confuses the parser. // for-loops confuses the parser.
pseudoBlock, ok := cs.parseBlock(block, fname, []ast.Stmt{stmt.Init}, inParams, outParams) pseudoBlock, ok := cs.parseBlock(block, fname, []ast.Stmt{stmt.Init}, inParams, outParams, false)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -173,7 +173,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
vartype := pseudoBlock.vars[0].typ vartype := pseudoBlock.vars[0].typ
init := ss[0].Exprs[1].Const init := ss[0].Exprs[1].Const
exprs, ts, ss, ok := cs.parseExpr(pseudoBlock, stmt.Cond) exprs, ts, ss, ok := cs.parseExpr(pseudoBlock, stmt.Cond, true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -258,7 +258,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
return nil, false return nil, false
} }
b, ok := cs.parseBlock(pseudoBlock, fname, []ast.Stmt{stmt.Body}, inParams, outParams) b, ok := cs.parseBlock(pseudoBlock, fname, []ast.Stmt{stmt.Body}, inParams, outParams, true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -289,7 +289,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
if stmt.Init != nil { if stmt.Init != nil {
init := stmt.Init init := stmt.Init
stmt.Init = nil stmt.Init = nil
b, ok := cs.parseBlock(block, fname, []ast.Stmt{init, stmt}, inParams, outParams) b, ok := cs.parseBlock(block, fname, []ast.Stmt{init, stmt}, inParams, outParams, true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -301,7 +301,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
return stmts, true return stmts, true
} }
exprs, ts, ss, ok := cs.parseExpr(block, stmt.Cond) exprs, ts, ss, ok := cs.parseExpr(block, stmt.Cond, true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -316,7 +316,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
stmts = append(stmts, ss...) stmts = append(stmts, ss...)
var bs []*shaderir.Block var bs []*shaderir.Block
b, ok := cs.parseBlock(block, fname, stmt.Body.List, inParams, outParams) b, ok := cs.parseBlock(block, fname, stmt.Body.List, inParams, outParams, true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -325,13 +325,13 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
if stmt.Else != nil { if stmt.Else != nil {
switch s := stmt.Else.(type) { switch s := stmt.Else.(type) {
case *ast.BlockStmt: case *ast.BlockStmt:
b, ok := cs.parseBlock(block, fname, s.List, inParams, outParams) b, ok := cs.parseBlock(block, fname, s.List, inParams, outParams, true)
if !ok { if !ok {
return nil, false return nil, false
} }
bs = append(bs, b.ir) bs = append(bs, b.ir)
default: default:
b, ok := cs.parseBlock(block, fname, []ast.Stmt{s}, inParams, outParams) b, ok := cs.parseBlock(block, fname, []ast.Stmt{s}, inParams, outParams, true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -346,7 +346,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
}) })
case *ast.IncDecStmt: case *ast.IncDecStmt:
exprs, _, ss, ok := cs.parseExpr(block, stmt.X) exprs, _, ss, ok := cs.parseExpr(block, stmt.X, false)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -388,7 +388,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
} }
for i, r := range stmt.Results { for i, r := range stmt.Results {
exprs, ts, ss, ok := cs.parseExpr(block, r) exprs, ts, ss, ok := cs.parseExpr(block, r, true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -463,7 +463,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
} }
case *ast.ExprStmt: case *ast.ExprStmt:
exprs, _, ss, ok := cs.parseExpr(block, stmt.X) exprs, _, ss, ok := cs.parseExpr(block, stmt.X, true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -495,7 +495,7 @@ func (cs *compileState) assign(block *block, fname string, pos token.Pos, lhs, r
for i, e := range lhs { for i, e := range lhs {
if len(lhs) == len(rhs) { if len(lhs) == len(rhs) {
// Prase RHS first for the order of the statements. // Prase RHS first for the order of the statements.
r, origts, ss, ok := cs.parseExpr(block, rhs[i]) r, origts, ss, ok := cs.parseExpr(block, rhs[i], true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -520,13 +520,11 @@ func (cs *compileState) assign(block *block, fname string, pos token.Pos, lhs, r
return nil, false return nil, false
} }
v := variable{ var t shaderir.Type
name: name,
}
if len(ts) == 1 { if len(ts) == 1 {
v.typ = ts[0] t = ts[0]
} }
block.vars = append(block.vars, v) block.addNamedLocalVariable(name, t, e.Pos())
} }
if len(r) > 1 { if len(r) > 1 {
@ -534,7 +532,7 @@ func (cs *compileState) assign(block *block, fname string, pos token.Pos, lhs, r
return nil, false return nil, false
} }
l, _, ss, ok := cs.parseExpr(block, lhs[i]) l, _, ss, ok := cs.parseExpr(block, lhs[i], false)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -618,7 +616,7 @@ func (cs *compileState) assign(block *block, fname string, pos token.Pos, lhs, r
if i == 0 { if i == 0 {
var ss []shaderir.Stmt var ss []shaderir.Stmt
var ok bool var ok bool
rhsExprs, rhsTypes, ss, ok = cs.parseExpr(block, rhs[0]) rhsExprs, rhsTypes, ss, ok = cs.parseExpr(block, rhs[0], true)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -638,14 +636,10 @@ func (cs *compileState) assign(block *block, fname string, pos token.Pos, lhs, r
} }
} }
} }
v := variable{ block.addNamedLocalVariable(name, rhsTypes[i], e.Pos())
name: name,
}
v.typ = rhsTypes[i]
block.vars = append(block.vars, v)
} }
l, _, ss, ok := cs.parseExpr(block, lhs[i]) l, _, ss, ok := cs.parseExpr(block, lhs[i], false)
if !ok { if !ok {
return nil, false return nil, false
} }

View File

@ -8,4 +8,5 @@ func Bar() {
_, _ = Foo() _, _ = Foo()
a, _ := Foo() a, _ := Foo()
_, b := Foo() _, b := Foo()
_, _ = a, b
} }

View File

@ -6,5 +6,6 @@ func Foo() vec2 {
x := 0 x := 0
return vec2(x) return vec2(x)
} }
_ = x
return vec2(1) return vec2(1)
} }

View File

@ -3,6 +3,7 @@ package main
func Foo() vec2 { func Foo() vec2 {
x0 := 1 * Bar() x0 := 1 * Bar()
x1 := Bar() * 1 x1 := Bar() * 1
_ = x1
return x0 return x0
} }

View File

@ -2,6 +2,7 @@ package main
func Foo(foo vec2) vec4 { func Foo(foo vec2) vec4 {
r1, r2 := Bar() r1, r2 := Bar()
_ = r2
return vec4(foo, r1, r1) return vec4(foo, r1, r1)
} }

View File

@ -9,5 +9,6 @@ func Foo() vec2 {
for i := 10.0; i >= 0; i -= 2 { for i := 10.0; i >= 0; i -= 2 {
v2.x += i v2.x += i
} }
_ = v2
return v return v
} }

View File

@ -11,5 +11,6 @@ func Foo() vec2 {
v4 := vec2(0) v4 := vec2(0)
v3 = v4 v3 = v4
} }
_ = v3
return v return v
} }

View File

@ -57,7 +57,7 @@ func (cs *compileState) parseType(block *block, expr ast.Expr) (shaderir.Type, b
if _, ok := t.Len.(*ast.Ellipsis); ok { if _, ok := t.Len.(*ast.Ellipsis); ok {
length = -1 // Determine the length later. length = -1 // Determine the length later.
} else { } else {
exprs, _, _, ok := cs.parseExpr(block, t.Len) exprs, _, _, ok := cs.parseExpr(block, t.Len, true)
if !ok { if !ok {
return shaderir.Type{}, false return shaderir.Type{}, false
} }

View File

@ -518,3 +518,15 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
t.Errorf("error must be nil but was non-nil") t.Errorf("error must be nil but was non-nil")
} }
} }
func TestShaderUnusedVariable(t *testing.T) {
if _, err := NewShader([]byte(`package main
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
x := 0
return vec4(0)
}
`)); err == nil {
t.Errorf("error must be non-nil but was nil")
}
}