summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-19 22:48:04 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-19 22:48:04 +0000
commit23065ddf6ed0b3762dda5f5059888eb52b5c2415 (patch)
treeaec187b8211203081e8dacb07a5ce325eb348cc4
parent3af5dcf34d705cc52c1ffe5b85c2a90b5104e4c9 (diff)
Fixes related to memory safety. I will work more on this related to the stdlib.
-rw-r--r--docs/std/vec.md1
-rw-r--r--std/fs.zc18
-rw-r--r--std/option.zc16
-rw-r--r--std/path.zc4
-rw-r--r--std/result.zc8
-rw-r--r--std/string.zc34
-rw-r--r--std/vec.zc7
-rw-r--r--tests/collections/test_string_suite.zc12
-rw-r--r--tests/std/test_fs.zc13
9 files changed, 83 insertions, 30 deletions
diff --git a/docs/std/vec.md b/docs/std/vec.md
index a0422bb..bda8d5b 100644
--- a/docs/std/vec.md
+++ b/docs/std/vec.md
@@ -87,4 +87,5 @@ struct Vec<T> {
| Method | Signature | Description |
| :--- | :--- | :--- |
| **Free** | `free(self)` | Manually frees memory. Safe to call multiple times. |
+| **Forget** | `forget(self)` | Detaches the memory buffer from the vector (sets fields to 0). Prevents `Drop` from freeing memory. Useful for implementing move semantics or transferring ownership. |
| **Trait** | `impl Drop for Vec` | Automatically calls `free()` when `Vec` goes out of scope. |
diff --git a/std/fs.zc b/std/fs.zc
index 4223054..d35a1fa 100644
--- a/std/fs.zc
+++ b/std/fs.zc
@@ -50,7 +50,7 @@ struct Metadata {
is_file: bool;
}
-@derive(Eq)
+// @derive(Eq) - Removed to avoid double-free on String field
struct DirEntry {
name: String;
is_dir: bool;
@@ -87,7 +87,11 @@ impl File {
var s = String::new(buffer);
free(buffer);
- return Result<String>::Ok(s);
+ var res = Result<String>::Ok(s);
+ s.forget();
+ var ret = res;
+ res.forget();
+ return ret;
}
fn read_all(path: char*) -> Result<String> {
@@ -97,7 +101,9 @@ impl File {
var f: File = res.unwrap();
var s_res = f.read_to_string();
f.close();
- return s_res;
+ var ret = s_res;
+ s_res.forget();
+ return ret;
}
fn write_string(self, content: char*) -> Result<bool> {
@@ -157,6 +163,10 @@ impl File {
free(name_buf);
closedir(dir);
- return Result< Vec<DirEntry> >::Ok(entries);
+ var res = Result< Vec<DirEntry> >::Ok(entries);
+ entries.forget();
+ var ret = res;
+ res.forget();
+ return ret;
}
}
diff --git a/std/option.zc b/std/option.zc
index 5652bd7..11b4428 100644
--- a/std/option.zc
+++ b/std/option.zc
@@ -23,12 +23,26 @@ impl Option<T> {
return !self.is_some;
}
+ fn forget(self) {
+ memset(&self.val, 0, sizeof(T));
+ }
+
fn unwrap(self) -> T {
if (!self.is_some) {
!"Panic: unwrap called on None";
exit(1);
}
- return self.val;
+ var v = self.val;
+ memset(&self.val, 0, sizeof(T));
+ return v;
+ }
+
+ fn unwrap_ref(self) -> T* {
+ if (!self.is_some) {
+ !"Panic: unwrap_ref called on None";
+ exit(1);
+ }
+ return &self.val;
}
fn unwrap_or(self, def_val: T) -> T {
diff --git a/std/path.zc b/std/path.zc
index 6a9531d..7055880 100644
--- a/std/path.zc
+++ b/std/path.zc
@@ -33,12 +33,12 @@ impl Path {
var last = self.str.vec.get(base_len - 1);
if (last != '/' && last != '\\') {
var sep = String::new("/");
- new_s.append(sep);
+ new_s.append(&sep);
}
}
var other_str = String::new(other);
- new_s.append(other_str);
+ new_s.append(&other_str);
return Path { str: new_s };
}
diff --git a/std/result.zc b/std/result.zc
index b043162..3794f1a 100644
--- a/std/result.zc
+++ b/std/result.zc
@@ -24,12 +24,18 @@ impl Result<T> {
return !self.is_ok;
}
+ fn forget(self) {
+ memset(&self.val, 0, sizeof(T));
+ }
+
fn unwrap(self) -> T {
if (!self.is_ok) {
!"Panic: unwrap called on Err: {self.err}";
exit(1);
}
- return self.val;
+ var v = self.val;
+ memset(&self.val, 0, sizeof(T));
+ return v;
}
fn expect(self, msg: char*) -> T {
diff --git a/std/string.zc b/std/string.zc
index 4e0ba03..2e59179 100644
--- a/std/string.zc
+++ b/std/string.zc
@@ -16,7 +16,11 @@ impl String {
v.push(s[i]);
}
v.push(0);
- return String { vec: v };
+ var d = v.data;
+ var l = v.len;
+ var c = v.cap;
+ v.forget();
+ return String { vec: Vec<char> { data: d, len: l, cap: c } };
}
fn from(s: char*) -> String {
@@ -30,23 +34,31 @@ impl String {
fn destroy(self) {
self.vec.free();
}
+
+ fn forget(self) {
+ self.vec.forget();
+ }
- fn append(self, other: String) {
+ fn append(self, other: String*) {
self.vec.len = self.vec.len - 1;
- var other_len = other.vec.len;
+ var other_len = (*other).vec.len;
for (var i = 0; i < other_len; i = i + 1) {
- self.vec.push(other.vec.get(i));
+ self.vec.push((*other).vec.get(i));
}
}
- fn add(self, other: String) -> String {
+ fn add(self, other: String*) -> String {
var new_s = String::from(self.c_str());
new_s.append(other);
- return new_s;
+ var d = new_s.vec.data;
+ var l = new_s.vec.len;
+ var c = new_s.vec.cap;
+ new_s.forget();
+ return String { vec: Vec<char> { data: d, len: l, cap: c } };
}
- fn eq(self, other: String) -> bool {
- return strcmp(self.c_str(), other.c_str()) == 0;
+ fn eq(self, other: String*) -> bool {
+ return strcmp(self.c_str(), (*other).c_str()) == 0;
}
fn length(self) -> usize {
@@ -66,7 +78,11 @@ impl String {
v.push(self.vec.get(start + i));
}
v.push(0);
- return String { vec: v };
+ var d = v.data;
+ var l = v.len;
+ var c = v.cap;
+ v.forget();
+ return String { vec: Vec<char> { data: d, len: l, cap: c } };
}
fn find(self, target: char) -> Option<usize> {
diff --git a/std/vec.zc b/std/vec.zc
index be4dc05..fe16ac8 100644
--- a/std/vec.zc
+++ b/std/vec.zc
@@ -244,6 +244,13 @@ impl Vec<T> {
return true;
}
+ // Prevent Drop from freeing memory (simulates move)
+ fn forget(self) {
+ self.data = 0;
+ self.len = 0;
+ self.cap = 0;
+ }
+
fn clone(self) -> Vec<T> {
if (self.len == 0) {
return Vec<T> { data: 0, len: 0, cap: 0 };
diff --git a/tests/collections/test_string_suite.zc b/tests/collections/test_string_suite.zc
index 4d2c080..a3f608d 100644
--- a/tests/collections/test_string_suite.zc
+++ b/tests/collections/test_string_suite.zc
@@ -10,7 +10,7 @@ test "test_string_methods" {
var s1: String = String::from("Hello World");
var sub: String = s1.substring(0, 5);
var expected1: String = String::from("Hello");
- if (sub.eq(expected1)) {
+ if (sub.eq(&expected1)) {
println " -> substring(0, 5): Passed";
} else {
assert(false, "substring(0, 5) failed");
@@ -19,7 +19,7 @@ test "test_string_methods" {
// Substring middle
var sub2: String = s1.substring(6, 5);
var expected2: String = String::from("World");
- if (sub2.eq(expected2)) {
+ if (sub2.eq(&expected2)) {
println " -> substring(6, 5): Passed";
} else {
assert(false, "substring(6, 5) failed");
@@ -67,9 +67,9 @@ test "test_string_methods" {
// Append
var s2: String = String::from("Hello");
var s3: String = String::from(" World");
- s2.append(s3);
+ s2.append(&s3);
var expected6: String = String::from("Hello World");
- if (s2.eq(expected6)) {
+ if (s2.eq(&expected6)) {
println " -> append(): Passed";
} else {
assert(false, "append() failed");
@@ -99,7 +99,7 @@ test "test_string_std_ops" {
var s1 = String::from("Hello");
var s2 = String::from(" World");
- var s3 = s1 + s2;
+ var s3 = s1.add(&s2);
print "Concatenated: ";
s3.println();
@@ -107,7 +107,7 @@ test "test_string_std_ops" {
assert(s3.length() == 11, "Length mismatch");
var s4 = String::from("Hello World");
- if (s3.eq(s4)) {
+ if (s3.eq(&s4)) {
println "Equality check passed: Strings are identical.";
} else {
assert(false, "Equality check failed");
diff --git a/tests/std/test_fs.zc b/tests/std/test_fs.zc
index 7f66589..fc6d069 100644
--- a/tests/std/test_fs.zc
+++ b/tests/std/test_fs.zc
@@ -13,19 +13,19 @@ test "test_std_fs_extended" {
var p3 = Path::new("file.txt");
var ext = p3.extension();
assert(ext.is_some(), "Extension missed");
- assert(strcmp(ext.unwrap().c_str(), ".txt") == 0, "Wrong extension");
+ assert(strcmp(ext.unwrap_ref().c_str(), ".txt") == 0, "Wrong extension");
var p4 = Path::new("/usr/bin/gcc");
var parent = p4.parent();
assert(parent.is_some(), "Parent missed");
- assert(strcmp(parent.unwrap().c_str(), "/usr/bin") == 0, "Wrong parent");
+ assert(strcmp(parent.unwrap_ref().c_str(), "/usr/bin") == 0, "Wrong parent");
var fname = p4.file_name();
assert(fname.is_some(), "Filename missed");
- assert(strcmp(fname.unwrap().c_str(), "gcc") == 0, "Wrong filename");
+ assert(strcmp(fname.unwrap_ref().c_str(), "gcc") == 0, "Wrong filename");
"Testing FS...";
- var test_dir = "test_fs_sandbox";
+ var test_dir = "tests/test_fs_sandbox";
if (File::exists(test_dir)) {
File::remove_dir(test_dir);
@@ -63,9 +63,8 @@ test "test_std_fs_extended" {
assert(list_res.is_ok(), "Read dir failed");
var entries = list_res.unwrap();
var found_idx = -1;
- for (var i: usize = 0; i < entries.length(); i = i + 1) {
- var entry: DirEntry = entries.get(i);
- if (strcmp(entry.name.c_str(), "hello.txt") == 0) {
+ for entry in &entries {
+ if (strcmp((*entry).name.c_str(), "hello.txt") == 0) {
found_idx = 1;
break;
}