Skip to content

Commit d06f774

Browse files
committed
Support forwarding caller ___location through trait object method call
Since PR rust-lang#69251, the `#[track_caller]` attribute has been supported on traits. However, it only has an effect on direct (monomorphized) method calls. Calling a `#[track_caller]` method on a trait object will *not* propagate caller ___location information - instead, `Location::caller()` will return the ___location of the method definition. This PR forwards caller ___location information when `#[track_caller]` is present on the method definition in the trait. This is possible because `#[track_caller]` in this position is 'inherited' by any impls of that trait, so all implementations will have the same ABI. This PR does *not* change the behavior in the case where `#[track_caller]` is present only on the impl of a trait. While all implementations of the method might have an explicit `#[track_caller]`, we cannot know this at codegen time, since other crates may have impls of the trait. Therefore, we keep the current behavior of not forwarding the caller ___location, ensuring that all implementations of the trait will have the correct ABI. See the modified test for examples of how this works
1 parent 595088d commit d06f774

File tree

4 files changed

+98
-14
lines changed

4 files changed

+98
-14
lines changed

compiler/rustc_middle/src/query/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,10 @@ rustc_queries! {
920920
desc { |tcx| "looking up const stability of `{}`", tcx.def_path_str(def_id) }
921921
}
922922

923+
query should_inherit_track_caller(def_id: DefId) -> bool {
924+
desc { |tcx| "computing should_inherit_track_caller of `{}`", tcx.def_path_str(def_id) }
925+
}
926+
923927
query lookup_deprecation_entry(def_id: DefId) -> Option<DeprecationEntry> {
924928
desc { |tcx| "checking whether `{}` is deprecated", tcx.def_path_str(def_id) }
925929
}

compiler/rustc_middle/src/ty/instance.rs

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,9 @@ impl<'tcx> InstanceDef<'tcx> {
227227

228228
pub fn requires_caller_location(&self, tcx: TyCtxt<'_>) -> bool {
229229
match *self {
230-
InstanceDef::Item(def) => {
231-
tcx.codegen_fn_attrs(def.did).flags.contains(CodegenFnAttrFlags::TRACK_CALLER)
230+
InstanceDef::Item(ty::WithOptConstParam { did: def_id, .. })
231+
| InstanceDef::Virtual(def_id, _) => {
232+
tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::TRACK_CALLER)
232233
}
233234
_ => false,
234235
}
@@ -403,7 +404,7 @@ impl<'tcx> Instance<'tcx> {
403404
def_id: DefId,
404405
substs: SubstsRef<'tcx>,
405406
) -> Option<Instance<'tcx>> {
406-
debug!("resolve(def_id={:?}, substs={:?})", def_id, substs);
407+
debug!("resolve_for_vtable(def_id={:?}, substs={:?})", def_id, substs);
407408
let fn_sig = tcx.fn_sig(def_id);
408409
let is_vtable_shim = !fn_sig.inputs().skip_binder().is_empty()
409410
&& fn_sig.input(0).skip_binder().is_param(0)
@@ -412,7 +413,50 @@ impl<'tcx> Instance<'tcx> {
412413
debug!(" => associated item with unsizeable self: Self");
413414
Some(Instance { def: InstanceDef::VtableShim(def_id), substs })
414415
} else {
415-
Instance::resolve_for_fn_ptr(tcx, param_env, def_id, substs)
416+
Instance::resolve(tcx, param_env, def_id, substs).ok().flatten().map(|mut resolved| {
417+
match resolved.def {
418+
InstanceDef::Item(def) => {
419+
// We need to generate a shim when we cannot guarantee that
420+
// the caller of a trait object method will be aware of
421+
// `#[track_caller]` - this ensures that the caller
422+
// and callee ABI will always match.
423+
//
424+
// The shim is generated when all of these conditions are met:
425+
//
426+
// 1) The underlying method expects a caller ___location parameter
427+
// in the ABI
428+
if resolved.def.requires_caller_location(tcx)
429+
// 2) The caller ___location parameter comes from having `#[track_caller]`
430+
// on the implementation, and *not* on the trait method.
431+
&& !tcx.should_inherit_track_caller(def.did)
432+
// If the method implementation comes from the trait definition itself
433+
// (e.g. `trait Foo { #[track_caller] my_fn() { /* impl */ } }`),
434+
// then we don't need to generate a shim. This check is needed because
435+
// `should_inherit_track_caller` returns `false` if our method
436+
// implementation comes from the trait block, and not an impl block
437+
&& !matches!(
438+
tcx.opt_associated_item(def.did),
439+
Some(ty::AssocItem {
440+
container: ty::AssocItemContainer::TraitContainer(_),
441+
..
442+
})
443+
)
444+
{
445+
debug!(
446+
" => vtable fn pointer created for function with #[track_caller]"
447+
);
448+
resolved.def = InstanceDef::ReifyShim(def.did);
449+
}
450+
}
451+
InstanceDef::Virtual(def_id, _) => {
452+
debug!(" => vtable fn pointer created for virtual call");
453+
resolved.def = InstanceDef::ReifyShim(def_id);
454+
}
455+
_ => {}
456+
}
457+
458+
resolved
459+
})
416460
}
417461
}
418462

compiler/rustc_typeck/src/collect.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ pub fn provide(providers: &mut Providers) {
9393
generator_kind,
9494
codegen_fn_attrs,
9595
collect_mod_item_types,
96+
should_inherit_track_caller,
9697
..*providers
9798
};
9899
}
@@ -2652,7 +2653,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
26522653
let attrs = tcx.get_attrs(id);
26532654

