Skip to content

Commit 57b3c85

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 9e00c04 commit 57b3c85

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
@@ -646,6 +646,11 @@ impl<'a> GeneralItem<'a> {
646646
/// the *type* has the verus_macro attribute.
647647
///
648648
/// Different traits are handled on a case-by-case basis; see automatic_derive.rs
649+
///
650+
/// Proc-macro crates (e.g., serde) do not emit `#[automatically_derived]`, so
651+
/// their impls would normally be invisible to this function. As a second path,
652+
/// we also handle macro-expanded impls where the struct has an explicit
653+
/// `#[verifier::external_derive]` annotation naming the trait.
649654
fn get_attributes_for_automatic_derive<'tcx>(
650655
ctxt: &ContextX<'tcx>,
651656
general_item: &GeneralItem<'tcx>,
@@ -663,7 +668,11 @@ fn get_attributes_for_automatic_derive<'tcx>(
663668
);
664669
};
665670

666-
if !crate::automatic_derive::is_automatically_derived(attrs) {
671+
let is_auto_derived = crate::automatic_derive::is_automatically_derived(attrs);
672+
// proc-macro derives do not emit #[automatically_derived]; check span origin instead
673+
let is_macro_expanded = !is_auto_derived && span.from_expansion();
674+
675+
if !is_auto_derived && !is_macro_expanded {
667676
return None;
668677
}
669678

@@ -679,7 +688,9 @@ fn get_attributes_for_automatic_derive<'tcx>(
679688
path.res.def_id()
680689
}
681690
_ => {
682-
warn_unknown();
691+
if is_auto_derived {
692+
warn_unknown();
693+
}
683694
return None;
684695
}
685696
};
@@ -689,11 +700,25 @@ fn get_attributes_for_automatic_derive<'tcx>(
689700
let mut type_eattrs = match ctxt.get_external_attrs(type_attrs) {
690701
Ok(eattrs) => eattrs,
691702
Err(_) => {
692-
warn_unknown();
703+
if is_auto_derived {
704+
warn_unknown();
705+
}
693706
return None;
694707
}
695708
};
696709

710+
// for proc-macro-derived impls, only proceed if the type explicitly
711+
// requests external treatment via external_derive; otherwise fall through
712+
// to None so the impl is handled by the normal classification logic
713+
if is_macro_expanded
714+
&& matches!(
715+
&type_eattrs.external_auto_derives,
716+
crate::attributes::AutoDerivesAttr::Regular
717+
)
718+
{
719+
return None;
720+
}
721+
697722
if match &type_eattrs.external_auto_derives {
698723
crate::attributes::AutoDerivesAttr::Regular => false,
699724
crate::attributes::AutoDerivesAttr::AllExternal => true,
@@ -721,6 +746,12 @@ fn get_attributes_for_automatic_derive<'tcx>(
721746
return Some(type_eattrs);
722747
}
723748

749+
// for proc-macro impls that have external_derive but the trait name did not
750+
// match any listed name, fall through to normal classification
751+
if is_macro_expanded {
752+
return None;
753+
}
754+
724755
if opts_in_to_verus(&type_eattrs) {
725756
let trait_def_id = impll.of_trait.unwrap().trait_ref.path.res.def_id();
726757
let rust_item = get_rust_item(ctxt.tcx, trait_def_id);
@@ -737,17 +768,23 @@ fn get_attributes_for_automatic_derive<'tcx>(
737768
None
738769
}
739770
} else {
740-
warn_unknown();
771+
if is_auto_derived {
772+
warn_unknown();
773+
}
741774
None
742775
}
743776
}
744777
_ => {
745-
warn_unknown();
778+
if is_auto_derived {
779+
warn_unknown();
780+
}
746781
None
747782
}
748783
},
749784
_ => {
750-
warn_unknown();
785+
if is_auto_derived {
786+
warn_unknown();
787+
}
751788
None
752789
}
753790
}

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)