Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mock-builder: add support for custom lifetimes #1335

Merged
merged 10 commits into from
May 12, 2023

Conversation

lemunozm
Copy link
Contributor

@lemunozm lemunozm commented May 3, 2023

Description

This is the last technical requirement for mock-builder. After this PR, I think we can test any possible trait. 馃帀 Future work is to create procedural macros to reduce boilerplate. Roadmap here

New features:

  • References supported.
  • Generics with random lifetimes (other than 'static) supported.

Fixes #1322

Changes and Descriptions

  • New storage implementation to support input/output types that could not be 'static

@lemunozm lemunozm added D2-notify Pull request can be merged and notification about changes should be documented. crcl-protocol Circle protocol related. labels May 3, 2023
@lemunozm lemunozm self-assigned this May 3, 2023
@lemunozm lemunozm force-pushed the mock-builder/custom-lifetimes branch from c2d8023 to 27039bc Compare May 4, 2023 09:50
@lemunozm lemunozm force-pushed the mock-builder/custom-lifetimes branch from 26f9f80 to 37190f7 Compare May 4, 2023 14:29
Copy link
Contributor

@wischli wischli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit I can only follow on a much much higher level and on that, it looks good to me! I'd be interested in an argument for your safe unsafe transmutation, see below.

let f = Box::new(f) as Box<dyn Fn(I) -> O>;
let ptr = Box::into_raw(f);

// SAFETY: transforming a wide pointer to an u128 is always safe.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, I don't think this is true. Can you show a short proof?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's safe in the same way a "normal" reference is always transformable into a * const u64. In fact, the following line does not require unsafe rust:

let reference: &u64 = ...
let ptr = reference as *const u64 

This is safe because, it is just an address, a number. What it's not safe is what you do with that address, because all invariants and protections are broken after this.

This is why the other unsafe is the critical one, and you must ensure that the ptr still exists and corresponds to the same type as the original.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding "why exactly u128?" It's because any &dyn Type is a wide pointer: one part corresponds to the pointer itself and the other to the metadata pointer that contains the trait information of the closure. We do not care about how both pointers are sorted in memory, because we are undoing the same we did in the other method for the same type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to add more explanation in the comments--like how this enables us to use a fixed size in the CallInfo struct for the ptr as opposed to having to know the size/alignment of the vtable ptr in the fat pointer.
Would it be reasonable to have CallInfo have 2 usize fields for the closure address and vtable address instead and use ptr::from_raw_parts to build ptr instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree 馃憤馃徎 I'll add a better explanation. Regarding the 2 usize fields, I think separating could confuse in the sense that seems that they should be used separately when not. We only need to allocate that, but not use the internals. Again, I think a good explanation into the code is needed.

));
}

// SAFETY: The existance of this boxed clousure in consequent calls is ensured
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
// SAFETY: The existance of this boxed clousure in consequent calls is ensured
// SAFETY: The existence of this boxed closure in consequent calls is ensured

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is some real inception level stuff: The existence is ensured by the forget call 馃く

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realize this means that you have done a very good review. Thanks so much! 馃檶馃徎

wischli
wischli previously approved these changes May 8, 2023
Copy link
Contributor

@wischli wischli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit I can only follow on a much much higher level and on that, it looks good to me! I'd be interested in an argument for your safe unsafe transmutation, see below.

thea-leake
thea-leake previously approved these changes May 11, 2023
Copy link
Contributor

@thea-leake thea-leake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! I really like the better error formatting, known size for stored mock fns, and reduced transmute usage!
Added a few comments, but nothing blocking.

let f = Box::new(f) as Box<dyn Fn(I) -> O>;
let ptr = Box::into_raw(f);

// SAFETY: transforming a wide pointer to an u128 is always safe.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to add more explanation in the comments--like how this enables us to use a fixed size in the CallInfo struct for the ptr as opposed to having to know the size/alignment of the vtable ptr in the fat pointer.
Would it be reasonable to have CallInfo have 2 usize fields for the closure address and vtable address instead and use ptr::from_raw_parts to build ptr instead?

));
}

// SAFETY: The existence of this boxed closure in consequent calls is ensured
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to update comment to indicate how these are being persisted--that forget takes ownership, but doesn't call destructor. I was initially unclear as to what was actually going on here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! I'll do it. Thanks for the advice and for taking the time to deep into it, it's not an easy PR.

Error::TypeNotMatch => "The function is registered but the type mismatches",
}
)
match self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


/// Identify a call in the call storage
pub type CallId = u64;

trait Callable {
fn as_any(&self) -> &dyn Any;
struct CallInfo {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool that we can have this as known fixed size, though have comment below in casting thread about the transmute.

@lemunozm
Copy link
Contributor Author

@thea-leake, I've documented every weird case I was able to see. In the process, I've simplified a bit the forget() call part (which is no longer needed).

Copy link
Contributor

@thea-leake thea-leake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@lemunozm lemunozm enabled auto-merge (squash) May 12, 2023 08:47
@lemunozm lemunozm merged commit 0613791 into main May 12, 2023
11 checks passed
@lemunozm lemunozm deleted the mock-builder/custom-lifetimes branch May 12, 2023 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crcl-protocol Circle protocol related. D2-notify Pull request can be merged and notification about changes should be documented.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Mock-builder: support trait methods with no static lifetimes
3 participants