Skip to main content

boa_engine/value/display/
object.rs

1use std::collections::HashSet;
2use std::fmt::{self, Display, Write};
3
4use crate::{
5    JsObject, JsString, JsValue, js_string,
6    property::{DescriptorKind, PropertyKey},
7};
8
9fn print_obj_value_internals(
10    f: &mut fmt::Formatter<'_>,
11    obj: &JsObject,
12    indent: usize,
13    encounters: &mut HashSet<usize>,
14) -> fmt::Result {
15    let object = obj.borrow();
16    write!(f, "{:>indent$}__proto__: ", "")?;
17    if let Some(object) = object.prototype() {
18        log_object_to_internal(
19            f,
20            &object.clone().into(),
21            encounters,
22            indent.wrapping_add(4),
23            true,
24        )?;
25    } else {
26        write!(f, "{}", JsValue::null().display())?;
27    }
28    f.write_char(',')?;
29    f.write_char('\n')
30}
31
32fn print_obj_value_props(
33    f: &mut fmt::Formatter<'_>,
34    obj: &JsObject,
35    indent: usize,
36    encounters: &mut HashSet<usize>,
37    print_internals: bool,
38) -> fmt::Result {
39    let mut keys: Vec<_> = obj
40        .borrow()
41        .properties()
42        .index_property_keys()
43        .map(PropertyKey::from)
44        .collect();
45    keys.extend(obj.borrow().properties().shape.keys());
46
47    let mut first = true;
48    for key in keys {
49        if first {
50            first = false;
51        } else {
52            f.write_char(',')?;
53            f.write_char('\n')?;
54        }
55        let val = obj
56            .borrow()
57            .properties()
58            .get(&key)
59            .expect("There should be a value");
60
61        write!(f, "{:>width$}{}: ", "", key, width = indent)?;
62        if val.is_data_descriptor() {
63            let v = val.expect_value();
64            log_object_to_internal(f, v, encounters, indent.wrapping_add(4), print_internals)?;
65        } else {
66            let display = match (val.set().is_some(), val.get().is_some()) {
67                (true, true) => "Getter & Setter",
68                (true, false) => "Setter",
69                (false, true) => "Getter",
70                _ => "No Getter/Setter",
71            };
72            write!(f, "{display}")?;
73        }
74    }
75    f.write_char('\n')?;
76    Ok(())
77}
78
79pub(super) fn log_object_to_internal(
80    f: &mut fmt::Formatter<'_>,
81    data: &JsValue,
82    encounters: &mut HashSet<usize>,
83    indent: usize,
84    print_internals: bool,
85) -> fmt::Result {
86    if let Some(v) = data.as_object() {
87        // The in-memory address of the current object
88        let addr = std::ptr::from_ref(v.as_ref()).addr();
89
90        // We need not continue if this object has already been
91        // printed up the current chain
92        if encounters.contains(&addr) {
93            return f.write_str("[Cycle]");
94        }
95
96        // Mark the current object as encountered
97        encounters.insert(addr);
98
99        if v.is::<crate::builtins::Array>() {
100            encounters.remove(&addr);
101            return super::array::log_array_to(f, &v, print_internals, false);
102        }
103
104        if v.is::<crate::builtins::typed_array::TypedArray>() {
105            encounters.remove(&addr);
106            return super::typed_array::log_typed_array(f, &v, true, print_internals);
107        }
108
109        let constructor_name = get_constructor_name_of(&v);
110        if let Some(name) = constructor_name {
111            write!(f, "{} ", name.to_std_string_lossy())?;
112        }
113        f.write_str("{\n")?;
114
115        if print_internals {
116            print_obj_value_internals(f, &v, indent, encounters)?;
117        }
118        print_obj_value_props(f, &v, indent, encounters, print_internals)?;
119        write!(f, "{:>indent$}}}", "", indent = indent.saturating_sub(4))?;
120
121        // If the current object is referenced in a different branch,
122        // it will not cause an infinite printing loop, so it is safe to be printed again
123        encounters.remove(&addr);
124        Ok(())
125    } else {
126        // Every other type of data is printed with the display method
127        super::value::log_value_to(f, data, print_internals, false)
128    }
129}
130
131/// The constructor can be retrieved as `Object.getPrototypeOf(obj).constructor`.
132///
133/// Returns `None` if the constructor is `Object` as plain objects don't need a name.
134fn get_constructor_name_of(obj: &JsObject) -> Option<JsString> {
135    let prototype = obj.prototype()?;
136
137    // To neglect out plain object
138    // `Object.getPrototypeOf(Object.prototype)` => null.
139    // For user created `Object.create(Object.create(null))`,
140    // we also don't need to display its name.
141    prototype.prototype()?;
142
143    let constructor_property = prototype
144        .borrow()
145        .properties()
146        .get(&PropertyKey::from(js_string!("constructor")))?;
147    let constructor = constructor_property.value()?;
148
149    let name = constructor
150        .as_object()?
151        .borrow()
152        .properties()
153        .get(&PropertyKey::from(js_string!("name")))?
154        .value()?
155        .as_string()?;
156
157    Some(name)
158}
159
160pub(super) fn log_plain_object_compact(
161    f: &mut fmt::Formatter<'_>,
162    obj: &JsObject,
163    depth: u32,
164    print_internals: bool,
165    encounters: &mut HashSet<usize>,
166) -> fmt::Result {
167    let addr = std::ptr::from_ref(obj.as_ref()).addr();
168    if encounters.contains(&addr) {
169        return f.write_str("[Circular *]");
170    }
171    encounters.insert(addr);
172
173    let mut keys: Vec<_> = obj
174        .borrow()
175        .properties()
176        .index_property_keys()
177        .map(PropertyKey::from)
178        .collect();
179    keys.extend(obj.borrow().properties().shape.keys());
180
181    if keys.is_empty() {
182        encounters.remove(&addr);
183        return f.write_str("{}");
184    }
185
186    f.write_str("{ ")?;
187    let mut first = true;
188    for key in &keys {
189        if first {
190            first = false;
191        } else {
192            f.write_str(", ")?;
193        }
194        write!(f, "{key}: ")?;
195
196        if let Some(val) = obj.borrow().properties().get(key) {
197            match val.kind() {
198                DescriptorKind::Data { value, .. } => {
199                    if let Some(value) = value {
200                        super::value::log_value_compact(
201                            f,
202                            value,
203                            depth + 1,
204                            print_internals,
205                            encounters,
206                        )?;
207                    } else {
208                        f.write_str("undefined")?;
209                    }
210                }
211                DescriptorKind::Accessor { get, set } => {
212                    let display = match (get.is_some(), set.is_some()) {
213                        (true, true) => "[Getter/Setter]",
214                        (true, false) => "[Getter]",
215                        (false, true) => "[Setter]",
216                        _ => "[No Getter/Setter]",
217                    };
218                    f.write_str(display)?;
219                }
220                DescriptorKind::Generic => {
221                    f.write_str("undefined")?;
222                }
223            }
224        }
225    }
226    f.write_str(" }")?;
227
228    encounters.remove(&addr);
229    Ok(())
230}
231
232impl JsValue {
233    /// A helper function for specifically printing object values
234    #[must_use]
235    pub fn display_obj(&self, print_internals: bool) -> String {
236        struct DisplayObj<'a>(&'a JsValue, bool);
237        impl Display for DisplayObj<'_> {
238            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239                log_object_to_internal(f, self.0, &mut HashSet::new(), 4, self.1)
240            }
241        }
242
243        DisplayObj(self, print_internals).to_string()
244    }
245}