diff options
| author | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-25 11:53:53 +0000 |
|---|---|---|
| committer | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-25 11:53:53 +0000 |
| commit | 2dc5214fd8bb6a1168e2f2b643a36043c36c908a (patch) | |
| tree | 55780a330598d3606205b662584a839ea60d0315 | |
| parent | 6a45f6a640dc8f7b5f9819d22d68cd79fbe3c260 (diff) | |
Fix for #121
| -rw-r--r-- | src/codegen/codegen.c | 30 | ||||
| -rw-r--r-- | src/codegen/codegen.h | 3 | ||||
| -rw-r--r-- | src/codegen/codegen_stmt.c | 15 | ||||
| -rw-r--r-- | src/codegen/codegen_utils.c | 81 | ||||
| -rw-r--r-- | src/parser/parser_expr.c | 11 | ||||
| -rw-r--r-- | tests/memory/test_copy_trait.zc (renamed from tests/features/test_copy_trait.zc) | 0 | ||||
| -rw-r--r-- | tests/memory/test_drop.zc (renamed from tests/features/test_drop.zc) | 0 | ||||
| -rw-r--r-- | tests/memory/test_move_double_free.zc | 87 | ||||
| -rw-r--r-- | tests/memory/test_move_semantics.zc (renamed from tests/features/test_move_semantics.zc) | 0 | ||||
| -rw-r--r-- | tests/memory/test_resources.zc (renamed from tests/features/test_resources.zc) | 0 |
10 files changed, 209 insertions, 18 deletions
diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index b5aecfa..8f2b6c1 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -289,9 +289,23 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) else { fprintf(out, "("); - codegen_expression(ctx, node->binary.left, out); + // Left side: Only move if NOT an assignment target + int is_assignment = + (node->binary.op[strlen(node->binary.op) - 1] == '=' && + strcmp(node->binary.op, "==") != 0 && strcmp(node->binary.op, "!=") != 0 && + strcmp(node->binary.op, "<=") != 0 && strcmp(node->binary.op, ">=") != 0); + + if (is_assignment) + { + codegen_expression(ctx, node->binary.left, out); + } + else + { + codegen_expression_with_move(ctx, node->binary.left, out); + } + fprintf(out, " %s ", node->binary.op); - codegen_expression(ctx, node->binary.right, out); + codegen_expression_with_move(ctx, node->binary.right, out); fprintf(out, ")"); } break; @@ -408,7 +422,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) while (arg) { fprintf(out, ", "); - codegen_expression(ctx, arg, out); + codegen_expression_with_move(ctx, arg, out); arg = arg->next; } fprintf(out, "); })"); @@ -491,7 +505,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) while (arg) { fprintf(out, ", "); - codegen_expression(ctx, arg, out); + codegen_expression_with_move(ctx, arg, out); arg = arg->next; } fprintf(out, ")"); @@ -541,7 +555,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) while (arg) { fprintf(out, ", "); - codegen_expression(ctx, arg, out); + codegen_expression_with_move(ctx, arg, out); arg = arg->next; } fprintf(out, "); })"); @@ -583,7 +597,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) fprintf(out, ", "); } first = 0; - codegen_expression(ctx, arg, out); + codegen_expression_with_move(ctx, arg, out); arg = arg->next; } } @@ -634,7 +648,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) fprintf(out, ", "); } first_field = 0; - codegen_expression(ctx, curr, out); + codegen_expression_with_move(ctx, curr, out); curr = curr->next; } fprintf(out, "}"); @@ -654,7 +668,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } else { - codegen_expression(ctx, arg, out); + codegen_expression_with_move(ctx, arg, out); } if (arg && arg->next) diff --git a/src/codegen/codegen.h b/src/codegen/codegen.h index d27356a..942de41 100644 --- a/src/codegen/codegen.h +++ b/src/codegen/codegen.h @@ -26,6 +26,9 @@ void emit_auto_type(ParserContext *ctx, ASTNode *init_expr, Token t, FILE *out); char *codegen_type_to_string(Type *t); void emit_func_signature(FILE *out, ASTNode *func, const char *name_override); char *strip_template_suffix(const char *name); +char *strip_template_suffix(const char *name); +int emit_move_invalidation(ParserContext *ctx, ASTNode *node, FILE *out); +void codegen_expression_with_move(ParserContext *ctx, ASTNode *node, FILE *out); // Declaration emission (codegen_decl.c). void emit_preamble(ParserContext *ctx, FILE *out); diff --git a/src/codegen/codegen_stmt.c b/src/codegen/codegen_stmt.c index 1cbd3a2..542a3c0 100644 --- a/src/codegen/codegen_stmt.c +++ b/src/codegen/codegen_stmt.c @@ -901,6 +901,11 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) codegen_expression(ctx, node->var_decl.init_expr, out); } fprintf(out, ";\n"); + if (node->var_decl.init_expr && + emit_move_invalidation(ctx, node->var_decl.init_expr, out)) + { + fprintf(out, ";\n"); + } if (node->type_info) { @@ -935,6 +940,11 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) fprintf(out, " = "); codegen_expression(ctx, node->var_decl.init_expr, out); fprintf(out, ";\n"); + if (node->var_decl.init_expr && + emit_move_invalidation(ctx, node->var_decl.init_expr, out)) + { + fprintf(out, ";\n"); + } } else { @@ -949,6 +959,11 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) fprintf(out, " = "); codegen_expression(ctx, node->var_decl.init_expr, out); fprintf(out, ";\n"); + if (node->var_decl.init_expr && + emit_move_invalidation(ctx, node->var_decl.init_expr, out)) + { + fprintf(out, ";\n"); + } } } } diff --git a/src/codegen/codegen_utils.c b/src/codegen/codegen_utils.c index 3169eba..38ae409 100644 --- a/src/codegen/codegen_utils.c +++ b/src/codegen/codegen_utils.c @@ -706,3 +706,84 @@ void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) } fprintf(out, ")"); } + +// Invalidate a moved-from variable by zeroing it out to prevent double-free +int emit_move_invalidation(ParserContext *ctx, ASTNode *node, FILE *out) +{ + if (!node) + { + return 0; + } + + // Check if it's a valid l-value we can memset + if (node->type != NODE_EXPR_VAR && node->type != NODE_EXPR_MEMBER) + { + return 0; + } + + // Common logic to find type and check Drop + char *type_name = infer_type(ctx, node); + ASTNode *def = NULL; + if (type_name) + { + char *clean_type = type_name; + if (strncmp(clean_type, "struct ", 7) == 0) + { + clean_type += 7; + } + def = find_struct_def(ctx, clean_type); + } + + if (def && def->type_info && def->type_info->traits.has_drop) + { + if (node->type == NODE_EXPR_VAR) + { + fprintf(out, "memset(&%s, 0, sizeof(%s))", node->var_ref.name, node->var_ref.name); + return 1; + } + else if (node->type == NODE_EXPR_MEMBER) + { + // For members: memset(&foo.bar, 0, sizeof(foo.bar)) + fprintf(out, "memset(&"); + codegen_expression(ctx, node, out); + fprintf(out, ", 0, sizeof("); + codegen_expression(ctx, node, out); + fprintf(out, "))"); + return 1; + } + } + return 0; +} + +// Emits expression, wrapping it in a move-invalidation block if it's a consuming variable usage +void codegen_expression_with_move(ParserContext *ctx, ASTNode *node, FILE *out) +{ + if (node && (node->type == NODE_EXPR_VAR || node->type == NODE_EXPR_MEMBER)) + { + // Re-use infer logic to see if we need invalidation + char *type_name = infer_type(ctx, node); + ASTNode *def = NULL; + if (type_name) + { + char *clean_type = type_name; + if (strncmp(clean_type, "struct ", 7) == 0) + { + clean_type += 7; + } + def = find_struct_def(ctx, clean_type); + } + + if (def && def->type_info && def->type_info->traits.has_drop) + { + fprintf(out, "({ __typeof__("); + codegen_expression(ctx, node, out); + fprintf(out, ") _mv = "); + codegen_expression(ctx, node, out); + fprintf(out, "; "); + emit_move_invalidation(ctx, node, out); + fprintf(out, "; _mv; })"); + return; + } + } + codegen_expression(ctx, node, out); +} diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index 455baa3..50d96f0 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -4884,8 +4884,6 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) bin->binary.op = token_strdup(op); } - fprintf(stderr, "DEBUG: Binary Loop Op: %s\n", bin->binary.op); - if (strcmp(bin->binary.op, "/") == 0 || strcmp(bin->binary.op, "%") == 0) { if (rhs->type == NODE_EXPR_LITERAL && rhs->literal.type_kind == LITERAL_INT && @@ -5285,8 +5283,6 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) } } - fprintf(stderr, "DEBUG: Past Method Check Op=%s\n", bin->binary.op); - // Standard Type Checking (if no overload found) if (lhs->type_info && rhs->type_info) { @@ -5577,7 +5573,7 @@ ASTNode *parse_arrow_lambda_single(ParserContext *ctx, Lexer *l, char *param_nam { if (param_name[0] == 'x') { - fprintf(stderr, "DEBUG: Updating return type to %d\n", ret_val->type_info->kind); + // fprintf(stderr, "DEBUG: Updating return type to %d\n", ret_val->type_info->kind); } // Update return type if (t->inner) @@ -5606,11 +5602,6 @@ ASTNode *parse_arrow_lambda_single(ParserContext *ctx, Lexer *l, char *param_nam } else { - if (param_name[0] == 'x') - { - fprintf(stderr, "DEBUG: Fallback! sym=%p kind=%d\n", sym, - sym && sym->type_info ? sym->type_info->kind : -1); - } // Fallback to int if still unknown if (lambda->lambda.param_types[0]) { diff --git a/tests/features/test_copy_trait.zc b/tests/memory/test_copy_trait.zc index 994ccee..994ccee 100644 --- a/tests/features/test_copy_trait.zc +++ b/tests/memory/test_copy_trait.zc diff --git a/tests/features/test_drop.zc b/tests/memory/test_drop.zc index 8b34efe..8b34efe 100644 --- a/tests/features/test_drop.zc +++ b/tests/memory/test_drop.zc diff --git a/tests/memory/test_move_double_free.zc b/tests/memory/test_move_double_free.zc new file mode 100644 index 0000000..b82bd38 --- /dev/null +++ b/tests/memory/test_move_double_free.zc @@ -0,0 +1,87 @@ +import "../../std/mem.zc" + +// Global counters to track drop calls +var DROP_COUNT = 0; +var DROP_NULL_COUNT = 0; + +struct Resource { + data: int*; + id: int; +} + +impl Drop for Resource { + fn drop(self) { + if (self.data != NULL) { + DROP_COUNT = DROP_COUNT + 1; + free(self.data); + self.data = NULL; // Prevent double free logic if called again, though generated code should zero + } else { + DROP_NULL_COUNT = DROP_NULL_COUNT + 1; + } + } +} + +struct Container { + res: Resource; +} + +// No explicit Drop for Container, relies on compiler generating one + +test "move_variable" { + DROP_COUNT = 0; + DROP_NULL_COUNT = 0; + + { + var r1 = Resource { data: malloc(10), id: 1 }; + var r2 = r1; // Move + + // r1 should be nullified + // r2 owns data + } + + assert(DROP_COUNT == 1, "Should drop exactly once (r2)"); + assert(DROP_NULL_COUNT == 1, "Should see one null drop (r1)"); +} + +fn pass_through(r: Resource) -> Resource { + return r; // Move return +} + +test "move_function" { + DROP_COUNT = 0; + DROP_NULL_COUNT = 0; + + { + var r1 = Resource { data: malloc(10), id: 2 }; + var r2 = pass_through(r1); + // r1 moved to arg -> moved to return -> moved to r2 + } + + // r1: null drop + // arg: null drop (moved to return) + // return temp: null drop (moved to r2) + // r2: valid drop + + assert(DROP_COUNT == 1, "Should drop exactly once (final r2)"); + // We expect multiple null drops due to intermediate moves + assert(DROP_NULL_COUNT >= 1, "Should see at least one null drop"); +} + +test "partial_move_member" { + DROP_COUNT = 0; + DROP_NULL_COUNT = 0; + + { + var c = Container { res: Resource { data: malloc(10), id: 3 } }; + var r = c.res; // Partial move + + // c.res should be nullified + // r owns data + } + + // r drops valid + // c drops, checks res -> null drop + + assert(DROP_COUNT == 1, "Should drop exactly once (r)"); + assert(DROP_NULL_COUNT == 1, "Should see null drop (c.res)"); +} diff --git a/tests/features/test_move_semantics.zc b/tests/memory/test_move_semantics.zc index bf0d717..bf0d717 100644 --- a/tests/features/test_move_semantics.zc +++ b/tests/memory/test_move_semantics.zc diff --git a/tests/features/test_resources.zc b/tests/memory/test_resources.zc index dc7b9f9..dc7b9f9 100644 --- a/tests/features/test_resources.zc +++ b/tests/memory/test_resources.zc |
