snix_eval/value/string/
mod.rs

1//! This module implements Nix language strings.
2//!
3//! See [`NixString`] for more information about the internals of string values
4
5use bstr::{BStr, BString, ByteSlice, Chars};
6use nohash_hasher::BuildNoHashHasher;
7use rnix::ast;
8#[cfg(feature = "no_leak")]
9use rustc_hash::FxHashSet;
10use rustc_hash::FxHasher;
11use std::alloc::dealloc;
12use std::alloc::{alloc, handle_alloc_error, Layout};
13use std::borrow::{Borrow, Cow};
14use std::cell::RefCell;
15use std::ffi::c_void;
16use std::fmt::{self, Debug, Display};
17use std::hash::{Hash, Hasher};
18use std::ops::Deref;
19use std::ptr::{self, NonNull};
20use std::slice;
21
22use serde::de::{Deserializer, Visitor};
23use serde::Deserialize;
24
25mod context;
26
27pub use context::{NixContext, NixContextElement};
28
29/// This type is never instantiated, but serves to document the memory layout of the actual heap
30/// allocation for Nix strings.
31#[allow(dead_code)]
32struct NixStringInner {
33    /// The string context, if any.  Note that this is boxed to take advantage of the null pointer
34    /// niche, otherwise this field ends up being very large:
35    ///
36    /// ```notrust
37    /// >> std::mem::size_of::<Option<HashSet<String>>>()
38    /// 48
39    ///
40    /// >> std::mem::size_of::<Option<Box<HashSet<String>>>>()
41    /// 8
42    /// ```
43    context: Option<Box<NixContext>>,
44    /// The length of the data, stored *inline in the allocation*
45    length: usize,
46    /// The actual data for the string itself. Will always be `length` bytes long
47    data: [u8],
48}
49
50#[allow(clippy::zst_offset)]
51impl NixStringInner {
52    /// Construct a [`Layout`] for a nix string allocation with the given length.
53    ///
54    /// Returns a tuple of:
55    /// 1. The layout itself.
56    /// 2. The offset of [`Self::length`] within the allocation, assuming the allocation starts at 0
57    /// 3. The offset of the data array within the allocation, assuming the allocation starts at 0
58    fn layout(len: usize) -> (Layout, usize, usize) {
59        let layout = Layout::new::<Option<Box<NixContext>>>();
60        let (layout, len_offset) = layout.extend(Layout::new::<usize>()).unwrap();
61        let (layout, data_offset) = layout.extend(Layout::array::<u8>(len).unwrap()).unwrap();
62        (layout, len_offset, data_offset)
63    }
64
65    /// Returns the [`Layout`] for an *already-allocated* nix string, loading the length from the
66    /// pointer.
67    ///
68    /// Returns a tuple of:
69    /// 1. The layout itself.
70    /// 2. The offset of [`Self::length`] within the allocation, assuming the allocation starts at 0
71    /// 3. The offset of the data array within the allocation, assuming the allocation starts at 0
72    ///
73    /// # Safety
74    ///
75    /// This function must only be called on a pointer that has been properly initialized with
76    /// [`Self::alloc`]. The data buffer may not necessarily be initialized
77    unsafe fn layout_of(this: NonNull<c_void>) -> (Layout, usize, usize) {
78        let layout = Layout::new::<Option<Box<NixContext>>>();
79        let (_, len_offset) = layout.extend(Layout::new::<usize>()).unwrap();
80        // SAFETY: Layouts are linear, so even though we haven't involved data at all yet, we know
81        // the len_offset is a valid offset into the second field of the allocation
82        let len = *(this.as_ptr().add(len_offset) as *const usize);
83        Self::layout(len)
84    }
85
86    /// Allocate an *uninitialized* nix string with the given length. Writes the length to the
87    /// length value in the pointer, but leaves both context and data uninitialized
88    ///
89    /// This function is safe to call (as constructing pointers of any sort of validity is always
90    /// safe in Rust) but it is unsafe to use the resulting pointer to do anything other than
91    ///
92    /// 1. Read the length
93    /// 2. Write the context
94    /// 3. Write the data
95    ///
96    /// until the string is fully initialized
97    fn alloc(len: usize) -> NonNull<c_void> {
98        let (layout, len_offset, _data_offset) = Self::layout(len);
99        debug_assert_ne!(layout.size(), 0);
100        unsafe {
101            // SAFETY: Layout has non-zero size, since the layout of the context and the
102            // layout of the len both have non-zero size
103            let ptr = alloc(layout);
104
105            if let Some(this) = NonNull::new(ptr as *mut _) {
106                // SAFETY: We've allocated with a layout that causes the len_offset to be in-bounds
107                // and writeable, and if the allocation succeeded it won't wrap
108                ((this.as_ptr() as *mut u8).add(len_offset) as *mut usize).write(len);
109                debug_assert_eq!(Self::len(this), len);
110                this
111            } else {
112                handle_alloc_error(layout);
113            }
114        }
115    }
116
117    /// Deallocate the Nix string at the given pointer
118    ///
119    /// # Safety
120    ///
121    /// This function must only be called with a pointer that has been properly initialized with
122    /// [`Self::alloc`]
123    unsafe fn dealloc(this: NonNull<c_void>) {
124        let (layout, _, _) = Self::layout_of(this);
125        // SAFETY: okay because of the safety guarantees of this method
126        dealloc(this.as_ptr() as *mut u8, layout)
127    }
128
129    /// Return the length of the Nix string at the given pointer
130    ///
131    /// # Safety
132    ///
133    /// This function must only be called with a pointer that has been properly initialized with
134    /// [`Self::alloc`]
135    unsafe fn len(this: NonNull<c_void>) -> usize {
136        let (_, len_offset, _) = Self::layout_of(this);
137        // SAFETY: As long as the safety guarantees of this method are upheld, we've allocated with
138        // a layout that causes the len_offset to be in-bounds and writeable, and if the allocation
139        // succeeded it won't wrap
140        *(this.as_ptr().add(len_offset) as *const usize)
141    }
142
143    /// Return a pointer to the context value within the given Nix string pointer
144    ///
145    /// # Safety
146    ///
147    /// This function must only be called with a pointer that has been properly initialized with
148    /// [`Self::alloc`]
149    unsafe fn context_ptr(this: NonNull<c_void>) -> *mut Option<Box<NixContext>> {
150        // SAFETY: The context is the first field in the layout of the allocation
151        this.as_ptr() as *mut Option<Box<NixContext>>
152    }
153
154    /// Construct a shared reference to the context value within the given Nix string pointer
155    ///
156    /// # Safety
157    ///
158    /// This function must only be called with a pointer that has been properly initialized with
159    /// [`Self::alloc`], and where the context has been properly initialized (by writing to the
160    /// pointer returned from [`Self::context_ptr`]).
161    ///
162    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
163    /// [`NonNull::as_ref`] for more.
164    unsafe fn context_ref<'a>(this: NonNull<c_void>) -> &'a Option<Box<NixContext>> {
165        Self::context_ptr(this).as_ref().unwrap()
166    }
167
168    /// Construct a mutable reference to the context value within the given Nix string pointer
169    ///
170    /// # Safety
171    ///
172    /// This function must only be called with a pointer that has been properly initialized with
173    /// [`Self::alloc`], and where the context has been properly initialized (by writing to the
174    /// pointer returned from [`Self::context_ptr`]).
175    ///
176    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
177    /// [`NonNull::as_mut`] for more.
178    unsafe fn context_mut<'a>(this: NonNull<c_void>) -> &'a mut Option<Box<NixContext>> {
179        Self::context_ptr(this).as_mut().unwrap()
180    }
181
182    /// Return a pointer to the data array within the given Nix string pointer
183    ///
184    /// # Safety
185    ///
186    /// This function must only be called with a pointer that has been properly initialized with
187    /// [`Self::alloc`]
188    unsafe fn data_ptr(this: NonNull<c_void>) -> *mut u8 {
189        let (_, _, data_offset) = Self::layout_of(this);
190        // SAFETY: data is the third field in the layout of the allocation
191        this.as_ptr().add(data_offset) as *mut u8
192    }
193
194    /// Construct a shared reference to the data slice within the given Nix string pointer
195    ///
196    /// # Safety
197    ///
198    /// This function must only be called with a pointer that has been properly initialized with
199    /// [`Self::alloc`], and where the data array has been properly initialized (by writing to the
200    /// pointer returned from [`Self::data_ptr`]).
201    ///
202    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
203    /// [`slice::from_raw_parts`] for more.
204    unsafe fn data_slice<'a>(this: NonNull<c_void>) -> &'a [u8] {
205        let len = Self::len(this);
206        let data = Self::data_ptr(this);
207        slice::from_raw_parts(data, len)
208    }
209
210    /// Construct a mutable reference to the data slice within the given Nix string pointer
211    ///
212    /// # Safety
213    ///
214    /// This function must only be called with a pointer that has been properly initialized with
215    /// [`Self::alloc`], and where the data array has been properly initialized (by writing to the
216    /// pointer returned from [`Self::data_ptr`]).
217    ///
218    /// Also, all the normal Rust rules about pointer-to-reference conversion apply. See
219    /// [`slice::from_raw_parts_mut`] for more.
220    #[allow(dead_code)]
221    unsafe fn data_slice_mut<'a>(this: NonNull<c_void>) -> &'a mut [u8] {
222        let len = Self::len(this);
223        let data = Self::data_ptr(this);
224        slice::from_raw_parts_mut(data, len)
225    }
226
227    /// Clone the Nix string pointed to by this pointer, and return a pointer to a new Nix string
228    /// containing the same data and context.
229    ///
230    /// # Safety
231    ///
232    /// This function must only be called with a pointer that has been properly initialized with
233    /// [`Self::alloc`], and where the context has been properly initialized (by writing to the
234    /// pointer returned from [`Self::context_ptr`]), and the data array has been properly
235    /// initialized (by writing to the pointer returned from [`Self::data_ptr`]).
236    unsafe fn clone(this: NonNull<c_void>) -> NonNull<c_void> {
237        let (layout, _, _) = Self::layout_of(this);
238        let ptr = alloc(layout);
239        if let Some(new) = NonNull::new(ptr as *mut _) {
240            ptr::copy_nonoverlapping(this.as_ptr(), new.as_ptr(), layout.size());
241            Self::context_ptr(new).write(Self::context_ref(this).clone());
242            new
243        } else {
244            handle_alloc_error(layout);
245        }
246    }
247}
248
249#[derive(Default)]
250struct InternerInner {
251    #[allow(clippy::disallowed_types)] // Not using the default hasher
252    map: std::collections::HashMap<u64, NonNull<c_void>, BuildNoHashHasher<u64>>,
253    #[cfg(feature = "no_leak")]
254    #[allow(clippy::disallowed_types)] // Not using the default hasher
255    interned_strings: FxHashSet<NonNull<c_void>>,
256}
257
258unsafe impl Send for InternerInner {}
259
260fn hash<T>(s: T) -> u64
261where
262    T: Hash,
263{
264    let mut hasher = FxHasher::default();
265    s.hash(&mut hasher);
266    hasher.finish()
267}
268
269impl InternerInner {
270    pub fn intern(&mut self, s: &[u8]) -> NixString {
271        let hash = hash(s);
272        if let Some(s) = self.map.get(&hash) {
273            return NixString(*s);
274        }
275
276        let string = NixString::new_inner(s, None);
277        self.map.insert(hash, string.0);
278        #[cfg(feature = "no_leak")]
279        self.interned_strings.insert(string.0);
280        string
281    }
282}
283
284#[derive(Default)]
285struct Interner(RefCell<InternerInner>);
286
287impl Interner {
288    pub fn intern(&self, s: &[u8]) -> NixString {
289        self.0.borrow_mut().intern(s)
290    }
291
292    #[cfg(feature = "no_leak")]
293    pub fn is_interned_string(&self, string: &NixString) -> bool {
294        self.0.borrow().interned_strings.contains(&string.0)
295    }
296}
297
298thread_local! {
299    static INTERNER: Interner = Interner::default();
300}
301
302/// Nix string values
303///
304/// # Internals
305///
306/// For performance reasons (to keep allocations small, and to avoid indirections), [`NixString`] is
307/// represented as a single *thin* pointer to a packed data structure containing the
308/// [context][NixContext] and the string data itself (which is a raw byte array, to match the Nix
309/// string semantics that allow any array of bytes to be represented by a string).
310///
311/// This memory representation is documented in [`NixStringInner`], but since Rust prefers to deal
312/// with slices via *fat pointers* (pointers that include the length in the *pointer*, not in the
313/// heap allocation), we have to do mostly manual layout management and allocation for this
314/// representation. See the documentation for the methods of [`NixStringInner`] for more information
315pub struct NixString(NonNull<c_void>);
316
317unsafe impl Send for NixString {}
318unsafe impl Sync for NixString {}
319
320impl Drop for NixString {
321    #[cfg(not(feature = "no_leak"))]
322    fn drop(&mut self) {
323        if self.context().is_some() {
324            // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
325            // according to the rules of dealloc
326            unsafe {
327                NixStringInner::dealloc(self.0);
328            }
329        }
330    }
331
332    #[cfg(feature = "no_leak")]
333    fn drop(&mut self) {
334        if INTERNER.with(|i| i.is_interned_string(self)) {
335            return;
336        }
337
338        // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
339        // according to the rules of dealloc
340        unsafe {
341            NixStringInner::dealloc(self.0);
342        }
343    }
344}
345
346impl Clone for NixString {
347    fn clone(&self) -> Self {
348        if cfg!(feature = "no_leak") || self.context().is_some() {
349            // SAFETY: There's no way to construct a NixString that doesn't leave the allocation correct
350            // according to the rules of clone
351            unsafe { Self(NixStringInner::clone(self.0)) }
352        } else {
353            // SAFETY:
354            //
355            // - NixStrings are never mutated
356            // - NixStrings are never freed
357            Self(self.0)
358        }
359    }
360}
361
362impl Debug for NixString {
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        if let Some(ctx) = self.context() {
365            f.debug_struct("NixString")
366                .field("context", ctx)
367                .field("data", &self.as_bstr())
368                .finish()
369        } else {
370            write!(f, "{:?}", self.as_bstr())
371        }
372    }
373}
374
375impl PartialEq for NixString {
376    fn eq(&self, other: &Self) -> bool {
377        self.0 == other.0 || self.as_bstr() == other.as_bstr()
378    }
379}
380
381impl Eq for NixString {}
382
383impl PartialEq<&[u8]> for NixString {
384    fn eq(&self, other: &&[u8]) -> bool {
385        **self == **other
386    }
387}
388
389impl PartialEq<&str> for NixString {
390    fn eq(&self, other: &&str) -> bool {
391        **self == other.as_bytes()
392    }
393}
394
395impl PartialOrd for NixString {
396    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
397        Some(self.cmp(other))
398    }
399}
400
401impl Ord for NixString {
402    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
403        if self.0 == other.0 {
404            return std::cmp::Ordering::Equal;
405        }
406        self.as_bstr().cmp(other.as_bstr())
407    }
408}
409
410impl From<Box<BStr>> for NixString {
411    fn from(value: Box<BStr>) -> Self {
412        Self::new(&value, None)
413    }
414}
415
416impl From<BString> for NixString {
417    fn from(value: BString) -> Self {
418        Self::new(&value, None)
419    }
420}
421
422impl From<&BStr> for NixString {
423    fn from(value: &BStr) -> Self {
424        value.to_owned().into()
425    }
426}
427
428impl From<&[u8]> for NixString {
429    fn from(value: &[u8]) -> Self {
430        Self::from(value.to_owned())
431    }
432}
433
434impl From<Vec<u8>> for NixString {
435    fn from(value: Vec<u8>) -> Self {
436        value.into_boxed_slice().into()
437    }
438}
439
440impl From<Box<[u8]>> for NixString {
441    fn from(value: Box<[u8]>) -> Self {
442        Self::new(&value, None)
443    }
444}
445
446impl From<&str> for NixString {
447    fn from(s: &str) -> Self {
448        s.as_bytes().into()
449    }
450}
451
452impl From<String> for NixString {
453    fn from(s: String) -> Self {
454        s.into_bytes().into()
455    }
456}
457
458impl From<Box<str>> for NixString {
459    fn from(s: Box<str>) -> Self {
460        s.into_boxed_bytes().into()
461    }
462}
463
464impl From<ast::Ident> for NixString {
465    fn from(ident: ast::Ident) -> Self {
466        ident.ident_token().unwrap().text().into()
467    }
468}
469
470impl<'a> From<&'a NixString> for &'a BStr {
471    fn from(s: &'a NixString) -> Self {
472        s.as_bstr()
473    }
474}
475
476// No impl From<NixString> for String, that one quotes.
477
478impl From<NixString> for BString {
479    fn from(s: NixString) -> Self {
480        s.as_bstr().to_owned()
481    }
482}
483
484impl AsRef<[u8]> for NixString {
485    fn as_ref(&self) -> &[u8] {
486        self.as_bytes()
487    }
488}
489
490impl Borrow<BStr> for NixString {
491    fn borrow(&self) -> &BStr {
492        self.as_bstr()
493    }
494}
495
496impl Hash for NixString {
497    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
498        self.as_bstr().hash(state)
499    }
500}
501
502impl<'de> Deserialize<'de> for NixString {
503    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
504    where
505        D: Deserializer<'de>,
506    {
507        struct StringVisitor;
508
509        impl Visitor<'_> for StringVisitor {
510            type Value = NixString;
511
512            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
513                formatter.write_str("a valid Nix string")
514            }
515
516            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
517            where
518                E: serde::de::Error,
519            {
520                Ok(v.into())
521            }
522
523            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
524            where
525                E: serde::de::Error,
526            {
527                Ok(v.into())
528            }
529        }
530
531        deserializer.deserialize_string(StringVisitor)
532    }
533}
534
535impl Deref for NixString {
536    type Target = BStr;
537
538    fn deref(&self) -> &Self::Target {
539        self.as_bstr()
540    }
541}
542
543#[cfg(feature = "arbitrary")]
544mod arbitrary {
545    use super::*;
546    use proptest::prelude::{any_with, Arbitrary};
547    use proptest::strategy::{BoxedStrategy, Strategy};
548
549    impl Arbitrary for NixString {
550        type Parameters = <String as Arbitrary>::Parameters;
551
552        type Strategy = BoxedStrategy<Self>;
553
554        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
555            any_with::<String>(args).prop_map(Self::from).boxed()
556        }
557    }
558}
559
560/// Set non-scientifically. TODO(aspen): think more about what this should be
561const INTERN_THRESHOLD: usize = 32;
562
563impl NixString {
564    fn new(contents: &[u8], context: Option<Box<NixContext>>) -> Self {
565        debug_assert!(
566            !context.as_deref().is_some_and(NixContext::is_empty),
567            "BUG: initialized with empty context"
568        );
569
570        if !cfg!(feature = "no_leak") /* It's only safe to intern if we leak strings, since there's
571                                       * nothing yet preventing interned strings from getting freed
572                                       * (and then used by other copies) otherwise
573                                       */
574            && contents.len() <= INTERN_THRESHOLD
575            && context.is_none()
576        {
577            return INTERNER.with(|i| i.intern(contents));
578        }
579
580        Self::new_inner(contents, context)
581    }
582
583    fn new_inner(contents: &[u8], context: Option<Box<NixContext>>) -> Self {
584        // SAFETY: We're always fully initializing a NixString here:
585        //
586        // 1. NixStringInner::alloc sets up the len for us
587        // 2. We set the context, using ptr::write to make sure that the uninitialized memory isn't
588        //    read or dropped
589        // 3. We set the data, using copy_from_nonoverlapping to make sure that the uninitialized
590        //    memory isn't read or dropped
591        //
592        // Only *then* can we construct a NixString
593        unsafe {
594            let inner = NixStringInner::alloc(contents.len());
595            NixStringInner::context_ptr(inner).write(context);
596            NixStringInner::data_ptr(inner)
597                .copy_from_nonoverlapping(contents.as_ptr(), contents.len());
598            Self(inner)
599        }
600    }
601
602    pub fn new_inherit_context_from<T>(other: &NixString, new_contents: T) -> Self
603    where
604        NixString: From<T>,
605    {
606        Self::new(
607            Self::from(new_contents).as_ref(),
608            other.context().map(|c| Box::new(c.clone())),
609        )
610    }
611
612    pub fn new_context_from<T>(context: NixContext, contents: T) -> Self
613    where
614        NixString: From<T>,
615    {
616        Self::new(
617            Self::from(contents).as_ref(),
618            if context.is_empty() {
619                None
620            } else {
621                Some(Box::new(context))
622            },
623        )
624    }
625
626    pub fn as_bstr(&self) -> &BStr {
627        BStr::new(self.as_bytes())
628    }
629
630    pub fn as_bytes(&self) -> &[u8] {
631        // SAFETY: There's no way to construct an uninitialized NixString (see the SAFETY comment in
632        // `new`)
633        unsafe { NixStringInner::data_slice(self.0) }
634    }
635
636    /// Returns a &str if the NixString is a valid UTF-8 string.
637    pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
638        std::str::from_utf8(self.as_bytes())
639    }
640
641    /// Return a displayable representation of the string as an
642    /// identifier.
643    ///
644    /// This is used when printing out strings used as e.g. attribute
645    /// set keys, as those are only escaped in the presence of special
646    /// characters.
647    pub fn ident_str(&self) -> Cow<str> {
648        let escaped = match self.to_str_lossy() {
649            Cow::Borrowed(s) => nix_escape_string(s),
650            Cow::Owned(s) => nix_escape_string(&s).into_owned().into(),
651        };
652        match escaped {
653            // A borrowed string is unchanged and can be returned as
654            // is.
655            Cow::Borrowed(_) => {
656                if is_valid_nix_identifier(&escaped) && !is_keyword(&escaped) {
657                    escaped
658                } else {
659                    Cow::Owned(format!("\"{}\"", escaped))
660                }
661            }
662
663            // An owned string has escapes, and needs the outer quotes
664            // for display.
665            Cow::Owned(s) => Cow::Owned(format!("\"{}\"", s)),
666        }
667    }
668
669    pub fn concat(&self, other: &Self) -> Self {
670        let mut s = self.to_vec();
671        s.extend(&(***other));
672
673        let context = [self.context(), other.context()]
674            .into_iter()
675            .flatten()
676            .fold(NixContext::new(), |mut acc_ctx, new_ctx| {
677                // TODO: consume new_ctx?
678                acc_ctx.extend(new_ctx.iter().cloned());
679                acc_ctx
680            });
681        Self::new_context_from(context, s)
682    }
683
684    pub(crate) fn context(&self) -> Option<&NixContext> {
685        // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY
686        // comment in `new`).
687        //
688        // Also, we're using the same lifetime and mutability as self, to fit the
689        // pointer-to-reference conversion rules
690        let context = unsafe { NixStringInner::context_ref(self.0).as_deref() };
691
692        debug_assert!(
693            !context.is_some_and(NixContext::is_empty),
694            "BUG: empty context"
695        );
696
697        context
698    }
699
700    pub(crate) fn context_mut(&mut self) -> &mut Option<Box<NixContext>> {
701        // SAFETY: There's no way to construct an uninitialized or invalid NixString (see the SAFETY
702        // comment in `new`).
703        //
704        // Also, we're using the same lifetime and mutability as self, to fit the
705        // pointer-to-reference conversion rules
706        let context = unsafe { NixStringInner::context_mut(self.0) };
707
708        debug_assert!(
709            !context.as_deref().is_some_and(NixContext::is_empty),
710            "BUG: empty context"
711        );
712
713        context
714    }
715
716    /// Iterates over all context elements.
717    /// See [context::NixContext::iter_plain], [context::NixContext::iter_derivation], [context::NixContext::iter_single_outputs].
718    pub fn iter_context(&self) -> impl Iterator<Item = &NixContext> {
719        self.context().into_iter()
720    }
721
722    /// Iterates over "plain" context elements, e.g. sources imported
723    /// in the store without more information, i.e. `toFile` or coerced imported paths.
724    /// It yields paths to the store.
725    pub fn iter_ctx_plain(&self) -> impl Iterator<Item = &str> {
726        self.iter_context().flat_map(|context| context.iter_plain())
727    }
728
729    /// Iterates over "full derivations" context elements, e.g. something
730    /// referring to their `drvPath`, i.e. their full sources and binary closure.
731    /// It yields derivation paths.
732    pub fn iter_ctx_derivation(&self) -> impl Iterator<Item = &str> {
733        self.iter_context()
734            .flat_map(|context| context.iter_derivation())
735    }
736
737    /// Iterates over "single" context elements, e.g. single derived paths,
738    /// or also known as the single output of a given derivation.
739    /// The first element of the tuple is the output name
740    /// and the second element is the derivation path.
741    pub fn iter_ctx_single_outputs(&self) -> impl Iterator<Item = (&str, &str)> {
742        self.iter_context()
743            .flat_map(|context| context.iter_single_outputs())
744    }
745
746    /// Returns whether this Nix string possess a context or not.
747    pub fn has_context(&self) -> bool {
748        self.context().is_some()
749    }
750
751    /// This clears the context of the string, returning
752    /// the removed dependency tracking information.
753    pub fn take_context(&mut self) -> Option<Box<NixContext>> {
754        self.context_mut().take()
755    }
756
757    /// This clears the context of that string, losing
758    /// all dependency tracking information.
759    pub fn clear_context(&mut self) {
760        let _ = self.take_context();
761    }
762
763    pub fn chars(&self) -> Chars<'_> {
764        self.as_bstr().chars()
765    }
766}
767
768fn nix_escape_char(ch: char, next: Option<&char>) -> Option<&'static str> {
769    match (ch, next) {
770        ('\\', _) => Some("\\\\"),
771        ('"', _) => Some("\\\""),
772        ('\n', _) => Some("\\n"),
773        ('\t', _) => Some("\\t"),
774        ('\r', _) => Some("\\r"),
775        ('$', Some('{')) => Some("\\$"),
776        _ => None,
777    }
778}
779
780/// Return true if this string is a keyword -- character strings
781/// which lexically match the "identifier" production but are not
782/// parsed as identifiers.  See also cppnix commit
783/// b72bc4a972fe568744d98b89d63adcd504cb586c.
784fn is_keyword(s: &str) -> bool {
785    matches!(
786        s,
787        "if" | "then" | "else" | "assert" | "with" | "let" | "in" | "rec" | "inherit"
788    )
789}
790
791/// Return true if this string can be used as an identifier in Nix.
792fn is_valid_nix_identifier(s: &str) -> bool {
793    // adapted from rnix-parser's tokenizer.rs
794    let mut chars = s.chars();
795    match chars.next() {
796        Some('a'..='z' | 'A'..='Z' | '_') => (),
797        _ => return false,
798    }
799    for c in chars {
800        match c {
801            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '\'' => (),
802            _ => return false,
803        }
804    }
805    true
806}
807
808/// Escape a Nix string for display, as most user-visible representation
809/// are escaped strings.
810///
811/// Note that this does not add the outer pair of surrounding quotes.
812fn nix_escape_string(input: &str) -> Cow<str> {
813    let mut iter = input.char_indices().peekable();
814
815    while let Some((i, c)) = iter.next() {
816        if let Some(esc) = nix_escape_char(c, iter.peek().map(|(_, c)| c)) {
817            let mut escaped = String::with_capacity(input.len());
818            escaped.push_str(&input[..i]);
819            escaped.push_str(esc);
820
821            // In theory we calculate how many bytes it takes to represent `esc`
822            // in UTF-8 and use that for the offset. It is, however, safe to
823            // assume that to be 1, as all characters that can be escaped in a
824            // Nix string are ASCII.
825            let mut inner_iter = input[i + 1..].chars().peekable();
826            while let Some(c) = inner_iter.next() {
827                match nix_escape_char(c, inner_iter.peek()) {
828                    Some(esc) => escaped.push_str(esc),
829                    None => escaped.push(c),
830                }
831            }
832
833            return Cow::Owned(escaped);
834        }
835    }
836
837    Cow::Borrowed(input)
838}
839
840impl Display for NixString {
841    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
842        f.write_str("\"")?;
843        f.write_str(&nix_escape_string(&self.to_str_lossy()))?;
844        f.write_str("\"")
845    }
846}
847
848#[cfg(all(test, feature = "arbitrary"))]
849mod tests {
850    use test_strategy::proptest;
851
852    use super::*;
853
854    use crate::properties::{eq_laws, hash_laws, ord_laws};
855
856    #[test]
857    fn size() {
858        assert_eq!(std::mem::size_of::<NixString>(), 8);
859    }
860
861    #[proptest]
862    fn clone_strings(s: NixString) {
863        drop(s.clone())
864    }
865
866    eq_laws!(NixString);
867    hash_laws!(NixString);
868    ord_laws!(NixString);
869}