github.com/jmigpin/editor@v1.6.0/core/godebug/annotatorset.go (about) 1 package godebug 2 3 import ( 4 "crypto/sha1" 5 "fmt" 6 "go/ast" 7 "go/parser" 8 "go/token" 9 "go/types" 10 "io/ioutil" 11 "strconv" 12 "strings" 13 "sync" 14 15 "github.com/jmigpin/editor/core/godebug/debug" 16 "golang.org/x/tools/go/ast/astutil" 17 ) 18 19 // TODO: a built binary will only be able to run with this editor instance (but on the other side, it can self debug any part of the editor, including the debug pkg) 20 var genEncoderId string // used to build config.go 21 22 func init() { 23 genEncoderId = genDigitsStr(10) 24 debug.RegisterStructsForEncodeDecode(genEncoderId) 25 } 26 27 //---------- 28 29 type AnnotatorSet struct { 30 fset *token.FileSet 31 debugPkgName string 32 debugVarPrefix string 33 afds struct { 34 sync.Mutex 35 m map[string]*debug.AnnotatorFileData // map[filename]afd 36 order []*debug.AnnotatorFileData // ordered 37 index int // counter for new files 38 } 39 } 40 41 func NewAnnotatorSet(fset *token.FileSet) *AnnotatorSet { 42 annset := &AnnotatorSet{} 43 annset.fset = fset 44 annset.afds.m = map[string]*debug.AnnotatorFileData{} 45 annset.debugPkgName = "Σ" // uncommon rune to avoid clashes 46 annset.debugVarPrefix = "Σ" 47 return annset 48 } 49 50 //---------- 51 52 func (annset *AnnotatorSet) AnnotateAstFile(astFile *ast.File, ti *types.Info, nat map[ast.Node]AnnotationType) error { 53 54 filename, err := nodeFilename(annset.fset, astFile) 55 if err != nil { 56 return err 57 } 58 59 afd, err := annset.annotatorFileData(filename) 60 if err != nil { 61 return err 62 } 63 64 ann := NewAnnotator(annset.fset) 65 ann.debugPkgName = annset.debugPkgName 66 ann.debugVarPrefix = annset.debugVarPrefix 67 ann.fileIndex = afd.FileIndex 68 ann.typesInfo = ti 69 ann.nodeAnnTypes = nat 70 ann.AnnotateAstFile(astFile) 71 72 // n debug stmts inserted 73 afd.DebugLen = ann.debugLastIndex 74 75 // insert imports if debug stmts were inserted 76 if ann.builtDebugLineStmt { 77 annset.insertDebugPkgImport(astFile) 78 annset.updateOsExitCalls(astFile) 79 } 80 return nil 81 } 82 83 //---------- 84 85 func (annset *AnnotatorSet) setupDebugExitInFuncDecl(fd *ast.FuncDecl, astFile *ast.File) { 86 // defer exit stmt 87 stmt1 := &ast.DeferStmt{ 88 Call: &ast.CallExpr{ 89 Fun: &ast.SelectorExpr{ 90 X: ast.NewIdent(annset.debugPkgName), 91 Sel: ast.NewIdent("ExitServer"), 92 }, 93 }, 94 } 95 96 //// initial call to force server start in case of an empty main 97 //stmt2 := &ast.ExprStmt{ 98 // X: &ast.CallExpr{ 99 // Fun: &ast.SelectorExpr{ 100 // X: ast.NewIdent(annset.debugPkgName), 101 // Sel: ast.NewIdent("StartServer"), 102 // }, 103 // }, 104 //} 105 106 // insert as first stmts 107 stmts := []ast.Stmt{stmt1} 108 //stmts:=[]ast.Stmt{stmt1, stmt2} 109 fd.Body.List = append(stmts, fd.Body.List...) 110 111 annset.insertDebugPkgImport(astFile) 112 annset.updateOsExitCalls(astFile) 113 } 114 115 //---------- 116 117 func (annset *AnnotatorSet) insertDebugPkgImport(astFile *ast.File) { 118 // adds import if absent 119 _ = astutil.AddNamedImport(annset.fset, astFile, annset.debugPkgName, debugPkgPath) 120 } 121 122 //---------- 123 124 func (annset *AnnotatorSet) updateOsExitCalls(astFile *ast.File) { 125 // check if "os" is imported 126 osImported := false 127 for _, imp := range astFile.Imports { 128 v, err := strconv.Unquote(imp.Path.Value) 129 if err == nil && v == "os" { 130 if imp.Name == nil { // must not have been named 131 osImported = true 132 } 133 break 134 } 135 } 136 if !osImported { 137 return 138 } 139 140 // replace os.Exit() calls with debug.Exit() 141 // count other os.* calls to know if the "os" import should be removed 142 count := 0 143 _ = astutil.Apply(astFile, func(c *astutil.Cursor) bool { 144 if se, ok := c.Node().(*ast.SelectorExpr); ok { 145 if id1, ok := se.X.(*ast.Ident); ok { 146 if id1.Name == "os" { 147 count++ 148 if se.Sel.Name == "Exit" { 149 count-- 150 se2 := &ast.SelectorExpr{ 151 X: ast.NewIdent(annset.debugPkgName), 152 Sel: ast.NewIdent("Exit"), 153 } 154 c.Replace(se2) 155 } 156 } 157 } 158 } 159 return true 160 }, nil) 161 162 // if !astutil.UsesImport(astFile, "os"){ 163 if count == 0 { 164 _ = astutil.DeleteImport(annset.fset, astFile, "os") 165 166 // TODO: waiting for fix: vet: ./testmain64531_test.go:3:10: could not import os (can't resolve import "") 167 // https://github.com/golang/go/issues/50044 168 // https://github.com/golang/go/issues/44957 169 // ensure "os" is imported as "_" 170 _ = astutil.AddNamedImport(annset.fset, astFile, "_", "os") 171 } 172 } 173 174 //---------- 175 176 func (annset *AnnotatorSet) insertTestMain(astFile *ast.File) error { 177 // TODO: detect if used imports are already imported with another name (os,testing) 178 179 astutil.AddImport(annset.fset, astFile, "os") 180 astutil.AddImport(annset.fset, astFile, "testing") 181 182 // build ast to insert (easier to parse from text then to build the ast manually here. notice how "imports" are missing since it is just to get the ast of the funcdecl) 183 src := ` 184 package main 185 func TestMain(m *testing.M) { 186 os.Exit(m.Run()) 187 } 188 ` 189 fset := token.NewFileSet() 190 astFile2, err := parser.ParseFile(fset, "a.go", src, 0) 191 if err != nil { 192 panic(err) 193 } 194 //goutil.PrintNode(fset, astFile2) 195 196 // get the only func decl for insertion 197 fd := (*ast.FuncDecl)(nil) 198 ast.Inspect(astFile2, func(n ast.Node) bool { 199 if n2, ok := n.(*ast.FuncDecl); ok { 200 fd = n2 201 return false 202 } 203 return true 204 }) 205 if fd == nil { 206 err := fmt.Errorf("missing func decl") 207 panic(err) 208 } 209 210 // insert in ast file 211 astFile.Decls = append(astFile.Decls, fd) 212 213 // DEBUG 214 //goutil.PrintNode(fa.cmd.fset, astFile) 215 216 annset.setupDebugExitInFuncDecl(fd, astFile) 217 218 return nil 219 } 220 221 //---------- 222 223 func (annset *AnnotatorSet) annotatorFileData(filename string) (*debug.AnnotatorFileData, error) { 224 annset.afds.Lock() 225 defer annset.afds.Unlock() 226 227 afd, ok := annset.afds.m[filename] 228 if ok { 229 return afd, nil 230 } 231 232 src, err := ioutil.ReadFile(filename) 233 if err != nil { 234 return nil, fmt.Errorf("annotatorfiledata: %w", err) 235 } 236 237 // create new afd 238 afd = &debug.AnnotatorFileData{ 239 FileIndex: annset.afds.index, 240 Filename: filename, 241 FileSize: len(src), 242 FileHash: sourceHash(src), 243 } 244 annset.afds.m[filename] = afd 245 246 annset.afds.order = append(annset.afds.order, afd) // keep order 247 annset.afds.index++ 248 249 return afd, nil 250 } 251 252 //---------- 253 254 func (annset *AnnotatorSet) BuildConfigSrc(serverNetwork, serverAddr string, flags *flags) []byte { 255 acceptOnlyFirstClient := flags.mode.run || flags.mode.test 256 aofc := strconv.FormatBool(acceptOnlyFirstClient) 257 bcce := annset.buildConfigAfdEntries() 258 259 src := ` 260 package debug 261 func init(){ 262 ServerNetwork = "` + serverNetwork + `" 263 ServerAddress = "` + serverAddr + `" 264 265 hasGenConfig = true 266 encoderId = "` + genEncoderId + `" 267 syncSend = ` + strconv.FormatBool(flags.syncSend) + ` 268 acceptOnlyFirstClient = ` + aofc + ` 269 stringifyBytesRunes = ` + strconv.FormatBool(flags.stringifyBytesRunes) + ` 270 hasSrcLines = ` + strconv.FormatBool(flags.srcLines) + ` 271 annotatorFilesData = []*AnnotatorFileData{` + bcce + `} 272 } 273 ` 274 return []byte(src) 275 } 276 277 func (annset *AnnotatorSet) buildConfigAfdEntries() string { 278 u := []string{} 279 for _, afd := range annset.afds.order { 280 s := fmt.Sprintf("&AnnotatorFileData{%v,%v,%q,%v,[]byte(%q)}", 281 afd.FileIndex, 282 afd.DebugLen, 283 afd.Filename, 284 afd.FileSize, 285 string(afd.FileHash), 286 ) 287 u = append(u, s) 288 } 289 return strings.Join(u, ",") 290 } 291 292 //---------- 293 //---------- 294 //---------- 295 296 func findFuncDeclWithBody(astFile *ast.File, name string) (*ast.FuncDecl, bool) { 297 // commented: in the case of the main func being inserted in the ast, it would not be available in the scope lookup 298 //obj := astFile.Scope.Lookup(name) 299 //if obj != nil && obj.Kind == ast.Fun { 300 // fd, ok := obj.Decl.(*ast.FuncDecl) 301 // if ok && fd.Body != nil { 302 // return fd, true 303 // } 304 //} 305 //return nil, false 306 307 fd := (*ast.FuncDecl)(nil) 308 ast.Inspect(astFile, func(n ast.Node) bool { 309 switch t := n.(type) { 310 case *ast.File: 311 return true 312 case *ast.FuncDecl: 313 if t.Name.Name == name && t.Body != nil { 314 fd = t 315 } 316 return false 317 default: 318 return false 319 } 320 }) 321 return fd, fd != nil 322 } 323 324 //---------- 325 326 func sourceHash(b []byte) []byte { 327 h := sha1.New() 328 h.Write(b) 329 return h.Sum(nil) 330 }