github.com/goplus/gossa@v0.3.25/cmd/qexp/ssa.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "go/build" 6 "go/constant" 7 "go/token" 8 "go/types" 9 "strings" 10 11 "golang.org/x/tools/go/loader" 12 "golang.org/x/tools/go/ssa" 13 "golang.org/x/tools/go/ssa/ssautil" 14 ) 15 16 type Program struct { 17 prog *ssa.Program 18 } 19 20 func loadProgram(path string, ctx *build.Context) (*Program, error) { 21 var cfg loader.Config 22 cfg.Build = ctx 23 cfg.Import(path) 24 25 iprog, err := cfg.Load() 26 if err != nil { 27 return nil, fmt.Errorf("conf.Load failed: %s", err) 28 } 29 30 prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions|ssa.NaiveForm) 31 prog.Build() 32 return &Program{prog: prog}, nil 33 } 34 35 func (p *Program) DumpDeps(path string) { 36 pkg := p.prog.ImportedPackage(path) 37 for _, im := range pkg.Pkg.Imports() { 38 fmt.Println(im.Path()) 39 } 40 } 41 42 func (p *Program) dumpDeps(path string, sep string) { 43 pkg := p.prog.ImportedPackage(path) 44 for _, im := range pkg.Pkg.Imports() { 45 fmt.Println(sep, im.Path()) 46 p.dumpDeps(im.Path(), sep+" ") 47 } 48 } 49 50 func (p *Program) DumpExport(path string) { 51 pkg := p.prog.ImportedPackage(path) 52 for k, v := range pkg.Members { 53 if token.IsExported(k) { 54 fmt.Printf("%v %v %T\n", k, v, v) 55 } 56 } 57 } 58 59 /* 60 type ConstValue struct { 61 Typ string 62 Value constant.Value 63 } 64 65 type Package struct { 66 Name string 67 Path string 68 Types []reflect.Type 69 Vars map[string]reflect.Value 70 Funcs map[string]reflect.Value 71 Methods map[string]reflect.Value 72 Consts map[string]ConstValue 73 Deps map[string]string 74 } 75 76 */ 77 78 type Package struct { 79 Name string 80 Path string 81 Deps []string 82 NamedTypes []string 83 Interfaces []string 84 AliasTypes []string 85 Vars []string 86 Funcs []string 87 Methods []string 88 Consts []string 89 TypedConsts []string 90 UntypedConsts []string 91 } 92 93 /* 94 func unmarshalFloat(str string) constant.Value { 95 if sep := strings.IndexByte(str, '/'); sep >= 0 { 96 x := constant.MakeFromLiteral(str[:sep], token.FLOAT, 0) 97 y := constant.MakeFromLiteral(str[sep+1:], token.FLOAT, 0) 98 return constant.BinaryOp(x, token.QUO, y) 99 } 100 return constant.MakeFromLiteral(str, token.FLOAT, 0) 101 } 102 */ 103 104 func (p *Program) constToLit(named string, c constant.Value) string { 105 switch c.Kind() { 106 case constant.Bool: 107 if named != "" { 108 return fmt.Sprintf("constant.MakeBool(bool(%v))", named) 109 } 110 return fmt.Sprintf("constant.MakeBool(%v)", constant.BoolVal(c)) 111 case constant.String: 112 if named != "" { 113 return fmt.Sprintf("constant.MakeString(string(%v))", named) 114 } 115 return fmt.Sprintf("constant.MakeString(%q)", constant.StringVal(c)) 116 case constant.Int: 117 if v, ok := constant.Int64Val(c); ok { 118 if named != "" { 119 return fmt.Sprintf("constant.MakeInt64(int64(%v))", named) 120 } 121 return fmt.Sprintf("constant.MakeInt64(%v)", v) 122 } else if v, ok := constant.Uint64Val(c); ok { 123 if named != "" { 124 return fmt.Sprintf("constant.MakeUint64(uint64(%v))", named) 125 } 126 return fmt.Sprintf("constant.MakeUint64(%v)", v) 127 } 128 return fmt.Sprintf("constant.MakeFromLiteral(%q, token.INT, 0)", c.ExactString()) 129 case constant.Float: 130 s := c.ExactString() 131 if pos := strings.IndexByte(s, '/'); pos >= 0 { 132 sx := s[:pos] 133 sy := s[pos+1:] 134 // simplify 314/100 => 3.14 135 // 80901699437494742410229341718281905886015458990288143106772431 136 // 50000000000000000000000000000000000000000000000000000000000000 137 if strings.HasPrefix(sy, "1") && strings.Count(sy, "0") == len(sy)-1 { 138 if len(sx) == len(sy) { 139 return fmt.Sprintf("constant.MakeFromLiteral(\"%v.%v\", token.FLOAT, 0)", sx[:1], sx[1:]) 140 } else if len(sx) == len(sy)-1 { 141 return fmt.Sprintf("constant.MakeFromLiteral(\"0.%v\", token.FLOAT, 0)", sx) 142 } else if len(sx) < len(sy) { 143 return fmt.Sprintf("constant.MakeFromLiteral(\"%v.%ve-%v\", token.FLOAT, 0)", sx[:1], sx[1:], len(sy)-len(sx)) 144 } 145 } else if strings.HasPrefix(sy, "5") && strings.Count(sy, "0") == len(sy)-1 { 146 if len(sx) == len(sy) { 147 c := constant.BinaryOp(constant.MakeFromLiteral(sx, token.INT, 0), token.MUL, constant.MakeInt64(2)) 148 sx = c.ExactString() 149 return fmt.Sprintf("constant.MakeFromLiteral(\"%v.%v\", token.FLOAT, 0)", sx[:1], sx[1:]) 150 } 151 } else if strings.HasPrefix(sx, "1") && strings.Count(sx, "0") == len(sx)-1 { 152 // skip 153 } 154 x := fmt.Sprintf("constant.MakeFromLiteral(%q, token.INT, 0)", sx) 155 y := fmt.Sprintf("constant.MakeFromLiteral(%q, token.INT, 0)", sy) 156 return fmt.Sprintf("constant.BinaryOp(%v, token.QUO, %v)", x, y) 157 } 158 if pos := strings.LastIndexAny(s, "123456789"); pos != -1 { 159 sx := s[:pos+1] 160 return fmt.Sprintf("constant.MakeFromLiteral(\"%v.%ve+%v\", token.FLOAT, 0)", sx[:1], sx[1:], len(s)-1) 161 } 162 return fmt.Sprintf("constant.MakeFromLiteral(%q, token.FLOAT, 0)", s) 163 case constant.Complex: 164 re := p.constToLit("", constant.Real(c)) 165 im := p.constToLit("", constant.Imag(c)) 166 return fmt.Sprintf("constant.BinaryOp(%v, token.ADD, constan.MakeImag(%v))", re, im) 167 default: 168 panic("unreachable") 169 } 170 } 171 172 func (p *Program) ExportPkg(path string, sname string) *Package { 173 pkg := p.prog.ImportedPackage(path) 174 pkgPath := pkg.Pkg.Path() 175 pkgName := pkg.Pkg.Name() 176 e := &Package{Name: pkgName, Path: pkgPath} 177 pkgName = sname 178 for _, v := range pkg.Pkg.Imports() { 179 e.Deps = append(e.Deps, fmt.Sprintf("%q: %q", v.Path(), v.Name())) 180 } 181 checked := make(map[ssa.Member]bool) 182 for k, v := range pkg.Members { 183 if token.IsExported(k) { 184 if checked[v] { 185 continue 186 } 187 checked[v] = true 188 switch t := v.(type) { 189 case *ssa.NamedConst: 190 named := pkgName + "." + t.Name() 191 if typ := t.Type().String(); strings.HasPrefix(typ, "untyped ") { 192 e.UntypedConsts = append(e.UntypedConsts, fmt.Sprintf("%q: {%q, %v}", t.Name(), t.Type().String(), p.constToLit(named, t.Value.Value))) 193 } else { 194 e.TypedConsts = append(e.TypedConsts, fmt.Sprintf("%q : {reflect.TypeOf(%v), %v}", t.Name(), pkgName+"."+t.Name(), p.constToLit(named, t.Value.Value))) 195 } 196 case *ssa.Global: 197 e.Vars = append(e.Vars, fmt.Sprintf("%q : reflect.ValueOf(&%v)", t.Name(), pkgName+"."+t.Name())) 198 case *ssa.Function: 199 e.Funcs = append(e.Funcs, fmt.Sprintf("%q : reflect.ValueOf(%v)", t.Name(), pkgName+"."+t.Name())) 200 case *ssa.Type: 201 typ := t.Type() 202 if obj, ok := t.Object().(*types.TypeName); ok && obj.IsAlias() { 203 name := obj.Name() 204 switch typ := obj.Type().(type) { 205 case *types.Basic: 206 e.AliasTypes = append(e.AliasTypes, fmt.Sprintf("%q: reflect.TypeOf((*%v)(nil)).Elem()", name, typ.Name())) 207 // case *types.Named: 208 // e.AliasTypes = append(e.AliasTypes, fmt.Sprintf("%q: reflect.TypeOf((*%v.%v)(nil)).Elem()", name, sname, name)) 209 default: 210 e.AliasTypes = append(e.AliasTypes, fmt.Sprintf("%q: reflect.TypeOf((*%v.%v)(nil)).Elem()", name, sname, name)) 211 } 212 continue 213 } 214 var ms, pms []string 215 recvId := typ.String() 216 typeName := typ.(*types.Named).Obj().Name() 217 methods := IntuitiveMethodSet(typ) 218 for _, method := range methods { 219 name := method.Obj().Name() 220 mid := method.Obj().Type().(*types.Signature).Recv().Type().String() 221 if mid[0] == '*' { 222 if mid[1:] == recvId { 223 pms = append(pms, name) 224 } 225 } else if mid == recvId { 226 ms = append(ms, name) 227 } 228 if token.IsExported(name) { 229 info := fmt.Sprintf("(%v).%v", method.Recv(), name) 230 if pkgPath == pkgName { 231 e.Methods = append(e.Methods, fmt.Sprintf("%q : reflect.ValueOf(%v)", info, info)) 232 } else { 233 var fn string 234 if isPointer(method.Recv()) { 235 fn = fmt.Sprintf("(*%v.%v).%v", pkgName, typeName, name) 236 } else { 237 fn = fmt.Sprintf("(%v.%v).%v", pkgName, typeName, name) 238 } 239 e.Methods = append(e.Methods, fmt.Sprintf("%q: reflect.ValueOf(%v)", info, fn)) 240 } 241 } 242 } 243 if types.IsInterface(typ) { 244 e.Interfaces = append(e.Interfaces, fmt.Sprintf("%q : reflect.TypeOf((*%v.%v)(nil)).Elem()", typeName, pkgName, typeName)) 245 } else { 246 e.NamedTypes = append(e.NamedTypes, fmt.Sprintf("%q : {reflect.TypeOf((*%v.%v)(nil)).Elem(),\"%v\",\"%v\"}", typeName, pkgName, typeName, 247 strings.Join(ms, ","), strings.Join(pms, ","))) 248 } 249 default: 250 panic("unreachable") 251 } 252 } 253 } 254 return e 255 } 256 257 func (p *Program) Export(path string) (extList []string, typList []string) { 258 pkg := p.prog.ImportedPackage(path) 259 pkgPath := pkg.Pkg.Path() 260 pkgName := pkg.Pkg.Name() 261 for k, v := range pkg.Members { 262 if token.IsExported(k) { 263 switch t := v.(type) { 264 case *ssa.NamedConst: 265 case *ssa.Global: 266 extList = append(extList, fmt.Sprintf("%q : &%v", pkgPath+"."+t.Name(), pkgName+"."+t.Name())) 267 case *ssa.Function: 268 extList = append(extList, fmt.Sprintf("%q : %v", pkgPath+"."+t.Name(), pkgName+"."+t.Name())) 269 case *ssa.Type: 270 named := t.Type().(*types.Named) 271 typeName := named.Obj().Name() 272 273 typList = append(typList, fmt.Sprintf("(*%v.%v)(nil)", pkgName, typeName)) 274 if named.Obj().Pkg() != pkg.Pkg { 275 continue 276 } 277 methods := IntuitiveMethodSet(t.Type()) 278 for _, method := range methods { 279 name := method.Obj().Name() 280 if token.IsExported(name) { 281 info := fmt.Sprintf("(%v).%v", method.Recv(), name) 282 if pkgPath == pkgName { 283 extList = append(extList, fmt.Sprintf("%q : %v", info, info)) 284 } else { 285 var fn string 286 if isPointer(method.Recv()) { 287 fn = fmt.Sprintf("(*%v.%v).%v", pkgName, typeName, name) 288 } else { 289 fn = fmt.Sprintf("(%v.%v).%v", pkgName, typeName, name) 290 } 291 extList = append(extList, fmt.Sprintf("%q : %v", info, fn)) 292 } 293 } 294 } 295 } 296 } 297 } 298 return 299 } 300 301 func isPointer(T types.Type) bool { 302 _, ok := T.(*types.Pointer) 303 return ok 304 } 305 306 // golang.org/x/tools/go/types/typeutil.IntuitiveMethodSet 307 func IntuitiveMethodSet(T types.Type) []*types.Selection { 308 isPointerToConcrete := func(T types.Type) bool { 309 ptr, ok := T.(*types.Pointer) 310 return ok && !types.IsInterface(ptr.Elem()) 311 } 312 313 var result []*types.Selection 314 mset := types.NewMethodSet(T) 315 if types.IsInterface(T) || isPointerToConcrete(T) { 316 for i, n := 0, mset.Len(); i < n; i++ { 317 result = append(result, mset.At(i)) 318 } 319 } else { 320 // T is some other concrete type. 321 // Report methods of T and *T, preferring those of T. 322 pmset := types.NewMethodSet(types.NewPointer(T)) 323 for i, n := 0, pmset.Len(); i < n; i++ { 324 meth := pmset.At(i) 325 if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil { 326 meth = m 327 } 328 result = append(result, meth) 329 } 330 } 331 return result 332 }