1mod resolve;
16
17use proc_macro::TokenStream;
18use quote::{format_ident, quote};
19use syn::punctuated::Punctuated;
20use syn::{FnArg, ItemFn, ItemStruct, Meta, Pat, Token, Type, parse_macro_input};
21
22const KNOWN_PERMISSIONS: &[&str] = &[
24 "FsRead",
25 "FsWrite",
26 "FsAll",
27 "NetConnect",
28 "NetBind",
29 "NetAll",
30 "EnvRead",
31 "EnvWrite",
32 "Spawn",
33 "Ambient",
34];
35
36#[proc_macro_attribute]
67pub fn permission(attr: TokenStream, item: TokenStream) -> TokenStream {
68 let attr2: proc_macro2::TokenStream = attr.into();
69 let input = parse_macro_input!(item as ItemStruct);
70
71 match permission_inner(attr2, &input) {
72 Ok(tokens) => tokens.into(),
73 Err(e) => e.into_compile_error().into(),
74 }
75}
76
77fn permission_inner(
78 attr: proc_macro2::TokenStream,
79 input: &ItemStruct,
80) -> syn::Result<proc_macro2::TokenStream> {
81 match &input.fields {
83 syn::Fields::Unit => {}
84 _ => {
85 return Err(syn::Error::new_spanned(
86 input,
87 "#[capsec::permission] requires a unit struct (no fields)",
88 ));
89 }
90 }
91
92 if !input.generics.params.is_empty() {
94 return Err(syn::Error::new_spanned(
95 &input.generics,
96 "#[capsec::permission] does not support generic structs",
97 ));
98 }
99
100 let struct_name = &input.ident;
101 let struct_vis = &input.vis;
102 let struct_attrs = &input.attrs;
103
104 let subsumes = parse_subsumes(attr)?;
106
107 let permission_impl = quote! {
109 impl capsec_core::permission::Permission for #struct_name {
110 type __CapsecSeal = capsec_core::__private::SealProof;
111 }
112 };
113
114 let subsumes_impls: Vec<_> = subsumes
122 .iter()
123 .map(|sub| {
124 quote! {
125 impl capsec_core::permission::Subsumes<#sub> for #struct_name {}
126
127 impl capsec_core::has::Has<#sub> for capsec_core::cap::Cap<#struct_name> {
128 fn cap_ref(&self) -> capsec_core::cap::Cap<#sub> {
129 capsec_core::cap::Cap::__capsec_new_derived(capsec_core::__private::__capsec_seal())
130 }
131 }
132
133 impl capsec_core::has::Has<#sub> for capsec_core::cap::SendCap<#struct_name> {
134 fn cap_ref(&self) -> capsec_core::cap::Cap<#sub> {
135 capsec_core::cap::Cap::__capsec_new_derived(capsec_core::__private::__capsec_seal())
136 }
137 }
138
139 impl capsec_core::cap_provider::CapProvider<#sub> for capsec_core::cap::Cap<#struct_name> {
140 fn provide_cap(&self, _target: &str) -> Result<capsec_core::cap::Cap<#sub>, capsec_core::error::CapSecError> {
141 Ok(capsec_core::cap::Cap::__capsec_new_derived(capsec_core::__private::__capsec_seal()))
142 }
143 }
144
145 impl capsec_core::cap_provider::CapProvider<#sub> for capsec_core::cap::SendCap<#struct_name> {
146 fn provide_cap(&self, _target: &str) -> Result<capsec_core::cap::Cap<#sub>, capsec_core::error::CapSecError> {
147 Ok(capsec_core::cap::Cap::__capsec_new_derived(capsec_core::__private::__capsec_seal()))
148 }
149 }
150 }
151 })
152 .collect();
153
154 Ok(quote! {
155 #(#struct_attrs)*
156 #struct_vis struct #struct_name;
157
158 #permission_impl
159 #(#subsumes_impls)*
160 })
161}
162
163fn parse_subsumes(attr: proc_macro2::TokenStream) -> syn::Result<Vec<syn::Path>> {
165 if attr.is_empty() {
166 return Ok(Vec::new());
167 }
168
169 let meta: Meta = syn::parse2(attr)?;
171 match &meta {
172 Meta::NameValue(nv) if nv.path.is_ident("subsumes") => {
173 if let syn::Expr::Array(arr) = &nv.value {
175 let mut paths = Vec::new();
176 let mut seen = std::collections::HashSet::new();
177 for elem in &arr.elems {
178 if let syn::Expr::Path(ep) = elem {
179 let path_str = quote::quote!(#ep).to_string();
180 if !seen.insert(path_str.clone()) {
181 return Err(syn::Error::new_spanned(
182 elem,
183 format!("duplicate subsumes entry: {}", path_str),
184 ));
185 }
186 paths.push(ep.path.clone());
187 } else {
188 return Err(syn::Error::new_spanned(
189 elem,
190 "expected a permission type path in subsumes list",
191 ));
192 }
193 }
194 Ok(paths)
195 } else {
196 Err(syn::Error::new_spanned(
197 &nv.value,
198 "expected an array [A, B, C] for subsumes",
199 ))
200 }
201 }
202 _ => Err(syn::Error::new_spanned(
203 &meta,
204 "expected `subsumes = [...]`",
205 )),
206 }
207}
208
209#[proc_macro_attribute]
249pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream {
250 let attr2: proc_macro2::TokenStream = attr.into();
251 let func = parse_macro_input!(item as ItemFn);
252
253 match requires_inner(attr2, &func) {
254 Ok(tokens) => tokens.into(),
255 Err(e) => e.into_compile_error().into(),
256 }
257}
258
259fn requires_inner(
260 attr: proc_macro2::TokenStream,
261 func: &ItemFn,
262) -> syn::Result<proc_macro2::TokenStream> {
263 let metas: Punctuated<Meta, Token![,]> =
264 syn::parse::Parser::parse2(Punctuated::parse_terminated, attr)?;
265
266 let mut on_param: Option<syn::Ident> = None;
268 let mut perm_metas: Vec<&Meta> = Vec::new();
269
270 for meta in &metas {
271 if let Meta::NameValue(nv) = meta
272 && nv.path.is_ident("on")
273 {
274 if let syn::Expr::Path(ep) = &nv.value
275 && let Some(ident) = ep.path.get_ident()
276 {
277 on_param = Some(ident.clone());
278 continue;
279 }
280 return Err(syn::Error::new_spanned(&nv.value, "expected an identifier"));
281 }
282 perm_metas.push(meta);
283 }
284
285 let mut cap_types = Vec::new();
287 for meta in &perm_metas {
288 cap_types.push(resolve::meta_to_permission_type(meta)?);
289 }
290
291 let doc_string = format!(
293 "capsec::requires({})",
294 cap_types
295 .iter()
296 .map(|c| quote!(#c).to_string())
297 .collect::<Vec<_>>()
298 .join(", ")
299 );
300
301 let has_impl_bounds = func.sig.inputs.iter().any(|arg| {
303 if let FnArg::Typed(pat_type) = arg {
304 contains_impl_trait(&pat_type.ty)
305 } else {
306 false
307 }
308 });
309
310 let mut modified_sig: Option<syn::Signature> = None;
312 let assertion = if let Some(ref param_name) = on_param {
313 let param_type = find_param_type(&func.sig, param_name)?;
315 let inner_type = unwrap_references(¶m_type);
316
317 let generic_ident = func.sig.generics.type_params().find_map(|tp| {
319 if let Type::Path(ref type_path) = *inner_type
320 && type_path.path.is_ident(&tp.ident)
321 {
322 Some(tp.ident.clone())
323 } else {
324 None
325 }
326 });
327
328 if let Some(ref gen_ident) = generic_ident {
329 let mut sig = func.sig.clone();
331 let where_clause = sig.generics.make_where_clause();
332 let bounds: Vec<_> = cap_types
333 .iter()
334 .map(|perm_ty| quote! { capsec_core::cap_provider::CapProvider<#perm_ty> })
335 .collect();
336 where_clause
337 .predicates
338 .push(syn::parse2(quote! { #gen_ident: #(#bounds)+* }).unwrap());
339 modified_sig = Some(sig);
340 None } else {
342 let assert_fns: Vec<_> = cap_types
344 .iter()
345 .enumerate()
346 .map(|(i, perm_ty)| {
347 let fn_name = format_ident!("_assert_has_{}", i);
348 quote! {
349 fn #fn_name<T: capsec_core::cap_provider::CapProvider<#perm_ty>>() {}
350 }
351 })
352 .collect();
353
354 let assert_calls: Vec<_> = (0..cap_types.len())
355 .map(|i| {
356 let fn_name = format_ident!("_assert_has_{}", i);
357 quote! { #fn_name::<#inner_type>(); }
358 })
359 .collect();
360
361 Some(quote! {
362 const _: () = {
363 #(#assert_fns)*
364 fn _check() {
365 #(#assert_calls)*
366 }
367 };
368 })
369 }
370 } else if !has_impl_bounds && !func.sig.inputs.is_empty() && !cap_types.is_empty() {
371 return Err(syn::Error::new_spanned(
373 &func.sig,
374 "#[capsec::requires] on a function with concrete parameter types requires \
375 `on = <param>` to identify the capability parameter.\n\
376 Example: #[capsec::requires(fs::read, on = ctx)]",
377 ));
378 } else {
379 None
380 };
381
382 let func_vis = &func.vis;
383 let func_sig = modified_sig.as_ref().unwrap_or(&func.sig);
384 let func_block = &func.block;
385 let func_attrs = &func.attrs;
386
387 Ok(quote! {
388 #(#func_attrs)*
389 #[doc = #doc_string]
390 #func_vis #func_sig {
391 #assertion
392 #func_block
393 }
394 })
395}
396
397fn contains_impl_trait(ty: &Type) -> bool {
398 match ty {
399 Type::ImplTrait(_) => true,
400 Type::Reference(r) => contains_impl_trait(&r.elem),
401 Type::Paren(p) => contains_impl_trait(&p.elem),
402 _ => false,
403 }
404}
405
406fn find_param_type(sig: &syn::Signature, name: &syn::Ident) -> syn::Result<Type> {
407 for arg in &sig.inputs {
408 if let FnArg::Typed(pat_type) = arg
409 && let Pat::Ident(pi) = &*pat_type.pat
410 && pi.ident == *name
411 {
412 return Ok((*pat_type.ty).clone());
413 }
414 }
415 Err(syn::Error::new_spanned(
416 name,
417 format!("parameter '{}' not found in function signature", name),
418 ))
419}
420
421fn unwrap_references(ty: &Type) -> &Type {
422 match ty {
423 Type::Reference(r) => unwrap_references(&r.elem),
424 Type::Paren(p) => unwrap_references(&p.elem),
425 _ => ty,
426 }
427}
428
429#[proc_macro_attribute]
457pub fn deny(attr: TokenStream, item: TokenStream) -> TokenStream {
458 let denied = parse_macro_input!(attr with Punctuated::<Meta, Token![,]>::parse_terminated);
459
460 let item_clone: proc_macro2::TokenStream = item.clone().into();
461 let func = match syn::parse::<ItemFn>(item) {
462 Ok(f) => f,
463 Err(e) => {
464 let err = e.into_compile_error();
465 return quote! { #err #item_clone }.into();
466 }
467 };
468
469 let deny_names: Vec<String> = denied
470 .iter()
471 .map(|meta| {
472 meta.path()
473 .get_ident()
474 .map(|i| i.to_string())
475 .unwrap_or_default()
476 })
477 .collect();
478
479 let doc_string = format!("capsec::deny({})", deny_names.join(", "));
480
481 let func_vis = &func.vis;
482 let func_sig = &func.sig;
483 let func_block = &func.block;
484 let func_attrs = &func.attrs;
485
486 let expanded = quote! {
487 #(#func_attrs)*
488 #[doc = #doc_string]
489 #func_vis #func_sig
490 #func_block
491 };
492
493 expanded.into()
494}
495
496#[proc_macro_attribute]
521pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
522 let func = parse_macro_input!(item as ItemFn);
523
524 match main_inner(&func) {
525 Ok(tokens) => tokens.into(),
526 Err(e) => e.into_compile_error().into(),
527 }
528}
529
530fn main_inner(func: &ItemFn) -> syn::Result<proc_macro2::TokenStream> {
531 if func.sig.inputs.is_empty() {
532 if func.sig.asyncness.is_some() {
533 return Err(syn::Error::new_spanned(
534 &func.sig,
535 "#[capsec::main] found no CapRoot parameter. If combining with #[tokio::main], \
536 place #[capsec::main] above #[tokio::main]:\n\n \
537 #[capsec::main]\n \
538 #[tokio::main]\n \
539 async fn main(root: CapRoot) { ... }",
540 ));
541 }
542 return Err(syn::Error::new_spanned(
543 &func.sig,
544 "#[capsec::main] expected first parameter of type CapRoot",
545 ));
546 }
547
548 let first_arg = &func.sig.inputs[0];
550 let (param_name, param_type) = match first_arg {
551 FnArg::Typed(pat_type) => {
552 let name = if let Pat::Ident(pi) = &*pat_type.pat {
553 pi.ident.clone()
554 } else {
555 return Err(syn::Error::new_spanned(
556 &pat_type.pat,
557 "#[capsec::main] expected a simple identifier for the CapRoot parameter",
558 ));
559 };
560 (name, &*pat_type.ty)
561 }
562 FnArg::Receiver(r) => {
563 return Err(syn::Error::new_spanned(
564 r,
565 "#[capsec::main] cannot be used on methods with self",
566 ));
567 }
568 };
569
570 let type_str = quote!(#param_type).to_string().replace(' ', "");
572 if type_str != "CapRoot" && type_str != "capsec::CapRoot" {
573 return Err(syn::Error::new_spanned(
574 param_type,
575 "first parameter must be CapRoot",
576 ));
577 }
578
579 let remaining_params: Vec<_> = func.sig.inputs.iter().skip(1).collect();
581 let func_attrs = &func.attrs;
582 let func_vis = &func.vis;
583 let func_name = &func.sig.ident;
584 let func_generics = &func.sig.generics;
585 let func_output = &func.sig.output;
586 let func_asyncness = &func.sig.asyncness;
587 let func_block = &func.block;
588
589 Ok(quote! {
590 #(#func_attrs)*
591 #func_vis #func_asyncness fn #func_name #func_generics(#(#remaining_params),*) #func_output {
592 let #param_name = capsec::root();
593 #func_block
594 }
595 })
596}
597
598#[proc_macro_attribute]
622pub fn context(attr: TokenStream, item: TokenStream) -> TokenStream {
623 let attr2: proc_macro2::TokenStream = attr.into();
624 let input = parse_macro_input!(item as ItemStruct);
625
626 match context_inner(attr2, &input) {
627 Ok(tokens) => tokens.into(),
628 Err(e) => e.into_compile_error().into(),
629 }
630}
631
632fn context_inner(
633 attr: proc_macro2::TokenStream,
634 input: &ItemStruct,
635) -> syn::Result<proc_macro2::TokenStream> {
636 let attr_str = attr.to_string();
638 let is_send = match attr_str.trim() {
639 "" => false,
640 "send" => true,
641 other => {
642 return Err(syn::Error::new_spanned(
643 &attr,
644 format!("unexpected attribute '{}', expected empty or 'send'", other),
645 ));
646 }
647 };
648
649 if !input.generics.params.is_empty() {
651 return Err(syn::Error::new_spanned(
652 &input.generics,
653 "#[capsec::context] does not support generic structs",
654 ));
655 }
656
657 let fields = match &input.fields {
659 syn::Fields::Named(f) => f,
660 _ => {
661 return Err(syn::Error::new_spanned(
662 input,
663 "#[capsec::context] requires a struct with named fields",
664 ));
665 }
666 };
667
668 let mut field_infos: Vec<(syn::Ident, proc_macro2::TokenStream)> = Vec::new();
671 let mut seen_perms: std::collections::HashSet<String> = std::collections::HashSet::new();
672
673 for field in &fields.named {
674 let field_name = field.ident.as_ref().unwrap().clone();
675 let ty = &field.ty;
676
677 if let Type::Tuple(_) = ty {
679 return Err(syn::Error::new_spanned(
680 ty,
681 "tuple permission types are not supported in context structs — use separate fields instead",
682 ));
683 }
684
685 let (perm_key, perm_tokens) = match ty {
687 Type::Path(tp) => {
688 if let Some(seg) = tp.path.segments.last() {
689 let ident_str = seg.ident.to_string();
690 if KNOWN_PERMISSIONS.contains(&ident_str.as_str()) {
692 let ident = &seg.ident;
693 (ident_str, quote! { capsec_core::permission::#ident })
694 } else {
695 (ident_str, quote! { #tp })
697 }
698 } else {
699 return Err(syn::Error::new_spanned(
700 ty,
701 format!("field '{}' has an empty type path", field_name,),
702 ));
703 }
704 }
705 _ => {
706 return Err(syn::Error::new_spanned(
707 ty,
708 format!(
709 "field '{}' has type '{}', which is not a valid permission type",
710 field_name,
711 quote!(#ty),
712 ),
713 ));
714 }
715 };
716
717 if !seen_perms.insert(perm_key.clone()) {
719 return Err(syn::Error::new_spanned(
720 ty,
721 format!(
722 "duplicate permission type '{}' — each permission can only appear once in a context struct",
723 perm_key
724 ),
725 ));
726 }
727
728 field_infos.push((field_name, perm_tokens));
729 }
730
731 let struct_name = &input.ident;
732 let struct_vis = &input.vis;
733 let struct_attrs = &input.attrs;
734
735 let struct_fields: Vec<_> = field_infos
737 .iter()
738 .map(|(name, perm)| {
739 if is_send {
740 quote! { #name: capsec_core::cap::SendCap<#perm> }
741 } else {
742 quote! { #name: capsec_core::cap::Cap<#perm> }
743 }
744 })
745 .collect();
746
747 let constructor_fields: Vec<_> = field_infos
749 .iter()
750 .map(|(name, perm)| {
751 if is_send {
752 quote! { #name: root.grant::<#perm>().make_send() }
753 } else {
754 quote! { #name: root.grant::<#perm>() }
755 }
756 })
757 .collect();
758
759 let has_impls: Vec<_> = field_infos
761 .iter()
762 .map(|(name, perm)| {
763 quote! {
764 impl capsec_core::has::Has<#perm> for #struct_name {
765 fn cap_ref(&self) -> capsec_core::cap::Cap<#perm> {
766 self.#name.cap_ref()
767 }
768 }
769 }
770 })
771 .collect();
772
773 let cap_provider_impls: Vec<_> = field_infos
775 .iter()
776 .map(|(_name, perm)| {
777 quote! {
778 impl capsec_core::cap_provider::CapProvider<#perm> for #struct_name {
779 fn provide_cap(&self, _target: &str) -> Result<capsec_core::cap::Cap<#perm>, capsec_core::error::CapSecError> {
780 Ok(<Self as capsec_core::has::Has<#perm>>::cap_ref(self))
781 }
782 }
783 }
784 })
785 .collect();
786
787 Ok(quote! {
788 #(#struct_attrs)*
789 #struct_vis struct #struct_name {
790 #(#struct_fields,)*
791 }
792
793 impl #struct_name {
794 pub fn new(root: &capsec_core::root::CapRoot) -> Self {
796 Self {
797 #(#constructor_fields,)*
798 }
799 }
800 }
801
802 #(#has_impls)*
803 #(#cap_provider_impls)*
804 })
805}