about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/Cargo.nix2
-rw-r--r--tvix/eval/Cargo.toml5
-rw-r--r--tvix/eval/src/value/string.rs18
3 files changed, 21 insertions, 4 deletions
diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix
index 4826317e9fb1..926145c31fca 100644
--- a/tvix/Cargo.nix
+++ b/tvix/Cargo.nix
@@ -16122,7 +16122,7 @@ rec {
           "proptest" = [ "dep:proptest" ];
           "test-strategy" = [ "dep:test-strategy" ];
         };
-        resolvedDefaultFeatures = [ "arbitrary" "default" "impure" "nix_tests" "proptest" "test-strategy" ];
+        resolvedDefaultFeatures = [ "arbitrary" "default" "impure" "nix_tests" "no_leak" "proptest" "test-strategy" ];
       };
       "tvix-eval-builtin-macros" = rec {
         crateName = "tvix-eval-builtin-macros";
diff --git a/tvix/eval/Cargo.toml b/tvix/eval/Cargo.toml
index 83600eb6e2bd..ed2ed0440804 100644
--- a/tvix/eval/Cargo.toml
+++ b/tvix/eval/Cargo.toml
@@ -58,6 +58,11 @@ impure = []
 # Enables Arbitrary impls for internal types (required to run tests)
 arbitrary = ["proptest", "test-strategy", "imbl/proptest"]
 
+# Don't leak strings (enable this if you care about peak memory usage of eval)
+#
+# This is intended as a stop-gap until we have a garbage collector
+no_leak = []
+
 [[bench]]
 name = "eval"
 harness = false
diff --git a/tvix/eval/src/value/string.rs b/tvix/eval/src/value/string.rs
index 163e140a19c4..3e991890ea5a 100644
--- a/tvix/eval/src/value/string.rs
+++ b/tvix/eval/src/value/string.rs
@@ -409,6 +409,10 @@ unsafe impl Sync for NixString {}
 
 impl Drop for NixString {
     fn drop(&mut self) {
+        if !cfg!(feature = "no_leak") {
+            return;
+        }
+
         // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
         // according to the rules of dealloc
         unsafe {
@@ -419,9 +423,17 @@ impl Drop for NixString {
 
 impl Clone for NixString {
     fn clone(&self) -> Self {
-        // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
-        // according to the rules of clone
-        unsafe { Self(NixStringInner::clone(self.0)) }
+        if cfg!(feature = "no_leak") || self.context().is_some() {
+            // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
+            // according to the rules of clone
+            unsafe { Self(NixStringInner::clone(self.0)) }
+        } else {
+            // SAFETY:
+            //
+            // - NixStrings are never mutated
+            // - NixStrings are never freed
+            Self(self.0)
+        }
     }
 }