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

Unions contain unsafe JS<T> values #2661

Closed
jdm opened this issue Jun 16, 2014 · 18 comments
Closed

Unions contain unsafe JS<T> values #2661

jdm opened this issue Jun 16, 2014 · 18 comments

Comments

@jdm
Copy link
Member

@jdm jdm commented Jun 16, 2014

What we really want are unions that contain JSRef<T> values when used as arguments, and Temporary<T> values when used as return values. This is kind of awkward - maybe we make two variants for each interface that part of a union, eFooArg and eFooRetval. Alternatively, maybe we generate two different unions, FooOrStringArg and FooOrStringRetval.

@Ms2ger
Copy link
Contributor

@Ms2ger Ms2ger commented Jun 16, 2014

Two unions, I think, to make sure the compiler checks us.

Ms2ger added a commit to Ms2ger/servo that referenced this issue Feb 11, 2015
There should not be a JS here; that is servo#2661. Until that's fixed, though,
it's better to encapsulate it.
Ms2ger added a commit to Ms2ger/servo that referenced this issue Feb 12, 2015
There should not be a JS here; that is servo#2661. Until that's fixed, though,
it's better to encapsulate it.
@jdm
Copy link
Member Author

@jdm jdm commented Apr 16, 2015

@nox suggested we could define our unions as enum NodeOrString<T: Assignable<Node>> { eNode(T), eString(DOMString). This would allow us to store NodeOrString<JS<Node>> values, accept NodeOrString<JSRef<Node>> arguments, and return NodeOrString<Temporary<Node>> values. That's pretty compelling.

@Manishearth
Copy link
Member

@Manishearth Manishearth commented Apr 16, 2015

That sounds like a good idea :)

@nox
Copy link
Member

@nox nox commented Apr 16, 2015

If we do this and ever need unions of multiple interfaces, a lint should probably be added to make sure the various type parameters are actually of the same flavour of Assignable.

@nox
Copy link
Member

@nox nox commented Apr 25, 2015

The Assignable trick doesn't work because it doesn't have an impl for Unrooted.

@jdm
Copy link
Member Author

@jdm jdm commented Apr 25, 2015

I don't think there's a problem with adding one.

@nox
Copy link
Member

@nox nox commented Apr 25, 2015

@jdm I forgot that it's the same for Root.

@jdm
Copy link
Member Author

@jdm jdm commented Apr 25, 2015

Perhaps to get around the Root problem we can instantiate JSRef values from them ahead of time?

@nox
Copy link
Member

@nox nox commented Apr 25, 2015

So my idea is to generate something like this:

pub enum BlobOrString<TBlob: Assignable<Blob>> {
    eBlob(TBlob),
    eString(TDOMString),
}

impl<TBlob: ToJSValConvertible> ToJSValConvertible for BlobOrString<TBlob> {
    fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
        match *self {
            BlobOrString::eBlob(ref inner) => inner.to_jsval(cx),
            BlobOrString::eString(ref inner) => inner.to_jsval(cx),
        }
    }
}

pub struct BlobOrStringConfig<'a> {
    eBlob: &'a mut RootedVec<JS<Blob>>,
}

impl<'a> FromJSValConvertible for BlobOrString<JSRef<'a, Blob>> {
    type Config = BlobOrStringConfig<'a>;
    fn from_jsval(cx: *mut JSContext, value: JSVal, config: BlobOrStringConfig<'a>) -> Result<Self, ()> {
        if value.is_object() {
            match BlobOrString::TryConvertToBlob(cx, value) {
                Err(_) => return Err(()),
                Ok(Some(value)) => {
                    config.eBlob.push(JS::from_unrooted(value));
                    return Ok(BlobOrString::eBlob(???));
                },
                Ok(None) => (),
            }

        }

        match BlobOrString::TryConvertToString(cx, value) {
            Err(_) => return Err(()),
            Ok(Some(value)) => return Ok(BlobOrString::eString(value)),
            Ok(None) => (),
        }


        throw_not_in_union(cx, "Blob, String");
        Err(())
    }
}

