Skip to main content

zuqe/object/
prototype.rs

1use std::marker::PhantomData;
2
3use crate::{
4    context::{ContextImpl, ContextPtr},
5    error::JSResult,
6    object::{Object, ObjectImpl},
7    utils::unlikely,
8    value::{JS_GetPrototypePrimitive, JSValue},
9};
10
11/// To iterate over shape proto chain of an object
12pub struct ObjectShapeProtoIter<'a> {
13    obj: Option<Object>,
14    _mark: PhantomData<&'a ()>,
15}
16
17impl<'a> Iterator for ObjectShapeProtoIter<'a> {
18    type Item = Object;
19
20    #[inline]
21    fn next(&mut self) -> Option<Self::Item> {
22        if let Some(o) = self.obj.take() {
23            self.obj = o.shape().and_then(|s| s.proto());
24            Some(o)
25        } else {
26            None
27        }
28    }
29}
30
31impl ObjectImpl {
32    /// get shape proto of `self`
33    #[inline(always)]
34    pub fn shape_proto(&self) -> Option<Object> {
35        self.shape().and_then(|s| s.proto())
36    }
37
38    /// Get shape proto chain iterator, to iterate over the proto chain of an object.
39    ///
40    /// **Note: this iterator only concerns the Shape `proto` field, no proxy case is considered.**
41    #[inline]
42    pub fn shape_proto_iter<'a>(&self, include_self: bool) -> ObjectShapeProtoIter<'a> {
43        ObjectShapeProtoIter {
44            obj: if include_self {
45                Some(unsafe { Object::from_ref_unchecked(self) })
46            } else {
47                self.shape_proto()
48            },
49            _mark: PhantomData,
50        }
51    }
52
53    /// Get prototype object of `self`, both shape and proxy are considered.
54    ///
55    /// Returns:
56    /// - Some(object) if object has prototype
57    /// - None if object has no prototype.
58    /// - Err(_) in case Proxy object calling getPrototypeOf() failed.
59    pub fn prototype(&self, ctx: &ContextImpl) -> Result<Option<Object>, i32> {
60        if self.flags().is_exotic()
61            && let Some(cb) = ctx
62                .rt_ref()
63                .class_registry
64                .get(self.class_id() as usize)
65                .and_then(|c| c.exotic.as_deref())
66                .and_then(|em| em.get_prototype)
67        {
68            // exotic: Proxy
69            cb(
70                unsafe { ContextPtr::from_ref_unchecked(ctx) },
71                JSValue::Object(unsafe { Object::from_ref_unchecked(self) }),
72            )
73            .map(|v| Object::try_from(v).ok())
74        } else {
75            Ok(self.shape_proto())
76        }
77    }
78
79    /// Modify object `self`'s prototype.
80    /// - `proto_val`: the new prototype, object or null
81    /// - `throw_flag`: Whether to throw on error
82    pub(crate) fn set_prototype_internal(
83        &mut self,
84        ctx: &ContextImpl,
85        proto_val: JSValue,
86        throw_flag: bool,
87    ) -> Result<bool, i32> {
88        let proto = if let JSValue::Object(p) = proto_val {
89            Some(p)
90        } else if proto_val.is_null() {
91            None
92        } else {
93            ctx.throw_type_error_not_an_object();
94            return Err(-1);
95        };
96
97        let obj = unsafe { Object::from_ref_unchecked(self) };
98
99        if self.flags().is_exotic()
100            && let Some(cb) = ctx
101                .rt_ref()
102                .class_registry
103                .get(self.class_id() as usize)
104                .and_then(|c| c.exotic.as_deref())
105                .and_then(|em| em.set_prototype)
106        {
107            // exotic handler: Proxy
108            let ret = cb(
109                unsafe { ContextPtr::from_ref_unchecked(ctx) },
110                JSValue::Object(obj),
111                proto_val,
112            );
113
114            return if ret == Ok(false) && throw_flag {
115                ctx.throw_type_error("proxy: bad prototype");
116                Err(-1)
117            } else {
118                ret
119            };
120        }
121
122        if self.shape().unwrap().proto == proto {
123            return Ok(true);
124        }
125
126        let flags = self.flags();
127        if flags.immutable_prototype() {
128            return if throw_flag {
129                ctx.throw_type_error("prototype is immutable");
130                Err(-1)
131            } else {
132                Ok(false)
133            };
134        } else if !flags.extensible() {
135            return if throw_flag {
136                ctx.throw_type_error("object is not extensible");
137                Err(-1)
138            } else {
139                Ok(false)
140            };
141        }
142
143        if let Some(up) = proto {
144            // check if there is a cycle
145            let mut p1 = up;
146
147            loop {
148                if p1.id() == self.id() {
149                    if throw_flag {
150                        ctx.throw_type_error("circular prototype chain");
151                        return Err(-1);
152                    } else {
153                        return Ok(false);
154                    }
155                }
156
157                // Note: for Proxy object, proto is NULL
158                if let Some(p2) = p1.shape().and_then(|s| s.proto()) {
159                    p1 = p2;
160                } else {
161                    break;
162                }
163            }
164        }
165
166        self.modify_shape(ctx, |sh| {
167            sh.proto = proto;
168        })?;
169
170        Ok(true)
171    }
172}
173
174impl JSValue {
175    /// Get prototype of value.
176    /// Returns an object value if it has prototype, NULL for no prototype.
177    /// Returns Err in case of Proxy object.
178    pub fn prototype(&self, ctx: &ContextImpl) -> JSResult {
179        if let Self::Object(o) = self {
180            Ok(o.prototype(ctx)?.map_or(JSValue::NULL, JSValue::Object))
181        } else {
182            JS_GetPrototypePrimitive(ctx, self).err()
183        }
184    }
185
186    /// Modify value `self`'s prototype.
187    /// - `proto_val`: the new prototype, object or null
188    /// - `throw_flag`: Whether to throw on error
189    pub(crate) fn set_prototype_internal(
190        &mut self,
191        ctx: &ContextImpl,
192        proto_val: JSValue,
193        throw_flag: bool,
194    ) -> Result<bool, i32> {
195        if unlikely(throw_flag && self.is_undefined_or_null()) {
196            ctx.throw_type_error_not_an_object();
197            Err(-1)
198        } else if let JSValue::Object(o) = self {
199            o.set_prototype_internal(ctx, proto_val, throw_flag)
200        } else if throw_flag {
201            Ok(true)
202        } else {
203            ctx.throw_type_error_not_an_object();
204            Err(-1)
205        }
206    }
207}