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()
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()
136 }
137 }
138 }
139 })
140 .collect();
141
142 Ok(quote! {
143 #(#struct_attrs)*
144 #struct_vis struct #struct_name;
145
146 #permission_impl
147 #(#subsumes_impls)*
148 })
149}
150
151fn parse_subsumes(attr: proc_macro2::TokenStream) -> syn::Result<Vec<syn::Path>> {
153 if attr.is_empty() {
154 return Ok(Vec::new());
155 }
156
157 let meta: Meta = syn::parse2(attr)?;
159 match &meta {
160 Meta::NameValue(nv) if nv.path.is_ident("subsumes") => {
161 if let syn::Expr::Array(arr) = &nv.value {
163 let mut paths = Vec::new();
164 let mut seen = std::collections::HashSet::new();
165 for elem in &arr.elems {
166 if let syn::Expr::Path(ep) = elem {
167 let path_str = quote::quote!(#ep).to_string();
168 if !seen.insert(path_str.clone()) {
169 return Err(syn::Error::new_spanned(
170 elem,
171 format!("duplicate subsumes entry: {}", path_str),
172 ));
173 }
174 paths.push(ep.path.clone());
175 } else {
176 return Err(syn::Error::new_spanned(
177 elem,
178 "expected a permission type path in subsumes list",
179 ));
180 }
181 }
182 Ok(paths)
183 } else {
184 Err(syn::Error::new_spanned(
185 &nv.value,
186 "expected an array [A, B, C] for subsumes",
187 ))
188 }
189 }
190 _ => Err(syn::Error::new_spanned(
191 &meta,
192 "expected `subsumes = [...]`",
193 )),
194 }
195}
196
197#[proc_macro_attribute]
237pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream {
238 let attr2: proc_macro2::TokenStream = attr.into();
239 let func = parse_macro_input!(item as ItemFn);
240
241 match requires_inner(attr2, &func) {
242 Ok(tokens) => tokens.into(),
243 Err(e) => e.into_compile_error().into(),
244 }
245}
246
247fn requires_inner(
248 attr: proc_macro2::TokenStream,
249 func: &ItemFn,
250) -> syn::Result<proc_macro2::TokenStream> {
251 let metas: Punctuated<Meta, Token![,]> =
252 syn::parse::Parser::parse2(Punctuated::parse_terminated, attr)?;
253
254 let mut on_param: Option<syn::Ident> = None;
256 let mut perm_metas: Vec<&Meta> = Vec::new();
257
258 for meta in &metas {
259 if let Meta::NameValue(nv) = meta
260 && nv.path.is_ident("on")
261 {
262 if let syn::Expr::Path(ep) = &nv.value
263 && let Some(ident) = ep.path.get_ident()
264 {
265 on_param = Some(ident.clone());
266 continue;
267 }
268 return Err(syn::Error::new_spanned(&nv.value, "expected an identifier"));
269 }
270 perm_metas.push(meta);
271 }
272
273 let mut cap_types = Vec::new();
275 for meta in &perm_metas {
276 cap_types.push(resolve::meta_to_permission_type(meta)?);
277 }
278
279 let doc_string = format!(
281 "capsec::requires({})",
282 cap_types
283 .iter()
284 .map(|c| quote!(#c).to_string())
285 .collect::<Vec<_>>()
286 .join(", ")
287 );
288
289 let has_impl_bounds = func.sig.inputs.iter().any(|arg| {
291 if let FnArg::Typed(pat_type) = arg {
292 contains_impl_trait(&pat_type.ty)
293 } else {
294 false
295 }
296 });
297
298 let assertion = if let Some(ref param_name) = on_param {
300 let param_type = find_param_type(&func.sig, param_name)?;
302 let inner_type = unwrap_references(¶m_type);
303
304 let assert_fns: Vec<_> = cap_types
305 .iter()
306 .enumerate()
307 .map(|(i, perm_ty)| {
308 let fn_name = format_ident!("_assert_has_{}", i);
309 quote! {
310 fn #fn_name<T: capsec_core::has::Has<#perm_ty>>() {}
311 }
312 })
313 .collect();
314
315 let assert_calls: Vec<_> = (0..cap_types.len())
316 .map(|i| {
317 let fn_name = format_ident!("_assert_has_{}", i);
318 quote! { #fn_name::<#inner_type>(); }
319 })
320 .collect();
321
322 Some(quote! {
323 const _: () = {
324 #(#assert_fns)*
325 fn _check() {
326 #(#assert_calls)*
327 }
328 };
329 })
330 } else if !has_impl_bounds && !func.sig.inputs.is_empty() && !cap_types.is_empty() {
331 return Err(syn::Error::new_spanned(
333 &func.sig,
334 "#[capsec::requires] on a function with concrete parameter types requires \
335 `on = <param>` to identify the capability parameter.\n\
336 Example: #[capsec::requires(fs::read, on = ctx)]",
337 ));
338 } else {
339 None
340 };
341
342 let func_vis = &func.vis;
343 let func_sig = &func.sig;
344 let func_block = &func.block;
345 let func_attrs = &func.attrs;
346
347 Ok(quote! {
348 #(#func_attrs)*
349 #[doc = #doc_string]
350 #func_vis #func_sig {
351 #assertion
352 #func_block
353 }
354 })
355}
356
357fn contains_impl_trait(ty: &Type) -> bool {
358 match ty {
359 Type::ImplTrait(_) => true,
360 Type::Reference(r) => contains_impl_trait(&r.elem),
361 Type::Paren(p) => contains_impl_trait(&p.elem),
362 _ => false,
363 }
364}
365
366fn find_param_type(sig: &syn::Signature, name: &syn::Ident) -> syn::Result<Type> {
367 for arg in &sig.inputs {
368 if let FnArg::Typed(pat_type) = arg
369 && let Pat::Ident(pi) = &*pat_type.pat
370 && pi.ident == *name
371 {
372 return Ok((*pat_type.ty).clone());
373 }
374 }
375 Err(syn::Error::new_spanned(
376 name,
377 format!("parameter '{}' not found in function signature", name),
378 ))
379}
380
381fn unwrap_references(ty: &Type) -> &Type {
382 match ty {
383 Type::Reference(r) => unwrap_references(&r.elem),
384 Type::Paren(p) => unwrap_references(&p.elem),
385 _ => ty,
386 }
387}
388
389#[proc_macro_attribute]
417pub fn deny(attr: TokenStream, item: TokenStream) -> TokenStream {
418 let denied = parse_macro_input!(attr with Punctuated::<Meta, Token![,]>::parse_terminated);
419
420 let item_clone: proc_macro2::TokenStream = item.clone().into();
421 let func = match syn::parse::<ItemFn>(item) {
422 Ok(f) => f,
423 Err(e) => {
424 let err = e.into_compile_error();
425 return quote! { #err #item_clone }.into();
426 }
427 };
428
429 let deny_names: Vec<String> = denied
430 .iter()
431 .map(|meta| {
432 meta.path()
433 .get_ident()
434 .map(|i| i.to_string())
435 .unwrap_or_default()
436 })
437 .collect();
438
439 let doc_string = format!("capsec::deny({})", deny_names.join(", "));
440
441 let func_vis = &func.vis;
442 let func_sig = &func.sig;
443 let func_block = &func.block;
444 let func_attrs = &func.attrs;
445
446 let expanded = quote! {
447 #(#func_attrs)*
448 #[doc = #doc_string]
449 #func_vis #func_sig
450 #func_block
451 };
452
453 expanded.into()
454}
455
456#[proc_macro_attribute]
481pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
482 let func = parse_macro_input!(item as ItemFn);
483
484 match main_inner(&func) {
485 Ok(tokens) => tokens.into(),
486 Err(e) => e.into_compile_error().into(),
487 }
488}
489
490fn main_inner(func: &ItemFn) -> syn::Result<proc_macro2::TokenStream> {
491 if func.sig.inputs.is_empty() {
492 if func.sig.asyncness.is_some() {
493 return Err(syn::Error::new_spanned(
494 &func.sig,
495 "#[capsec::main] found no CapRoot parameter. If combining with #[tokio::main], \
496 place #[capsec::main] above #[tokio::main]:\n\n \
497 #[capsec::main]\n \
498 #[tokio::main]\n \
499 async fn main(root: CapRoot) { ... }",
500 ));
501 }
502 return Err(syn::Error::new_spanned(
503 &func.sig,
504 "#[capsec::main] expected first parameter of type CapRoot",
505 ));
506 }
507
508 let first_arg = &func.sig.inputs[0];
510 let (param_name, param_type) = match first_arg {
511 FnArg::Typed(pat_type) => {
512 let name = if let Pat::Ident(pi) = &*pat_type.pat {
513 pi.ident.clone()
514 } else {
515 return Err(syn::Error::new_spanned(
516 &pat_type.pat,
517 "#[capsec::main] expected a simple identifier for the CapRoot parameter",
518 ));
519 };
520 (name, &*pat_type.ty)
521 }
522 FnArg::Receiver(r) => {
523 return Err(syn::Error::new_spanned(
524 r,
525 "#[capsec::main] cannot be used on methods with self",
526 ));
527 }
528 };
529
530 let type_str = quote!(#param_type).to_string().replace(' ', "");
532 if type_str != "CapRoot" && type_str != "capsec::CapRoot" {
533 return Err(syn::Error::new_spanned(
534 param_type,
535 "first parameter must be CapRoot",
536 ));
537 }
538
539 let remaining_params: Vec<_> = func.sig.inputs.iter().skip(1).collect();
541 let func_attrs = &func.attrs;
542 let func_vis = &func.vis;
543 let func_name = &func.sig.ident;
544 let func_generics = &func.sig.generics;
545 let func_output = &func.sig.output;
546 let func_asyncness = &func.sig.asyncness;
547 let func_block = &func.block;
548
549 Ok(quote! {
550 #(#func_attrs)*
551 #func_vis #func_asyncness fn #func_name #func_generics(#(#remaining_params),*) #func_output {
552 let #param_name = capsec::root();
553 #func_block
554 }
555 })
556}
557
558#[proc_macro_attribute]
582pub fn context(attr: TokenStream, item: TokenStream) -> TokenStream {
583 let attr2: proc_macro2::TokenStream = attr.into();
584 let input = parse_macro_input!(item as ItemStruct);
585
586 match context_inner(attr2, &input) {
587 Ok(tokens) => tokens.into(),
588 Err(e) => e.into_compile_error().into(),
589 }
590}
591
592fn context_inner(
593 attr: proc_macro2::TokenStream,
594 input: &ItemStruct,
595) -> syn::Result<proc_macro2::TokenStream> {
596 let attr_str = attr.to_string();
598 let is_send = match attr_str.trim() {
599 "" => false,
600 "send" => true,
601 other => {
602 return Err(syn::Error::new_spanned(
603 &attr,
604 format!("unexpected attribute '{}', expected empty or 'send'", other),
605 ));
606 }
607 };
608
609 if !input.generics.params.is_empty() {
611 return Err(syn::Error::new_spanned(
612 &input.generics,
613 "#[capsec::context] does not support generic structs",
614 ));
615 }
616
617 let fields = match &input.fields {
619 syn::Fields::Named(f) => f,
620 _ => {
621 return Err(syn::Error::new_spanned(
622 input,
623 "#[capsec::context] requires a struct with named fields",
624 ));
625 }
626 };
627
628 let mut field_infos: Vec<(syn::Ident, proc_macro2::TokenStream)> = Vec::new();
631 let mut seen_perms: std::collections::HashSet<String> = std::collections::HashSet::new();
632
633 for field in &fields.named {
634 let field_name = field.ident.as_ref().unwrap().clone();
635 let ty = &field.ty;
636
637 if let Type::Tuple(_) = ty {
639 return Err(syn::Error::new_spanned(
640 ty,
641 "tuple permission types are not supported in context structs — use separate fields instead",
642 ));
643 }
644
645 let (perm_key, perm_tokens) = match ty {
647 Type::Path(tp) => {
648 if let Some(seg) = tp.path.segments.last() {
649 let ident_str = seg.ident.to_string();
650 if KNOWN_PERMISSIONS.contains(&ident_str.as_str()) {
652 let ident = &seg.ident;
653 (ident_str, quote! { capsec_core::permission::#ident })
654 } else {
655 (ident_str, quote! { #tp })
657 }
658 } else {
659 return Err(syn::Error::new_spanned(
660 ty,
661 format!("field '{}' has an empty type path", field_name,),
662 ));
663 }
664 }
665 _ => {
666 return Err(syn::Error::new_spanned(
667 ty,
668 format!(
669 "field '{}' has type '{}', which is not a valid permission type",
670 field_name,
671 quote!(#ty),
672 ),
673 ));
674 }
675 };
676
677 if !seen_perms.insert(perm_key.clone()) {
679 return Err(syn::Error::new_spanned(
680 ty,
681 format!(
682 "duplicate permission type '{}' — each permission can only appear once in a context struct",
683 perm_key
684 ),
685 ));
686 }
687
688 field_infos.push((field_name, perm_tokens));
689 }
690
691 let struct_name = &input.ident;
692 let struct_vis = &input.vis;
693 let struct_attrs = &input.attrs;
694
695 let struct_fields: Vec<_> = field_infos
697 .iter()
698 .map(|(name, perm)| {
699 if is_send {
700 quote! { #name: capsec_core::cap::SendCap<#perm> }
701 } else {
702 quote! { #name: capsec_core::cap::Cap<#perm> }
703 }
704 })
705 .collect();
706
707 let constructor_fields: Vec<_> = field_infos
709 .iter()
710 .map(|(name, perm)| {
711 if is_send {
712 quote! { #name: root.grant::<#perm>().make_send() }
713 } else {
714 quote! { #name: root.grant::<#perm>() }
715 }
716 })
717 .collect();
718
719 let has_impls: Vec<_> = field_infos
721 .iter()
722 .map(|(name, perm)| {
723 quote! {
724 impl capsec_core::has::Has<#perm> for #struct_name {
725 fn cap_ref(&self) -> capsec_core::cap::Cap<#perm> {
726 self.#name.cap_ref()
727 }
728 }
729 }
730 })
731 .collect();
732
733 Ok(quote! {
734 #(#struct_attrs)*
735 #struct_vis struct #struct_name {
736 #(#struct_fields,)*
737 }
738
739 impl #struct_name {
740 pub fn new(root: &capsec_core::root::CapRoot) -> Self {
742 Self {
743 #(#constructor_fields,)*
744 }
745 }
746 }
747
748 #(#has_impls)*
749 })
750}