From 06bb7dea80090c6cca41fbd5070c2e74d057a415 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= <jedel0124@gmail.com>
Date: Wed, 25 Oct 2023 21:12:54 +0000
Subject: [PATCH] Disallow changing type of already created objects (#3410)

* Disallow changing type of already created objects

* Fix regressions

* Fix spec steps

* Rollback restrictions and add new conversion
---
 boa_ast/src/expression/regexp.rs              |   6 +-
 boa_cli/src/main.rs                           |   6 +-
 boa_engine/src/builtins/error/type.rs         |  31 +-
 boa_engine/src/builtins/mod.rs                | 199 +++------
 boa_engine/src/builtins/regexp/mod.rs         | 152 +++----
 boa_engine/src/context/intrinsics.rs          |  30 ++
 boa_engine/src/context/mod.rs                 |   4 +-
 boa_engine/src/module/mod.rs                  |  44 +-
 boa_engine/src/module/synthetic.rs            |   1 -
 boa_engine/src/native_function.rs             |  34 +-
 boa_engine/src/object/builtins/jsfunction.rs  |  12 +-
 boa_engine/src/object/builtins/jspromise.rs   | 385 ++++++++----------
 boa_engine/src/object/builtins/jsregexp.rs    |  10 +-
 .../src/object/internal_methods/function.rs   |  41 +-
 boa_engine/src/object/mod.rs                  | 102 +++--
 boa_engine/src/object/operations.rs           |   4 +-
 boa_examples/src/bin/modules.rs               |  51 +--
 boa_examples/src/bin/synthetic.rs             |   4 +-
 boa_tester/src/exec/mod.rs                    |  25 +-
 19 files changed, 531 insertions(+), 610 deletions(-)

diff --git a/boa_ast/src/expression/regexp.rs b/boa_ast/src/expression/regexp.rs
index ccb38489509..031a9f93342 100644
--- a/boa_ast/src/expression/regexp.rs
+++ b/boa_ast/src/expression/regexp.rs
@@ -35,21 +35,21 @@ pub struct RegExpLiteral {
 }
 
 impl RegExpLiteral {
-    /// Create a new [`RegExp`].
+    /// Create a new [`RegExpLiteral`].
     #[inline]
     #[must_use]
     pub const fn new(pattern: Sym, flags: Sym) -> Self {
         Self { pattern, flags }
     }
 
-    /// Get the pattern part of the [`RegExp`].
+    /// Get the pattern part of the [`RegExpLiteral`].
     #[inline]
     #[must_use]
     pub const fn pattern(&self) -> Sym {
         self.pattern
     }
 
-    /// Get the flags part of the [`RegExp`].
+    /// Get the flags part of the [`RegExpLiteral`].
     #[inline]
     #[must_use]
     pub const fn flags(&self) -> Sym {
diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs
index 31f9a72b54d..073613eafe6 100644
--- a/boa_cli/src/main.rs
+++ b/boa_cli/src/main.rs
@@ -325,7 +325,7 @@ fn evaluate_files(
                 Err(v) => eprintln!("Uncaught {v}"),
             }
         } else if args.module {
-            let result = (|| {
+            let result: JsResult<PromiseState> = (|| {
                 let module = Module::parse(Source::from_bytes(&buffer), None, context)?;
 
                 loader.insert(
@@ -334,10 +334,10 @@ fn evaluate_files(
                     module.clone(),
                 );
 
-                let promise = module.load_link_evaluate(context)?;
+                let promise = module.load_link_evaluate(context);
 
                 context.run_jobs();
-                promise.state()
+                Ok(promise.state())
             })();
 
             match result {
diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs
index 9ebbed89ccb..aab6c9c40ad 100644
--- a/boa_engine/src/builtins/error/type.rs
+++ b/boa_engine/src/builtins/error/type.rs
@@ -20,7 +20,8 @@ use crate::{
     context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
     error::JsNativeError,
     js_string,
-    object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, ObjectKind},
+    native_function::NativeFunctionObject,
+    object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
     property::Attribute,
     realm::Realm,
     string::{common::StaticJsStrings, utf16},
@@ -115,15 +116,6 @@ pub(crate) struct ThrowTypeError;
 
 impl IntrinsicObject for ThrowTypeError {
     fn init(realm: &Realm) {
-        fn throw_type_error(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
-            Err(JsNativeError::typ()
-                .with_message(
-                    "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode \
-                    functions or the arguments objects for calls to them",
-                )
-                .into())
-        }
-
         let obj = BuiltInBuilder::with_intrinsic::<Self>(realm)
             .prototype(realm.intrinsics().constructors().function().prototype())
             .static_property(utf16!("length"), 0, Attribute::empty())
@@ -132,12 +124,21 @@ impl IntrinsicObject for ThrowTypeError {
 
         let mut obj = obj.borrow_mut();
 
-        obj.extensible = false;
-        *obj.kind_mut() = ObjectKind::NativeFunction {
-            function: NativeFunction::from_fn_ptr(throw_type_error),
+        *obj.as_native_function_mut()
+            .expect("`%ThrowTypeError%` must be a function") = NativeFunctionObject {
+            f: NativeFunction::from_fn_ptr(|_, _, _| {
+                Err(JsNativeError::typ()
+                    .with_message(
+                        "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode \
+                        functions or the arguments objects for calls to them",
+                    )
+                    .into())
+            }),
             constructor: None,
-            realm: realm.clone(),
-        }
+            realm: Some(realm.clone()),
+        };
+
+        obj.extensible = false;
     }
 
     fn get(intrinsics: &Intrinsics) -> JsObject {
diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs
index e150d22000a..097c5fbd177 100644
--- a/boa_engine/src/builtins/mod.rs
+++ b/boa_engine/src/builtins/mod.rs
@@ -46,6 +46,9 @@ pub(crate) mod options;
 #[cfg(feature = "temporal")]
 pub mod temporal;
 
+use boa_gc::GcRefMut;
+
+use self::function::ConstructorKind;
 pub(crate) use self::{
     array::Array,
     async_function::AsyncFunction,
@@ -100,11 +103,11 @@ use crate::{
     },
     context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
     js_string,
-    native_function::{NativeFunction, NativeFunctionPointer},
+    native_function::{NativeFunction, NativeFunctionObject, NativeFunctionPointer},
     object::{
         shape::{property_table::PropertyTableInner, slot::SlotAttributes},
-        FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, ObjectKind,
-        CONSTRUCTOR, PROTOTYPE,
+        FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, CONSTRUCTOR,
+        PROTOTYPE,
     },
     property::{Attribute, PropertyDescriptor, PropertyKey},
     realm::Realm,
@@ -405,85 +408,6 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
 
 // === Builder typestate ===
 
-#[derive(Debug)]
-enum BuiltInObjectInitializer {
-    Shared(JsObject),
-    Unique { object: Object, data: ObjectData },
-}
-
-impl BuiltInObjectInitializer {
-    /// Inserts a new property descriptor into the builtin.
-    fn insert<K, P>(&mut self, key: K, property: P)
-    where
-        K: Into<PropertyKey>,
-        P: Into<PropertyDescriptor>,
-    {
-        match self {
-            Self::Shared(obj) => obj.borrow_mut().insert(key, property),
-            Self::Unique { object, .. } => object.insert(key, property),
-        };
-    }
-
-    /// Sets the prototype of the builtin
-    fn set_prototype(&mut self, prototype: JsObject) {
-        match self {
-            Self::Shared(obj) => {
-                let mut obj = obj.borrow_mut();
-                obj.set_prototype(prototype);
-            }
-            Self::Unique { object, .. } => {
-                object.set_prototype(prototype);
-            }
-        }
-    }
-
-    /// Sets the `ObjectData` of the builtin.
-    ///
-    /// # Panics
-    ///
-    /// Panics if the builtin is a shared builtin and the data's vtable is not the same as the
-    /// builtin's vtable.
-    fn set_data(&mut self, new_data: ObjectData) {
-        match self {
-            Self::Shared(obj) => {
-                assert!(
-                    std::ptr::eq(obj.vtable(), new_data.internal_methods),
-                    "intrinsic object's vtable didn't match with new data"
-                );
-                *obj.borrow_mut().kind_mut() = new_data.kind;
-            }
-            Self::Unique { ref mut data, .. } => *data = new_data,
-        }
-    }
-
-    /// Gets a shared object from the builtin, transitioning its state if it's necessary.
-    fn as_shared(&mut self) -> JsObject {
-        match std::mem::replace(
-            self,
-            Self::Unique {
-                object: Object::default(),
-                data: ObjectData::ordinary(),
-            },
-        ) {
-            Self::Shared(obj) => {
-                *self = Self::Shared(obj.clone());
-                obj
-            }
-            Self::Unique { mut object, data } => {
-                *object.kind_mut() = data.kind;
-                let obj = JsObject::from_object_and_vtable(object, data.internal_methods);
-                *self = Self::Shared(obj.clone());
-                obj
-            }
-        }
-    }
-
-    /// Converts the builtin into a shared object.
-    fn into_shared(mut self) -> JsObject {
-        self.as_shared()
-    }
-}
-
 /// Marker for a constructor function.
 struct Constructor {
     prototype: JsObject,
@@ -528,11 +452,11 @@ struct OrdinaryObject;
 
 /// Applies the pending builder data to the object.
 trait ApplyToObject {
-    fn apply_to(self, object: &mut BuiltInObjectInitializer);
+    fn apply_to(self, object: &JsObject);
 }
 
 impl ApplyToObject for Constructor {
-    fn apply_to(self, object: &mut BuiltInObjectInitializer) {
+    fn apply_to(self, object: &JsObject) {
         object.insert(
             PROTOTYPE,
             PropertyDescriptor::builder()
@@ -542,15 +466,13 @@ impl ApplyToObject for Constructor {
                 .configurable(false),
         );
 
-        let object = object.as_shared();
-
         {
             let mut prototype = self.prototype.borrow_mut();
             prototype.set_prototype(self.inherits);
             prototype.insert(
                 CONSTRUCTOR,
                 PropertyDescriptor::builder()
-                    .value(object)
+                    .value(object.clone())
                     .writable(self.attributes.writable())
                     .enumerable(self.attributes.enumerable())
                     .configurable(self.attributes.configurable()),
@@ -560,21 +482,23 @@ impl ApplyToObject for Constructor {
 }
 
 impl ApplyToObject for ConstructorNoProto {
-    fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
+    fn apply_to(self, _: &JsObject) {}
 }
 
 impl ApplyToObject for OrdinaryFunction {
-    fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
+    fn apply_to(self, _: &JsObject) {}
 }
 
 impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
-    fn apply_to(self, object: &mut BuiltInObjectInitializer) {
-        let function = ObjectData::native_function(
-            NativeFunction::from_fn_ptr(self.function),
-            S::IS_CONSTRUCTOR,
-            self.realm,
-        );
-        object.set_data(function);
+    fn apply_to(self, object: &JsObject) {
+        {
+            let mut function =
+                GcRefMut::try_map(object.borrow_mut(), Object::as_native_function_mut)
+                    .expect("Builtin must be a function object");
+            function.f = NativeFunction::from_fn_ptr(self.function);
+            function.constructor = S::IS_CONSTRUCTOR.then_some(ConstructorKind::Base);
+            function.realm = Some(self.realm);
+        }
         object.insert(
             utf16!("length"),
             PropertyDescriptor::builder()
@@ -597,7 +521,7 @@ impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
 }
 
 impl ApplyToObject for OrdinaryObject {
-    fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
+    fn apply_to(self, _: &JsObject) {}
 }
 
 /// Builder for creating built-in objects, like `Array`.
@@ -608,30 +532,18 @@ impl ApplyToObject for OrdinaryObject {
 #[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"]
 struct BuiltInBuilder<'ctx, Kind> {
     realm: &'ctx Realm,
-    object: BuiltInObjectInitializer,
+    object: JsObject,
     kind: Kind,
     prototype: JsObject,
 }
 
 impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
-    // fn new(realm: &'ctx Realm) -> BuiltInBuilder<'ctx, OrdinaryObject> {
-    //     BuiltInBuilder {
-    //         realm,
-    //         object: BuiltInObjectInitializer::Unique {
-    //             object: Object::default(),
-    //             data: ObjectData::ordinary(),
-    //         },
-    //         kind: OrdinaryObject,
-    //         prototype: realm.intrinsics().constructors().object().prototype(),
-    //     }
-    // }
-
     fn with_intrinsic<I: IntrinsicObject>(
         realm: &'ctx Realm,
     ) -> BuiltInBuilder<'ctx, OrdinaryObject> {
         BuiltInBuilder {
             realm,
-            object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())),
+            object: I::get(realm.intrinsics()),
             kind: OrdinaryObject,
             prototype: realm.intrinsics().constructors().object().prototype(),
         }
@@ -836,12 +748,6 @@ impl BuiltInConstructorWithPrototype<'_> {
     }
 
     fn build(mut self) {
-        let function = ObjectKind::NativeFunction {
-            function: NativeFunction::from_fn_ptr(self.function),
-            constructor: Some(function::ConstructorKind::Base),
-            realm: self.realm.clone(),
-        };
-
         let length = self.length;
         let name = self.name.clone();
         let prototype = self.prototype.clone();
@@ -871,7 +777,12 @@ impl BuiltInConstructorWithPrototype<'_> {
         }
 
         let mut object = self.object.borrow_mut();
-        *object.kind_mut() = function;
+        let function = object
+            .as_native_function_mut()
+            .expect("Builtin must be a function object");
+        function.f = NativeFunction::from_fn_ptr(self.function);
+        function.constructor = Some(ConstructorKind::Base);
+        function.realm = Some(self.realm.clone());
         object
             .properties_mut()
             .shape
@@ -886,19 +797,18 @@ impl BuiltInConstructorWithPrototype<'_> {
     }
 
     fn build_without_prototype(mut self) {
-        let function = ObjectKind::NativeFunction {
-            function: NativeFunction::from_fn_ptr(self.function),
-            constructor: Some(function::ConstructorKind::Base),
-            realm: self.realm.clone(),
-        };
-
         let length = self.length;
         let name = self.name.clone();
         self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE);
         self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE);
 
         let mut object = self.object.borrow_mut();
-        *object.kind_mut() = function;
+        let function = object
+            .as_native_function_mut()
+            .expect("Builtin must be a function object");
+        function.f = NativeFunction::from_fn_ptr(self.function);
+        function.constructor = Some(ConstructorKind::Base);
+        function.realm = Some(self.realm.clone());
         object
             .properties_mut()
             .shape
@@ -940,11 +850,11 @@ impl BuiltInCallable<'_> {
 
     fn build(self) -> JsFunction {
         let object = self.realm.intrinsics().templates().function().create(
-            ObjectData::native_function(
-                NativeFunction::from_fn_ptr(self.function),
-                false,
-                self.realm.clone(),
-            ),
+            ObjectData::native_function(NativeFunctionObject {
+                f: NativeFunction::from_fn_ptr(self.function),
+                constructor: None,
+                realm: Some(self.realm.clone()),
+            }),
             vec![JsValue::new(self.length), JsValue::new(self.name)],
         );
 
@@ -968,7 +878,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
     ) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> {
         BuiltInBuilder {
             realm,
-            object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())),
+            object: I::get(realm.intrinsics()),
             kind: Callable {
                 function,
                 name: js_string!(""),
@@ -987,7 +897,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
     ) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> {
         BuiltInBuilder {
             realm,
-            object: BuiltInObjectInitializer::Shared(object),
+            object,
             kind: Callable {
                 function,
                 name: js_string!(""),
@@ -1025,12 +935,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
 
 impl<T> BuiltInBuilder<'_, T> {
     /// Adds a new static method to the builtin object.
-    fn static_method<B>(
-        mut self,
-        function: NativeFunctionPointer,
-        binding: B,
-        length: usize,
-    ) -> Self
+    fn static_method<B>(self, function: NativeFunctionPointer, binding: B, length: usize) -> Self
     where
         B: Into<FunctionBinding>,
     {
@@ -1052,7 +957,7 @@ impl<T> BuiltInBuilder<'_, T> {
     }
 
     /// Adds a new static data property to the builtin object.
-    fn static_property<K, V>(mut self, key: K, value: V, attribute: Attribute) -> Self
+    fn static_property<K, V>(self, key: K, value: V, attribute: Attribute) -> Self
     where
         K: Into<PropertyKey>,
         V: Into<JsValue>,
@@ -1096,22 +1001,22 @@ impl<FnTyp> BuiltInBuilder<'_, Callable<FnTyp>> {
 
 impl BuiltInBuilder<'_, OrdinaryObject> {
     /// Build the builtin object.
-    fn build(mut self) -> JsObject {
-        self.kind.apply_to(&mut self.object);
+    fn build(self) -> JsObject {
+        self.kind.apply_to(&self.object);
 
-        self.object.set_prototype(self.prototype);
+        self.object.set_prototype(Some(self.prototype));
 
-        self.object.into_shared()
+        self.object
     }
 }
 
 impl<FnTyp: ApplyToObject + IsConstructor> BuiltInBuilder<'_, Callable<FnTyp>> {
     /// Build the builtin callable.
-    fn build(mut self) -> JsFunction {
-        self.kind.apply_to(&mut self.object);
+    fn build(self) -> JsFunction {
+        self.kind.apply_to(&self.object);
 
-        self.object.set_prototype(self.prototype);
+        self.object.set_prototype(Some(self.prototype));
 
-        JsFunction::from_object_unchecked(self.object.into_shared())
+        JsFunction::from_object_unchecked(self.object)
     }
 }
diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs
index 9ca6a727e3f..966acc672ae 100644
--- a/boa_engine/src/builtins/regexp/mod.rs
+++ b/boa_engine/src/builtins/regexp/mod.rs
@@ -15,10 +15,9 @@ use crate::{
     error::JsNativeError,
     js_string,
     object::{
-        internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, ObjectKind,
-        CONSTRUCTOR,
+        internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, CONSTRUCTOR,
     },
-    property::{Attribute, PropertyDescriptorBuilder},
+    property::Attribute,
     realm::Realm,
     string::{common::StaticJsStrings, utf16, CodePoint},
     symbol::JsSymbol,
@@ -252,10 +251,11 @@ impl BuiltInConstructor for RegExp {
         };
 
         // 7. Let O be ? RegExpAlloc(newTarget).
-        let o = Self::alloc(new_target, context)?;
+        let proto =
+            get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?;
 
         // 8.Return ? RegExpInitialize(O, P, F).
-        Self::initialize(o, &p, &f, context)
+        Self::initialize(Some(proto), &p, &f, context)
     }
 }
 
@@ -294,50 +294,16 @@ impl RegExp {
         Ok(None)
     }
 
-    /// `22.2.3.2.1 RegExpAlloc ( newTarget )`
-    ///
-    /// More information:
-    ///  - [ECMAScript reference][spec]
-    ///
-    /// [spec]: https://tc39.es/ecma262/#sec-regexpalloc
-    pub(crate) fn alloc(new_target: &JsValue, context: &mut Context<'_>) -> JsResult<JsObject> {
-        // 1. Let obj be ? OrdinaryCreateFromConstructor(newTarget, "%RegExp.prototype%", « [[RegExpMatcher]], [[OriginalSource]], [[OriginalFlags]] »).
-        let proto =
-            get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?;
-        let obj = JsObject::from_proto_and_data_with_shared_shape(
-            context.root_shape(),
-            proto,
-            ObjectData::ordinary(),
-        );
-
-        // 2. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
-        obj.define_property_or_throw(
-            utf16!("lastIndex"),
-            PropertyDescriptorBuilder::new()
-                .writable(true)
-                .enumerable(false)
-                .configurable(false)
-                .build(),
-            context,
-        )
-        .expect("this cannot fail per spec");
-
-        // 3. Return obj.
-        Ok(obj)
-    }
-
-    /// `22.2.3.2.2 RegExpInitialize ( obj, pattern, flags )`
+    /// Compiles a `RegExp` from the provided pattern and flags.
     ///
-    /// More information:
-    ///  - [ECMAScript reference][spec]
+    /// Equivalent to the beginning of [`RegExpInitialize ( obj, pattern, flags )`][spec]
     ///
     /// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize
-    pub(crate) fn initialize(
-        obj: JsObject,
+    fn compile_native_regexp(
         pattern: &JsValue,
         flags: &JsValue,
         context: &mut Context<'_>,
-    ) -> JsResult<JsValue> {
+    ) -> JsResult<RegExp> {
         // 1. If pattern is undefined, let P be the empty String.
         // 2. Else, let P be ? ToString(pattern).
         let p = if pattern.is_undefined() {
@@ -362,43 +328,66 @@ impl RegExp {
             Ok(result) => result,
         };
 
-        // 10. If u is true, then
-        //     a. Let patternText be StringToCodePoints(P).
-        // 11. Else,
-        //     a. Let patternText be the result of interpreting each of P's 16-bit elements as a Unicode BMP code point. UTF-16 decoding is not applied to the elements.
-        // 12. Let parseResult be ParsePattern(patternText, u).
-        // 13. If parseResult is a non-empty List of SyntaxError objects, throw a SyntaxError exception.
-        // 14. Assert: parseResult is a Pattern Parse Node.
-        // 15. Set obj.[[OriginalSource]] to P.
-        // 16. Set obj.[[OriginalFlags]] to F.
-        // 17. Let capturingGroupsCount be CountLeftCapturingParensWithin(parseResult).
-        // 18. Let rer be the RegExp Record { [[IgnoreCase]]: i, [[Multiline]]: m, [[DotAll]]: s, [[Unicode]]: u, [[CapturingGroupsCount]]: capturingGroupsCount }.
-        // 19. Set obj.[[RegExpRecord]] to rer.
-        // 20. Set obj.[[RegExpMatcher]] to CompilePattern of parseResult with argument rer.
+        // 13. Let parseResult be ParsePattern(patternText, u, v).
+        // 14. If parseResult is a non-empty List of SyntaxError objects, throw a SyntaxError exception.
         let matcher =
-            match Regex::from_unicode(p.code_points().map(CodePoint::as_u32), Flags::from(flags)) {
-                Err(error) => {
-                    return Err(JsNativeError::syntax()
+            Regex::from_unicode(p.code_points().map(CodePoint::as_u32), Flags::from(flags))
+                .map_err(|error| {
+                    JsNativeError::syntax()
                         .with_message(format!("failed to create matcher: {}", error.text))
-                        .into());
-                }
-                Ok(val) => val,
-            };
-
-        let regexp = Self {
+                })?;
+
+        // 15. Assert: parseResult is a Pattern Parse Node.
+        // 16. Set obj.[[OriginalSource]] to P.
+        // 17. Set obj.[[OriginalFlags]] to F.
+        // 18. Let capturingGroupsCount be CountLeftCapturingParensWithin(parseResult).
+        // 19. Let rer be the RegExp Record { [[IgnoreCase]]: i, [[Multiline]]: m, [[DotAll]]: s, [[Unicode]]: u, [[UnicodeSets]]: v, [[CapturingGroupsCount]]: capturingGroupsCount }.
+        // 20. Set obj.[[RegExpRecord]] to rer.
+        // 21. Set obj.[[RegExpMatcher]] to CompilePattern of parseResult with argument rer.
+        Ok(RegExp {
             matcher,
             flags,
             original_source: p,
             original_flags: f,
-        };
-
-        // Safe to directly initialize since previous assertions ensure `obj` is a `Regexp` object.
-        *obj.borrow_mut().kind_mut() = ObjectKind::RegExp(Box::new(regexp));
+        })
+    }
 
-        // 16. Perform ? Set(obj, "lastIndex", +0𝔽, true).
-        obj.set(utf16!("lastIndex"), 0, true, context)?;
+    /// `RegExpInitialize ( obj, pattern, flags )`
+    ///
+    /// If prototype is `None`, initializes the prototype to `%RegExp%.prototype`.
+    ///
+    /// More information:
+    ///  - [ECMAScript reference][spec]
+    ///
+    /// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize
+    pub(crate) fn initialize(
+        prototype: Option<JsObject>,
+        pattern: &JsValue,
+        flags: &JsValue,
+        context: &mut Context<'_>,
+    ) -> JsResult<JsValue> {
+        // Has the steps  of `RegExpInitialize`.
+        let regexp = Self::compile_native_regexp(pattern, flags, context)?;
+        let data = ObjectData::regexp(regexp);
+
+        // 22. Perform ? Set(obj, "lastIndex", +0𝔽, true).
+        let obj = if let Some(prototype) = prototype {
+            let mut template = context
+                .intrinsics()
+                .templates()
+                .regexp_without_proto()
+                .clone();
+            template.set_prototype(prototype);
+            template.create(data, vec![0.into()])
+        } else {
+            context
+                .intrinsics()
+                .templates()
+                .regexp()
+                .create(data, vec![0.into()])
+        };
 
-        // 16. Return obj.
+        // 23. Return obj.
         Ok(obj.into())
     }
 
@@ -410,10 +399,8 @@ impl RegExp {
     /// [spec]: https://tc39.es/ecma262/#sec-regexpcreate
     pub(crate) fn create(p: &JsValue, f: &JsValue, context: &mut Context<'_>) -> JsResult<JsValue> {
         // 1. Let obj be ? RegExpAlloc(%RegExp%).
-        let obj = Self::alloc(&context.global_object().get(Self::NAME, context)?, context)?;
-
         // 2. Return ? RegExpInitialize(obj, P, F).
-        Self::initialize(obj, p, f, context)
+        Self::initialize(None, p, f, context)
     }
 
     /// `get RegExp [ @@species ]`
@@ -1859,6 +1846,7 @@ impl RegExp {
     fn compile(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
         // 1. Let O be the this value.
         // 2. Perform ? RequireInternalSlot(O, [[RegExpMatcher]]).
+
         let this = this
             .as_object()
             .filter(|o| o.borrow().is_regexp())
@@ -1893,8 +1881,20 @@ impl RegExp {
             //     b. Let F be flags.
             (pattern.clone(), flags.clone())
         };
+
+        let regexp = Self::compile_native_regexp(&pattern, &flags, context)?;
+
         // 5. Return ? RegExpInitialize(O, P, F).
-        Self::initialize(this, &pattern, &flags, context)
+        {
+            let mut obj = this.borrow_mut();
+
+            *obj.as_regexp_mut()
+                .expect("already checked that the object was a RegExp") = regexp;
+        }
+
+        this.set(utf16!("lastIndex"), 0, true, context)?;
+
+        Ok(this.into())
     }
 }
 
diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs
index 18cb4bb2596..14c7ad31a63 100644
--- a/boa_engine/src/context/intrinsics.rs
+++ b/boa_engine/src/context/intrinsics.rs
@@ -1324,6 +1324,9 @@ pub(crate) struct ObjectTemplates {
     bigint: ObjectTemplate,
     boolean: ObjectTemplate,
 
+    regexp: ObjectTemplate,
+    regexp_without_proto: ObjectTemplate,
+
     unmapped_arguments: ObjectTemplate,
     mapped_arguments: ObjectTemplate,
 
@@ -1369,6 +1372,12 @@ impl ObjectTemplates {
         );
         string.set_prototype(constructors.string().prototype());
 
+        let mut regexp_without_prototype = ObjectTemplate::new(root_shape);
+        regexp_without_prototype.property(js_string!("lastIndex").into(), Attribute::WRITABLE);
+
+        let mut regexp = regexp_without_prototype.clone();
+        regexp.set_prototype(constructors.regexp().prototype());
+
         let name_property_key: PropertyKey = js_string!("name").into();
         let mut function = ObjectTemplate::new(root_shape);
         function.property(
@@ -1474,6 +1483,8 @@ impl ObjectTemplates {
             symbol,
             bigint,
             boolean,
+            regexp,
+            regexp_without_proto: regexp_without_prototype,
             unmapped_arguments,
             mapped_arguments,
             function_with_prototype,
@@ -1564,6 +1575,25 @@ impl ObjectTemplates {
         &self.boolean
     }
 
+    /// Cached regexp object template.
+    ///
+    /// Transitions:
+    ///
+    /// 1. `"lastIndex"`: (`WRITABLE` , `PERMANENT`,`NON_ENUMERABLE`)
+    pub(crate) const fn regexp(&self) -> &ObjectTemplate {
+        &self.regexp
+    }
+
+    /// Cached regexp object template without `__proto__` template.
+    ///
+    /// Transitions:
+    ///
+    /// 1. `"lastIndex"`: (`WRITABLE` , `PERMANENT`,`NON_ENUMERABLE`)
+    /// 2. `__proto__`: `RegExp.prototype`
+    pub(crate) const fn regexp_without_proto(&self) -> &ObjectTemplate {
+        &self.regexp_without_proto
+    }
+
     /// Cached unmapped arguments object template.
     ///
     /// Transitions:
diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs
index 9fc0c2ba812..638058629c6 100644
--- a/boa_engine/src/context/mod.rs
+++ b/boa_engine/src/context/mod.rs
@@ -75,7 +75,9 @@ use self::intrinsics::StandardConstructor;
 /// let arg = ObjectInitializer::new(&mut context)
 ///     .property(js_string!("x"), 12, Attribute::READONLY)
 ///     .build();
-/// context.register_global_property(js_string!("arg"), arg, Attribute::all());
+/// context
+///     .register_global_property(js_string!("arg"), arg, Attribute::all())
+///     .expect("property shouldn't exist");
 ///
 /// let value = context.eval(Source::from_bytes("test(arg)")).unwrap();
 ///
diff --git a/boa_engine/src/module/mod.rs b/boa_engine/src/module/mod.rs
index a2941118e41..65223788497 100644
--- a/boa_engine/src/module/mod.rs
+++ b/boa_engine/src/module/mod.rs
@@ -47,7 +47,7 @@ use crate::{
     builtins::promise::{PromiseCapability, PromiseState},
     environments::DeclarativeEnvironment,
     js_string,
-    object::{FunctionObjectBuilder, JsObject, JsPromise, ObjectData},
+    object::{JsObject, JsPromise, ObjectData},
     realm::Realm,
     Context, HostDefined, JsError, JsResult, JsString, JsValue, NativeFunction,
 };
@@ -430,7 +430,7 @@ impl Module {
             ModuleKind::Synthetic(synth) => {
                 // a. Let promise be ! module.Evaluate().
                 let promise: JsPromise = synth.evaluate(context);
-                let state = promise.state()?;
+                let state = promise.state();
                 match state {
                     PromiseState::Pending => {
                         unreachable!("b. Assert: promise.[[PromiseState]] is not pending.")
@@ -467,59 +467,53 @@ impl Module {
     ///
     /// loader.insert(Path::new("main.mjs").to_path_buf(), module.clone());
     ///
-    /// let promise = module.load_link_evaluate(context).unwrap();
+    /// let promise = module.load_link_evaluate(context);
     ///
     /// context.run_jobs();
     ///
     /// assert_eq!(
-    ///     promise.state().unwrap(),
+    ///     promise.state(),
     ///     PromiseState::Fulfilled(JsValue::undefined())
     /// );
     /// ```
     #[allow(dropping_copy_types)]
     #[inline]
-    pub fn load_link_evaluate(&self, context: &mut Context<'_>) -> JsResult<JsPromise> {
+    pub fn load_link_evaluate(&self, context: &mut Context<'_>) -> JsPromise {
         let main_timer = Profiler::global().start_event("Module evaluation", "Main");
 
         let promise = self
             .load(context)
             .then(
                 Some(
-                    FunctionObjectBuilder::new(
-                        context.realm(),
-                        NativeFunction::from_copy_closure_with_captures(
-                            |_, _, module, context| {
-                                module.link(context)?;
-                                Ok(JsValue::undefined())
-                            },
-                            self.clone(),
-                        ),
+                    NativeFunction::from_copy_closure_with_captures(
+                        |_, _, module, context| {
+                            module.link(context)?;
+                            Ok(JsValue::undefined())
+                        },
+                        self.clone(),
                     )
-                    .build(),
+                    .to_js_function(context.realm()),
                 ),
                 None,
                 context,
-            )?
+            )
             .then(
                 Some(
-                    FunctionObjectBuilder::new(
-                        context.realm(),
-                        NativeFunction::from_copy_closure_with_captures(
-                            |_, _, module, context| Ok(module.evaluate(context).into()),
-                            self.clone(),
-                        ),
+                    NativeFunction::from_copy_closure_with_captures(
+                        |_, _, module, context| Ok(module.evaluate(context).into()),
+                        self.clone(),
                     )
-                    .build(),
+                    .to_js_function(context.realm()),
                 ),
                 None,
                 context,
-            )?;
+            );
 
         // The main_timer needs to be dropped before the Profiler is.
         drop(main_timer);
         Profiler::global().drop();
 
-        Ok(promise)
+        promise
     }
 
     /// Abstract operation [`GetModuleNamespace ( module )`][spec].
diff --git a/boa_engine/src/module/synthetic.rs b/boa_engine/src/module/synthetic.rs
index 01a30514589..9587e90ada6 100644
--- a/boa_engine/src/module/synthetic.rs
+++ b/boa_engine/src/module/synthetic.rs
@@ -198,7 +198,6 @@ impl SyntheticModule {
     pub(super) fn load(context: &mut Context<'_>) -> JsPromise {
         // 1. Return ! PromiseResolve(%Promise%, undefined).
         JsPromise::resolve(JsValue::undefined(), context)
-            .expect("creating a promise from the %Promise% constructor must not fail")
     }
 
     /// Concrete method [`GetExportedNames ( [ exportStarSet ] )`][spec].
diff --git a/boa_engine/src/native_function.rs b/boa_engine/src/native_function.rs
index 20d65c3ed5c..2f707716c7a 100644
--- a/boa_engine/src/native_function.rs
+++ b/boa_engine/src/native_function.rs
@@ -5,7 +5,12 @@
 
 use boa_gc::{custom_trace, Finalize, Gc, Trace};
 
-use crate::{object::JsPromise, Context, JsResult, JsValue};
+use crate::{
+    builtins::function::ConstructorKind,
+    object::{FunctionObjectBuilder, JsFunction, JsPromise},
+    realm::Realm,
+    Context, JsResult, JsValue,
+};
 
 /// The required signature for all native built-in function pointers.
 ///
@@ -56,6 +61,25 @@ where
     }
 }
 
+#[derive(Clone, Debug, Finalize)]
+/// The data of an object containing a `NativeFunction`.
+pub struct NativeFunctionObject {
+    /// The rust function.
+    pub(crate) f: NativeFunction,
+    /// The kind of the function constructor if it is a constructor.
+    pub(crate) constructor: Option<ConstructorKind>,
+    /// The [`Realm`] in which the function is defined, or `None` if the realm is uninitialized.
+    pub(crate) realm: Option<Realm>,
+}
+
+// SAFETY: this traces all fields that need to be traced by the GC.
+unsafe impl Trace for NativeFunctionObject {
+    custom_trace!(this, {
+        mark(&this.f);
+        mark(&this.realm);
+    });
+}
+
 /// A callable Rust function that can be invoked by the engine.
 ///
 /// `NativeFunction` functions are divided in two:
@@ -280,4 +304,12 @@ impl NativeFunction {
             Inner::Closure(ref c) => c.call(this, args, context),
         }
     }
+
+    /// Converts this `NativeFunction` into a `JsFunction` without setting its name or length.
+    ///
+    /// Useful to create functions that will only be used once, such as callbacks.
+    #[must_use]
+    pub fn to_js_function(self, realm: &Realm) -> JsFunction {
+        FunctionObjectBuilder::new(realm, self).build()
+    }
 }
diff --git a/boa_engine/src/object/builtins/jsfunction.rs b/boa_engine/src/object/builtins/jsfunction.rs
index d252b84bcd8..046f68a2f0d 100644
--- a/boa_engine/src/object/builtins/jsfunction.rs
+++ b/boa_engine/src/object/builtins/jsfunction.rs
@@ -1,13 +1,15 @@
 //! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object
 use crate::{
+    builtins::function::ConstructorKind,
+    native_function::NativeFunctionObject,
     object::{
         internal_methods::function::{
             NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS,
         },
-        JsObject, JsObjectType, Object,
+        JsObject, JsObjectType, Object, ObjectKind,
     },
     value::TryFromJs,
-    Context, JsNativeError, JsResult, JsValue,
+    Context, JsNativeError, JsResult, JsValue, NativeFunction,
 };
 use boa_gc::{Finalize, Trace};
 use std::ops::Deref;
@@ -32,7 +34,11 @@ impl JsFunction {
     pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self {
         Self {
             inner: JsObject::from_object_and_vtable(
-                Object::default(),
+                Object::with_kind(ObjectKind::NativeFunction(NativeFunctionObject {
+                    f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())),
+                    constructor: constructor.then_some(ConstructorKind::Base),
+                    realm: None,
+                })),
                 if constructor {
                     &NATIVE_CONSTRUCTOR_INTERNAL_METHODS
                 } else {
diff --git a/boa_engine/src/object/builtins/jspromise.rs b/boa_engine/src/object/builtins/jspromise.rs
index 2c006944642..9d694020802 100644
--- a/boa_engine/src/object/builtins/jspromise.rs
+++ b/boa_engine/src/object/builtins/jspromise.rs
@@ -8,9 +8,8 @@ use crate::{
         promise::{PromiseState, ResolvingFunctions},
         Promise,
     },
-    context::intrinsics::StandardConstructors,
     job::NativeJob,
-    object::{FunctionObjectBuilder, JsObject, JsObjectType, ObjectData},
+    object::{JsObject, JsObjectType, ObjectData},
     value::TryFromJs,
     Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction,
 };
@@ -52,56 +51,45 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
 ///         Ok(JsValue::undefined())
 ///     },
 ///     context,
-/// )?;
+/// );
 ///
 /// let promise = promise
 ///     .then(
 ///         Some(
-///             FunctionObjectBuilder::new(
-///                 context.realm(),
-///                 NativeFunction::from_fn_ptr(|_, args, _| {
-///                     Err(JsError::from_opaque(
-///                         args.get_or_undefined(0).clone(),
-///                     )
+///             NativeFunction::from_fn_ptr(|_, args, _| {
+///                 Err(JsError::from_opaque(args.get_or_undefined(0).clone())
 ///                     .into())
-///                 }),
-///             )
-///             .build(),
+///             })
+///             .to_js_function(context.realm()),
 ///         ),
 ///         None,
 ///         context,
-///     )?
+///     )
 ///     .catch(
-///         FunctionObjectBuilder::new(
-///             context.realm(),
-///             NativeFunction::from_fn_ptr(|_, args, _| {
-///                 Ok(args.get_or_undefined(0).clone())
-///             }),
-///         )
-///         .build(),
+///         NativeFunction::from_fn_ptr(|_, args, _| {
+///             Ok(args.get_or_undefined(0).clone())
+///         })
+///         .to_js_function(context.realm()),
 ///         context,
-///     )?
+///     )
 ///     .finally(
-///         FunctionObjectBuilder::new(
-///             context.realm(),
-///             NativeFunction::from_fn_ptr(|_, _, context| {
-///                 context.global_object().clone().set(
-///                     js_string!("finally"),
-///                     JsValue::from(true),
-///                     true,
-///                     context,
-///                 )?;
-///                 Ok(JsValue::undefined())
-///             }),
-///         )
-///         .build(),
+///         NativeFunction::from_fn_ptr(|_, _, context| {
+///             context.global_object().clone().set(
+///                 js_string!("finally"),
+///                 JsValue::from(true),
+///                 true,
+///                 context,
+///             )?;
+///             Ok(JsValue::undefined())
+///         })
+///         .to_js_function(context.realm()),
 ///         context,
-///     )?;
+///     );
 ///
 /// context.run_jobs();
 ///
 /// assert_eq!(
-///     promise.state()?,
+///     promise.state(),
 ///     PromiseState::Fulfilled(js_string!("hello world!").into())
 /// );
 ///
@@ -160,12 +148,12 @@ impl JsPromise {
     ///         Ok(JsValue::undefined())
     ///     },
     ///     context,
-    /// )?;
+    /// );
     ///
     /// context.run_jobs();
     ///
     /// assert_eq!(
-    ///     promise.state()?,
+    ///     promise.state(),
     ///     PromiseState::Fulfilled(js_string!("hello world").into())
     /// );
     /// # Ok(())
@@ -173,7 +161,7 @@ impl JsPromise {
     /// ```
     ///
     /// [`Promise()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
-    pub fn new<F>(executor: F, context: &mut Context<'_>) -> JsResult<Self>
+    pub fn new<F>(executor: F, context: &mut Context<'_>) -> Self
     where
         F: FnOnce(&ResolvingFunctions, &mut Context<'_>) -> JsResult<JsValue>,
     {
@@ -188,10 +176,11 @@ impl JsPromise {
             let e = e.to_opaque(context);
             resolvers
                 .reject
-                .call(&JsValue::undefined(), &[e], context)?;
+                .call(&JsValue::undefined(), &[e], context)
+                .expect("default `reject` function cannot throw");
         }
 
-        Ok(Self { inner: promise })
+        Self { inner: promise }
     }
 
     /// Creates a new pending promise and returns it and its associated `ResolvingFunctions`.
@@ -214,13 +203,13 @@ impl JsPromise {
     ///
     /// let (promise, resolvers) = JsPromise::new_pending(context);
     ///
-    /// assert_eq!(promise.state()?, PromiseState::Pending);
+    /// assert_eq!(promise.state(), PromiseState::Pending);
     ///
     /// resolvers
     ///     .reject
     ///     .call(&JsValue::undefined(), &[5.into()], context)?;
     ///
-    /// assert_eq!(promise.state()?, PromiseState::Rejected(5.into()));
+    /// assert_eq!(promise.state(), PromiseState::Rejected(5.into()));
     ///
     /// # Ok(())
     /// # }
@@ -262,7 +251,7 @@ impl JsPromise {
     /// let promise = JsPromise::from_object(promise)?;
     ///
     /// assert_eq!(
-    ///     promise.state()?,
+    ///     promise.state(),
     ///     PromiseState::Fulfilled(JsValue::undefined())
     /// );
     ///
@@ -295,7 +284,6 @@ impl JsPromise {
     /// #    builtins::promise::PromiseState,
     /// #    Context, JsResult, JsValue
     /// # };
-    /// # fn main() -> Result<(), Box<dyn Error>> {
     /// async fn f() -> JsResult<JsValue> {
     ///     Ok(JsValue::null())
     /// }
@@ -305,9 +293,7 @@ impl JsPromise {
     ///
     /// context.run_jobs();
     ///
-    /// assert_eq!(promise.state()?, PromiseState::Fulfilled(JsValue::null()));
-    /// # Ok(())
-    /// # }
+    /// assert_eq!(promise.state(), PromiseState::Fulfilled(JsValue::null()));
     /// ```
     ///
     /// [async_fn]: crate::native_function::NativeFunction::from_async_fn
@@ -353,29 +339,26 @@ impl JsPromise {
     /// #    builtins::promise::PromiseState,
     /// #    Context, js_string
     /// # };
-    /// # fn main() -> Result<(), Box<dyn Error>> {
     /// let context = &mut Context::default();
     ///
-    /// let promise = JsPromise::resolve(js_string!("resolved!"), context)?;
+    /// let promise = JsPromise::resolve(js_string!("resolved!"), context);
     ///
     /// assert_eq!(
-    ///     promise.state()?,
+    ///     promise.state(),
     ///     PromiseState::Fulfilled(js_string!("resolved!").into())
     /// );
-    ///
-    /// # Ok(())
-    /// # }
     /// ```
     ///
     /// [`Promise.resolve()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
     /// [thenables]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables
-    pub fn resolve<V: Into<JsValue>>(value: V, context: &mut Context<'_>) -> JsResult<Self> {
+    pub fn resolve<V: Into<JsValue>>(value: V, context: &mut Context<'_>) -> Self {
         Promise::promise_resolve(
             &context.intrinsics().constructors().promise().constructor(),
             value.into(),
             context,
         )
         .and_then(Self::from_object)
+        .expect("default resolving functions cannot throw and must return a promise")
     }
 
     /// Creates a `JsPromise` that is rejected with the reason `error`.
@@ -394,32 +377,29 @@ impl JsPromise {
     /// #    builtins::promise::PromiseState,
     /// #    Context, js_string, JsError
     /// # };
-    /// # fn main() -> Result<(), Box<dyn Error>> {
     /// let context = &mut Context::default();
     ///
     /// let promise = JsPromise::reject(
     ///     JsError::from_opaque(js_string!("oops!").into()),
     ///     context,
-    /// )?;
+    /// );
     ///
     /// assert_eq!(
-    ///     promise.state()?,
+    ///     promise.state(),
     ///     PromiseState::Rejected(js_string!("oops!").into())
     /// );
-    ///
-    /// # Ok(())
-    /// # }
     /// ```
     ///
     /// [`Promise.reject`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
     /// [thenable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables
-    pub fn reject<E: Into<JsError>>(error: E, context: &mut Context<'_>) -> JsResult<Self> {
+    pub fn reject<E: Into<JsError>>(error: E, context: &mut Context<'_>) -> Self {
         Promise::promise_reject(
             &context.intrinsics().constructors().promise().constructor(),
             &error.into(),
             context,
         )
         .and_then(Self::from_object)
+        .expect("default resolving functions cannot throw and must return a promise")
     }
 
     /// Gets the current state of the promise.
@@ -433,26 +413,21 @@ impl JsPromise {
     /// #    builtins::promise::PromiseState,
     /// #    Context
     /// # };
-    /// # fn main() -> Result<(), Box<dyn Error>> {
     /// let context = &mut Context::default();
     ///
     /// let promise = JsPromise::new_pending(context).0;
     ///
-    /// assert_eq!(promise.state()?, PromiseState::Pending);
-    ///
-    /// # Ok(())
-    /// # }
+    /// assert_eq!(promise.state(), PromiseState::Pending);
     /// ```
     #[inline]
-    pub fn state(&self) -> JsResult<PromiseState> {
-        // TODO: if we can guarantee that objects cannot change type after creation,
-        // we can remove this throw.
-        let promise = self.inner.borrow();
-        let promise = promise
+    #[must_use]
+    pub fn state(&self) -> PromiseState {
+        self.inner
+            .borrow()
             .as_promise()
-            .ok_or_else(|| JsNativeError::typ().with_message("object is not a Promise"))?;
-
-        Ok(promise.state().clone())
+            .expect("objects cannot change type after creation")
+            .state()
+            .clone()
     }
 
     /// Schedules callback functions to run when the promise settles.
@@ -489,7 +464,6 @@ impl JsPromise {
     /// #     object::{builtins::JsPromise, FunctionObjectBuilder},
     /// #     Context, JsArgs, JsError, JsValue, NativeFunction,
     /// # };
-    /// # fn main() -> Result<(), Box<dyn Error>> {
     /// let context = &mut Context::default();
     ///
     /// let promise = JsPromise::new(
@@ -502,44 +476,40 @@ impl JsPromise {
     ///         Ok(JsValue::undefined())
     ///     },
     ///     context,
-    /// )?
+    /// )
     /// .then(
     ///     Some(
-    ///         FunctionObjectBuilder::new(
-    ///             context.realm(),
-    ///             NativeFunction::from_fn_ptr(|_, args, context| {
-    ///                 args.get_or_undefined(0)
-    ///                     .to_string(context)
-    ///                     .map(JsValue::from)
-    ///             }),
-    ///         )
-    ///         .build(),
+    ///         NativeFunction::from_fn_ptr(|_, args, context| {
+    ///             args.get_or_undefined(0)
+    ///                 .to_string(context)
+    ///                 .map(JsValue::from)
+    ///         })
+    ///         .to_js_function(context.realm()),
     ///     ),
     ///     None,
     ///     context,
-    /// )?;
+    /// );
     ///
     /// context.run_jobs();
     ///
     /// assert_eq!(
-    ///     promise.state()?,
+    ///     promise.state(),
     ///     PromiseState::Fulfilled(js_string!("255.255").into())
     /// );
-    ///
-    /// # Ok(())
-    /// # }
     /// ```
     ///
     /// [`Promise.prototype.then`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
     #[inline]
+    #[allow(clippy::return_self_not_must_use)] // Could just be used to add handlers on an existing promise
     pub fn then(
         &self,
         on_fulfilled: Option<JsFunction>,
         on_rejected: Option<JsFunction>,
         context: &mut Context<'_>,
-    ) -> JsResult<Self> {
-        let result_promise = Promise::inner_then(self, on_fulfilled, on_rejected, context)?;
-        Self::from_object(result_promise)
+    ) -> Self {
+        Promise::inner_then(self, on_fulfilled, on_rejected, context)
+            .and_then(Self::from_object)
+            .expect("`inner_then` cannot fail for native `JsPromise`")
     }
 
     /// Schedules a callback to run when the promise is rejected.
@@ -559,7 +529,6 @@ impl JsPromise {
     /// #     object::{builtins::JsPromise, FunctionObjectBuilder},
     /// #     Context, JsArgs, JsNativeError, JsValue, NativeFunction,
     /// # };
-    /// # fn main() -> Result<(), Box<dyn Error>> {
     /// let context = &mut Context::default();
     ///
     /// let promise = JsPromise::new(
@@ -574,35 +543,30 @@ impl JsPromise {
     ///         Ok(JsValue::undefined())
     ///     },
     ///     context,
-    /// )?
+    /// )
     /// .catch(
-    ///     FunctionObjectBuilder::new(
-    ///         context.realm(),
-    ///         NativeFunction::from_fn_ptr(|_, args, context| {
-    ///             args.get_or_undefined(0)
-    ///                 .to_string(context)
-    ///                 .map(JsValue::from)
-    ///         }),
-    ///     )
-    ///     .build(),
+    ///     NativeFunction::from_fn_ptr(|_, args, context| {
+    ///         args.get_or_undefined(0)
+    ///             .to_string(context)
+    ///             .map(JsValue::from)
+    ///     })
+    ///     .to_js_function(context.realm()),
     ///     context,
-    /// )?;
+    /// );
     ///
     /// context.run_jobs();
     ///
     /// assert_eq!(
-    ///     promise.state()?,
+    ///     promise.state(),
     ///     PromiseState::Fulfilled(js_string!("TypeError: thrown").into())
     /// );
-    ///
-    /// # Ok(())
-    /// # }
     /// ```
     ///
     /// [`Promise.prototype.catch`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
     /// [then]: JsPromise::then
     #[inline]
-    pub fn catch(&self, on_rejected: JsFunction, context: &mut Context<'_>) -> JsResult<Self> {
+    #[allow(clippy::return_self_not_must_use)] // Could just be used to add a handler on an existing promise
+    pub fn catch(&self, on_rejected: JsFunction, context: &mut Context<'_>) -> Self {
         self.then(None, Some(on_rejected), context)
     }
 
@@ -633,7 +597,7 @@ impl JsPromise {
     ///     js_string!("finally"),
     ///     false,
     ///     Attribute::all(),
-    /// );
+    /// )?;
     ///
     /// let promise = JsPromise::new(
     ///     |resolvers, context| {
@@ -647,23 +611,20 @@ impl JsPromise {
     ///         Ok(JsValue::undefined())
     ///     },
     ///     context,
-    /// )?
+    /// )
     /// .finally(
-    ///     FunctionObjectBuilder::new(
-    ///         context.realm(),
-    ///         NativeFunction::from_fn_ptr(|_, _, context| {
-    ///             context.global_object().clone().set(
-    ///                 js_string!("finally"),
-    ///                 JsValue::from(true),
-    ///                 true,
-    ///                 context,
-    ///             )?;
-    ///             Ok(JsValue::undefined())
-    ///         }),
-    ///     )
-    ///     .build(),
+    ///     NativeFunction::from_fn_ptr(|_, _, context| {
+    ///         context.global_object().clone().set(
+    ///             js_string!("finally"),
+    ///             JsValue::from(true),
+    ///             true,
+    ///             context,
+    ///         )?;
+    ///         Ok(JsValue::undefined())
+    ///     })
+    ///     .to_js_function(context.realm()),
     ///     context,
-    /// )?;
+    /// );
     ///
     /// context.run_jobs();
     ///
@@ -682,10 +643,16 @@ impl JsPromise {
     /// [`Promise.prototype.finally()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
     /// [then]: JsPromise::then
     #[inline]
-    pub fn finally(&self, on_finally: JsFunction, context: &mut Context<'_>) -> JsResult<Self> {
-        let c = self.species_constructor(StandardConstructors::promise, context)?;
-        let (then, catch) = Promise::then_catch_finally_closures(c, on_finally, context);
-        self.then(Some(then), Some(catch), context)
+    #[allow(clippy::return_self_not_must_use)] // Could just be used to add a handler on an existing promise
+    pub fn finally(&self, on_finally: JsFunction, context: &mut Context<'_>) -> Self {
+        let (then, catch) = Promise::then_catch_finally_closures(
+            context.intrinsics().constructors().promise().constructor(),
+            on_finally,
+            context,
+        );
+        Promise::inner_then(self, Some(then), Some(catch), context)
+            .and_then(Self::from_object)
+            .expect("`inner_then` cannot fail for native `JsPromise`")
     }
 
     /// Waits for a list of promises to settle with fulfilled values, rejecting the aggregate promise
@@ -707,26 +674,26 @@ impl JsPromise {
     ///
     /// let promise1 = JsPromise::all(
     ///     [
-    ///         JsPromise::resolve(0, context)?,
-    ///         JsPromise::resolve(2, context)?,
-    ///         JsPromise::resolve(4, context)?,
+    ///         JsPromise::resolve(0, context),
+    ///         JsPromise::resolve(2, context),
+    ///         JsPromise::resolve(4, context),
     ///     ],
     ///     context,
-    /// )?;
+    /// );
     ///
     /// let promise2 = JsPromise::all(
     ///     [
-    ///         JsPromise::resolve(1, context)?,
-    ///         JsPromise::reject(JsNativeError::typ(), context)?,
-    ///         JsPromise::resolve(3, context)?,
+    ///         JsPromise::resolve(1, context),
+    ///         JsPromise::reject(JsNativeError::typ(), context),
+    ///         JsPromise::resolve(3, context),
     ///     ],
     ///     context,
-    /// )?;
+    /// );
     ///
     /// context.run_jobs();
     ///
     /// let array = promise1
-    ///     .state()?
+    ///     .state()
     ///     .as_fulfilled()
     ///     .and_then(JsValue::as_object)
     ///     .unwrap()
@@ -736,7 +703,7 @@ impl JsPromise {
     /// assert_eq!(array.at(1, context)?, 2.into());
     /// assert_eq!(array.at(2, context)?, 4.into());
     ///
-    /// let error = promise2.state()?.as_rejected().unwrap().clone();
+    /// let error = promise2.state().as_rejected().unwrap().clone();
     /// assert_eq!(error.to_string(context)?, js_string!("TypeError"));
     ///
     /// # Ok(())
@@ -744,7 +711,7 @@ impl JsPromise {
     /// ```
     ///
     /// [`Promise.all`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
-    pub fn all<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self>
+    pub fn all<I>(promises: I, context: &mut Context<'_>) -> Self
     where
         I: IntoIterator<Item = Self>,
     {
@@ -757,12 +724,15 @@ impl JsPromise {
             .constructor()
             .into();
 
-        let value = Promise::all(c, &[promises.into()], context)?;
-        let value = value
+        let value = Promise::all(c, &[promises.into()], context)
+            .expect("Promise.all cannot fail with the default `%Promise%` constructor");
+
+        let object = value
             .as_object()
-            .expect("Promise.all always returns an object on success");
+            .expect("`Promise.all` always returns an object on success");
 
-        Self::from_object(value.clone())
+        Self::from_object(object.clone())
+        .expect("`Promise::all` with the  default `%Promise%` constructor always returns a native `JsPromise`")
     }
 
     /// Waits for a list of promises to settle, fulfilling with an array of the outcomes of every
@@ -784,17 +754,17 @@ impl JsPromise {
     ///
     /// let promise = JsPromise::all_settled(
     ///     [
-    ///         JsPromise::resolve(1, context)?,
-    ///         JsPromise::reject(JsNativeError::typ(), context)?,
-    ///         JsPromise::resolve(3, context)?,
+    ///         JsPromise::resolve(1, context),
+    ///         JsPromise::reject(JsNativeError::typ(), context),
+    ///         JsPromise::resolve(3, context),
     ///     ],
     ///     context,
-    /// )?;
+    /// );
     ///
     /// context.run_jobs();
     ///
     /// let array = promise
-    ///     .state()?
+    ///     .state()
     ///     .as_fulfilled()
     ///     .and_then(JsValue::as_object)
     ///     .unwrap()
@@ -830,7 +800,7 @@ impl JsPromise {
     /// ```
     ///
     /// [`Promise.allSettled`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
-    pub fn all_settled<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self>
+    pub fn all_settled<I>(promises: I, context: &mut Context<'_>) -> Self
     where
         I: IntoIterator<Item = Self>,
     {
@@ -843,12 +813,15 @@ impl JsPromise {
             .constructor()
             .into();
 
-        let value = Promise::all_settled(c, &[promises.into()], context)?;
-        let value = value
+        let value = Promise::all_settled(c, &[promises.into()], context)
+            .expect("`Promise.all_settled` cannot fail with the default `%Promise%` constructor");
+
+        let object = value
             .as_object()
-            .expect("Promise.allSettled always returns an object on success");
+            .expect("`Promise.all_settled` always returns an object on success");
 
-        Self::from_object(value.clone())
+        Self::from_object(object.clone())
+        .expect("`Promise::all_settled` with the  default `%Promise%` constructor always returns a native `JsPromise`")
     }
 
     /// Returns the first promise that fulfills from a list of promises.
@@ -869,32 +842,28 @@ impl JsPromise {
     /// #     object::builtins::JsPromise,
     /// #     Context, JsNativeError,
     /// # };
-    /// # fn main() -> Result<(), Box<dyn Error>> {
     /// let context = &mut Context::default();
     ///
     /// let promise = JsPromise::any(
     ///     [
-    ///         JsPromise::reject(JsNativeError::syntax(), context)?,
-    ///         JsPromise::reject(JsNativeError::typ(), context)?,
-    ///         JsPromise::resolve(js_string!("fulfilled"), context)?,
-    ///         JsPromise::reject(JsNativeError::range(), context)?,
+    ///         JsPromise::reject(JsNativeError::syntax(), context),
+    ///         JsPromise::reject(JsNativeError::typ(), context),
+    ///         JsPromise::resolve(js_string!("fulfilled"), context),
+    ///         JsPromise::reject(JsNativeError::range(), context),
     ///     ],
     ///     context,
-    /// )?;
+    /// );
     ///
     /// context.run_jobs();
     ///
     /// assert_eq!(
-    ///     promise.state()?,
+    ///     promise.state(),
     ///     PromiseState::Fulfilled(js_string!("fulfilled").into())
     /// );
-    ///
-    /// # Ok(())
-    /// # }
     /// ```
     ///
     /// [`Promise.any`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
-    pub fn any<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self>
+    pub fn any<I>(promises: I, context: &mut Context<'_>) -> Self
     where
         I: IntoIterator<Item = Self>,
     {
@@ -907,12 +876,15 @@ impl JsPromise {
             .constructor()
             .into();
 
-        let value = Promise::any(c, &[promises.into()], context)?;
-        let value = value
+        let value = Promise::any(c, &[promises.into()], context)
+            .expect("`Promise.any` cannot fail with the default `%Promise%` constructor");
+
+        let object = value
             .as_object()
-            .expect("Promise.any always returns an object on success");
+            .expect("`Promise.any` always returns an object on success");
 
-        Self::from_object(value.clone())
+        Self::from_object(object.clone())
+        .expect("`Promise::any` with the  default `%Promise%` constructor always returns a native `JsPromise`")
     }
 
     /// Returns the first promise that settles from a list of promises.
@@ -939,22 +911,24 @@ impl JsPromise {
     /// let (b, resolvers_b) = JsPromise::new_pending(context);
     /// let (c, resolvers_c) = JsPromise::new_pending(context);
     ///
-    /// let promise = JsPromise::race([a, b, c], context)?;
+    /// let promise = JsPromise::race([a, b, c], context);
     ///
-    /// resolvers_b.reject.call(&JsValue::undefined(), &[], context);
+    /// resolvers_b
+    ///     .reject
+    ///     .call(&JsValue::undefined(), &[], context)?;
     /// resolvers_a
     ///     .resolve
-    ///     .call(&JsValue::undefined(), &[5.into()], context);
+    ///     .call(&JsValue::undefined(), &[5.into()], context)?;
     /// resolvers_c.reject.call(
     ///     &JsValue::undefined(),
     ///     &[js_string!("c error").into()],
     ///     context,
-    /// );
+    /// )?;
     ///
     /// context.run_jobs();
     ///
     /// assert_eq!(
-    ///     promise.state()?,
+    ///     promise.state(),
     ///     PromiseState::Rejected(JsValue::undefined())
     /// );
     ///
@@ -963,7 +937,7 @@ impl JsPromise {
     /// ```
     ///
     /// [`Promise.race`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
-    pub fn race<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self>
+    pub fn race<I>(promises: I, context: &mut Context<'_>) -> Self
     where
         I: IntoIterator<Item = Self>,
     {
@@ -976,12 +950,15 @@ impl JsPromise {
             .constructor()
             .into();
 
-        let value = Promise::race(c, &[promises.into()], context)?;
-        let value = value
+        let value = Promise::race(c, &[promises.into()], context)
+            .expect("`Promise.race` cannot fail with the default `%Promise%` constructor");
+
+        let object = value
             .as_object()
-            .expect("Promise.race always returns an object on success");
+            .expect("`Promise.race` always returns an object on success");
 
-        Self::from_object(value.clone())
+        Self::from_object(object.clone())
+        .expect("`Promise::race` with the  default `%Promise%` constructor always returns a native `JsPromise`")
     }
 
     /// Creates a `JsFuture` from this `JsPromise`.
@@ -1003,7 +980,7 @@ impl JsPromise {
     /// let context = &mut Context::default();
     ///
     /// let (promise, resolvers) = JsPromise::new_pending(context);
-    /// let promise_future = promise.into_js_future(context)?;
+    /// let promise_future = promise.into_js_future(context);
     ///
     /// let future1 = async move { promise_future.await };
     ///
@@ -1023,7 +1000,7 @@ impl JsPromise {
     /// # Ok(())
     /// # }
     /// ```
-    pub fn into_js_future(self, context: &mut Context<'_>) -> JsResult<JsFuture> {
+    pub fn into_js_future(self, context: &mut Context<'_>) -> JsFuture {
         // Mostly based from:
         // https://docs.rs/wasm-bindgen-futures/0.4.37/src/wasm_bindgen_futures/lib.rs.html#109-168
 
@@ -1057,39 +1034,35 @@ impl JsPromise {
         let resolve = {
             let state = state.clone();
 
-            FunctionObjectBuilder::new(
-                context.realm(),
-                NativeFunction::from_copy_closure_with_captures(
-                    move |_, args, state, _| {
-                        finish(state, Ok(args.get_or_undefined(0).clone()));
-                        Ok(JsValue::undefined())
-                    },
-                    state,
-                ),
+            NativeFunction::from_copy_closure_with_captures(
+                move |_, args, state, _| {
+                    finish(state, Ok(args.get_or_undefined(0).clone()));
+                    Ok(JsValue::undefined())
+                },
+                state,
             )
-            .build()
         };
 
         let reject = {
             let state = state.clone();
 
-            FunctionObjectBuilder::new(
-                context.realm(),
-                NativeFunction::from_copy_closure_with_captures(
-                    move |_, args, state, _| {
-                        let err = JsError::from_opaque(args.get_or_undefined(0).clone());
-                        finish(state, Err(err));
-                        Ok(JsValue::undefined())
-                    },
-                    state,
-                ),
+            NativeFunction::from_copy_closure_with_captures(
+                move |_, args, state, _| {
+                    let err = JsError::from_opaque(args.get_or_undefined(0).clone());
+                    finish(state, Err(err));
+                    Ok(JsValue::undefined())
+                },
+                state,
             )
-            .build()
         };
 
-        drop(self.then(Some(resolve), Some(reject), context)?);
+        drop(self.then(
+            Some(resolve.to_js_function(context.realm())),
+            Some(reject.to_js_function(context.realm())),
+            context,
+        ));
 
-        Ok(JsFuture { inner: state })
+        JsFuture { inner: state }
     }
 }
 
diff --git a/boa_engine/src/object/builtins/jsregexp.rs b/boa_engine/src/object/builtins/jsregexp.rs
index dfa4e3493de..fc5b2e68a97 100644
--- a/boa_engine/src/object/builtins/jsregexp.rs
+++ b/boa_engine/src/object/builtins/jsregexp.rs
@@ -60,15 +60,7 @@ impl JsRegExp {
     where
         S: Into<JsValue>,
     {
-        let constructor = &context
-            .intrinsics()
-            .constructors()
-            .regexp()
-            .constructor()
-            .into();
-        let obj = RegExp::alloc(constructor, context)?;
-
-        let regexp = RegExp::initialize(obj, &pattern.into(), &flags.into(), context)?
+        let regexp = RegExp::initialize(None, &pattern.into(), &flags.into(), context)?
             .as_object()
             .expect("RegExp::initialize must return a RegExp object")
             .clone();
diff --git a/boa_engine/src/object/internal_methods/function.rs b/boa_engine/src/object/internal_methods/function.rs
index d621739403e..2d65f009d31 100644
--- a/boa_engine/src/object/internal_methods/function.rs
+++ b/boa_engine/src/object/internal_methods/function.rs
@@ -2,9 +2,10 @@ use crate::{
     builtins::function::{arguments::Arguments, FunctionKind, ThisMode},
     context::intrinsics::StandardConstructors,
     environments::{FunctionSlots, ThisBindingStatus},
+    native_function::NativeFunctionObject,
     object::{
         internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
-        JsObject, ObjectData, ObjectKind,
+        JsObject, ObjectData,
     },
     vm::{CallFrame, CallFrameFlags},
     Context, JsNativeError, JsResult, JsValue,
@@ -336,21 +337,18 @@ pub(crate) fn native_function_call(
     // vm, but we'll eventually have to combine the native stack with the vm stack.
     context.check_runtime_limits()?;
     let this_function_object = obj.clone();
-    let object = obj.borrow();
 
-    let ObjectKind::NativeFunction {
-        function,
+    let NativeFunctionObject {
+        f: function,
         constructor,
         realm,
-    } = object.kind()
-    else {
-        unreachable!("the object should be a native function object");
-    };
+    } = obj
+        .borrow()
+        .as_native_function()
+        .cloned()
+        .expect("the object should be a native function object");
 
-    let mut realm = realm.clone();
-    let function = function.clone();
-    let constructor = *constructor;
-    drop(object);
+    let mut realm = realm.unwrap_or_else(|| context.realm().clone());
 
     context.swap_realm(&mut realm);
     context.vm.native_active_function = Some(this_function_object);
@@ -385,21 +383,18 @@ fn native_function_construct(
     // vm, but we'll eventually have to combine the native stack with the vm stack.
     context.check_runtime_limits()?;
     let this_function_object = obj.clone();
-    let object = obj.borrow();
 
-    let ObjectKind::NativeFunction {
-        function,
+    let NativeFunctionObject {
+        f: function,
         constructor,
         realm,
-    } = object.kind()
-    else {
-        unreachable!("the object should be a native function object");
-    };
+    } = obj
+        .borrow()
+        .as_native_function()
+        .cloned()
+        .expect("the object should be a native function object");
 
-    let mut realm = realm.clone();
-    let function = function.clone();
-    let constructor = *constructor;
-    drop(object);
+    let mut realm = realm.unwrap_or_else(|| context.realm().clone());
 
     context.swap_realm(&mut realm);
     context.vm.native_active_function = Some(this_function_object);
diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs
index 1b215db797e..50fa5d0ed1a 100644
--- a/boa_engine/src/object/mod.rs
+++ b/boa_engine/src/object/mod.rs
@@ -67,7 +67,7 @@ use crate::{
     context::intrinsics::StandardConstructor,
     js_string,
     module::ModuleNamespace,
-    native_function::NativeFunction,
+    native_function::{NativeFunction, NativeFunctionObject},
     property::{Attribute, PropertyDescriptor, PropertyKey},
     realm::Realm,
     string::utf16,
@@ -356,16 +356,7 @@ pub enum ObjectKind {
     GeneratorFunction(OrdinaryFunction),
 
     /// A native rust function.
-    NativeFunction {
-        /// The rust function.
-        function: NativeFunction,
-
-        /// The kind of the function constructor if it is a constructor.
-        constructor: Option<ConstructorKind>,
-
-        /// The [`Realm`] in which the function is defined.
-        realm: Realm,
-    },
+    NativeFunction(NativeFunctionObject),
 
     /// The `Set` object kind.
     Set(OrderedSet),
@@ -510,10 +501,7 @@ unsafe impl Trace for ObjectKind {
             Self::OrdinaryFunction(f) | Self::GeneratorFunction(f) | Self::AsyncGeneratorFunction(f) => mark(f),
             Self::BoundFunction(f) => mark(f),
             Self::Generator(g) => mark(g),
-            Self::NativeFunction { function, constructor: _, realm } => {
-                mark(function);
-                mark(realm);
-            }
+            Self::NativeFunction(f) => mark(f),
             Self::Set(s) => mark(s),
             Self::SetIterator(i) => mark(i),
             Self::StringIterator(i) => mark(i),
@@ -659,9 +647,9 @@ impl ObjectData {
 
     /// Create the `RegExp` object data
     #[must_use]
-    pub fn reg_exp(reg_exp: Box<RegExp>) -> Self {
+    pub fn regexp(reg_exp: RegExp) -> Self {
         Self {
-            kind: ObjectKind::RegExp(reg_exp),
+            kind: ObjectKind::RegExp(Box::new(reg_exp)),
             internal_methods: &ORDINARY_INTERNAL_METHODS,
         }
     }
@@ -737,8 +725,8 @@ impl ObjectData {
 
     /// Create the native function object data
     #[must_use]
-    pub fn native_function(function: NativeFunction, constructor: bool, realm: Realm) -> Self {
-        let internal_methods = if constructor {
+    pub fn native_function(function: NativeFunctionObject) -> Self {
+        let internal_methods = if function.constructor.is_some() {
             &NATIVE_CONSTRUCTOR_INTERNAL_METHODS
         } else {
             &NATIVE_FUNCTION_INTERNAL_METHODS
@@ -746,11 +734,7 @@ impl ObjectData {
 
         Self {
             internal_methods,
-            kind: ObjectKind::NativeFunction {
-                function,
-                constructor: constructor.then_some(ConstructorKind::Base),
-                realm,
-            },
+            kind: ObjectKind::NativeFunction(function),
         }
     }
 
@@ -1210,9 +1194,12 @@ impl Debug for ObjectKind {
 }
 
 impl Object {
-    /// Returns a mutable reference to the kind of an object.
-    pub(crate) fn kind_mut(&mut self) -> &mut ObjectKind {
-        &mut self.kind
+    /// Creates a new `Object` with the specified `ObjectKind`.
+    pub(crate) fn with_kind(kind: ObjectKind) -> Self {
+        Self {
+            kind,
+            ..Self::default()
+        }
     }
 
     /// Returns the shape of the object.
@@ -1726,6 +1713,16 @@ impl Object {
         }
     }
 
+    /// Gets a mutable reference to the regexp data if the object is a regexp.
+    #[inline]
+    #[must_use]
+    pub fn as_regexp_mut(&mut self) -> Option<&mut RegExp> {
+        match &mut self.kind {
+            ObjectKind::RegExp(regexp) => Some(regexp),
+            _ => None,
+        }
+    }
+
     /// Checks if it a `TypedArray` object.
     #[inline]
     #[must_use]
@@ -1958,6 +1955,30 @@ impl Object {
         }
     }
 
+    /// Returns `true` if it holds a native Rust function.
+    #[inline]
+    #[must_use]
+    pub const fn is_native_function(&self) -> bool {
+        matches!(self.kind, ObjectKind::NativeFunction { .. })
+    }
+
+    /// Returns this `NativeFunctionObject` if this object contains a `NativeFunctionObject`.
+    pub(crate) fn as_native_function(&self) -> Option<&NativeFunctionObject> {
+        match &self.kind {
+            ObjectKind::NativeFunction(f) => Some(f),
+            _ => None,
+        }
+    }
+
+    /// Returns a mutable reference to this `NativeFunctionObject` if this object contains a
+    /// `NativeFunctionObject`.
+    pub(crate) fn as_native_function_mut(&mut self) -> Option<&mut NativeFunctionObject> {
+        match &mut self.kind {
+            ObjectKind::NativeFunction(f) => Some(f),
+            _ => None,
+        }
+    }
+
     /// Gets the prototype instance of this object.
     #[inline]
     #[must_use]
@@ -1990,13 +2011,6 @@ impl Object {
         matches!(self.kind, ObjectKind::NativeObject(_))
     }
 
-    /// Returns `true` if it holds a native Rust function.
-    #[inline]
-    #[must_use]
-    pub const fn is_native_function(&self) -> bool {
-        matches!(self.kind, ObjectKind::NativeFunction { .. })
-    }
-
     /// Gets the native object data if the object is a `NativeObject`.
     #[inline]
     #[must_use]
@@ -2602,11 +2616,11 @@ impl<'realm> FunctionObjectBuilder<'realm> {
     #[must_use]
     pub fn build(self) -> JsFunction {
         let object = self.realm.intrinsics().templates().function().create(
-            ObjectData::native_function(
-                self.function,
-                self.constructor.is_some(),
-                self.realm.clone(),
-            ),
+            ObjectData::native_function(NativeFunctionObject {
+                f: self.function,
+                constructor: self.constructor,
+                realm: Some(self.realm.clone()),
+            }),
             vec![self.length.into(), self.name.into()],
         );
 
@@ -3044,11 +3058,11 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
             let mut constructor = self.constructor_object;
             constructor.insert(utf16!("length"), length);
             constructor.insert(utf16!("name"), name);
-            let data = ObjectData::native_function(
-                self.function,
-                self.kind.is_some(),
-                self.context.realm().clone(),
-            );
+            let data = ObjectData::native_function(NativeFunctionObject {
+                f: self.function,
+                constructor: self.kind,
+                realm: Some(self.context.realm().clone()),
+            });
 
             constructor.kind = data.kind;
 
diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs
index c54f0837b61..42a450be9c3 100644
--- a/boa_engine/src/object/operations.rs
+++ b/boa_engine/src/object/operations.rs
@@ -720,8 +720,8 @@ impl JsObject {
             return Ok(fun.realm().clone());
         }
 
-        if let ObjectKind::NativeFunction { realm, .. } = constructor.kind() {
-            return Ok(realm.clone());
+        if let ObjectKind::NativeFunction(f) = constructor.kind() {
+            return Ok(f.realm.clone().unwrap_or_else(|| context.realm().clone()));
         }
 
         if let Some(bound) = constructor.as_bound_function() {
diff --git a/boa_examples/src/bin/modules.rs b/boa_examples/src/bin/modules.rs
index e6c22600caf..02f6d69bc4d 100644
--- a/boa_examples/src/bin/modules.rs
+++ b/boa_examples/src/bin/modules.rs
@@ -4,7 +4,6 @@ use boa_engine::{
     builtins::promise::PromiseState,
     js_string,
     module::{ModuleLoader, SimpleModuleLoader},
-    object::FunctionObjectBuilder,
     Context, JsError, JsNativeError, JsValue, Module, NativeFunction,
 };
 use boa_parser::Source;
@@ -58,49 +57,43 @@ fn main() -> Result<(), Box<dyn Error>> {
         .load(context)
         .then(
             Some(
-                FunctionObjectBuilder::new(
-                    context.realm(),
-                    NativeFunction::from_copy_closure_with_captures(
-                        |_, _, module, context| {
-                            // After loading, link all modules by resolving the imports
-                            // and exports on the full module graph, initializing module
-                            // environments. This returns a plain `Err` since all modules
-                            // must link at the same time.
-                            module.link(context)?;
-                            Ok(JsValue::undefined())
-                        },
-                        module.clone(),
-                    ),
+                NativeFunction::from_copy_closure_with_captures(
+                    |_, _, module, context| {
+                        // After loading, link all modules by resolving the imports
+                        // and exports on the full module graph, initializing module
+                        // environments. This returns a plain `Err` since all modules
+                        // must link at the same time.
+                        module.link(context)?;
+                        Ok(JsValue::undefined())
+                    },
+                    module.clone(),
                 )
-                .build(),
+                .to_js_function(context.realm()),
             ),
             None,
             context,
-        )?
+        )
         .then(
             Some(
-                FunctionObjectBuilder::new(
-                    context.realm(),
-                    NativeFunction::from_copy_closure_with_captures(
-                        // Finally, evaluate the root module.
-                        // This returns a `JsPromise` since a module could have
-                        // top-level await statements, which defers module execution to the
-                        // job queue.
-                        |_, _, module, context| Ok(module.evaluate(context).into()),
-                        module.clone(),
-                    ),
+                NativeFunction::from_copy_closure_with_captures(
+                    // Finally, evaluate the root module.
+                    // This returns a `JsPromise` since a module could have
+                    // top-level await statements, which defers module execution to the
+                    // job queue.
+                    |_, _, module, context| Ok(module.evaluate(context).into()),
+                    module.clone(),
                 )
-                .build(),
+                .to_js_function(context.realm()),
             ),
             None,
             context,
-        )?;
+        );
 
     // Very important to push forward the job queue after queueing promises.
     context.run_jobs();
 
     // Checking if the final promise didn't return an error.
-    match promise_result.state()? {
+    match promise_result.state() {
         PromiseState::Pending => return Err("module didn't execute!".into()),
         PromiseState::Fulfilled(v) => {
             assert_eq!(v, JsValue::undefined())
diff --git a/boa_examples/src/bin/synthetic.rs b/boa_examples/src/bin/synthetic.rs
index 54564cf340a..c194d746567 100644
--- a/boa_examples/src/bin/synthetic.rs
+++ b/boa_examples/src/bin/synthetic.rs
@@ -59,13 +59,13 @@ fn main() -> Result<(), Box<dyn Error>> {
     // This uses the utility function to load, link and evaluate a module without having to deal
     // with callbacks. For an example demonstrating the whole lifecycle of a module, see
     // `modules.rs`
-    let promise_result = module.load_link_evaluate(context)?;
+    let promise_result = module.load_link_evaluate(context);
 
     // Very important to push forward the job queue after queueing promises.
     context.run_jobs();
 
     // Checking if the final promise didn't return an error.
-    match promise_result.state()? {
+    match promise_result.state() {
         PromiseState::Pending => return Err("module didn't execute!".into()),
         PromiseState::Fulfilled(v) => {
             assert_eq!(v, JsValue::undefined())
diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs
index 356e3d831fc..a8656c58562 100644
--- a/boa_tester/src/exec/mod.rs
+++ b/boa_tester/src/exec/mod.rs
@@ -250,17 +250,11 @@ impl Test {
                         module.clone(),
                     );
 
-                    let promise = match module.load_link_evaluate(context) {
-                        Ok(promise) => promise,
-                        Err(err) => return (false, format!("Uncaught {err}")),
-                    };
+                    let promise = module.load_link_evaluate(context);
 
                     context.run_jobs();
 
-                    match promise
-                        .state()
-                        .expect("tester can only use builtin promises")
-                    {
+                    match promise.state() {
                         PromiseState::Pending => {
                             return (false, "module should have been executed".to_string())
                         }
@@ -364,10 +358,7 @@ impl Test {
 
                 context.run_jobs();
 
-                match promise
-                    .state()
-                    .expect("tester can only use builtin promises")
-                {
+                match promise.state() {
                     PromiseState::Pending => {
                         return (false, "module didn't try to load".to_string())
                     }
@@ -428,10 +419,7 @@ impl Test {
 
                     context.run_jobs();
 
-                    match promise
-                        .state()
-                        .expect("tester can only use builtin promises")
-                    {
+                    match promise.state() {
                         PromiseState::Pending => {
                             return (false, "module didn't try to load".to_string())
                         }
@@ -449,10 +437,7 @@ impl Test {
 
                     context.run_jobs();
 
-                    match promise
-                        .state()
-                        .expect("tester can only use builtin promises")
-                    {
+                    match promise.state() {
                         PromiseState::Pending => {
                             return (false, "module didn't try to evaluate".to_string())
                         }