-
-
Notifications
You must be signed in to change notification settings - Fork 253
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make unboxing args sound with ArgAbi
#1731
Make unboxing args sound with ArgAbi
#1731
Conversation
8e7fe1b
to
889a244
Compare
oh, right, tests. |
Most of the current failures are from this, which will require the most thought. https://blog.rust-lang.org/2022/10/28/gats-stabilization.html#implied-static-requirement-from-higher-ranked-trait-bounds |
While sorting out lifetime problems to make #1731 landable, I noticed JsonInOutFuncs is completely replaceable with two reexports. So I replaced it. This also made the changes to sort out the rest of the lifetimes obvious, and now we return CString always from these functions.
f6edb6f
to
ffc4acd
Compare
3737118
to
9c274b5
Compare
This reverts commit e98bb1b.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've read through this, I'm very impressed with how much cleaner / more sensible the lifetimes are with these changes. Looks good to me apart from a few documentation nitpicks.
} | ||
} | ||
|
||
// TODO: rebadge this as AnyElement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit - could this be within the scope of this pull request? "Argument" does describe it, though, I don't mind this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, so AnyElement has a special type definition relative to Postgres. Our AnyElement is here:
pgrx/pgrx/src/datum/anyelement.rs
Lines 22 to 26 in 7ea2480
#[derive(Debug, Clone, Copy)] | |
pub struct AnyElement { | |
datum: pg_sys::Datum, | |
typoid: pg_sys::Oid, | |
} |
And it's modeling this type: https://www.postgresql.org/docs/current/datatype-pseudo.html
But there may have to be a bit of additional work to make a wrapper around Argument usable as AnyElement. It's based on the use-cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only question is: What's the plan for Aggregates?
Also, do you have a simple example of what a #[pg_extern]
function expands to now? I feel like I just recently wrote some docs that kinda outlines that, and we should probably update those too.
@@ -145,6 +145,7 @@ fn make_friendship( | |||
} | |||
|
|||
/* | |||
FIXME: make this example no longer DOA |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the plan for this? Aggregate support is a pretty important feature
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem isn't with Aggregate, per se, it's with certain types.
These are aggregates that are supposed to manage composite types. But we know composite types are allocated in Postgres, thus are effectively lifetime-bound. But #[pg_aggregate]
and Aggregate don't know how to convey the lifetimes through. Thus even annotating it with something like this does not actually fix the problem:
#[pg_aggregate]
impl<'a> Aggregate for SumScritches<'a> {
type State = i32;
const INITIAL_CONDITION: Option<&'static str> = Some("0");
type Args = pgrx::name!(value, pgrx::composite_type!('a, "Dog"));
fn state(
current: Self::State,
arg: Self::Args,
_fcinfo: pg_sys::FunctionCallInfo,
) -> Self::State {
todo!()
}
}
Thus in order to make aggregates work for complex cases we must break the world for them as well, making all their functions use the correct, lifetime-bound types. Hopefully this will let us simplify the code expansion for them as well.
This has clearly been a ton of work! I'm excited!! |
The wrapper function will always expand like this: #[no_mangle]
#[doc(hidden)]
pub unsafe extern "C" fn substring_wrapper(
fcinfo: ::pgrx::pg_sys::FunctionCallInfo,
) -> ::pgrx::pg_sys::Datum {
fn _internal_wrapper<'fcx>(
fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>,
) -> ::pgrx::datum::Datum<'fcx> {
#[allow(unused_unsafe)]
unsafe {
let call_flow = <&str as ::pgrx::callconv::RetAbi>::check_and_prepare(fcinfo);
let result = match call_flow {
::pgrx::callconv::CallCx::WrappedFn(mcx) => {
let mut mcx = ::pgrx::PgMemoryContexts::For(mcx);
let _args = &mut fcinfo.arguments();
let call_result = mcx.switch_to(|_| {
let input_ = _args.unbox_next_unchecked().unwrap_or_else(|| {
::core::panicking::panic_fmt(format_args!(
"unboxing {0} argument failed",
"input_"
));
});
let start_ = _args.unbox_next_unchecked().unwrap_or_else(|| {
::core::panicking::panic_fmt(format_args!(
"unboxing {0} argument failed",
"start_"
));
});
let end_ = _args.unbox_next_unchecked().unwrap_or_else(|| {
::core::panicking::panic_fmt(format_args!(
"unboxing {0} argument failed",
"end_"
));
});
substring(input_, start_, end_)
});
::pgrx::callconv::RetAbi::to_ret(call_result)
}
::pgrx::callconv::CallCx::RestoreCx => {
<&str as ::pgrx::callconv::RetAbi>::ret_from_fcx(fcinfo)
}
};
::core::mem::transmute(unsafe {
<&str as ::pgrx::callconv::RetAbi>::box_ret_in(fcinfo, result)
})
}
}
let datum = unsafe {
::pgrx::pg_sys::submodules::panic::pgrx_extern_c_guard(|| {
let mut fcinfo = ::pgrx::callconv::FcInfo::from_ptr(fcinfo);
_internal_wrapper(&mut fcinfo)
})
};
datum.sans_lifetime()
} There is no meaningful variation except based on number of arguments. |
...
::core::mem::transmute(unsafe {
<&str as ::pgrx::callconv::RetAbi>::box_ret_in(fcinfo, result)
})
... What is the thing being transmuted here? Is it the lifetime of the return value? |
Correct. I wonder if I can erase that... |
Yeah, that lifetime transmutation seems to be completely unnecessary now. |
I wasn't quite happy with the remainder of the point of InOutFuncs being part of the collateral damage, so I adjusted it to better support the quirky behavior of Postgres on that count. Regardless of what I think about the API design, the fact that its test failed seemed bad for correctness, especially given how easy it was to incur. Now, if you ask for, say, a if nullable_datum.isnull {
Nullable::None
} else if nullable_datum.value == pg_sys::Datum::null() {
Nullable::Valid(None)
} else {
Nullable::Valid(Some(the_conversion(nullable_datum.value)))
} |
argh. |
7556ad7
into
pgcentralfoundation:develop
So this lays the ground work for pg_agg, but that work isn't happening atm is it? |
correct. |
This makes moving arguments into pgrx's
pg_extern
functions sound, by using a new trait,ArgAbi
, to describe this behavior. The supportingArg
andArgs
types allow effectively treating the arguments of the function like an iterator, but the behavior is much moreunsafe
.The victories
#[pg_extern] fn
must be sound in its arguments and return types.The collateral damage
'static
lifetimes and#[pg_aggregate]
does not yet understand how to pass through lifetimes correctly.'static
now fail to compile, even in a hypothetical case it might be okay. This is because pgrx attempts to translate an argument from a lifetime-boundFcInfo
into the'static
lifetime, which is correctly rejected by Rust's type system.'static
lifetime also fails, for similar reasons. This affects even hypothetical cases it would be okay (e.g. if the return was actually a&'static str
). Types that are inherently'static
likeString
are still fine.Future work
FromDatum
and thus doesn't hammer the nail in the coffin on the unsoundness in the numerous SPI functions that useFromDatum
.