Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/valgrind.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,5 @@ jobs:
run: make all

- name: Run tests under Valgrind
run: |
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--error-exitcode=1 \
./build/cleaf test.clf
run: make valgrind-test

29 changes: 27 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,33 @@ asan-test:
CFLAGS="-fsanitize=address,undefined -g -O1" make test

valgrind-test:
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test.clf

@echo "Testing memory safety with valgrind..."
@echo "=== Testing normal execution ==="
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/full.clf
@echo "=== Testing lexer errors ==="
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/lexer_error.clf
@echo "=== Testing parser errors ==="
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/parser_missing_semicolon.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/parser_unmatched_brace.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/parser_missing_paren.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/parser_incomplete_function.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/parser_missing_function_name.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/parser_missing_var_name.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/parser_for_missing_increment.clf
@echo "=== Testing semantic errors ==="
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_function_reserved.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_undefined_vars.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_type_mismatch.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_var_redefinition.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_undefined_function.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_function_duplicate.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_function_args_error.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_return_type_error.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_unary_type_error.clf
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/semantic_control_flow_errors.clf
@echo "=== Testing combined errors ==="
valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./build/cleaf test/valgrind_case/combined_multiple_errors.clf
@echo "=== All valgrind tests passed ==="

clean:
rm -rf $(BUILD)
Expand Down
230 changes: 105 additions & 125 deletions src/frontend/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -368,135 +368,96 @@ expression_t* ast_parse_expr_assign(parser_t* p)
return e;
}