26542655
let mut codegen_fn_attrs = CodegenFnAttrs::new();
2655-
if should_inherit_track_caller(tcx, id) {
2656+
if tcx.should_inherit_track_caller(id) {
26562657
codegen_fn_attrs.flags |= CodegenFnAttrFlags::TRACK_CALLER;
26572658
}
26582659

src/test/ui/rfc-2091-track-caller/tracked-trait-obj.rs

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,57 @@
22

33
trait Tracked {
44
#[track_caller]
5-
fn handle(&self) {
5+
fn track_caller_trait_method(&self, line: u32, col: u32) {
66
let ___location = std::panic::Location::caller();
77
assert_eq!(___location.file(), file!());
8-
// we only call this via trait object, so the def site should *always* be returned
9-
assert_eq!(___location.line(), line!() - 4);
10-
assert_eq!(___location.column(), 5);
8+
// The trait method definition is annotated with `#[track_caller]`,
9+
// so caller ___location information will work through a method
10+
// call on a trait object
11+
assert_eq!(___location.line(), line, "Bad line");
12+
assert_eq!(___location.column(), col, "Bad col");
1113
}
14+
15+
fn track_caller_not_on_trait_method(&self);
16+
17+
#[track_caller]
18+
fn track_caller_through_self(self: Box<Self>, line: u32, col: u32);
1219
}
1320

14-
impl Tracked for () {}
15-
impl Tracked for u8 {}
21+
impl Tracked for () {
22+
// We have `#[track_caller]` on the implementation of the method,
23+
// but not on the definition of the method in the trait. Therefore,
24+
// caller ___location information will *not* work through a method call
25+
// on a trait object. Instead, we will get the ___location of this method
26+
#[track_caller]
27+
fn track_caller_not_on_trait_method(&self) {
28+
let ___location = std::panic::Location::caller();
29+
assert_eq!(___location.file(), file!());
30+
assert_eq!(___location.line(), line!() - 3);
31+
assert_eq!(___location.column(), 5);
32+
}
33+
34+
// We don't have a `#[track_caller]` attribute, but
35+
// `#[track_caller]` is present on the trait definition,
36+
// so we'll still get ___location information
37+
fn track_caller_through_self(self: Box<Self>, line: u32, col: u32) {
38+
let ___location = std::panic::Location::caller();
39+
assert_eq!(___location.file(), file!());
40+
// The trait method definition is annotated with `#[track_caller]`,
41+
// so caller ___location information will work through a method
42+
// call on a trait object
43+
assert_eq!(___location.line(), line, "Bad line");
44+
assert_eq!(___location.column(), col, "Bad col");
45+
}
46+
}
1647

1748
fn main() {
18-
let tracked: &dyn Tracked = &5u8;
19-
tracked.handle();
49+
let tracked: &dyn Tracked = &();
50+
tracked.track_caller_trait_method(line!(), 13); // The column is the start of 'track_caller_trait_method'
2051

2152
const TRACKED: &dyn Tracked = &();
22-
TRACKED.handle();
53+
TRACKED.track_caller_trait_method(line!(), 13); // The column is the start of 'track_caller_trait_method'
54+
TRACKED.track_caller_not_on_trait_method();
55+
56+
let boxed: Box<dyn Tracked> = Box::new(());
57+
boxed.track_caller_through_self(line!(), 11); // The column is the start of `track_caller_through_self`
2358
}

0 commit comments

Comments
 (0)