github.com/Konstantin8105/c4go@v0.0.0-20240505174241-768bb1c65a51/debug.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/Konstantin8105/c4go/ast"
    12  	"github.com/Konstantin8105/c4go/preprocessor"
    13  )
    14  
    15  type Positioner interface {
    16  	Position() ast.Position
    17  	Inject(lines [][]byte, filePP preprocessor.FilePP) error
    18  }
    19  
    20  type cases struct {
    21  	name string
    22  	pos  ast.Position
    23  }
    24  
    25  func (f cases) Position() ast.Position {
    26  	return f.pos
    27  }
    28  
    29  func (f cases) Inject(lines [][]byte, filePP preprocessor.FilePP) error {
    30  
    31  	b, err := getByte(lines, f.pos)
    32  	if err != nil {
    33  		return err
    34  	}
    35  
    36  	if b != 'c' {
    37  		return fmt.Errorf("unacceptable char 'c' : %c", lines[f.pos.Line-1][f.pos.Column-1])
    38  	}
    39  
    40  	col := f.pos.Column - 1
    41  	found := false
    42  	for ; col < len(lines[f.pos.Line-1]); col++ {
    43  		if lines[f.pos.Line-1][col] == ':' {
    44  			found = true
    45  			break
    46  		}
    47  	}
    48  
    49  	if !found {
    50  		return fmt.Errorf("cannot find char ':' : %s", lines[f.pos.Line-1])
    51  	}
    52  
    53  	// compare line of code
    54  	{
    55  		buf, err := filePP.GetSnippet(f.pos.File, f.pos.Line, f.pos.Line, 0, f.pos.Column)
    56  		if err != nil {
    57  			return err
    58  		}
    59  		if !bytes.Equal(lines[f.pos.Line-1][:f.pos.Column], buf) {
    60  			return fmt.Errorf("lines in source and pp source is not equal")
    61  		}
    62  	}
    63  
    64  	f.pos.Column = col + 1
    65  
    66  	lines[f.pos.Line-1] = append(lines[f.pos.Line-1][:f.pos.Column],
    67  		append([]byte(fmt.Sprintf(";%s(%d,\"%s\");", debugFunctionName, f.pos.Line, f.name)),
    68  			lines[f.pos.Line-1][f.pos.Column:]...)...)
    69  
    70  	return nil
    71  }
    72  
    73  type compount struct {
    74  	name string
    75  	pos  ast.Position
    76  }
    77  
    78  func (f compount) Position() ast.Position {
    79  	return f.pos
    80  }
    81  
    82  func (f compount) Inject(lines [][]byte, filePP preprocessor.FilePP) error {
    83  
    84  	b, err := getByte(lines, f.pos)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	if b != '{' {
    90  		return fmt.Errorf("unacceptable char '{' : %c", lines[f.pos.Line-1][f.pos.Column-1])
    91  	}
    92  
    93  	// compare line of code
    94  	{
    95  		buf, err := filePP.GetSnippet(f.pos.File, f.pos.Line, f.pos.Line, 0, f.pos.Column)
    96  		if err != nil {
    97  			return err
    98  		}
    99  		if !bytes.Equal(lines[f.pos.Line-1][:f.pos.Column], buf) {
   100  			return fmt.Errorf("lines in source and pp source is not equal")
   101  		}
   102  	}
   103  
   104  	lines[f.pos.Line-1] = append(lines[f.pos.Line-1][:f.pos.Column],
   105  		append([]byte(fmt.Sprintf(";%s(%d,\"%s\");", debugFunctionName, f.pos.Line, f.name)),
   106  			lines[f.pos.Line-1][f.pos.Column:]...)...)
   107  
   108  	return nil
   109  }
   110  
   111  func getByte(lines [][]byte, pos ast.Position) (b byte, err error) {
   112  	if pos.Line-1 <= 0 {
   113  		err = fmt.Errorf("outside line")
   114  		return
   115  	}
   116  	if pos.Line-1 >= len(lines) {
   117  		err = fmt.Errorf("try to add debug on outside of allowable line: %v", pos)
   118  		return
   119  	}
   120  	if pos.Column-1 >= len(lines[pos.Line-1]) {
   121  		err = fmt.Errorf("try to add debug on outside of allowable column: %v", pos)
   122  		return
   123  	}
   124  
   125  	b = lines[pos.Line-1][pos.Column-1]
   126  	return
   127  }
   128  
   129  type argument struct {
   130  	pos         ast.Position
   131  	description string
   132  	varName     string
   133  	cType       string
   134  }
   135  
   136  func (v argument) Position() ast.Position {
   137  	return v.pos
   138  }
   139  
   140  func (v argument) Inject(lines [][]byte, filePP preprocessor.FilePP) error {
   141  	// v.cType = strings.Replace(v.cType, "const ", "", -1)
   142  
   143  	if v.pos.Line-1 <= 0 {
   144  		return fmt.Errorf("outside lines")
   145  	}
   146  
   147  	if v.pos.Column-1 >= len(lines[v.pos.Line-1]) {
   148  		return fmt.Errorf("column is outside line")
   149  	}
   150  
   151  	// compare line of code
   152  	{
   153  		buf, err := filePP.GetSnippet(v.pos.File, v.pos.Line, v.pos.Line, 0, v.pos.Column)
   154  		if err != nil {
   155  			return err
   156  		}
   157  		if !bytes.Equal(lines[v.pos.Line-1][:v.pos.Column], buf) {
   158  			return fmt.Errorf("lines in source and pp source is not equal")
   159  		}
   160  	}
   161  
   162  	var index int = -1
   163  	for i := range FuncArgs {
   164  		if FuncArgs[i].cType == v.cType {
   165  			index = i
   166  		}
   167  	}
   168  	if index >= 0 {
   169  		// find argument type
   170  		function := fmt.Sprintf(";%s%s(%d,\"%s\",\"%s\",%s);",
   171  			debugArgument, FuncArgs[index].postfix,
   172  			v.pos.Line, v.description, v.varName, v.varName)
   173  		lines[v.pos.Line-1] = append(lines[v.pos.Line-1][:v.pos.Column],
   174  			append([]byte(function), lines[v.pos.Line-1][v.pos.Column:]...)...)
   175  	} else if v.cType == "char *" || v.cType == "const char *" {
   176  		function := fmt.Sprintf(";%s(%d,\"%s\",\"%s\",%s);",
   177  			debugArgumentString,
   178  			v.pos.Line, v.description, v.varName, v.varName)
   179  		lines[v.pos.Line-1] = append(lines[v.pos.Line-1][:v.pos.Column],
   180  			append([]byte(function), lines[v.pos.Line-1][v.pos.Column:]...)...)
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func generateDebugCCode(args ProgramArgs, lines []string, filePP preprocessor.FilePP) (
   187  	err error) {
   188  	if args.verbose {
   189  		fmt.Fprintln(os.Stdout, "Convert ast lines to ast tree")
   190  	}
   191  
   192  	// convert lines to tree ast
   193  	tree, errs := FromLinesToTree(args.verbose, lines, filePP)
   194  	for i := range errs {
   195  		fmt.Fprintf(os.Stderr, "AST error #%d:\n%v\n",
   196  			i, errs[i].Error())
   197  	}
   198  	if tree == nil {
   199  		return fmt.Errorf("cannot create tree: tree is nil. Please try another version of clang")
   200  	}
   201  
   202  	// Example of AST:
   203  	//
   204  	// TranslationUnitDecl
   205  	// |-TypedefDecl
   206  	// | `-...
   207  	// |-FunctionDecl used a 'void (int *)'
   208  	// |-FunctionDecl
   209  	// | |-ParmVarDecl
   210  	// | `-CompoundStmt
   211  	// |   `-...
   212  
   213  	if len(tree) == 0 {
   214  		return fmt.Errorf("tree is empty")
   215  	}
   216  
   217  	if args.verbose {
   218  		fmt.Fprintln(os.Stdout, "Walking by tree...")
   219  	}
   220  
   221  	// map[filename] []funcPos
   222  	funcPoses := map[string][]Positioner{}
   223  
   224  	for i := range tree {
   225  		tr, ok := tree[i].(*ast.TranslationUnitDecl)
   226  		if !ok {
   227  			return fmt.Errorf("first node %d is not TranslationUnitDecl: %d", i, tree[i])
   228  		}
   229  		for j := range tr.Children() {
   230  			// is it FunctionDecl
   231  			fd, ok := tr.Children()[j].(*ast.FunctionDecl)
   232  			if !ok {
   233  				continue
   234  			}
   235  			if len(fd.Children()) == 0 {
   236  				continue
   237  			}
   238  			// have a body
   239  			mst, ok := fd.Children()[len(fd.Children())-1].(*ast.CompoundStmt)
   240  			if !ok {
   241  				continue
   242  			}
   243  			// is user source
   244  			if !filePP.IsUserSource(mst.Position().File) {
   245  				continue
   246  			}
   247  
   248  			// initialize slice
   249  			if _, ok := funcPoses[mst.Position().File]; !ok {
   250  				funcPoses[mst.Position().File] = make([]Positioner, 0, 10)
   251  			}
   252  
   253  			if args.verbose {
   254  				fmt.Fprintf(os.Stdout, "find function : %s\n", fd.Name)
   255  			}
   256  
   257  			// Example for input function input data:
   258  			//
   259  			// FunctionDecl used readline 'char *(char *, FILE *, char *)'
   260  			// |-ParmVarDecl used string 'char *'
   261  			// |-ParmVarDecl used infile 'FILE *'
   262  			// |-ParmVarDecl used infilename 'char *'
   263  			// `-CompoundStmt
   264  			//   |-...
   265  			//
   266  			// FunctionDecl used tolower 'long (int, int)'
   267  			// |-ParmVarDecl used a 'int'
   268  			// |-ParmVarDecl used b 'int'
   269  			// `-CompoundStmt
   270  			//   `-...
   271  
   272  			// function name
   273  			{
   274  				f := compount{
   275  					name: "func " + fd.Name,
   276  					pos:  mst.Position(),
   277  				}
   278  				sl, _ := funcPoses[mst.Position().File]
   279  				sl = append(sl, f)
   280  				funcPoses[mst.Position().File] = sl
   281  			}
   282  
   283  			// function variable
   284  			for k := range fd.Children() {
   285  				parm, ok := fd.Children()[k].(*ast.ParmVarDecl)
   286  				if !ok {
   287  					continue
   288  				}
   289  				p := argument{
   290  					varName:     parm.Name,
   291  					pos:         mst.Position(),
   292  					description: fmt.Sprintf("%d", k),
   293  					cType:       parm.Type,
   294  				}
   295  				sl, _ := funcPoses[mst.Position().File]
   296  				sl = append(sl, p)
   297  				funcPoses[mst.Position().File] = sl
   298  			}
   299  
   300  			var injector inj
   301  			injector.walk(fd.Children()[len(fd.Children())-1])
   302  			list := injector.getPositioner()
   303  			for p := range list {
   304  				file := list[p].Position().File
   305  				sl, _ := funcPoses[file]
   306  				funcPoses[file] = append(sl, list[p])
   307  			}
   308  		}
   309  	}
   310  
   311  	if args.verbose {
   312  		fmt.Fprintf(os.Stdout, "found %d files with functions\n", len(funcPoses))
   313  	}
   314  
   315  	for file, positions := range funcPoses {
   316  		// sort from end to begin
   317  		sort.SliceStable(positions, func(i, j int) bool {
   318  			if positions[i].Position().Line == positions[j].Position().Line {
   319  				return positions[i].Position().Column < positions[j].Position().Column
   320  			}
   321  			return positions[i].Position().Line < positions[j].Position().Line
   322  		})
   323  
   324  		// read present file
   325  		dat, err := ioutil.ReadFile(file)
   326  		if err != nil {
   327  			return err
   328  		}
   329  
   330  		if args.verbose {
   331  			fmt.Fprintln(os.Stdout, "inject debug information in file: ", file)
   332  		}
   333  
   334  		// inject function
   335  		lines := bytes.Split(dat, []byte("\n"))
   336  		for k := len(positions) - 1; k >= 0; k-- {
   337  			err2 := positions[k].Inject(lines, filePP)
   338  			if err2 != nil {
   339  				// error is ignored
   340  				_ = err2
   341  			} else {
   342  				// non error is ignored
   343  			}
   344  		}
   345  
   346  		// add main debug function
   347  		lines = append([][]byte{[]byte(debugCode())}, lines...)
   348  
   349  		filename := file
   350  		// create a new filename
   351  		if index := strings.LastIndex(file, "/"); index >= 0 {
   352  			filename = file[:index+1] + args.debugPrefix + file[index+1:]
   353  		} else {
   354  			filename = args.debugPrefix + file
   355  		}
   356  
   357  		if args.verbose {
   358  			fmt.Fprintln(os.Stdout, "Write file with debug information in file: ", filename)
   359  		}
   360  
   361  		// save file with prefix+filename
   362  		err = ioutil.WriteFile(filename, bytes.Join(lines, []byte{'\n'}), 0644)
   363  		if err != nil {
   364  			return err
   365  		}
   366  	}
   367  
   368  	return nil
   369  }
   370  
   371  const (
   372  	debugFunctionName   string = "c4go_debug_compount"
   373  	debugArgument       string = "c4go_debug_function_arg_"
   374  	debugArgumentString string = "c4go_debug_function_arg_string"
   375  )
   376  
   377  func debugCode() string {
   378  	body := `
   379  #include <stdio.h>
   380  #include <stdlib.h>
   381  
   382  FILE * c4go_get_debug_file()
   383  {
   384  	FILE * file;
   385  	file = fopen("./debug.txt","a");
   386  	if(file==NULL){
   387  		exit(53);
   388  	};
   389  	return file;
   390  }
   391  
   392  void c4go_debug_compount(int line, char * functionName)
   393  {
   394  	FILE * file = c4go_get_debug_file();
   395  	fprintf(file,"Line: %d. name: %s\n",line, functionName);
   396  	fclose(file);
   397  }
   398  
   399  #define c4go_arg(type, postfix, format) \
   400  void c4go_debug_function_arg_##postfix(int line, char * arg_pos, char * name, type arg_value) \
   401  { \
   402  	FILE * file = c4go_get_debug_file(); \
   403  	fprintf(file,"Line: %d\n",line); \
   404  	fprintf(file,"\tdescription: %s\n", arg_pos); \
   405  	fprintf(file,"\tname: %s\n", name); \
   406  	fprintf(file,"\tval : \""); \
   407  	fprintf(file,format, arg_value); \
   408  	fprintf(file,"\"\n"); \
   409  	fclose(file); \
   410  }
   411  
   412  void c4go_debug_function_arg_string(int line, const char * arg_pos, const char * name,const char * arg_value)
   413  {
   414  	FILE * file = c4go_get_debug_file();
   415  	fprintf(file,"Line: %d\n",line);
   416  	fprintf(file,"\tdescription: %s\n", arg_pos);
   417  	fprintf(file,"\tname: %s\n", name);
   418  	fprintf(file,"\tval : \"");
   419  	if (arg_value == NULL) {
   420  		fprintf(file, "<null>");
   421  	} else {
   422  		fprintf(file, "%s" , arg_value);
   423  	}
   424  	fprintf(file,"\"\n");
   425  	fclose(file);
   426  }
   427  
   428  `
   429  
   430  	for i := range FuncArgs {
   431  		body += fmt.Sprintf("\nc4go_arg(%s,%s,\"%s\");\n",
   432  			FuncArgs[i].cType, FuncArgs[i].postfix, FuncArgs[i].format)
   433  	}
   434  
   435  	return body
   436  }
   437  
   438  var FuncArgs = []struct {
   439  	cType   string
   440  	postfix string
   441  	format  string
   442  }{
   443  	{"int", "int", "%d"},
   444  	{"char", "char", "%d"},
   445  	{"unsigned int", "uint", "%d"},
   446  	{"long", "long", "%ld"},
   447  	{"float", "float", "%f"},
   448  	{"double", "double", "%f"},
   449  	// {"int *", "pnt_int", "%d"},
   450  	// {"char *", "string", "%s"},
   451  	// {"char **", "double_string", "%s"},
   452  	// {"unsigned char *", "ustring", "%s"},
   453  }
   454  
   455  type inj struct {
   456  	poss                 []Positioner
   457  	varDecls             []argument
   458  	insideBinaryOperator bool
   459  }
   460  
   461  func (in *inj) addVarDecl(arg argument) {
   462  	// add only uniq VarDecls
   463  	for i := range in.varDecls {
   464  		if in.varDecls[i].varName == arg.varName &&
   465  			in.varDecls[i].cType == arg.cType {
   466  			return
   467  		}
   468  	}
   469  	in.varDecls = append(in.varDecls, arg)
   470  }
   471  
   472  func (in *inj) getPositioner() []Positioner {
   473  	return in.poss
   474  }
   475  
   476  func (in *inj) newAllowablePosition(pos ast.Position) {
   477  	// add Positioner after symbol `pos`
   478  	for k := len(in.varDecls) - 1; k >= 0; k-- {
   479  		// avoid names intersection
   480  		var ignore bool
   481  		for g := len(in.varDecls) - 1; g > k; g-- {
   482  			if in.varDecls[k].varName == in.varDecls[g].varName ||
   483  				"*"+in.varDecls[k].varName == in.varDecls[g].varName ||
   484  				in.varDecls[k].varName == "*"+in.varDecls[g].varName {
   485  				ignore = true
   486  				break
   487  			}
   488  		}
   489  		if ignore {
   490  			continue
   491  		}
   492  		// add all ast.VarDecl
   493  		vd := in.varDecls[k]
   494  		vd.pos = pos
   495  		in.poss = append(in.poss, vd)
   496  	}
   497  }
   498  
   499  func (in *inj) walk(node ast.Node) {
   500  	// ignore nil node
   501  	if node == nil {
   502  		return
   503  	}
   504  
   505  	switch v := node.(type) {
   506  	case *ast.CompoundStmt:
   507  		in.poss = append(in.poss, compount{name: "CompoundStmt", pos: node.Position()})
   508  		in.newAllowablePosition(node.Position())
   509  		size := len(in.varDecls)
   510  		for i := 0; i < len(node.Children()); i++ {
   511  			in.walk(node.Children()[i]) // walking inside
   512  		}
   513  		if size < len(in.varDecls) { // remove last VarDecls, if some added
   514  			in.varDecls = in.varDecls[:size]
   515  		}
   516  
   517  	case *ast.ArraySubscriptExpr:
   518  		// ignore
   519  		return
   520  
   521  	case *ast.IfStmt:
   522  		// IfStmt
   523  		// |-<<<NULL>>>
   524  		// |-<<<NULL>>>
   525  		// |-BinaryOperator 'int' '!='
   526  		// | `-...
   527  		// |-CompoundStmt
   528  		// | `-...
   529  		// `-<<<NULL>>>
   530  		for i := 3; i < len(v.Children()); i++ {
   531  			in.walk(v.Children()[i])
   532  		}
   533  		return
   534  
   535  	case *ast.ForStmt:
   536  		// ForStmt
   537  		// |-... // check this
   538  		// |-<<<NULL>>>
   539  		// |-...
   540  		// |-...
   541  		// `-CompoundStmt  // check this
   542  		size := len(in.varDecls)
   543  		for i := 0; i < len(v.Children()); i++ {
   544  			in.walk(v.Children()[i])
   545  		}
   546  		if size < len(in.varDecls) { // remove last VarDecls, if some added
   547  			in.varDecls = in.varDecls[:size]
   548  		}
   549  		return
   550  
   551  	case *ast.WhileStmt:
   552  		// WhileStmt
   553  		// |-<<<NULL>>>
   554  		// |-BinaryOperator 'int' '<='
   555  		// | `-...
   556  		// `-CompoundStmt
   557  		//   |-...
   558  		for i := 2; i < len(v.Children()); i++ {
   559  			in.walk(v.Children()[i])
   560  		}
   561  		return
   562  
   563  	case *ast.DefaultStmt:
   564  		// that node bug in column identification
   565  		return
   566  
   567  	case *ast.ImplicitCastExpr:
   568  		in.walk(v.Children()[0])
   569  		return
   570  
   571  	case *ast.CallExpr:
   572  		// CallExpr  'double'
   573  		// |-ImplicitCastExpr 'double (*)(int, float, double)' <LValueToRValue>
   574  		// | `-DeclRefExpr 'double (*)(int, float, double)' lvalue ParmVar 0x42be310 'F' 'double (*)(int, float, double)'
   575  		// |-...
   576  		// |-...
   577  		// `-...
   578  		for i := 1; i < len(v.Children()); i++ {
   579  			in.walk(v.Children()[i])
   580  		}
   581  		// not in BinaryOperator
   582  		if in.insideBinaryOperator {
   583  			return
   584  		}
   585  		// new place at the end of CallExpr:
   586  		// memmove(...,...,...);
   587  		//                    |--- end position is here
   588  		if v.Pos.LineEnd != 0 {
   589  			v.Pos.Line = v.Pos.LineEnd
   590  		}
   591  		v.Pos.Column = v.Pos.ColumnEnd + 1
   592  		in.newAllowablePosition(v.Pos)
   593  		in.poss = append(in.poss, compount{name: "After CallExpr", pos: v.Pos})
   594  		return
   595  
   596  	case *ast.CaseStmt:
   597  	// TODO: is same source line
   598  	// TODO: create a newAllowablePosition
   599  	// TODO: addCase("case", node)
   600  	//
   601  	// walking by tree
   602  	// addCase := func(name string, node ast.Node) {
   603  	// sl, _ := funcPoses[node.Position().File]
   604  	// sl = append(sl, cases{name: name, pos: node.Position()})
   605  	// funcPoses[node.Position().File] = sl
   606  	// }
   607  	// }
   608  
   609  	case *ast.VarDecl:
   610  		// VarDecl with initialization
   611  		if len(v.Children()) > 0 {
   612  			in.addVarDecl(argument{
   613  				// Not define Position
   614  				description: "VarDecl",
   615  				varName:     v.Name,
   616  				cType:       v.Type,
   617  			})
   618  		}
   619  
   620  	case *ast.DeclRefExpr:
   621  		in.addVarDecl(argument{
   622  			// Not define Position
   623  			description: "BinEQ_Decl",
   624  			varName:     v.Name,
   625  			cType:       v.Type,
   626  		})
   627  
   628  	case *ast.MemberExpr:
   629  		if decl, ok := v.Children()[0].(*ast.DeclRefExpr); ok {
   630  			in.addVarDecl(argument{
   631  				// Not define Position
   632  				description: "BinEQ_MemDecl",
   633  				varName:     fmt.Sprintf("%s.%s", decl.Name, v.Name),
   634  				cType:       v.Type,
   635  			})
   636  			return
   637  		}
   638  		if impl, ok := v.Children()[0].(*ast.ImplicitCastExpr); ok {
   639  			if decl, ok := impl.Children()[0].(*ast.DeclRefExpr); ok {
   640  				if v.IsPointer {
   641  					in.addVarDecl(argument{
   642  						// Not define Position
   643  						description: "BinEQ_MemImpDeclP",
   644  						varName:     fmt.Sprintf("%s->%s", decl.Name, v.Name),
   645  						cType:       v.Type,
   646  					})
   647  					return
   648  				}
   649  				in.addVarDecl(argument{
   650  					// Not define Position
   651  					description: "BinEQ_MemImpDecl",
   652  					varName:     fmt.Sprintf("%s.%s", decl.Name, v.Name),
   653  					cType:       v.Type,
   654  				})
   655  				return
   656  			}
   657  		}
   658  
   659  	case *ast.UnaryOperator:
   660  		if v.Operator == "*" {
   661  			if impl, ok := v.Children()[0].(*ast.ImplicitCastExpr); ok {
   662  				if decl, ok := impl.Children()[0].(*ast.DeclRefExpr); ok {
   663  					in.addVarDecl(argument{
   664  						// Not define Position
   665  						description: "UID",
   666  						varName:     fmt.Sprintf("*%s", decl.Name),
   667  						cType:       v.Type,
   668  					})
   669  				}
   670  			}
   671  		}
   672  
   673  	case *ast.BinaryOperator:
   674  		in.insideBinaryOperator = true
   675  		defer func() {
   676  			in.insideBinaryOperator = false
   677  		}()
   678  		for pos := range v.Children() {
   679  			in.walk(v.Children()[pos])
   680  		}
   681  
   682  	case *ast.DeclStmt:
   683  		for pos := range v.Children() {
   684  			in.walk(v.Children()[pos])
   685  		}
   686  	}
   687  }