summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-25 11:53:53 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-25 11:53:53 +0000
commit2dc5214fd8bb6a1168e2f2b643a36043c36c908a (patch)
tree55780a330598d3606205b662584a839ea60d0315
parent6a45f6a640dc8f7b5f9819d22d68cd79fbe3c260 (diff)
Fix for #121
-rw-r--r--src/codegen/codegen.c30
-rw-r--r--src/codegen/codegen.h3
-rw-r--r--src/codegen/codegen_stmt.c15
-rw-r--r--src/codegen/codegen_utils.c81
-rw-r--r--src/parser/parser_expr.c11
-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.zc87
-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