Mercurial > hg > Others > Rakudo
diff src/core.c/Backtrace.pm6 @ 0:c341f82e7ad7 default tip
Rakudo branch in cr.ie.u-ryukyu.ac.jp
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Thu, 26 Dec 2019 16:50:27 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core.c/Backtrace.pm6 Thu Dec 26 16:50:27 2019 +0900 @@ -0,0 +1,354 @@ +my class Exception { ... } + +my class Backtrace { ... } +my class CompUnit::RepositoryRegistry is repr('Uninstantiable') { ... } + +my class Backtrace::Frame { + has Str $.file; + has Int $.line; + has Mu $.code; + has Str $.subname; + + method !SET-SELF($!file,$!line,\code,$!subname) { + $!code := code; + self + } + multi method new(Backtrace::Frame: \file,\line,\code,\subname) { + nqp::create(self)!SET-SELF(file,line,code,subname) + } + multi method new(Backtrace::Frame: |c) { + self.bless(|c) + } + + method subtype(Backtrace::Frame:D:) { + my $s = $!code.^name.lc.split('+', 2).cache[0]; + $s eq 'mu' ?? '' !! $s; + } + + method package(Backtrace::Frame:D:) { + $.code.package; + } + + multi method Str(Backtrace::Frame:D:) { + my $s = self.subtype; + $s ~= ' ' if $s.chars; + my $text = " in {$s}$.subname at {$.file} line $.line\n"; + + if Backtrace.RAKUDO_VERBOSE_STACKFRAME -> $extra { + my $io = $!file.IO; + if $io.e { + my @lines = $io.lines; + my $from = max $!line - $extra, 1; + my $to = min $!line + $extra, +@lines; + for $from..$to -> $line { + my $star = $line == $!line ?? '*' !! ' '; + $text ~= "$line.fmt('%5d')$star @lines[$line - 1]\n"; + } + $text ~= "\n"; + } + } + $text; + } + + method is-hidden(Backtrace::Frame:D:) { + nqp::if( + nqp::can($!code,"is-hidden-from-backtrace"), + $!code.is-hidden-from-backtrace, + False + ) + } + method is-routine(Backtrace::Frame:D:) { + nqp::hllbool(nqp::istype($!code,Routine)) + } + method is-setting(Backtrace::Frame:D:) { + $!file.starts-with("SETTING::") +#?if jvm + || $!file ~~ / "CORE." \w+ ".setting" $ / +#?endif +#?if !jvm + || $!file ~~ / "CORE." \w+ ".setting.{ Rakudo::Internals.PRECOMP-EXT }" $ / +#?endif + || $!file.ends-with(".nqp") + } +} + +my class Backtrace { + has Mu $!bt; + has Mu $!frames; + has Int $!bt-next; # next bt index to vivify + + my $RAKUDO_VERBOSE_STACKFRAME := nqp::null; + method RAKUDO_VERBOSE_STACKFRAME() { + nqp::ifnull( + $RAKUDO_VERBOSE_STACKFRAME, + $RAKUDO_VERBOSE_STACKFRAME := + (%*ENV<RAKUDO_VERBOSE_STACKFRAME> // 0).Int + ) + } + + method !SET-SELF($!bt,$!bt-next) { + $!frames := nqp::list; + self + } + multi method new() { + try X::AdHoc.new(:payload("Died")).throw; + nqp::create(self)!SET-SELF( + nqp::backtrace(nqp::getattr(nqp::decont($!),Exception,'$!ex')), + 1) + } + multi method new(Int:D $offset) { + try X::AdHoc.new(:payload("Died")).throw; + nqp::create(self)!SET-SELF( + nqp::backtrace(nqp::getattr(nqp::decont($!),Exception,'$!ex')), + 1 + $offset) + } + multi method new(Mu \ex) { + nqp::create(self)!SET-SELF( + ex.^name eq 'BOOTException' + ?? nqp::backtrace(nqp::decont(ex)) + !! nqp::backtrace(nqp::getattr(nqp::decont(ex),Exception,'$!ex')), + 0) + } + multi method new(Mu \ex, Int:D $offset) { + nqp::create(self)!SET-SELF( + ex.^name eq 'BOOTException' + ?? nqp::backtrace(nqp::decont(ex)) + !! nqp::backtrace(nqp::getattr(nqp::decont(ex),Exception,'$!ex')), + $offset) + } + # note that backtraces are nqp::list()s, marshalled to us as a List + multi method new(List:D $bt) { + nqp::create(self)!SET-SELF($bt,0) + } + multi method new(List:D $bt, Int:D $offset) { + nqp::create(self)!SET-SELF($bt,$offset) + } + + method AT-POS($pos) { + return nqp::atpos($!frames,$pos) if nqp::existspos($!frames,$pos); + + my int $elems = $!bt.elems; + return Nil if $!bt-next >= $elems; # bt-next can init > elems + + my int $todo = $pos - nqp::elems($!frames) + 1; + return Nil if $todo < 1; # in case absurd $pos passed + while $!bt-next < $elems { + my $frame := $!bt.AT-POS($!bt-next++); + my $sub := $frame<sub>; + next unless defined $sub; + + my Mu $do := nqp::getattr(nqp::decont($sub), ForeignCode, '$!do'); + next if nqp::isnull($do); + + my $annotations := $frame<annotations>; + next unless $annotations; + + my $file := $annotations<file>; + next unless $file; + + if CompUnit::RepositoryRegistry.file-for-spec($file) -> $path { + $file := $path.absolute; + } + + next if $file.ends-with('BOOTSTRAP.nqp') + || $file.ends-with('QRegex.nqp') + || $file.ends-with('Perl6/Ops.nqp'); + if $file.ends-with('NQPHLL.nqp') || $file.ends-with('NQPHLL.moarvm') { + # This could mean we're at the end of the interesting backtrace, + # or it could mean that we're in something like sprintf (which + # uses an NQP grammar to parse the format string). + while $!bt-next < $elems { + my $frame := $!bt.AT-POS($!bt-next++); + my $annotations := $frame<annotations>; + next unless $annotations; + my $file := $annotations<file>; + next unless $file; + if $file.starts-with('SETTING::') { + $!bt-next--; # re-visit this frame + last; + } + } + next; + } + + my $line := $annotations<line>; + next unless $line; + + my $name := nqp::p6box_s(nqp::getcodename($do)); + if $name eq 'handle-begin-time-exceptions' { + $!bt-next = $elems; + last; + } + + my $code; + try { + $code := nqp::getcodeobj($do); + $code := Any unless nqp::istype($code, Mu); + }; + + nqp::push($!frames, + Backtrace::Frame.new( + $file, + $line.Int, + $code, + $name.starts-with("_block") ?? '<anon>' !! $name, + ) + ); + last unless $todo = $todo - 1; + } + + # found something + if nqp::existspos($!frames,$pos) { + nqp::atpos($!frames,$pos); + } + + # we've reached the end, don't show the last <unit-outer> if there is one + else { + nqp::pop($!frames) if $!frames; + Nil; + } + } + + method next-interesting-index(Backtrace:D: + Int $idx is copy = 0, :$named, :$noproto, :$setting) { + ++$idx; + + while self.AT-POS($idx++) -> $cand { + next if $cand.is-hidden; # hidden is never interesting + next if $noproto # no proto's please + && nqp::can($cand,"is_dispatcher") + && $cand.code.is_dispatcher; # if a dispatcher + next if !$setting # no settings please + && $cand.is-setting; # and in setting + + my $n := $cand.subname; + next if $named && !$n; # only want named ones and no name + next if $n eq '<unit-outer>'; # outer calling context + + return $idx - 1; + } + Nil; + } + + method outer-caller-idx(Backtrace:D: Int $startidx) { + + if self.AT-POS($startidx).code -> $start { + my %outers; + + my $current = $start.outer; + while $current.DEFINITE { + %outers{$current.static_id} = $start; + $current = $current.outer; + } + + my @outers; + my $i = $startidx; + while self.AT-POS($i++) -> $cand { + my $code = $cand.code; + next unless $code.DEFINITE && %outers{$code.static_id}.DEFINITE; + + @outers.push: $i - 1; + last if $cand.is-routine; + } + @outers; + } + + else { + $startidx.list; + } + } + + method nice(Backtrace:D: :$oneline) { + my $setting = %*ENV<RAKUDO_BACKTRACE_SETTING>; + try { + my @frames; + my Int $i = self.next-interesting-index(-1); + while $i.defined { + $i = self.next-interesting-index($i, :$setting) if $oneline; + last unless $i.defined; + + my $prev = self.AT-POS($i); + if $prev.is-routine { + @frames.push: $prev; + } else { + my @outer_callers := self.outer-caller-idx($i); + my $target_idx = @outer_callers.keys.grep({self.AT-POS($i).code.^isa(Routine)})[0]; + $target_idx ||= @outer_callers[0] || $i; + my $current = self.AT-POS($target_idx); + @frames.append: $current.clone(line => $prev.line); + $i = $target_idx; + } + last if $oneline; + $i = self.next-interesting-index($i, :$setting); + } + CATCH { + default { + return "<Internal error while creating backtrace: $_.message() $_.backtrace.full().\n" + ~ "Please report this as a bug (mail to rakudobug@perl.org)\n", + ~ "and re-run with the --ll-exception command line option\n" + ~ "to get more information about your error>"; + } + } + @frames.join; + } + } + + multi method gist(Backtrace:D:) { + my $els := +self.list; + 'Backtrace(' ~ $els ~ ' frame' ~ 's' x ($els != 1) ~ ')' + } + multi method Str(Backtrace:D:) { self.nice } + multi method flat(Backtrace:D:) { self.list } + multi method map(Backtrace:D: &block) { + my $pos = 0; + gather while self.AT-POS($pos++) -> $cand { + take block($cand); + } + } + multi method first(Backtrace:D: Mu $test) { + my $pos = 0; + while self.AT-POS($pos++) -> $cand { + return-rw $cand if $cand ~~ $test; + } + Nil; + } + multi method list(Backtrace:D:) { + self.AT-POS(1_000_000); # will stop when no more frames to be found + nqp::p6bindattrinvres(nqp::create(List), List, '$!reified', $!frames) + } + + method first-none-setting-line(Backtrace:D:) { + (self.first({ !.is-hidden && !.is-setting }) // "\n").Str; + } + + method concise(Backtrace:D:) { + (self.grep({ !.is-hidden && .is-routine && !.is-setting }) // "\n").join; + } + + method full(Backtrace:D:) { self.list.join } + + method summary(Backtrace:D:) { + (self.grep({ !.is-hidden && (.is-routine || !.is-setting)}) // "\n").join; + } + + method is-runtime (Backtrace:D:) { + my $bt = $!bt; + for $bt.keys { + my $p6sub := $bt[$_]<sub>; + if nqp::istype($p6sub, ForeignCode) { + try { + my Mu $sub := nqp::getattr(nqp::decont($p6sub), ForeignCode, '$!do'); + my str $name = nqp::getcodename($sub); + return True if nqp::iseq_s($name, 'THREAD-ENTRY'); + return True if nqp::iseq_s($name, 'eval'); + return True if nqp::iseq_s($name, 'print_control'); + return False if nqp::iseq_s($name, 'compile'); + } + } + } + False; + } + +} + +# vim: ft=perl6 expandtab sw=4