expression_t* ast_parse_expr_binary(parser_t* p)
expression_t* ast_parse_expr_binary(parser_t* p, int min_bp)
{
expression_t* e = (expression_t*) malloc(sizeof(expression_t));
if (!e) {
error_report_general(ERROR_SEVERITY_ERROR, "out of memory");
token_t* tok;
expression_t* expr = parse_primary(p);
if (!expr) {
// TODO: add some sort of error_report_stack_trace later on
return NULL;
}
memset(e, 0, sizeof(expression_t));

e->type = EXPRESSION_BINARY;
e->source_pos = peek(p)->source_pos;
while ((tok = peek(p))->type != ';' &&
(tok = peek(p))->type != ')') {
int lbp, rbp;
switch (tok->type) {
case '+': case '-':
lbp = 10; rbp = 11;
break;
case '*': case '/':
lbp = 20; rbp = 21;
break;
case '>':
case '<':
case LEXER_token_gteq:
case LEXER_token_lseq:
lbp = 5; rbp = 6;
break;
case LEXER_token_eq:
case LEXER_token_neq:
lbp = 4;
rbp = 5;
break;
}

expression_t* left = (expression_t*) malloc(sizeof(expression_t));
if (!left) {
error_report_general(ERROR_SEVERITY_ERROR, "out of memory");
free_expression(e);
return NULL;
}
memset(left, 0, sizeof(expression_t));

token_t* left_tok = advance(p);
left->source_pos = left_tok->source_pos;
switch (left_tok->type) {
case LEXER_token_intlit:
left->type = EXPRESSION_INT_LIT;
left->int_lit.value = left_tok->int_value;
break;
case LEXER_token_dqstring:
left->type = EXPRESSION_STRING_LIT;
if (!left_tok->string_value) {
if (p->error_ctx) {
error_report_at_token(p->error_ctx, left_tok, ERROR_SEVERITY_ERROR,
"string literal has no value");
}
free_expression(e);
free_expression(left);
return NULL;
}
left->string_lit.value = strdup(left_tok->string_value);
if (!left->string_lit.value) {
error_report_general(ERROR_SEVERITY_ERROR, "out of memory");
free_expression(e);
free_expression(left);
return NULL;
}
break;
case LEXER_token_id:
left->type = EXPRESSION_VAR;
if (!left_tok->string_value) {
if (p->error_ctx) {
error_report_at_token(p->error_ctx, left_tok, ERROR_SEVERITY_ERROR,
"identifier has no value");
}
free_expression(e);
free_expression(left);
return NULL;
}
left->var.name = strdup(left_tok->string_value);
if (!left->var.name) {
error_report_general(ERROR_SEVERITY_ERROR, "out of memory");
free_expression(e);
free_expression(left);
return NULL;
}
break;
default:
// unexpected
if (p->error_ctx) {
error_report_at_token(p->error_ctx, left_tok, ERROR_SEVERITY_ERROR,
"unexpected token in binary expression");
}
free_expression(e);
free_expression(left);
if (lbp < min_bp) break;

expression_t* e = (expression_t*) calloc(1, sizeof(expression_t));
if (!e) {
error_report_general(ERROR_SEVERITY_ERROR, "out of memory");
free_expression(expr);
return NULL;
}
}

e->binary.left = left;

token_t* op_tok = advance(p);
switch (op_tok->type) {
case '+':
e->binary.op = BINARY_PLUS;
break;
case '-':
e->binary.op = BINARY_MINUS;
break;
case '*':
e->binary.op = BINARY_MUL;
break;
case '/':
e->binary.op = BINARY_DIV;
break;
case '>':
e->binary.op = BINARY_GT;
break;
case LEXER_token_gteq:
e->binary.op = BINARY_GTE;
break;
case '<':
e->binary.op = BINARY_LT;
break;
case LEXER_token_lseq:
e->binary.op = BINARY_LTE;
break;
case LEXER_token_eq:
e->binary.op = BINARY_EQ;
break;
case LEXER_token_neq:
e->binary.op = BINARY_NEQ;
break;
default:
error_report_at_token(p->error_ctx, op_tok, ERROR_SEVERITY_ERROR,
"unexpected binary operation token");
}

e->binary.right = parse_expression(p);
if (!e->binary.right) {
if (p->error_ctx) {
error_report_at_token(p->error_ctx, op_tok, ERROR_SEVERITY_ERROR,
"expected expression after binary operator");
advance(p);

e->type = EXPRESSION_BINARY;
e->source_pos = tok->source_pos;

switch (tok->type) {
case '*':
e->binary.op = BINARY_MUL;
break;
case '/':
e->binary.op = BINARY_DIV;
break;
case '+':
e->binary.op = BINARY_PLUS;
break;
case '-':
e->binary.op = BINARY_MINUS;
break;
case '>':
e->binary.op = BINARY_GT;
break;
case '<':
e->binary.op = BINARY_LT;
break;
case LEXER_token_gteq:
e->binary.op = BINARY_GTE;
break;
case LEXER_token_lseq:
e->binary.op = BINARY_LTE;
break;
case LEXER_token_eq:
e->binary.op = BINARY_EQ;
break;
case LEXER_token_neq:
e->binary.op = BINARY_NEQ;
break;
}
free_expression(e);
return NULL;
}

return e;
expression_t* right = ast_parse_expr_binary(p, rbp);

e->binary.left = expr;
e->binary.right = right;
expr = e;
}

return expr;
}

expression_t* ast_parse_expr_call(parser_t* p)
expression_t* ast_parse_expr_call(parser_t* p)
{
expression_t* e = (expression_t*) malloc(sizeof(expression_t));
if (!e) {
Expand Down Expand Up @@ -651,6 +612,24 @@ expression_t* ast_parse_expr_unary(parser_t* p)
return e;
}

expression_t* parse_primary(parser_t* p)
{
if (check(p, LEXER_token_id) && check_next(p, '(', 1)) {
return ast_parse_expr_call(p);
}

if (check(p, LEXER_token_id))
return ast_parse_expr_var(p);

if (check(p, LEXER_token_dqstring))
return ast_parse_expr_string_lit(p);

if (check(p, LEXER_token_intlit))
return ast_parse_expr_int_lit(p);

return NULL;
}

expression_t* parse_expression(parser_t* p)
{
if (check_next(p, '=', 1))
Expand All @@ -670,11 +649,11 @@ expression_t* parse_expression(parser_t* p)
check_next(p, '/', 1) ||
check_next(p, '<', 1) ||
check_next(p, '>', 1) ||
check_next(p, LEXER_token_eq, 1) ||
check_next(p, LEXER_token_neq, 1) ||
check_next(p, LEXER_token_gteq, 1) ||
check_next(p, LEXER_token_lseq, 1))
return ast_parse_expr_binary(p);
check_next(p, LEXER_token_lseq, 1) ||
check_next(p, LEXER_token_eq, 1) ||
check_next(p, LEXER_token_neq, 1))
return ast_parse_expr_binary(p, 0);

if (check(p, LEXER_token_id) && check_next(p, '(', 1)) {
return ast_parse_expr_call(p);
Expand Down Expand Up @@ -1416,7 +1395,8 @@ statement_t* parse_statement(parser_t* p)
return ast_parse_decl_stmt(p);
}

// for now, we can maybe assume that this is the always wanted fallback
// TODO: keep an eye on this
return ast_parse_expr_stmt(p);
if ((size_t) p->pos < p->count)
return ast_parse_expr_stmt(p);

return NULL;
}
7 changes: 6 additions & 1 deletion src/frontend/ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,24 @@ declaration_t* ast_parse_function(parser_t* p);
declaration_t* ast_parse_var_decl(parser_t* p);
declaration_t* ast_parse_untype_var_decl(parser_t* p);
declaration_t* parse_declaration(parser_t* p);

statement_t* ast_parse_return_stmt(parser_t* p);
statement_t* ast_parse_decl_stmt(parser_t* p);
statement_t* ast_parse_expr_stmt(parser_t* p);
statement_t* ast_parse_if_stmt(parser_t* p);
statement_t* ast_parse_while_stmt(parser_t* p);
statement_t* ast_parse_for_stmt(parser_t* p);
statement_t* parse_statement(parser_t* p);

expression_t* parse_expression(parser_t* p);
expression_t* parse_primary(parser_t* p);
expression_t* ast_parse_expr_int_lit(parser_t* p);
expression_t* ast_parse_expr_string_lit(parser_t* p);
expression_t* ast_parse_expr_var(parser_t* p);
expression_t* ast_parse_expr_assign(parser_t* p);
expression_t* ast_parse_expr_binary(parser_t* p);
expression_t* ast_parse_expr_binary(parser_t* p,
int bp);
expression_t* ast_parse_expr_comparison_binary(parser_t* p);
expression_t* ast_parse_expr_call(parser_t* p);
expression_t* ast_parse_expr_unary(parser_t* p);

Expand Down
Loading