diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9869a9a71c..f2d5f1c84d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -63,6 +63,7 @@ jobs: echo "LFORTRAN_CMAKE_GENERATOR=Unix Makefiles" >> $GITHUB_ENV echo "WIN=0" >> $GITHUB_ENV echo "MACOS=0" >> $GITHUB_ENV + echo "ENABLE_RUNTIME_STACKTRACE=yes" >> $GITHUB_ENV - name: Setup Platform (macOS) if: contains(matrix.os, 'macos') @@ -71,6 +72,7 @@ jobs: echo "LFORTRAN_CMAKE_GENERATOR=Unix Makefiles" >> $GITHUB_ENV echo "WIN=0" >> $GITHUB_ENV echo "MACOS=1" >> $GITHUB_ENV + echo "ENABLE_RUNTIME_STACKTRACE=yes" >> $GITHUB_ENV - name: Build (Linux / macOS) shell: bash -l {0} @@ -88,6 +90,7 @@ jobs: set LFORTRAN_CMAKE_GENERATOR=Ninja set WIN=1 set MACOS=0 + set ENABLE_RUNTIME_STACKTRACE=no call "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat" xonsh ci\build.xsh diff --git a/.gitignore b/.gitignore index da0bcebc84..468bb9c6ed 100644 --- a/.gitignore +++ b/.gitignore @@ -84,6 +84,9 @@ inst/bin/* *.sln *.dll *.manifest +*_lines.txt +*_ldd.txt +*_lines.dat.txt ### https://raw.github.com/github/gitignore/218a941be92679ce67d0484547e3e142b2f5f6f0/Global/macOS.gitignore diff --git a/CMakeLists.txt b/CMakeLists.txt index b8c70a06fb..2f777e05dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,6 +243,15 @@ if (WITH_STACKTRACE) endif() set(HAVE_LFORTRAN_STACKTRACE yes) endif() +if (WITH_RUNTIME_STACKTRACE) + set(WITH_UNWIND yes) + if (APPLE) + set(WITH_MACHO yes) + else() + set(WITH_LINKH yes) + endif() + set(HAVE_RUNTIME_STACKTRACE yes) +endif() if (WITH_BFD) find_package(BFD REQUIRED) set(HAVE_LFORTRAN_BFD yes) diff --git a/build1.sh b/build1.sh index 376783d79b..8bf7476431 100755 --- a/build1.sh +++ b/build1.sh @@ -8,6 +8,7 @@ cmake \ -DWITH_LLVM=yes \ -DLPYTHON_BUILD_ALL=yes \ -DWITH_STACKTRACE=yes \ + -DWITH_RUNTIME_STACKTRACE=yes \ -DWITH_LSP=no \ -DWITH_LFORTRAN_BINARY_MODFILES=no \ -DCMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH_LPYTHON;$CONDA_PREFIX" \ diff --git a/ci/build.xsh b/ci/build.xsh index b95646b1c2..fb2d9233c2 100755 --- a/ci/build.xsh +++ b/ci/build.xsh @@ -51,7 +51,7 @@ cd test-bld # compiled in Release mode and we get link failures if we mix and match build # modes: BUILD_TYPE = "Release" -cmake -G $LFORTRAN_CMAKE_GENERATOR -DCMAKE_VERBOSE_MAKEFILE=ON -DWITH_LLVM=yes -DWITH_LSP=yes -DWITH_XEUS=yes -DCMAKE_PREFIX_PATH=$CONDA_PREFIX -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX -DWITH_LFORTRAN_BINARY_MODFILES=no -DCMAKE_BUILD_TYPE=@(BUILD_TYPE) .. +cmake -G $LFORTRAN_CMAKE_GENERATOR -DCMAKE_VERBOSE_MAKEFILE=ON -DWITH_LLVM=yes -DWITH_LSP=yes -DWITH_XEUS=yes -DCMAKE_PREFIX_PATH=$CONDA_PREFIX -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX -DWITH_LFORTRAN_BINARY_MODFILES=no -DCMAKE_BUILD_TYPE=@(BUILD_TYPE) -DWITH_RUNTIME_STACKTRACE=$ENABLE_RUNTIME_STACKTRACE .. cmake --build . --target install -j16 ./src/lpython/tests/test_lpython #./src/bin/lpython < ../src/bin/example_input.txt diff --git a/run_tests.py b/run_tests.py index 461eaa8613..9393e302ba 100755 --- a/run_tests.py +++ b/run_tests.py @@ -27,6 +27,7 @@ def is_included(backend): c = is_included("c") wat = is_included("wat") run = is_included("run") + run_with_dbg = is_included("run_with_dbg") pass_ = test.get("pass", None) optimization_passes = ["flip_sign", "div_to_mul", "fma", "sign_from_value", "inline_function_calls", "loop_unroll", @@ -117,5 +118,11 @@ def is_included(backend): run_test(filename, "runtime", "lpython {infile}", filename, update_reference, extra_args) + if run_with_dbg: + run_test( + filename, "run_dbg", + "lpython {infile} -g --debug-with-line-column --no-color", + filename, update_reference, extra_args) + if __name__ == "__main__": tester_main("LPython", single_test) diff --git a/src/bin/dat_convert.py b/src/bin/dat_convert.py new file mode 100755 index 0000000000..d7ae867e29 --- /dev/null +++ b/src/bin/dat_convert.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +from struct import unpack +from sys import argv +from re import sub + +lines = "" +with open(argv[1], "rb") as f: + lines = f.read() + +list = [] +for i in range(0, len(lines), 24): + list.append(sub('[(),]', '', str(unpack("3Q", lines[i:i+24])))) + +with open(argv[1] + ".txt", "w") as f: + j = 0 + for i in list: + f.write(i+'\n') diff --git a/src/bin/lpython.cpp b/src/bin/lpython.cpp index 56a2627ee3..37fd39ab5f 100644 --- a/src/bin/lpython.cpp +++ b/src/bin/lpython.cpp @@ -1639,6 +1639,29 @@ int main(int argc, char *argv[]) err = link_executable({tmp_o}, outfile, runtime_library_dir, backend, static_link, true, compiler_options); if (err != 0) return err; + + if (compiler_options.emit_debug_info) { + // TODO: Replace the following hardcoded part + std::string cmd = ""; +#ifdef HAVE_LFORTRAN_MACHO + cmd += "dsymutil " + basename + ".out && llvm-dwarfdump --debug-line " + + basename + ".out.dSYM > "; +#else + cmd += "llvm-dwarfdump --debug-line " + basename + ".out > "; +#endif + cmd += basename + "_ldd.txt && (cd src/bin; ./dwarf_convert.py ../../" + + basename + "_ldd.txt ../../" + basename + "_lines.txt ../../" + + basename + "_lines.dat && ./dat_convert.py ../../" + + basename + "_lines.dat)"; + int status = system(cmd.c_str()); + if ( status != 0 ) { + std::cerr << "Error in creating the files used to generate " + "the debug information. This might be caused because either" + "`llvm-dwarfdump` or `Python` are not available. " + "Please activate the CONDA environment and compile again.\n"; + return status; + } + } #else std::cerr << "Compiling Python files to object files requires the LLVM backend to be enabled. Recompile with `WITH_LLVM=yes`." << std::endl; return 1; diff --git a/src/libasr/codegen/asr_to_llvm.cpp b/src/libasr/codegen/asr_to_llvm.cpp index dfe19271fe..ac0af8707e 100644 --- a/src/libasr/codegen/asr_to_llvm.cpp +++ b/src/libasr/codegen/asr_to_llvm.cpp @@ -5165,8 +5165,16 @@ class ASRToLLVMVisitor : public ASR::BaseVisitor } void visit_Assert(const ASR::Assert_t &x) { + if (compiler_options.emit_debug_info) debug_emit_loc(x); this->visit_expr_wrapper(x.m_test, true); create_if_else(tmp, []() {}, [=]() { + if (compiler_options.emit_debug_info) { + llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr(infile); + llvm::Value *fmt_ptr1 = llvm::ConstantInt::get(context, llvm::APInt( + 1, compiler_options.use_colors)); + call_print_stacktrace_addresses(context, *module, *builder, + {fmt_ptr, fmt_ptr1}); + } if (x.m_msg) { char* s = ASR::down_cast(x.m_msg)->m_s; llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr("AssertionError: %s\n"); @@ -5949,6 +5957,14 @@ class ASRToLLVMVisitor : public ASR::BaseVisitor } void visit_Stop(const ASR::Stop_t &x) { + if (compiler_options.emit_debug_info) { + debug_emit_loc(x); + llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr(infile); + llvm::Value *fmt_ptr1 = llvm::ConstantInt::get(context, llvm::APInt( + 1, compiler_options.use_colors)); + call_print_stacktrace_addresses(context, *module, *builder, + {fmt_ptr, fmt_ptr1}); + } llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr("STOP\n"); print_error(context, *module, *builder, {fmt_ptr}); llvm::Value *exit_code; @@ -5963,7 +5979,15 @@ class ASRToLLVMVisitor : public ASR::BaseVisitor exit(context, *module, *builder, exit_code); } - void visit_ErrorStop(const ASR::ErrorStop_t & /* x */) { + void visit_ErrorStop(const ASR::ErrorStop_t &x) { + if (compiler_options.emit_debug_info) { + debug_emit_loc(x); + llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr(infile); + llvm::Value *fmt_ptr1 = llvm::ConstantInt::get(context, llvm::APInt( + 1, compiler_options.use_colors)); + call_print_stacktrace_addresses(context, *module, *builder, + {fmt_ptr, fmt_ptr1}); + } llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr("ERROR STOP\n"); print_error(context, *module, *builder, {fmt_ptr}); int exit_code_int = 1; diff --git a/src/libasr/codegen/llvm_utils.h b/src/libasr/codegen/llvm_utils.h index eee874a8b3..5a35bac52f 100644 --- a/src/libasr/codegen/llvm_utils.h +++ b/src/libasr/codegen/llvm_utils.h @@ -56,6 +56,27 @@ namespace LCompilers { builder.CreateCall(fn_exit, {exit_code}); } + // Insert the following anywhere inside the LLVM backend to print + // addresses at runtime: + // call_print_stacktrace_addresses(context, *module, *builder, {filename, use_colors}); + static inline void call_print_stacktrace_addresses(llvm::LLVMContext &context, + llvm::Module &module, llvm::IRBuilder<> &builder, + const std::vector &args) + { + llvm::Function *fn = module.getFunction("print_stacktrace_addresses"); + if (!fn) { + llvm::FunctionType *function_type = llvm::FunctionType::get( + llvm::Type::getVoidTy(context), { + llvm::Type::getInt8PtrTy(context), + llvm::Type::getInt1Ty(context) + }, true); + fn = llvm::Function::Create(function_type, + llvm::Function::ExternalLinkage, "print_stacktrace_addresses", + &module); + } + builder.CreateCall(fn, args); + } + namespace LLVM { llvm::Value* CreateLoad(llvm::IRBuilder<> &builder, llvm::Value *x); diff --git a/src/libasr/config.h.in b/src/libasr/config.h.in index cc0e058a02..292b593bb4 100644 --- a/src/libasr/config.h.in +++ b/src/libasr/config.h.in @@ -15,6 +15,7 @@ /* Define if stacktrace is enabled */ #cmakedefine HAVE_LFORTRAN_STACKTRACE +#cmakedefine HAVE_RUNTIME_STACKTRACE #cmakedefine HAVE_LFORTRAN_BFD #cmakedefine HAVE_LFORTRAN_DWARFDUMP #cmakedefine HAVE_LFORTRAN_LINK diff --git a/src/libasr/runtime/lfortran_intrinsics.c b/src/libasr/runtime/lfortran_intrinsics.c index 45ca9a931c..8ad1f7a72b 100644 --- a/src/libasr/runtime/lfortran_intrinsics.c +++ b/src/libasr/runtime/lfortran_intrinsics.c @@ -8,9 +8,62 @@ #include #include #include +#include + +#include +#include + +#ifdef HAVE_RUNTIME_STACKTRACE + +#ifdef HAVE_LFORTRAN_LINK +// For dl_iterate_phdr() functionality +# include +struct dl_phdr_info { + ElfW(Addr) dlpi_addr; + const char *dlpi_name; + const ElfW(Phdr) *dlpi_phdr; + ElfW(Half) dlpi_phnum; +}; +extern int dl_iterate_phdr (int (*__callback) (struct dl_phdr_info *, + size_t, void *), void *__data); +#endif + +#ifdef HAVE_LFORTRAN_UNWIND +// For _Unwind_Backtrace() function +# include +#endif + +#ifdef HAVE_LFORTRAN_MACHO +# include +#endif + +// Runtime Stacktrace +#define LCOMPILERS_MAX_STACKTRACE_LENGTH 200 +char *source_filename; +char *binary_executable_path = "/proc/self/exe"; + +struct Stacktrace { + uintptr_t pc[LCOMPILERS_MAX_STACKTRACE_LENGTH]; + uint64_t pc_size; + uintptr_t current_pc; + + uintptr_t local_pc[LCOMPILERS_MAX_STACKTRACE_LENGTH]; + char *binary_filename[LCOMPILERS_MAX_STACKTRACE_LENGTH]; + uint64_t local_pc_size; -#include "lfortran_intrinsics.h" + uint64_t addresses[LCOMPILERS_MAX_STACKTRACE_LENGTH]; + uint64_t line_numbers[LCOMPILERS_MAX_STACKTRACE_LENGTH]; + uint64_t stack_size; +}; +// Styles and Colors +#define DIM "\033[2m" +#define BOLD "\033[1m" +#define S_RESET "\033[0m" +#define MAGENTA "\033[35m" +#define C_RESET "\033[39m" + +#endif // HAVE_RUNTIME_STACKTRACE LFORTRAN_API double _lfortran_sum(int n, double *v) { @@ -1176,14 +1229,14 @@ LFORTRAN_API void _lpython_close(int64_t fd) } LFORTRAN_API int32_t _lfortran_ichar(char *c) { - return (int32_t) c[0]; + return (int32_t) c[0]; } LFORTRAN_API int32_t _lfortran_iachar(char *c) { - return (int32_t) c[0]; - } + return (int32_t) c[0]; +} -// Command line arguments +// >> Command line arguments >> ------------------------------------------------ int32_t argc; char **argv; @@ -1202,3 +1255,300 @@ LFORTRAN_API int32_t _lpython_get_argc() { LFORTRAN_API char *_lpython_get_argv(int32_t index) { return argv[index]; } + +// << Command line arguments << ------------------------------------------------ + +// >> Runtime Stacktrace >> ---------------------------------------------------- +#ifdef HAVE_RUNTIME_STACKTRACE + +#ifdef HAVE_LFORTRAN_UNWIND +static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context *context, + void *vdata) { + struct Stacktrace *d = (struct Stacktrace *) vdata; + uintptr_t pc = _Unwind_GetIP(context); + if (pc != 0) { + pc--; + if (d->pc_size < LCOMPILERS_MAX_STACKTRACE_LENGTH) { + d->pc[d->pc_size] = pc; + d->pc_size++; + } else { + printf("The stacktrace length is out of range.\nAborting..."); + abort(); + } + } + return _URC_NO_REASON; +} +#endif // HAVE_LFORTRAN_UNWIND + +struct Stacktrace get_stacktrace_addresses() { + struct Stacktrace d; + d.pc_size = 0; +#ifdef HAVE_LFORTRAN_UNWIND + _Unwind_Backtrace(unwind_callback, &d); +#endif + return d; +} + +char *get_base_name(char *filename) { + size_t start = strrchr(filename, '/')-filename+1; + size_t end = strrchr(filename, '.')-filename-1; + int nos_of_chars = end - start + 1; + char *base_name; + if (nos_of_chars < 0) { + return NULL; + } + base_name = malloc (sizeof (char) * (nos_of_chars + 1)); + base_name[nos_of_chars] = '\0'; + strncpy (base_name, filename + start, nos_of_chars); + return base_name; +} + +#ifdef HAVE_LFORTRAN_LINK +int shared_lib_callback(struct dl_phdr_info *info, + size_t /* size */, void *_data) { + struct Stacktrace *d = (struct Stacktrace *) _data; + for (int i = 0; i < info->dlpi_phnum; i++) { + if (info->dlpi_phdr[i].p_type == PT_LOAD) { + ElfW(Addr) min_addr = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr; + ElfW(Addr) max_addr = min_addr + info->dlpi_phdr[i].p_memsz; + if ((d->current_pc >= min_addr) && (d->current_pc < max_addr)) { + d->binary_filename[d->local_pc_size] = (char *)info->dlpi_name; + if (d->binary_filename[d->local_pc_size][0] == '\0') { + d->binary_filename[d->local_pc_size] = binary_executable_path; + d->local_pc[d->local_pc_size] = d->current_pc - info->dlpi_addr; + d->local_pc_size++; + } + // We found a match, return a non-zero value + return 1; + } + } + } + // We didn't find a match, return a zero value + return 0; +} +#endif // HAVE_LFORTRAN_LINK + +#ifdef HAVE_LFORTRAN_MACHO +void get_local_address_mac(struct Stacktrace *d) { + for (uint32_t i = 0; i < _dyld_image_count(); i++) { + const struct mach_header *header = _dyld_get_image_header(i); + intptr_t offset = _dyld_get_image_vmaddr_slide(i); + struct load_command* cmd = (struct load_command*)((char *)header + sizeof(struct mach_header)); + if(header->magic == MH_MAGIC_64) { + cmd = (struct load_command*)((char *)header + sizeof(struct mach_header_64)); + } + for (uint32_t j = 0; j < header->ncmds; j++) { + if (cmd->cmd == LC_SEGMENT) { + struct segment_command* seg = (struct segment_command*)cmd; + if (((intptr_t)d->current_pc >= (seg->vmaddr+offset)) && + ((intptr_t)d->current_pc < (seg->vmaddr+offset + seg->vmsize))) { + int check_filename = strcmp(get_base_name( + (char *)_dyld_get_image_name(i)), + get_base_name(source_filename)); + if ( check_filename != 0 ) return; + d->local_pc[d->local_pc_size] = d->current_pc - offset; + d->binary_filename[d->local_pc_size] = (char *)_dyld_get_image_name(i); + // Resolve symlinks to a real path: + char buffer[PATH_MAX]; + char* resolved; + resolved = realpath(d->binary_filename[d->local_pc_size], buffer); + if (resolved) d->binary_filename[d->local_pc_size] = resolved; + d->local_pc_size++; + return; + } + } + if (cmd->cmd == LC_SEGMENT_64) { + struct segment_command_64* seg = (struct segment_command_64*)cmd; + if ((d->current_pc >= (seg->vmaddr + offset)) && + (d->current_pc < (seg->vmaddr + offset + seg->vmsize))) { + int check_filename = strcmp(get_base_name( + (char *)_dyld_get_image_name(i)), + get_base_name(source_filename)); + if ( check_filename != 0 ) return; + d->local_pc[d->local_pc_size] = d->current_pc - offset; + d->binary_filename[d->local_pc_size] = (char *)_dyld_get_image_name(i); + // Resolve symlinks to a real path: + char buffer[PATH_MAX]; + char* resolved; + resolved = realpath(d->binary_filename[d->local_pc_size], buffer); + if (resolved) d->binary_filename[d->local_pc_size] = resolved; + d->local_pc_size++; + return; + } + } + cmd = (struct load_command*)((char*)cmd + cmd->cmdsize); + } + } + printf("The stack address was not found in any shared library or" + " the main program, the stack is probably corrupted.\n" + "Aborting...\n"); + abort(); +} +#endif // HAVE_LFORTRAN_MACHO + +// Fills in `local_pc` and `binary_filename` +void get_local_address(struct Stacktrace *d) { + d->local_pc_size = 0; + for (int32_t i=0; i < d->pc_size; i++) { + d->current_pc = d->pc[i]; +#ifdef HAVE_LFORTRAN_LINK + // Iterates over all loaded shared libraries + // See `stacktrace.cpp` to get more information + if (dl_iterate_phdr(shared_lib_callback, d) == 0) { + printf("The stack address was not found in any shared library or" + " the main program, the stack is probably corrupted.\n" + "Aborting...\n"); + abort(); + } +#else +#ifdef HAVE_LFORTRAN_MACHO + get_local_address_mac(& *d); +#else + d->local_pc[d->local_pc_size] = 0; + d->local_pc_size++; +#endif // HAVE_LFORTRAN_MACHO +#endif // HAVE_LFORTRAN_LINK + } +} + +uint32_t get_file_size(int64_t fp) { + FILE *fp_ = (FILE *)fp; + int prev = ftell(fp_); + fseek(fp_, 0, SEEK_END); + int size = ftell(fp_); + fseek(fp_, prev, SEEK_SET); + return size; +} + +/* + * `lines_dat.txt` file must be created before calling this function, + * The file can be created using the command: + * ./src/bin/dat_convert.py lines.dat + * This function fills in the `addresses` and `line_numbers` + * from the `lines_dat.txt` file. + */ +void get_local_info_dwarfdump(struct Stacktrace *d) { + // TODO: Read the contents of lines.dat from here itself. + char *base_name = get_base_name(source_filename); + char *filename = malloc(strlen(base_name) + 14); + strcpy(filename, base_name); + strcat(filename, "_lines.dat.txt"); + int64_t fd = _lpython_open(filename, "r"); + uint32_t size = get_file_size(fd); + char *file_contents = _lpython_read(fd, size); + _lpython_close(fd); + free(filename); + + char s[LCOMPILERS_MAX_STACKTRACE_LENGTH]; + bool address = true; + uint32_t j = 0; + d->stack_size = 0; + for (uint32_t i = 0; i < size; i++) { + if (file_contents[i] == '\n') { + memset(s, '\0', sizeof(s)); + j = 0; + d->stack_size++; + continue; + } else if (file_contents[i] == ' ') { + s[j] = '\0'; + j = 0; + if (address) { + d->addresses[d->stack_size] = strtol(s, NULL, 10); + address = false; + } else { + d->line_numbers[d->stack_size] = strtol(s, NULL, 10); + address = true; + } + memset(s, '\0', sizeof(s)); + continue; + } + s[j++] = file_contents[i]; + } +} + +char *read_line_from_file(char *filename, uint32_t line_number) { + FILE *fp; + char *line = NULL; + size_t len = 0, n = 0; + + fp = fopen(filename, "r"); + if (fp == NULL) exit(1); + while (n < line_number && (getline(&line, &len, fp) != -1)) n++; + fclose(fp); + + return line; +} + +static inline uint64_t bisection(const uint64_t vec[], + uint64_t size, uint64_t i) { + if (i < vec[0]) return 0; + if (i >= vec[size-1]) return size; + uint64_t i1 = 0, i2 = size-1; + while (i1 < i2-1) { + uint64_t imid = (i1+i2)/2; + if (i < vec[imid]) { + i2 = imid; + } else { + i1 = imid; + } + } + return i1; +} + +char *remove_whitespace(char *str) { + char *end; + // remove leading space + while(isspace((unsigned char)*str)) str++; + if(*str == 0) // All spaces? + return str; + // remove trailing space + end = str + strlen(str) - 1; + while(end > str && isspace((unsigned char)*end)) end--; + // Write new null terminator character + end[1] = '\0'; + return str; +} + +LFORTRAN_API void print_stacktrace_addresses(char *filename, bool use_colors) { + source_filename = filename; + struct Stacktrace d = get_stacktrace_addresses(); + get_local_address(&d); + get_local_info_dwarfdump(&d); + +#ifdef HAVE_LFORTRAN_MACHO + for (int32_t i = d.local_pc_size-2; i >= 0; i--) { +#else + for (int32_t i = d.local_pc_size-3; i >= 0; i--) { +#endif + uint64_t index = bisection(d.addresses, d.stack_size, d.local_pc[i]); + if(use_colors) { + fprintf(stderr, DIM " File " S_RESET + BOLD MAGENTA "\"%s\"" C_RESET S_RESET +#ifdef HAVE_LFORTRAN_MACHO + DIM ", line %lld\n" S_RESET +#else + DIM ", line %ld\n" S_RESET +#endif + " %s\n", source_filename, d.line_numbers[index], + remove_whitespace(read_line_from_file(source_filename, + d.line_numbers[index]))); + } else { + fprintf(stderr, " File \"%s\", " +#ifdef HAVE_LFORTRAN_MACHO + "line %lld\n %s\n", +#else + "line %ld\n %s\n", +#endif + source_filename, d.line_numbers[index], + remove_whitespace(read_line_from_file(source_filename, + d.line_numbers[index]))); + } +#ifdef HAVE_LFORTRAN_MACHO + } +#else + } +#endif +} + +#endif // HAVE_RUNTIME_STACKTRACE +// << Runtime Stacktrace << ---------------------------------------------------- diff --git a/src/libasr/runtime/lfortran_intrinsics.h b/src/libasr/runtime/lfortran_intrinsics.h index b72fcb57cc..f62cf844d6 100644 --- a/src/libasr/runtime/lfortran_intrinsics.h +++ b/src/libasr/runtime/lfortran_intrinsics.h @@ -215,6 +215,7 @@ LFORTRAN_API int32_t _lfortran_iachar(char *c); LFORTRAN_API void _lpython_set_argv(int32_t argc_1, char *argv_1[]); LFORTRAN_API int32_t _lpython_get_argc(); LFORTRAN_API char *_lpython_get_argv(int32_t index); +LFORTRAN_API void print_stacktrace_addresses(char *filename, bool use_colors); #ifdef __cplusplus } diff --git a/src/lpython/python_evaluator.cpp b/src/lpython/python_evaluator.cpp index 2f76f5793d..4652c13f46 100644 --- a/src/lpython/python_evaluator.cpp +++ b/src/lpython/python_evaluator.cpp @@ -56,6 +56,16 @@ Result> PythonCompiler::get_llvm3( = asr_to_llvm(asr, diagnostics, e->get_context(), al, lpm, compiler_options, run_fn, infile); + if (compiler_options.emit_debug_info) { + if (!compiler_options.emit_debug_line_column) { + diagnostics.add(diag::Diagnostic( + "The `emit_debug_line_column` is not enabled; please use the " + "`--debug-with-line-column` option to get the correct " + "location information", + diag::Level::Warning, diag::Stage::Semantic, {}) + ); + } + } if (res.ok) { m = std::move(res.result); } else { diff --git a/src/runtime/legacy/CMakeLists.txt b/src/runtime/legacy/CMakeLists.txt index e57489bb96..cacc62fba9 100644 --- a/src/runtime/legacy/CMakeLists.txt +++ b/src/runtime/legacy/CMakeLists.txt @@ -2,11 +2,15 @@ set(SRC ../../../src/libasr/runtime/lfortran_intrinsics.c ) add_library(lpython_runtime SHARED ${SRC}) +target_include_directories(lpython_runtime BEFORE PUBLIC ${libasr_SOURCE_DIR}/..) +target_include_directories(lpython_runtime BEFORE PUBLIC ${libasr_BINARY_DIR}/..) set_target_properties(lpython_runtime PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../$<0:> LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../$<0:> ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../$<0:>) add_library(lpython_runtime_static STATIC ${SRC}) +target_include_directories(lpython_runtime_static BEFORE PUBLIC ${libasr_SOURCE_DIR}/..) +target_include_directories(lpython_runtime_static BEFORE PUBLIC ${libasr_BINARY_DIR}/..) set_target_properties(lpython_runtime_static PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../$<0:> LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../$<0:> diff --git a/tests/reference/run_dbg-test_assert_01-2f34744.json b/tests/reference/run_dbg-test_assert_01-2f34744.json new file mode 100644 index 0000000000..127eb3e44f --- /dev/null +++ b/tests/reference/run_dbg-test_assert_01-2f34744.json @@ -0,0 +1,13 @@ +{ + "basename": "run_dbg-test_assert_01-2f34744", + "cmd": "lpython {infile} -g --debug-with-line-column --no-color", + "infile": "tests/runtime_errors/test_assert_01.py", + "infile_hash": "7a380295a35651b1a5120e1e64a44cae95d2c14c89cf5a4b6ea3dd1a", + "outfile": null, + "outfile_hash": null, + "stdout": null, + "stdout_hash": null, + "stderr": "run_dbg-test_assert_01-2f34744.stderr", + "stderr_hash": "5ded88da4106fc9a3cfbaad6f82cc820a79a6ef8cc1661ecfcb37924", + "returncode": 1 +} \ No newline at end of file diff --git a/tests/reference/run_dbg-test_assert_01-2f34744.stderr b/tests/reference/run_dbg-test_assert_01-2f34744.stderr new file mode 100644 index 0000000000..c561678d44 --- /dev/null +++ b/tests/reference/run_dbg-test_assert_01-2f34744.stderr @@ -0,0 +1,5 @@ + File "tests/runtime_errors/test_assert_01.py", line 4 + test() + File "tests/runtime_errors/test_assert_01.py", line 2 + assert False +AssertionError diff --git a/tests/reference/run_dbg-test_assert_02-c6de25a.json b/tests/reference/run_dbg-test_assert_02-c6de25a.json new file mode 100644 index 0000000000..25aa716a99 --- /dev/null +++ b/tests/reference/run_dbg-test_assert_02-c6de25a.json @@ -0,0 +1,13 @@ +{ + "basename": "run_dbg-test_assert_02-c6de25a", + "cmd": "lpython {infile} -g --debug-with-line-column --no-color", + "infile": "tests/runtime_errors/test_assert_02.py", + "infile_hash": "85f0e908c3e5d21da83216f95221f59ebd69b477f42718842c1d6c8c", + "outfile": null, + "outfile_hash": null, + "stdout": null, + "stdout_hash": null, + "stderr": "run_dbg-test_assert_02-c6de25a.stderr", + "stderr_hash": "ddba8a92bcfd5a30016735589da0dc56f2785e7636afcc0edeca4139", + "returncode": 1 +} \ No newline at end of file diff --git a/tests/reference/run_dbg-test_assert_02-c6de25a.stderr b/tests/reference/run_dbg-test_assert_02-c6de25a.stderr new file mode 100644 index 0000000000..4fe6972010 --- /dev/null +++ b/tests/reference/run_dbg-test_assert_02-c6de25a.stderr @@ -0,0 +1,5 @@ + File "tests/runtime_errors/test_assert_02.py", line 4 + test() + File "tests/runtime_errors/test_assert_02.py", line 2 + assert 1 != 1, "One is equal to one." +AssertionError: One is equal to one. diff --git a/tests/reference/run_dbg-test_assert_03-bd7b7dd.json b/tests/reference/run_dbg-test_assert_03-bd7b7dd.json new file mode 100644 index 0000000000..c05b63780b --- /dev/null +++ b/tests/reference/run_dbg-test_assert_03-bd7b7dd.json @@ -0,0 +1,13 @@ +{ + "basename": "run_dbg-test_assert_03-bd7b7dd", + "cmd": "lpython {infile} -g --debug-with-line-column --no-color", + "infile": "tests/runtime_errors/test_assert_03.py", + "infile_hash": "8d04aa5eb18e1faa6b0f923f5585bfd8211391b1e423b159d5da4764", + "outfile": null, + "outfile_hash": null, + "stdout": null, + "stdout_hash": null, + "stderr": "run_dbg-test_assert_03-bd7b7dd.stderr", + "stderr_hash": "7f97899439260443b40867e81d7c481c2fc23ec84ee777e7b43984d8", + "returncode": 1 +} \ No newline at end of file diff --git a/tests/reference/run_dbg-test_assert_03-bd7b7dd.stderr b/tests/reference/run_dbg-test_assert_03-bd7b7dd.stderr new file mode 100644 index 0000000000..8ee39fc2e8 --- /dev/null +++ b/tests/reference/run_dbg-test_assert_03-bd7b7dd.stderr @@ -0,0 +1,9 @@ + File "tests/runtime_errors/test_assert_03.py", line 10 + main() + File "tests/runtime_errors/test_assert_03.py", line 8 + f() + File "tests/runtime_errors/test_assert_03.py", line 2 + g() + File "tests/runtime_errors/test_assert_03.py", line 5 + assert False +AssertionError diff --git a/tests/reference/run_dbg-test_quit_01-30889cc.json b/tests/reference/run_dbg-test_quit_01-30889cc.json new file mode 100644 index 0000000000..4e50fcce5f --- /dev/null +++ b/tests/reference/run_dbg-test_quit_01-30889cc.json @@ -0,0 +1,13 @@ +{ + "basename": "run_dbg-test_quit_01-30889cc", + "cmd": "lpython {infile} -g --debug-with-line-column --no-color", + "infile": "tests/runtime_errors/test_quit_01.py", + "infile_hash": "d2c544c21b4973dde53993e0a26bae6fac9581e7d5c87dd2cfc93f07", + "outfile": null, + "outfile_hash": null, + "stdout": null, + "stdout_hash": null, + "stderr": "run_dbg-test_quit_01-30889cc.stderr", + "stderr_hash": "f5a660003a2da017d3ced437825a1e6f1c0c046d73cf68d183c92a40", + "returncode": 10 +} \ No newline at end of file diff --git a/tests/reference/run_dbg-test_quit_01-30889cc.stderr b/tests/reference/run_dbg-test_quit_01-30889cc.stderr new file mode 100644 index 0000000000..c7e212253e --- /dev/null +++ b/tests/reference/run_dbg-test_quit_01-30889cc.stderr @@ -0,0 +1,5 @@ + File "tests/runtime_errors/test_quit_01.py", line 4 + test() + File "tests/runtime_errors/test_quit_01.py", line 2 + quit(10) +STOP diff --git a/tests/reference/run_dbg-test_raise_01-dfd86ca.json b/tests/reference/run_dbg-test_raise_01-dfd86ca.json new file mode 100644 index 0000000000..199a810c97 --- /dev/null +++ b/tests/reference/run_dbg-test_raise_01-dfd86ca.json @@ -0,0 +1,13 @@ +{ + "basename": "run_dbg-test_raise_01-dfd86ca", + "cmd": "lpython {infile} -g --debug-with-line-column --no-color", + "infile": "tests/runtime_errors/test_raise_01.py", + "infile_hash": "359f595e813294fccdacb372c25a2aa6d3827066af7d91db62e32d83", + "outfile": null, + "outfile_hash": null, + "stdout": null, + "stdout_hash": null, + "stderr": "run_dbg-test_raise_01-dfd86ca.stderr", + "stderr_hash": "073aae20bbe7cf78e116825e3e825365b07230972ff7bcb3a5dddb93", + "returncode": 1 +} \ No newline at end of file diff --git a/tests/reference/run_dbg-test_raise_01-dfd86ca.stderr b/tests/reference/run_dbg-test_raise_01-dfd86ca.stderr new file mode 100644 index 0000000000..9c5a4dafd0 --- /dev/null +++ b/tests/reference/run_dbg-test_raise_01-dfd86ca.stderr @@ -0,0 +1,5 @@ + File "tests/runtime_errors/test_raise_01.py", line 4 + test() + File "tests/runtime_errors/test_raise_01.py", line 2 + raise +ERROR STOP diff --git a/tests/runtime_errors/test_assert_03.py b/tests/runtime_errors/test_assert_03.py new file mode 100644 index 0000000000..579bc41455 --- /dev/null +++ b/tests/runtime_errors/test_assert_03.py @@ -0,0 +1,10 @@ +def f(): + g() + +def g(): + assert False + +def main(): + f() + +main() diff --git a/tests/tests.toml b/tests/tests.toml index b57b441807..ad2d2d52fa 100644 --- a/tests/tests.toml +++ b/tests/tests.toml @@ -7,6 +7,7 @@ # tokens ... output Tokens, compare against reference version # run ... compiles and executes the specified file; # checks both runtime errors and successful runs +# run_with_dbg ... runtime checks with debug information enabled # tests @@ -989,16 +990,20 @@ run = true [[test]] filename = "runtime_errors/test_assert_01.py" -run = true +run_with_dbg = true [[test]] filename = "runtime_errors/test_assert_02.py" -run = true +run_with_dbg = true + +[[test]] +filename = "runtime_errors/test_assert_03.py" +run_with_dbg = true [[test]] filename = "runtime_errors/test_quit_01.py" -run = true +run_with_dbg = true [[test]] filename = "runtime_errors/test_raise_01.py" -run = true +run_with_dbg = true