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 mut modified_sig: Option<syn::Signature> = None;
300 let assertion = if let Some(ref param_name) = on_param {
301 let param_type = find_param_type(&func.sig, param_name)?;
303 let inner_type = unwrap_references(¶m_type);
304
305 let generic_ident = func.sig.generics.type_params().find_map(|tp| {
307 if let Type::Path(ref type_path) = *inner_type
308 && type_path.path.is_ident(&tp.ident)
309 {
310 Some(tp.ident.clone())
311 } else {
312 None
313 }
314 });
315
316 if let Some(ref gen_ident) = generic_ident {
317 let mut sig = func.sig.clone();
319 let where_clause = sig.generics.make_where_clause();
320 let bounds: Vec<_> = cap_types
321 .iter()
322 .map(|perm_ty| quote! { capsec_core::has::Has<#perm_ty> })
323 .collect();
324 where_clause
325 .predicates
326 .push(syn::parse2(quote! { #gen_ident: #(#bounds)+* }).unwrap());
327 modified_sig = Some(sig);
328 None } else {
330 let assert_fns: Vec<_> = cap_types
332 .iter()
333 .enumerate()
334 .map(|(i, perm_ty)| {
335 let fn_name = format_ident!("_assert_has_{}", i);
336 quote! {
337 fn #fn_name<T: capsec_core::has::Has<#perm_ty>>() {}
338 }
339 })
340 .collect();
341
342 let assert_calls: Vec<_> = (0..cap_types.len())
343 .map(|i| {
344 let fn_name = format_ident!("_assert_has_{}", i);
345 quote! { #fn_name::<#inner_type>(); }
346 })
347 .collect();
348
349 Some(quote! {
350 const _: () = {
351 #(#assert_fns)*
352 fn _check() {
353 #(#assert_calls)*
354 }
355 };
356 })
357 }
358 } else if !has_impl_bounds && !func.sig.inputs.is_empty() && !cap_types.is_empty() {
359 return Err(syn::Error::new_spanned(
361 &func.sig,
362 "#[capsec::requires] on a function with concrete parameter types requires \
363 `on = <param>` to identify the capability parameter.\n\
364 Example: #[capsec::requires(fs::read, on = ctx)]",
365 ));
366 } else {
367 None
368 };
369
370 let func_vis = &func.vis;
371 let func_sig = modified_sig.as_ref().unwrap_or(&func.sig);
372 let func_block = &func.block;
373 let func_attrs = &func.attrs;
374
375 Ok(quote! {
376 #(#func_attrs)*
377 #[doc = #doc_string]
378 #func_vis #func_sig {
379 #assertion
380 #func_block
381 }
382 })
383}
384
385fn contains_impl_trait(ty: &Type) -> bool {
386 match ty {
387 Type::ImplTrait(_) => true,
388 Type::Reference(r) => contains_impl_trait(&r.elem),
389 Type::Paren(p) => contains_impl_trait(&p.elem),
390 _ => false,
391 }
392}
393
394fn find_param_type(sig: &syn::Signature, name: &syn::Ident) -> syn::Result<Type> {
395 for arg in &sig.inputs {
396 if let FnArg::Typed(pat_type) = arg
397 && let Pat::Ident(pi) = &*pat_type.pat
398 && pi.ident == *name
399 {
400 return Ok((*pat_type.ty).clone());
401 }
402 }
403 Err(syn::Error::new_spanned(
404 name,
405 format!("parameter '{}' not found in function signature", name),
406 ))
407}
408
409fn unwrap_references(ty: &Type) -> &Type {
410 match ty {
411 Type::Reference(r) => unwrap_references(&r.elem),
412 Type::Paren(p) => unwrap_references(&p.elem),
413 _ => ty,
414 }
415}
416
417#[proc_macro_attribute]
445pub fn deny(attr: TokenStream, item: TokenStream) -> TokenStream {
446 let denied = parse_macro_input!(attr with Punctuated::<Meta, Token![,]>::parse_terminated);
447
448 let item_clone: proc_macro2::TokenStream = item.clone().into();
449 let func = match syn::parse::<ItemFn>(item) {
450 Ok(f) => f,
451 Err(e) => {
452 let err = e.into_compile_error();
453 return quote! { #err #item_clone }.into();
454 }
455 };
456
457 let deny_names: Vec<String> = denied
458 .iter()
459 .map(|meta| {
460 meta.path()
461 .get_ident()
462 .map(|i| i.to_string())
463 .unwrap_or_default()
464 })
465 .collect();
466
467 let doc_string = format!("capsec::deny({})", deny_names.join(", "));
468
469 let func_vis = &func.vis;
470 let func_sig = &func.sig;
471 let func_block = &func.block;
472 let func_attrs = &func.attrs;
473
474 let expanded = quote! {
475 #(#func_attrs)*
476 #[doc = #doc_string]
477 #func_vis #func_sig
478 #func_block
479 };
480
481 expanded.into()
482}
483
484#[proc_macro_attribute]
509pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
510 let func = parse_macro_input!(item as ItemFn);
511
512 match main_inner(&func) {
513 Ok(tokens) => tokens.into(),
514 Err(e) => e.into_compile_error().into(),
515 }
516}
517
518fn main_inner(func: &ItemFn) -> syn::Result<proc_macro2::TokenStream> {
519 if func.sig.inputs.is_empty() {
520 if func.sig.asyncness.is_some() {
521 return Err(syn::Error::new_spanned(
522 &func.sig,
523 "#[capsec::main] found no CapRoot parameter. If combining with #[tokio::main], \
524 place #[capsec::main] above #[tokio::main]:\n\n \
525 #[capsec::main]\n \
526 #[tokio::main]\n \
527 async fn main(root: CapRoot) { ... }",
528 ));
529 }
530 return Err(syn::Error::new_spanned(
531 &func.sig,
532 "#[capsec::main] expected first parameter of type CapRoot",
533 ));
534 }
535
536 let first_arg = &func.sig.inputs[0];
538 let (param_name, param_type) = match first_arg {
539 FnArg::Typed(pat_type) => {
540 let name = if let Pat::Ident(pi) = &*pat_type.pat {
541 pi.ident.clone()
542 } else {
543 return Err(syn::Error::new_spanned(
544 &pat_type.pat,
545 "#[capsec::main] expected a simple identifier for the CapRoot parameter",
546 ));
547 };
548 (name, &*pat_type.ty)
549 }
550 FnArg::Receiver(r) => {
551 return Err(syn::Error::new_spanned(
552 r,
553 "#[capsec::main] cannot be used on methods with self",
554 ));
555 }
556 };
557
558 let type_str = quote!(#param_type).to_string().replace(' ', "");
560 if type_str != "CapRoot" && type_str != "capsec::CapRoot" {
561 return Err(syn::Error::new_spanned(
562 param_type,
563 "first parameter must be CapRoot",
564 ));
565 }
566
567 let remaining_params: Vec<_> = func.sig.inputs.iter().skip(1).collect();
569 let func_attrs = &func.attrs;
570 let func_vis = &func.vis;
571 let func_name = &func.sig.ident;
572 let func_generics = &func.sig.generics;
573 let func_output = &func.sig.output;
574 let func_asyncness = &func.sig.asyncness;
575 let func_block = &func.block;
576
577 Ok(quote! {
578 #(#func_attrs)*
579 #func_vis #func_asyncness fn #func_name #func_generics(#(#remaining_params),*) #func_output {
580 let #param_name = capsec::root();
581 #func_block
582 }
583 })
584}
585
586#[proc_macro_attribute]
610pub fn context(attr: TokenStream, item: TokenStream) -> TokenStream {
611 let attr2: proc_macro2::TokenStream = attr.into();
612 let input = parse_macro_input!(item as ItemStruct);
613
614 match context_inner(attr2, &input) {
615 Ok(tokens) => tokens.into(),
616 Err(e) => e.into_compile_error().into(),
617 }
618}
619
620fn context_inner(
621 attr: proc_macro2::TokenStream,
622 input: &ItemStruct,
623) -> syn::Result<proc_macro2::TokenStream> {
624 let attr_str = attr.to_string();
626 let is_send = match attr_str.trim() {
627 "" => false,
628 "send" => true,
629 other => {
630 return Err(syn::Error::new_spanned(
631 &attr,
632 format!("unexpected attribute '{}', expected empty or 'send'", other),
633 ));
634 }
635 };
636
637 if !input.generics.params.is_empty() {
639 return Err(syn::Error::new_spanned(
640 &input.generics,
641 "#[capsec::context] does not support generic structs",
642 ));
643 }
644
645 let fields = match &input.fields {
647 syn::Fields::Named(f) => f,
648 _ => {
649 return Err(syn::Error::new_spanned(
650 input,
651 "#[capsec::context] requires a struct with named fields",
652 ));
653 }
654 };
655
656 let mut field_infos: Vec<(syn::Ident, proc_macro2::TokenStream)> = Vec::new();
659 let mut seen_perms: std::collections::HashSet<String> = std::collections::HashSet::new();
660
661 for field in &fields.named {
662 let field_name = field.ident.as_ref().unwrap().clone();
663 let ty = &field.ty;
664
665 if let Type::Tuple(_) = ty {
667 return Err(syn::Error::new_spanned(
668 ty,
669 "tuple permission types are not supported in context structs — use separate fields instead",
670 ));
671 }
672
673 let (perm_key, perm_tokens) = match ty {
675 Type::Path(tp) => {
676 if let Some(seg) = tp.path.segments.last() {
677 let ident_str = seg.ident.to_string();
678 if KNOWN_PERMISSIONS.contains(&ident_str.as_str()) {
680 let ident = &seg.ident;
681 (ident_str, quote! { capsec_core::permission::#ident })
682 } else {
683 (ident_str, quote! { #tp })
685 }
686 } else {
687 return Err(syn::Error::new_spanned(
688 ty,
689 format!("field '{}' has an empty type path", field_name,),
690 ));
691 }
692 }
693 _ => {
694 return Err(syn::Error::new_spanned(
695 ty,
696 format!(
697 "field '{}' has type '{}', which is not a valid permission type",
698 field_name,
699 quote!(#ty),
700 ),
701 ));
702 }
703 };
704
705 if !seen_perms.insert(perm_key.clone()) {
707 return Err(syn::Error::new_spanned(
708 ty,
709 format!(
710 "duplicate permission type '{}' — each permission can only appear once in a context struct",
711 perm_key
712 ),
713 ));
714 }
715
716 field_infos.push((field_name, perm_tokens));
717 }
718
719 let struct_name = &input.ident;
720 let struct_vis = &input.vis;
721 let struct_attrs = &input.attrs;
722
723 let struct_fields: Vec<_> = field_infos
725 .iter()
726 .map(|(name, perm)| {
727 if is_send {
728 quote! { #name: capsec_core::cap::SendCap<#perm> }
729 } else {
730 quote! { #name: capsec_core::cap::Cap<#perm> }
731 }
732 })
733 .collect();
734
735 let constructor_fields: Vec<_> = field_infos
737 .iter()
738 .map(|(name, perm)| {
739 if is_send {
740 quote! { #name: root.grant::<#perm>().make_send() }
741 } else {
742 quote! { #name: root.grant::<#perm>() }
743 }
744 })
745 .collect();
746
747 let has_impls: Vec<_> = field_infos
749 .iter()
750 .map(|(name, perm)| {
751 quote! {
752 impl capsec_core::has::Has<#perm> for #struct_name {
753 fn cap_ref(&self) -> capsec_core::cap::Cap<#perm> {
754 self.#name.cap_ref()
755 }
756 }
757 }
758 })
759 .collect();
760
761 Ok(quote! {
762 #(#struct_attrs)*
763 #struct_vis struct #struct_name {
764 #(#struct_fields,)*
765 }
766
767 impl #struct_name {
768 pub fn new(root: &capsec_core::root::CapRoot) -> Self {
770 Self {
771 #(#constructor_fields,)*
772 }
773 }
774 }
775
776 #(#has_impls)*
777 })
778}