From 2b087dbf955ecf59fe260d7aa767df712ed032a3 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 12 Oct 2019 00:04:42 +0300 Subject: [PATCH] backtraces: add source line numbers too on linux * Add source line numbers to backtraces on linux. * Fix -g (broken after token caching). * reset the #line directives after all the v code is compiled * cleanup p.cgen.line setting inside p.next() . * Support windows filepaths like "C:\Users\travis\build\vlang\v\v.exe.tmp.c" inside generated #line directives. * Try to diagnose better windows-gcc failing. * Revert "Try to diagnose better windows-gcc failing." * implement and use cescaped_path/1 . * Use cescaped_path/1 consistently throughout compiler/ . --- compiler/cgen.v | 4 +++- compiler/fn.v | 2 +- compiler/live.v | 4 ++-- compiler/main.v | 14 ++++++++++++++ compiler/parser.v | 21 +++++++++++++++------ compiler/scanner.v | 2 +- vlib/builtin/builtin.v | 27 ++++++++++++++++++++++++++- 7 files changed, 62 insertions(+), 12 deletions(-) diff --git a/compiler/cgen.v b/compiler/cgen.v index 4ea21c6701..6140906558 100644 --- a/compiler/cgen.v +++ b/compiler/cgen.v @@ -63,7 +63,9 @@ fn (g mut CGen) genln(s string) { g.cur_line = '$g.cur_line $s' if g.cur_line != '' { if g.line_directives && g.cur_line.trim_space() != '' { - g.lines << '\n#line $g.line "$g.file"' + if g.file.len > 0 && g.line > 0 { + g.lines << '\n#line $g.line "$g.file"' + } } g.lines << g.cur_line g.prev_line = g.cur_line diff --git a/compiler/fn.v b/compiler/fn.v index 2f93d6e4e5..8cf3572aa1 100644 --- a/compiler/fn.v +++ b/compiler/fn.v @@ -809,7 +809,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { if p.v.pref.is_debug && f.name == 'panic' && !p.is_js { mod_name := p.mod.replace('_dot_', '.') fn_name := p.cur_fn.name.replace('${p.mod}__', '') - file_path := p.file_path.replace('\\', '\\\\') // escape \ + file_path := cescaped_path(p.file_path) p.cgen.resetln(p.cgen.cur_line.replace( 'v_panic (', 'panic_debug ($p.scanner.line_nr, tos3("$file_path"), tos3("$mod_name"), tos2((byte *)"$fn_name"), ' diff --git a/compiler/live.v b/compiler/live.v index 7d5fce9763..72d190d1f0 100644 --- a/compiler/live.v +++ b/compiler/live.v @@ -73,8 +73,8 @@ fn (v &V) generate_hot_reload_code() { mut vexe := os.args[0] if os.user_os() == 'windows' { - vexe = vexe.replace('\\', '\\\\') - file = file.replace('\\', '\\\\') + vexe = cescaped_path(vexe) + file = cescaped_path(file) } mut msvc := '' diff --git a/compiler/main.v b/compiler/main.v index 996d03020f..1e48c9dc01 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -338,6 +338,7 @@ fn (v mut V) compile() { if v.pref.build_mode == .build_module { v.generate_vh() } + // parse generated V code (str() methods etc) mut vgen_parser := v.new_parser_from_string(v.vgen_buf.str(), 'vgen') // free the string builder which held the generated methods @@ -383,6 +384,15 @@ fn (v mut V) generate_main() { mut cgen := v.cgen $if js { return } + ///// After this point, the v files are compiled. + ///// The rest is auto generated code, which will not have + ///// different .v source file/line numbers. + lines_so_far := cgen.lines.join('\n').count('\n') + 5 + cgen.genln('') + cgen.genln('////////////////// Reset the file/line numbers //////////') + cgen.lines << '#line $lines_so_far "${cescaped_path(os.realpath(cgen.out_path))}"' + cgen.genln('') + if v.pref.build_mode == .default_mode { mut consts_init_body := cgen.consts_init.join_lines() // vlib can't have `init_consts()` @@ -1042,3 +1052,7 @@ fn vhash() string { C.snprintf(*char(buf), 50, '%s', C.V_COMMIT_HASH ) return tos_clone(buf) } + +fn cescaped_path(s string) string { + return s.replace('\\','\\\\') +} diff --git a/compiler/parser.v b/compiler/parser.v index 4781eafeed..3211ef32fd 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -100,7 +100,14 @@ fn (v mut V) new_parser_from_string(text string, id string) Parser { return p } +fn (v mut V) reset_cgen_file_line_parameters(){ + v.cgen.line = 0 + v.cgen.file = '' + v.cgen.line_directives = v.pref.is_debuggable +} + fn (v mut V) new_parser_from_file(path string) Parser { + v.reset_cgen_file_line_parameters() //println('new_parser("$path")') mut path_pcguard := '' mut path_platform := '.v' @@ -124,7 +131,6 @@ fn (v mut V) new_parser_from_file(path string) Parser { if p.pref.building_v { p.scanner.should_print_relative_paths_on_error = true } - v.cgen.file = path p.scan_tokens() //p.scanner.debug_tokens() return p @@ -133,6 +139,7 @@ fn (v mut V) new_parser_from_file(path string) Parser { // creates a new parser. most likely you will want to use // `new_parser_file` or `new_parser_string` instead. fn (v mut V) new_parser(scanner &Scanner, id string) Parser { + v.reset_cgen_file_line_parameters() mut p := Parser { id: id scanner: scanner @@ -155,8 +162,6 @@ fn (v mut V) new_parser(scanner &Scanner, id string) Parser { p.scanner.should_print_errors_in_color = false p.scanner.should_print_relative_paths_on_error = true } - v.cgen.line_directives = v.pref.is_debuggable - // v.cgen.file = path return p } @@ -195,6 +200,7 @@ fn (p mut Parser) next() { p.tok = res.tok p.lit = res.lit p.scanner.line_nr = res.line_nr + p.cgen.line = res.line_nr } fn (p & Parser) peek() TokenKind { @@ -229,7 +235,10 @@ fn (p &Parser) log(s string) { */ } -fn (p mut Parser) parse(pass Pass) { +fn (p mut Parser) parse(pass Pass) { + p.cgen.line = 0 + p.cgen.file = cescaped_path(os.realpath(p.file_path)) + ///////////////////////////////////// p.pass = pass p.token_idx = 0 p.next() @@ -2807,7 +2816,7 @@ fn (p mut Parser) string_expr() { // No ${}, just return a simple string if p.peek() != .dollar || is_raw { p.fgen("'$str'") - f := if is_raw { str.replace('\\', '\\\\') } else { format_str(str) } + f := if is_raw { cescaped_path(str) } else { format_str(str) } // `C.puts('hi')` => `puts("hi");` /* Calling a C function sometimes requires a call to a string method @@ -3849,7 +3858,7 @@ fn (p mut Parser) assert_statement() { p.gen('bool $tmp = ') p.check_types(p.bool_expression(), 'bool') // TODO print "expected: got" for failed tests - filename := p.file_path.replace('\\', '\\\\') + filename := cescaped_path(p.file_path) p.genln('; \n diff --git a/compiler/scanner.v b/compiler/scanner.v index b97a7392d3..5829f6ec8c 100644 --- a/compiler/scanner.v +++ b/compiler/scanner.v @@ -410,7 +410,7 @@ fn (s mut Scanner) scan() ScanRes { // println( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @FN) // ... which is useful while debugging/tracing if name == 'FN' { return scan_res(.str, s.fn_name) } - if name == 'FILE' { return scan_res(.str, os.realpath(s.file_path).replace('\\', '\\\\')) } // escape \ + if name == 'FILE' { return scan_res(.str, cescaped_path(os.realpath(s.file_path))) } if name == 'LINE' { return scan_res(.str, (s.line_nr+1).str()) } if name == 'COLUMN' { return scan_res(.str, (s.current_column()).str()) } if name == 'VHASH' { return scan_res(.str, vhash()) } diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index 60d0d0e9f3..90e216437e 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -55,7 +55,32 @@ pub fn print_backtrace_skipping_top_frames(skipframes int) { if C.backtrace_symbols_fd != 0 { buffer := [100]byteptr nr_ptrs := C.backtrace(*voidptr(buffer), 100) - C.backtrace_symbols_fd(*voidptr(&buffer[skipframes]), nr_ptrs-skipframes, 1) + nr_actual_frames := nr_ptrs-skipframes + mut sframes := []string + csymbols := *byteptr(C.backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames)) + for i in 0..nr_actual_frames { sframes << tos2(csymbols[i]) } + for sframe in sframes { + executable := sframe.all_before('(') + addr := sframe.all_after('[').all_before(']') + cmd := 'addr2line -e $executable $addr' + + // taken from os, to avoid depending on the os module inside builtin.v + f := byteptr(C.popen(cmd.str, 'r')) + if isnil(f) { + println(sframe) continue + } + buf := [1000]byte + mut output := '' + for C.fgets(buf, 1000, f) != 0 { + output += tos(buf, vstrlen(buf)) + } + output = output.trim_space()+':' + if 0 != int(C.pclose(f)) { + println(sframe) continue + } + println( '${output:-45s} | $sframe') + } + //C.backtrace_symbols_fd(*voidptr(&buffer[skipframes]), nr_actual_frames, 1) return }else{ C.printf('backtrace_symbols_fd is missing, so printing backtraces is not available.\n')