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  }