about summary refs log tree commit diff
diff options
context:
space:
mode:
authoredef <edef@edef.eu>2024-05-25T04·24+0000
committeredef <edef@edef.eu>2024-05-25T08·42+0000
commit49750fa1e7e307a323e22f4f0d98ad2dc88d78bf (patch)
treef98c24c80097f1fdbab222985ad0d9e9566f932e
parentc4c42c8b6c064b8b1a30632034948de07cbef305 (diff)
fix(tvix/eval): disallow empty but allocated string contexts r/8167
Both `Some(NixContext::new())` and `None` represent empty contexts,
but the former trips up `NixString::has_context`, and seems likely
to trip up other things.

We could hide the difference in the accessors, but we don't really
*want* the distinction to exist, since heap-allocating a null value
is pretty much always a mistake.

Change-Id: Ie84d26fb0d4b59e68354891ba13bde3bae40ab6e
Reviewed-on: https://cl.tvl.fyi/c/depot/+/11712
Tested-by: BuildkiteCI
Reviewed-by: flokli <flokli@flokli.de>
-rw-r--r--tvix/eval/src/value/string.rs28
1 files changed, 25 insertions, 3 deletions
diff --git a/tvix/eval/src/value/string.rs b/tvix/eval/src/value/string.rs
index 2a7a20209634..d0c7a254fb82 100644
--- a/tvix/eval/src/value/string.rs
+++ b/tvix/eval/src/value/string.rs
@@ -625,6 +625,11 @@ mod arbitrary {
 
 impl NixString {
     fn new(contents: &[u8], context: Option<Box<NixContext>>) -> Self {
+        debug_assert!(
+            !context.as_deref().is_some_and(NixContext::is_empty),
+            "BUG: initialized with empty context"
+        );
+
         // SAFETY: We're always fully initializing a NixString here:
         //
         // 1. NixStringInner::alloc sets up the len for us
@@ -649,7 +654,10 @@ impl NixString {
     {
         Self::new(
             Self::from(new_contents).as_ref(),
-            other.context().map(|c| Box::new(c.clone())),
+            other
+                .context()
+                .filter(|ctx| !ctx.is_empty())
+                .map(|c| Box::new(c.clone())),
         )
     }
 
@@ -730,7 +738,14 @@ impl NixString {
         //
         // Also, we're using the same lifetime and mutability as self, to fit the
         // pointer-to-reference conversion rules
-        unsafe { NixStringInner::context_ref(self.0).as_deref() }
+        let context = unsafe { NixStringInner::context_ref(self.0).as_deref() };
+
+        debug_assert!(
+            !context.is_some_and(NixContext::is_empty),
+            "BUG: empty context"
+        );
+
+        context
     }
 
     pub(crate) fn context_mut(&mut self) -> &mut Option<Box<NixContext>> {
@@ -739,7 +754,14 @@ impl NixString {
         //
         // Also, we're using the same lifetime and mutability as self, to fit the
         // pointer-to-reference conversion rules
-        unsafe { NixStringInner::context_mut(self.0) }
+        let context = unsafe { NixStringInner::context_mut(self.0) };
+
+        debug_assert!(
+            !context.as_deref().is_some_and(NixContext::is_empty),
+            "BUG: empty context"
+        );
+
+        context
     }
 
     pub fn iter_context(&self) -> impl Iterator<Item = &NixContext> {