Skip to content

Commit fb795ec

Browse files
committed
Extend external_derive to cover proc-macro-generated impls
Proc-macro crates such as serde do not emit #[automatically_derived] on the impls they generate. As a result, #[verifier::external_derive] had no effect on serde-derived Serialize/Deserialize impls (issues #1575, #1577, #1956): the gate in get_attributes_for_automatic_derive returned None immediately for any impl lacking that attribute. The fix checks span.from_expansion() as a secondary signal. When an impl is macro-expanded but not #[automatically_derived], and the self type has #[verifier::external_derive] (with AllExternal or a matching trait name), the impl is now marked external. If the type has no external_derive annotation at all, macro-expanded impls fall through to the normal classification path unchanged, preserving existing behavior. Tests added in rust_verify_test/tests/std.rs simulate proc-macro derives with macro_rules! (which produces macro-expanded impls without #[automatically_derived]) and verify that external_derive AllExternal and SomeExternal(TraitName) both suppress verification of the generated impl.
1 parent 2fbad8a commit fb795ec

2 files changed

Lines changed: 102 additions & 6 deletions

File tree

source/rust_verify/src/external.rs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,11 @@ impl<'a> GeneralItem<'a> {
645645
/// the *type* has the verus_macro attribute.
646646
///
647647
/// Different traits are handled on a case-by-case basis; see automatic_derive.rs
648+
///
649+
/// Proc-macro crates (e.g., serde) do not emit `#[automatically_derived]`, so
650+
/// their impls would normally be invisible to this function. As a second path,
651+
/// we also handle macro-expanded impls where the struct has an explicit
652+
/// `#[verifier::external_derive]` annotation naming the trait.
648653
fn get_attributes_for_automatic_derive<'tcx>(
649654
ctxt: &ContextX<'tcx>,
650655
general_item: &GeneralItem<'tcx>,
@@ -660,7 +665,11 @@ fn get_attributes_for_automatic_derive<'tcx>(
660665
)));
661666
};
662667

663-
if !crate::automatic_derive::is_automatically_derived(attrs) {
668+
let is_auto_derived = crate::automatic_derive::is_automatically_derived(attrs);
669+
// proc-macro derives do not emit #[automatically_derived]; check span origin instead
670+
let is_macro_expanded = !is_auto_derived && span.from_expansion();
671+
672+
if !is_auto_derived && !is_macro_expanded {
664673
return None;
665674
}
666675

@@ -676,7 +685,9 @@ fn get_attributes_for_automatic_derive<'tcx>(
676685
path.res.def_id()
677686
}
678687
_ => {
679-
warn_unknown();
688+
if is_auto_derived {
689+
warn_unknown();
690+
}
680691
return None;
681692
}
682693
};
@@ -686,11 +697,25 @@ fn get_attributes_for_automatic_derive<'tcx>(
686697
let mut type_eattrs = match ctxt.get_external_attrs(type_attrs) {
687698
Ok(eattrs) => eattrs,
688699
Err(_) => {
689-
warn_unknown();
700+
if is_auto_derived {
701+
warn_unknown();
702+
}
690703
return None;
691704
}
692705
};
693706

707+
// for proc-macro-derived impls, only proceed if the type explicitly
708+
// requests external treatment via external_derive; otherwise fall through
709+
// to None so the impl is handled by the normal classification logic
710+
if is_macro_expanded
711+
&& matches!(
712+
&type_eattrs.external_auto_derives,
713+
crate::attributes::AutoDerivesAttr::Regular
714+
)
715+
{
716+
return None;
717+
}
718+
694719
if match &type_eattrs.external_auto_derives {
695720
crate::attributes::AutoDerivesAttr::Regular => false,
696721
crate::attributes::AutoDerivesAttr::AllExternal => true,
@@ -718,6 +743,12 @@ fn get_attributes_for_automatic_derive<'tcx>(
718743
return Some(type_eattrs);
719744
}
720745

746+
// for proc-macro impls that have external_derive but the trait name did not
747+
// match any listed name, fall through to normal classification
748+
if is_macro_expanded {
749+
return None;
750+
}
751+
721752
if opts_in_to_verus(&type_eattrs) {
722753
let trait_def_id = impll.of_trait.unwrap().trait_ref.path.res.def_id();
723754
let rust_item = get_rust_item(ctxt.tcx, trait_def_id);
@@ -734,17 +765,23 @@ fn get_attributes_for_automatic_derive<'tcx>(
734765
None
735766
}
736767
} else {
737-
warn_unknown();
768+
if is_auto_derived {
769+
warn_unknown();
770+
}
738771
None
739772
}
740773
}
741774
_ => {
742-
warn_unknown();
775+
if is_auto_derived {
776+
warn_unknown();
777+
}
743778
None
744779
}
745780
},
746781
_ => {
747-
warn_unknown();
782+
if is_auto_derived {
783+
warn_unknown();
784+
}
748785
None
749786
}
750787
}

source/rust_verify_test/tests/std.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,65 @@ test_verify_one_file_with_options! {
603603
} => Err(err) => assert_vir_error_msg(err, "cannot use function `test_crate::X::clone` which is ignored")
604604
}
605605

606+
// Proc-macro crates (e.g., serde) do not emit #[automatically_derived], so
607+
// their impls have span.from_expansion() == true but no automatically_derived attr.
608+
// We simulate this with a macro_rules! that produces an impl without that attribute.
609+
test_verify_one_file_with_options! {
610+
#[test] external_derive_proc_macro_all ["--no-external-by-default"] => verus_code! {
611+
macro_rules! fake_serialize {
612+
($t:ty) => {
613+
impl FakeSerialize for $t {
614+
fn serialize(&self) -> u64 { 0 }
615+
}
616+
}
617+
}
618+
619+
trait FakeSerialize {
620+
fn serialize(&self) -> u64;
621+
}
622+
623+
#[verifier::external_derive]
624+
struct X {
625+
u: u64,
626+
}
627+
628+
fake_serialize!(X);
629+
630+
fn test(x: X) {
631+
// calling serialize on X should work since the impl is marked external
632+
let _ = x.serialize();
633+
}
634+
} => Ok(())
635+
}
636+
637+
test_verify_one_file_with_options! {
638+
#[test] external_derive_proc_macro_named ["--no-external-by-default"] => verus_code! {
639+
macro_rules! fake_serialize {
640+
($t:ty) => {
641+
impl FakeSerialize for $t {
642+
fn serialize(&self) -> u64 { 0 }
643+
}
644+
}
645+
}
646+
647+
trait FakeSerialize {
648+
fn serialize(&self) -> u64;
649+
}
650+
651+
// only FakeSerialize is listed -- the macro-expanded impl should be ignored
652+
#[verifier::external_derive(FakeSerialize)]
653+
struct X {
654+
u: u64,
655+
}
656+
657+
fake_serialize!(X);
658+
659+
fn test(x: X) {
660+
let _ = x.serialize();
661+
}
662+
} => Ok(())
663+
}
664+
606665
test_verify_one_file! {
607666
#[test] vec_index_nounwind verus_code! {
608667
use vstd::*;

0 commit comments

Comments
 (0)