It's an… interesting use of FromJSValConvertible::Config to say the least, but that lets us pass RootedVec values from the method bindings and share them between different values from vectors of unions ((Node or String)...) and even from different union arguments ((Foo or Bar) bar, (Foo or Qux) qux). The problem is that to return the JSRef while inserting it to the RootedVec, I either need to use get_unsound_ref_forever or to somehow append it from a &'a value. I'm thinking something similar to a TypedArena would be a fit here.

@nox
Copy link
Member

@nox nox commented Apr 26, 2015

Following what I said, here is what I came with:

/// An arena of items that are rooted for the lifetime of this struct.
#[allow(unrooted_must_root)]
#[jstraceable]
pub struct RootedArena<T: Reflectable> {
    v: UnsafeCell<RootedVec<JS<T>>>,
}

impl<T: Reflectable> RootedArena<T> {
    /// Create an arena of items of type T.
    pub fn new() -> Self {
        RootedArena { v: UnsafeCell::new(RootedVec::new()) }
    }

    /// Push a value into an arena and return a `JSRef` of it.
    #[allow(unrooted_must_root)]
    pub fn push<U: Assignable<T>>(&self, value: U) -> JSRef<T> {
        let value = unsafe { value.get_js() };
        unsafe { &mut *self.v.get() }.push(value);
        JSRef {
            ptr: value.ptr,
            chain: PhantomData,
        }
    }
}

It compiles but I've yet to test it.

@nox
Copy link
Member

@nox nox commented Apr 26, 2015

Can't I simplify everything and make RootedArena store a list of Reflector or even *mut JSObject?

@nox
Copy link
Member

@nox nox commented Apr 26, 2015

Sounds like I just want a RootCollection after all.

@nox
Copy link
Member

@nox nox commented Apr 26, 2015

/// An arena of items that are rooted for the lifetime of this struct.
#[allow(unrooted_must_root)]
#[jstraceable]
pub struct RootedArena {
    collection: RootCollection,
}

impl RootedArena {
    /// Create an arena of items.
    pub fn new() -> Self {
        RootedArena { collection: RootCollection::new() }
    }

    /// Push a value into an arena and return a `JSRef` of it.
    #[allow(unrooted_must_root)]
    pub fn root<T: Reflectable, U: Assignable<T>>(&self, value: U) -> JSRef<T> {
        let value = value.get_js();
        self.collection.root(value.reflector().get_jsobject());
        JSRef {
            ptr: value.ptr,
            chain: PhantomData,
        }
    }
}

Something like this would allow me to use this structure for all union members, wouldn't it?

@nox
Copy link
Member

@nox nox commented Apr 26, 2015

@jdm Implementing Assignable on Unrooted and Root might looks weird because functions such as JS::from_rooted() use that trait. "Unrooted" doesn't sound like it should be an argument to JS::from_rooted().

@jdm
Copy link
Member Author

@jdm jdm commented Apr 26, 2015

Yes, that's true. It's why I originally suggested getting a JSRef from those values and using those in places that took Assignable, instead of implementing that trait.

@nox
Copy link
Member

@nox nox commented Apr 26, 2015

I see. After coding I realised I don't need the additional Assignable impls anyway.

@nox
Copy link
Member

@nox nox commented Jun 12, 2015

I am waiting for #6150 to land before resuming work on this.

bors-servo pushed a commit that referenced this issue Jul 29, 2015
Remove unrooted_must_root annotation from unions (fixes #2661).

The unsafety was fixed as part of the SpiderMonkey upgrade; this removes the
now unused annotation.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/6809)
<!-- Reviewable:end -->
@Ms2ger Ms2ger closed this in efa12e6 Jul 29, 2015
huonw added a commit to huonw/servo that referenced this issue Jul 30, 2015
The unsafety was fixed as part of the SpiderMonkey upgrade; this removes the
now unused annotation.
josiahdaniels added a commit to josiahdaniels/servo that referenced this issue Sep 28, 2015
The unsafety was fixed as part of the SpiderMonkey upgrade; this removes the
now unused annotation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants
You can’t perform that action at this time.