|
|
|
package ssa2ast
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/token"
|
|
|
|
"go/types"
|
|
|
|
"maps"
|
|
|
|
"slices"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"golang.org/x/tools/go/ssa"
|
|
|
|
ah "mvdan.cc/garble/internal/asthelper"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrUnsupported = errors.New("unsupported")
|
|
|
|
|
|
|
|
MarkerInstr = &ssa.Panic{}
|
|
|
|
)
|
|
|
|
|
|
|
|
type NameType int
|
|
|
|
|
|
|
|
type ImportNameResolver func(pkg *types.Package) *ast.Ident
|
|
|
|
|
|
|
|
type ConverterConfig struct {
|
|
|
|
// ImportNameResolver function to get the actual import name.
|
|
|
|
// Because converting works at function level, only the caller knows actual name of the import.
|
|
|
|
ImportNameResolver ImportNameResolver
|
|
|
|
|
|
|
|
// NamePrefix prefix added to all new local variables. Must be reasonably unique
|
|
|
|
NamePrefix string
|
|
|
|
|
|
|
|
// SsaValueRemap is used to replace ssa.Value with the specified ssa.Expr.
|
|
|
|
// Note: Replacing ssa.Expr does not guarantee the correctness of the generated code.
|
|
|
|
// When using it, strictly adhere to the value types.
|
|
|
|
SsaValueRemap map[ssa.Value]ast.Expr
|
|
|
|
|
|
|
|
// MarkerInstrCallback is called every time a MarkerInstr instruction is encountered.
|
|
|
|
// Callback result is inserted into ast as is
|
|
|
|
MarkerInstrCallback func(vars map[string]types.Type) []ast.Stmt
|
|
|
|
}
|
|
|
|
|
|
|
|
func DefaultConfig() *ConverterConfig {
|
|
|
|
return &ConverterConfig{
|
|
|
|
ImportNameResolver: defaultImportNameResolver,
|
|
|
|
NamePrefix: "_s2a_",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultImportNameResolver(pkg *types.Package) *ast.Ident {
|
|
|
|
if pkg == nil || pkg.Name() == "main" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ast.NewIdent(pkg.Name())
|
|
|
|
}
|
|
|
|
|
|
|
|
type funcConverter struct {
|
|
|
|
importNameResolver ImportNameResolver
|
|
|
|
tc *TypeConverter
|
|
|
|
namePrefix string
|
|
|
|
valueNameMap map[ssa.Value]string
|
|
|
|
ssaValueRemap map[ssa.Value]ast.Expr
|
|
|
|
markerInstrCallback func(map[string]types.Type) []ast.Stmt
|
|
|
|
}
|
|
|
|
|
|
|
|
func Convert(ssaFunc *ssa.Function, cfg *ConverterConfig) (*ast.FuncDecl, error) {
|
|
|
|
return newFuncConverter(cfg).convert(ssaFunc)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newFuncConverter(cfg *ConverterConfig) *funcConverter {
|
|
|
|
return &funcConverter{
|
|
|
|
importNameResolver: cfg.ImportNameResolver,
|
|
|
|
tc: &TypeConverter{resolver: cfg.ImportNameResolver},
|
|
|
|
namePrefix: cfg.NamePrefix,
|
|
|
|
valueNameMap: make(map[ssa.Value]string),
|
|
|
|
ssaValueRemap: cfg.SsaValueRemap,
|
|
|
|
markerInstrCallback: cfg.MarkerInstrCallback,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) getVarName(val ssa.Value) string {
|
|
|
|
if name, ok := fc.valueNameMap[val]; ok {
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
|
|
|
name := fc.namePrefix + strconv.Itoa(len(fc.valueNameMap))
|
|
|
|
fc.valueNameMap[val] = name
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) convertSignatureToFuncDecl(name string, signature *types.Signature) (*ast.FuncDecl, error) {
|
|
|
|
funcTypeDecl, err := fc.tc.Convert(signature)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
funcDecl := &ast.FuncDecl{Name: ast.NewIdent(name), Type: funcTypeDecl.(*ast.FuncType)}
|
|
|
|
if recv := signature.Recv(); recv != nil {
|
|
|
|
recvTypeExpr, err := fc.tc.Convert(recv.Type())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
f := &ast.Field{Type: recvTypeExpr}
|
|
|
|
if recvName := recv.Name(); recvName != "" {
|
|
|
|
f.Names = []*ast.Ident{ast.NewIdent(recvName)}
|
|
|
|
}
|
|
|
|
funcDecl.Recv = &ast.FieldList{List: []*ast.Field{f}}
|
|
|
|
}
|
|
|
|
return funcDecl, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) convertSignatureToFuncLit(signature *types.Signature) (*ast.FuncLit, error) {
|
|
|
|
funcTypeDecl, err := fc.tc.Convert(signature)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &ast.FuncLit{Type: funcTypeDecl.(*ast.FuncType)}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type AstBlock struct {
|
|
|
|
Index int
|
|
|
|
HasRefs bool
|
|
|
|
Body []ast.Stmt
|
|
|
|
Phi []ast.Stmt
|
|
|
|
Exit ast.Stmt
|
|
|
|
}
|
|
|
|
|
|
|
|
type AstFunc struct {
|
|
|
|
Vars map[string]types.Type
|
|
|
|
Blocks []*AstBlock
|
|
|
|
}
|
|
|
|
|
|
|
|
func isVoidType(typ types.Type) bool {
|
|
|
|
tuple, ok := typ.(*types.Tuple)
|
|
|
|
return ok && tuple.Len() == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func isStringType(typ types.Type) bool {
|
|
|
|
return types.Identical(typ, types.Typ[types.String]) || types.Identical(typ, types.Typ[types.UntypedString])
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFieldName(tp types.Type, index int) (string, error) {
|
|
|
|
if pt, ok := tp.(*types.Pointer); ok {
|
|
|
|
tp = pt.Elem()
|
|
|
|
}
|
|
|
|
if named, ok := tp.(*types.Named); ok {
|
|
|
|
tp = named.Underlying()
|
|
|
|
}
|
|
|
|
if stp, ok := tp.(*types.Struct); ok {
|
|
|
|
return stp.Field(index).Name(), nil
|
|
|
|
}
|
|
|
|
return "", fmt.Errorf("field %d not found in %v", index, tp)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) castCallExpr(typ types.Type, x ssa.Value) (*ast.CallExpr, error) {
|
|
|
|
castExpr, err := fc.tc.Convert(typ)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
valExpr, err := fc.convertSsaValue(x)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ah.CallExpr(&ast.ParenExpr{X: castExpr}, valExpr), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) getLabelName(blockIdx int) *ast.Ident {
|
|
|
|
return ast.NewIdent(fmt.Sprintf("%sl%d", fc.namePrefix, blockIdx))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) gotoStmt(blockIdx int) *ast.BranchStmt {
|
|
|
|
return &ast.BranchStmt{
|
|
|
|
Tok: token.GOTO,
|
|
|
|
Label: fc.getLabelName(blockIdx),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) getAnonFunctionName(val *ssa.Function) (*ast.Ident, error) {
|
|
|
|
parent := val.Parent()
|
|
|
|
if parent == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
anonFuncIdx := slices.Index(parent.AnonFuncs, val)
|
|
|
|
if anonFuncIdx < 0 {
|
|
|
|
return nil, fmt.Errorf("anon func %q for call not found", val.Name())
|
|
|
|
}
|
|
|
|
return ast.NewIdent(fc.getAnonFuncName(anonFuncIdx)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) convertCall(callCommon ssa.CallCommon) (*ast.CallExpr, error) {
|
|
|
|
callExpr := &ast.CallExpr{}
|
|
|
|
argsOffset := 0
|
|
|
|
|
|
|
|
if !callCommon.IsInvoke() {
|
|
|
|
switch val := callCommon.Value.(type) {
|
|
|
|
case *ssa.Function:
|
|
|
|
anonFuncName, err := fc.getAnonFunctionName(val)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if anonFuncName != nil {
|
|
|
|
callExpr.Fun = anonFuncName
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
thunkCall, err := fc.getThunkMethodCall(val)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if thunkCall != nil {
|
|
|
|
callExpr.Fun = thunkCall
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
hasRecv := val.Signature.Recv() != nil
|
|
|
|
methodName := ast.NewIdent(val.Name())
|
|
|
|
if hasRecv {
|
|
|
|
argsOffset = 1
|
|
|
|
recvExpr, err := fc.convertSsaValue(callCommon.Args[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
callExpr.Fun = ah.SelectExpr(recvExpr, methodName)
|
|
|
|
} else {
|
|
|
|
if val.Pkg != nil {
|
|
|
|
if pkgIdent := fc.importNameResolver(val.Pkg.Pkg); pkgIdent != nil {
|
|
|
|
callExpr.Fun = ah.SelectExpr(pkgIdent, methodName)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
callExpr.Fun = methodName
|
|
|
|
}
|
|
|
|
if typeArgs := val.TypeArgs(); len(typeArgs) > 0 {
|
|
|
|
// Generic methods are called in a monomorphic view (e.g. "someMethod[int string]"),
|
|
|
|
// so to get the original name, delete everything starting from "[" inclusive.
|
|
|
|
methodName.Name, _, _ = strings.Cut(methodName.Name, "[")
|
|
|
|
genericCallExpr := &ast.IndexListExpr{
|
|
|
|
X: callExpr.Fun,
|
|
|
|
}
|
|
|
|
|
|
|
|
// For better readability of generated code and to avoid ambiguities,
|
|
|
|
// we explicitly specify generic method types (e.g. "someMethod[int, string](0, "str")")
|
|
|
|
for _, typArg := range typeArgs {
|
|
|
|
typeExpr, err := fc.tc.Convert(typArg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
genericCallExpr.Indices = append(genericCallExpr.Indices, typeExpr)
|
|
|
|
}
|
|
|
|
callExpr.Fun = genericCallExpr
|
|
|
|
}
|
|
|
|
case *ssa.Builtin:
|
|
|
|
name := val.Name()
|
|
|
|
if _, ok := types.Unsafe.Scope().Lookup(name).(*types.Builtin); ok {
|
|
|
|
unsafePkgIdent := fc.importNameResolver(types.Unsafe)
|
|
|
|
if unsafePkgIdent == nil {
|
|
|
|
return nil, fmt.Errorf("cannot resolve unsafe package")
|
|
|
|
}
|
|
|
|
callExpr.Fun = &ast.SelectorExpr{X: unsafePkgIdent, Sel: ast.NewIdent(name)}
|
|
|
|
} else {
|
|
|
|
callExpr.Fun = ast.NewIdent(name)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
callFunExpr, err := fc.convertSsaValue(val)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
callExpr.Fun = callFunExpr
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
recvExpr, err := fc.convertSsaValue(callCommon.Value)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
callExpr.Fun = ah.SelectExpr(recvExpr, ast.NewIdent(callCommon.Method.Name()))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, arg := range callCommon.Args[argsOffset:] {
|
|
|
|
argExpr, err := fc.convertSsaValue(arg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
callExpr.Args = append(callExpr.Args, argExpr)
|
|
|
|
}
|
|
|
|
if callCommon.Signature().Variadic() {
|
|
|
|
callExpr.Ellipsis = 1
|
|
|
|
}
|
|
|
|
return callExpr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) convertSsaValueNonExplicitNil(ssaValue ssa.Value) (ast.Expr, error) {
|
|
|
|
return fc.ssaValue(ssaValue, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) convertSsaValue(ssaValue ssa.Value) (ast.Expr, error) {
|
|
|
|
return fc.ssaValue(ssaValue, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) getThunkMethodCall(val *ssa.Function) (ast.Expr, error) {
|
|
|
|
const thunkPrefix = "$thunk"
|
|
|
|
if !strings.HasSuffix(val.Name(), thunkPrefix) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
thunkType, ok := val.Object().Type().Underlying().(*types.Signature)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("unsupported thunk type: %w", ErrUnsupported)
|
|
|
|
}
|
|
|
|
recvVar := thunkType.Recv()
|
|
|
|
if recvVar == nil {
|
|
|
|
return nil, fmt.Errorf("unsupported non method thunk: %w", ErrUnsupported)
|
|
|
|
}
|
|
|
|
|
|
|
|
thunkTypeAst, err := fc.tc.Convert(recvVar.Type())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
trimmedName := ast.NewIdent(strings.TrimSuffix(val.Name(), thunkPrefix))
|
|
|
|
return ah.SelectExpr(&ast.ParenExpr{X: thunkTypeAst}, trimmedName), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) ssaValue(ssaValue ssa.Value, explicitNil bool) (expr ast.Expr, err error) {
|
|
|
|
defer func() {
|
|
|
|
if err == nil && len(fc.ssaValueRemap) > 0 {
|
|
|
|
if newExpr, ok := fc.ssaValueRemap[ssaValue]; ok {
|
|
|
|
expr = newExpr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
switch val := ssaValue.(type) {
|
|
|
|
case *ssa.Builtin:
|
|
|
|
return ast.NewIdent(val.Name()), nil
|
|
|
|
case *ssa.Global:
|
|
|
|
globalExpr := &ast.UnaryExpr{Op: token.AND}
|
|
|
|
newName := ast.NewIdent(val.Name())
|
|
|
|
if pkgIdent := fc.importNameResolver(val.Pkg.Pkg); pkgIdent != nil {
|
|
|
|
globalExpr.X = ah.SelectExpr(pkgIdent, newName)
|
|
|
|
} else {
|
|
|
|
globalExpr.X = newName
|
|
|
|
}
|
|
|
|
return globalExpr, nil
|
|
|
|
case *ssa.Function:
|
|
|
|
anonFuncName, err := fc.getAnonFunctionName(val)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if anonFuncName != nil {
|
|
|
|
return anonFuncName, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
thunkCall, err := fc.getThunkMethodCall(val)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if thunkCall != nil {
|
|
|
|
return thunkCall, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
name := ast.NewIdent(val.Name())
|
|
|
|
if val.Signature.Recv() == nil && val.Pkg != nil {
|
|
|
|
if pkgIdent := fc.importNameResolver(val.Pkg.Pkg); pkgIdent != nil {
|
|
|
|
return ah.SelectExpr(pkgIdent, name), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return name, nil
|
|
|
|
case *ssa.Const:
|
|
|
|
var constExpr ast.Expr
|
|
|
|
if val.Value == nil {
|
|
|
|
// handle nil constant for non-pointer structs
|
|
|
|
typ := val.Type()
|
|
|
|
if _, ok := typ.(*types.Named); ok {
|
|
|
|
typ = typ.Underlying()
|
|
|
|
}
|
|
|
|
if _, ok := typ.(*types.Struct); ok {
|
|
|
|
typExpr, err := fc.tc.Convert(val.Type())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &ast.CompositeLit{Type: typExpr}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
constExpr = ast.NewIdent("nil")
|
|
|
|
if !explicitNil {
|
|
|
|
return constExpr, nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
constExpr = ah.ConstToAst(val.Value)
|
|
|
|
}
|
|
|
|
|
|
|
|
if basicType, ok := val.Type().(*types.Basic); ok {
|
|
|
|
if basicType.Info()&(types.IsString|types.IsUntyped) != 0 {
|
|
|
|
return constExpr, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
castExpr, err := fc.tc.Convert(val.Type())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ah.CallExpr(&ast.ParenExpr{X: castExpr}, constExpr), nil
|
|
|
|
case *ssa.Parameter, *ssa.FreeVar:
|
|
|
|
return ast.NewIdent(val.Name()), nil
|
|
|
|
default:
|
|
|
|
return ast.NewIdent(fc.getVarName(val)), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type register interface {
|
|
|
|
Name() string
|
|
|
|
Referrers() *[]ssa.Instruction
|
|
|
|
Type() types.Type
|
|
|
|
|
|
|
|
String() string
|
|
|
|
Parent() *ssa.Function
|
|
|
|
Pos() token.Pos
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) tupleVarName(val ssa.Value, idx int) string {
|
|
|
|
return fmt.Sprintf("%s_%d", fc.getVarName(val), idx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) tupleVarNameAndType(reg ssa.Value, idx int) (name string, typ types.Type, hasRefs bool) {
|
|
|
|
tupleType := reg.Type().(*types.Tuple)
|
|
|
|
typ = tupleType.At(idx).Type()
|
|
|
|
name = "_"
|
|
|
|
|
|
|
|
refs := reg.Referrers()
|
|
|
|
if refs == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, instr := range *refs {
|
|
|
|
extractInstr, ok := instr.(*ssa.Extract)
|
|
|
|
if ok && extractInstr.Index == idx {
|
|
|
|
hasRefs = true
|
|
|
|
name = fc.tupleVarName(reg, idx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func isNilValue(value ssa.Value) bool {
|
|
|
|
constVal, ok := value.(*ssa.Const)
|
|
|
|
return ok && constVal.Value == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) convertBlock(astFunc *AstFunc, ssaBlock *ssa.BasicBlock, astBlock *AstBlock) error {
|
|
|
|
astBlock.HasRefs = len(ssaBlock.Preds) != 0
|
|
|
|
|
|
|
|
defineTypedVar := func(r register, typ types.Type, expr ast.Expr) ast.Stmt {
|
|
|
|
if isVoidType(typ) {
|
|
|
|
return &ast.ExprStmt{X: expr}
|
|
|
|
}
|
|
|
|
if tuple, ok := typ.(*types.Tuple); ok {
|
|
|
|
assignStmt := &ast.AssignStmt{Tok: token.ASSIGN, Rhs: []ast.Expr{expr}}
|
|
|
|
localTuple := true
|
|
|
|
tmpVars := make(map[string]types.Type)
|
|
|
|
|
|
|
|
for i := range tuple.Len() {
|
|
|
|
name, typ, hasRefs := fc.tupleVarNameAndType(r, i)
|
|
|
|
tmpVars[name] = typ
|
|
|
|
if hasRefs {
|
|
|
|
localTuple = false
|
|
|
|
}
|
|
|
|
assignStmt.Lhs = append(assignStmt.Lhs, ast.NewIdent(name))
|
|
|
|
}
|
|
|
|
|
|
|
|
if !localTuple {
|
|
|
|
maps.Copy(astFunc.Vars, tmpVars)
|
|
|
|
}
|
|
|
|
|
|
|
|
return assignStmt
|
|
|
|
}
|
|
|
|
|
|
|
|
refs := r.Referrers()
|
|
|
|
if refs == nil || len(*refs) == 0 {
|
|
|
|
return ah.AssignStmt(ast.NewIdent("_"), expr)
|
|
|
|
}
|
|
|
|
|
|
|
|
localVar := true
|
|
|
|
for _, refInstr := range *refs {
|
|
|
|
if _, ok := refInstr.(*ssa.Phi); ok || refInstr.Block() != ssaBlock {
|
|
|
|
localVar = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newName := fc.getVarName(r)
|
|
|
|
assignStmt := ah.AssignDefineStmt(ast.NewIdent(newName), expr)
|
|
|
|
if !localVar {
|
|
|
|
assignStmt.Tok = token.ASSIGN
|
|
|
|
astFunc.Vars[newName] = typ
|
|
|
|
}
|
|
|
|
return assignStmt
|
|
|
|
}
|
|
|
|
defineVar := func(r register, expr ast.Expr) ast.Stmt {
|
|
|
|
return defineTypedVar(r, r.Type(), expr)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, instr := range ssaBlock.Instrs[:len(ssaBlock.Instrs)-1] {
|
|
|
|
if instr == MarkerInstr {
|
|
|
|
if fc.markerInstrCallback == nil {
|
|
|
|
panic("marker callback is nil")
|
|
|
|
}
|
|
|
|
astBlock.Body = append(astBlock.Body, nil)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var stmt ast.Stmt
|
|
|
|
switch instr := instr.(type) {
|
|
|
|
case *ssa.Alloc:
|
|
|
|
varType := instr.Type().Underlying().(*types.Pointer).Elem()
|
|
|
|
varExpr, err := fc.tc.Convert(varType)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, ah.CallExprByName("new", varExpr))
|
|
|
|
case *ssa.BinOp:
|
|
|
|
xExpr, err := fc.convertSsaValueNonExplicitNil(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var yExpr ast.Expr
|
|
|
|
// Handle special case: if nil == nil
|
|
|
|
if isNilValue(instr.X) && isNilValue(instr.Y) {
|
|
|
|
yExpr, err = fc.convertSsaValue(instr.Y)
|
|
|
|
} else {
|
|
|
|
yExpr, err = fc.convertSsaValueNonExplicitNil(instr.Y)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt = defineVar(instr, &ast.BinaryExpr{
|
|
|
|
X: xExpr,
|
|
|
|
Op: instr.Op,
|
|
|
|
Y: yExpr,
|
|
|
|
})
|
|
|
|
case *ssa.Call:
|
|
|
|
callFunExpr, err := fc.convertCall(instr.Call)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, callFunExpr)
|
|
|
|
case *ssa.ChangeInterface:
|
|
|
|
castExpr, err := fc.castCallExpr(instr.Type(), instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, castExpr)
|
|
|
|
case *ssa.ChangeType:
|
|
|
|
castExpr, err := fc.castCallExpr(instr.Type(), instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, castExpr)
|
|
|
|
case *ssa.Convert:
|
|
|
|
castExpr, err := fc.castCallExpr(instr.Type(), instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, castExpr)
|
|
|
|
case *ssa.Defer:
|
|
|
|
callExpr, err := fc.convertCall(instr.Call)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = &ast.DeferStmt{Call: callExpr}
|
|
|
|
case *ssa.Extract:
|
|
|
|
name := fc.tupleVarName(instr.Tuple, instr.Index)
|
|
|
|
stmt = defineVar(instr, ast.NewIdent(name))
|
|
|
|
case *ssa.Field:
|
|
|
|
xExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldName, err := getFieldName(instr.X.Type(), instr.Field)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, ah.SelectExpr(xExpr, ast.NewIdent(fieldName)))
|
|
|
|
case *ssa.FieldAddr:
|
|
|
|
xExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldName, err := getFieldName(instr.X.Type(), instr.Field)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, &ast.UnaryExpr{
|
|
|
|
Op: token.AND,
|
|
|
|
X: ah.SelectExpr(xExpr, ast.NewIdent(fieldName)),
|
|
|
|
})
|
|
|
|
case *ssa.Go:
|
|
|
|
callExpr, err := fc.convertCall(instr.Call)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = &ast.GoStmt{Call: callExpr}
|
|
|
|
case *ssa.Index:
|
|
|
|
xExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
indexExpr, err := fc.convertSsaValue(instr.Index)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, ah.IndexExprByExpr(xExpr, indexExpr))
|
|
|
|
case *ssa.IndexAddr:
|
|
|
|
xExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
indexExpr, err := fc.convertSsaValue(instr.Index)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, &ast.UnaryExpr{Op: token.AND, X: ah.IndexExprByExpr(xExpr, indexExpr)})
|
|
|
|
case *ssa.Lookup:
|
|
|
|
mapExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
indexExpr, err := fc.convertSsaValue(instr.Index)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
mapIndexExpr := ah.IndexExprByExpr(mapExpr, indexExpr)
|
|
|
|
if instr.CommaOk {
|
|
|
|
valName, valType, valHasRefs := fc.tupleVarNameAndType(instr, 0)
|
|
|
|
okName, okType, okHasRefs := fc.tupleVarNameAndType(instr, 1)
|
|
|
|
|
|
|
|
if valHasRefs {
|
|
|
|
astFunc.Vars[valName] = valType
|
|
|
|
}
|
|
|
|
if okHasRefs {
|
|
|
|
astFunc.Vars[okName] = okType
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt = &ast.AssignStmt{
|
|
|
|
Lhs: []ast.Expr{ast.NewIdent(valName), ast.NewIdent(okName)},
|
|
|
|
Tok: token.ASSIGN,
|
|
|
|
Rhs: []ast.Expr{mapIndexExpr},
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
stmt = defineVar(instr, mapIndexExpr)
|
|
|
|
}
|
|
|
|
case *ssa.MakeChan:
|
|
|
|
chanExpr, err := fc.tc.Convert(instr.Type())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
makeExpr := ah.CallExprByName("make", chanExpr)
|
|
|
|
if instr.Size != nil {
|
|
|
|
reserveExpr, err := fc.convertSsaValue(instr.Size)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
makeExpr.Args = append(makeExpr.Args, reserveExpr)
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, makeExpr)
|
|
|
|
case *ssa.MakeInterface:
|
|
|
|
castExpr, err := fc.castCallExpr(instr.Type(), instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, castExpr)
|
|
|
|
case *ssa.MakeMap:
|
|
|
|
mapExpr, err := fc.tc.Convert(instr.Type())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
makeExpr := ah.CallExprByName("make", mapExpr)
|
|
|
|
if instr.Reserve != nil {
|
|
|
|
reserveExpr, err := fc.convertSsaValue(instr.Reserve)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
makeExpr.Args = append(makeExpr.Args, reserveExpr)
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, makeExpr)
|
|
|
|
case *ssa.MakeSlice:
|
|
|
|
sliceExpr, err := fc.tc.Convert(instr.Type())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
lenExpr, err := fc.convertSsaValue(instr.Len)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
capExpr, err := fc.convertSsaValue(instr.Cap)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, ah.CallExprByName("make", sliceExpr, lenExpr, capExpr))
|
|
|
|
case *ssa.MapUpdate:
|
|
|
|
mapExpr, err := fc.convertSsaValue(instr.Map)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
keyExpr, err := fc.convertSsaValue(instr.Key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
valueExpr, err := fc.convertSsaValue(instr.Value)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = ah.AssignStmt(ah.IndexExprByExpr(mapExpr, keyExpr), valueExpr)
|
|
|
|
case *ssa.Next:
|
|
|
|
okName, okType, okHasRefs := fc.tupleVarNameAndType(instr, 0)
|
|
|
|
keyName, keyType, keyHasRefs := fc.tupleVarNameAndType(instr, 1)
|
|
|
|
valName, valType, valHasRefs := fc.tupleVarNameAndType(instr, 2)
|
|
|
|
if okHasRefs {
|
|
|
|
astFunc.Vars[okName] = okType
|
|
|
|
}
|
|
|
|
if keyHasRefs {
|
|
|
|
astFunc.Vars[keyName] = keyType
|
|
|
|
}
|
|
|
|
if valHasRefs {
|
|
|
|
astFunc.Vars[valName] = valType
|
|
|
|
}
|
|
|
|
|
|
|
|
if instr.IsString {
|
|
|
|
idxName := fc.tupleVarName(instr.Iter, 0)
|
|
|
|
iterValName := fc.tupleVarName(instr.Iter, 1)
|
|
|
|
|
|
|
|
stmt = ah.BlockStmt(
|
|
|
|
ah.AssignStmt(ast.NewIdent(okName), &ast.BinaryExpr{
|
|
|
|
X: ast.NewIdent(idxName),
|
|
|
|
Op: token.LSS,
|
|
|
|
Y: ah.CallExprByName("len", ast.NewIdent(iterValName)),
|
|
|
|
}),
|
|
|
|
&ast.IfStmt{
|
|
|
|
Cond: ast.NewIdent(okName),
|
|
|
|
Body: ah.BlockStmt(
|
|
|
|
&ast.AssignStmt{
|
|
|
|
Lhs: []ast.Expr{ast.NewIdent(keyName), ast.NewIdent(valName)},
|
|
|
|
Tok: token.ASSIGN,
|
|
|
|
Rhs: []ast.Expr{ast.NewIdent(idxName), ah.IndexExprByExpr(ast.NewIdent(iterValName), ast.NewIdent(idxName))},
|
|
|
|
},
|
|
|
|
&ast.IncDecStmt{X: ast.NewIdent(idxName), Tok: token.INC},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
stmt = &ast.AssignStmt{
|
|
|
|
Lhs: []ast.Expr{ast.NewIdent(okName), ast.NewIdent(keyName), ast.NewIdent(valName)},
|
|
|
|
Tok: token.ASSIGN,
|
|
|
|
Rhs: []ast.Expr{ah.CallExprByName(fc.getVarName(instr.Iter))},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *ssa.Phi:
|
|
|
|
phiName := fc.getVarName(instr)
|
|
|
|
astFunc.Vars[phiName] = instr.Type()
|
|
|
|
|
|
|
|
for predIdx, edge := range instr.Edges {
|
|
|
|
edgeExpr, err := fc.convertSsaValue(edge)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
blockIdx := ssaBlock.Preds[predIdx].Index
|
|
|
|
astFunc.Blocks[blockIdx].Phi = append(astFunc.Blocks[blockIdx].Phi, ah.AssignStmt(ast.NewIdent(phiName), edgeExpr))
|
|
|
|
}
|
|
|
|
case *ssa.Range:
|
|
|
|
xExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if isStringType(instr.X.Type()) {
|
|
|
|
idxName := fc.tupleVarName(instr, 0)
|
|
|
|
valName := fc.tupleVarName(instr, 1)
|
|
|
|
|
|
|
|
astFunc.Vars[idxName] = types.Typ[types.Int]
|
|
|
|
astFunc.Vars[valName] = types.NewSlice(types.Typ[types.Rune])
|
|
|
|
|
|
|
|
stmt = &ast.AssignStmt{
|
|
|
|
Lhs: []ast.Expr{ast.NewIdent(idxName), ast.NewIdent(valName)},
|
|
|
|
Tok: token.ASSIGN,
|
|
|
|
Rhs: []ast.Expr{
|
|
|
|
ah.IntLit(0),
|
|
|
|
ah.CallExpr(&ast.ArrayType{Elt: ast.NewIdent("rune")}, xExpr),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
makeIterExpr, nextType, err := makeMapIteratorPolyfill(fc.tc, instr.X.Type().(*types.Map))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt = defineTypedVar(instr, nextType, ah.CallExpr(makeIterExpr, xExpr))
|
|
|
|
}
|
|
|
|
case *ssa.Select:
|
|
|
|
const reservedTupleIdx = 2
|
|
|
|
|
|
|
|
indexName, indexType, indexHasRefs := fc.tupleVarNameAndType(instr, 0)
|
|
|
|
okName, okType, okHasRefs := fc.tupleVarNameAndType(instr, 1)
|
|
|
|
if indexHasRefs {
|
|
|
|
astFunc.Vars[indexName] = indexType
|
|
|
|
}
|
|
|
|
if okHasRefs {
|
|
|
|
astFunc.Vars[okName] = okType
|
|
|
|
}
|
|
|
|
|
|
|
|
var stmts []ast.Stmt
|
|
|
|
|
|
|
|
recvIndex := 0
|
|
|
|
for idx, state := range instr.States {
|
|
|
|
chanExpr, err := fc.convertSsaValue(state.Chan)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var commStmt ast.Stmt
|
|
|
|
switch state.Dir {
|
|
|
|
case types.SendOnly:
|
|
|
|
valueExpr, err := fc.convertSsaValue(state.Send)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
commStmt = &ast.SendStmt{Chan: chanExpr, Value: valueExpr}
|
|
|
|
case types.RecvOnly:
|
|
|
|
valName, valType, valHasRefs := fc.tupleVarNameAndType(instr, reservedTupleIdx+recvIndex)
|
|
|
|
if valHasRefs {
|
|
|
|
astFunc.Vars[valName] = valType
|
|
|
|
}
|
|
|
|
commStmt = ah.AssignStmt(ast.NewIdent(valName), &ast.UnaryExpr{Op: token.ARROW, X: chanExpr})
|
|
|
|
recvIndex++
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("not supported select chan dir %d: %w", state.Dir, ErrUnsupported)
|
|
|
|
}
|
|
|
|
|
|
|
|
stmts = append(stmts, &ast.CommClause{
|
|
|
|
Comm: commStmt,
|
|
|
|
Body: []ast.Stmt{
|
|
|
|
ah.AssignStmt(ast.NewIdent(indexName), ah.IntLit(idx)),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if !instr.Blocking {
|
|
|
|
stmts = append(stmts, &ast.CommClause{Body: []ast.Stmt{ah.AssignStmt(ast.NewIdent(indexName), ah.IntLit(len(instr.States)))}})
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt = &ast.SelectStmt{Body: ah.BlockStmt(stmts...)}
|
|
|
|
case *ssa.Send:
|
|
|
|
chanExpr, err := fc.convertSsaValue(instr.Chan)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
valExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = &ast.SendStmt{
|
|
|
|
Chan: chanExpr,
|
|
|
|
Value: valExpr,
|
|
|
|
}
|
|
|
|
case *ssa.Slice:
|
|
|
|
valExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
sliceExpr := &ast.SliceExpr{X: valExpr}
|
|
|
|
if instr.Low != nil {
|
|
|
|
sliceExpr.Low, err = fc.convertSsaValue(instr.Low)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if instr.High != nil {
|
|
|
|
sliceExpr.High, err = fc.convertSsaValue(instr.High)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if instr.Max != nil {
|
|
|
|
sliceExpr.Max, err = fc.convertSsaValue(instr.Max)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, sliceExpr)
|
|
|
|
case *ssa.SliceToArrayPointer:
|
|
|
|
castExpr, err := fc.tc.Convert(instr.Type())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
xExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = defineVar(instr, ah.CallExpr(&ast.ParenExpr{X: castExpr}, xExpr))
|
|
|
|
case *ssa.Store:
|
|
|
|
addrExpr, err := fc.convertSsaValue(instr.Addr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
valExpr, err := fc.convertSsaValue(instr.Val)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
stmt = ah.AssignStmt(&ast.StarExpr{X: addrExpr}, valExpr)
|
|
|
|
case *ssa.TypeAssert:
|
|
|
|
valExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
assertTypeExpr, err := fc.tc.Convert(instr.AssertedType)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if instr.CommaOk {
|
|
|
|
valName, valType, valHasRefs := fc.tupleVarNameAndType(instr, 0)
|
|
|
|
okName, okType, okHasRefs := fc.tupleVarNameAndType(instr, 1)
|
|
|
|
if valHasRefs {
|
|
|
|
astFunc.Vars[valName] = valType
|
|
|
|
}
|
|
|
|
if okHasRefs {
|
|
|
|
astFunc.Vars[okName] = okType
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt = &ast.AssignStmt{
|
|
|
|
Lhs: []ast.Expr{ast.NewIdent(valName), ast.NewIdent(okName)},
|
|
|
|
Tok: token.ASSIGN,
|
|
|
|
Rhs: []ast.Expr{&ast.TypeAssertExpr{X: valExpr, Type: assertTypeExpr}},
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
stmt = defineVar(instr, &ast.TypeAssertExpr{X: valExpr, Type: assertTypeExpr})
|
|
|
|
}
|
|
|
|
case *ssa.UnOp:
|
|
|
|
valExpr, err := fc.convertSsaValue(instr.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if instr.CommaOk {
|
|
|
|
if instr.Op != token.ARROW {
|
|
|
|
return fmt.Errorf("unary operator %q in %v: %w", instr.Op, instr, ErrUnsupported)
|
|
|
|
}
|
|
|
|
|
|
|
|
valName, valType, valHasRefs := fc.tupleVarNameAndType(instr, 0)
|
|
|
|
okName, okType, okHasRefs := fc.tupleVarNameAndType(instr, 1)
|
|
|
|
if valHasRefs {
|
|
|
|
astFunc.Vars[valName] = valType
|
|
|
|
}
|
|
|
|
if okHasRefs {
|
|
|
|
astFunc.Vars[okName] = okType
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt = &ast.AssignStmt{
|
|
|
|
Lhs: []ast.Expr{ast.NewIdent(valName), ast.NewIdent(okName)},
|
|
|
|
Tok: token.ASSIGN,
|
|
|
|
Rhs: []ast.Expr{&ast.UnaryExpr{
|
|
|
|
Op: token.ARROW,
|
|
|
|
X: valExpr,
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
} else if instr.Op == token.MUL {
|
|
|
|
stmt = defineVar(instr, &ast.StarExpr{X: valExpr})
|
|
|
|
} else {
|
|
|
|
stmt = defineVar(instr, &ast.UnaryExpr{Op: instr.Op, X: valExpr})
|
|
|
|
}
|
|
|
|
case *ssa.MakeClosure:
|
|
|
|
anonFunc := instr.Fn.(*ssa.Function)
|
|
|
|
anonFuncName, err := fc.getAnonFunctionName(anonFunc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if anonFuncName == nil {
|
|
|
|
return fmt.Errorf("make closure for non anon func %q: %w", anonFunc.Name(), ErrUnsupported)
|
|
|
|
}
|
|
|
|
|
|
|
|
callExpr := &ast.CallExpr{Fun: anonFuncName}
|
|
|
|
for _, freeVar := range instr.Bindings {
|
|
|
|
varExr, err := fc.convertSsaValue(freeVar)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
callExpr.Args = append(callExpr.Args, varExr)
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt = defineVar(instr, callExpr)
|
|
|
|
case *ssa.RunDefers, *ssa.DebugRef:
|
|
|
|
// ignored
|
|
|
|
continue
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("instruction %v: %w", instr, ErrUnsupported)
|
|
|
|
}
|
|
|
|
|
|
|
|
if stmt != nil {
|
|
|
|
astBlock.Body = append(astBlock.Body, stmt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exitInstr := ssaBlock.Instrs[len(ssaBlock.Instrs)-1]
|
|
|
|
switch exit := exitInstr.(type) {
|
|
|
|
case *ssa.Jump:
|
|
|
|
targetBlockIdx := ssaBlock.Succs[0].Index
|
|
|
|
astBlock.Exit = fc.gotoStmt(targetBlockIdx)
|
|
|
|
case *ssa.If:
|
|
|
|
tblock := ssaBlock.Succs[0].Index
|
|
|
|
fblock := ssaBlock.Succs[1].Index
|
|
|
|
|
|
|
|
condExpr, err := fc.convertSsaValue(exit.Cond)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
astBlock.Exit = &ast.IfStmt{
|
|
|
|
Cond: condExpr,
|
|
|
|
Body: ah.BlockStmt(fc.gotoStmt(tblock)),
|
|
|
|
Else: ah.BlockStmt(fc.gotoStmt(fblock)),
|
|
|
|
}
|
|
|
|
case *ssa.Return:
|
|
|
|
exitStmt := &ast.ReturnStmt{}
|
|
|
|
for _, result := range exit.Results {
|
|
|
|
resultExpr, err := fc.convertSsaValue(result)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
exitStmt.Results = append(exitStmt.Results, resultExpr)
|
|
|
|
}
|
|
|
|
astBlock.Exit = exitStmt
|
|
|
|
case *ssa.Panic:
|
|
|
|
panicArgExpr, err := fc.convertSsaValue(exit.X)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
astBlock.Exit = &ast.ExprStmt{X: ah.CallExprByName("panic", panicArgExpr)}
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("exit instruction %v: %w", exit, ErrUnsupported)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) getAnonFuncName(idx int) string {
|
|
|
|
return fmt.Sprintf(fc.namePrefix+"anonFunc%d", idx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) convertAnonFuncs(anonFuncs []*ssa.Function) ([]ast.Stmt, error) {
|
|
|
|
var stmts []ast.Stmt
|
|
|
|
|
|
|
|
for i, anonFunc := range anonFuncs {
|
|
|
|
anonLit, err := fc.convertSignatureToFuncLit(anonFunc.Signature)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
anonStmts, err := fc.convertToStmts(anonFunc)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
anonLit.Body = ah.BlockStmt(anonStmts...)
|
|
|
|
|
|
|
|
if len(anonFunc.FreeVars) == 0 {
|
|
|
|
stmts = append(stmts, &ast.AssignStmt{
|
|
|
|
Lhs: []ast.Expr{ast.NewIdent(fc.getAnonFuncName(i))},
|
|
|
|
Tok: token.DEFINE,
|
|
|
|
Rhs: []ast.Expr{anonLit},
|
|
|
|
})
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var closureVars []*types.Var
|
|
|
|
for _, freeVar := range anonFunc.FreeVars {
|
|
|
|
closureVars = append(closureVars, types.NewVar(token.NoPos, nil, freeVar.Name(), freeVar.Type()))
|
|
|
|
}
|
|
|
|
|
|
|
|
makeClosureType := types.NewSignatureType(nil, nil, nil, types.NewTuple(closureVars...), types.NewTuple(
|
|
|
|
types.NewVar(token.NoPos, nil, "", anonFunc.Signature),
|
|
|
|
), false)
|
|
|
|
|
|
|
|
makeClosureLit, err := fc.convertSignatureToFuncLit(makeClosureType)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
makeClosureLit.Body = ah.BlockStmt(&ast.ReturnStmt{Results: []ast.Expr{anonLit}})
|
|
|
|
|
|
|
|
stmts = append(stmts, &ast.AssignStmt{
|
|
|
|
Lhs: []ast.Expr{ast.NewIdent(fc.getAnonFuncName(i))},
|
|
|
|
Tok: token.DEFINE,
|
|
|
|
Rhs: []ast.Expr{makeClosureLit},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return stmts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) convertToStmts(ssaFunc *ssa.Function) ([]ast.Stmt, error) {
|
|
|
|
stmts, err := fc.convertAnonFuncs(ssaFunc.AnonFuncs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
f := &AstFunc{
|
|
|
|
Vars: make(map[string]types.Type),
|
|
|
|
Blocks: make([]*AstBlock, len(ssaFunc.Blocks)),
|
|
|
|
}
|
|
|
|
for i := range f.Blocks {
|
|
|
|
f.Blocks[i] = &AstBlock{Index: ssaFunc.Blocks[i].Index}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, ssaBlock := range ssaFunc.Blocks {
|
|
|
|
if err := fc.convertBlock(f, ssaBlock, f.Blocks[i]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
groupedVar := make(map[types.Type][]string)
|
|
|
|
for varName, varType := range f.Vars {
|
|
|
|
exists := false
|
|
|
|
for groupedType, names := range groupedVar {
|
|
|
|
if types.Identical(varType, groupedType) {
|
|
|
|
groupedVar[groupedType] = append(names, varName)
|
|
|
|
exists = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
groupedVar[varType] = []string{varName}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var specs []ast.Spec
|
|
|
|
for varType, varNames := range groupedVar {
|
|
|
|
typeExpr, err := fc.tc.Convert(varType)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
spec := &ast.ValueSpec{
|
|
|
|
Type: typeExpr,
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(varNames)
|
|
|
|
for _, name := range varNames {
|
|
|
|
spec.Names = append(spec.Names, ast.NewIdent(name))
|
|
|
|
}
|
|
|
|
specs = append(specs, spec)
|
|
|
|
}
|
|
|
|
if len(specs) > 0 {
|
|
|
|
stmts = append(stmts, &ast.DeclStmt{Decl: &ast.GenDecl{
|
|
|
|
Tok: token.VAR,
|
|
|
|
Specs: specs,
|
|
|
|
}})
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, block := range f.Blocks {
|
|
|
|
if fc.markerInstrCallback != nil {
|
|
|
|
var newBody []ast.Stmt
|
|
|
|
for _, stmt := range block.Body {
|
|
|
|
if stmt != nil {
|
|
|
|
newBody = append(newBody, stmt)
|
|
|
|
} else {
|
|
|
|
newBody = append(newBody, fc.markerInstrCallback(f.Vars)...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
block.Body = newBody
|
|
|
|
}
|
|
|
|
|
|
|
|
blockStmts := &ast.BlockStmt{List: append(block.Body, block.Phi...)}
|
|
|
|
blockStmts.List = append(blockStmts.List, block.Exit)
|
|
|
|
if block.HasRefs {
|
|
|
|
stmts = append(stmts, &ast.LabeledStmt{Label: fc.getLabelName(block.Index), Stmt: blockStmts})
|
|
|
|
} else {
|
|
|
|
stmts = append(stmts, blockStmts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stmts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fc *funcConverter) convert(ssaFunc *ssa.Function) (*ast.FuncDecl, error) {
|
|
|
|
funcDecl, err := fc.convertSignatureToFuncDecl(ssaFunc.Name(), ssaFunc.Signature)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
funcStmts, err := fc.convertToStmts(ssaFunc)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
funcDecl.Body = ah.BlockStmt(funcStmts...)
|
|
|
|
return funcDecl, err
|
|
|
|
}
|