@@ -20,6 +20,7 @@ import (
20
20
"path/filepath"
21
21
"reflect"
22
22
"runtime"
23
+ "runtime/debug"
23
24
"strconv"
24
25
"strings"
25
26
"sync"
@@ -222,6 +223,7 @@ type Interpreter struct {
222
223
223
224
debugger * Debugger
224
225
calls map [uintptr ]* node // for translating runtime stacktrace, see FilterStack()
226
+ panics []* Panic // list of panics we have had, see GetOldestPanicForErr()
225
227
}
226
228
227
229
const (
@@ -250,6 +252,7 @@ var Symbols = Exports{
250
252
"Interpreter" : reflect .ValueOf ((* Interpreter )(nil )),
251
253
"Options" : reflect .ValueOf ((* Options )(nil )),
252
254
"Panic" : reflect .ValueOf ((* Panic )(nil )),
255
+ "IFunc" : reflect .ValueOf ((* IFunc )(nil )),
253
256
},
254
257
}
255
258
@@ -263,24 +266,6 @@ type _error struct {
263
266
264
267
func (w _error ) Error () string { return w .WError () }
265
268
266
- // Panic is an error recovered from a panic call in interpreted code.
267
- type Panic struct {
268
- // Value is the recovered value of a call to panic.
269
- Value interface {}
270
-
271
- // Callers is the call stack obtained from the recover call.
272
- // It may be used as the parameter to runtime.CallersFrames.
273
- Callers []uintptr
274
-
275
- // Stack is the call stack buffer for debug.
276
- Stack []byte
277
- }
278
-
279
- // TODO: Capture interpreter stack frames also and remove
280
- // fmt.Fprintln(n.interp.stderr, oNode.cfgErrorf("panic")) in runCfg.
281
-
282
- func (e Panic ) Error () string { return fmt .Sprint (e .Value ) }
283
-
284
269
// Walk traverses AST n in depth first order, call cbin function
285
270
// at node entry and cbout function at node exit.
286
271
func (n * node ) Walk (in func (n * node ) bool , out func (n * node )) {
@@ -338,6 +323,7 @@ func New(options Options) *Interpreter {
338
323
rdir : map [string ]bool {},
339
324
hooks : & hooks {},
340
325
calls : map [uintptr ]* node {},
326
+ panics : []* Panic {},
341
327
}
342
328
343
329
if i .opt .stdin = options .Stdin ; i .opt .stdin == nil {
@@ -597,28 +583,46 @@ func (f *Func) Name() string {
597
583
return f .name
598
584
}
599
585
600
- func (interp * Interpreter ) FuncForCall (handle uintptr ) (* Func , error ) {
586
+ type IFunc interface {
587
+ Entry () uintptr
588
+ FileLine (uintptr ) (string , int )
589
+ Name () string
590
+ }
591
+
592
+ // return call if we know it, pass to runtime.FuncForPC otherwise
593
+ func (interp * Interpreter ) FuncForPC (handle uintptr ) IFunc {
601
594
n , ok := interp .calls [handle ]
602
595
if ! ok {
603
- return nil , fmt . Errorf ( "Call not found" )
596
+ return runtime . FuncForPC ( handle )
604
597
}
605
598
pos := n .interp .fset .Position (n .pos )
606
599
return & Func {
607
600
pos ,
608
601
funcName (n ),
609
602
handle ,
610
- }, nil
603
+ }
604
+ }
605
+
606
+ func (interp * Interpreter ) FilteredStack () []byte {
607
+ return interp .FilterStack (debug .Stack ())
608
+ }
609
+
610
+ func (interp * Interpreter ) FilteredCallers () []uintptr {
611
+ pc := make ([]uintptr , 64 )
612
+ runtime .Callers (0 , pc )
613
+ _ , fPc := interp .FilterStackAndCallers (debug .Stack (), pc , 2 )
614
+ return fPc
611
615
}
612
616
613
617
func (interp * Interpreter ) FilterStack (stack []byte ) []byte {
614
- newStack , _ := interp .FilterStackAndCallers (stack , []uintptr {})
618
+ newStack , _ := interp .FilterStackAndCallers (stack , []uintptr {}, 2 )
615
619
return newStack
616
620
}
617
621
618
622
// Given a runtime stacktrace and callers list, filter out the interpreter runtime
619
623
// and replace it with the interpreted calls. Parses runtime stacktrace to figure
620
624
// out which interp node by placing a magic value in parameters to runCfg and callBin
621
- func (interp * Interpreter ) FilterStackAndCallers (stack []byte , callers []uintptr ) ([]byte , []uintptr ) {
625
+ func (interp * Interpreter ) FilterStackAndCallers (stack []byte , callers []uintptr , skip int ) ([]byte , []uintptr ) {
622
626
newFrames := [][]string {}
623
627
newCallers := []uintptr {}
624
628
@@ -638,6 +642,7 @@ func (interp *Interpreter) FilterStackAndCallers(stack []byte, callers []uintptr
638
642
}
639
643
640
644
// Parse stack in reverse order, because sometimes we want to skip frames
645
+ var lastInterpFrame int
641
646
for i := len (stackLines ) - 1 ; i >= 0 ; i -- {
642
647
// Split stack trace into paragraphs (frames)
643
648
if len (stackLines [i ]) == 0 || stackLines [i ][0 ] == '\t' {
@@ -664,12 +669,15 @@ func (interp *Interpreter) FilterStackAndCallers(stack []byte, callers []uintptr
664
669
if callersIndex >= 0 {
665
670
callName := runtime .FuncForPC (callers [callersIndex ]).Name ()
666
671
if callName != strings .Split (p [0 ], "(" )[0 ] {
667
- // since we're walking stack and callers at the same time they
668
- // should be in sync. If not, we stop messing with callers
669
- for ; callersIndex >= 0 ; callersIndex -- {
670
- newCallers = append (newCallers , callers [callersIndex ])
672
+ // for some reason runtime.gopanic shows up as panic in stacktrace
673
+ if callName != "runtime.gopanic" || strings .Split (p [0 ], "(" )[0 ] != "panic" {
674
+ // since we're walking stack and callers at the same time they
675
+ // should be in sync. If not, we stop messing with callers
676
+ for ; callersIndex >= 0 ; callersIndex -- {
677
+ newCallers = append (newCallers , callers [callersIndex ])
678
+ }
679
+ callersIndex = dontSync
671
680
}
672
- callersIndex = dontSync
673
681
}
674
682
}
675
683
@@ -703,13 +711,20 @@ func (interp *Interpreter) FilterStackAndCallers(stack []byte, callers []uintptr
703
711
}
704
712
705
713
var handle uintptr
714
+ originalExecNode := false
706
715
707
716
// A runCfg call refers to an interpreter level call
708
717
// grab callHandle from the first parameter to it
709
718
if strings .HasPrefix (funcPath [1 ], "runCfg(" ) {
710
719
fmt .Sscanf (funcPath [1 ], "runCfg(%v," , & handle )
711
720
}
712
721
722
+ // capture node that panicked
723
+ if strings .HasPrefix (funcPath [1 ], "runCfgPanic(" ) {
724
+ fmt .Sscanf (funcPath [1 ], "runCfgPanic(%v," , & handle )
725
+ originalExecNode = true
726
+ }
727
+
713
728
// callBin is a call to a binPkg
714
729
// the callHandle will be on the first or second function literal
715
730
if funcPath [1 ] == "callBin" &&
@@ -720,38 +735,53 @@ func (interp *Interpreter) FilterStackAndCallers(stack []byte, callers []uintptr
720
735
skipFrame = 2
721
736
}
722
737
723
- // special case for panic builtin
724
- if funcPath [1 ] == "_panic" && strings .HasPrefix (funcPath [2 ], "func1(" ) {
725
- fmt .Sscanf (strings .Split (funcPath [2 ], "(" )[1 ], "%v," , & handle )
726
- }
727
-
728
738
if handle != 0 {
729
739
if callersIndex >= 0 {
730
740
newCallers = append (newCallers , handle )
731
741
}
732
742
n , ok := interp .calls [handle ]
733
- if ! ok || n .kind != callExpr {
743
+
744
+ // Don't print scopes that weren't function calls
745
+ // (unless they're the node that caused the panic)
746
+ if ! ok || (n .kind != callExpr && ! originalExecNode ) {
734
747
continue
735
748
}
749
+
736
750
pos := n .interp .fset .Position (n .pos )
737
- newFrames = append ( newFrames , []string {
751
+ newFrame := []string {
738
752
funcName (n ) + "()" ,
739
753
fmt .Sprintf ("\t %s" , pos ),
740
- })
754
+ }
755
+
756
+ // we only find originalExecNode a few frames later
757
+ // so place it right after the last interpreted frame
758
+ if originalExecNode && len (newFrames ) != lastInterpFrame {
759
+ newFrames = append (
760
+ newFrames [:lastInterpFrame + 1 ],
761
+ newFrames [lastInterpFrame :]... )
762
+ newFrames [lastInterpFrame ] = newFrame
763
+ } else {
764
+ newFrames = append (newFrames , newFrame )
765
+ }
766
+ lastInterpFrame = len (newFrames )
741
767
}
742
768
}
743
769
744
770
// reverse order because we parsed from bottom up, fix that now.
745
771
newStack := []string {}
746
- for i := len (newFrames ) - 1 ; i >= 0 ; i -- {
772
+ newStack = append (newStack , newFrames [len (newFrames )- 1 ]... ) // skip after goroutine id
773
+ for i := len (newFrames ) - 2 - skip ; i >= 0 ; i -- {
747
774
newStack = append (newStack , newFrames [i ]... )
748
775
}
749
776
unreversedNewCallers := []uintptr {}
750
- for i := len (newCallers ) - 1 ; i >= 0 ; i -- {
751
- unreversedNewCallers = append (unreversedNewCallers , newCallers [i ])
752
- }
753
- if len (unreversedNewCallers ) == 0 {
754
- unreversedNewCallers = callers // just pass the original through
777
+ if len (newCallers ) == 0 {
778
+ if len (callers ) >= skip {
779
+ unreversedNewCallers = callers [skip :] // just pass the original through
780
+ }
781
+ } else {
782
+ for i := len (newCallers ) - 1 - skip ; i >= 0 ; i -- {
783
+ unreversedNewCallers = append (unreversedNewCallers , newCallers [i ])
784
+ }
755
785
}
756
786
757
787
newStackJoined := strings .Join (newStack , "\n " )
@@ -760,6 +790,64 @@ func (interp *Interpreter) FilterStackAndCallers(stack []byte, callers []uintptr
760
790
return newStackBytes , unreversedNewCallers
761
791
}
762
792
793
+ // Panic is an error recovered from a panic call in interpreted code.
794
+ type Panic struct {
795
+ // Value is the recovered value of a call to panic.
796
+ Value interface {}
797
+
798
+ // Callers is the call stack obtained from the recover call.
799
+ // It may be used as the parameter to runtime.CallersFrames.
800
+ Callers []uintptr
801
+
802
+ // Stack is the call stack buffer for debug.
803
+ Stack []byte
804
+
805
+ // Interpreter runtime frames replaced by interpreted code
806
+ FilteredCallers []uintptr
807
+ FilteredStack []byte
808
+ }
809
+
810
+ func (e Panic ) Error () string {
811
+ return fmt .Sprintf ("panic: %s\n %s\n " , e .Value , e .FilteredStack )
812
+ }
813
+
814
+ // Store a panic record if this is an error we have not seen.
815
+ // Not strictly correct: code might recover from err and never
816
+ // call GetOldestPanicForErr(), and we later return the wrong one.
817
+ func (interp * Interpreter ) Panic (err interface {}) {
818
+ if len (interp .panics ) > 0 && interp .panics [len (interp .panics )- 1 ].Value == err {
819
+ return
820
+ }
821
+ pc := make ([]uintptr , 64 )
822
+ runtime .Callers (0 , pc )
823
+ stack := debug .Stack ()
824
+ fStack , fPc := interp .FilterStackAndCallers (stack , pc , 2 )
825
+ interp .panics = append (interp .panics , & Panic {
826
+ Value : err ,
827
+ Callers : pc ,
828
+ Stack : stack ,
829
+ FilteredCallers : fPc ,
830
+ FilteredStack : fStack ,
831
+ })
832
+ }
833
+
834
+ // We want to capture the full stacktrace from where the panic originated.
835
+ // Return oldest panic that matches err. Then, clear out the list of panics.
836
+ func (interp * Interpreter ) GetOldestPanicForErr (err interface {}) * Panic {
837
+ if _ , ok := err .(* Panic ); ok {
838
+ return err .(* Panic )
839
+ }
840
+ r := (* Panic )(nil )
841
+ for i := len (interp .panics ) - 1 ; i >= 0 ; i -- {
842
+ if interp .panics [i ].Value == err {
843
+ r = interp .panics [i ]
844
+ break
845
+ }
846
+ }
847
+ interp .panics = []* Panic {}
848
+ return r
849
+ }
850
+
763
851
// Eval evaluates Go code represented as a string. Eval returns the last result
764
852
// computed by the interpreter, and a non nil error in case of failure.
765
853
func (interp * Interpreter ) Eval (src string ) (res reflect.Value , err error ) {
0 commit comments