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]
76pub fn requires(attr: TokenStream, item: TokenStream) -> TokenStream {
77 let attr2: proc_macro2::TokenStream = attr.into();
78 let func = parse_macro_input!(item as ItemFn);
79
80 match requires_inner(attr2, &func) {
81 Ok(tokens) => tokens.into(),
82 Err(e) => e.into_compile_error().into(),
83 }
84}
85
86fn requires_inner(
87 attr: proc_macro2::TokenStream,
88 func: &ItemFn,
89) -> syn::Result<proc_macro2::TokenStream> {
90 let metas: Punctuated<Meta, Token![,]> =
91 syn::parse::Parser::parse2(Punctuated::parse_terminated, attr)?;
92
93 let mut on_param: Option<syn::Ident> = None;
95 let mut perm_metas: Vec<&Meta> = Vec::new();
96
97 for meta in &metas {
98 if let Meta::NameValue(nv) = meta
99 && nv.path.is_ident("on")
100 {
101 if let syn::Expr::Path(ep) = &nv.value
102 && let Some(ident) = ep.path.get_ident()
103 {
104 on_param = Some(ident.clone());
105 continue;
106 }
107 return Err(syn::Error::new_spanned(&nv.value, "expected an identifier"));
108 }
109 perm_metas.push(meta);
110 }
111
112 let mut cap_types = Vec::new();
114 for meta in &perm_metas {
115 cap_types.push(resolve::meta_to_permission_type(meta)?);
116 }
117
118 let doc_string = format!(
120 "capsec::requires({})",
121 cap_types
122 .iter()
123 .map(|c| quote!(#c).to_string())
124 .collect::<Vec<_>>()
125 .join(", ")
126 );
127
128 let has_impl_bounds = func.sig.inputs.iter().any(|arg| {
130 if let FnArg::Typed(pat_type) = arg {
131 contains_impl_trait(&pat_type.ty)
132 } else {
133 false
134 }
135 });
136
137 let assertion = if let Some(ref param_name) = on_param {
139 let param_type = find_param_type(&func.sig, param_name)?;
141 let inner_type = unwrap_references(¶m_type);
142
143 let assert_fns: Vec<_> = cap_types
144 .iter()
145 .enumerate()
146 .map(|(i, perm_ty)| {
147 let fn_name = format_ident!("_assert_has_{}", i);
148 quote! {
149 fn #fn_name<T: capsec_core::has::Has<#perm_ty>>() {}
150 }
151 })
152 .collect();
153
154 let assert_calls: Vec<_> = (0..cap_types.len())
155 .map(|i| {
156 let fn_name = format_ident!("_assert_has_{}", i);
157 quote! { #fn_name::<#inner_type>(); }
158 })
159 .collect();
160
161 Some(quote! {
162 const _: () = {
163 #(#assert_fns)*
164 fn _check() {
165 #(#assert_calls)*
166 }
167 };
168 })
169 } else if !has_impl_bounds && !func.sig.inputs.is_empty() && !cap_types.is_empty() {
170 return Err(syn::Error::new_spanned(
172 &func.sig,
173 "#[capsec::requires] on a function with concrete parameter types requires \
174 `on = <param>` to identify the capability parameter.\n\
175 Example: #[capsec::requires(fs::read, on = ctx)]",
176 ));
177 } else {
178 None
179 };
180
181 let func_vis = &func.vis;
182 let func_sig = &func.sig;
183 let func_block = &func.block;
184 let func_attrs = &func.attrs;
185
186 Ok(quote! {
187 #(#func_attrs)*
188 #[doc = #doc_string]
189 #func_vis #func_sig {
190 #assertion
191 #func_block
192 }
193 })
194}
195
196fn contains_impl_trait(ty: &Type) -> bool {
197 match ty {
198 Type::ImplTrait(_) => true,
199 Type::Reference(r) => contains_impl_trait(&r.elem),
200 Type::Paren(p) => contains_impl_trait(&p.elem),
201 _ => false,
202 }
203}
204
205fn find_param_type(sig: &syn::Signature, name: &syn::Ident) -> syn::Result<Type> {
206 for arg in &sig.inputs {
207 if let FnArg::Typed(pat_type) = arg
208 && let Pat::Ident(pi) = &*pat_type.pat
209 && pi.ident == *name
210 {
211 return Ok((*pat_type.ty).clone());
212 }
213 }
214 Err(syn::Error::new_spanned(
215 name,
216 format!("parameter '{}' not found in function signature", name),
217 ))
218}
219
220fn unwrap_references(ty: &Type) -> &Type {
221 match ty {
222 Type::Reference(r) => unwrap_references(&r.elem),
223 Type::Paren(p) => unwrap_references(&p.elem),
224 _ => ty,
225 }
226}
227
228#[proc_macro_attribute]
256pub fn deny(attr: TokenStream, item: TokenStream) -> TokenStream {
257 let denied = parse_macro_input!(attr with Punctuated::<Meta, Token![,]>::parse_terminated);
258
259 let item_clone: proc_macro2::TokenStream = item.clone().into();
260 let func = match syn::parse::<ItemFn>(item) {
261 Ok(f) => f,
262 Err(e) => {
263 let err = e.into_compile_error();
264 return quote! { #err #item_clone }.into();
265 }
266 };
267
268 let deny_names: Vec<String> = denied
269 .iter()
270 .map(|meta| {
271 meta.path()
272 .get_ident()
273 .map(|i| i.to_string())
274 .unwrap_or_default()
275 })
276 .collect();
277
278 let doc_string = format!("capsec::deny({})", deny_names.join(", "));
279
280 let func_vis = &func.vis;
281 let func_sig = &func.sig;
282 let func_block = &func.block;
283 let func_attrs = &func.attrs;
284
285 let expanded = quote! {
286 #(#func_attrs)*
287 #[doc = #doc_string]
288 #func_vis #func_sig
289 #func_block
290 };
291
292 expanded.into()
293}
294
295#[proc_macro_attribute]
320pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
321 let func = parse_macro_input!(item as ItemFn);
322
323 match main_inner(&func) {
324 Ok(tokens) => tokens.into(),
325 Err(e) => e.into_compile_error().into(),
326 }
327}
328
329fn main_inner(func: &ItemFn) -> syn::Result<proc_macro2::TokenStream> {
330 if func.sig.inputs.is_empty() {
331 if func.sig.asyncness.is_some() {
332 return Err(syn::Error::new_spanned(
333 &func.sig,
334 "#[capsec::main] found no CapRoot parameter. If combining with #[tokio::main], \
335 place #[capsec::main] above #[tokio::main]:\n\n \
336 #[capsec::main]\n \
337 #[tokio::main]\n \
338 async fn main(root: CapRoot) { ... }",
339 ));
340 }
341 return Err(syn::Error::new_spanned(
342 &func.sig,
343 "#[capsec::main] expected first parameter of type CapRoot",
344 ));
345 }
346
347 let first_arg = &func.sig.inputs[0];
349 let (param_name, param_type) = match first_arg {
350 FnArg::Typed(pat_type) => {
351 let name = if let Pat::Ident(pi) = &*pat_type.pat {
352 pi.ident.clone()
353 } else {
354 return Err(syn::Error::new_spanned(
355 &pat_type.pat,
356 "#[capsec::main] expected a simple identifier for the CapRoot parameter",
357 ));
358 };
359 (name, &*pat_type.ty)
360 }
361 FnArg::Receiver(r) => {
362 return Err(syn::Error::new_spanned(
363 r,
364 "#[capsec::main] cannot be used on methods with self",
365 ));
366 }
367 };
368
369 let type_str = quote!(#param_type).to_string().replace(' ', "");
371 if type_str != "CapRoot" && type_str != "capsec::CapRoot" {
372 return Err(syn::Error::new_spanned(
373 param_type,
374 "first parameter must be CapRoot",
375 ));
376 }
377
378 let remaining_params: Vec<_> = func.sig.inputs.iter().skip(1).collect();
380 let func_attrs = &func.attrs;
381 let func_vis = &func.vis;
382 let func_name = &func.sig.ident;
383 let func_generics = &func.sig.generics;
384 let func_output = &func.sig.output;
385 let func_asyncness = &func.sig.asyncness;
386 let func_block = &func.block;
387
388 Ok(quote! {
389 #(#func_attrs)*
390 #func_vis #func_asyncness fn #func_name #func_generics(#(#remaining_params),*) #func_output {
391 let #param_name = capsec::root();
392 #func_block
393 }
394 })
395}
396
397#[proc_macro_attribute]
421pub fn context(attr: TokenStream, item: TokenStream) -> TokenStream {
422 let attr2: proc_macro2::TokenStream = attr.into();
423 let input = parse_macro_input!(item as ItemStruct);
424
425 match context_inner(attr2, &input) {
426 Ok(tokens) => tokens.into(),
427 Err(e) => e.into_compile_error().into(),
428 }
429}
430
431fn context_inner(
432 attr: proc_macro2::TokenStream,
433 input: &ItemStruct,
434) -> syn::Result<proc_macro2::TokenStream> {
435 let attr_str = attr.to_string();
437 let is_send = match attr_str.trim() {
438 "" => false,
439 "send" => true,
440 other => {
441 return Err(syn::Error::new_spanned(
442 &attr,
443 format!("unexpected attribute '{}', expected empty or 'send'", other),
444 ));
445 }
446 };
447
448 if !input.generics.params.is_empty() {
450 return Err(syn::Error::new_spanned(
451 &input.generics,
452 "#[capsec::context] does not support generic structs",
453 ));
454 }
455
456 let fields = match &input.fields {
458 syn::Fields::Named(f) => f,
459 _ => {
460 return Err(syn::Error::new_spanned(
461 input,
462 "#[capsec::context] requires a struct with named fields",
463 ));
464 }
465 };
466
467 let mut field_infos: Vec<(syn::Ident, syn::Ident)> = Vec::new(); let mut seen_perms: std::collections::HashSet<String> = std::collections::HashSet::new();
470
471 for field in &fields.named {
472 let field_name = field.ident.as_ref().unwrap().clone();
473 let ty = &field.ty;
474
475 if let Type::Tuple(_) = ty {
477 return Err(syn::Error::new_spanned(
478 ty,
479 "tuple permission types are not supported in context structs — use separate fields instead",
480 ));
481 }
482
483 let perm_ident = match ty {
485 Type::Path(tp) => {
486 if let Some(seg) = tp.path.segments.last() {
487 seg.ident.clone()
488 } else {
489 return Err(syn::Error::new_spanned(
490 ty,
491 format!(
492 "field '{}' has type '{}', which is not a capsec permission type. \
493 Expected one of: {}",
494 field_name,
495 quote!(#ty),
496 KNOWN_PERMISSIONS.join(", ")
497 ),
498 ));
499 }
500 }
501 _ => {
502 return Err(syn::Error::new_spanned(
503 ty,
504 format!(
505 "field '{}' has type '{}', which is not a capsec permission type. \
506 Expected one of: {}",
507 field_name,
508 quote!(#ty),
509 KNOWN_PERMISSIONS.join(", ")
510 ),
511 ));
512 }
513 };
514
515 let perm_str = perm_ident.to_string();
516
517 if !KNOWN_PERMISSIONS.contains(&perm_str.as_str()) {
519 return Err(syn::Error::new_spanned(
520 ty,
521 format!(
522 "field '{}' has type '{}', which is not a capsec permission type. \
523 Expected one of: {}",
524 field_name,
525 perm_str,
526 KNOWN_PERMISSIONS.join(", ")
527 ),
528 ));
529 }
530
531 if !seen_perms.insert(perm_str.clone()) {
533 return Err(syn::Error::new_spanned(
534 ty,
535 format!(
536 "duplicate permission type '{}' — each permission can only appear once in a context struct",
537 perm_str
538 ),
539 ));
540 }
541
542 field_infos.push((field_name, perm_ident));
543 }
544
545 let struct_name = &input.ident;
546 let struct_vis = &input.vis;
547 let struct_attrs = &input.attrs;
548
549 let struct_fields: Vec<_> = field_infos
551 .iter()
552 .map(|(name, perm)| {
553 if is_send {
554 quote! { #name: capsec_core::cap::SendCap<capsec_core::permission::#perm> }
555 } else {
556 quote! { #name: capsec_core::cap::Cap<capsec_core::permission::#perm> }
557 }
558 })
559 .collect();
560
561 let constructor_fields: Vec<_> = field_infos
563 .iter()
564 .map(|(name, perm)| {
565 if is_send {
566 quote! { #name: root.grant::<capsec_core::permission::#perm>().make_send() }
567 } else {
568 quote! { #name: root.grant::<capsec_core::permission::#perm>() }
569 }
570 })
571 .collect();
572
573 let has_impls: Vec<_> = field_infos
575 .iter()
576 .map(|(name, perm)| {
577 quote! {
578 impl capsec_core::has::Has<capsec_core::permission::#perm> for #struct_name {
579 fn cap_ref(&self) -> capsec_core::cap::Cap<capsec_core::permission::#perm> {
580 self.#name.cap_ref()
581 }
582 }
583 }
584 })
585 .collect();
586
587 Ok(quote! {
588 #(#struct_attrs)*
589 #struct_vis struct #struct_name {
590 #(#struct_fields,)*
591 }
592
593 impl #struct_name {
594 pub fn new(root: &capsec_core::root::CapRoot) -> Self {
596 Self {
597 #(#constructor_fields,)*
598 }
599 }
600 }
601
602 #(#has_impls)*
603 })
604}