diff --git a/cmd/xgo/exec_tool/option.go b/cmd/xgo/exec_tool/option.go index a4e2ab30..cd6a4898 100644 --- a/cmd/xgo/exec_tool/option.go +++ b/cmd/xgo/exec_tool/option.go @@ -65,7 +65,7 @@ func parseOptions(args []string, stopAfterFirstArg bool) (*options, error) { continue } - ok, err := flag.TryParseFlagsValue([]string{"--debug"}, &debug, &i, args) + ok, err := flag.TryParseFlagsValue([]string{"--debug"}, &debug, nil, &i, args) if err != nil { return nil, err } diff --git a/cmd/xgo/main.go b/cmd/xgo/main.go index 1820a755..05b2e731 100644 --- a/cmd/xgo/main.go +++ b/cmd/xgo/main.go @@ -147,6 +147,7 @@ func handleBuild(cmd string, args []string) error { withGoroot := opts.withGoroot dumpIR := opts.dumpIR dumpAST := opts.dumpAST + stackTrace := opts.stackTrace if cmdExec && len(remainArgs) == 0 { return fmt.Errorf("exec requires command") @@ -237,7 +238,7 @@ func handleBuild(cmd string, args []string) error { // gcflags can cause the build cache to invalidate // so separate them with normal one buildCacheSuffix := "" - if gcflags != "" { + if len(gcflags) > 0 { buildCacheSuffix = "-gcflags" } buildCacheDir := filepath.Join(instrumentDir, "build-cache"+buildCacheSuffix) @@ -382,8 +383,8 @@ func handleBuild(cmd string, args []string) error { if flagC { buildCmdArgs = append(buildCmdArgs, "-c") } - if gcflags != "" { - buildCmdArgs = append(buildCmdArgs, "-gcflags="+gcflags) + for _, f := range gcflags { + buildCmdArgs = append(buildCmdArgs, "-gcflags="+f) } if cmdBuild || (cmdTest && flagC) { // output @@ -435,6 +436,9 @@ func handleBuild(cmd string, args []string) error { if vscodeDebugFile != "" { execCmd.Env = append(execCmd.Env, "XGO_DEBUG_VSCODE="+vscodeDebugFile+vscodeDebugFileSuffix) } + if stackTrace != "" { + execCmd.Env = append(execCmd.Env, "XGO_STACK_TRACE="+stackTrace) + } } logDebug("command env: %v", execCmd.Env) execCmd.Stdout = os.Stdout diff --git a/cmd/xgo/option.go b/cmd/xgo/option.go index feaadc5f..15fbb39c 100644 --- a/cmd/xgo/option.go +++ b/cmd/xgo/option.go @@ -43,7 +43,15 @@ type options struct { // recognize go flags as is // -gcflags - gcflags string + // can repeat + gcflags []string + + // xgo test --trace + + // --strace, --strace=on, --strace=off + // --stack-stackTrace, --stack-stackTrace=off, --stack-stackTrace=on + // to be used in test mode + stackTrace string remainArgs []string } @@ -80,7 +88,8 @@ func parseOptions(args []string) (*options, error) { var noBuildOutput bool - var gcflags string + var gcflags []string + var stackTrace string var remainArgs []string nArg := len(args) @@ -127,7 +136,9 @@ func parseOptions(args []string) (*options, error) { }, { Flags: []string{"-gcflags"}, - Value: &gcflags, + Set: func(v string) { + gcflags = append(gcflags, v) + }, }, { Flags: []string{"--log-debug"}, @@ -211,6 +222,13 @@ func parseOptions(args []string) (*options, error) { noSetup = true continue } + + argVal, ok := parseStackTraceFlag(arg) + if ok { + stackTrace = argVal + continue + } + if isDevelopment && arg == "--debug-with-dlv" { debugWithDlv = true continue @@ -226,7 +244,7 @@ func parseOptions(args []string) (*options, error) { } continue } - ok, err := flag.TryParseFlagsValue(flagVal.Flags, flagVal.Value, &i, args) + ok, err := flag.TryParseFlagsValue(flagVal.Flags, flagVal.Value, flagVal.Set, &i, args) if err != nil { return nil, err } @@ -292,8 +310,35 @@ func parseOptions(args []string) (*options, error) { // default true syncWithLink: syncWithLink == nil || *syncWithLink, - gcflags: gcflags, + gcflags: gcflags, + stackTrace: stackTrace, remainArgs: remainArgs, }, nil } + +func parseStackTraceFlag(arg string) (string, bool) { + var stackTracePrefix string + if strings.HasPrefix(arg, "--strace") { + stackTracePrefix = "--strace" + } else if strings.HasPrefix(arg, "--stack-trace") { + stackTracePrefix = "--stack-trace" + } + if stackTracePrefix == "" { + return "", false + } + if len(arg) == len(stackTracePrefix) { + return "on", true + } + if arg[len(stackTracePrefix)] != '=' { + return "", false + } + val := arg[len(stackTracePrefix)+1:] + if val == "" || val == "on" { + return "on", true + } + if val == "off" { + return "off", true + } + panic(fmt.Errorf("unrecognized value %s: %s, expects on|off", arg, val)) +} diff --git a/cmd/xgo/patch/runtime_def.go b/cmd/xgo/patch/runtime_def.go index d8fbb87b..6d8f6db4 100644 --- a/cmd/xgo/patch/runtime_def.go +++ b/cmd/xgo/patch/runtime_def.go @@ -22,11 +22,20 @@ const TestingCallbackDeclarations = `func __xgo_link_get_test_starts() []interfa return nil } ` +const TestingEndCallbackDeclarations = `func __xgo_link_get_test_ends() []interface{}{ + // link by compiler + return nil +} +` const TestingStart = `for _,__xgo_on_test_start:=range __xgo_link_get_test_starts(){ (__xgo_on_test_start.(func(*T,func(*T))))(t,fn) } ` +const TestingEnd = `for _,__xgo_on_test_end:=range __xgo_link_get_test_ends(){ + defer (__xgo_on_test_end.(func(*T,func(*T))))(t,fn) +} +` const RuntimeGetFuncName_Go117_120 = ` func __xgo_get_pc_name_impl(pc uintptr) string { diff --git a/cmd/xgo/patch/runtime_def_gen.go b/cmd/xgo/patch/runtime_def_gen.go index d11046d3..e6cefd02 100644 --- a/cmd/xgo/patch/runtime_def_gen.go +++ b/cmd/xgo/patch/runtime_def_gen.go @@ -18,7 +18,9 @@ func __xgo_on_init_finished(fn func()) func __xgo_on_gonewproc(fn func(g uintptr)) func __xgo_on_goexit(fn func()) func __xgo_on_test_start(fn interface{}) +func __xgo_on_test_end(fn interface{}) func __xgo_get_test_starts() []interface{} +func __xgo_get_test_ends() []interface{} func __xgo_peek_panic() interface{} func __xgo_mem_equal(a, b unsafe.Pointer, size uintptr) bool func __xgo_get_pc_name(pc uintptr) string` \ No newline at end of file diff --git a/cmd/xgo/patch_runtime.go b/cmd/xgo/patch_runtime.go index b26ee5d3..5e1b2322 100644 --- a/cmd/xgo/patch_runtime.go +++ b/cmd/xgo/patch_runtime.go @@ -30,7 +30,7 @@ var testingFilePatch = &FilePatch{ "{", "\n", }, - Content: patch.TestingCallbackDeclarations, + Content: patch.TestingCallbackDeclarations + patch.TestingEndCallbackDeclarations, }, { Mark: "call_testing_callback_v2", @@ -43,7 +43,7 @@ var testingFilePatch = &FilePatch{ `t.start = time.Now()`, "fn(t", }, - Content: patch.TestingStart, + Content: patch.TestingStart + patch.TestingEnd, }, }, } @@ -151,10 +151,10 @@ func patchRuntimeProc(goroot string, goVersion *goinfo.GoVersion) error { procDecl := `func newproc(fn` newProc := `newg := newproc1(fn, gp, pc)` if goVersion.Major == 1 && goVersion.Minor <= 17 { - // siz: to avoid typo check - const siz = "s" + "i" + "z" - procDecl = `func newproc(` + siz + ` int32` - newProc = `newg := newproc1(fn, argp, ` + siz + `, gp, pc)` + // to avoid typo check + const size = "s" + "i" + "z" + procDecl = `func newproc(` + size + ` int32` + newProc = `newg := newproc1(fn, argp, ` + size + `, gp, pc)` } // see https://github.com/xhd2015/xgo/issues/67 diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index 93bfe714..d8d9e221 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -2,9 +2,9 @@ package main import "fmt" -const VERSION = "1.0.24" -const REVISION = "56315bee3eb7415a4c3d88d4f26bd79c8333a434+1" -const NUMBER = 185 +const VERSION = "1.0.25" +const REVISION = "6eeff524a454a6032a2294b47d1dc1c892b5545f+1" +const NUMBER = 186 func getRevision() string { revSuffix := "" diff --git a/patch/ctxt/ctx.go b/patch/ctxt/ctx.go index 8f301407..65b55a27 100644 --- a/patch/ctxt/ctx.go +++ b/patch/ctxt/ctx.go @@ -9,6 +9,7 @@ import ( const XgoModule = "github.com/xhd2015/xgo" const XgoRuntimePkg = XgoModule + "/runtime" const XgoRuntimeCorePkg = XgoModule + "/runtime/core" +const XgoRuntimeTracePkg = XgoModule + "/runtime/trace" var XgoMainModule = os.Getenv("XGO_MAIN_MODULE") var XgoCompilePkgDataDir = os.Getenv("XGO_COMPILE_PKG_DATA_DIR") diff --git a/patch/link_name.go b/patch/link_name.go index caa44a80..30a5fae7 100644 --- a/patch/link_name.go +++ b/patch/link_name.go @@ -17,6 +17,7 @@ const xgoRuntimeTrapPkg = xgoRuntimePkgPrefix + "trap" // accepts interface{} as argument const xgoOnTestStart = "__xgo_on_test_start" +const xgoOnTestEnd = "__xgo_on_test_end" const XgoLinkSetTrap = "__xgo_link_set_trap" const XgoLinkSetTrapVar = "__xgo_link_set_trap_var" @@ -39,7 +40,9 @@ var linkMap = map[string]string{ "__xgo_link_on_gonewproc": "__xgo_on_gonewproc", "__xgo_link_on_goexit": "__xgo_on_goexit", "__xgo_link_on_test_start": xgoOnTestStart, + "__xgo_link_on_test_end": xgoOnTestEnd, "__xgo_link_get_test_starts": "__xgo_get_test_starts", + "__xgo_link_get_test_ends": "__xgo_get_test_ends", "__xgo_link_retrieve_all_funcs_and_clear": "__xgo_retrieve_all_funcs_and_clear", "__xgo_link_peek_panic": "__xgo_peek_panic", "__xgo_link_mem_equal": "__xgo_mem_equal", @@ -130,7 +133,7 @@ func replaceWithRuntimeCall(fn *ir.Func, name string) { resNames := getTypeNames(results) fnPos := fn.Pos() - if needConvertArg && name == xgoOnTestStart { + if needConvertArg && (name == xgoOnTestStart || name == xgoOnTestEnd) { for i, p := range paramNames { paramNames[i] = convToEFace(fnPos, p, p.(*ir.Name).Type(), false) } diff --git a/patch/syntax/call_expr_go1.17_18_19.go b/patch/syntax/call_expr_go1.17_18_19.go index 03cf3e0d..82a4b237 100644 --- a/patch/syntax/call_expr_go1.17_18_19.go +++ b/patch/syntax/call_expr_go1.17_18_19.go @@ -8,3 +8,13 @@ import "cmd/compile/internal/syntax" func (ctx *BlockContext) traverseCallStmtCallExpr(node *syntax.CallExpr, globaleNames map[string]*DeclInfo, imports map[string]string) *syntax.CallExpr { return ctx.traverseCallExpr(node, globaleNames, imports) } + +func copyCallExpr(expr *syntax.CallExpr) *syntax.CallExpr { + if expr == nil { + return nil + } + x := *expr + x.Fun = copyExpr(expr.Fun) + x.ArgList = copyExprs(expr.ArgList) + return &x +} diff --git a/patch/syntax/call_expr_go1.20.go b/patch/syntax/call_expr_go1.20.go index 4ad71eb7..8240b374 100644 --- a/patch/syntax/call_expr_go1.20.go +++ b/patch/syntax/call_expr_go1.20.go @@ -8,3 +8,7 @@ import "cmd/compile/internal/syntax" func (ctx *BlockContext) traverseCallStmtCallExpr(node syntax.Expr, globaleNames map[string]*DeclInfo, imports map[string]string) syntax.Expr { return ctx.traverseExpr(node, globaleNames, imports) } + +func copyCallExpr(expr syntax.Expr) syntax.Expr { + return copyExpr(expr) +} diff --git a/patch/syntax/rewrite.go b/patch/syntax/rewrite.go index d2ec0a21..10238a18 100644 --- a/patch/syntax/rewrite.go +++ b/patch/syntax/rewrite.go @@ -34,6 +34,66 @@ func fillFuncArgResNames(fileList []*syntax.File) { } } +const patchedTimeNow = "Now_Xgo_Original" +const patchedTimeSince = "Since_Xgo_Original" + +func addTimePatch(funcDelcs []*DeclInfo) { + for _, fn := range funcDelcs { + if fn.RecvTypeName != "" { + continue + } + if fn.Name == "Now" { + newNow := copyFuncDecl(fn.FuncDecl, true) + newNow.Name.Value = patchedTimeNow + fn.FileSyntax.DeclList = append(fn.FileSyntax.DeclList, newNow) + + fillPos(fn.FuncDecl.Pos(), newNow) + continue + } + if fn.Name == "Since" { + newSince := copyFuncDecl(fn.FuncDecl, true) + newSince.Name.Value = patchedTimeSince + syntax.Inspect(newSince, func(n syntax.Node) bool { + if callExpr, ok := n.(*syntax.CallExpr); ok { + if callName, ok := callExpr.Fun.(*syntax.Name); ok && callName.Value == "Now" { + callName.Value = patchedTimeNow + } + } + return true + }) + fn.FileSyntax.DeclList = append(fn.FileSyntax.DeclList, newSince) + + fillPos(fn.FuncDecl.Pos(), newSince) + continue + } + } +} + +func rewriteTimePatch(funcDelcs []*DeclInfo) { + for _, fn := range funcDelcs { + if fn.RecvTypeName != "" { + continue + } + if fn.Name == "timeNow" { + replaceIdent(fn.FuncDecl, "Now", patchedTimeNow) + continue + } + if fn.Name == "timeSince" { + replaceIdent(fn.FuncDecl, "Since", patchedTimeSince) + continue + } + } +} + +func replaceIdent(root syntax.Node, match string, to string) { + syntax.Inspect(root, func(n syntax.Node) bool { + if name, ok := n.(*syntax.Name); ok && name != nil && name.Value == match { + name.Value = to + } + return true + }) +} + func rewriteStdAndGenericFuncs(funcDecls []*DeclInfo, pkgPath string) { for _, fn := range funcDecls { if !fn.Kind.IsFunc() { @@ -355,6 +415,10 @@ func getPresetNames(node syntax.Node) map[string]bool { } func copyFuncDeclWithoutBody(decl *syntax.FuncDecl) *syntax.FuncDecl { + return copyFuncDecl(decl, false) +} + +func copyFuncDecl(decl *syntax.FuncDecl, withBody bool) *syntax.FuncDecl { if decl == nil { return nil } @@ -363,9 +427,14 @@ func copyFuncDeclWithoutBody(decl *syntax.FuncDecl) *syntax.FuncDecl { x.Name = copyName(decl.Name) x.TParamList = copyFields(decl.TParamList) x.Type = copyFuncType(decl.Type) - + if withBody { + x.Body = copyBlockStmts(decl.Body) + } else { + x.Body = nil + } return &x } + func copyFuncType(typ_ *syntax.FuncType) *syntax.FuncType { if typ_ == nil { return nil @@ -390,6 +459,7 @@ func copyBasicLiterals(literals []*syntax.BasicLit) []*syntax.BasicLit { } return c } + func copyBasicLit(lit *syntax.BasicLit) *syntax.BasicLit { if lit == nil { return nil @@ -406,6 +476,19 @@ func copyField(field *syntax.Field) *syntax.Field { x.Type = copyExpr(field.Type) return &x } + +func copyNames(names []*syntax.Name) []*syntax.Name { + if names == nil { + return nil + } + n := len(names) + copiedNames := make([]*syntax.Name, n) + for i := 0; i < n; i++ { + copiedNames[i] = copyName(names[i]) + } + return copiedNames +} + func copyName(name *syntax.Name) *syntax.Name { if name == nil { return nil @@ -423,6 +506,183 @@ func copyExprs(exprs []syntax.Expr) []syntax.Expr { return copyExprs } +func copySimpleStmt(stmt syntax.SimpleStmt) syntax.SimpleStmt { + if stmt == nil { + return nil + } + switch stmt := stmt.(type) { + case *syntax.AssignStmt: + x := *stmt + x.Lhs = copyExpr(stmt.Lhs) + x.Rhs = copyExpr(stmt.Rhs) + return &x + case *syntax.SendStmt: + x := *stmt + x.Chan = copyExpr(stmt.Chan) + x.Value = copyExpr(stmt.Value) + return &x + case *syntax.ExprStmt: + x := *stmt + x.X = copyExpr(stmt.X) + return &x + case *syntax.EmptyStmt: + x := *stmt + return &x + case *syntax.RangeClause: + x := *stmt + x.Lhs = copyExpr(stmt.Lhs) + x.X = copyExpr(stmt.X) + return &x + default: + panic(fmt.Errorf("unrecognized simple stmt: %T", stmt)) + } +} +func copyCaseClause(n *syntax.CaseClause) *syntax.CaseClause { + if n == nil { + return nil + } + x := *n + x.Cases = copyExpr(n.Cases) + x.Body = copyStmts(n.Body) + return &x +} + +func copyCaseClauses(list []*syntax.CaseClause) []*syntax.CaseClause { + if list == nil { + return nil + } + n := len(list) + copiedList := make([]*syntax.CaseClause, n) + for i := 0; i < n; i++ { + copiedList[i] = copyCaseClause(list[i]) + } + return copiedList +} + +func copyCommClause(n *syntax.CommClause) *syntax.CommClause { + if n == nil { + return nil + } + x := *n + x.Comm = copySimpleStmt(n.Comm) + x.Body = copyStmts(n.Body) + return &x +} + +func copyCommClauses(list []*syntax.CommClause) []*syntax.CommClause { + if list == nil { + return nil + } + n := len(list) + copiedList := make([]*syntax.CommClause, n) + for i := 0; i < n; i++ { + copiedList[i] = copyCommClause(list[i]) + } + return copiedList +} +func copyDeclList(decls []syntax.Decl) []syntax.Decl { + if decls == nil { + return nil + } + n := len(decls) + copiedDecls := make([]syntax.Decl, n) + for i := 0; i < n; i++ { + copiedDecls[i] = copyDecl(decls[i]) + } + return copiedDecls +} +func copyDecl(decl syntax.Decl) syntax.Decl { + if decl == nil { + return nil + } + switch decl := decl.(type) { + case *syntax.ConstDecl: + x := *decl + if decl.Group != nil { + g := *decl.Group + x.Group = &g + } + x.NameList = copyNames(decl.NameList) + x.Type = copyExpr(decl.Type) + x.Values = copyExpr(decl.Values) + return &x + case *syntax.VarDecl: + x := *decl + if decl.Group != nil { + g := *decl.Group + x.Group = &g + } + x.NameList = copyNames(decl.NameList) + x.Type = copyExpr(decl.Type) + x.Values = copyExpr(decl.Values) + return &x + case *syntax.TypeDecl: + x := *decl + if decl.Group != nil { + g := *decl.Group + x.Group = &g + } + x.Name = copyName(decl.Name) + x.TParamList = copyFields(decl.TParamList) + x.Type = copyExpr(decl.Type) + return &x + default: + panic(fmt.Errorf("unrecognized decl: %T", decl)) + } +} +func copyStmt(stmt syntax.Stmt) syntax.Stmt { + if stmt == nil { + return nil + } + switch stmt := stmt.(type) { + case syntax.SimpleStmt: + return copySimpleStmt(stmt) + case *syntax.BlockStmt: + return copyBlockStmts(stmt) + case *syntax.IfStmt: + x := *stmt + x.Init = copySimpleStmt(stmt.Init) + x.Cond = copyExpr(stmt.Cond) + x.Then = copyBlockStmts(stmt.Then) + x.Else = copyStmt(stmt.Else) + return &x + case *syntax.CallStmt: + x := *stmt + x.Call = copyCallExpr(stmt.Call) + return &x + case *syntax.DeclStmt: + x := *stmt + x.DeclList = copyDeclList(stmt.DeclList) + return &x + case *syntax.ReturnStmt: + x := *stmt + x.Results = copyExpr(stmt.Results) + return &x + case *syntax.SwitchStmt: + x := *stmt + x.Init = copySimpleStmt(stmt.Init) + x.Tag = copyExpr(stmt.Tag) + x.Body = copyCaseClauses(stmt.Body) + return &x + } + panic(fmt.Sprintf("unrecognized stmt: %T", stmt)) +} +func copyStmts(stmts []syntax.Stmt) []syntax.Stmt { + cpStmts := make([]syntax.Stmt, len(stmts)) + for i := 0; i < len(stmts); i++ { + cpStmts[i] = copyStmt(stmts[i]) + } + return cpStmts +} + +func copyBlockStmts(stmt *syntax.BlockStmt) *syntax.BlockStmt { + if stmt == nil { + return nil + } + x := *stmt + x.List = copyStmts(x.List) + return &x +} func copyExpr(expr syntax.Expr) syntax.Expr { if expr == nil { return nil @@ -443,6 +703,15 @@ func copyExpr(expr syntax.Expr) syntax.Expr { x.X = copyExpr(expr.X) x.Y = copyExpr(expr.Y) return &x + case *syntax.CallExpr: + x := *expr + x.Fun = copyExpr(expr.Fun) + x.ArgList = copyExprs(expr.ArgList) + return &x + case *syntax.ParenExpr: + x := *expr + x.X = copyExpr(expr.X) + return &x case *syntax.DotsType: x := *expr x.Elem = copyExpr(expr.Elem) @@ -461,6 +730,11 @@ func copyExpr(expr syntax.Expr) syntax.Expr { x.Index[1] = copyExpr(expr.Index[1]) x.Index[2] = copyExpr(expr.Index[2]) return &x + case *syntax.CompositeLit: + x := *expr + x.Type = copyExpr(expr.Type) + x.ElemList = copyExprs(expr.ElemList) + return &x case *syntax.SliceType: x := *expr x.Elem = copyExpr(expr.Elem) diff --git a/patch/syntax/syntax.go b/patch/syntax/syntax.go index bdad73f9..3a352616 100644 --- a/patch/syntax/syntax.go +++ b/patch/syntax/syntax.go @@ -180,15 +180,19 @@ func ClearSyntaxDeclMapping() { func registerFuncs(fileList []*syntax.File, addFile func(name string, r io.Reader) *syntax.File) { allFiles = fileList - if xgo_ctxt.SkipPackageTrap() { - return - } - var pkgName string + pkgPath := xgo_ctxt.GetPkgPath() - if len(fileList) > 0 { - pkgName = fileList[0].PkgName.Value + + var needTimePatch bool + var needTimeRewrite bool + if base.Flag.Std && pkgPath == "time" { + needTimePatch = true + } else if pkgPath == xgo_ctxt.XgoRuntimeTracePkg { + needTimeRewrite = true } + skipTrap := xgo_ctxt.SkipPackageTrap() + // debugPkgSyntax(fileList) // if true { // return @@ -203,8 +207,21 @@ func registerFuncs(fileList []*syntax.File, addFile func(name string, r io.Reade // where its _pkg_.a is. varTrap := allowVarTrap() + var funcDelcs []*DeclInfo + if needTimePatch || needTimeRewrite || !skipTrap { + funcDelcs = getFuncDecls(fileList, varTrap) + } + if needTimePatch { + // add time.Now_Xgo_Original() and time.Since_Xgo_Original() + addTimePatch(funcDelcs) + } + if needTimeRewrite { + rewriteTimePatch(funcDelcs) + } + if skipTrap { + return + } - funcDelcs := getFuncDecls(fileList, varTrap) for _, funcDecl := range funcDelcs { if funcDecl.RecvTypeName == "" && funcDecl.Name == XgoLinkGeneratedRegisterFunc { // ensure we are safe @@ -212,6 +229,12 @@ func registerFuncs(fileList []*syntax.File, addFile func(name string, r io.Reade return } } + + var pkgName string + if len(fileList) > 0 { + pkgName = fileList[0].PkgName.Value + } + // filterFuncDecls funcDelcs = filterFuncDecls(funcDelcs, pkgPath) // assign to global diff --git a/patch/trap_runtime/xgo_trap.go b/patch/trap_runtime/xgo_trap.go index 05d912a1..b283b0d9 100644 --- a/patch/trap_runtime/xgo_trap.go +++ b/patch/trap_runtime/xgo_trap.go @@ -128,14 +128,21 @@ func __xgo_on_goexit(fn func()) { } var __xgo_on_test_starts []interface{} // func(t *testing.T,fn func(t *testing.T)) +var __xgo_on_test_ends []interface{} // func(t *testing.T,fn func(t *testing.T)) func __xgo_on_test_start(fn interface{}) { __xgo_on_test_starts = append(__xgo_on_test_starts, fn) } +func __xgo_on_test_end(fn interface{}) { + __xgo_on_test_ends = append(__xgo_on_test_ends, fn) +} func __xgo_get_test_starts() []interface{} { return __xgo_on_test_starts } +func __xgo_get_test_ends() []interface{} { + return __xgo_on_test_ends +} // check gorecover() for implementation details func __xgo_peek_panic() interface{} { diff --git a/runtime/core/version.go b/runtime/core/version.go index c1faf9c9..98a35b5a 100644 --- a/runtime/core/version.go +++ b/runtime/core/version.go @@ -6,9 +6,9 @@ import ( "os" ) -const VERSION = "1.0.24" -const REVISION = "56315bee3eb7415a4c3d88d4f26bd79c8333a434+1" -const NUMBER = 185 +const VERSION = "1.0.25" +const REVISION = "6eeff524a454a6032a2294b47d1dc1c892b5545f+1" +const NUMBER = 186 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/runtime/test/debug/debug_test.go b/runtime/test/debug/debug_test.go index d5509215..f78a978b 100644 --- a/runtime/test/debug/debug_test.go +++ b/runtime/test/debug/debug_test.go @@ -5,6 +5,27 @@ package debug -const __xgo_trap_cfg2 = 1 +import ( + "context" + "testing" + "time" -var cfg2 int + "github.com/xhd2015/xgo/runtime/core" + "github.com/xhd2015/xgo/runtime/trap" +) + +func TestNowOriginal(t *testing.T) { + var captured bool + trap.AddFuncInterceptor(time.Now, &trap.Interceptor{ + Pre: func(ctx context.Context, f *core.FuncInfo, args, result core.Object) (data interface{}, err error) { + if f.Pkg == "time" && f.IdentityName == "Now" { + captured = true + } + return nil, nil + }, + }) + time.Now() + if captured { + t.Fatalf("should not be captured") + } +} diff --git a/runtime/test/trace/check_trace_flag/check_trace_flag_test.go b/runtime/test/trace/check_trace_flag/check_trace_flag_test.go new file mode 100644 index 00000000..58881084 --- /dev/null +++ b/runtime/test/trace/check_trace_flag/check_trace_flag_test.go @@ -0,0 +1,26 @@ +//go:build ignore +// +build ignore + +package check_trace_flag + +import ( + "testing" + + _ "github.com/xhd2015/xgo/runtime/trace" +) + +// check if 'xgo test --strace' would actually work +// it should generate a trace next the test file +// without explicit `defer trace.Begin()()`, +// even if panic +func TestCheckTraceFlagNormal(t *testing.T) { + A() +} + +func TestCheckTraceFlagPanic(t *testing.T) { + A() + panic("test") +} + +func A() { B() } +func B() {} diff --git a/runtime/trace/trace.go b/runtime/trace/trace.go index b811219b..7f0d602f 100644 --- a/runtime/trace/trace.go +++ b/runtime/trace/trace.go @@ -20,8 +20,12 @@ import ( var stackMap sync.Map // uintptr(goroutine) -> *Root var testInfoMapping sync.Map // uintptr(goroutine) -> *testInfo +var xgoStackTrace = os.Getenv("XGO_STACK_TRACE") + type testInfo struct { name string + + onFinish func() } func init() { @@ -31,12 +35,30 @@ func init() { return } key := uintptr(__xgo_link_getcurg()) - testInfoMapping.LoadOrStore(key, &testInfo{ + tInfo := &testInfo{ name: name, - }) + } + testInfoMapping.LoadOrStore(key, tInfo) + if xgoStackTrace == "on" { + tInfo.onFinish = Begin() + } }) + __xgo_link_on_test_end(func(t *testing.T, fn func(t *testing.T)) { + key := uintptr(__xgo_link_getcurg()) + val, ok := testInfoMapping.Load(key) + if !ok { + return + } + ttInfo := val.(*testInfo) + if ttInfo.onFinish != nil { + ttInfo.onFinish() + ttInfo.onFinish = nil + } + }) + __xgo_link_on_goexit(func() { key := uintptr(__xgo_link_getcurg()) + testInfoMapping.Delete(key) collectingMap.Delete(key) }) @@ -46,6 +68,9 @@ func init() { func __xgo_link_on_test_start(fn func(t *testing.T, fn func(t *testing.T))) { fmt.Fprintln(os.Stderr, "WARNING: failed to link __xgo_link_on_test_start(requires xgo).") } +func __xgo_link_on_test_end(fn func(t *testing.T, fn func(t *testing.T))) { + fmt.Fprintln(os.Stderr, "WARNING: failed to link __xgo_link_on_test_end(requires xgo).") +} // link by compiler func __xgo_link_getcurg() unsafe.Pointer { @@ -67,6 +92,17 @@ func __xgo_link_peek_panic() interface{} { return nil } +// xgo will optimize these two functions to direct call +func timeNow() time.Time { + // to: time.Now_Xgo_Original() + return time.Now() +} + +func timeSince(t time.Time) time.Duration { + // to: time.Since_Xgo_Original() + return time.Since(t) +} + var enabledGlobally bool var interceptorRefCount int32 @@ -192,12 +228,12 @@ func handleTracePre(ctx context.Context, f *core.FuncInfo, args core.Object, res // initial stack root := &Root{ Top: stack, - Begin: getNow(), + Begin: timeNow(), Children: []*Stack{ stack, }, } - stack.Begin = int64(time.Since(root.Begin)) + stack.Begin = int64(timeSince(root.Begin)) if localOpts == nil { stackMap.Store(key, root) } else { @@ -213,7 +249,7 @@ func handleTracePre(ctx context.Context, f *core.FuncInfo, args core.Object, res } else { root = globalRoot.(*Root) } - stack.Begin = int64(time.Since(root.Begin)) + stack.Begin = int64(timeSince(root.Begin)) prevTop := root.Top root.Top.Children = append(root.Top.Children, stack) root.Top = stack @@ -264,7 +300,7 @@ func handleTracePost(ctx context.Context, f *core.FuncInfo, args core.Object, re } } } - root.Top.End = int64(time.Since(root.Begin)) + root.Top.End = int64(timeSince(root.Begin)) if data == nil { root.Top = nil // stack finished @@ -315,7 +351,7 @@ func enableLocal(collOpts *collectOpts) func() { if collOpts.root == nil { collOpts.root = &Root{ Top: &Stack{}, - Begin: getNow(), + Begin: timeNow(), } } top := collOpts.root.Top @@ -391,12 +427,6 @@ func emitTraceNoErr(name string, root *Root, opts *ExportOptions) { emitTrace(name, root, opts) } -func getNow() (now time.Time) { - trap.Direct(func() { - now = time.Now() - }) - return -} func formatTime(t time.Time, layout string) (output string) { trap.Direct(func() { output = t.Format(layout) @@ -408,6 +438,7 @@ func formatTime(t time.Time, layout string) (output string) { // TODO: may add callback for this func emitTrace(name string, root *Root, opts *ExportOptions) error { xgoTraceOutput := getTraceOutput() + if xgoTraceOutput == "off" { return nil } @@ -418,7 +449,7 @@ func emitTrace(name string, root *Root, opts *ExportOptions) error { ghex := fmt.Sprintf("g_%x", __xgo_link_getcurg()) traceID := "t_" + strconv.FormatInt(traceIDNum, 10) if xgoTraceOutput == "" { - traceDir := formatTime(getNow(), "trace_20060102_150405") + traceDir := formatTime(timeNow(), "trace_20060102_150405") subName = filepath.Join(traceDir, ghex, traceID) } else if useStdout { subName = fmt.Sprintf("%s/%s", ghex, traceID) diff --git a/runtime/trace/trace_test.go b/runtime/trace/trace_test.go new file mode 100644 index 00000000..7b603085 --- /dev/null +++ b/runtime/trace/trace_test.go @@ -0,0 +1,42 @@ +package trace + +import ( + "context" + "testing" + "time" + + "github.com/xhd2015/xgo/runtime/core" + "github.com/xhd2015/xgo/runtime/trap" +) + +func TestNowOriginal(t *testing.T) { + var captured bool + trap.AddFuncInterceptor(time.Now, &trap.Interceptor{ + Pre: func(ctx context.Context, f *core.FuncInfo, args, result core.Object) (data interface{}, err error) { + if f.Pkg == "time" && f.IdentityName == "Now" { + captured = true + } + return nil, nil + }, + }) + time.Now() + if !captured { + t.Fatalf("should be captured") + } +} + +func TestNowPatched(t *testing.T) { + var captured bool + trap.AddFuncInterceptor(time.Now, &trap.Interceptor{ + Pre: func(ctx context.Context, f *core.FuncInfo, args, result core.Object) (data interface{}, err error) { + if f.Pkg == "time" && f.IdentityName == "Now" { + captured = true + } + return nil, nil + }, + }) + timeNow() + if captured { + t.Fatalf("should not be captured") + } +} diff --git a/support/flag/flag.go b/support/flag/flag.go index 6ce3836e..7f569fc9 100644 --- a/support/flag/flag.go +++ b/support/flag/flag.go @@ -5,7 +5,7 @@ import ( "strings" ) -func TryParseFlagValue(flag string, pval *string, pi *int, args []string) (ok bool, err error) { +func TryParseFlagValue(flag string, pval *string, set func(v string), pi *int, args []string) (ok bool, err error) { i := *pi val, next, ok := tryParseArg(flag, args[i]) if !ok { @@ -18,13 +18,17 @@ func TryParseFlagValue(flag string, pval *string, pi *int, args []string) (ok bo val = args[i+1] *pi++ } - *pval = val + if set != nil { + set(val) + } else { + *pval = val + } return true, nil } -func TryParseFlagsValue(flags []string, pval *string, pi *int, args []string) (ok bool, err error) { +func TryParseFlagsValue(flags []string, pval *string, set func(v string), pi *int, args []string) (ok bool, err error) { for _, flag := range flags { - ok, err := TryParseFlagValue(flag, pval, pi, args) + ok, err := TryParseFlagValue(flag, pval, set, pi, args) if err != nil { return false, err }