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