Skip to content

Commit

Permalink
Introduce "jou --valgrind" (#409)
Browse files Browse the repository at this point in the history
  • Loading branch information
Akuli authored Dec 3, 2023
1 parent 06bdf5e commit 3db47b8
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 9 deletions.
11 changes: 11 additions & 0 deletions examples/memory_leak.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import "stdlib/io.jou"
import "stdlib/mem.jou"
import "stdlib/str.jou"

def main() -> int:
foo: byte* = malloc(100)
strcpy(foo, "Hello")
strcpy(&foo[5], "World")
puts(foo) # Output: HelloWorld

return 0
10 changes: 8 additions & 2 deletions runtests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,13 @@ function run_test()
# We don't do this for all files, because I like relative paths in error messages.
command="cd $(dirname $joufile) && $(printf "$command_template" $(basename $joufile))"
else
command="$(printf "$command_template" $joufile)"
# For non-aoc files we can valgrind the compiled Jou executables.
# Aoc solutions can be really slow --> valgrind only the compilation.
if [ $valgrind = yes ] && [ $correct_exit_code == 0 ]; then
command="$(printf "$command_template" "--valgrind $joufile")"
else
command="$(printf "$command_template" $joufile)"
fi
fi

local diffpath
Expand All @@ -160,7 +166,7 @@ function run_test()
# * the "test" is actually a GUI program in examples/
if ( ! [[ "$command_template" =~ -O0 ]] && [[ $joufile =~ ^tests/crash/ ]] ) \
|| ( [[ "$command_template" =~ valgrind ]] && [ $correct_exit_code != 0 ] ) \
|| [ $joufile = examples/x11_window.jou ]
|| [ $joufile = examples/x11_window.jou ] || [ $joufile = examples/memory_leak.jou ]
then
show_skip $joufile
mv $diffpath $diffpath.skip
Expand Down
3 changes: 2 additions & 1 deletion src/jou_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ typedef struct CfInstruction CfInstruction;
extern struct CommandLineArgs {
const char *argv0; // Program name
int verbosity; // How much debug/progress info to print, how many times -v/--verbose passed
bool valgrind; // true --> Use valgrind when runnning user's jou program
bool tokenize_only; // If true, tokenize the file passed on command line and don't actually compile anything
bool parse_only; // If true, parse the file passed on command line and don't actually compile anything
int optlevel; // Optimization level (0 don't optimize, 3 optimize a lot)
Expand Down Expand Up @@ -620,7 +621,7 @@ LLVMModuleRef codegen(const CfGraphFile *cfgfile, const FileTypes *ft);
char *compile_to_object_file(LLVMModuleRef module);
char *get_default_exe_path(void);
void run_linker(const char *const *objpaths, const char *exepath);
int run_exe(const char *exepath);
int run_exe(const char *exepath, bool valgrind);

/*
Use these to clean up return values of compiling functions.
Expand Down
6 changes: 5 additions & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ static const char help_fmt[] =
" -O0/-O1/-O2/-O3 set optimization level (0 = default, 3 = runs fastest)\n"
" -v / --verbose display some progress information\n"
" -vv display a lot of information about all compilation steps\n"
" --valgrind use valgrind when running the code\n"
" --tokenize-only display only the output of the tokenizer, don't do anything else\n"
" --parse-only display only the AST (parse tree), don't do anything else\n"
" --linker-flags appended to the linker command, so you can use external libraries\n"
Expand Down Expand Up @@ -88,6 +89,9 @@ void parse_arguments(int argc, char **argv)
} else if (strncmp(argv[i], "-v", 2) == 0 && strspn(argv[i] + 1, "v") == strlen(argv[i])-1) {
command_line_args.verbosity += strlen(argv[i]) - 1;
i++;
} else if (!strcmp(argv[i], "--valgrind")) {
command_line_args.valgrind = true;
i++;
} else if (!strcmp(argv[i], "--tokenize-only")) {
if (argc > 3) {
fprintf(stderr, "%s: --tokenize-only cannot be used together with other flags", argv[0]);
Expand Down Expand Up @@ -490,7 +494,7 @@ int main(int argc, char **argv)
if (!command_line_args.outfile) {
if(command_line_args.verbosity >= 1)
printf("Run: %s\n", exepath);
ret = run_exe(exepath);
ret = run_exe(exepath, command_line_args.valgrind);
}

free(exepath);
Expand Down
9 changes: 6 additions & 3 deletions src/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,19 @@ char *compile_to_object_file(LLVMModuleRef module)
return path;
}

int run_exe(const char *exepath)
int run_exe(const char *exepath, bool valgrind)
{
char *command = malloc(strlen(exepath) + 50);
char *command = malloc(strlen(exepath) + 1000);
#ifdef _WIN32
sprintf(command, "\"%s\"", exepath);
char *p;
while ((p = strchr(command, '/')))
*p = '\\';
#else
sprintf(command, "'%s'", exepath);
if (valgrind)
sprintf(command, "valgrind -q --leak-check=full --show-leak-kinds=all --error-exitcode=1 '%s'", exepath);
else
sprintf(command, "'%s'", exepath);
#endif

// Make sure that everything else shows up before the user's prints.
Expand Down
13 changes: 11 additions & 2 deletions tests/should_succeed/compiler_cli.jou
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def main() -> int:
# Output: -O0/-O1/-O2/-O3 set optimization level (0 = default, 3 = runs fastest)
# Output: -v / --verbose display some progress information
# Output: -vv display a lot of information about all compilation steps
# Output: --valgrind use valgrind when running the code
# Output: --tokenize-only display only the output of the tokenizer, don't do anything else
# Output: --parse-only display only the AST (parse tree), don't do anything else
# Output: --linker-flags appended to the linker command, so you can use external libraries
Expand Down Expand Up @@ -90,7 +91,15 @@ def main() -> int:
# Compile a GUI program
if not is_windows():
ret = system("./jou -o /dev/null --linker-flags \"-lX11\" examples/x11_window.jou")
if ret != 0:
printf("Compiling failed???\n")
assert ret == 0

# Compile a program with a memory leak.
# Output: 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
if is_windows() or system("which valgrind >/dev/null 2>/dev/null") != 0:
# valgrind not available --> produce some fake output to pass the test
puts("100 bytes in 1 blocks are definitely lost in loss record 1 of 1")
else:
ret = system("./jou --valgrind examples/memory_leak.jou 2>&1 | grep 'definitely lost' | cut -d' ' -f2-")
assert ret == 0

return 0
8 changes: 8 additions & 0 deletions tests/should_succeed/linked_list.jou
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ def prepend(list: Node**, value: byte) -> void:
*new_first = Node{value = value, next = *list}
*list = new_first

def free_list(list: Node*) -> void:
while list != NULL:
next = list->next
free(list)
list = next

def main() -> int:
list: Node* = NULL
prepend(&list, 'o')
Expand All @@ -25,4 +31,6 @@ def main() -> int:
prepend(&list, 'e')
prepend(&list, 'h')
list->print() # Output: hello

free_list(list)
return 0

0 comments on commit 3db47b8

Please sign in to comment.