1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sw=2 et tw=0 ft=c:
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include "frontend/ObjLiteral.h"
9 
10 #include "mozilla/HashTable.h"  // mozilla::HashSet
11 
12 #include "NamespaceImports.h"  // ValueVector
13 
14 #include "builtin/Array.h"  // NewDenseCopiedArray
15 #include "frontend/CompilationStencil.h"  // frontend::{CompilationStencil, CompilationAtomCache}
16 #include "frontend/ParserAtom.h"                   // frontend::ParserAtomTable
17 #include "frontend/TaggedParserAtomIndexHasher.h"  // TaggedParserAtomIndexHasher
18 #include "gc/AllocKind.h"                          // gc::AllocKind
19 #include "gc/Rooting.h"                            // RootedPlainObject
20 #include "js/Id.h"                                 // INT_TO_JSID
21 #include "js/RootingAPI.h"                         // Rooted
22 #include "js/TypeDecls.h"                          // RootedId, RootedValue
23 #include "vm/JSAtom.h"                             // JSAtom
24 #include "vm/JSObject.h"                           // TenuredObject
25 #include "vm/JSONPrinter.h"                        // js::JSONPrinter
26 #include "vm/NativeObject.h"                       // NativeDefineDataProperty
27 #include "vm/PlainObject.h"                        // PlainObject
28 #include "vm/Printer.h"                            // js::Fprinter
29 
30 #include "gc/ObjectKind-inl.h"    // gc::GetGCObjectKind
31 #include "vm/JSAtom-inl.h"        // AtomToId
32 #include "vm/JSObject-inl.h"      // NewBuiltinClassInstance
33 #include "vm/NativeObject-inl.h"  // AddDataPropertyNonDelegate
34 
35 namespace js {
36 
checkForDuplicatedNames(JSContext * cx)37 bool ObjLiteralWriter::checkForDuplicatedNames(JSContext* cx) {
38   if (!mightContainDuplicatePropertyNames_) {
39     return true;
40   }
41 
42   // If possible duplicate property names are detected by bloom-filter,
43   // check again with hash-set.
44 
45   mozilla::HashSet<frontend::TaggedParserAtomIndex,
46                    frontend::TaggedParserAtomIndexHasher>
47       propNameSet;
48 
49   if (!propNameSet.reserve(propertyCount_)) {
50     js::ReportOutOfMemory(cx);
51     return false;
52   }
53 
54   ObjLiteralReader reader(getCode());
55 
56   while (true) {
57     ObjLiteralInsn insn;
58     if (!reader.readInsn(&insn)) {
59       break;
60     }
61 
62     if (insn.getKey().isArrayIndex()) {
63       continue;
64     }
65 
66     auto propName = insn.getKey().getAtomIndex();
67 
68     auto p = propNameSet.lookupForAdd(propName);
69     if (p) {
70       flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
71       break;
72     }
73 
74     // Already reserved above and doesn't fail.
75     MOZ_ALWAYS_TRUE(propNameSet.add(p, propName));
76   }
77 
78   return true;
79 }
80 
InterpretObjLiteralValue(JSContext * cx,const frontend::CompilationAtomCache & atomCache,const ObjLiteralInsn & insn,MutableHandleValue valOut)81 static void InterpretObjLiteralValue(
82     JSContext* cx, const frontend::CompilationAtomCache& atomCache,
83     const ObjLiteralInsn& insn, MutableHandleValue valOut) {
84   switch (insn.getOp()) {
85     case ObjLiteralOpcode::ConstValue:
86       valOut.set(insn.getConstValue());
87       return;
88     case ObjLiteralOpcode::ConstAtom: {
89       frontend::TaggedParserAtomIndex index = insn.getAtomIndex();
90       JSAtom* jsatom = atomCache.getExistingAtomAt(cx, index);
91       MOZ_ASSERT(jsatom);
92       valOut.setString(jsatom);
93       return;
94     }
95     case ObjLiteralOpcode::Null:
96       valOut.setNull();
97       return;
98     case ObjLiteralOpcode::Undefined:
99       valOut.setUndefined();
100       return;
101     case ObjLiteralOpcode::True:
102       valOut.setBoolean(true);
103       return;
104     case ObjLiteralOpcode::False:
105       valOut.setBoolean(false);
106       return;
107     default:
108       MOZ_CRASH("Unexpected object-literal instruction opcode");
109   }
110 }
111 
112 enum class PropertySetKind {
113   UniqueNames,
114   Normal,
115 };
116 
117 template <PropertySetKind kind>
InterpretObjLiteralObj(JSContext * cx,HandlePlainObject obj,const frontend::CompilationAtomCache & atomCache,const mozilla::Span<const uint8_t> literalInsns,ObjLiteralFlags flags)118 bool InterpretObjLiteralObj(JSContext* cx, HandlePlainObject obj,
119                             const frontend::CompilationAtomCache& atomCache,
120                             const mozilla::Span<const uint8_t> literalInsns,
121                             ObjLiteralFlags flags) {
122   bool singleton = flags.hasFlag(ObjLiteralFlag::Singleton);
123 
124   ObjLiteralReader reader(literalInsns);
125 
126   RootedId propId(cx);
127   RootedValue propVal(cx);
128   while (true) {
129     // Make sure `insn` doesn't live across GC.
130     ObjLiteralInsn insn;
131     if (!reader.readInsn(&insn)) {
132       break;
133     }
134     MOZ_ASSERT(insn.isValid());
135     MOZ_ASSERT_IF(kind == PropertySetKind::UniqueNames,
136                   !insn.getKey().isArrayIndex());
137 
138     if (kind == PropertySetKind::Normal && insn.getKey().isArrayIndex()) {
139       propId = INT_TO_JSID(insn.getKey().getArrayIndex());
140     } else {
141       JSAtom* jsatom =
142           atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex());
143       MOZ_ASSERT(jsatom);
144       propId = AtomToId(jsatom);
145     }
146 
147     if (singleton) {
148       InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
149     } else {
150       propVal.setUndefined();
151     }
152 
153     if (kind == PropertySetKind::UniqueNames) {
154       if (!AddDataPropertyNonPrototype(cx, obj, propId, propVal)) {
155         return false;
156       }
157     } else {
158       if (!NativeDefineDataProperty(cx, obj, propId, propVal,
159                                     JSPROP_ENUMERATE)) {
160         return false;
161       }
162     }
163   }
164   return true;
165 }
166 
InterpretObjLiteralObj(JSContext * cx,const frontend::CompilationAtomCache & atomCache,const mozilla::Span<const uint8_t> literalInsns,ObjLiteralFlags flags,uint32_t propertyCount)167 static JSObject* InterpretObjLiteralObj(
168     JSContext* cx, const frontend::CompilationAtomCache& atomCache,
169     const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags,
170     uint32_t propertyCount) {
171   // Use NewObjectGCKind for empty object literals to reserve some fixed slots
172   // for new properties. This improves performance for common patterns such as
173   // |Object.assign({}, ...)|.
174   gc::AllocKind allocKind;
175   if (propertyCount == 0) {
176     allocKind = NewObjectGCKind();
177   } else {
178     allocKind = gc::GetGCObjectKind(propertyCount);
179   }
180 
181   RootedPlainObject obj(
182       cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind, TenuredObject));
183   if (!obj) {
184     return nullptr;
185   }
186 
187   if (!flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
188     if (!InterpretObjLiteralObj<PropertySetKind::UniqueNames>(
189             cx, obj, atomCache, literalInsns, flags)) {
190       return nullptr;
191     }
192   } else {
193     if (!InterpretObjLiteralObj<PropertySetKind::Normal>(cx, obj, atomCache,
194                                                          literalInsns, flags)) {
195       return nullptr;
196     }
197   }
198   return obj;
199 }
200 
InterpretObjLiteralArray(JSContext * cx,const frontend::CompilationAtomCache & atomCache,const mozilla::Span<const uint8_t> literalInsns,ObjLiteralFlags flags,uint32_t propertyCount)201 static JSObject* InterpretObjLiteralArray(
202     JSContext* cx, const frontend::CompilationAtomCache& atomCache,
203     const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags,
204     uint32_t propertyCount) {
205   ObjLiteralReader reader(literalInsns);
206   ObjLiteralInsn insn;
207 
208   Rooted<ValueVector> elements(cx, ValueVector(cx));
209   if (!elements.reserve(propertyCount)) {
210     return nullptr;
211   }
212 
213   RootedValue propVal(cx);
214   while (reader.readInsn(&insn)) {
215     MOZ_ASSERT(insn.isValid());
216 
217     InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
218     elements.infallibleAppend(propVal);
219   }
220 
221   return NewDenseCopiedArray(cx, elements.length(), elements.begin(),
222                              /* proto = */ nullptr,
223                              NewObjectKind::TenuredObject);
224 }
225 
InterpretObjLiteral(JSContext * cx,const frontend::CompilationAtomCache & atomCache,const mozilla::Span<const uint8_t> literalInsns,ObjLiteralFlags flags,uint32_t propertyCount)226 static JSObject* InterpretObjLiteral(
227     JSContext* cx, const frontend::CompilationAtomCache& atomCache,
228     const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags,
229     uint32_t propertyCount) {
230   return flags.hasFlag(ObjLiteralFlag::Array)
231              ? InterpretObjLiteralArray(cx, atomCache, literalInsns, flags,
232                                         propertyCount)
233              : InterpretObjLiteralObj(cx, atomCache, literalInsns, flags,
234                                       propertyCount);
235 }
236 
create(JSContext * cx,const frontend::CompilationAtomCache & atomCache) const237 JSObject* ObjLiteralStencil::create(
238     JSContext* cx, const frontend::CompilationAtomCache& atomCache) const {
239   return InterpretObjLiteral(cx, atomCache, code_, flags_, propertyCount_);
240 }
241 
242 #ifdef DEBUG
isContainedIn(const LifoAlloc & alloc) const243 bool ObjLiteralStencil::isContainedIn(const LifoAlloc& alloc) const {
244   return alloc.contains(code_.data());
245 }
246 #endif
247 
248 #if defined(DEBUG) || defined(JS_JITSPEW)
249 
DumpObjLiteralFlagsItems(js::JSONPrinter & json,ObjLiteralFlags flags)250 static void DumpObjLiteralFlagsItems(js::JSONPrinter& json,
251                                      ObjLiteralFlags flags) {
252   if (flags.hasFlag(ObjLiteralFlag::Array)) {
253     json.value("Array");
254     flags.clearFlag(ObjLiteralFlag::Array);
255   }
256   if (flags.hasFlag(ObjLiteralFlag::Singleton)) {
257     json.value("Singleton");
258     flags.clearFlag(ObjLiteralFlag::Singleton);
259   }
260   if (flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
261     json.value("HasIndexOrDuplicatePropName");
262     flags.clearFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
263   }
264 
265   if (!flags.isEmpty()) {
266     json.value("Unknown(%x)", flags.toRaw());
267   }
268 }
269 
DumpObjLiteral(js::JSONPrinter & json,const frontend::CompilationStencil * stencil,mozilla::Span<const uint8_t> code,const ObjLiteralFlags & flags,uint32_t propertyCount)270 static void DumpObjLiteral(js::JSONPrinter& json,
271                            const frontend::CompilationStencil* stencil,
272                            mozilla::Span<const uint8_t> code,
273                            const ObjLiteralFlags& flags,
274                            uint32_t propertyCount) {
275   json.beginListProperty("flags");
276   DumpObjLiteralFlagsItems(json, flags);
277   json.endList();
278 
279   json.beginListProperty("code");
280   ObjLiteralReader reader(code);
281   ObjLiteralInsn insn;
282   while (reader.readInsn(&insn)) {
283     json.beginObject();
284 
285     if (insn.getKey().isNone()) {
286       json.nullProperty("key");
287     } else if (insn.getKey().isAtomIndex()) {
288       frontend::TaggedParserAtomIndex index = insn.getKey().getAtomIndex();
289       json.beginObjectProperty("key");
290       DumpTaggedParserAtomIndex(json, index, stencil);
291       json.endObject();
292     } else if (insn.getKey().isArrayIndex()) {
293       uint32_t index = insn.getKey().getArrayIndex();
294       json.formatProperty("key", "ArrayIndex(%u)", index);
295     }
296 
297     switch (insn.getOp()) {
298       case ObjLiteralOpcode::ConstValue: {
299         const Value& v = insn.getConstValue();
300         json.formatProperty("op", "ConstValue(%f)", v.toNumber());
301         break;
302       }
303       case ObjLiteralOpcode::ConstAtom: {
304         frontend::TaggedParserAtomIndex index = insn.getAtomIndex();
305         json.beginObjectProperty("op");
306         DumpTaggedParserAtomIndex(json, index, stencil);
307         json.endObject();
308         break;
309       }
310       case ObjLiteralOpcode::Null:
311         json.property("op", "Null");
312         break;
313       case ObjLiteralOpcode::Undefined:
314         json.property("op", "Undefined");
315         break;
316       case ObjLiteralOpcode::True:
317         json.property("op", "True");
318         break;
319       case ObjLiteralOpcode::False:
320         json.property("op", "False");
321         break;
322       default:
323         json.formatProperty("op", "Invalid(%x)", uint8_t(insn.getOp()));
324         break;
325     }
326 
327     json.endObject();
328   }
329   json.endList();
330 
331   json.property("propertyCount", propertyCount);
332 }
333 
dump() const334 void ObjLiteralWriter::dump() const {
335   js::Fprinter out(stderr);
336   js::JSONPrinter json(out);
337   dump(json, nullptr);
338 }
339 
dump(js::JSONPrinter & json,const frontend::CompilationStencil * stencil) const340 void ObjLiteralWriter::dump(js::JSONPrinter& json,
341                             const frontend::CompilationStencil* stencil) const {
342   json.beginObject();
343   dumpFields(json, stencil);
344   json.endObject();
345 }
346 
dumpFields(js::JSONPrinter & json,const frontend::CompilationStencil * stencil) const347 void ObjLiteralWriter::dumpFields(
348     js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
349   DumpObjLiteral(json, stencil, getCode(), flags_, propertyCount_);
350 }
351 
dump() const352 void ObjLiteralStencil::dump() const {
353   js::Fprinter out(stderr);
354   js::JSONPrinter json(out);
355   dump(json, nullptr);
356 }
357 
dump(js::JSONPrinter & json,const frontend::CompilationStencil * stencil) const358 void ObjLiteralStencil::dump(
359     js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
360   json.beginObject();
361   dumpFields(json, stencil);
362   json.endObject();
363 }
364 
dumpFields(js::JSONPrinter & json,const frontend::CompilationStencil * stencil) const365 void ObjLiteralStencil::dumpFields(
366     js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
367   DumpObjLiteral(json, stencil, code_, flags_, propertyCount_);
368 }
369 
370 #endif  // defined(DEBUG) || defined(JS_JITSPEW)
371 
372 }  // namespace js
373