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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for throwing Exception objects #214

Merged
merged 9 commits into from
Nov 24, 2023
Merged

Conversation

joehoyle
Copy link
Collaborator

@joehoyle joehoyle commented Dec 20, 2022

Currently we only support throwing exception class entries, i.e. stateless exceptions. Ideally we also want to support throwing a ZVal which has a ClassEntry that extends the Exception PHP class. This can be used to throw stateful exceptions which is not uncommon.

This PR is missing one piece: throw_object will currently drop the Zval when the function is completed, causing reference / null pointer errors. It seems ZVal::Drop doesn't actually free the zval currently, it just sets the type to NULL (which also breaks things.) Discussed briefly in https://discord.com/channels/115233111977099271/1025314959179120714 on how best to solve this, but I'm not totally clear still! Ideally I think we want throw_object to own the zval but not free it once the function returns.

Related: #203

Fixed: #137

Currently we only support throwing exception class entries, i.e. stateless exceptions. Ideally we also want to support throwing a ZVal which has a ClassEntry that extends the Exception PHP class. This can be used to throw stateful exceptions which is not uncommon.

This PR is missing one piece: `throw_object` will currently `drop` the Zval when the function is completed, causing reference / null pointer errors. It seems `ZVal::Drop` doesn't actually free the zval currently, it just sets the type to NULL (which also breaks things.) Discussed briefly in https://discord.com/channels/115233111977099271/1025314959179120714 on how best to solve this, but I'm not totally clear still! Ideally I think we want `throw_object` to own the `zval` but not free it once the function returns.
@ptondereau
Copy link
Collaborator

@joehoyle thank you for this PR, the CI is failing for now. Could you please fix it before a in-depth review?

@ptondereau
Copy link
Collaborator

Sorry, I've merged #212 earlier, you can rebase into master to fix some clippy warning

@joehoyle
Copy link
Collaborator Author

@ptondereau ah, have you handled the merge, or do I need to rebase the branch?

@ptondereau
Copy link
Collaborator

@joehoyle totally handled sorry for the annoying

@joehoyle
Copy link
Collaborator Author

Ok cool, I think mostly what I need is input on how to handle the Zval getting dropped after zend_throw_exception_object is called.

@ptondereau
Copy link
Collaborator

I've tested your branch with a small example:

use ext_php_rs::convert::IntoZval;
use ext_php_rs::exception::throw_object;
use ext_php_rs::prelude::*;

#[derive(Debug)]
#[php_class]
#[extends(ext_php_rs::zend::ce::exception())]
pub struct JsException {
    #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)]
    message: String,
    #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)]
    code: i32,
    #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)]
    file: String,
}

#[php_function]
fn test_throw() {
    let error = JsException {
        message: "A JS error occurred.".to_string(),
        code: 100,
        file: "index.js".to_string(),
    };
    throw_object(error.into_zval(true).unwrap()).unwrap();

    println!("dropped")
}

#[php_module]
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
    module
}

And it returns:

❯ ../../php/php-debug-82/bin/php -dextension=target/debug/libext_test_php.so index.php                                       
dropped
[1]    105306 segmentation fault (core dumped)  ../../php/php-debug-82/bin/php -dextension=target/debug/libext_test_php.so 

@joehoyle
Copy link
Collaborator Author

joehoyle commented Jan 3, 2023

@ptondereau yes, this is expected, and what i see too, as when the Zval is dropped, the type is updated to null, that's what I posted my question about on this PR.

@joehoyle
Copy link
Collaborator Author

@davidcole1340 perhaps you could advise here, I know we talked briefly about shallow_clone and allocating ZVals on the heap. I think the issue I'm running into there is that Zval sets it's self to null on drop().

@joelwurtz
Copy link
Contributor

Does this zval will be correctly drop by php engine ?
If this is case maybe you can use https://doc.rust-lang.org/stable/std/mem/struct.ManuallyDrop.html so this zval is never dropped by rust ?

@ju1ius
Copy link
Contributor

ju1ius commented Oct 20, 2023

If this is case maybe you can use ManuallyDrop so this zval is never dropped by rust ?

Yes, it is indeed a cleaner solution, e.g.

pub fn throw_object(zval: Zval) {
    let mut zv = core::mem::ManuallyDrop::new(zval);
    unsafe {
        zend_throw_exception_object(core::ptr::addr_of_mut!(zv).cast())
    };
}

@joelwurtz
Copy link
Contributor

joelwurtz commented Oct 20, 2023

It could also use Box like :

let zval_ptr = Box::into_raw(Box::new(zval)); // *mut zval;

as stated by rust, it will not drop the value if used like this (and also avoid using it latter since it will be consumed by this call)

@ju1ius
Copy link
Contributor

ju1ius commented Oct 20, 2023

It could also use Box like Box::into_raw(Box::new(zval))

Nope, zvals must always live on the stack (unless part of another heap-allocated struct).

Using a Box here just creates an unnecessary heap allocation. Plus you'll have to reclaim the box using Box::from_raw (otherwise that would be a memory leak).

Ref: https://www.phpinternalsbook.com/php7/zvals/memory_management.html#zval-memory-management

@joehoyle
Copy link
Collaborator Author

Thanks @ju1ius, I used that code, PR updated.

@joehoyle joehoyle requested a review from danog November 24, 2023 12:39
@danog
Copy link
Collaborator

danog commented Nov 24, 2023

These build times are killing me, I'd rather just merge a pending build and then fix formatting with a second PR on master tbh :)

@joehoyle
Copy link
Collaborator Author

@danog haha that's nothing! Try using github actions for Arm builds :P

@danog
Copy link
Collaborator

danog commented Nov 24, 2023

@joehoyle

Try using github actions for Arm builds :P

Github actions + QEMU is precisely the reason why I run all my CI builds on a raspberry pi at home with woodpecker :D

@danog danog merged commit 3f50e7e into master Nov 24, 2023
26 checks passed
@joehoyle joehoyle deleted the throw-exception-object branch November 24, 2023 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support zend_throw_exception_object
5 participants