1use bstr::{BStr, BString, ByteSlice, Chars};
6use rnix::ast;
7#[cfg(feature = "no_leak")]
8use rustc_hash::FxHashSet;
9use rustc_hash::FxHasher;
10use std::alloc::dealloc;
11use std::alloc::{Layout, alloc, handle_alloc_error};
12use std::borrow::{Borrow, Cow};
13use std::cell::RefCell;
14use std::ffi::c_void;
15use std::fmt::{self, Debug, Display};
16use std::hash::{Hash, Hasher};
17use std::ops::Deref;
18use std::ptr::{self, NonNull};
19use std::slice;
20
21use serde::Deserialize;
22use serde::de::{Deserializer, Visitor};
23
24mod context;
25
26pub use context::{NixContext, NixContextElement};
27
28#[allow(dead_code)]
31struct NixStringInner {
32 context: Option<Box<NixContext>>,
43 length: usize,
45 data: [u8],
47}
48
49#[allow(clippy::zst_offset)]
50impl NixStringInner {
51 fn layout(len: usize) -> (Layout, usize, usize) {
58 let layout = Layout::new::<Option<Box<NixContext>>>();
59 let (layout, len_offset) = layout.extend(Layout::new::<usize>()).unwrap();
60 let (layout, data_offset) = layout.extend(Layout::array::<u8>(len).unwrap()).unwrap();
61 (layout, len_offset, data_offset)
62 }
63
64 unsafe fn layout_of(this: NonNull<c_void>) -> (Layout, usize, usize) {
77 unsafe {
78 let layout = Layout::new::<Option<Box<NixContext>>>();
79 let (_, len_offset) = layout.extend(Layout::new::<usize>()).unwrap();
80 let len = *(this.as_ptr().add(len_offset) as *const usize);
83 Self::layout(len)
84 }
85 }
86
87 fn alloc(len: usize) -> NonNull<c_void> {
99 let (layout, len_offset, _data_offset) = Self::layout(len);
100 debug_assert_ne!(layout.size(), 0);
101 unsafe {
102 let ptr = alloc(layout);
105
106 if let Some(this) = NonNull::new(ptr as *mut _) {
107 ((this.as_ptr() as *mut u8).add(len_offset) as *mut usize).write(len);
110 debug_assert_eq!(Self::len(this), len);
111 this
112 } else {
113 handle_alloc_error(layout);
114 }
115 }
116 }
117
118 unsafe fn dealloc(this: NonNull<c_void>) {
125 unsafe {
126 let (layout, _, _) = Self::layout_of(this);
127 dealloc(this.as_ptr() as *mut u8, layout)
129 }
130 }
131
132 unsafe fn len(this: NonNull<c_void>) -> usize {
139 unsafe {
140 let (_, len_offset, _) = Self::layout_of(this);
141 *(this.as_ptr().add(len_offset) as *const usize)
145 }
146 }
147
148 unsafe fn context_ptr(this: NonNull<c_void>) -> *mut Option<Box<NixContext>> {
155 this.as_ptr() as *mut Option<Box<NixContext>>
157 }
158
159 unsafe fn context_ref<'a>(this: NonNull<c_void>) -> &'a Option<Box<NixContext>> {
170 unsafe { Self::context_ptr(this).as_ref().unwrap() }
171 }
172
173 unsafe fn context_mut<'a>(this: NonNull<c_void>) -> &'a mut Option<Box<NixContext>> {
184 unsafe { Self::context_ptr(this).as_mut().unwrap() }
185 }
186
187 unsafe fn data_ptr(this: NonNull<c_void>) -> *mut u8 {
194 unsafe {
195 let (_, _, data_offset) = Self::layout_of(this);
196 this.as_ptr().add(data_offset) as *mut u8
198 }
199 }
200
201 unsafe fn data_slice<'a>(this: NonNull<c_void>) -> &'a [u8] {
212 unsafe {
213 let len = Self::len(this);
214 let data = Self::data_ptr(this);
215 slice::from_raw_parts(data, len)
216 }
217 }
218
219 #[allow(dead_code)]
230 unsafe fn data_slice_mut<'a>(this: NonNull<c_void>) -> &'a mut [u8] {
231 unsafe {
232 let len = Self::len(this);
233 let data = Self::data_ptr(this);
234 slice::from_raw_parts_mut(data, len)
235 }
236 }
237
238 unsafe fn clone(this: NonNull<c_void>) -> NonNull<c_void> {
248 unsafe {
249 let (layout, _, _) = Self::layout_of(this);
250 let ptr = alloc(layout);
251 if let Some(new) = NonNull::new(ptr as *mut _) {
252 ptr::copy_nonoverlapping(this.as_ptr(), new.as_ptr(), layout.size());
253 Self::context_ptr(new).write(Self::context_ref(this).clone());
254 new
255 } else {
256 handle_alloc_error(layout);
257 }
258 }
259 }
260}
261
262#[derive(Default)]
263struct InternerInner {
264 map: hashbrown::HashTable<NonNull<c_void>>,
265 #[cfg(feature = "no_leak")]
266 #[allow(clippy::disallowed_types)] interned_strings: FxHashSet<NonNull<c_void>>,
268}
269
270unsafe impl Send for InternerInner {}
271
272fn hash<T>(s: T) -> u64
273where
274 T: Hash,
275{
276 let mut hasher = FxHasher::default();
277 s.hash(&mut hasher);
278 hasher.finish()
279}
280
281impl InternerInner {
282 pub fn intern(&mut self, contents: &[u8]) -> NixString {
283 let hashed = hash(contents);
284 if let Some(s) = self.map.find(hashed, |m| NixString(*m) == contents) {
285 return NixString(*s);
286 }
287
288 let string = NixString::new_inner(contents, None);
289 self.map.insert_unique(hashed, string.0, |val| hash(val));
290 #[cfg(feature = "no_leak")]
291 self.interned_strings.insert(string.0);
292 string
293 }
294}
295
296#[derive(Default)]
297struct Interner(RefCell<InternerInner>);
298
299impl Interner {
300 pub fn intern(&self, s: &[u8]) -> NixString {
301 self.0.borrow_mut().intern(s)
302 }
303
304 #[cfg(feature = "no_leak")]
305 pub fn is_interned_string(&self, string: &NixString) -> bool {
306 self.0.borrow().interned_strings.contains(&string.0)
307 }
308}
309
310thread_local! {
311 static INTERNER: Interner = Interner::default();
312}
313
314pub struct NixString(NonNull<c_void>);
328
329unsafe impl Send for NixString {}
330unsafe impl Sync for NixString {}
331
332impl Drop for NixString {
333 #[cfg(not(feature = "no_leak"))]
334 fn drop(&mut self) {
335 if self.context().is_some() {
336 unsafe {
339 NixStringInner::dealloc(self.0);
340 }
341 }
342 }
343
344 #[cfg(feature = "no_leak")]
345 fn drop(&mut self) {
346 if INTERNER.with(|i| i.is_interned_string(self)) {
347 return;
348 }
349
350 unsafe {
353 NixStringInner::dealloc(self.0);
354 }
355 }
356}
357
358impl Clone for NixString {
359 fn clone(&self) -> Self {
360 if cfg!(feature = "no_leak") || self.context().is_some() {
361 unsafe { Self(NixStringInner::clone(self.0)) }
364 } else {
365 Self(self.0)
370 }
371 }
372}
373
374impl Debug for NixString {
375 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376 if let Some(ctx) = self.context() {
377 f.debug_struct("NixString")
378 .field("context", ctx)
379 .field("data", &self.as_bstr())
380 .finish()
381 } else {
382 write!(f, "{:?}", self.as_bstr())
383 }
384 }
385}
386
387impl PartialEq for NixString {
388 fn eq(&self, other: &Self) -> bool {
389 self.0 == other.0 || self.as_bstr() == other.as_bstr()
390 }
391}
392
393impl Eq for NixString {}
394
395impl PartialEq<&[u8]> for NixString {
396 fn eq(&self, other: &&[u8]) -> bool {
397 **self == **other
398 }
399}
400
401impl PartialEq<&str> for NixString {
402 fn eq(&self, other: &&str) -> bool {
403 **self == other.as_bytes()
404 }
405}
406
407impl PartialOrd for NixString {
408 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
409 Some(self.cmp(other))
410 }
411}
412
413impl Ord for NixString {
414 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
415 if self.0 == other.0 {
416 return std::cmp::Ordering::Equal;
417 }
418 self.as_bstr().cmp(other.as_bstr())
419 }
420}
421
422impl From<Box<BStr>> for NixString {
423 fn from(value: Box<BStr>) -> Self {
424 Self::new(&value, None)
425 }
426}
427
428impl From<BString> for NixString {
429 fn from(value: BString) -> Self {
430 Self::new(&value, None)
431 }
432}
433
434impl From<&BStr> for NixString {
435 fn from(value: &BStr) -> Self {
436 value.to_owned().into()
437 }
438}
439
440impl From<&[u8]> for NixString {
441 fn from(value: &[u8]) -> Self {
442 Self::from(value.to_owned())
443 }
444}
445
446impl From<Vec<u8>> for NixString {
447 fn from(value: Vec<u8>) -> Self {
448 value.into_boxed_slice().into()
449 }
450}
451
452impl From<Box<[u8]>> for NixString {
453 fn from(value: Box<[u8]>) -> Self {
454 Self::new(&value, None)
455 }
456}
457
458impl From<&str> for NixString {
459 fn from(s: &str) -> Self {
460 s.as_bytes().into()
461 }
462}
463
464impl From<String> for NixString {
465 fn from(s: String) -> Self {
466 s.into_bytes().into()
467 }
468}
469
470impl From<Box<str>> for NixString {
471 fn from(s: Box<str>) -> Self {
472 s.into_boxed_bytes().into()
473 }
474}
475
476impl From<ast::Ident> for NixString {
477 fn from(ident: ast::Ident) -> Self {
478 ident.ident_token().unwrap().text().into()
479 }
480}
481
482impl<'a> From<&'a NixString> for &'a BStr {
483 fn from(s: &'a NixString) -> Self {
484 s.as_bstr()
485 }
486}
487
488impl From<NixString> for BString {
491 fn from(s: NixString) -> Self {
492 s.as_bstr().to_owned()
493 }
494}
495
496impl AsRef<[u8]> for NixString {
497 fn as_ref(&self) -> &[u8] {
498 self.as_bytes()
499 }
500}
501
502impl Borrow<BStr> for NixString {
503 fn borrow(&self) -> &BStr {
504 self.as_bstr()
505 }
506}
507
508impl Hash for NixString {
509 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
510 self.as_bstr().hash(state)
511 }
512}
513
514impl<'de> Deserialize<'de> for NixString {
515 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
516 where
517 D: Deserializer<'de>,
518 {
519 struct StringVisitor;
520
521 impl Visitor<'_> for StringVisitor {
522 type Value = NixString;
523
524 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
525 formatter.write_str("a valid Nix string")
526 }
527
528 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
529 where
530 E: serde::de::Error,
531 {
532 Ok(v.into())
533 }
534
535 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
536 where
537 E: serde::de::Error,
538 {
539 Ok(v.into())
540 }
541 }
542
543 deserializer.deserialize_string(StringVisitor)
544 }
545}
546
547impl Deref for NixString {
548 type Target = BStr;
549
550 fn deref(&self) -> &Self::Target {
551 self.as_bstr()
552 }
553}
554
555#[cfg(feature = "arbitrary")]
556mod arbitrary {
557 use super::*;
558 use proptest::prelude::{Arbitrary, any_with};
559 use proptest::strategy::{BoxedStrategy, Strategy};
560
561 impl Arbitrary for NixString {
562 type Parameters = <String as Arbitrary>::Parameters;
563
564 type Strategy = BoxedStrategy<Self>;
565
566 fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
567 any_with::<String>(args).prop_map(Self::from).boxed()
568 }
569 }
570}
571
572const INTERN_THRESHOLD: usize = 32;
574
575impl NixString {
576 fn new(contents: &[u8], context: Option<Box<NixContext>>) -> Self {
577 debug_assert!(
578 !context.as_deref().is_some_and(NixContext::is_empty),
579 "BUG: initialized with empty context"
580 );
581
582 if !cfg!(feature = "no_leak") && contents.len() <= INTERN_THRESHOLD
587 && context.is_none()
588 {
589 return INTERNER.with(|i| i.intern(contents));
590 }
591
592 Self::new_inner(contents, context)
593 }
594
595 fn new_inner(contents: &[u8], context: Option<Box<NixContext>>) -> Self {
596 unsafe {
606 let inner = NixStringInner::alloc(contents.len());
607 NixStringInner::context_ptr(inner).write(context);
608 NixStringInner::data_ptr(inner)
609 .copy_from_nonoverlapping(contents.as_ptr(), contents.len());
610 Self(inner)
611 }
612 }
613
614 pub fn new_inherit_context_from<T>(other: &NixString, new_contents: T) -> Self
615 where
616 NixString: From<T>,
617 {
618 Self::new(
619 Self::from(new_contents).as_ref(),
620 other.context().map(|c| Box::new(c.clone())),
621 )
622 }
623
624 pub fn new_context_from<T>(context: NixContext, contents: T) -> Self
625 where
626 NixString: From<T>,
627 {
628 Self::new(
629 Self::from(contents).as_ref(),
630 if context.is_empty() {
631 None
632 } else {
633 Some(Box::new(context))
634 },
635 )
636 }
637
638 pub fn as_bstr(&self) -> &BStr {
639 BStr::new(self.as_bytes())
640 }
641
642 pub fn as_bytes(&self) -> &[u8] {
643 unsafe { NixStringInner::data_slice(self.0) }
646 }
647
648 pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
650 std::str::from_utf8(self.as_bytes())
651 }
652
653 pub fn ident_str(&self) -> Cow<str> {
660 let escaped = match self.to_str_lossy() {
661 Cow::Borrowed(s) => nix_escape_string(s),
662 Cow::Owned(s) => nix_escape_string(&s).into_owned().into(),
663 };
664 match escaped {
665 Cow::Borrowed(_) => {
668 if is_valid_nix_identifier(&escaped) && !is_keyword(&escaped) {
669 escaped
670 } else {
671 Cow::Owned(format!("\"{escaped}\""))
672 }
673 }
674
675 Cow::Owned(s) => Cow::Owned(format!("\"{s}\"")),
678 }
679 }
680
681 pub fn concat(&self, other: &Self) -> Self {
682 let mut s = self.to_vec();
683 s.extend(&(***other));
684
685 let context = [self.context(), other.context()]
686 .into_iter()
687 .flatten()
688 .fold(NixContext::new(), |mut acc_ctx, new_ctx| {
689 acc_ctx.extend(new_ctx.iter().cloned());
691 acc_ctx
692 });
693 Self::new_context_from(context, s)
694 }
695
696 pub(crate) fn context(&self) -> Option<&NixContext> {
697 let context = unsafe { NixStringInner::context_ref(self.0).as_deref() };
703
704 debug_assert!(
705 !context.is_some_and(NixContext::is_empty),
706 "BUG: empty context"
707 );
708
709 context
710 }
711
712 pub(crate) fn context_mut(&mut self) -> &mut Option<Box<NixContext>> {
713 let context = unsafe { NixStringInner::context_mut(self.0) };
719
720 debug_assert!(
721 !context.as_deref().is_some_and(NixContext::is_empty),
722 "BUG: empty context"
723 );
724
725 context
726 }
727
728 pub fn iter_context(&self) -> impl Iterator<Item = &NixContext> {
731 self.context().into_iter()
732 }
733
734 pub fn iter_ctx_plain(&self) -> impl Iterator<Item = &str> {
738 self.iter_context().flat_map(|context| context.iter_plain())
739 }
740
741 pub fn iter_ctx_derivation(&self) -> impl Iterator<Item = &str> {
745 self.iter_context()
746 .flat_map(|context| context.iter_derivation())
747 }
748
749 pub fn iter_ctx_single_outputs(&self) -> impl Iterator<Item = (&str, &str)> {
754 self.iter_context()
755 .flat_map(|context| context.iter_single_outputs())
756 }
757
758 pub fn has_context(&self) -> bool {
760 self.context().is_some()
761 }
762
763 pub fn take_context(&mut self) -> Option<Box<NixContext>> {
766 self.context_mut().take()
767 }
768
769 pub fn clear_context(&mut self) {
772 let _ = self.take_context();
773 }
774
775 pub fn chars(&self) -> Chars<'_> {
776 self.as_bstr().chars()
777 }
778}
779
780fn nix_escape_char(ch: char, next: Option<&char>) -> Option<&'static str> {
781 match (ch, next) {
782 ('\\', _) => Some("\\\\"),
783 ('"', _) => Some("\\\""),
784 ('\n', _) => Some("\\n"),
785 ('\t', _) => Some("\\t"),
786 ('\r', _) => Some("\\r"),
787 ('$', Some('{')) => Some("\\$"),
788 _ => None,
789 }
790}
791
792fn is_keyword(s: &str) -> bool {
797 matches!(
798 s,
799 "if" | "then" | "else" | "assert" | "with" | "let" | "in" | "rec" | "inherit"
800 )
801}
802
803fn is_valid_nix_identifier(s: &str) -> bool {
805 let mut chars = s.chars();
807 match chars.next() {
808 Some('a'..='z' | 'A'..='Z' | '_') => (),
809 _ => return false,
810 }
811 for c in chars {
812 match c {
813 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '\'' => (),
814 _ => return false,
815 }
816 }
817 true
818}
819
820fn nix_escape_string(input: &str) -> Cow<str> {
825 let mut iter = input.char_indices().peekable();
826
827 while let Some((i, c)) = iter.next() {
828 if let Some(esc) = nix_escape_char(c, iter.peek().map(|(_, c)| c)) {
829 let mut escaped = String::with_capacity(input.len());
830 escaped.push_str(&input[..i]);
831 escaped.push_str(esc);
832
833 let mut inner_iter = input[i + 1..].chars().peekable();
838 while let Some(c) = inner_iter.next() {
839 match nix_escape_char(c, inner_iter.peek()) {
840 Some(esc) => escaped.push_str(esc),
841 None => escaped.push(c),
842 }
843 }
844
845 return Cow::Owned(escaped);
846 }
847 }
848
849 Cow::Borrowed(input)
850}
851
852impl Display for NixString {
853 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
854 f.write_str("\"")?;
855 f.write_str(&nix_escape_string(&self.to_str_lossy()))?;
856 f.write_str("\"")
857 }
858}
859
860#[cfg(all(test, feature = "arbitrary"))]
861mod tests {
862 use test_strategy::proptest;
863
864 use super::*;
865
866 use crate::properties::{eq_laws, hash_laws, ord_laws};
867
868 #[test]
869 fn size() {
870 assert_eq!(std::mem::size_of::<NixString>(), 8);
871 }
872
873 #[proptest]
874 fn clone_strings(s: NixString) {
875 drop(s.clone())
876 }
877
878 eq_laws!(NixString);
879 hash_laws!(NixString);
880 ord_laws!(NixString);
881}