diff --git a/Makefile b/Makefile index a911562..01d6852 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -EMBEDDED_SOURCE = $(wildcard run_time_python/*.py wrapper_c/*.c) +EMBEDDED_SOURCE = $(wildcard run_time_python/*.py wrapper_c/*.c wrapper_c/*.cpp) SOURCE = $(wildcard compile_time_python/*.py) BUILD_DIR=_build diff --git a/README.md b/README.md index c55c2e3..f36bbdb 100644 --- a/README.md +++ b/README.md @@ -260,9 +260,6 @@ Install by creating a symbolic link, e.g.: sudo ln -sf dcc /usr/local/bin/dcc++ ``` -A significant limitation is that running dual-sanitizers is not currently supported for the - `iostream` library which is commonly used in novice programs. - # Run-time Error Handling Implementation diff --git a/compile_time_python/compile.py b/compile_time_python/compile.py index 330cb40..fbefd59 100644 --- a/compile_time_python/compile.py +++ b/compile_time_python/compile.py @@ -37,7 +37,7 @@ def main(): def compile_user_program(options): - wrapper_source, tar_source = get_wrapper_tar_source(options) + wrapper_source, tar_source, wrapper_cpp_source = get_wrapper_code(options) compiler_stdout = "" executable_source = "" @@ -67,8 +67,9 @@ def compile_user_program(options): + sanitizer2_sanitizer_args + ["-o", executable], options, - wrapper_source=sanitizer2_wrapper_source, - debug_wrapper_file="tmp_dcc_sanitizer2.c", + wrapper_C_source=sanitizer2_wrapper_source, + wrapper_cpp_source=wrapper_cpp_source, + debug_C_wrapper_file="tmp_dcc_sanitizer2.c", ) with open(executable, "rb") as f: ( @@ -115,7 +116,8 @@ def compile_user_program(options): + sanitizer_args + ["-o", options.object_pathname], options, - wrapper_source=wrapper_source, + wrapper_C_source=wrapper_source, + wrapper_cpp_source=wrapper_cpp_source, print_stdout=not compiler_stdout, ) @@ -142,8 +144,8 @@ def compile_user_program(options): # customize wrapper source for a particular sanitizer -def update_wrapper_source(sanitizer, sanitizer_n, wrapper_source, tar_source, options): - wrapper_source = wrapper_source.replace("__SANITIZER__", sanitizer.upper()) +def update_wrapper_source(sanitizer, sanitizer_n, src, tar_source, options): + src = src.replace("__SANITIZER__", sanitizer.upper()) if sanitizer == "valgrind": sanitizer_args = [] elif sanitizer == "memory": @@ -153,11 +155,9 @@ def update_wrapper_source(sanitizer, sanitizer_n, wrapper_source, tar_source, op # if sanitizer != "memory" and not (sanitizer_n == 2 and sanitizer == "valgrind"): if sanitizer != "memory" and not (sanitizer_n == 2 and sanitizer == "valgrind"): - # FIXME if we enable '-fsanitize=undefined', '-fno-sanitize-recover=undefined,integer' for memory + # FIXME if we enable '-fsanitize=undefined', '-fno-sanitize-recover=undefined,integer' for memory # which would be preferable here we get uninitialized variable error message for undefined errors - wrapper_source = wrapper_source.replace( - "__UNDEFINED_BEHAVIOUR_SANITIZER_IN_USE__", "1" - ) + src = src.replace("__UNDEFINED_BEHAVIOUR_SANITIZER_IN_USE__", "1") sanitizer_args += ["-fsanitize=undefined"] # These options stop error explanations if __ubsan_on_report can not be intercepted (on Ubuntu) @@ -172,59 +172,64 @@ def update_wrapper_source(sanitizer, sanitizer_n, wrapper_source, tar_source, op if os.path.exists(lib_dir): sanitizer_args += ["-shared-libasan", "-Wl,-rpath," + lib_dir] - wrapper_source = wrapper_source.replace( - "__LEAK_CHECK_YES_NO__", "yes" if options.leak_check else "no" - ) + src = src.replace("__LEAK_CHECK_YES_NO__", "yes" if options.leak_check else "no") leak_check = options.leak_check if leak_check and options.sanitizers[1:] == ["valgrind"]: # do leak checking in valgrind (only) for (currently) better messages leak_check = False - wrapper_source = wrapper_source.replace( - "__LEAK_CHECK_1_0__", "1" if leak_check else "0" - ) - wrapper_source = wrapper_source.replace( - "__USE_FUNOPEN__", "1" if options.use_funopen else "0" - ) + src = src.replace("__LEAK_CHECK_1_0__", "1" if leak_check else "0") + src = src.replace("__USE_FUNOPEN__", "1" if options.use_funopen else "0") - wrapper_source = wrapper_source.replace( - "__I_AM_SANITIZER1__", "1" if sanitizer_n == 1 else "0" - ) - wrapper_source = wrapper_source.replace( - "__I_AM_SANITIZER2__", "1" if sanitizer_n == 2 else "0" - ) - wrapper_source = wrapper_source.replace( + src = src.replace("__I_AM_SANITIZER1__", "1" if sanitizer_n == 1 else "0") + src = src.replace("__I_AM_SANITIZER2__", "1" if sanitizer_n == 2 else "0") + src = src.replace( "__WHICH_SANITIZER__", "sanitizer2" if sanitizer_n == 2 else "sanitizer1" ) - wrapper_source = tar_source + wrapper_source - return wrapper_source, sanitizer_args + src = tar_source + src + return src, sanitizer_args def execute_compiler( compiler, dcc_supplied_arguments, options, - wrapper_source="", - debug_wrapper_file="tmp_dcc_sanitizer1.c", + wrapper_C_source="", + debug_C_wrapper_file="tmp_dcc_sanitizer1.c", rename_functions=True, print_stdout=True, checking_only=False, + wrapper_cpp_source="", + debug_cpp_wrapper_file="tmp_dcc_sanitizer1.cpp", ): - extra_arguments, extra_arguments_debug = compile_wrapper_source( - wrapper_source, options, rename_functions, debug_wrapper_file + extra_c_arguments, extra_c_arguments_debug = compile_wrapper_source( + wrapper_C_source, + options, + debug_C_wrapper_file, + cpp=False, + rename_functions=rename_functions, + ) + extra_cpp_arguments, extra_cpp_arguments_debug = compile_wrapper_source( + wrapper_cpp_source, + options, + debug_cpp_wrapper_file, + cpp=True, + rename_functions=rename_functions, ) command = ( [compiler] + dcc_supplied_arguments - + extra_arguments + + extra_c_arguments + + extra_cpp_arguments + options.user_supplied_compiler_args ) if options.debug > 1: debug_command = ( [compiler] + dcc_supplied_arguments - + extra_arguments_debug + + extra_c_arguments_debug + + extra_cpp_arguments_debug + options.user_supplied_compiler_args ) append_debug_compile(debug_command) @@ -282,9 +287,11 @@ def execute_compiler( dcc_supplied_arguments, options, rename_functions=False, - wrapper_source=wrapper_source, + wrapper_C_source=wrapper_C_source, print_stdout=print_stdout, - debug_wrapper_file=debug_wrapper_file, + debug_C_wrapper_file=debug_C_wrapper_file, + wrapper_cpp_source=wrapper_cpp_source, + debug_cpp_wrapper_file=debug_cpp_wrapper_file, ) if stdout and print_stdout: @@ -300,15 +307,20 @@ def execute_compiler( def compile_wrapper_source( - source, options, rename_functions=True, debug_wrapper_file="tmp_dcc_sanitizer1.c" + source, options, debug_wrapper_file, cpp=False, rename_functions=True ): if not source: return [], [] rename_arguments, source = get_rename_arguments(source, options, rename_functions) - relocatable_filename = os.path.join( - options.temporary_directory, "dcc_wrapper_source.o" + relocatable_basename = ( + "dcc_cpp_wrapper_source.o" if cpp else "dcc_c_wrapper_source.o" + ) + relocatable_pathname = os.path.join( + options.temporary_directory, relocatable_basename ) - compiler = options.c_compiler.replace("clang++", "clang").replace("++", "cc") + compiler = options.c_compiler + if not cpp: + compiler = options.c_compiler.replace("clang++", "clang").replace("++", "cc") if options.debug > 1: try: options.debug_print("Leaving dcc code in", debug_wrapper_file) @@ -321,23 +333,23 @@ def compile_wrapper_source( "-c", debug_wrapper_file, "-o", - "dcc_wrapper_source.o", + relocatable_basename, ] + WRAPPER_SOURCE_COMPILER_ARGS append_debug_compile(debug_command) command = [ compiler, "-c", "-x", - "c", + "c++" if cpp else "c", "-", "-o", - relocatable_filename, + relocatable_pathname, ] + WRAPPER_SOURCE_COMPILER_ARGS process = run(command, options, input=source) if process.stdout or process.returncode != 0: options.die("Internal error\n" + process.stdout) - return rename_arguments + [relocatable_filename], rename_arguments + [ - "dcc_wrapper_source.o", + return rename_arguments + [relocatable_pathname], rename_arguments + [ + relocatable_basename ] @@ -401,7 +413,17 @@ def append_debug_compile(command): print(e, file=sys.stderr) -def get_wrapper_tar_source(options): +def get_wrapper_cpp_code(options): + wrapper_source = "".join( + pkgutil.get_data("embedded_src", f).decode("utf8") + for f in [ + "dcc_io.c", + ] + ) + return add_constants_to_source_code(wrapper_source, options) + + +def get_wrapper_code(options): wrapper_source = "".join( pkgutil.get_data("embedded_src", f).decode("utf8") for f in [ @@ -411,41 +433,45 @@ def get_wrapper_tar_source(options): "dcc_check_output.c", ] ) - - wrapper_source = wrapper_source.replace("__PATH__", options.dcc_path) - wrapper_source = wrapper_source.replace("__DCC_VERSION__", '"' + VERSION + '"') - wrapper_source = wrapper_source.replace("__HOSTNAME__", '"' + platform.node() + '"') - wrapper_source = wrapper_source.replace( - "__CLANG_VERSION__", f'"{options.clang_version}"' - ) - wrapper_source = wrapper_source.replace( - "__SUPRESSIONS_FILE__", options.suppressions_file + wrapper_source = add_constants_to_source_code(wrapper_source, options) + wrapper_source, tar_source = add_embedded_tarfile_handling_to_source_code( + wrapper_source, options ) - wrapper_source = wrapper_source.replace( + wrapper_cpp_source = "" + if options.cpp_mode: + wrapper_cpp_source = "".join( + pkgutil.get_data("embedded_src", f).decode("utf8") + for f in [ + "dcc_io.cpp", + ] + ) + return wrapper_source, tar_source, wrapper_cpp_source + + +def add_constants_to_source_code(src, options): + src = src.replace("__PATH__", options.dcc_path) + src = src.replace("__DCC_VERSION__", '"' + VERSION + '"') + src = src.replace("__HOSTNAME__", '"' + platform.node() + '"') + src = src.replace("__CLANG_VERSION__", f'"{options.clang_version}"') + src = src.replace("__SUPRESSIONS_FILE__", options.suppressions_file) + src = src.replace( "__STACK_USE_AFTER_RETURN__", "1" if options.stack_use_after_return else "0" ) - wrapper_source = wrapper_source.replace( - "__CHECK_OUTPUT__", "1" if options.check_output else "0" - ) - wrapper_source = wrapper_source.replace( + src = src.replace("__CHECK_OUTPUT__", "1" if options.check_output else "0") + src = src.replace("__CPP_MODE__", "1" if options.cpp_mode else "0") + src = src.replace( "__WRAP_POSIX_SPAWN__", "1" if options.valgrind_fix_posix_spawn else "0" ) - wrapper_source = wrapper_source.replace( - "__CLANG_VERSION_MAJOR__", str(options.clang_version_major) - ) - wrapper_source = wrapper_source.replace( - "__CLANG_VERSION_MINOR__", str(options.clang_version_minor) - ) - wrapper_source = wrapper_source.replace( - "__N_SANITIZERS__", str(len(options.sanitizers)) - ) - wrapper_source = wrapper_source.replace("__DEBUG__", "1" if options.debug else "0") - + src = src.replace("__CLANG_VERSION_MAJOR__", str(options.clang_version_major)) + src = src.replace("__CLANG_VERSION_MINOR__", str(options.clang_version_minor)) + src = src.replace("__N_SANITIZERS__", str(len(options.sanitizers))) + src = src.replace("__DEBUG__", "1" if options.debug else "0") if len(options.sanitizers) > 1: - wrapper_source = wrapper_source.replace( - "__SANITIZER_2__", options.sanitizers[1].upper() - ) + src = src.replace("__SANITIZER_2__", options.sanitizers[1].upper()) + return src + +def add_embedded_tarfile_handling_to_source_code(src, options): tar_n_bytes, tar_source = source_for_embedded_tarfile(options) watcher = rf"python3 -E -c \"import io,os,sys,tarfile,tempfile\n\ with tempfile.TemporaryDirectory() as temp_dir:\n\ @@ -455,10 +481,8 @@ def get_wrapper_tar_source(options): os.chdir(temp_dir)\n\ exec(open('watch_valgrind.py').read())\n\ \"" - - wrapper_source = wrapper_source.replace("__MONITOR_VALGRIND__", watcher) - - return wrapper_source, tar_source + src = src.replace("__MONITOR_VALGRIND__", watcher) + return src, tar_source def run( diff --git a/compile_time_python/options.py b/compile_time_python/options.py index 0f9d24b..ca37a5a 100644 --- a/compile_time_python/options.py +++ b/compile_time_python/options.py @@ -1,7 +1,9 @@ import io, os, platform, re, subprocess, sys, tarfile from version import VERSION -# on some platforms -Wno-unused-result is needed to avoid warnings about scanf's return value being ignored - + +# on some platforms -Wno-unused-result is needed +# to avoid warnings about scanf's return value being ignored and # novice programmers will often be told to ignore scanf's return value # when writing their first programs @@ -27,17 +29,20 @@ """.split() -# gcc flags some novice programmer mistakes than clang doesn't so -# we run it has an extra checking pass with several extra warnings enabled -# -Wduplicated-branches was only added with gcc-8 so it will break older versions of gcc -# this will be silent but we lose gcc checking - we could fix by checking gcc version +# gcc detects some typical novice programmer mistakes that clang doesn't +# We run gcc has an extra checking pass with several warnings options enabled +# +# The option -Wduplicated-branches was only added with gcc-8 +# it will break older versions of gcc +# this will be silent but we lose gcc checking # -# -Wnull-dererefence would be useful here but when it flags potential paths -# the errors look confusing for novice programmers ,and there appears no way to get only definite null-derefs +# The option -Wnull-dererefence looks be useful but when it flags potential paths +# the errors look confusing for novice programmers, +# and there appears no way to get only definite null-derefs # # -O is needed with gcc to get warnings for some things -GCC_ONLY_ARGS = "-Wunused-but-set-variable -Wduplicated-cond -Wduplicated-branches -Wlogical-op -O -o /dev/null".split() +GCC_ONLY_ARGS = "-Wunused-but-set-variable -Wduplicated-cond -Wduplicated-branches -Wlogical-op -O -o /dev/null".split() class Options: @@ -109,6 +114,7 @@ def __init__(self): ] safe_c_includes = [i + ".h" for i in safe_c_includes_basenames] safe_cpp_includes = ["c" + i for i in safe_c_includes_basenames if "/" not in i] + safe_cpp_includes += ["iostream"] self.dual_sanitizer_safe_system_includes = set( safe_c_includes + safe_cpp_includes ) diff --git a/tests/do_tests.sh b/tests/do_tests.sh index 20a6c1b..6eef7d7 100755 --- a/tests/do_tests.sh +++ b/tests/do_tests.sh @@ -37,7 +37,9 @@ REMOVE_NON_DETERMINATE_VALUES=' ' export dcc="${1:-./dcc}" +export dcc_cpp="${dcc}++" c_compiler="${2:-clang}" +cpp_compiler="${3:-clang++}" mkdir -p extracted_compile_time_errors cd extracted_compile_time_errors || exit @@ -71,6 +73,16 @@ do test ! -s tmp.actual_stderr && DCC_DEBUG=1 ./a.out >tmp.actual_stderr >tmp.actual_stdout ;; + *.cpp) + dcc_flags= + suffix=`echo $compile_options|sed 's/^dcc_flags=//;s/ /_/g;s/["$]//g;s/src_file//;s?/?_?g'` + eval $compile_options + expected_output_basename="`basename $src_file`$suffix" + #echo "$dcc" --c-compiler=$cpp_compiler $dcc_flags "$src_file" + "$dcc_cpp" --c-compiler=$cpp_compiler $dcc_flags "$src_file" 2>tmp.actual_stderr >/dev/null + test ! -s tmp.actual_stderr && DCC_DEBUG=1 ./a.out >tmp.actual_stderr >tmp.actual_stdout + ;; + *.sh) expected_output_basename="`basename $src_file .sh`" $src_file tmp.actual_stderr >/dev/null diff --git a/tests/expected_output/illegal_2d_array_access.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/illegal_2d_array_access.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..fbf0f7a --- /dev/null +++ b/tests/expected_output/illegal_2d_array_access.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,21 @@ +================================================================= + +tests/run_time_errors/illegal_2d_array_access.cpp:4 runtime error - stack buffer overflow + +dcc explanation: access past the end of a local variable. + Make sure the size of your array is correct. + Make sure your array indices are correct. + +Execution stopped in twod(b=0x7ffdf5d821a0) in tests/run_time_errors/illegal_2d_array_access.cpp at line 4: + +void twod(int b[5][4]) { +--> std::cout << b[5][2] << "\n"; +} + +Values when execution stopped: + +b[5][2] = + +Function Call Traceback +twod(b=0x7ffdf5d821a0) called at line 9 of tests/run_time_errors/illegal_2d_array_access.cpp +main() diff --git a/tests/expected_output/illegal_argv_index.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/illegal_argv_index.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..6344ef8 --- /dev/null +++ b/tests/expected_output/illegal_argv_index.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,12 @@ + +Execution terminated by signal 11 +Execution stopped in main() in tests/run_time_errors/illegal_argv_index.cpp at line 4: + +int main(int argc, char **argv) { +--> std::cout << argv[-1] << "\n"; +} + +Values when execution stopped: + +argc = 1 +argv[-1] = 0x1 diff --git a/tests/expected_output/leak_then_exit--leak-check/000001-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/leak_then_exit--leak-check/000001-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..f6c1e03 --- /dev/null +++ b/tests/expected_output/leak_then_exit--leak-check/000001-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,2 @@ + +Error: free not called for memory allocated with malloc in function main in tests/run_time_errors/leak_then_exit.c at line 6. diff --git a/tests/expected_output/leak_valgrind--leak-check/000001-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/leak_valgrind--leak-check/000001-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..a0ab63d --- /dev/null +++ b/tests/expected_output/leak_valgrind--leak-check/000001-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,2 @@ + +Error: free not called for memory allocated with malloc in function main in tests/run_time_errors/leak_valgrind.c at line 6. diff --git a/tests/expected_output/mod_zero.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/mod_zero.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..f4df72e --- /dev/null +++ b/tests/expected_output/mod_zero.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,14 @@ + +tests/run_time_errors/mod_zero.cpp:4:18: runtime error - division by zero + +dcc explanation: A common error is to evaluate x % y when y == 0 which is undefined. + +Execution stopped in main() in tests/run_time_errors/mod_zero.cpp at line 4: + +int main(int argc, char **argv) { +--> std::cout << 42 % (argc - 1) << "\n"; +} + +Values when execution stopped: + +argc = 1 diff --git a/tests/expected_output/uninitialized-array-element-printf.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/uninitialized-array-element-printf.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..d77c523 --- /dev/null +++ b/tests/expected_output/uninitialized-array-element-printf.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,17 @@ + +Runtime error: uninitialized variable accessed. + +Execution stopped in main() in tests/run_time_errors/uninitialized-array-element-printf.cpp at line 6: + +int main(int argc, char **argv) { + int a[1000]; + a[42] = 42; +--> std::cout << a[argc] << "\n"; +} + +Values when execution stopped: + +a = { , 42, } +argc = 1 +a[42] = 42 +a[argc] = diff --git a/tests/expected_output/uninitialized-char-array-large.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/uninitialized-char-array-large.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..3ea6cfd --- /dev/null +++ b/tests/expected_output/uninitialized-char-array-large.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,17 @@ + +Runtime error: uninitialized variable accessed. + +Execution stopped in main() in tests/run_time_errors/uninitialized-char-array-large.cpp at line 7: + +int main(int argc, char *argv[]) { + char input[8192]; + input[argc] = 0; +--> std::cout << input[0]; +} + +Values when execution stopped: + +argc = 1 +input = "\276\000", <8190 uninitialized values> +input[0] = +input[argc] = 0 = '\0' diff --git a/tests/expected_output/uninitialized-char-array.cpp-fsanitize=valgrind/000000-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/uninitialized-char-array.cpp-fsanitize=valgrind/000000-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..d547953 --- /dev/null +++ b/tests/expected_output/uninitialized-char-array.cpp-fsanitize=valgrind/000000-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,17 @@ + +Runtime error: uninitialized variable accessed. + +Execution stopped in main() in tests/run_time_errors/uninitialized-char-array.cpp at line 8: + +int main(int argc, char **argv) { + char input[2][10]; + input[argc][argc] = 0; +--> std::cout << input[0]; +} + +Values when execution stopped: + +argc = 1 +input = {<10 uninitialized values>, "\276\000\276\276\276\276\276\276\276\276"} +input[0] = <10 uninitialized values> +input[argc][argc] = 0 = '\0' diff --git a/tests/expected_output/unitialized_exit_status.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/unitialized_exit_status.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..a417c37 --- /dev/null +++ b/tests/expected_output/unitialized_exit_status.cpp/000000-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,5 @@ + +Runtime error: exit value is uninitialized + +Main is returning an uninitialized value or exit has been passed an uninitialized value. + diff --git a/tests/expected_output/use_after_free/000001-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/use_after_free/000001-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..f89bb62 --- /dev/null +++ b/tests/expected_output/use_after_free/000001-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,20 @@ +================================================================= + +tests/run_time_errors/use_after_free.c:9 runtime error - malloc use after free + +dcc explanation: access to memory that has already been freed. + +Execution stopped in main() in tests/run_time_errors/use_after_free.c at line 9: + +int main(int argc, char *argv[]) { + int *p = (int *)malloc(sizeof(int *)); + *p = 1; + if (argc > 0) { + free(p); + } +--> return *p; +} + +Values when execution stopped: + +argc = 1 diff --git a/tests/expected_output/use_after_return--use-after-return/000001-clang-14.0-x86_64-pc-linux-gnu.txt b/tests/expected_output/use_after_return--use-after-return/000001-clang-14.0-x86_64-pc-linux-gnu.txt new file mode 100644 index 0000000..b4e6eab --- /dev/null +++ b/tests/expected_output/use_after_return--use-after-return/000001-clang-14.0-x86_64-pc-linux-gnu.txt @@ -0,0 +1,13 @@ +================================================================= + +tests/run_time_errors/use_after_return.c:26 runtime error - stack use after return + +dcc explanation: You have used a pointer to a local variable that no longer exists. + When a function returns its local variables are destroyed. + +For more information see: https://comp1511unsw.github.io/dcc/stack_use_after_return.html +Execution stopped in main() in tests/run_time_errors/use_after_return.c at line 26: + +int main(void) { +--> printf("%d\n", *f(50)); +} diff --git a/tests/run_time_errors/illegal_2d_array_access.cpp b/tests/run_time_errors/illegal_2d_array_access.cpp new file mode 100644 index 0000000..00c2913 --- /dev/null +++ b/tests/run_time_errors/illegal_2d_array_access.cpp @@ -0,0 +1,11 @@ +#include + +void twod(int b[5][4]) { + std::cout << b[5][2] << "\n"; +} + +int main(int argc, char **argv) { + int a[5][4] = {{0}}; + twod(a); + return 0; +} diff --git a/tests/run_time_errors/illegal_argv_index.cpp b/tests/run_time_errors/illegal_argv_index.cpp new file mode 100644 index 0000000..23bf99b --- /dev/null +++ b/tests/run_time_errors/illegal_argv_index.cpp @@ -0,0 +1,5 @@ +#include + +int main(int argc, char **argv) { + std::cout << argv[-1] << "\n"; +} diff --git a/tests/run_time_errors/mod_zero.cpp b/tests/run_time_errors/mod_zero.cpp new file mode 100644 index 0000000..4cd29b8 --- /dev/null +++ b/tests/run_time_errors/mod_zero.cpp @@ -0,0 +1,5 @@ +#include + +int main(int argc, char **argv) { + std::cout << 42 % (argc - 1) << "\n"; +} diff --git a/tests/run_time_errors/uninitialized-array-element-printf.cpp b/tests/run_time_errors/uninitialized-array-element-printf.cpp new file mode 100644 index 0000000..6c103eb --- /dev/null +++ b/tests/run_time_errors/uninitialized-array-element-printf.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char **argv) { + int a[1000]; + a[42] = 42; + std::cout << a[argc] << "\n"; +} diff --git a/tests/run_time_errors/uninitialized-char-array-large.cpp b/tests/run_time_errors/uninitialized-char-array-large.cpp new file mode 100644 index 0000000..893b37b --- /dev/null +++ b/tests/run_time_errors/uninitialized-char-array-large.cpp @@ -0,0 +1,8 @@ +// --memory does not detect this +#include + +int main(int argc, char *argv[]) { + char input[8192]; + input[argc] = 0; + std::cout << input[0]; +} diff --git a/tests/run_time_errors/uninitialized-char-array.cpp b/tests/run_time_errors/uninitialized-char-array.cpp new file mode 100644 index 0000000..0d6eade --- /dev/null +++ b/tests/run_time_errors/uninitialized-char-array.cpp @@ -0,0 +1,9 @@ +//dcc_flags=-fsanitize=valgrind +// --memory does not detect this +#include + +int main(int argc, char **argv) { + char input[2][10]; + input[argc][argc] = 0; + std::cout << input[0]; +} diff --git a/tests/run_time_errors/unitialized_exit_status.cpp b/tests/run_time_errors/unitialized_exit_status.cpp new file mode 100644 index 0000000..d85dbd8 --- /dev/null +++ b/tests/run_time_errors/unitialized_exit_status.cpp @@ -0,0 +1,8 @@ +#include + +int main(int argc, char *argv[]) { + int a[2]; + a[0] = 0; + std::cout << "Hello, it is good to C you!\n"; + return a[argc]; +} diff --git a/tests/run_time_errors/use_after_free.c b/tests/run_time_errors/use_after_free.c index 86a308e..1b7f1d6 100644 --- a/tests/run_time_errors/use_after_free.c +++ b/tests/run_time_errors/use_after_free.c @@ -1,8 +1,10 @@ #include -int main(void) { - int *p = (int *)malloc(sizeof (int *)); +int main(int argc, char *argv[]) { + int *p = (int *)malloc(sizeof(int *)); *p = 1; - free(p); + if (argc > 0) { + free(p); + } return *p; } diff --git a/tests/run_time_errors/use_after_return.c b/tests/run_time_errors/use_after_return.c index 91cffe3..291cfdd 100644 --- a/tests/run_time_errors/use_after_return.c +++ b/tests/run_time_errors/use_after_return.c @@ -15,6 +15,9 @@ int * f(int num) { } factors[0] = count; int *factorPointer = factors; + if (num == 0) { + factorPointer = NULL; + } return factorPointer; } diff --git a/wrapper_c/dcc_dual_sanitizers.c b/wrapper_c/dcc_dual_sanitizers.c index 79295d3..63e4506 100644 --- a/wrapper_c/dcc_dual_sanitizers.c +++ b/wrapper_c/dcc_dual_sanitizers.c @@ -46,7 +46,6 @@ static FILE *get_cookie(FILE *f, const char *mode) { #endif static int init_check_output(void); - static void init_cookies(void) { setbuf(stderr, NULL); debug_stream = stderr; @@ -60,9 +59,23 @@ static void init_cookies(void) { // this should be workable setlinebuf(stdin); setlinebuf(stdout); +#if __CPP_MODE__ + extern void __dcc_replace_cin(FILE *stream); + extern void __dcc_replace_cout(FILE *stream); + extern void __dcc_replace_cerr(FILE *stream); + __dcc_replace_cin(stdin); + __dcc_replace_cout(stdout); + __dcc_replace_cerr(stderr); +#endif + #else if (init_check_output()) { stdout = get_cookie(stdout, "w"); +#if __CPP_MODE__ + extern void __dcc_replace_cout(FILE *stream); + __dcc_replace_cout(stdout); +#endif + } #endif } @@ -244,6 +257,15 @@ static void __dcc_cleanup_before_exit(void) __attribute__((destructor)); static void __dcc_cleanup_before_exit(void) { debug_printf(3, "__dcc_cleanup_before_exit\n"); __dcc_check_output_exit(); +#if __CPP_MODE__ + extern void __dcc_restore_cin(void); + extern void __dcc_restore_cout(void); + extern void __dcc_restore_cerr(void); + __dcc_restore_cin(); + __dcc_restore_cout(); + __dcc_restore_cerr(); +#endif + disconnect_sanitizers(); #if __I_AM_SANITIZER1__ wait_for_sanitizer2_to_terminate(); diff --git a/wrapper_c/dcc_io.cpp b/wrapper_c/dcc_io.cpp new file mode 100644 index 0000000..f456d75 --- /dev/null +++ b/wrapper_c/dcc_io.cpp @@ -0,0 +1,106 @@ +#include +#include + +#define STDIOBUF_BUFFER_SIZE 8192 +#define STDIOBUF_PUSHBACK_MAX 4 + +// redirect cin, cout, cerr to stdin, stdout, stderr +// to allow dcc to synchronize across + +// similar code at http://ilab.usc.edu/rjpeters/groovx/stdiobuf_8cc-source.html +// documentation at https://cplusplus.com/reference/streambuf/streambuf/ + +class stdiobuf: public std::streambuf { + FILE *stdio_stream; + char buffer[STDIOBUF_PUSHBACK_MAX + STDIOBUF_BUFFER_SIZE] = {0}; +public: + stdiobuf(FILE *f) { + stdio_stream = f; + char *b = buffer + STDIOBUF_PUSHBACK_MAX; + setp(b, b); + setg(buffer, b, b); + } + + + // input methods + int underflow() { + if (gptr() == egptr()) { + char *b = buffer + STDIOBUF_PUSHBACK_MAX; + // just get one character to avoid introducing inappropriate buffering + // bufferring will still be happening in stdio + int c = fgetc(stdio_stream); + if (c != EOF) { + b[0] = c; + } + setg(buffer, b, b + 1); + } + return gptr() == egptr() ? traits_type::eof() : traits_type::to_int_type(*gptr()); + } + + // do we need to implement pbackfail? + + + // output methods + int overflow(int c) { + if (c != EOF) { + *pptr() = c; + pbump(1); + } + return flush_buffer() == EOF ? EOF : traits_type::to_int_type(c); + } + + int sync() { + return flush_buffer() == EOF ? -1 : 0; + } + + // helper function + int flush_buffer() { + const size_t num = pptr() - pbase(); + if (fwrite(pbase(), 1, num, stdio_stream) != num) { + return EOF; + } + pbump(-num); + return num; + } +}; + + +static std::streambuf *original_cin_streambuf; + +extern "C" void __dcc_replace_cin(FILE *stream) { + original_cin_streambuf = std::cin.rdbuf(new stdiobuf(stream)); +} + +extern "C" void __dcc_restore_cin(void) { + if (original_cin_streambuf) { + delete std::cin.rdbuf(original_cin_streambuf); + } +} + + +static std::streambuf *original_cout_streambuf; + +extern "C" void __dcc_replace_cout(FILE *stream) { + original_cout_streambuf = std::cout.rdbuf(new stdiobuf(stream)); +} + +extern "C" void __dcc_restore_cout() { + if (original_cout_streambuf) { + std::cout << std::flush; + delete std::cout.rdbuf(original_cout_streambuf); + } +} + + +static std::streambuf *original_cerr_streambuf; + +extern "C" void __dcc_replace_cerr(FILE *stream) { + original_cerr_streambuf = std::cerr.rdbuf(new stdiobuf(stream)); +} + +extern "C" void __dcc_restore_cerr() { + if (original_cerr_streambuf) { + std::cerr << std::flush; + delete std::cerr.rdbuf(original_cerr_streambuf); + } +} diff --git a/wrapper_c/dcc_main.c b/wrapper_c/dcc_main.c index 7a08c2f..efdf578 100644 --- a/wrapper_c/dcc_main.c +++ b/wrapper_c/dcc_main.c @@ -56,7 +56,8 @@ static FILE *debug_stream = NULL; int __wrap_main(int argc, char *argv[], char *envp[]) NO_SANITIZE; int __real_main(int argc, char *argv[], char *envp[]); -static void __dcc_start(void) __attribute__((constructor)) NO_SANITIZE; +//static void __dcc_start(void) __attribute__((constructor)) NO_SANITIZE; +static void __dcc_start(void) NO_SANITIZE; void __dcc_error_exit(void) NO_SANITIZE; static void __dcc_signal_handler(int signum) NO_SANITIZE; static void set_signals_default(void) NO_SANITIZE; @@ -87,6 +88,7 @@ static void init_cookies(void); #if __N_SANITIZERS__ == 1 int __wrap_main(int argc, char *argv[], char *envp[]) { + __dcc_start(); (void)envp; // avoid unused parameter warning debug_stream = stderr; char *mypath = realpath(argv[0], NULL); @@ -104,6 +106,7 @@ static int from_sanitizer2_pipe[2]; #if __I_AM_SANITIZER2__ int __wrap_main(int argc, char *argv[], char *envp[]) { + __dcc_start(); (void)envp; // avoid unused parameter warning debug_stream = stderr; to_sanitizer2_pipe[0] = atoi(getenv("DCC_PIPE_TO_CHILD")); @@ -124,6 +127,7 @@ static void __dcc_main_sanitizer1(int argc, char *argv[]) NO_SANITIZE; static void __dcc_main_sanitizer2(int argc, char *argv[], const char *sanitizer2_executable_pathname) NO_SANITIZE; int __wrap_main(int argc, char *argv[], char *envp[]) { + __dcc_start(); (void)envp; // avoid unused parameter warning extern char **environ; char *mypath = realpath(argv[0], NULL);