blob: 412bbcde831f2e42ecf3493ecc5269616ba79e42 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package annotate
import (
"fmt"
"android.googlesource.com/platform/tools/gpu/api/semantic"
"android.googlesource.com/platform/tools/gpu/api/snippets"
)
// nodeString returns a textual representation of a semantic node. If the
// node has a name that is used, otherwise its path is used, otherwise it
// is just formatted with %T:%v.
func nodeString(n semantic.Node) string {
if n, ok := n.(semantic.NamedNode); ok {
return n.Name()
}
if hasPath(n) {
return fmt.Sprint(path(n))
}
return fmt.Sprintf("%T:%v", n, n)
}
// flow make two locations equivalent.
func flow(left *Location, right *Location) {
if left == nil || right == nil {
return
}
left.alias(right.leader())
}
// Annotator is used to perform the static analysis.
type Annotator struct {
symbols SymbolSpace
}
func (a Annotator) String() string {
return fmt.Sprintf("annotator(%s)", a.symbols)
}
// New returns a newly initialized Annotator
func New() *Annotator {
return &Annotator{symbols: MakeSymbolSpace()}
}
// flow makes information flow between an expression right and an location
// leader left. The specified reason is ignored at the moment.
func (a *Annotator) flow(left *Location, right semantic.Expression) {
flow(left, a.visitExpression(right))
}
// accept used to indicate the expression on the left hand side accepts
// values from the expression on the right hand side. The left hand side
// expression must be assignable (an l-value). The underscore '_' expression
// is ignored on the left hand side.
func (a *Annotator) accept(left semantic.Expression, right semantic.Expression) {
if _, ignore := left.(*semantic.Ignore); ignore {
return
}
leftHasPath := hasPath(left)
if !leftHasPath {
panic(fmt.Errorf("Left hand side of assignment is not assignable: %T:%v",
left, left))
}
a.duplex(left, right)
}
// isAssign tests to see if node is an assignment
func isAssign(node semantic.Node) bool {
switch node.(type) {
case *semantic.ArrayAssign, *semantic.SliceAssign,
*semantic.Assign, *semantic.MapAssign:
return true
default:
return false
}
}
// assignSrcDest returns the left and right side of an assignment node.
// If isAssign is false this function may panic.
func assignDestSrc(node semantic.Node) (semantic.Expression, semantic.Expression) {
switch node := node.(type) {
case *semantic.ArrayAssign:
return node.To, node.Value
case *semantic.SliceAssign:
return node.To, node.Value
case *semantic.Assign:
return node.LHS, node.RHS
case *semantic.MapAssign:
return node.To, node.Value
case *semantic.Copy:
return node.Src, node.Dst
}
panic(fmt.Errorf("Expected assign %T:%v", node, node))
}
// isPrimitiveValue returns true if expr is a simple expressions which just
// represents a value of a particular type.
func isPrimitiveValue(expr semantic.Expression) bool {
switch expr.(type) {
case semantic.Null,
semantic.Int8Value, semantic.Uint8Value,
semantic.Int16Value, semantic.Uint16Value,
semantic.Int32Value, semantic.Uint32Value,
semantic.Int64Value, semantic.Uint64Value,
semantic.BoolValue,
semantic.StringValue,
semantic.Float64Value, semantic.Float32Value,
*semantic.EnumEntry,
*semantic.Label:
return true
default:
return false
}
}
// elem decomposes an expression one level. Returns the location which
// represents an element of the collection.
func (a *Annotator) elem(expr semantic.Expression) *Location {
nested := a.visitExpression(expr).getNested()
if nested == nil {
return &Location{}
}
if container, ok := nested.(*container); ok {
return container.Elem()
}
return &Location{}
}
// key decomposes an expression one level. Returns the location which
// represents the key of the collection.
func (a *Annotator) key(expr semantic.Expression) *Location {
nested := a.visitExpression(expr).getNested()
if nested == nil {
return &Location{}
}
if container, ok := nested.(*container); ok {
return container.Key()
}
return &Location{}
}
// symbolTable returns a symbol table for expression of the category specified
func (a *Annotator) symbolTable(expr semantic.Expression) *ScopedSymbolTable {
return a.symbols[category(expr)]
}
// declare makes a new symbol table entry for the specified expression.
// The specified expression must be a NamedNode and it must not have
// previously been declared. The location for the new declaration is returned.
func (a *Annotator) declare(expr semantic.Expression) *Location {
if named, ok := expr.(semantic.NamedNode); ok {
return a.symbolTable(expr).declare(named.Name())
}
panic(fmt.Errorf("Attempt to declare non-named type: %T:%v:%s",
expr, expr, nodeString(expr)))
}
// locate returns a location leader for the expression specified. It
// will panic if the expression does not have a location.
func (a *Annotator) locate(expr semantic.Expression) *Location {
if !hasPath(expr) {
panic(fmt.Errorf("Unexpected lvalue %T:%v", expr, expr))
}
switch t := expr.(type) {
case nil:
panic("nil expression in locate")
case semantic.NamedNode:
return a.lookup(expr, t.Name()).leader()
case *semantic.Member:
return a.locateMember(t)
case *semantic.PointerRange:
return a.locateRange(t.Pointer)
case *semantic.SliceRange:
return a.locate(t.Slice)
case *semantic.SliceIndex:
return a.locateElem(t.Slice)
case *semantic.ArrayIndex:
return a.locateElem(t.Array)
case *semantic.MapIndex:
return a.locateElem(t.Map)
case *semantic.Cast:
return a.locate(t.Object)
case *semantic.Observed:
return a.locate(t.Parameter)
default:
panic(fmt.Errorf("Unexpected type %T in locate %v", expr, expr))
}
}
// locateElem returns a location leader for an element of the collection
// specified by the expression.
func (a *Annotator) locateElem(expr semantic.Expression) *Location {
return a.locate(expr).container(expr).Elem()
}
// locateKey returns a location leader for the key of the collection
// specified by the expression.
func (a *Annotator) locateKey(expr semantic.Expression) *Location {
return a.locate(expr).container(expr).Key()
}
// locateRange returns a location leader for the key of the collection
// specified by the expression.
func (a *Annotator) locateRange(expr semantic.Expression) *Location {
return a.locate(expr).pointer(expr).Range()
}
// lookup returns a location for an expression in the symbol table. The
// expression and name should match. The name is expected to have been
// previously declared.
func (a *Annotator) lookup(expr semantic.Expression, name string) *Location {
return a.symbolTable(expr).lookup(name)
}
// boolExpr return a value for a boolean result.
func (a *Annotator) boolExpr() *Location {
return a.value(semantic.BoolValue(false))
}
// duplex visits the left and right expressions and allows information to flow
// in both directions.
func (a *Annotator) duplex(left semantic.Expression, right semantic.Expression) {
leftHasPath := hasPath(left)
rightHasPath := hasPath(right)
r := a.visitExpression(right)
l := a.visitExpression(left)
if leftHasPath {
flow(a.locate(left), r)
} else if rightHasPath {
flow(a.locate(right), l)
}
}
// compare visits the left and right sides of a comparison and returns a result
func (a *Annotator) compare(left semantic.Expression, right semantic.Expression) *Location {
a.duplex(left, right)
return a.boolExpr()
}
// value returns a location for a value expression.
func (a *Annotator) value(right semantic.Expression) *Location {
if isPrimitiveValue(right) {
return &Location{}
}
t := right.ExpressionType()
switch t := t.(type) {
case *semantic.Class:
return newEntity(t).leader()
case *semantic.Slice, *semantic.Map, *semantic.StaticArray:
return newContainer().leader()
case *semantic.Pointer:
return newPointer().leader()
case *semantic.Builtin:
return &Location{}
default:
panic(fmt.Errorf("Unexpected value expr=%T:%v type=%T:%v\n", right, right, t, t))
}
}
// member visits a member expression and returns a location for the field
// specified by the member.
func (a *Annotator) member(right *semantic.Member) *Location {
nested := a.visitExpression(right.Object).getNested()
if nested == nil {
return &Location{}
}
if ent, ok := nested.(*entity); ok {
return ent.Field(right.Field.Name())
}
return &Location{}
}
// locateMember returns a leader location for the specified member expression.
func (a *Annotator) locateMember(right *semantic.Member) *Location {
return a.locate(right.Object).entity(right.Object).Field(right.Field.Name())
}
// TODO is a placeholder for expressions which are not implemented.
func (a *Annotator) TODO(right semantic.Expression) *Location {
return &Location{}
}
// read associates a Read observation with the location of an expression
func (a *Annotator) read(expr semantic.Expression) *Location {
if hasPath(expr) {
flow(a.locate(expr), newObservation(snippets.ObservationType_Read))
}
return a.value(expr)
}
// write associates an Write observation with the location of an expression
func (a *Annotator) write(expr semantic.Expression) *Location {
if hasPath(expr) {
flow(a.locate(expr), newObservation(snippets.ObservationType_Write))
}
return a.value(expr)
}
// create makes a new nested entity structure based on a class initializer
func (a *Annotator) create(typ semantic.Type, ini *semantic.ClassInitializer) Nested {
e := newEntity(typ)
for _, f := range ini.Fields {
a.flow(e.Field(f.Field.Name()), f.Value)
}
return e
}
// beginScope enter a new scope in the symbol table.
func (a *Annotator) beginScope() {
a.symbols.enter()
}
// endScope finish the current scope in the symbol table.
func (a *Annotator) endScope() {
a.symbols.leave()
}
// scoped calls the function f with a symbol space scope.
func (a *Annotator) scoped(f func()) {
a.beginScope()
defer a.endScope()
f()
}
// visitExpression visits a semantic expression and returns a location for
// the value returned by the expression. The return value maybe nil, which
// means nothing of interest.
func (a *Annotator) visitExpression(expr semantic.Expression) *Location {
switch expr := expr.(type) {
case nil:
panic(fmt.Errorf("nil expression passed"))
case *semantic.Global:
return a.lookup(expr, expr.Name())
case *semantic.Local:
return a.lookup(expr, expr.Name())
case *semantic.Parameter:
return a.lookup(expr, expr.Name())
case *semantic.Select:
for _, choice := range expr.Choices {
for _, c := range choice.Conditions {
a.compare(expr.Value, c)
}
}
return a.boolExpr()
case *semantic.UnaryOp:
return a.visitExpression(expr.Expression)
case *semantic.BinaryOp:
return a.compare(expr.LHS, expr.RHS)
case *semantic.BitTest:
return a.compare(expr.Bitfield, expr.Bits)
case *semantic.MapContains:
a.flow(a.locateKey(expr.Map), expr.Key)
return a.value(expr)
case *semantic.Member:
return a.member(expr)
case *semantic.PointerRange:
return a.locateRange(expr.Pointer)
case *semantic.SliceRange:
return a.visitExpression(expr.Slice)
case *semantic.ArrayIndex:
a.flow(a.locateKey(expr.Array), expr.Index)
return a.elem(expr.Array)
case *semantic.SliceIndex:
a.flow(a.locateKey(expr.Slice), expr.Index)
return a.elem(expr.Slice)
case *semantic.MapIndex:
a.flow(a.locateKey(expr.Map), expr.Index)
return a.elem(expr.Map)
case *semantic.Callable:
// TODO: ultimately interested in snippets attached to return value
return a.TODO(expr)
case *semantic.Call:
result := a.TODO(expr.Target)
if f := expr.Target.Function; f != nil && f.Block != nil {
args := make([]*Location, len(expr.Arguments))
for i := range args {
args[i] = a.visitExpression(expr.Arguments[i])
}
a.scoped(func() {
// Propagate arguments into subroutine scope as parameters.
for i, p := range f.CallParameters() {
a.symbols[snippets.SymbolCategory_Parameter].add(p.Name(), args[i])
}
// Declare the return 'parameter'.
if f.Return != nil {
result = a.declare(f.Return)
}
// Process the subroutine.
a.visitStatement(f.Block)
})
}
return result
case *semantic.Length:
return a.value(expr)
case *semantic.Assert:
return a.visitExpression(expr.Condition)
case *semantic.Cast:
// TODO annotate cast. Needs Type binary.Object and support for Pseudonym.
return a.visitExpression(expr.Object)
case *semantic.Create:
return a.create(expr.Type.To, expr.Initializer).leader()
case *semantic.Make:
return a.value(expr)
case *semantic.Clone:
return a.read(expr.Slice)
case *semantic.Read:
return a.read(expr.Slice)
case *semantic.Write:
a.write(expr.Slice)
return a.value(expr)
case *semantic.Copy:
a.read(expr.Src)
a.write(expr.Dst)
a.accept(expr.Dst, expr.Src)
return a.value(expr)
case *semantic.ClassInitializer:
return a.create(expr.ExpressionType(), expr).leader()
case *semantic.Definition:
a.declare(expr)
a.accept(expr, expr.Expression)
return &Location{}
case *semantic.DefinitionUsage:
// TODO this type never visited: lookup(expr.Expression, expr.Definition.Name())
return a.TODO(expr)
case *semantic.EnumEntry:
return newLabel(expr.Name())
case *semantic.Label:
return newLabel(expr.Name())
case *semantic.ArrayInitializer:
c := newContainer()
e := c.Elem()
for _, v := range expr.Values {
a.flow(e, v)
}
return c.leader()
case *semantic.Unknown:
return a.visitExpression(expr.Inferred)
case *semantic.Observed:
return a.visitExpression(expr.Parameter)
default:
if isPrimitiveValue(expr) {
return a.value(expr)
}
panic(fmt.Errorf("Unexpected expression %T %v", expr, expr))
}
}
// visitStatement visits a semantic node. Information flows between locations
// used in assignment or comparison.
func (a *Annotator) visitStatement(stmt semantic.Node) {
if isAssign(stmt) {
dest, src := assignDestSrc(stmt)
a.accept(dest, src)
return
}
switch stmt := stmt.(type) {
case nil:
panic(fmt.Errorf("nil statement passed"))
case semantic.Expression:
a.visitExpression(stmt)
case *semantic.Block:
a.scoped(func() {
for _, s := range stmt.Statements {
a.visitStatement(s)
}
})
case *semantic.Branch:
a.visitExpression(stmt.Condition)
a.visitStatement(stmt.True)
if stmt.False != nil {
a.visitStatement(stmt.False)
}
case *semantic.Switch:
for _, c := range stmt.Cases {
for _, cond := range c.Conditions {
a.compare(stmt.Value, cond)
}
a.visitStatement(c.Block)
}
case *semantic.Iteration:
a.scoped(func() {
a.declare(stmt.Iterator)
a.visitExpression(stmt.Iterable)
if hasPath(stmt.Iterable) {
a.flow(a.locateElem(stmt.Iterable), stmt.Iterator)
}
a.visitStatement(stmt.Block)
})
case *semantic.MapRemove:
a.flow(a.locateKey(stmt.Map), stmt.Key)
case *semantic.DeclareLocal:
a.declare(stmt.Local)
a.accept(stmt.Local, stmt.Local.Value)
case *semantic.Return:
if stmt.Value != nil {
a.accept(stmt.Function.Return, stmt.Value)
}
case *semantic.Abort:
// don't do anything at the moment.
case *semantic.Fence:
if stmt.Statement != nil {
a.visitStatement(stmt.Statement)
}
default:
panic(fmt.Errorf("Unexpected statement type %T:%v@%p", stmt, stmt, stmt))
}
}