1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /*
8 * JS script operations.
9 */
10
11 #include "vm/JSScript-inl.h"
12
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/CheckedInt.h"
15 #include "mozilla/DebugOnly.h"
16 #include "mozilla/Maybe.h"
17 #include "mozilla/MemoryReporting.h"
18 #include "mozilla/PodOperations.h"
19 #include "mozilla/ScopeExit.h"
20 #include "mozilla/Span.h" // mozilla::{Span,Span}
21 #include "mozilla/Sprintf.h"
22 #include "mozilla/Utf8.h"
23 #include "mozilla/Vector.h"
24
25 #include <algorithm>
26 #include <new>
27 #include <string.h>
28 #include <type_traits>
29 #include <utility>
30
31 #include "jsapi.h"
32 #include "jstypes.h"
33
34 #include "frontend/BytecodeCompiler.h"
35 #include "frontend/BytecodeEmitter.h"
36 #include "frontend/CompilationStencil.h" // frontend::CompilationStencil
37 #include "frontend/SharedContext.h"
38 #include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteIterator
39 #include "frontend/StencilXdr.h" // frontend::StencilXdr::SharedData, CanCopyDataToDisk
40 #include "gc/FreeOp.h"
41 #include "jit/BaselineJIT.h"
42 #include "jit/CacheIRHealth.h"
43 #include "jit/Invalidation.h"
44 #include "jit/Ion.h"
45 #include "jit/IonScript.h"
46 #include "jit/JitCode.h"
47 #include "jit/JitOptions.h"
48 #include "jit/JitRuntime.h"
49 #include "js/CompileOptions.h"
50 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
51 #include "js/MemoryMetrics.h"
52 #include "js/Printf.h"
53 #include "js/SourceText.h"
54 #include "js/Transcoding.h"
55 #include "js/UniquePtr.h"
56 #include "js/Utility.h"
57 #include "js/Wrapper.h"
58 #include "util/Memory.h"
59 #include "util/Poison.h"
60 #include "util/StringBuffer.h"
61 #include "util/Text.h"
62 #include "vm/ArgumentsObject.h"
63 #include "vm/BytecodeIterator.h"
64 #include "vm/BytecodeLocation.h"
65 #include "vm/BytecodeUtil.h"
66 #include "vm/Compression.h"
67 #include "vm/FunctionFlags.h" // js::FunctionFlags
68 #include "vm/HelperThreadState.h" // js::RunPendingSourceCompressions
69 #include "vm/JSAtom.h"
70 #include "vm/JSContext.h"
71 #include "vm/JSFunction.h"
72 #include "vm/JSObject.h"
73 #include "vm/Opcodes.h"
74 #include "vm/PlainObject.h" // js::PlainObject
75 #include "vm/SelfHosting.h"
76 #include "vm/Shape.h"
77 #include "vm/SharedImmutableStringsCache.h"
78 #include "vm/Warnings.h" // js::WarnNumberLatin1
79 #include "vm/Xdr.h"
80 #ifdef MOZ_VTUNE
81 # include "vtune/VTuneWrapper.h"
82 #endif
83
84 #include "debugger/DebugAPI-inl.h"
85 #include "gc/Marking-inl.h"
86 #include "vm/BytecodeIterator-inl.h"
87 #include "vm/BytecodeLocation-inl.h"
88 #include "vm/Compartment-inl.h"
89 #include "vm/EnvironmentObject-inl.h"
90 #include "vm/JSFunction-inl.h"
91 #include "vm/JSObject-inl.h"
92 #include "vm/NativeObject-inl.h"
93 #include "vm/SharedImmutableStringsCache-inl.h"
94 #include "vm/Stack-inl.h"
95
96 using namespace js;
97
98 using mozilla::CheckedInt;
99 using mozilla::Maybe;
100 using mozilla::PodCopy;
101 using mozilla::PointerRangeSize;
102 using mozilla::Utf8AsUnsignedChars;
103 using mozilla::Utf8Unit;
104
105 using JS::CompileOptions;
106 using JS::ReadOnlyCompileOptions;
107 using JS::SourceText;
108
109 template <XDRMode mode>
XDRScriptConst(XDRState<mode> * xdr,MutableHandleValue vp)110 XDRResult js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp) {
111 JSContext* cx = xdr->cx();
112
113 enum ConstTag {
114 SCRIPT_INT,
115 SCRIPT_DOUBLE,
116 SCRIPT_ATOM,
117 SCRIPT_TRUE,
118 SCRIPT_FALSE,
119 SCRIPT_NULL,
120 SCRIPT_OBJECT,
121 SCRIPT_VOID,
122 SCRIPT_HOLE,
123 SCRIPT_BIGINT
124 };
125
126 ConstTag tag;
127 if (mode == XDR_ENCODE) {
128 if (vp.isInt32()) {
129 tag = SCRIPT_INT;
130 } else if (vp.isDouble()) {
131 tag = SCRIPT_DOUBLE;
132 } else if (vp.isString()) {
133 tag = SCRIPT_ATOM;
134 } else if (vp.isTrue()) {
135 tag = SCRIPT_TRUE;
136 } else if (vp.isFalse()) {
137 tag = SCRIPT_FALSE;
138 } else if (vp.isNull()) {
139 tag = SCRIPT_NULL;
140 } else if (vp.isObject()) {
141 tag = SCRIPT_OBJECT;
142 } else if (vp.isMagic(JS_ELEMENTS_HOLE)) {
143 tag = SCRIPT_HOLE;
144 } else if (vp.isBigInt()) {
145 tag = SCRIPT_BIGINT;
146 } else {
147 MOZ_ASSERT(vp.isUndefined());
148 tag = SCRIPT_VOID;
149 }
150 }
151
152 MOZ_TRY(xdr->codeEnum32(&tag));
153
154 switch (tag) {
155 case SCRIPT_INT: {
156 uint32_t i;
157 if (mode == XDR_ENCODE) {
158 i = uint32_t(vp.toInt32());
159 }
160 MOZ_TRY(xdr->codeUint32(&i));
161 if (mode == XDR_DECODE) {
162 vp.set(Int32Value(int32_t(i)));
163 }
164 break;
165 }
166 case SCRIPT_DOUBLE: {
167 double d;
168 if (mode == XDR_ENCODE) {
169 d = vp.toDouble();
170 }
171 MOZ_TRY(xdr->codeDouble(&d));
172 if (mode == XDR_DECODE) {
173 vp.set(DoubleValue(d));
174 }
175 break;
176 }
177 case SCRIPT_ATOM: {
178 RootedAtom atom(cx);
179 if (mode == XDR_ENCODE) {
180 atom = &vp.toString()->asAtom();
181 }
182 MOZ_TRY(XDRAtom(xdr, &atom));
183 if (mode == XDR_DECODE) {
184 vp.set(StringValue(atom));
185 }
186 break;
187 }
188 case SCRIPT_TRUE:
189 if (mode == XDR_DECODE) {
190 vp.set(BooleanValue(true));
191 }
192 break;
193 case SCRIPT_FALSE:
194 if (mode == XDR_DECODE) {
195 vp.set(BooleanValue(false));
196 }
197 break;
198 case SCRIPT_NULL:
199 if (mode == XDR_DECODE) {
200 vp.set(NullValue());
201 }
202 break;
203 case SCRIPT_OBJECT: {
204 RootedObject obj(cx);
205 if (mode == XDR_ENCODE) {
206 obj = &vp.toObject();
207 }
208
209 MOZ_TRY(XDRObjectLiteral(xdr, &obj));
210
211 if (mode == XDR_DECODE) {
212 vp.setObject(*obj);
213 }
214 break;
215 }
216 case SCRIPT_VOID:
217 if (mode == XDR_DECODE) {
218 vp.set(UndefinedValue());
219 }
220 break;
221 case SCRIPT_HOLE:
222 if (mode == XDR_DECODE) {
223 vp.setMagic(JS_ELEMENTS_HOLE);
224 }
225 break;
226 case SCRIPT_BIGINT: {
227 RootedBigInt bi(cx);
228 if (mode == XDR_ENCODE) {
229 bi = vp.toBigInt();
230 }
231
232 MOZ_TRY(XDRBigInt(xdr, &bi));
233
234 if (mode == XDR_DECODE) {
235 vp.setBigInt(bi);
236 }
237 break;
238 }
239 default:
240 // Fail in debug, but only soft-fail in release
241 MOZ_ASSERT(false, "Bad XDR value kind");
242 return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
243 }
244 return Ok();
245 }
246
247 template XDRResult js::XDRScriptConst(XDRState<XDR_ENCODE>*,
248 MutableHandleValue);
249
250 template XDRResult js::XDRScriptConst(XDRState<XDR_DECODE>*,
251 MutableHandleValue);
252
253 // Code lazy scripts's closed over bindings.
254 template <XDRMode mode>
255 /* static */
XDRLazyScriptData(XDRState<mode> * xdr,HandleScriptSourceObject sourceObject,Handle<BaseScript * > lazy)256 XDRResult BaseScript::XDRLazyScriptData(XDRState<mode>* xdr,
257 HandleScriptSourceObject sourceObject,
258 Handle<BaseScript*> lazy) {
259 JSContext* cx = xdr->cx();
260
261 RootedAtom atom(cx);
262 RootedFunction func(cx);
263
264 if (lazy->useMemberInitializers()) {
265 uint32_t bits;
266 if (mode == XDR_ENCODE) {
267 MOZ_ASSERT(lazy->getMemberInitializers().valid);
268 bits = lazy->getMemberInitializers().serialize();
269 }
270 MOZ_TRY(xdr->codeUint32(&bits));
271 if (mode == XDR_DECODE) {
272 lazy->setMemberInitializers(MemberInitializers::deserialize(bits));
273 }
274 }
275
276 mozilla::Span<JS::GCCellPtr> gcThings =
277 lazy->data_ ? lazy->data_->gcthings() : mozilla::Span<JS::GCCellPtr>();
278
279 for (JS::GCCellPtr& elem : gcThings) {
280 JS::TraceKind kind = elem.kind();
281
282 MOZ_TRY(xdr->codeEnum32(&kind));
283
284 switch (kind) {
285 case JS::TraceKind::Object: {
286 if (mode == XDR_ENCODE) {
287 func = &elem.as<JSObject>().as<JSFunction>();
288 }
289 MOZ_TRY(XDRInterpretedFunction(xdr, nullptr, sourceObject, &func));
290 if (mode == XDR_DECODE) {
291 func->setEnclosingLazyScript(lazy);
292
293 elem = JS::GCCellPtr(func);
294 }
295 break;
296 }
297
298 case JS::TraceKind::String: {
299 if (mode == XDR_ENCODE) {
300 gc::Cell* cell = elem.asCell();
301 MOZ_ASSERT_IF(cell, cell->as<JSString>()->isAtom());
302 atom = static_cast<JSAtom*>(cell);
303 }
304 MOZ_TRY(XDRAtom(xdr, &atom));
305 if (mode == XDR_DECODE) {
306 elem = JS::GCCellPtr(static_cast<JSString*>(atom));
307 }
308 break;
309 }
310
311 case JS::TraceKind::Null: {
312 // This is default so nothing to do.
313 MOZ_ASSERT(!elem);
314 break;
315 }
316
317 default: {
318 // Fail in debug, but only soft-fail in release
319 MOZ_ASSERT(false, "Bad XDR class kind");
320 return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
321 }
322 }
323 }
324
325 return Ok();
326 }
327
FindScopeIndex(mozilla::Span<const JS::GCCellPtr> scopes,Scope & scope)328 static inline uint32_t FindScopeIndex(mozilla::Span<const JS::GCCellPtr> scopes,
329 Scope& scope) {
330 unsigned length = scopes.size();
331 for (uint32_t i = 0; i < length; ++i) {
332 if (scopes[i].asCell() == &scope) {
333 return i;
334 }
335 }
336
337 MOZ_CRASH("Scope not found");
338 }
339
340 template <XDRMode mode>
XDRInnerObject(XDRState<mode> * xdr,js::PrivateScriptData * data,HandleScriptSourceObject sourceObject,MutableHandleObject inner)341 static XDRResult XDRInnerObject(XDRState<mode>* xdr,
342 js::PrivateScriptData* data,
343 HandleScriptSourceObject sourceObject,
344 MutableHandleObject inner) {
345 enum class ClassKind { RegexpObject, JSFunction, JSObject, ArrayObject };
346 JSContext* cx = xdr->cx();
347
348 ClassKind classk;
349
350 if (mode == XDR_ENCODE) {
351 if (inner->is<RegExpObject>()) {
352 classk = ClassKind::RegexpObject;
353 } else if (inner->is<JSFunction>()) {
354 classk = ClassKind::JSFunction;
355 } else if (inner->is<PlainObject>()) {
356 classk = ClassKind::JSObject;
357 } else if (inner->is<ArrayObject>()) {
358 classk = ClassKind::ArrayObject;
359 } else {
360 MOZ_CRASH("Cannot encode this class of object.");
361 }
362 }
363
364 MOZ_TRY(xdr->codeEnum32(&classk));
365
366 switch (classk) {
367 case ClassKind::RegexpObject: {
368 Rooted<RegExpObject*> regexp(cx);
369 if (mode == XDR_ENCODE) {
370 regexp = &inner->as<RegExpObject>();
371 }
372 MOZ_TRY(XDRScriptRegExpObject(xdr, ®exp));
373 if (mode == XDR_DECODE) {
374 inner.set(regexp);
375 }
376 break;
377 }
378
379 case ClassKind::JSFunction: {
380 /* Code the nested function's enclosing scope. */
381 uint32_t funEnclosingScopeIndex = 0;
382 RootedScope funEnclosingScope(cx);
383
384 if (mode == XDR_ENCODE) {
385 RootedFunction function(cx, &inner->as<JSFunction>());
386
387 if (function->isAsmJSNative()) {
388 return xdr->fail(JS::TranscodeResult::Failure_AsmJSNotSupported);
389 }
390
391 MOZ_ASSERT(function->enclosingScope());
392 funEnclosingScope = function->enclosingScope();
393 funEnclosingScopeIndex =
394 FindScopeIndex(data->gcthings(), *funEnclosingScope);
395 }
396
397 MOZ_TRY(xdr->codeUint32(&funEnclosingScopeIndex));
398
399 if (mode == XDR_DECODE) {
400 funEnclosingScope =
401 &data->gcthings()[funEnclosingScopeIndex].as<Scope>();
402 }
403
404 // Code nested function and script.
405 RootedFunction tmp(cx);
406 if (mode == XDR_ENCODE) {
407 tmp = &inner->as<JSFunction>();
408 }
409 MOZ_TRY(
410 XDRInterpretedFunction(xdr, funEnclosingScope, sourceObject, &tmp));
411 if (mode == XDR_DECODE) {
412 inner.set(tmp);
413 }
414 break;
415 }
416
417 case ClassKind::JSObject:
418 case ClassKind::ArrayObject: {
419 /* Code object literal. */
420 RootedObject tmp(cx);
421 if (mode == XDR_ENCODE) {
422 tmp = inner.get();
423 }
424 MOZ_TRY(XDRObjectLiteral(xdr, &tmp));
425 if (mode == XDR_DECODE) {
426 inner.set(tmp);
427 }
428 break;
429 }
430
431 default: {
432 // Fail in debug, but only soft-fail in release
433 MOZ_ASSERT(false, "Bad XDR class kind");
434 return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
435 }
436 }
437
438 return Ok();
439 }
440
441 template <XDRMode mode>
XDRScope(XDRState<mode> * xdr,js::PrivateScriptData * data,HandleScope scriptEnclosingScope,HandleObject funOrMod,bool isFirstScope,MutableHandleScope scope)442 static XDRResult XDRScope(XDRState<mode>* xdr, js::PrivateScriptData* data,
443 HandleScope scriptEnclosingScope,
444 HandleObject funOrMod, bool isFirstScope,
445 MutableHandleScope scope) {
446 JSContext* cx = xdr->cx();
447
448 ScopeKind scopeKind;
449 RootedScope enclosing(cx);
450 RootedFunction fun(cx);
451 RootedModuleObject module(cx);
452 uint32_t enclosingIndex = 0;
453
454 // The enclosingScope is encoded using an integer index into the scope array.
455 // This means that scopes must be topologically sorted.
456 if (mode == XDR_ENCODE) {
457 scopeKind = scope->kind();
458
459 if (isFirstScope) {
460 enclosingIndex = UINT32_MAX;
461 } else {
462 MOZ_ASSERT(scope->enclosing());
463 enclosingIndex = FindScopeIndex(data->gcthings(), *scope->enclosing());
464 }
465 }
466
467 MOZ_TRY(xdr->codeEnum32(&scopeKind));
468 MOZ_TRY(xdr->codeUint32(&enclosingIndex));
469
470 if (mode == XDR_DECODE) {
471 if (isFirstScope) {
472 MOZ_ASSERT(enclosingIndex == UINT32_MAX);
473 enclosing = scriptEnclosingScope;
474 } else {
475 enclosing = &data->gcthings()[enclosingIndex].as<Scope>();
476 }
477
478 if (funOrMod && funOrMod->is<ModuleObject>()) {
479 module.set(funOrMod.as<ModuleObject>());
480 } else if (funOrMod && funOrMod->is<JSFunction>()) {
481 fun.set(funOrMod.as<JSFunction>());
482 }
483 }
484
485 switch (scopeKind) {
486 case ScopeKind::Function:
487 MOZ_TRY(FunctionScope::XDR(xdr, fun, enclosing, scope));
488 break;
489 case ScopeKind::FunctionBodyVar:
490 MOZ_TRY(VarScope::XDR(xdr, scopeKind, enclosing, scope));
491 break;
492 case ScopeKind::Lexical:
493 case ScopeKind::SimpleCatch:
494 case ScopeKind::Catch:
495 case ScopeKind::NamedLambda:
496 case ScopeKind::StrictNamedLambda:
497 case ScopeKind::FunctionLexical:
498 MOZ_TRY(LexicalScope::XDR(xdr, scopeKind, enclosing, scope));
499 break;
500 case ScopeKind::ClassBody:
501 MOZ_TRY(ClassBodyScope::XDR(xdr, scopeKind, enclosing, scope));
502 break;
503 case ScopeKind::With:
504 MOZ_TRY(WithScope::XDR(xdr, enclosing, scope));
505 break;
506 case ScopeKind::Eval:
507 case ScopeKind::StrictEval:
508 MOZ_TRY(EvalScope::XDR(xdr, scopeKind, enclosing, scope));
509 break;
510 case ScopeKind::Global:
511 case ScopeKind::NonSyntactic:
512 MOZ_TRY(GlobalScope::XDR(xdr, scopeKind, scope));
513 break;
514 case ScopeKind::Module:
515 MOZ_TRY(ModuleScope::XDR(xdr, module, enclosing, scope));
516 break;
517 case ScopeKind::WasmInstance:
518 MOZ_CRASH("NYI");
519 break;
520 case ScopeKind::WasmFunction:
521 MOZ_CRASH("wasm functions cannot be nested in JSScripts");
522 break;
523 default:
524 // Fail in debug, but only soft-fail in release
525 MOZ_ASSERT(false, "Bad XDR scope kind");
526 return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
527 }
528
529 return Ok();
530 }
531
532 template <XDRMode mode>
XDRScriptGCThing(XDRState<mode> * xdr,PrivateScriptData * data,HandleScriptSourceObject sourceObject,HandleScope scriptEnclosingScope,HandleObject funOrMod,bool * isFirstScope,JS::GCCellPtr * thingp)533 static XDRResult XDRScriptGCThing(XDRState<mode>* xdr, PrivateScriptData* data,
534 HandleScriptSourceObject sourceObject,
535 HandleScope scriptEnclosingScope,
536 HandleObject funOrMod, bool* isFirstScope,
537 JS::GCCellPtr* thingp) {
538 JSContext* cx = xdr->cx();
539
540 JS::TraceKind kind = thingp->kind();
541
542 MOZ_TRY(xdr->codeEnum32(&kind));
543
544 switch (kind) {
545 case JS::TraceKind::String: {
546 RootedAtom atom(cx);
547 if (mode == XDR_ENCODE) {
548 atom = &thingp->as<JSString>().asAtom();
549 }
550 MOZ_TRY(XDRAtom(xdr, &atom));
551 if (mode == XDR_DECODE) {
552 *thingp = JS::GCCellPtr(atom.get());
553 }
554 break;
555 }
556
557 case JS::TraceKind::Object: {
558 RootedObject obj(cx);
559 if (mode == XDR_ENCODE) {
560 obj = &thingp->as<JSObject>();
561 }
562 MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &obj));
563 if (mode == XDR_DECODE) {
564 *thingp = JS::GCCellPtr(obj.get());
565 }
566 break;
567 }
568
569 case JS::TraceKind::Scope: {
570 RootedScope scope(cx);
571 if (mode == XDR_ENCODE) {
572 scope = &thingp->as<Scope>();
573 }
574 MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, funOrMod, *isFirstScope,
575 &scope));
576 if (mode == XDR_DECODE) {
577 *thingp = JS::GCCellPtr(scope.get());
578 }
579 *isFirstScope = false;
580 break;
581 }
582
583 case JS::TraceKind::BigInt: {
584 RootedBigInt bi(cx);
585 if (mode == XDR_ENCODE) {
586 bi = &thingp->as<BigInt>();
587 }
588 MOZ_TRY(XDRBigInt(xdr, &bi));
589 if (mode == XDR_DECODE) {
590 *thingp = JS::GCCellPtr(bi.get());
591 }
592 break;
593 }
594
595 default:
596 // Fail in debug, but only soft-fail in release.
597 MOZ_ASSERT(false, "Bad XDR class kind");
598 return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
599 }
600 return Ok();
601 }
602
isUsingInterpreterTrampoline(JSRuntime * rt) const603 bool js::BaseScript::isUsingInterpreterTrampoline(JSRuntime* rt) const {
604 return jitCodeRaw() == rt->jitRuntime()->interpreterStub().value;
605 }
606
maybeForwardedScriptSource() const607 js::ScriptSource* js::BaseScript::maybeForwardedScriptSource() const {
608 return MaybeForwarded(sourceObject())->source();
609 }
610
setEnclosingScript(BaseScript * enclosingScript)611 void js::BaseScript::setEnclosingScript(BaseScript* enclosingScript) {
612 MOZ_ASSERT(enclosingScript);
613 warmUpData_.initEnclosingScript(enclosingScript);
614 }
615
setEnclosingScope(Scope * enclosingScope)616 void js::BaseScript::setEnclosingScope(Scope* enclosingScope) {
617 if (warmUpData_.isEnclosingScript()) {
618 warmUpData_.clearEnclosingScript();
619 }
620
621 MOZ_ASSERT(enclosingScope);
622 warmUpData_.initEnclosingScope(enclosingScope);
623 }
624
finalize(JSFreeOp * fop)625 void js::BaseScript::finalize(JSFreeOp* fop) {
626 // Scripts with bytecode may have optional data stored in per-runtime or
627 // per-zone maps. Note that a failed compilation must not have entries since
628 // the script itself will not be marked as having bytecode.
629 if (hasBytecode()) {
630 JSScript* script = this->asJSScript();
631
632 if (coverage::IsLCovEnabled()) {
633 coverage::CollectScriptCoverage(script, true);
634 }
635
636 script->destroyScriptCounts();
637 }
638
639 fop->runtime()->geckoProfiler().onScriptFinalized(this);
640
641 #ifdef MOZ_VTUNE
642 if (zone()->scriptVTuneIdMap) {
643 // Note: we should only get here if the VTune JIT profiler is running.
644 zone()->scriptVTuneIdMap->remove(this);
645 }
646 #endif
647
648 if (warmUpData_.isJitScript()) {
649 JSScript* script = this->asJSScript();
650 #ifdef JS_CACHEIR_SPEW
651 maybeUpdateWarmUpCount(script);
652 #endif
653 script->releaseJitScriptOnFinalize(fop);
654 }
655
656 #ifdef JS_CACHEIR_SPEW
657 if (hasBytecode()) {
658 maybeSpewScriptFinalWarmUpCount(this->asJSScript());
659 }
660 #endif
661
662 if (data_) {
663 // We don't need to triger any barriers here, just free the memory.
664 size_t size = data_->allocationSize();
665 AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size,
666 MemCheckKind::MakeNoAccess);
667 fop->free_(this, data_, size, MemoryUse::ScriptPrivateData);
668 }
669
670 freeSharedData();
671 }
672
releaseEnclosingScope()673 js::Scope* js::BaseScript::releaseEnclosingScope() {
674 Scope* enclosing = warmUpData_.toEnclosingScope();
675 warmUpData_.clearEnclosingScope();
676 return enclosing;
677 }
678
swapData(UniquePtr<PrivateScriptData> & other)679 void js::BaseScript::swapData(UniquePtr<PrivateScriptData>& other) {
680 PrivateScriptData* tmp = other.release();
681
682 if (data_) {
683 // When disconnecting script data from the BaseScript, we must pre-barrier
684 // all edges contained in it. Those edges are no longer reachable from
685 // current location in the graph.
686 PreWriteBarrier(zone(), data_);
687
688 RemoveCellMemory(this, data_->allocationSize(),
689 MemoryUse::ScriptPrivateData);
690 }
691
692 std::swap(tmp, data_);
693
694 if (data_) {
695 AddCellMemory(this, data_->allocationSize(), MemoryUse::ScriptPrivateData);
696 }
697
698 other.reset(tmp);
699 }
700
enclosingScope() const701 js::Scope* js::BaseScript::enclosingScope() const {
702 MOZ_ASSERT(!warmUpData_.isEnclosingScript(),
703 "Enclosing scope is not computed yet");
704
705 if (warmUpData_.isEnclosingScope()) {
706 return warmUpData_.toEnclosingScope();
707 }
708
709 MOZ_ASSERT(data_, "Script doesn't seem to be compiled");
710
711 return gcthings()[js::GCThingIndex::outermostScopeIndex()]
712 .as<Scope>()
713 .enclosing();
714 }
715
numAlwaysLiveFixedSlots() const716 size_t JSScript::numAlwaysLiveFixedSlots() const {
717 if (bodyScope()->is<js::FunctionScope>()) {
718 return bodyScope()->as<js::FunctionScope>().nextFrameSlot();
719 }
720 if (bodyScope()->is<js::ModuleScope>()) {
721 return bodyScope()->as<js::ModuleScope>().nextFrameSlot();
722 }
723 if (bodyScope()->is<js::EvalScope>() &&
724 bodyScope()->kind() == ScopeKind::StrictEval) {
725 return bodyScope()->as<js::EvalScope>().nextFrameSlot();
726 }
727 return 0;
728 }
729
numArgs() const730 unsigned JSScript::numArgs() const {
731 if (bodyScope()->is<js::FunctionScope>()) {
732 return bodyScope()->as<js::FunctionScope>().numPositionalFormalParameters();
733 }
734 return 0;
735 }
736
functionHasParameterExprs() const737 bool JSScript::functionHasParameterExprs() const {
738 // Only functions have parameters.
739 js::Scope* scope = bodyScope();
740 if (!scope->is<js::FunctionScope>()) {
741 return false;
742 }
743 return scope->as<js::FunctionScope>().hasParameterExprs();
744 }
745
module() const746 js::ModuleObject* JSScript::module() const {
747 if (bodyScope()->is<js::ModuleScope>()) {
748 return bodyScope()->as<js::ModuleScope>().module();
749 }
750 return nullptr;
751 }
752
isGlobalCode() const753 bool JSScript::isGlobalCode() const {
754 return bodyScope()->is<js::GlobalScope>();
755 }
756
functionExtraBodyVarScope() const757 js::VarScope* JSScript::functionExtraBodyVarScope() const {
758 MOZ_ASSERT(functionHasExtraBodyVarScope());
759 for (JS::GCCellPtr gcThing : gcthings()) {
760 if (!gcThing.is<js::Scope>()) {
761 continue;
762 }
763 js::Scope* scope = &gcThing.as<js::Scope>();
764 if (scope->kind() == js::ScopeKind::FunctionBodyVar) {
765 return &scope->as<js::VarScope>();
766 }
767 }
768 MOZ_CRASH("Function extra body var scope not found");
769 }
770
needsBodyEnvironment() const771 bool JSScript::needsBodyEnvironment() const {
772 for (JS::GCCellPtr gcThing : gcthings()) {
773 if (!gcThing.is<js::Scope>()) {
774 continue;
775 }
776 js::Scope* scope = &gcThing.as<js::Scope>();
777 if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) {
778 return true;
779 }
780 }
781 return false;
782 }
783
isDirectEvalInFunction() const784 bool JSScript::isDirectEvalInFunction() const {
785 if (!isForEval()) {
786 return false;
787 }
788 return bodyScope()->hasOnChain(js::ScopeKind::Function);
789 }
790
791 template <XDRMode mode>
792 /* static */
XDR(XDRState<mode> * xdr,HandleScript script,HandleScriptSourceObject sourceObject,HandleScope scriptEnclosingScope,HandleObject funOrMod)793 XDRResult js::PrivateScriptData::XDR(XDRState<mode>* xdr, HandleScript script,
794 HandleScriptSourceObject sourceObject,
795 HandleScope scriptEnclosingScope,
796 HandleObject funOrMod) {
797 uint32_t ngcthings = 0;
798
799 JSContext* cx = xdr->cx();
800 PrivateScriptData* data = nullptr;
801
802 if (mode == XDR_ENCODE) {
803 data = script->data_;
804
805 ngcthings = data->gcthings().size();
806 }
807
808 MOZ_TRY(xdr->codeUint32(&ngcthings));
809
810 if (mode == XDR_DECODE) {
811 if (!JSScript::createPrivateScriptData(cx, script, ngcthings)) {
812 return xdr->fail(JS::TranscodeResult::Throw);
813 }
814
815 data = script->data_;
816 }
817
818 // Code the field initializer data.
819 if (script->useMemberInitializers()) {
820 uint32_t bits;
821 if (mode == XDR_ENCODE) {
822 MOZ_ASSERT(data->getMemberInitializers().valid);
823 bits = data->getMemberInitializers().serialize();
824 }
825 MOZ_TRY(xdr->codeUint32(&bits));
826 if (mode == XDR_DECODE) {
827 data->setMemberInitializers(MemberInitializers::deserialize(bits));
828 }
829 }
830
831 bool isFirstScope = true;
832 for (JS::GCCellPtr& gcThing : data->gcthings()) {
833 MOZ_TRY(XDRScriptGCThing(xdr, data, sourceObject, scriptEnclosingScope,
834 funOrMod, &isFirstScope, &gcThing));
835 }
836
837 // Verify marker to detect data corruption after decoding GC things. A
838 // mismatch here indicates we will almost certainly crash in release.
839 MOZ_TRY(xdr->codeMarker(0xF83B989A));
840
841 return Ok();
842 }
843
844 // Initialize the optional arrays in the trailing allocation. This is a set of
845 // offsets that delimit each optional array followed by the arrays themselves.
846 // See comment before 'ImmutableScriptData' for more details.
initOptionalArrays(Offset * pcursor,uint32_t numResumeOffsets,uint32_t numScopeNotes,uint32_t numTryNotes)847 void ImmutableScriptData::initOptionalArrays(Offset* pcursor,
848 uint32_t numResumeOffsets,
849 uint32_t numScopeNotes,
850 uint32_t numTryNotes) {
851 Offset cursor = (*pcursor);
852
853 // The byte arrays must have already been padded.
854 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor),
855 "Bytecode and source notes should be padded to keep alignment");
856
857 // Each non-empty optional array needs will need an offset to its end.
858 unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) +
859 unsigned(numScopeNotes > 0) +
860 unsigned(numTryNotes > 0);
861
862 // Default-initialize the optional-offsets.
863 initElements<Offset>(cursor, numOptionalArrays);
864 cursor += numOptionalArrays * sizeof(Offset);
865
866 // Offset between optional-offsets table and the optional arrays. This is
867 // later used to access the optional-offsets table as well as first optional
868 // array.
869 optArrayOffset_ = cursor;
870
871 // Each optional array that follows must store an end-offset in the offset
872 // table. Assign table entries by using this 'offsetIndex'. The index 0 is
873 // reserved for implicit value 'optArrayOffset'.
874 int offsetIndex = 0;
875
876 // Default-initialize optional 'resumeOffsets'.
877 MOZ_ASSERT(resumeOffsetsOffset() == cursor);
878 if (numResumeOffsets > 0) {
879 initElements<uint32_t>(cursor, numResumeOffsets);
880 cursor += numResumeOffsets * sizeof(uint32_t);
881 setOptionalOffset(++offsetIndex, cursor);
882 }
883 flagsRef().resumeOffsetsEndIndex = offsetIndex;
884
885 // Default-initialize optional 'scopeNotes'.
886 MOZ_ASSERT(scopeNotesOffset() == cursor);
887 if (numScopeNotes > 0) {
888 initElements<ScopeNote>(cursor, numScopeNotes);
889 cursor += numScopeNotes * sizeof(ScopeNote);
890 setOptionalOffset(++offsetIndex, cursor);
891 }
892 flagsRef().scopeNotesEndIndex = offsetIndex;
893
894 // Default-initialize optional 'tryNotes'
895 MOZ_ASSERT(tryNotesOffset() == cursor);
896 if (numTryNotes > 0) {
897 initElements<TryNote>(cursor, numTryNotes);
898 cursor += numTryNotes * sizeof(TryNote);
899 setOptionalOffset(++offsetIndex, cursor);
900 }
901 flagsRef().tryNotesEndIndex = offsetIndex;
902
903 MOZ_ASSERT(endOffset() == cursor);
904 (*pcursor) = cursor;
905 }
906
ImmutableScriptData(uint32_t codeLength,uint32_t noteLength,uint32_t numResumeOffsets,uint32_t numScopeNotes,uint32_t numTryNotes)907 ImmutableScriptData::ImmutableScriptData(uint32_t codeLength,
908 uint32_t noteLength,
909 uint32_t numResumeOffsets,
910 uint32_t numScopeNotes,
911 uint32_t numTryNotes)
912 : codeLength_(codeLength) {
913 // Variable-length data begins immediately after ImmutableScriptData itself.
914 Offset cursor = sizeof(ImmutableScriptData);
915
916 // The following arrays are byte-aligned with additional padding to ensure
917 // that together they maintain uint32_t-alignment.
918 {
919 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));
920
921 // Zero-initialize 'flags'
922 MOZ_ASSERT(isAlignedOffset<Flags>(cursor));
923 new (offsetToPointer<void>(cursor)) Flags{};
924 cursor += sizeof(Flags);
925
926 initElements<jsbytecode>(cursor, codeLength);
927 cursor += codeLength * sizeof(jsbytecode);
928
929 initElements<SrcNote>(cursor, noteLength);
930 cursor += noteLength * sizeof(SrcNote);
931
932 MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor));
933 }
934
935 // Initialization for remaining arrays.
936 initOptionalArrays(&cursor, numResumeOffsets, numScopeNotes, numTryNotes);
937
938 // Check that we correctly recompute the expected values.
939 MOZ_ASSERT(this->codeLength() == codeLength);
940 MOZ_ASSERT(this->noteLength() == noteLength);
941
942 // Sanity check
943 MOZ_ASSERT(endOffset() == cursor);
944 }
945
946 template <XDRMode mode>
XDRImmutableScriptData(XDRState<mode> * xdr,SharedImmutableScriptData & sisd)947 XDRResult js::XDRImmutableScriptData(XDRState<mode>* xdr,
948 SharedImmutableScriptData& sisd) {
949 static_assert(frontend::CanCopyDataToDisk<ImmutableScriptData>::value,
950 "ImmutableScriptData cannot be bulk-copied to disk");
951 static_assert(frontend::CanCopyDataToDisk<jsbytecode>::value,
952 "jsbytecode cannot be bulk-copied to disk");
953 static_assert(frontend::CanCopyDataToDisk<SrcNote>::value,
954 "SrcNote cannot be bulk-copied to disk");
955 static_assert(frontend::CanCopyDataToDisk<ScopeNote>::value,
956 "ScopeNote cannot be bulk-copied to disk");
957 static_assert(frontend::CanCopyDataToDisk<TryNote>::value,
958 "TryNote cannot be bulk-copied to disk");
959
960 uint32_t size;
961 if (mode == XDR_ENCODE) {
962 size = sisd.immutableDataLength();
963 }
964 MOZ_TRY(xdr->codeUint32(&size));
965
966 MOZ_TRY(xdr->align32());
967 static_assert(alignof(ImmutableScriptData) <= alignof(uint32_t));
968
969 if (mode == XDR_ENCODE) {
970 uint8_t* data = const_cast<uint8_t*>(sisd.get()->immutableData().data());
971 MOZ_ASSERT(data == reinterpret_cast<const uint8_t*>(sisd.get()),
972 "Decode below relies on the data placement");
973 MOZ_TRY(xdr->codeBytes(data, size));
974 } else {
975 MOZ_ASSERT(!sisd.get());
976
977 if (xdr->hasOptions() && xdr->options().usePinnedBytecode) {
978 ImmutableScriptData* isd;
979 MOZ_TRY(xdr->borrowedData(&isd, size));
980 sisd.setExternal(isd);
981 } else {
982 auto isd = ImmutableScriptData::new_(xdr->cx(), size);
983 if (!isd) {
984 return xdr->fail(JS::TranscodeResult::Throw);
985 }
986 uint8_t* data = reinterpret_cast<uint8_t*>(isd.get());
987 MOZ_TRY(xdr->codeBytes(data, size));
988 sisd.setOwn(std::move(isd));
989 }
990
991 if (size != sisd.get()->computedSize()) {
992 MOZ_ASSERT(false, "Bad ImmutableScriptData");
993 return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
994 }
995 }
996
997 return Ok();
998 }
999
1000 template XDRResult js::XDRImmutableScriptData(XDRState<XDR_ENCODE>* xdr,
1001 SharedImmutableScriptData& sisd);
1002 template XDRResult js::XDRImmutableScriptData(XDRState<XDR_DECODE>* xdr,
1003 SharedImmutableScriptData& sisd);
1004
1005 template <XDRMode mode>
XDRSourceExtent(XDRState<mode> * xdr,SourceExtent * extent)1006 XDRResult js::XDRSourceExtent(XDRState<mode>* xdr, SourceExtent* extent) {
1007 MOZ_TRY(xdr->codeUint32(&extent->sourceStart));
1008 MOZ_TRY(xdr->codeUint32(&extent->sourceEnd));
1009 MOZ_TRY(xdr->codeUint32(&extent->toStringStart));
1010 MOZ_TRY(xdr->codeUint32(&extent->toStringEnd));
1011 MOZ_TRY(xdr->codeUint32(&extent->lineno));
1012 MOZ_TRY(xdr->codeUint32(&extent->column));
1013
1014 return Ok();
1015 }
1016
1017 template /* static */
1018 XDRResult
1019 js::XDRSourceExtent(XDRState<XDR_ENCODE>* xdr, SourceExtent* extent);
1020
1021 template /* static */
1022 XDRResult
1023 js::XDRSourceExtent(XDRState<XDR_DECODE>* xdr, SourceExtent* extent);
1024
FillImmutableFlagsFromCompileOptionsForTopLevel(const ReadOnlyCompileOptions & options,ImmutableScriptFlags & flags)1025 void js::FillImmutableFlagsFromCompileOptionsForTopLevel(
1026 const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
1027 using ImmutableFlags = ImmutableScriptFlagsEnum;
1028
1029 js::FillImmutableFlagsFromCompileOptionsForFunction(options, flags);
1030
1031 flags.setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce);
1032 flags.setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval);
1033 }
1034
FillImmutableFlagsFromCompileOptionsForFunction(const ReadOnlyCompileOptions & options,ImmutableScriptFlags & flags)1035 void js::FillImmutableFlagsFromCompileOptionsForFunction(
1036 const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) {
1037 using ImmutableFlags = ImmutableScriptFlagsEnum;
1038
1039 flags.setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode);
1040 flags.setFlag(ImmutableFlags::ForceStrict, options.forceStrictMode());
1041 flags.setFlag(ImmutableFlags::HasNonSyntacticScope,
1042 options.nonSyntacticScope);
1043 }
1044
1045 // Check if flags matches to compile options for flags set by
1046 // FillImmutableFlagsFromCompileOptionsForTopLevel above.
1047 //
1048 // If isMultiDecode is true, this check minimal set of CompileOptions that is
1049 // shared across multiple scripts in JS::DecodeMultiOffThreadScripts.
1050 // Other options should be checked when getting the decoded script from the
1051 // cache.
CheckCompileOptionsMatch(const ReadOnlyCompileOptions & options,ImmutableScriptFlags flags,bool isMultiDecode)1052 bool js::CheckCompileOptionsMatch(const ReadOnlyCompileOptions& options,
1053 ImmutableScriptFlags flags,
1054 bool isMultiDecode) {
1055 using ImmutableFlags = ImmutableScriptFlagsEnum;
1056
1057 bool selfHosted = !!(flags & uint32_t(ImmutableFlags::SelfHosted));
1058 bool forceStrict = !!(flags & uint32_t(ImmutableFlags::ForceStrict));
1059 bool hasNonSyntacticScope =
1060 !!(flags & uint32_t(ImmutableFlags::HasNonSyntacticScope));
1061 bool noScriptRval = !!(flags & uint32_t(ImmutableFlags::NoScriptRval));
1062 bool treatAsRunOnce = !!(flags & uint32_t(ImmutableFlags::TreatAsRunOnce));
1063
1064 return options.selfHostingMode == selfHosted &&
1065 options.noScriptRval == noScriptRval &&
1066 options.isRunOnce == treatAsRunOnce &&
1067 (isMultiDecode || (options.forceStrictMode() == forceStrict &&
1068 options.nonSyntacticScope == hasNonSyntacticScope));
1069 }
1070
CheckCompileOptionsMatch(const ReadOnlyCompileOptions & options,JSScript * script)1071 JS_PUBLIC_API bool JS::CheckCompileOptionsMatch(
1072 const ReadOnlyCompileOptions& options, JSScript* script) {
1073 return js::CheckCompileOptionsMatch(options, script->immutableFlags(), false);
1074 }
1075
1076 template <XDRMode mode>
XDRScript(XDRState<mode> * xdr,HandleScope scriptEnclosingScope,HandleScriptSourceObject sourceObjectArg,HandleObject funOrMod,MutableHandleScript scriptp)1077 XDRResult js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
1078 HandleScriptSourceObject sourceObjectArg,
1079 HandleObject funOrMod, MutableHandleScript scriptp) {
1080 /* NB: Keep this in sync with CopyScriptImpl. */
1081
1082 enum XDRScriptFlags {
1083 OwnSource = 1 << 0,
1084 HasLazyScript = 1 << 1,
1085 };
1086
1087 uint8_t xdrFlags = 0;
1088
1089 SourceExtent extent;
1090 uint32_t immutableFlags = 0;
1091
1092 // NOTE: |mutableFlags| are not preserved by XDR.
1093
1094 JSContext* cx = xdr->cx();
1095 RootedScript script(cx);
1096
1097 bool isFunctionScript = funOrMod && funOrMod->is<JSFunction>();
1098
1099 if (mode == XDR_ENCODE) {
1100 script = scriptp.get();
1101
1102 MOZ_ASSERT_IF(isFunctionScript, script->function() == funOrMod);
1103
1104 if (!sourceObjectArg) {
1105 xdrFlags |= OwnSource;
1106 }
1107 // Preserve the MutableFlags::AllowRelazify flag.
1108 if (script->allowRelazify()) {
1109 xdrFlags |= HasLazyScript;
1110 }
1111 }
1112
1113 MOZ_TRY(xdr->codeUint8(&xdrFlags));
1114
1115 if (mode == XDR_ENCODE) {
1116 extent = script->extent();
1117 immutableFlags = script->immutableFlags();
1118 }
1119
1120 MOZ_TRY(XDRSourceExtent(xdr, &extent));
1121 MOZ_TRY(xdr->codeUint32(&immutableFlags));
1122
1123 RootedScriptSourceObject sourceObject(cx, sourceObjectArg);
1124 Maybe<CompileOptions> options;
1125
1126 if (mode == XDR_DECODE) {
1127 MOZ_ASSERT(xdr->hasOptions());
1128
1129 // When loading from the bytecode cache, and if we get the CompileOptions
1130 // from the document, if the ImmutableFlags and options don't agree, we
1131 // should fail. This only applies to the top-level and not its inner
1132 // functions.
1133 //
1134 // Also, JS::DecodeMultiOffThreadScripts uses single CompileOptions for
1135 // multiple scripts with different CompileOptions.
1136 // We should check minimal set of common flags here, and let the consumer
1137 // check the full flags when getting from the cache.
1138 if (xdrFlags & OwnSource) {
1139 options.emplace(xdr->cx(), xdr->options());
1140 if (!js::CheckCompileOptionsMatch(*options,
1141 ImmutableScriptFlags(immutableFlags),
1142 xdr->isMultiDecode())) {
1143 return xdr->fail(JS::TranscodeResult::Failure_WrongCompileOption);
1144 }
1145 }
1146 }
1147
1148 if (xdrFlags & OwnSource) {
1149 RefPtr<ScriptSource> source;
1150
1151 // We are relying on the script's ScriptSource so the caller should not
1152 // have passed in an explicit one.
1153 MOZ_ASSERT(sourceObjectArg == nullptr);
1154
1155 if (mode == XDR_ENCODE) {
1156 sourceObject = script->sourceObject();
1157 source = do_AddRef(sourceObject->source());
1158 }
1159
1160 MOZ_TRY(ScriptSource::XDR(xdr, options.ptrOr(nullptr), source));
1161
1162 if (mode == XDR_DECODE) {
1163 sourceObject = ScriptSourceObject::create(cx, source);
1164 if (!sourceObject) {
1165 return xdr->fail(JS::TranscodeResult::Throw);
1166 }
1167
1168 if (xdr->hasScriptSourceObjectOut()) {
1169 // When the ScriptSourceObjectOut is provided by ParseTask, it
1170 // is stored in a location which is traced by the GC.
1171 *xdr->scriptSourceObjectOut() = sourceObject;
1172 } else if (!ScriptSourceObject::initFromOptions(cx, sourceObject,
1173 *options)) {
1174 return xdr->fail(JS::TranscodeResult::Throw);
1175 }
1176 }
1177 } else {
1178 // While encoding, the ScriptSource passed in must match the ScriptSource
1179 // of the script.
1180 MOZ_ASSERT_IF(mode == XDR_ENCODE,
1181 sourceObjectArg->source() == script->scriptSource());
1182 }
1183
1184 if (mode == XDR_DECODE) {
1185 RootedObject functionOrGlobal(
1186 cx, isFunctionScript ? static_cast<JSObject*>(funOrMod)
1187 : static_cast<JSObject*>(cx->global()));
1188
1189 script = JSScript::Create(cx, functionOrGlobal, sourceObject, extent,
1190 ImmutableScriptFlags(immutableFlags));
1191 if (!script) {
1192 return xdr->fail(JS::TranscodeResult::Throw);
1193 }
1194 scriptp.set(script);
1195
1196 // Set the script in its function now so that inner scripts to be
1197 // decoded may iterate the static scope chain.
1198 if (isFunctionScript) {
1199 funOrMod->as<JSFunction>().initScript(script);
1200 }
1201 }
1202
1203 // If XDR operation fails, we must call BaseScript::freeSharedData in order to
1204 // neuter the script. Various things that iterate raw scripts in a GC arena
1205 // use the presense of this data to detect if initialization is complete.
1206 auto scriptDataGuard = mozilla::MakeScopeExit([&] {
1207 if (mode == XDR_DECODE) {
1208 script->freeSharedData();
1209 }
1210 });
1211
1212 // NOTE: The script data is rooted by the script.
1213 MOZ_TRY(PrivateScriptData::XDR<mode>(xdr, script, sourceObject,
1214 scriptEnclosingScope, funOrMod));
1215 MOZ_TRY(frontend::StencilXDR::codeSharedData<mode>(xdr, script->sharedData_));
1216
1217 if (xdrFlags & HasLazyScript) {
1218 if (mode == XDR_DECODE) {
1219 script->setAllowRelazify();
1220 }
1221 }
1222
1223 if (mode == XDR_DECODE) {
1224 if (coverage::IsLCovEnabled()) {
1225 if (!coverage::InitScriptCoverage(cx, script)) {
1226 return xdr->fail(JS::TranscodeResult::Throw);
1227 }
1228 }
1229
1230 /* see BytecodeEmitter::tellDebuggerAboutCompiledScript */
1231 if (!isFunctionScript && !cx->isHelperThreadContext() &&
1232 !xdr->options().hideFromNewScriptInitial()) {
1233 DebugAPI::onNewScript(cx, script);
1234 }
1235 }
1236
1237 MOZ_ASSERT(script->code(), "Where's our bytecode?");
1238 scriptDataGuard.release();
1239 return Ok();
1240 }
1241
1242 template XDRResult js::XDRScript(XDRState<XDR_ENCODE>*, HandleScope,
1243 HandleScriptSourceObject, HandleObject,
1244 MutableHandleScript);
1245
1246 template XDRResult js::XDRScript(XDRState<XDR_DECODE>*, HandleScope,
1247 HandleScriptSourceObject, HandleObject,
1248 MutableHandleScript);
1249
1250 template <XDRMode mode>
XDRLazyScript(XDRState<mode> * xdr,HandleScope enclosingScope,HandleScriptSourceObject sourceObject,HandleFunction fun,MutableHandle<BaseScript * > lazy)1251 XDRResult js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope,
1252 HandleScriptSourceObject sourceObject,
1253 HandleFunction fun,
1254 MutableHandle<BaseScript*> lazy) {
1255 MOZ_ASSERT_IF(mode == XDR_DECODE, sourceObject);
1256
1257 JSContext* cx = xdr->cx();
1258
1259 {
1260 SourceExtent extent;
1261 uint32_t immutableFlags;
1262 uint32_t ngcthings;
1263
1264 if (mode == XDR_ENCODE) {
1265 MOZ_ASSERT(fun == lazy->function());
1266
1267 extent = lazy->extent();
1268 immutableFlags = lazy->immutableFlags();
1269 ngcthings = lazy->gcthings().size();
1270 }
1271
1272 MOZ_TRY(XDRSourceExtent(xdr, &extent));
1273 MOZ_TRY(xdr->codeUint32(&immutableFlags));
1274 MOZ_TRY(xdr->codeUint32(&ngcthings));
1275
1276 if (mode == XDR_DECODE) {
1277 lazy.set(BaseScript::CreateRawLazy(cx, ngcthings, fun, sourceObject,
1278 extent, immutableFlags));
1279 if (!lazy) {
1280 return xdr->fail(JS::TranscodeResult::Throw);
1281 }
1282
1283 // Set the enclosing scope of the lazy function. This value should only be
1284 // set if we have a non-lazy enclosing script at this point.
1285 // BaseScript::enclosingScriptHasEverBeenCompiled relies on the enclosing
1286 // scope being non-null if we have ever been nested inside non-lazy
1287 // function.
1288 if (enclosingScope) {
1289 lazy->setEnclosingScope(enclosingScope);
1290 }
1291
1292 fun->initScript(lazy);
1293 }
1294 }
1295
1296 MOZ_TRY(BaseScript::XDRLazyScriptData(xdr, sourceObject, lazy));
1297
1298 return Ok();
1299 }
1300
1301 template XDRResult js::XDRLazyScript(XDRState<XDR_ENCODE>*, HandleScope,
1302 HandleScriptSourceObject, HandleFunction,
1303 MutableHandle<BaseScript*>);
1304
1305 template XDRResult js::XDRLazyScript(XDRState<XDR_DECODE>*, HandleScope,
1306 HandleScriptSourceObject, HandleFunction,
1307 MutableHandle<BaseScript*>);
1308
initScriptCounts(JSContext * cx)1309 bool JSScript::initScriptCounts(JSContext* cx) {
1310 MOZ_ASSERT(!hasScriptCounts());
1311
1312 // Record all pc which are the first instruction of a basic block.
1313 mozilla::Vector<jsbytecode*, 16, SystemAllocPolicy> jumpTargets;
1314
1315 js::BytecodeLocation main = mainLocation();
1316 AllBytecodesIterable iterable(this);
1317 for (auto& loc : iterable) {
1318 if (loc.isJumpTarget() || loc == main) {
1319 if (!jumpTargets.append(loc.toRawBytecode())) {
1320 ReportOutOfMemory(cx);
1321 return false;
1322 }
1323 }
1324 }
1325
1326 // Initialize all PCCounts counters to 0.
1327 ScriptCounts::PCCountsVector base;
1328 if (!base.reserve(jumpTargets.length())) {
1329 ReportOutOfMemory(cx);
1330 return false;
1331 }
1332
1333 for (size_t i = 0; i < jumpTargets.length(); i++) {
1334 base.infallibleEmplaceBack(pcToOffset(jumpTargets[i]));
1335 }
1336
1337 // Create zone's scriptCountsMap if necessary.
1338 if (!zone()->scriptCountsMap) {
1339 auto map = cx->make_unique<ScriptCountsMap>();
1340 if (!map) {
1341 return false;
1342 }
1343
1344 zone()->scriptCountsMap = std::move(map);
1345 }
1346
1347 // Allocate the ScriptCounts.
1348 UniqueScriptCounts sc = cx->make_unique<ScriptCounts>(std::move(base));
1349 if (!sc) {
1350 ReportOutOfMemory(cx);
1351 return false;
1352 }
1353
1354 MOZ_ASSERT(this->hasBytecode());
1355
1356 // Register the current ScriptCounts in the zone's map.
1357 if (!zone()->scriptCountsMap->putNew(this, std::move(sc))) {
1358 ReportOutOfMemory(cx);
1359 return false;
1360 }
1361
1362 // safe to set this; we can't fail after this point.
1363 setHasScriptCounts();
1364
1365 // Enable interrupts in any interpreter frames running on this script. This
1366 // is used to let the interpreter increment the PCCounts, if present.
1367 for (ActivationIterator iter(cx); !iter.done(); ++iter) {
1368 if (iter->isInterpreter()) {
1369 iter->asInterpreter()->enableInterruptsIfRunning(this);
1370 }
1371 }
1372
1373 return true;
1374 }
1375
GetScriptCountsMapEntry(JSScript * script)1376 static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript* script) {
1377 MOZ_ASSERT(script->hasScriptCounts());
1378 ScriptCountsMap::Ptr p = script->zone()->scriptCountsMap->lookup(script);
1379 MOZ_ASSERT(p);
1380 return p;
1381 }
1382
getScriptCounts()1383 ScriptCounts& JSScript::getScriptCounts() {
1384 ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
1385 return *p->value();
1386 }
1387
maybeGetPCCounts(size_t offset)1388 js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) {
1389 PCCounts searched = PCCounts(offset);
1390 PCCounts* elem =
1391 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
1392 if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
1393 return nullptr;
1394 }
1395 return elem;
1396 }
1397
maybeGetPCCounts(size_t offset) const1398 const js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) const {
1399 PCCounts searched = PCCounts(offset);
1400 const PCCounts* elem =
1401 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
1402 if (elem == pcCounts_.end() || elem->pcOffset() != offset) {
1403 return nullptr;
1404 }
1405 return elem;
1406 }
1407
getImmediatePrecedingPCCounts(size_t offset)1408 js::PCCounts* ScriptCounts::getImmediatePrecedingPCCounts(size_t offset) {
1409 PCCounts searched = PCCounts(offset);
1410 PCCounts* elem =
1411 std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched);
1412 if (elem == pcCounts_.end()) {
1413 return &pcCounts_.back();
1414 }
1415 if (elem->pcOffset() == offset) {
1416 return elem;
1417 }
1418 if (elem != pcCounts_.begin()) {
1419 return elem - 1;
1420 }
1421 return nullptr;
1422 }
1423
maybeGetThrowCounts(size_t offset) const1424 const js::PCCounts* ScriptCounts::maybeGetThrowCounts(size_t offset) const {
1425 PCCounts searched = PCCounts(offset);
1426 const PCCounts* elem =
1427 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
1428 if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
1429 return nullptr;
1430 }
1431 return elem;
1432 }
1433
getImmediatePrecedingThrowCounts(size_t offset) const1434 const js::PCCounts* ScriptCounts::getImmediatePrecedingThrowCounts(
1435 size_t offset) const {
1436 PCCounts searched = PCCounts(offset);
1437 const PCCounts* elem =
1438 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
1439 if (elem == throwCounts_.end()) {
1440 if (throwCounts_.begin() == throwCounts_.end()) {
1441 return nullptr;
1442 }
1443 return &throwCounts_.back();
1444 }
1445 if (elem->pcOffset() == offset) {
1446 return elem;
1447 }
1448 if (elem != throwCounts_.begin()) {
1449 return elem - 1;
1450 }
1451 return nullptr;
1452 }
1453
getThrowCounts(size_t offset)1454 js::PCCounts* ScriptCounts::getThrowCounts(size_t offset) {
1455 PCCounts searched = PCCounts(offset);
1456 PCCounts* elem =
1457 std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
1458 if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
1459 elem = throwCounts_.insert(elem, searched);
1460 }
1461 return elem;
1462 }
1463
sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)1464 size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
1465 size_t size = mallocSizeOf(this);
1466 size += pcCounts_.sizeOfExcludingThis(mallocSizeOf);
1467 size += throwCounts_.sizeOfExcludingThis(mallocSizeOf);
1468 if (ionCounts_) {
1469 size += ionCounts_->sizeOfIncludingThis(mallocSizeOf);
1470 }
1471 return size;
1472 }
1473
maybeGetPCCounts(jsbytecode * pc)1474 js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) {
1475 MOZ_ASSERT(containsPC(pc));
1476 return getScriptCounts().maybeGetPCCounts(pcToOffset(pc));
1477 }
1478
maybeGetThrowCounts(jsbytecode * pc)1479 const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) {
1480 MOZ_ASSERT(containsPC(pc));
1481 return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc));
1482 }
1483
getThrowCounts(jsbytecode * pc)1484 js::PCCounts* JSScript::getThrowCounts(jsbytecode* pc) {
1485 MOZ_ASSERT(containsPC(pc));
1486 return getScriptCounts().getThrowCounts(pcToOffset(pc));
1487 }
1488
getHitCount(jsbytecode * pc)1489 uint64_t JSScript::getHitCount(jsbytecode* pc) {
1490 MOZ_ASSERT(containsPC(pc));
1491 if (pc < main()) {
1492 pc = main();
1493 }
1494
1495 ScriptCounts& sc = getScriptCounts();
1496 size_t targetOffset = pcToOffset(pc);
1497 const js::PCCounts* baseCount =
1498 sc.getImmediatePrecedingPCCounts(targetOffset);
1499 if (!baseCount) {
1500 return 0;
1501 }
1502 if (baseCount->pcOffset() == targetOffset) {
1503 return baseCount->numExec();
1504 }
1505 MOZ_ASSERT(baseCount->pcOffset() < targetOffset);
1506 uint64_t count = baseCount->numExec();
1507 do {
1508 const js::PCCounts* throwCount =
1509 sc.getImmediatePrecedingThrowCounts(targetOffset);
1510 if (!throwCount) {
1511 return count;
1512 }
1513 if (throwCount->pcOffset() <= baseCount->pcOffset()) {
1514 return count;
1515 }
1516 count -= throwCount->numExec();
1517 targetOffset = throwCount->pcOffset() - 1;
1518 } while (true);
1519 }
1520
addIonCounts(jit::IonScriptCounts * ionCounts)1521 void JSScript::addIonCounts(jit::IonScriptCounts* ionCounts) {
1522 ScriptCounts& sc = getScriptCounts();
1523 if (sc.ionCounts_) {
1524 ionCounts->setPrevious(sc.ionCounts_);
1525 }
1526 sc.ionCounts_ = ionCounts;
1527 }
1528
getIonCounts()1529 jit::IonScriptCounts* JSScript::getIonCounts() {
1530 return getScriptCounts().ionCounts_;
1531 }
1532
releaseScriptCounts(ScriptCounts * counts)1533 void JSScript::releaseScriptCounts(ScriptCounts* counts) {
1534 ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
1535 *counts = std::move(*p->value().get());
1536 zone()->scriptCountsMap->remove(p);
1537 clearHasScriptCounts();
1538 }
1539
destroyScriptCounts()1540 void JSScript::destroyScriptCounts() {
1541 if (hasScriptCounts()) {
1542 ScriptCounts scriptCounts;
1543 releaseScriptCounts(&scriptCounts);
1544 }
1545 }
1546
resetScriptCounts()1547 void JSScript::resetScriptCounts() {
1548 if (!hasScriptCounts()) {
1549 return;
1550 }
1551
1552 ScriptCounts& sc = getScriptCounts();
1553
1554 for (PCCounts& elem : sc.pcCounts_) {
1555 elem.numExec() = 0;
1556 }
1557
1558 for (PCCounts& elem : sc.throwCounts_) {
1559 elem.numExec() = 0;
1560 }
1561 }
1562
finalize(JSFreeOp * fop,JSObject * obj)1563 void ScriptSourceObject::finalize(JSFreeOp* fop, JSObject* obj) {
1564 MOZ_ASSERT(fop->onMainThread());
1565 ScriptSourceObject* sso = &obj->as<ScriptSourceObject>();
1566 if (sso->isCanonical()) {
1567 sso->source()->finalizeGCData();
1568 }
1569 sso->source()->Release();
1570
1571 // Clear the private value, calling the release hook if necessary.
1572 sso->setPrivate(fop->runtime(), UndefinedValue());
1573 }
1574
1575 static const JSClassOps ScriptSourceObjectClassOps = {
1576 nullptr, // addProperty
1577 nullptr, // delProperty
1578 nullptr, // enumerate
1579 nullptr, // newEnumerate
1580 nullptr, // resolve
1581 nullptr, // mayResolve
1582 ScriptSourceObject::finalize, // finalize
1583 nullptr, // call
1584 nullptr, // hasInstance
1585 nullptr, // construct
1586 nullptr, // trace
1587 };
1588
1589 const JSClass ScriptSourceObject::class_ = {
1590 "ScriptSource",
1591 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
1592 &ScriptSourceObjectClassOps};
1593
createInternal(JSContext * cx,ScriptSource * source,HandleObject canonical)1594 ScriptSourceObject* ScriptSourceObject::createInternal(JSContext* cx,
1595 ScriptSource* source,
1596 HandleObject canonical) {
1597 ScriptSourceObject* obj =
1598 NewObjectWithGivenProto<ScriptSourceObject>(cx, nullptr);
1599 if (!obj) {
1600 return nullptr;
1601 }
1602
1603 // The matching decref is in ScriptSourceObject::finalize.
1604 obj->initReservedSlot(SOURCE_SLOT, PrivateValue(do_AddRef(source).take()));
1605
1606 if (canonical) {
1607 obj->initReservedSlot(CANONICAL_SLOT, ObjectValue(*canonical));
1608 } else {
1609 obj->initReservedSlot(CANONICAL_SLOT, ObjectValue(*obj));
1610 }
1611
1612 // The slots below should either be populated by a call to initFromOptions or,
1613 // if this is a non-canonical ScriptSourceObject, they are unused. Poison
1614 // them.
1615 obj->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC));
1616 obj->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC));
1617
1618 return obj;
1619 }
1620
create(JSContext * cx,ScriptSource * source)1621 ScriptSourceObject* ScriptSourceObject::create(JSContext* cx,
1622 ScriptSource* source) {
1623 return createInternal(cx, source, nullptr);
1624 }
1625
clone(JSContext * cx,HandleScriptSourceObject sso)1626 ScriptSourceObject* ScriptSourceObject::clone(JSContext* cx,
1627 HandleScriptSourceObject sso) {
1628 MOZ_ASSERT(cx->compartment() != sso->compartment());
1629
1630 RootedObject wrapped(cx, sso);
1631 if (!cx->compartment()->wrap(cx, &wrapped)) {
1632 return nullptr;
1633 }
1634
1635 return createInternal(cx, sso->source(), wrapped);
1636 }
1637
unwrappedCanonical() const1638 ScriptSourceObject* ScriptSourceObject::unwrappedCanonical() const {
1639 MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtimeFromAnyThread()));
1640
1641 JSObject* obj = &getReservedSlot(CANONICAL_SLOT).toObject();
1642 return &UncheckedUnwrap(obj)->as<ScriptSourceObject>();
1643 }
1644
MaybeValidateFilename(JSContext * cx,HandleScriptSourceObject sso,const ReadOnlyCompileOptions & options)1645 [[nodiscard]] static bool MaybeValidateFilename(
1646 JSContext* cx, HandleScriptSourceObject sso,
1647 const ReadOnlyCompileOptions& options) {
1648 // When parsing off-thread we want to do filename validation on the main
1649 // thread. This makes off-thread parsing more pure and is simpler because we
1650 // can't easily throw exceptions off-thread.
1651 MOZ_ASSERT(!cx->isHelperThreadContext());
1652
1653 if (!gFilenameValidationCallback) {
1654 return true;
1655 }
1656
1657 const char* filename = sso->source()->filename();
1658 if (!filename || options.skipFilenameValidation()) {
1659 return true;
1660 }
1661
1662 if (gFilenameValidationCallback(filename, cx->realm()->isSystem())) {
1663 return true;
1664 }
1665
1666 const char* utf8Filename;
1667 if (mozilla::IsUtf8(mozilla::MakeStringSpan(filename))) {
1668 utf8Filename = filename;
1669 } else {
1670 utf8Filename = "(invalid UTF-8 filename)";
1671 }
1672 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNSAFE_FILENAME,
1673 utf8Filename);
1674 return false;
1675 }
1676
1677 /* static */
initFromOptions(JSContext * cx,HandleScriptSourceObject source,const ReadOnlyCompileOptions & options)1678 bool ScriptSourceObject::initFromOptions(
1679 JSContext* cx, HandleScriptSourceObject source,
1680 const ReadOnlyCompileOptions& options) {
1681 cx->releaseCheck(source);
1682 MOZ_ASSERT(source->isCanonical());
1683 MOZ_ASSERT(
1684 source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC));
1685 MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SCRIPT_SLOT)
1686 .isMagic(JS_GENERIC_MAGIC));
1687
1688 if (!MaybeValidateFilename(cx, source, options)) {
1689 return false;
1690 }
1691
1692 if (options.deferDebugMetadata) {
1693 return true;
1694 }
1695
1696 // Initialize the element attribute slot and introduction script slot
1697 // this marks the SSO as initialized for asserts.
1698
1699 RootedString elementAttributeName(cx);
1700 if (!initElementProperties(cx, source, elementAttributeName)) {
1701 return false;
1702 }
1703
1704 RootedValue introductionScript(cx);
1705 source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);
1706
1707 return true;
1708 }
1709
1710 /* static */
initElementProperties(JSContext * cx,HandleScriptSourceObject source,HandleString elementAttrName)1711 bool ScriptSourceObject::initElementProperties(JSContext* cx,
1712 HandleScriptSourceObject source,
1713 HandleString elementAttrName) {
1714 MOZ_ASSERT(source->isCanonical());
1715
1716 RootedValue nameValue(cx);
1717 if (elementAttrName) {
1718 nameValue = StringValue(elementAttrName);
1719 }
1720 if (!cx->compartment()->wrap(cx, &nameValue)) {
1721 return false;
1722 }
1723
1724 source->setReservedSlot(ELEMENT_PROPERTY_SLOT, nameValue);
1725
1726 return true;
1727 }
1728
setPrivate(JSRuntime * rt,const Value & value)1729 void ScriptSourceObject::setPrivate(JSRuntime* rt, const Value& value) {
1730 // Update the private value, calling addRef/release hooks if necessary
1731 // to allow the embedding to maintain a reference count for the
1732 // private data.
1733 JS::AutoSuppressGCAnalysis nogc;
1734 Value prevValue = getReservedSlot(PRIVATE_SLOT);
1735 rt->releaseScriptPrivate(prevValue);
1736 setReservedSlot(PRIVATE_SLOT, value);
1737 rt->addRefScriptPrivate(value);
1738 }
1739
unwrappedElement(JSContext * cx) const1740 JSObject* ScriptSourceObject::unwrappedElement(JSContext* cx) const {
1741 JS::RootedValue privateValue(cx, unwrappedCanonical()->canonicalPrivate());
1742 if (privateValue.isUndefined()) {
1743 return nullptr;
1744 }
1745
1746 if (cx->runtime()->sourceElementCallback) {
1747 return (*cx->runtime()->sourceElementCallback)(cx, privateValue);
1748 }
1749
1750 return nullptr;
1751 }
1752
1753 class ScriptSource::LoadSourceMatcher {
1754 JSContext* const cx_;
1755 ScriptSource* const ss_;
1756 bool* const loaded_;
1757
1758 public:
LoadSourceMatcher(JSContext * cx,ScriptSource * ss,bool * loaded)1759 explicit LoadSourceMatcher(JSContext* cx, ScriptSource* ss, bool* loaded)
1760 : cx_(cx), ss_(ss), loaded_(loaded) {}
1761
1762 template <typename Unit, SourceRetrievable CanRetrieve>
operator ()(const Compressed<Unit,CanRetrieve> &) const1763 bool operator()(const Compressed<Unit, CanRetrieve>&) const {
1764 *loaded_ = true;
1765 return true;
1766 }
1767
1768 template <typename Unit, SourceRetrievable CanRetrieve>
operator ()(const Uncompressed<Unit,CanRetrieve> &) const1769 bool operator()(const Uncompressed<Unit, CanRetrieve>&) const {
1770 *loaded_ = true;
1771 return true;
1772 }
1773
1774 template <typename Unit>
operator ()(const Retrievable<Unit> &)1775 bool operator()(const Retrievable<Unit>&) {
1776 if (!cx_->runtime()->sourceHook.ref()) {
1777 *loaded_ = false;
1778 return true;
1779 }
1780
1781 size_t length;
1782
1783 // The first argument is just for overloading -- its value doesn't matter.
1784 if (!tryLoadAndSetSource(Unit('0'), &length)) {
1785 return false;
1786 }
1787
1788 return true;
1789 }
1790
operator ()(const Missing &) const1791 bool operator()(const Missing&) const {
1792 *loaded_ = false;
1793 return true;
1794 }
1795
1796 private:
tryLoadAndSetSource(const Utf8Unit &,size_t * length) const1797 bool tryLoadAndSetSource(const Utf8Unit&, size_t* length) const {
1798 char* utf8Source;
1799 if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), nullptr,
1800 &utf8Source, length)) {
1801 return false;
1802 }
1803
1804 if (!utf8Source) {
1805 *loaded_ = false;
1806 return true;
1807 }
1808
1809 if (!ss_->setRetrievedSource(
1810 cx_, EntryUnits<Utf8Unit>(reinterpret_cast<Utf8Unit*>(utf8Source)),
1811 *length)) {
1812 return false;
1813 }
1814
1815 *loaded_ = true;
1816 return true;
1817 }
1818
tryLoadAndSetSource(const char16_t &,size_t * length) const1819 bool tryLoadAndSetSource(const char16_t&, size_t* length) const {
1820 char16_t* utf16Source;
1821 if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &utf16Source,
1822 nullptr, length)) {
1823 return false;
1824 }
1825
1826 if (!utf16Source) {
1827 *loaded_ = false;
1828 return true;
1829 }
1830
1831 if (!ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(utf16Source),
1832 *length)) {
1833 return false;
1834 }
1835
1836 *loaded_ = true;
1837 return true;
1838 }
1839 };
1840
1841 /* static */
loadSource(JSContext * cx,ScriptSource * ss,bool * loaded)1842 bool ScriptSource::loadSource(JSContext* cx, ScriptSource* ss, bool* loaded) {
1843 return ss->data.match(LoadSourceMatcher(cx, ss, loaded));
1844 }
1845
1846 /* static */
sourceData(JSContext * cx,HandleScript script)1847 JSLinearString* JSScript::sourceData(JSContext* cx, HandleScript script) {
1848 MOZ_ASSERT(script->scriptSource()->hasSourceText());
1849 return script->scriptSource()->substring(cx, script->sourceStart(),
1850 script->sourceEnd());
1851 }
1852
appendSourceDataForToString(JSContext * cx,StringBuffer & buf)1853 bool BaseScript::appendSourceDataForToString(JSContext* cx, StringBuffer& buf) {
1854 MOZ_ASSERT(scriptSource()->hasSourceText());
1855 return scriptSource()->appendSubstring(cx, buf, toStringStart(),
1856 toStringEnd());
1857 }
1858
holdEntry(AutoHoldEntry & holder,const ScriptSourceChunk & ssc)1859 void UncompressedSourceCache::holdEntry(AutoHoldEntry& holder,
1860 const ScriptSourceChunk& ssc) {
1861 MOZ_ASSERT(!holder_);
1862 holder.holdEntry(this, ssc);
1863 holder_ = &holder;
1864 }
1865
releaseEntry(AutoHoldEntry & holder)1866 void UncompressedSourceCache::releaseEntry(AutoHoldEntry& holder) {
1867 MOZ_ASSERT(holder_ == &holder);
1868 holder_ = nullptr;
1869 }
1870
1871 template <typename Unit>
lookup(const ScriptSourceChunk & ssc,AutoHoldEntry & holder)1872 const Unit* UncompressedSourceCache::lookup(const ScriptSourceChunk& ssc,
1873 AutoHoldEntry& holder) {
1874 MOZ_ASSERT(!holder_);
1875 MOZ_ASSERT(ssc.ss->isCompressed<Unit>());
1876
1877 if (!map_) {
1878 return nullptr;
1879 }
1880
1881 if (Map::Ptr p = map_->lookup(ssc)) {
1882 holdEntry(holder, ssc);
1883 return static_cast<const Unit*>(p->value().get());
1884 }
1885
1886 return nullptr;
1887 }
1888
put(const ScriptSourceChunk & ssc,SourceData data,AutoHoldEntry & holder)1889 bool UncompressedSourceCache::put(const ScriptSourceChunk& ssc, SourceData data,
1890 AutoHoldEntry& holder) {
1891 MOZ_ASSERT(!holder_);
1892
1893 if (!map_) {
1894 map_ = MakeUnique<Map>();
1895 if (!map_) {
1896 return false;
1897 }
1898 }
1899
1900 if (!map_->put(ssc, std::move(data))) {
1901 return false;
1902 }
1903
1904 holdEntry(holder, ssc);
1905 return true;
1906 }
1907
purge()1908 void UncompressedSourceCache::purge() {
1909 if (!map_) {
1910 return;
1911 }
1912
1913 for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
1914 if (holder_ && r.front().key() == holder_->sourceChunk()) {
1915 holder_->deferDelete(std::move(r.front().value()));
1916 holder_ = nullptr;
1917 }
1918 }
1919
1920 map_ = nullptr;
1921 }
1922
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)1923 size_t UncompressedSourceCache::sizeOfExcludingThis(
1924 mozilla::MallocSizeOf mallocSizeOf) {
1925 size_t n = 0;
1926 if (map_ && !map_->empty()) {
1927 n += map_->shallowSizeOfIncludingThis(mallocSizeOf);
1928 for (Map::Range r = map_->all(); !r.empty(); r.popFront()) {
1929 n += mallocSizeOf(r.front().value().get());
1930 }
1931 }
1932 return n;
1933 }
1934
1935 template <typename Unit>
chunkUnits(JSContext * cx,UncompressedSourceCache::AutoHoldEntry & holder,size_t chunk)1936 const Unit* ScriptSource::chunkUnits(
1937 JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
1938 size_t chunk) {
1939 const CompressedData<Unit>& c = *compressedData<Unit>();
1940
1941 ScriptSourceChunk ssc(this, chunk);
1942 if (const Unit* decompressed =
1943 cx->caches().uncompressedSourceCache.lookup<Unit>(ssc, holder)) {
1944 return decompressed;
1945 }
1946
1947 size_t totalLengthInBytes = length() * sizeof(Unit);
1948 size_t chunkBytes = Compressor::chunkSize(totalLengthInBytes, chunk);
1949
1950 MOZ_ASSERT((chunkBytes % sizeof(Unit)) == 0);
1951 const size_t chunkLength = chunkBytes / sizeof(Unit);
1952 EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(chunkLength));
1953 if (!decompressed) {
1954 JS_ReportOutOfMemory(cx);
1955 return nullptr;
1956 }
1957
1958 // Compression treats input and output memory as plain ol' bytes. These
1959 // reinterpret_cast<>s accord exactly with that.
1960 if (!DecompressStringChunk(
1961 reinterpret_cast<const unsigned char*>(c.raw.chars()), chunk,
1962 reinterpret_cast<unsigned char*>(decompressed.get()), chunkBytes)) {
1963 JS_ReportOutOfMemory(cx);
1964 return nullptr;
1965 }
1966
1967 const Unit* ret = decompressed.get();
1968 if (!cx->caches().uncompressedSourceCache.put(
1969 ssc, ToSourceData(std::move(decompressed)), holder)) {
1970 JS_ReportOutOfMemory(cx);
1971 return nullptr;
1972 }
1973 return ret;
1974 }
1975
1976 template <typename Unit>
convertToCompressedSource(SharedImmutableString compressed,size_t uncompressedLength)1977 void ScriptSource::convertToCompressedSource(SharedImmutableString compressed,
1978 size_t uncompressedLength) {
1979 MOZ_ASSERT(isUncompressed<Unit>());
1980 MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);
1981
1982 if (data.is<Uncompressed<Unit, SourceRetrievable::Yes>>()) {
1983 data = SourceType(Compressed<Unit, SourceRetrievable::Yes>(
1984 std::move(compressed), uncompressedLength));
1985 } else {
1986 data = SourceType(Compressed<Unit, SourceRetrievable::No>(
1987 std::move(compressed), uncompressedLength));
1988 }
1989 }
1990
1991 template <typename Unit>
performDelayedConvertToCompressedSource()1992 void ScriptSource::performDelayedConvertToCompressedSource() {
1993 // There might not be a conversion to compressed source happening at all.
1994 if (pendingCompressed_.empty()) {
1995 return;
1996 }
1997
1998 CompressedData<Unit>& pending =
1999 pendingCompressed_.ref<CompressedData<Unit>>();
2000
2001 convertToCompressedSource<Unit>(std::move(pending.raw),
2002 pending.uncompressedLength);
2003
2004 pendingCompressed_.destroy();
2005 }
2006
2007 template <typename Unit>
~PinnedUnits()2008 ScriptSource::PinnedUnits<Unit>::~PinnedUnits() {
2009 if (units_) {
2010 MOZ_ASSERT(*stack_ == this);
2011 *stack_ = prev_;
2012 if (!prev_) {
2013 source_->performDelayedConvertToCompressedSource<Unit>();
2014 }
2015 }
2016 }
2017
2018 template <typename Unit>
units(JSContext * cx,UncompressedSourceCache::AutoHoldEntry & holder,size_t begin,size_t len)2019 const Unit* ScriptSource::units(JSContext* cx,
2020 UncompressedSourceCache::AutoHoldEntry& holder,
2021 size_t begin, size_t len) {
2022 MOZ_ASSERT(begin <= length());
2023 MOZ_ASSERT(begin + len <= length());
2024
2025 if (isUncompressed<Unit>()) {
2026 const Unit* units = uncompressedData<Unit>()->units();
2027 if (!units) {
2028 return nullptr;
2029 }
2030 return units + begin;
2031 }
2032
2033 if (data.is<Missing>()) {
2034 MOZ_CRASH("ScriptSource::units() on ScriptSource with missing source");
2035 }
2036
2037 if (data.is<Retrievable<Unit>>()) {
2038 MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source");
2039 }
2040
2041 MOZ_ASSERT(isCompressed<Unit>());
2042
2043 // Determine first/last chunks, the offset (in bytes) into the first chunk
2044 // of the requested units, and the number of bytes in the last chunk.
2045 //
2046 // Note that first and last chunk sizes are miscomputed and *must not be
2047 // used* when the first chunk is the last chunk.
2048 size_t firstChunk, firstChunkOffset, firstChunkSize;
2049 size_t lastChunk, lastChunkSize;
2050 Compressor::rangeToChunkAndOffset(
2051 begin * sizeof(Unit), (begin + len) * sizeof(Unit), &firstChunk,
2052 &firstChunkOffset, &firstChunkSize, &lastChunk, &lastChunkSize);
2053 MOZ_ASSERT(firstChunk <= lastChunk);
2054 MOZ_ASSERT(firstChunkOffset % sizeof(Unit) == 0);
2055 MOZ_ASSERT(firstChunkSize % sizeof(Unit) == 0);
2056
2057 size_t firstUnit = firstChunkOffset / sizeof(Unit);
2058
2059 // Directly return units within a single chunk. UncompressedSourceCache
2060 // and |holder| will hold the units alive past function return.
2061 if (firstChunk == lastChunk) {
2062 const Unit* units = chunkUnits<Unit>(cx, holder, firstChunk);
2063 if (!units) {
2064 return nullptr;
2065 }
2066
2067 return units + firstUnit;
2068 }
2069
2070 // Otherwise the units span multiple chunks. Copy successive chunks'
2071 // decompressed units into freshly-allocated memory to return.
2072 EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(len));
2073 if (!decompressed) {
2074 JS_ReportOutOfMemory(cx);
2075 return nullptr;
2076 }
2077
2078 Unit* cursor;
2079
2080 {
2081 // |AutoHoldEntry| is single-shot, and a holder successfully filled in
2082 // by |chunkUnits| must be destroyed before another can be used. Thus
2083 // we can't use |holder| with |chunkUnits| when |chunkUnits| is used
2084 // with multiple chunks, and we must use and destroy distinct, fresh
2085 // holders for each chunk.
2086 UncompressedSourceCache::AutoHoldEntry firstHolder;
2087 const Unit* units = chunkUnits<Unit>(cx, firstHolder, firstChunk);
2088 if (!units) {
2089 return nullptr;
2090 }
2091
2092 cursor = std::copy_n(units + firstUnit, firstChunkSize / sizeof(Unit),
2093 decompressed.get());
2094 }
2095
2096 for (size_t i = firstChunk + 1; i < lastChunk; i++) {
2097 UncompressedSourceCache::AutoHoldEntry chunkHolder;
2098 const Unit* units = chunkUnits<Unit>(cx, chunkHolder, i);
2099 if (!units) {
2100 return nullptr;
2101 }
2102
2103 cursor = std::copy_n(units, Compressor::CHUNK_SIZE / sizeof(Unit), cursor);
2104 }
2105
2106 {
2107 UncompressedSourceCache::AutoHoldEntry lastHolder;
2108 const Unit* units = chunkUnits<Unit>(cx, lastHolder, lastChunk);
2109 if (!units) {
2110 return nullptr;
2111 }
2112
2113 cursor = std::copy_n(units, lastChunkSize / sizeof(Unit), cursor);
2114 }
2115
2116 MOZ_ASSERT(PointerRangeSize(decompressed.get(), cursor) == len);
2117
2118 // Transfer ownership to |holder|.
2119 const Unit* ret = decompressed.get();
2120 holder.holdUnits(std::move(decompressed));
2121 return ret;
2122 }
2123
2124 template <typename Unit>
PinnedUnits(JSContext * cx,ScriptSource * source,UncompressedSourceCache::AutoHoldEntry & holder,size_t begin,size_t len)2125 ScriptSource::PinnedUnits<Unit>::PinnedUnits(
2126 JSContext* cx, ScriptSource* source,
2127 UncompressedSourceCache::AutoHoldEntry& holder, size_t begin, size_t len)
2128 : PinnedUnitsBase(source) {
2129 MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type");
2130
2131 units_ = source->units<Unit>(cx, holder, begin, len);
2132 if (units_) {
2133 stack_ = &source->pinnedUnitsStack_;
2134 prev_ = *stack_;
2135 *stack_ = this;
2136 }
2137 }
2138
2139 template class ScriptSource::PinnedUnits<Utf8Unit>;
2140 template class ScriptSource::PinnedUnits<char16_t>;
2141
substring(JSContext * cx,size_t start,size_t stop)2142 JSLinearString* ScriptSource::substring(JSContext* cx, size_t start,
2143 size_t stop) {
2144 MOZ_ASSERT(start <= stop);
2145
2146 size_t len = stop - start;
2147 if (!len) {
2148 return cx->emptyString();
2149 }
2150 UncompressedSourceCache::AutoHoldEntry holder;
2151
2152 // UTF-8 source text.
2153 if (hasSourceType<Utf8Unit>()) {
2154 PinnedUnits<Utf8Unit> units(cx, this, holder, start, len);
2155 if (!units.asChars()) {
2156 return nullptr;
2157 }
2158
2159 const char* str = units.asChars();
2160 return NewStringCopyUTF8N<CanGC>(cx, JS::UTF8Chars(str, len));
2161 }
2162
2163 // UTF-16 source text.
2164 PinnedUnits<char16_t> units(cx, this, holder, start, len);
2165 if (!units.asChars()) {
2166 return nullptr;
2167 }
2168
2169 return NewStringCopyN<CanGC>(cx, units.asChars(), len);
2170 }
2171
substringDontDeflate(JSContext * cx,size_t start,size_t stop)2172 JSLinearString* ScriptSource::substringDontDeflate(JSContext* cx, size_t start,
2173 size_t stop) {
2174 MOZ_ASSERT(start <= stop);
2175
2176 size_t len = stop - start;
2177 if (!len) {
2178 return cx->emptyString();
2179 }
2180 UncompressedSourceCache::AutoHoldEntry holder;
2181
2182 // UTF-8 source text.
2183 if (hasSourceType<Utf8Unit>()) {
2184 PinnedUnits<Utf8Unit> units(cx, this, holder, start, len);
2185 if (!units.asChars()) {
2186 return nullptr;
2187 }
2188
2189 const char* str = units.asChars();
2190
2191 // There doesn't appear to be a non-deflating UTF-8 string creation
2192 // function -- but then again, it's not entirely clear how current
2193 // callers benefit from non-deflation.
2194 return NewStringCopyUTF8N<CanGC>(cx, JS::UTF8Chars(str, len));
2195 }
2196
2197 // UTF-16 source text.
2198 PinnedUnits<char16_t> units(cx, this, holder, start, len);
2199 if (!units.asChars()) {
2200 return nullptr;
2201 }
2202
2203 return NewStringCopyNDontDeflate<CanGC>(cx, units.asChars(), len);
2204 }
2205
appendSubstring(JSContext * cx,StringBuffer & buf,size_t start,size_t stop)2206 bool ScriptSource::appendSubstring(JSContext* cx, StringBuffer& buf,
2207 size_t start, size_t stop) {
2208 MOZ_ASSERT(start <= stop);
2209
2210 size_t len = stop - start;
2211 UncompressedSourceCache::AutoHoldEntry holder;
2212
2213 if (hasSourceType<Utf8Unit>()) {
2214 PinnedUnits<Utf8Unit> pinned(cx, this, holder, start, len);
2215 if (!pinned.get()) {
2216 return false;
2217 }
2218 if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
2219 return false;
2220 }
2221
2222 const Utf8Unit* units = pinned.get();
2223 return buf.append(units, len);
2224 } else {
2225 PinnedUnits<char16_t> pinned(cx, this, holder, start, len);
2226 if (!pinned.get()) {
2227 return false;
2228 }
2229 if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) {
2230 return false;
2231 }
2232
2233 const char16_t* units = pinned.get();
2234 return buf.append(units, len);
2235 }
2236 }
2237
functionBodyString(JSContext * cx)2238 JSLinearString* ScriptSource::functionBodyString(JSContext* cx) {
2239 MOZ_ASSERT(isFunctionBody());
2240
2241 size_t start =
2242 parameterListEnd_ + (sizeof(FunctionConstructorMedialSigils) - 1);
2243 size_t stop = length() - (sizeof(FunctionConstructorFinalBrace) - 1);
2244 return substring(cx, start, stop);
2245 }
2246
2247 template <typename Unit>
setUncompressedSourceHelper(JSContext * cx,EntryUnits<Unit> && source,size_t length,SourceRetrievable retrievable)2248 [[nodiscard]] bool ScriptSource::setUncompressedSourceHelper(
2249 JSContext* cx, EntryUnits<Unit>&& source, size_t length,
2250 SourceRetrievable retrievable) {
2251 auto& cache = cx->runtime()->sharedImmutableStrings();
2252
2253 auto uniqueChars = SourceTypeTraits<Unit>::toCacheable(std::move(source));
2254 auto deduped = cache.getOrCreate(std::move(uniqueChars), length);
2255 if (!deduped) {
2256 ReportOutOfMemory(cx);
2257 return false;
2258 }
2259
2260 if (retrievable == SourceRetrievable::Yes) {
2261 data = SourceType(
2262 Uncompressed<Unit, SourceRetrievable::Yes>(std::move(deduped)));
2263 } else {
2264 data = SourceType(
2265 Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped)));
2266 }
2267 return true;
2268 }
2269
2270 template <typename Unit>
setRetrievedSource(JSContext * cx,EntryUnits<Unit> && source,size_t length)2271 [[nodiscard]] bool ScriptSource::setRetrievedSource(JSContext* cx,
2272 EntryUnits<Unit>&& source,
2273 size_t length) {
2274 MOZ_ASSERT(data.is<Retrievable<Unit>>(),
2275 "retrieved source can only overwrite the corresponding "
2276 "retrievable source");
2277 return setUncompressedSourceHelper(cx, std::move(source), length,
2278 SourceRetrievable::Yes);
2279 }
2280
IsOffThreadSourceCompressionEnabled()2281 bool js::IsOffThreadSourceCompressionEnabled() {
2282 // If we don't have concurrent execution compression will contend with
2283 // main-thread execution, in which case we disable. Similarly we don't want to
2284 // block the thread pool if it is too small.
2285 return GetHelperThreadCPUCount() > 1 && GetHelperThreadCount() > 1 &&
2286 CanUseExtraThreads();
2287 }
2288
tryCompressOffThread(JSContext * cx)2289 bool ScriptSource::tryCompressOffThread(JSContext* cx) {
2290 // Beware: |js::SynchronouslyCompressSource| assumes that this function is
2291 // only called once, just after a script has been compiled, and it's never
2292 // called at some random time after that. If multiple calls of this can ever
2293 // occur, that function may require changes.
2294
2295 // The SourceCompressionTask needs to record the major GC number for
2296 // scheduling. This cannot be accessed off-thread and must be handle in
2297 // ParseTask::finish instead.
2298 MOZ_ASSERT(!cx->isHelperThreadContext());
2299 MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
2300
2301 // If source compression was already attempted, do not queue a new task.
2302 if (hadCompressionTask_) {
2303 return true;
2304 }
2305
2306 if (!hasUncompressedSource()) {
2307 // This excludes compressed, missing, and retrievable source.
2308 return true;
2309 }
2310
2311 // There are several cases where source compression is not a good idea:
2312 // - If the script is tiny, then compression will save little or no space.
2313 // - If there is only one core, then compression will contend with JS
2314 // execution (which hurts benchmarketing).
2315 //
2316 // Otherwise, enqueue a compression task to be processed when a major
2317 // GC is requested.
2318
2319 if (length() < ScriptSource::MinimumCompressibleLength ||
2320 !IsOffThreadSourceCompressionEnabled()) {
2321 return true;
2322 }
2323
2324 // Heap allocate the task. It will be freed upon compression
2325 // completing in AttachFinishedCompressedSources.
2326 auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), this);
2327 if (!task) {
2328 ReportOutOfMemory(cx);
2329 return false;
2330 }
2331 return EnqueueOffThreadCompression(cx, std::move(task));
2332 }
2333
2334 template <typename Unit>
triggerConvertToCompressedSource(SharedImmutableString compressed,size_t uncompressedLength)2335 void ScriptSource::triggerConvertToCompressedSource(
2336 SharedImmutableString compressed, size_t uncompressedLength) {
2337 MOZ_ASSERT(isUncompressed<Unit>(),
2338 "should only be triggering compressed source installation to "
2339 "overwrite identically-encoded uncompressed source");
2340 MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);
2341
2342 // If units aren't pinned -- and they probably won't be, we'd have to have a
2343 // GC in the small window of time where a |PinnedUnits| was live -- then we
2344 // can immediately convert.
2345 if (MOZ_LIKELY(!pinnedUnitsStack_)) {
2346 convertToCompressedSource<Unit>(std::move(compressed), uncompressedLength);
2347 return;
2348 }
2349
2350 // Otherwise, set aside the compressed-data info. The conversion is performed
2351 // when the last |PinnedUnits| dies.
2352 MOZ_ASSERT(pendingCompressed_.empty(),
2353 "shouldn't be multiple conversions happening");
2354 pendingCompressed_.construct<CompressedData<Unit>>(std::move(compressed),
2355 uncompressedLength);
2356 }
2357
2358 template <typename Unit>
initializeWithUnretrievableCompressedSource(JSContext * cx,UniqueChars && compressed,size_t rawLength,size_t sourceLength)2359 [[nodiscard]] bool ScriptSource::initializeWithUnretrievableCompressedSource(
2360 JSContext* cx, UniqueChars&& compressed, size_t rawLength,
2361 size_t sourceLength) {
2362 MOZ_ASSERT(data.is<Missing>(), "shouldn't be double-initializing");
2363 MOZ_ASSERT(compressed != nullptr);
2364
2365 auto& cache = cx->runtime()->sharedImmutableStrings();
2366 auto deduped = cache.getOrCreate(std::move(compressed), rawLength);
2367 if (!deduped) {
2368 ReportOutOfMemory(cx);
2369 return false;
2370 }
2371
2372 MOZ_ASSERT(pinnedUnitsStack_ == nullptr,
2373 "shouldn't be initializing a ScriptSource while its characters "
2374 "are pinned -- that only makes sense with a ScriptSource actively "
2375 "being inspected");
2376
2377 data = SourceType(Compressed<Unit, SourceRetrievable::No>(std::move(deduped),
2378 sourceLength));
2379
2380 return true;
2381 }
2382
2383 template <typename Unit>
assignSource(JSContext * cx,const ReadOnlyCompileOptions & options,SourceText<Unit> & srcBuf)2384 bool ScriptSource::assignSource(JSContext* cx,
2385 const ReadOnlyCompileOptions& options,
2386 SourceText<Unit>& srcBuf) {
2387 MOZ_ASSERT(data.is<Missing>(),
2388 "source assignment should only occur on fresh ScriptSources");
2389
2390 if (options.discardSource) {
2391 return true;
2392 }
2393
2394 if (options.sourceIsLazy) {
2395 data = SourceType(Retrievable<Unit>());
2396 return true;
2397 }
2398
2399 JSRuntime* runtime = cx->runtime();
2400 auto& cache = runtime->sharedImmutableStrings();
2401 auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&srcBuf]() {
2402 using CharT = typename SourceTypeTraits<Unit>::CharT;
2403 return srcBuf.ownsUnits()
2404 ? UniquePtr<CharT[], JS::FreePolicy>(srcBuf.takeChars())
2405 : DuplicateString(srcBuf.get(), srcBuf.length());
2406 });
2407 if (!deduped) {
2408 ReportOutOfMemory(cx);
2409 return false;
2410 }
2411
2412 data =
2413 SourceType(Uncompressed<Unit, SourceRetrievable::No>(std::move(deduped)));
2414 return true;
2415 }
2416
2417 template bool ScriptSource::assignSource(JSContext* cx,
2418 const ReadOnlyCompileOptions& options,
2419 SourceText<char16_t>& srcBuf);
2420 template bool ScriptSource::assignSource(JSContext* cx,
2421 const ReadOnlyCompileOptions& options,
2422 SourceText<Utf8Unit>& srcBuf);
2423
finalizeGCData()2424 void ScriptSource::finalizeGCData() {
2425 // This should be kept in sync with ScriptSource::trace above.
2426
2427 // When the canonical ScriptSourceObject's finalizer runs, this
2428 // ScriptSource can no longer be accessed from the main
2429 // thread. However, an offthread source compression task may still
2430 // hold a reference. We must clean up any GC pointers owned by this
2431 // ScriptSource now, because trying to run those prebarriers
2432 // offthread later will fail.
2433 MOZ_ASSERT(TlsContext.get() && TlsContext.get()->isMainThreadContext());
2434
2435 if (xdrEncoder_) {
2436 xdrEncoder_.reset();
2437 }
2438 }
2439
~ScriptSource()2440 ScriptSource::~ScriptSource() {
2441 MOZ_ASSERT(refs == 0);
2442
2443 // GC pointers must have been cleared earlier, because this destructor could
2444 // be called off-thread by SweepCompressionTasks. See above.
2445 MOZ_ASSERT(!xdrEncoder_);
2446 }
2447
reallocUniquePtr(UniqueChars & unique,size_t size)2448 [[nodiscard]] static bool reallocUniquePtr(UniqueChars& unique, size_t size) {
2449 auto newPtr = static_cast<char*>(js_realloc(unique.get(), size));
2450 if (!newPtr) {
2451 return false;
2452 }
2453
2454 // Since the realloc succeeded, unique is now holding a freed pointer.
2455 (void)unique.release();
2456 unique.reset(newPtr);
2457 return true;
2458 }
2459
2460 template <typename Unit>
workEncodingSpecific()2461 void SourceCompressionTask::workEncodingSpecific() {
2462 MOZ_ASSERT(source_->isUncompressed<Unit>());
2463
2464 // Try to keep the maximum memory usage down by only allocating half the
2465 // size of the string, first.
2466 size_t inputBytes = source_->length() * sizeof(Unit);
2467 size_t firstSize = inputBytes / 2;
2468 UniqueChars compressed(js_pod_malloc<char>(firstSize));
2469 if (!compressed) {
2470 return;
2471 }
2472
2473 const Unit* chars = source_->uncompressedData<Unit>()->units();
2474 Compressor comp(reinterpret_cast<const unsigned char*>(chars), inputBytes);
2475 if (!comp.init()) {
2476 return;
2477 }
2478
2479 comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), firstSize);
2480 bool cont = true;
2481 bool reallocated = false;
2482 while (cont) {
2483 if (shouldCancel()) {
2484 return;
2485 }
2486
2487 switch (comp.compressMore()) {
2488 case Compressor::CONTINUE:
2489 break;
2490 case Compressor::MOREOUTPUT: {
2491 if (reallocated) {
2492 // The compressed string is longer than the original string.
2493 return;
2494 }
2495
2496 // The compressed output is greater than half the size of the
2497 // original string. Reallocate to the full size.
2498 if (!reallocUniquePtr(compressed, inputBytes)) {
2499 return;
2500 }
2501
2502 comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()),
2503 inputBytes);
2504 reallocated = true;
2505 break;
2506 }
2507 case Compressor::DONE:
2508 cont = false;
2509 break;
2510 case Compressor::OOM:
2511 return;
2512 }
2513 }
2514
2515 size_t totalBytes = comp.totalBytesNeeded();
2516
2517 // Shrink the buffer to the size of the compressed data.
2518 if (!reallocUniquePtr(compressed, totalBytes)) {
2519 return;
2520 }
2521
2522 comp.finish(compressed.get(), totalBytes);
2523
2524 if (shouldCancel()) {
2525 return;
2526 }
2527
2528 auto& strings = runtime_->sharedImmutableStrings();
2529 resultString_ = strings.getOrCreate(std::move(compressed), totalBytes);
2530 }
2531
2532 struct SourceCompressionTask::PerformTaskWork {
2533 SourceCompressionTask* const task_;
2534
PerformTaskWorkSourceCompressionTask::PerformTaskWork2535 explicit PerformTaskWork(SourceCompressionTask* task) : task_(task) {}
2536
2537 template <typename Unit, SourceRetrievable CanRetrieve>
operator ()SourceCompressionTask::PerformTaskWork2538 void operator()(const ScriptSource::Uncompressed<Unit, CanRetrieve>&) {
2539 task_->workEncodingSpecific<Unit>();
2540 }
2541
2542 template <typename T>
operator ()SourceCompressionTask::PerformTaskWork2543 void operator()(const T&) {
2544 MOZ_CRASH(
2545 "why are we compressing missing, missing-but-retrievable, "
2546 "or already-compressed source?");
2547 }
2548 };
2549
performTaskWork(SourceCompressionTask * task)2550 void ScriptSource::performTaskWork(SourceCompressionTask* task) {
2551 MOZ_ASSERT(hasUncompressedSource());
2552 data.match(SourceCompressionTask::PerformTaskWork(task));
2553 }
2554
runTask()2555 void SourceCompressionTask::runTask() {
2556 if (shouldCancel()) {
2557 return;
2558 }
2559
2560 TraceLoggerThread* logger = TraceLoggerForCurrentThread();
2561 AutoTraceLog logCompile(logger, TraceLogger_CompressSource);
2562
2563 MOZ_ASSERT(source_->hasUncompressedSource());
2564
2565 source_->performTaskWork(this);
2566 }
2567
runHelperThreadTask(AutoLockHelperThreadState & locked)2568 void SourceCompressionTask::runHelperThreadTask(
2569 AutoLockHelperThreadState& locked) {
2570 {
2571 AutoUnlockHelperThreadState unlock(locked);
2572 this->runTask();
2573 }
2574
2575 {
2576 AutoEnterOOMUnsafeRegion oomUnsafe;
2577 if (!HelperThreadState().compressionFinishedList(locked).append(this)) {
2578 oomUnsafe.crash("SourceCompressionTask::runHelperThreadTask");
2579 }
2580 }
2581 }
2582
triggerConvertToCompressedSourceFromTask(SharedImmutableString compressed)2583 void ScriptSource::triggerConvertToCompressedSourceFromTask(
2584 SharedImmutableString compressed) {
2585 data.match(TriggerConvertToCompressedSourceFromTask(this, compressed));
2586 }
2587
complete()2588 void SourceCompressionTask::complete() {
2589 if (!shouldCancel() && resultString_) {
2590 source_->triggerConvertToCompressedSourceFromTask(std::move(resultString_));
2591 }
2592 }
2593
SynchronouslyCompressSource(JSContext * cx,JS::Handle<BaseScript * > script)2594 bool js::SynchronouslyCompressSource(JSContext* cx,
2595 JS::Handle<BaseScript*> script) {
2596 MOZ_ASSERT(!cx->isHelperThreadContext(),
2597 "should only sync-compress on the main thread");
2598
2599 // Finish all pending source compressions, including the single compression
2600 // task that may have been created (by |ScriptSource::tryCompressOffThread|)
2601 // just after the script was compiled. Because we have flushed this queue,
2602 // no code below needs to synchronize with an off-thread parse task that
2603 // assumes the immutability of a |ScriptSource|'s data.
2604 //
2605 // This *may* end up compressing |script|'s source. If it does -- we test
2606 // this below -- that takes care of things. But if it doesn't, we will
2607 // synchronously compress ourselves (and as noted above, this won't race
2608 // anything).
2609 RunPendingSourceCompressions(cx->runtime());
2610
2611 ScriptSource* ss = script->scriptSource();
2612 MOZ_ASSERT(!ss->pinnedUnitsStack_,
2613 "can't synchronously compress while source units are in use");
2614
2615 // In principle a previously-triggered compression on a helper thread could
2616 // have already completed. If that happens, there's nothing more to do.
2617 if (ss->hasCompressedSource()) {
2618 return true;
2619 }
2620
2621 MOZ_ASSERT(ss->hasUncompressedSource(),
2622 "shouldn't be compressing uncompressible source");
2623
2624 // Use an explicit scope to delineate the lifetime of |task|, for simplicity.
2625 {
2626 #ifdef DEBUG
2627 uint32_t sourceRefs = ss->refs;
2628 #endif
2629 MOZ_ASSERT(sourceRefs > 0, "at least |script| here should have a ref");
2630
2631 // |SourceCompressionTask::shouldCancel| can periodically result in source
2632 // compression being canceled if we're not careful. Guarantee that two refs
2633 // to |ss| are always live in this function (at least one preexisting and
2634 // one held by the task) so that compression is never canceled.
2635 auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), ss);
2636 if (!task) {
2637 ReportOutOfMemory(cx);
2638 return false;
2639 }
2640
2641 MOZ_ASSERT(ss->refs > sourceRefs, "must have at least two refs now");
2642
2643 // Attempt to compress. This may not succeed if OOM happens, but (because
2644 // it ordinarily happens on a helper thread) no error will ever be set here.
2645 MOZ_ASSERT(!cx->isExceptionPending());
2646 ss->performTaskWork(task.get());
2647 MOZ_ASSERT(!cx->isExceptionPending());
2648
2649 // Convert |ss| from uncompressed to compressed data.
2650 task->complete();
2651
2652 MOZ_ASSERT(!cx->isExceptionPending());
2653 }
2654
2655 // The only way source won't be compressed here is if OOM happened.
2656 return ss->hasCompressedSource();
2657 }
2658
addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,JS::ScriptSourceInfo * info) const2659 void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
2660 JS::ScriptSourceInfo* info) const {
2661 info->misc += mallocSizeOf(this);
2662 info->numScripts++;
2663 }
2664
startIncrementalEncoding(JSContext * cx,const JS::ReadOnlyCompileOptions & options,UniquePtr<frontend::ExtensibleCompilationStencil> && initial)2665 bool ScriptSource::startIncrementalEncoding(
2666 JSContext* cx, const JS::ReadOnlyCompileOptions& options,
2667 UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) {
2668 // Encoding failures are reported by the xdrFinalizeEncoder function.
2669 if (containsAsmJS()) {
2670 return true;
2671 }
2672
2673 // Remove the reference to the source, to avoid the circular reference.
2674 initial->source = nullptr;
2675
2676 xdrEncoder_ = js::MakeUnique<XDRIncrementalStencilEncoder>();
2677 if (!xdrEncoder_) {
2678 ReportOutOfMemory(cx);
2679 return false;
2680 }
2681
2682 AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime);
2683 auto failureCase =
2684 mozilla::MakeScopeExit([&] { xdrEncoder_.reset(nullptr); });
2685
2686 XDRResult res = xdrEncoder_->setInitial(
2687 cx, options,
2688 std::forward<UniquePtr<frontend::ExtensibleCompilationStencil>>(initial));
2689 if (res.isErr()) {
2690 // On encoding failure, let failureCase destroy encoder and return true
2691 // to avoid failing any currently executing script.
2692 return JS::IsTranscodeFailureResult(res.unwrapErr());
2693 }
2694
2695 failureCase.release();
2696 return true;
2697 }
2698
addDelazificationToIncrementalEncoding(JSContext * cx,const frontend::CompilationStencil & stencil)2699 bool ScriptSource::addDelazificationToIncrementalEncoding(
2700 JSContext* cx, const frontend::CompilationStencil& stencil) {
2701 MOZ_ASSERT(hasEncoder());
2702 AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime);
2703 auto failureCase =
2704 mozilla::MakeScopeExit([&] { xdrEncoder_.reset(nullptr); });
2705
2706 XDRResult res = xdrEncoder_->addDelazification(cx, stencil);
2707 if (res.isErr()) {
2708 // On encoding failure, let failureCase destroy encoder and return true
2709 // to avoid failing any currently executing script.
2710 return JS::IsTranscodeFailureResult(res.unwrapErr());
2711 }
2712
2713 failureCase.release();
2714 return true;
2715 }
2716
xdrFinalizeEncoder(JSContext * cx,JS::TranscodeBuffer & buffer)2717 bool ScriptSource::xdrFinalizeEncoder(JSContext* cx,
2718 JS::TranscodeBuffer& buffer) {
2719 if (!hasEncoder()) {
2720 JS_ReportErrorASCII(cx, "XDR encoding failure");
2721 return false;
2722 }
2723
2724 auto cleanup = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(nullptr); });
2725
2726 XDRResult res = xdrEncoder_->linearize(cx, buffer, this);
2727 if (res.isErr()) {
2728 if (JS::IsTranscodeFailureResult(res.unwrapErr())) {
2729 JS_ReportErrorASCII(cx, "XDR encoding failure");
2730 }
2731 return false;
2732 }
2733 return true;
2734 }
2735
2736 template <typename Unit>
initializeUnretrievableUncompressedSource(JSContext * cx,EntryUnits<Unit> && source,size_t length)2737 [[nodiscard]] bool ScriptSource::initializeUnretrievableUncompressedSource(
2738 JSContext* cx, EntryUnits<Unit>&& source, size_t length) {
2739 MOZ_ASSERT(data.is<Missing>(), "must be initializing a fresh ScriptSource");
2740 return setUncompressedSourceHelper(cx, std::move(source), length,
2741 SourceRetrievable::No);
2742 }
2743
2744 template <typename Unit>
2745 struct UnretrievableSourceDecoder {
2746 XDRState<XDR_DECODE>* const xdr_;
2747 ScriptSource* const scriptSource_;
2748 const uint32_t uncompressedLength_;
2749
2750 public:
UnretrievableSourceDecoderUnretrievableSourceDecoder2751 UnretrievableSourceDecoder(XDRState<XDR_DECODE>* xdr,
2752 ScriptSource* scriptSource,
2753 uint32_t uncompressedLength)
2754 : xdr_(xdr),
2755 scriptSource_(scriptSource),
2756 uncompressedLength_(uncompressedLength) {}
2757
decodeUnretrievableSourceDecoder2758 XDRResult decode() {
2759 auto sourceUnits = xdr_->cx()->make_pod_array<Unit>(
2760 std::max<size_t>(uncompressedLength_, 1));
2761 if (!sourceUnits) {
2762 return xdr_->fail(JS::TranscodeResult::Throw);
2763 }
2764
2765 MOZ_TRY(xdr_->codeChars(sourceUnits.get(), uncompressedLength_));
2766
2767 if (!scriptSource_->initializeUnretrievableUncompressedSource(
2768 xdr_->cx(), std::move(sourceUnits), uncompressedLength_)) {
2769 return xdr_->fail(JS::TranscodeResult::Throw);
2770 }
2771
2772 return Ok();
2773 }
2774 };
2775
2776 namespace js {
2777
2778 template <>
xdrUnretrievableUncompressedSource(XDRState<XDR_DECODE> * xdr,uint8_t sourceCharSize,uint32_t uncompressedLength)2779 XDRResult ScriptSource::xdrUnretrievableUncompressedSource<XDR_DECODE>(
2780 XDRState<XDR_DECODE>* xdr, uint8_t sourceCharSize,
2781 uint32_t uncompressedLength) {
2782 MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
2783
2784 if (sourceCharSize == 1) {
2785 UnretrievableSourceDecoder<Utf8Unit> decoder(xdr, this, uncompressedLength);
2786 return decoder.decode();
2787 }
2788
2789 UnretrievableSourceDecoder<char16_t> decoder(xdr, this, uncompressedLength);
2790 return decoder.decode();
2791 }
2792
2793 } // namespace js
2794
2795 template <typename Unit>
2796 struct UnretrievableSourceEncoder {
2797 XDRState<XDR_ENCODE>* const xdr_;
2798 ScriptSource* const source_;
2799 const uint32_t uncompressedLength_;
2800
UnretrievableSourceEncoderUnretrievableSourceEncoder2801 UnretrievableSourceEncoder(XDRState<XDR_ENCODE>* xdr, ScriptSource* source,
2802 uint32_t uncompressedLength)
2803 : xdr_(xdr), source_(source), uncompressedLength_(uncompressedLength) {}
2804
encodeUnretrievableSourceEncoder2805 XDRResult encode() {
2806 Unit* sourceUnits =
2807 const_cast<Unit*>(source_->uncompressedData<Unit>()->units());
2808
2809 return xdr_->codeChars(sourceUnits, uncompressedLength_);
2810 }
2811 };
2812
2813 namespace js {
2814
2815 template <>
xdrUnretrievableUncompressedSource(XDRState<XDR_ENCODE> * xdr,uint8_t sourceCharSize,uint32_t uncompressedLength)2816 XDRResult ScriptSource::xdrUnretrievableUncompressedSource<XDR_ENCODE>(
2817 XDRState<XDR_ENCODE>* xdr, uint8_t sourceCharSize,
2818 uint32_t uncompressedLength) {
2819 MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
2820
2821 if (sourceCharSize == 1) {
2822 UnretrievableSourceEncoder<Utf8Unit> encoder(xdr, this, uncompressedLength);
2823 return encoder.encode();
2824 }
2825
2826 UnretrievableSourceEncoder<char16_t> encoder(xdr, this, uncompressedLength);
2827 return encoder.encode();
2828 }
2829
2830 } // namespace js
2831
2832 template <typename Unit, XDRMode mode>
2833 /* static */
codeUncompressedData(XDRState<mode> * const xdr,ScriptSource * const ss)2834 XDRResult ScriptSource::codeUncompressedData(XDRState<mode>* const xdr,
2835 ScriptSource* const ss) {
2836 static_assert(
2837 std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>,
2838 "should handle UTF-8 and UTF-16");
2839
2840 if (mode == XDR_ENCODE) {
2841 MOZ_ASSERT(ss->isUncompressed<Unit>());
2842 } else {
2843 MOZ_ASSERT(ss->data.is<Missing>());
2844 }
2845
2846 uint32_t uncompressedLength;
2847 if (mode == XDR_ENCODE) {
2848 uncompressedLength = ss->uncompressedData<Unit>()->length();
2849 }
2850 MOZ_TRY(xdr->codeUint32(&uncompressedLength));
2851
2852 return ss->xdrUnretrievableUncompressedSource(xdr, sizeof(Unit),
2853 uncompressedLength);
2854 }
2855
2856 template <typename Unit, XDRMode mode>
2857 /* static */
codeCompressedData(XDRState<mode> * const xdr,ScriptSource * const ss)2858 XDRResult ScriptSource::codeCompressedData(XDRState<mode>* const xdr,
2859 ScriptSource* const ss) {
2860 static_assert(
2861 std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>,
2862 "should handle UTF-8 and UTF-16");
2863
2864 if (mode == XDR_ENCODE) {
2865 MOZ_ASSERT(ss->isCompressed<Unit>());
2866 } else {
2867 MOZ_ASSERT(ss->data.is<Missing>());
2868 }
2869
2870 uint32_t uncompressedLength;
2871 if (mode == XDR_ENCODE) {
2872 uncompressedLength = ss->data.as<Compressed<Unit, SourceRetrievable::No>>()
2873 .uncompressedLength;
2874 }
2875 MOZ_TRY(xdr->codeUint32(&uncompressedLength));
2876
2877 uint32_t compressedLength;
2878 if (mode == XDR_ENCODE) {
2879 compressedLength =
2880 ss->data.as<Compressed<Unit, SourceRetrievable::No>>().raw.length();
2881 }
2882 MOZ_TRY(xdr->codeUint32(&compressedLength));
2883
2884 if (mode == XDR_DECODE) {
2885 // Compressed data is always single-byte chars.
2886 auto bytes = xdr->cx()->template make_pod_array<char>(compressedLength);
2887 if (!bytes) {
2888 return xdr->fail(JS::TranscodeResult::Throw);
2889 }
2890 MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength));
2891
2892 if (!ss->initializeWithUnretrievableCompressedSource<Unit>(
2893 xdr->cx(), std::move(bytes), compressedLength,
2894 uncompressedLength)) {
2895 return xdr->fail(JS::TranscodeResult::Throw);
2896 }
2897 } else {
2898 void* bytes = const_cast<char*>(ss->compressedData<Unit>()->raw.chars());
2899 MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
2900 }
2901
2902 return Ok();
2903 }
2904
2905 template <typename Unit,
2906 template <typename U, SourceRetrievable CanRetrieve> class Data,
2907 XDRMode mode>
2908 /* static */
codeRetrievable(ScriptSource * const ss)2909 void ScriptSource::codeRetrievable(ScriptSource* const ss) {
2910 static_assert(
2911 std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>,
2912 "should handle UTF-8 and UTF-16");
2913
2914 if (mode == XDR_ENCODE) {
2915 MOZ_ASSERT((ss->data.is<Data<Unit, SourceRetrievable::Yes>>()));
2916 } else {
2917 MOZ_ASSERT(ss->data.is<Missing>());
2918 ss->data = SourceType(Retrievable<Unit>());
2919 }
2920 }
2921
2922 template <typename Unit, XDRMode mode>
2923 /* static */
codeRetrievableData(ScriptSource * ss)2924 void ScriptSource::codeRetrievableData(ScriptSource* ss) {
2925 // There's nothing to code for retrievable data. Just be sure to set
2926 // retrievable data when decoding.
2927 if (mode == XDR_ENCODE) {
2928 MOZ_ASSERT(ss->data.is<Retrievable<Unit>>());
2929 } else {
2930 MOZ_ASSERT(ss->data.is<Missing>());
2931 ss->data = SourceType(Retrievable<Unit>());
2932 }
2933 }
2934
2935 template <XDRMode mode>
2936 /* static */
xdrData(XDRState<mode> * const xdr,ScriptSource * const ss)2937 XDRResult ScriptSource::xdrData(XDRState<mode>* const xdr,
2938 ScriptSource* const ss) {
2939 // The order here corresponds to the type order in |ScriptSource::SourceType|
2940 // so number->internal Variant tag is a no-op.
2941 enum class DataType {
2942 CompressedUtf8Retrievable,
2943 UncompressedUtf8Retrievable,
2944 CompressedUtf8NotRetrievable,
2945 UncompressedUtf8NotRetrievable,
2946 CompressedUtf16Retrievable,
2947 UncompressedUtf16Retrievable,
2948 CompressedUtf16NotRetrievable,
2949 UncompressedUtf16NotRetrievable,
2950 RetrievableUtf8,
2951 RetrievableUtf16,
2952 Missing,
2953 };
2954
2955 DataType tag;
2956 {
2957 // This is terrible, but we can't do better. When |mode == XDR_DECODE| we
2958 // don't have a |ScriptSource::data| |Variant| to match -- the entire XDR
2959 // idiom for tagged unions depends on coding a tag-number, then the
2960 // corresponding tagged data. So we must manually define a tag-enum, code
2961 // it, then switch on it (and ignore the |Variant::match| API).
2962 class XDRDataTag {
2963 public:
2964 DataType operator()(const Compressed<Utf8Unit, SourceRetrievable::Yes>&) {
2965 return DataType::CompressedUtf8Retrievable;
2966 }
2967 DataType operator()(
2968 const Uncompressed<Utf8Unit, SourceRetrievable::Yes>&) {
2969 return DataType::UncompressedUtf8Retrievable;
2970 }
2971 DataType operator()(const Compressed<Utf8Unit, SourceRetrievable::No>&) {
2972 return DataType::CompressedUtf8NotRetrievable;
2973 }
2974 DataType operator()(
2975 const Uncompressed<Utf8Unit, SourceRetrievable::No>&) {
2976 return DataType::UncompressedUtf8NotRetrievable;
2977 }
2978 DataType operator()(const Compressed<char16_t, SourceRetrievable::Yes>&) {
2979 return DataType::CompressedUtf16Retrievable;
2980 }
2981 DataType operator()(
2982 const Uncompressed<char16_t, SourceRetrievable::Yes>&) {
2983 return DataType::UncompressedUtf16Retrievable;
2984 }
2985 DataType operator()(const Compressed<char16_t, SourceRetrievable::No>&) {
2986 return DataType::CompressedUtf16NotRetrievable;
2987 }
2988 DataType operator()(
2989 const Uncompressed<char16_t, SourceRetrievable::No>&) {
2990 return DataType::UncompressedUtf16NotRetrievable;
2991 }
2992 DataType operator()(const Retrievable<Utf8Unit>&) {
2993 return DataType::RetrievableUtf8;
2994 }
2995 DataType operator()(const Retrievable<char16_t>&) {
2996 return DataType::RetrievableUtf16;
2997 }
2998 DataType operator()(const Missing&) { return DataType::Missing; }
2999 };
3000
3001 uint8_t type;
3002 if (mode == XDR_ENCODE) {
3003 type = static_cast<uint8_t>(ss->data.match(XDRDataTag()));
3004 }
3005 MOZ_TRY(xdr->codeUint8(&type));
3006
3007 if (type > static_cast<uint8_t>(DataType::Missing)) {
3008 // Fail in debug, but only soft-fail in release, if the type is invalid.
3009 MOZ_ASSERT_UNREACHABLE("bad tag");
3010 return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
3011 }
3012
3013 tag = static_cast<DataType>(type);
3014 }
3015
3016 switch (tag) {
3017 case DataType::CompressedUtf8Retrievable:
3018 ScriptSource::codeRetrievable<Utf8Unit, Compressed, mode>(ss);
3019 return Ok();
3020
3021 case DataType::CompressedUtf8NotRetrievable:
3022 return ScriptSource::codeCompressedData<Utf8Unit>(xdr, ss);
3023
3024 case DataType::UncompressedUtf8Retrievable:
3025 ScriptSource::codeRetrievable<Utf8Unit, Uncompressed, mode>(ss);
3026 return Ok();
3027
3028 case DataType::UncompressedUtf8NotRetrievable:
3029 return ScriptSource::codeUncompressedData<Utf8Unit>(xdr, ss);
3030
3031 case DataType::CompressedUtf16Retrievable:
3032 ScriptSource::codeRetrievable<char16_t, Compressed, mode>(ss);
3033 return Ok();
3034
3035 case DataType::CompressedUtf16NotRetrievable:
3036 return ScriptSource::codeCompressedData<char16_t>(xdr, ss);
3037
3038 case DataType::UncompressedUtf16Retrievable:
3039 ScriptSource::codeRetrievable<char16_t, Uncompressed, mode>(ss);
3040 return Ok();
3041
3042 case DataType::UncompressedUtf16NotRetrievable:
3043 return ScriptSource::codeUncompressedData<char16_t>(xdr, ss);
3044
3045 case DataType::Missing: {
3046 MOZ_ASSERT(ss->data.is<Missing>(),
3047 "ScriptSource::data is initialized as missing, so neither "
3048 "encoding nor decoding has to change anything");
3049
3050 // There's no data to XDR for missing source.
3051 break;
3052 }
3053
3054 case DataType::RetrievableUtf8:
3055 ScriptSource::codeRetrievableData<Utf8Unit, mode>(ss);
3056 return Ok();
3057
3058 case DataType::RetrievableUtf16:
3059 ScriptSource::codeRetrievableData<char16_t, mode>(ss);
3060 return Ok();
3061 }
3062
3063 // The range-check on |type| far above ought ensure the above |switch| is
3064 // exhaustive and all cases will return, but not all compilers understand
3065 // this. Make the Missing case break to here so control obviously never flows
3066 // off the end.
3067 MOZ_ASSERT(tag == DataType::Missing);
3068 return Ok();
3069 }
3070
3071 template <XDRMode mode>
3072 /* static */
XDR(XDRState<mode> * xdr,const ReadOnlyCompileOptions * maybeOptions,RefPtr<ScriptSource> & source)3073 XDRResult ScriptSource::XDR(XDRState<mode>* xdr,
3074 const ReadOnlyCompileOptions* maybeOptions,
3075 RefPtr<ScriptSource>& source) {
3076 JSContext* cx = xdr->cx();
3077
3078 if (mode == XDR_DECODE) {
3079 // Allocate a new ScriptSource and root it with the holder.
3080 source = do_AddRef(cx->new_<ScriptSource>());
3081 if (!source) {
3082 return xdr->fail(JS::TranscodeResult::Throw);
3083 }
3084
3085 // We use this CompileOptions only to initialize the ScriptSourceObject.
3086 // Most CompileOptions fields aren't used by ScriptSourceObject, and those
3087 // that are (element; elementAttributeName) aren't preserved by XDR. So
3088 // this can be simple.
3089 if (!source->initFromOptions(cx, *maybeOptions)) {
3090 return xdr->fail(JS::TranscodeResult::Throw);
3091 }
3092 }
3093
3094 MOZ_TRY(xdrData(xdr, source.get()));
3095
3096 uint8_t haveSourceMap = source->hasSourceMapURL();
3097 MOZ_TRY(xdr->codeUint8(&haveSourceMap));
3098
3099 if (haveSourceMap) {
3100 XDRTranscodeString<char16_t> chars;
3101
3102 if (mode == XDR_ENCODE) {
3103 chars.construct<const char16_t*>(source->sourceMapURL());
3104 }
3105 MOZ_TRY(xdr->codeCharsZ(chars));
3106 if (mode == XDR_DECODE) {
3107 if (!source->setSourceMapURL(
3108 cx, std::move(chars.ref<UniqueTwoByteChars>()))) {
3109 return xdr->fail(JS::TranscodeResult::Throw);
3110 }
3111 }
3112 }
3113
3114 uint8_t haveDisplayURL = source->hasDisplayURL();
3115 MOZ_TRY(xdr->codeUint8(&haveDisplayURL));
3116
3117 if (haveDisplayURL) {
3118 XDRTranscodeString<char16_t> chars;
3119
3120 if (mode == XDR_ENCODE) {
3121 chars.construct<const char16_t*>(source->displayURL());
3122 }
3123 MOZ_TRY(xdr->codeCharsZ(chars));
3124 if (mode == XDR_DECODE) {
3125 if (!source->setDisplayURL(cx,
3126 std::move(chars.ref<UniqueTwoByteChars>()))) {
3127 return xdr->fail(JS::TranscodeResult::Throw);
3128 }
3129 }
3130 }
3131
3132 uint8_t haveFilename = !!source->filename_;
3133 MOZ_TRY(xdr->codeUint8(&haveFilename));
3134
3135 if (haveFilename) {
3136 XDRTranscodeString<char> chars;
3137
3138 if (mode == XDR_ENCODE) {
3139 chars.construct<const char*>(source->filename());
3140 }
3141 MOZ_TRY(xdr->codeCharsZ(chars));
3142 if (mode == XDR_DECODE) {
3143 if (!source->filename()) {
3144 if (!source->setFilename(cx, std::move(chars.ref<UniqueChars>()))) {
3145 return xdr->fail(JS::TranscodeResult::Throw);
3146 }
3147 }
3148 MOZ_ASSERT(source->filename());
3149 }
3150 }
3151
3152 return Ok();
3153 }
3154
3155 template /* static */
3156 XDRResult
3157 ScriptSource::XDR(XDRState<XDR_ENCODE>* xdr,
3158 const ReadOnlyCompileOptions* maybeOptions,
3159 RefPtr<ScriptSource>& holder);
3160 template /* static */
3161 XDRResult
3162 ScriptSource::XDR(XDRState<XDR_DECODE>* xdr,
3163 const ReadOnlyCompileOptions* maybeOptions,
3164 RefPtr<ScriptSource>& holder);
3165
3166 // Format and return a cx->pod_malloc'ed URL for a generated script like:
3167 // {filename} line {lineno} > {introducer}
3168 // For example:
3169 // foo.js line 7 > eval
3170 // indicating code compiled by the call to 'eval' on line 7 of foo.js.
FormatIntroducedFilename(JSContext * cx,const char * filename,unsigned lineno,const char * introducer)3171 UniqueChars js::FormatIntroducedFilename(JSContext* cx, const char* filename,
3172 unsigned lineno,
3173 const char* introducer) {
3174 // Compute the length of the string in advance, so we can allocate a
3175 // buffer of the right size on the first shot.
3176 //
3177 // (JS_smprintf would be perfect, as that allocates the result
3178 // dynamically as it formats the string, but it won't allocate from cx,
3179 // and wants us to use a special free function.)
3180 char linenoBuf[15];
3181 size_t filenameLen = strlen(filename);
3182 size_t linenoLen = SprintfLiteral(linenoBuf, "%u", lineno);
3183 size_t introducerLen = strlen(introducer);
3184 size_t len = filenameLen + 6 /* == strlen(" line ") */ + linenoLen +
3185 3 /* == strlen(" > ") */ + introducerLen + 1 /* \0 */;
3186 UniqueChars formatted(cx->pod_malloc<char>(len));
3187 if (!formatted) {
3188 return nullptr;
3189 }
3190
3191 mozilla::DebugOnly<size_t> checkLen = snprintf(
3192 formatted.get(), len, "%s line %s > %s", filename, linenoBuf, introducer);
3193 MOZ_ASSERT(checkLen == len - 1);
3194
3195 return formatted;
3196 }
3197
initFromOptions(JSContext * cx,const ReadOnlyCompileOptions & options)3198 bool ScriptSource::initFromOptions(JSContext* cx,
3199 const ReadOnlyCompileOptions& options) {
3200 MOZ_ASSERT(!filename_);
3201 MOZ_ASSERT(!introducerFilename_);
3202
3203 mutedErrors_ = options.mutedErrors();
3204
3205 startLine_ = options.lineno;
3206 introductionType_ = options.introductionType;
3207 setIntroductionOffset(options.introductionOffset);
3208 // The parameterListEnd_ is initialized later by setParameterListEnd, before
3209 // we expose any scripts that use this ScriptSource to the debugger.
3210
3211 if (options.hasIntroductionInfo) {
3212 MOZ_ASSERT(options.introductionType != nullptr);
3213 const char* filename =
3214 options.filename() ? options.filename() : "<unknown>";
3215 UniqueChars formatted = FormatIntroducedFilename(
3216 cx, filename, options.introductionLineno, options.introductionType);
3217 if (!formatted) {
3218 return false;
3219 }
3220 if (!setFilename(cx, std::move(formatted))) {
3221 return false;
3222 }
3223 } else if (options.filename()) {
3224 if (!setFilename(cx, options.filename())) {
3225 return false;
3226 }
3227 }
3228
3229 if (options.introducerFilename()) {
3230 if (!setIntroducerFilename(cx, options.introducerFilename())) {
3231 return false;
3232 }
3233 }
3234
3235 return true;
3236 }
3237
3238 // Use the SharedImmutableString map to deduplicate input string. The input
3239 // string must be null-terminated.
3240 template <typename SharedT, typename CharT>
GetOrCreateStringZ(JSContext * cx,UniquePtr<CharT[],JS::FreePolicy> && str)3241 static SharedT GetOrCreateStringZ(JSContext* cx,
3242 UniquePtr<CharT[], JS::FreePolicy>&& str) {
3243 JSRuntime* rt = cx->runtime();
3244 size_t lengthWithNull = std::char_traits<CharT>::length(str.get()) + 1;
3245 auto res =
3246 rt->sharedImmutableStrings().getOrCreate(std::move(str), lengthWithNull);
3247 if (!res) {
3248 ReportOutOfMemory(cx);
3249 }
3250 return res;
3251 }
3252
getOrCreateStringZ(JSContext * cx,UniqueChars && str)3253 SharedImmutableString ScriptSource::getOrCreateStringZ(JSContext* cx,
3254 UniqueChars&& str) {
3255 return GetOrCreateStringZ<SharedImmutableString>(cx, std::move(str));
3256 }
3257
getOrCreateStringZ(JSContext * cx,UniqueTwoByteChars && str)3258 SharedImmutableTwoByteString ScriptSource::getOrCreateStringZ(
3259 JSContext* cx, UniqueTwoByteChars&& str) {
3260 return GetOrCreateStringZ<SharedImmutableTwoByteString>(cx, std::move(str));
3261 }
3262
setFilename(JSContext * cx,const char * filename)3263 bool ScriptSource::setFilename(JSContext* cx, const char* filename) {
3264 UniqueChars owned = DuplicateString(cx, filename);
3265 if (!owned) {
3266 return false;
3267 }
3268 return setFilename(cx, std::move(owned));
3269 }
3270
setFilename(JSContext * cx,UniqueChars && filename)3271 bool ScriptSource::setFilename(JSContext* cx, UniqueChars&& filename) {
3272 MOZ_ASSERT(!filename_);
3273 filename_ = getOrCreateStringZ(cx, std::move(filename));
3274 return bool(filename_);
3275 }
3276
setIntroducerFilename(JSContext * cx,const char * filename)3277 bool ScriptSource::setIntroducerFilename(JSContext* cx, const char* filename) {
3278 UniqueChars owned = DuplicateString(cx, filename);
3279 if (!owned) {
3280 return false;
3281 }
3282 return setIntroducerFilename(cx, std::move(owned));
3283 }
3284
setIntroducerFilename(JSContext * cx,UniqueChars && filename)3285 bool ScriptSource::setIntroducerFilename(JSContext* cx,
3286 UniqueChars&& filename) {
3287 MOZ_ASSERT(!introducerFilename_);
3288 introducerFilename_ = getOrCreateStringZ(cx, std::move(filename));
3289 return bool(introducerFilename_);
3290 }
3291
setDisplayURL(JSContext * cx,const char16_t * url)3292 bool ScriptSource::setDisplayURL(JSContext* cx, const char16_t* url) {
3293 UniqueTwoByteChars owned = DuplicateString(cx, url);
3294 if (!owned) {
3295 return false;
3296 }
3297 return setDisplayURL(cx, std::move(owned));
3298 }
3299
setDisplayURL(JSContext * cx,UniqueTwoByteChars && url)3300 bool ScriptSource::setDisplayURL(JSContext* cx, UniqueTwoByteChars&& url) {
3301 if (hasDisplayURL()) {
3302 // FIXME: filename() should be UTF-8 (bug 987069).
3303 if (!cx->isHelperThreadContext() &&
3304 !WarnNumberLatin1(cx, JSMSG_ALREADY_HAS_PRAGMA, filename(),
3305 "//# sourceURL")) {
3306 return false;
3307 }
3308 }
3309
3310 MOZ_ASSERT(url);
3311 if (url[0] == '\0') {
3312 return true;
3313 }
3314
3315 displayURL_ = getOrCreateStringZ(cx, std::move(url));
3316 return bool(displayURL_);
3317 }
3318
setSourceMapURL(JSContext * cx,const char16_t * url)3319 bool ScriptSource::setSourceMapURL(JSContext* cx, const char16_t* url) {
3320 UniqueTwoByteChars owned = DuplicateString(cx, url);
3321 if (!owned) {
3322 return false;
3323 }
3324 return setSourceMapURL(cx, std::move(owned));
3325 }
3326
setSourceMapURL(JSContext * cx,UniqueTwoByteChars && url)3327 bool ScriptSource::setSourceMapURL(JSContext* cx, UniqueTwoByteChars&& url) {
3328 MOZ_ASSERT(url);
3329 if (url[0] == '\0') {
3330 return true;
3331 }
3332
3333 sourceMapURL_ = getOrCreateStringZ(cx, std::move(url));
3334 return bool(sourceMapURL_);
3335 }
3336
3337 /* static */ mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent>
3338 ScriptSource::idCount_;
3339
3340 /*
3341 * [SMDOC] JSScript data layout (immutable)
3342 *
3343 * Script data that shareable across processes. There are no pointers (GC or
3344 * otherwise) and the data is relocatable.
3345 *
3346 * Array elements Pointed to by Length
3347 * -------------- ------------- ------
3348 * jsbytecode code() codeLength()
3349 * jsscrnote notes() noteLength()
3350 * uint32_t resumeOffsets()
3351 * ScopeNote scopeNotes()
3352 * TryNote tryNotes()
3353 */
3354
sizeFor(uint32_t codeLength,uint32_t noteLength,uint32_t numResumeOffsets,uint32_t numScopeNotes,uint32_t numTryNotes)3355 /* static */ CheckedInt<uint32_t> ImmutableScriptData::sizeFor(
3356 uint32_t codeLength, uint32_t noteLength, uint32_t numResumeOffsets,
3357 uint32_t numScopeNotes, uint32_t numTryNotes) {
3358 // Take a count of which optional arrays will be used and need offset info.
3359 unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) +
3360 unsigned(numScopeNotes > 0) +
3361 unsigned(numTryNotes > 0);
3362
3363 // Compute size including trailing arrays.
3364 CheckedInt<uint32_t> size = sizeof(ImmutableScriptData);
3365 size += sizeof(Flags);
3366 size += CheckedInt<uint32_t>(codeLength) * sizeof(jsbytecode);
3367 size += CheckedInt<uint32_t>(noteLength) * sizeof(SrcNote);
3368 size += CheckedInt<uint32_t>(numOptionalArrays) * sizeof(Offset);
3369 size += CheckedInt<uint32_t>(numResumeOffsets) * sizeof(uint32_t);
3370 size += CheckedInt<uint32_t>(numScopeNotes) * sizeof(ScopeNote);
3371 size += CheckedInt<uint32_t>(numTryNotes) * sizeof(TryNote);
3372
3373 return size;
3374 }
3375
new_(JSContext * cx,uint32_t codeLength,uint32_t noteLength,uint32_t numResumeOffsets,uint32_t numScopeNotes,uint32_t numTryNotes)3376 js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_(
3377 JSContext* cx, uint32_t codeLength, uint32_t noteLength,
3378 uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes) {
3379 auto size = sizeFor(codeLength, noteLength, numResumeOffsets, numScopeNotes,
3380 numTryNotes);
3381 if (!size.isValid()) {
3382 ReportAllocationOverflow(cx);
3383 return nullptr;
3384 }
3385
3386 // Allocate contiguous raw buffer.
3387 void* raw = cx->pod_malloc<uint8_t>(size.value());
3388 MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0);
3389 if (!raw) {
3390 return nullptr;
3391 }
3392
3393 // Constuct the ImmutableScriptData. Trailing arrays are uninitialized but
3394 // GCPtrs are put into a safe state.
3395 UniquePtr<ImmutableScriptData> result(new (raw) ImmutableScriptData(
3396 codeLength, noteLength, numResumeOffsets, numScopeNotes, numTryNotes));
3397 if (!result) {
3398 return nullptr;
3399 }
3400
3401 // Sanity check
3402 MOZ_ASSERT(result->endOffset() == size.value());
3403
3404 return result;
3405 }
3406
new_(JSContext * cx,uint32_t totalSize)3407 js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_(
3408 JSContext* cx, uint32_t totalSize) {
3409 void* raw = cx->pod_malloc<uint8_t>(totalSize);
3410 MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0);
3411 UniquePtr<ImmutableScriptData> result(
3412 reinterpret_cast<ImmutableScriptData*>(raw));
3413 return result;
3414 }
3415
computedSize()3416 uint32_t js::ImmutableScriptData::computedSize() {
3417 auto size = sizeFor(codeLength(), noteLength(), resumeOffsets().size(),
3418 scopeNotes().size(), tryNotes().size());
3419 MOZ_ASSERT(size.isValid());
3420 return size.value();
3421 }
3422
3423 /* static */
create(JSContext * cx)3424 SharedImmutableScriptData* SharedImmutableScriptData::create(JSContext* cx) {
3425 return cx->new_<SharedImmutableScriptData>();
3426 }
3427
3428 /* static */
createWith(JSContext * cx,js::UniquePtr<ImmutableScriptData> && isd)3429 SharedImmutableScriptData* SharedImmutableScriptData::createWith(
3430 JSContext* cx, js::UniquePtr<ImmutableScriptData>&& isd) {
3431 MOZ_ASSERT(isd.get());
3432 SharedImmutableScriptData* sisd = create(cx);
3433 if (!sisd) {
3434 return nullptr;
3435 }
3436
3437 sisd->setOwn(std::move(isd));
3438 return sisd;
3439 }
3440
relazify(JSRuntime * rt)3441 void JSScript::relazify(JSRuntime* rt) {
3442 js::Scope* scope = enclosingScope();
3443 UniquePtr<PrivateScriptData> scriptData;
3444
3445 #ifndef JS_CODEGEN_NONE
3446 // Any JIT compiles should have been released, so we already point to the
3447 // interpreter trampoline which supports lazy scripts.
3448 MOZ_ASSERT(isUsingInterpreterTrampoline(rt));
3449 #endif
3450
3451 // Without bytecode, the script counts are invalid so destroy them if they
3452 // still exist.
3453 destroyScriptCounts();
3454
3455 // Release the bytecode and gcthings list.
3456 // NOTE: We clear the PrivateScriptData to nullptr. This is fine because we
3457 // only allowed relazification (via AllowRelazify) if the original lazy
3458 // script we compiled from had a nullptr PrivateScriptData.
3459 swapData(scriptData);
3460 freeSharedData();
3461
3462 // We should not still be in any side-tables for the debugger or
3463 // code-coverage. The finalizer will not be able to clean them up once
3464 // bytecode is released. We check in JSFunction::maybeRelazify() for these
3465 // conditions before requesting relazification.
3466 MOZ_ASSERT(!coverage::IsLCovEnabled());
3467 MOZ_ASSERT(!hasScriptCounts());
3468 MOZ_ASSERT(!hasDebugScript());
3469
3470 // Rollback warmUpData_ to have enclosingScope.
3471 MOZ_ASSERT(warmUpData_.isWarmUpCount(),
3472 "JitScript should already be released");
3473 warmUpData_.resetWarmUpCount(0);
3474 warmUpData_.initEnclosingScope(scope);
3475
3476 MOZ_ASSERT(isReadyForDelazification());
3477 }
3478
3479 // Takes ownership of the passed SharedImmutableScriptData and either adds it
3480 // into the runtime's SharedImmutableScriptDataTable, or frees it if a matching
3481 // entry already exists and replaces the passed RefPtr with the existing entry.
3482 /* static */
shareScriptData(JSContext * cx,RefPtr<SharedImmutableScriptData> & sisd)3483 bool SharedImmutableScriptData::shareScriptData(
3484 JSContext* cx, RefPtr<SharedImmutableScriptData>& sisd) {
3485 MOZ_ASSERT(sisd);
3486 MOZ_ASSERT(sisd->refCount() == 1);
3487
3488 SharedImmutableScriptData* data = sisd.get();
3489
3490 // Calculate the hash before taking the lock. Because the data is reference
3491 // counted, it also will be freed after releasing the lock if necessary.
3492 SharedImmutableScriptData::Hasher::Lookup lookup(data);
3493
3494 AutoLockScriptData lock(cx->runtime());
3495
3496 SharedImmutableScriptDataTable::AddPtr p =
3497 cx->scriptDataTable(lock).lookupForAdd(lookup);
3498 if (p) {
3499 MOZ_ASSERT(data != *p);
3500 sisd = *p;
3501 } else {
3502 if (!cx->scriptDataTable(lock).add(p, data)) {
3503 ReportOutOfMemory(cx);
3504 return false;
3505 }
3506
3507 // Being in the table counts as a reference on the script data.
3508 data->AddRef();
3509 }
3510
3511 // Refs: sisd argument, SharedImmutableScriptDataTable
3512 MOZ_ASSERT(sisd->refCount() >= 2);
3513
3514 return true;
3515 }
3516
SweepScriptData(JSRuntime * rt)3517 void js::SweepScriptData(JSRuntime* rt) {
3518 // Entries are removed from the table when their reference count is one,
3519 // i.e. when the only reference to them is from the table entry.
3520
3521 AutoLockScriptData lock(rt);
3522 SharedImmutableScriptDataTable& table = rt->scriptDataTable(lock);
3523
3524 for (SharedImmutableScriptDataTable::Enum e(table); !e.empty();
3525 e.popFront()) {
3526 SharedImmutableScriptData* sharedData = e.front();
3527 if (sharedData->refCount() == 1) {
3528 sharedData->Release();
3529 e.removeFront();
3530 }
3531 }
3532 }
3533
allocationSize() const3534 inline size_t PrivateScriptData::allocationSize() const { return endOffset(); }
3535
3536 // Initialize and placement-new the trailing arrays.
PrivateScriptData(uint32_t ngcthings)3537 PrivateScriptData::PrivateScriptData(uint32_t ngcthings)
3538 : ngcthings(ngcthings) {
3539 // Variable-length data begins immediately after PrivateScriptData itself.
3540 // NOTE: Alignment is computed using cursor/offset so the alignment of
3541 // PrivateScriptData must be stricter than any trailing array type.
3542 Offset cursor = sizeof(PrivateScriptData);
3543
3544 // Layout and initialize the gcthings array.
3545 {
3546 initElements<JS::GCCellPtr>(cursor, ngcthings);
3547 cursor += ngcthings * sizeof(JS::GCCellPtr);
3548 }
3549
3550 // Sanity check.
3551 MOZ_ASSERT(endOffset() == cursor);
3552 }
3553
3554 /* static */
new_(JSContext * cx,uint32_t ngcthings)3555 PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t ngcthings) {
3556 // Compute size including trailing arrays.
3557 CheckedInt<Offset> size = sizeof(PrivateScriptData);
3558 size += CheckedInt<Offset>(ngcthings) * sizeof(JS::GCCellPtr);
3559 if (!size.isValid()) {
3560 ReportAllocationOverflow(cx);
3561 return nullptr;
3562 }
3563
3564 // Allocate contiguous raw buffer for the trailing arrays.
3565 void* raw = cx->pod_malloc<uint8_t>(size.value());
3566 MOZ_ASSERT(uintptr_t(raw) % alignof(PrivateScriptData) == 0);
3567 if (!raw) {
3568 return nullptr;
3569 }
3570
3571 // Constuct the PrivateScriptData. Trailing arrays are uninitialized but
3572 // GCPtrs are put into a safe state.
3573 PrivateScriptData* result = new (raw) PrivateScriptData(ngcthings);
3574 if (!result) {
3575 return nullptr;
3576 }
3577
3578 // Sanity check.
3579 MOZ_ASSERT(result->endOffset() == size.value());
3580
3581 return result;
3582 }
3583
3584 /* static */
InitFromStencil(JSContext * cx,js::HandleScript script,const js::frontend::CompilationInput & input,const js::frontend::CompilationStencil & stencil,js::frontend::CompilationGCOutput & gcOutput,const js::frontend::ScriptIndex scriptIndex)3585 bool PrivateScriptData::InitFromStencil(
3586 JSContext* cx, js::HandleScript script,
3587 const js::frontend::CompilationInput& input,
3588 const js::frontend::CompilationStencil& stencil,
3589 js::frontend::CompilationGCOutput& gcOutput,
3590 const js::frontend::ScriptIndex scriptIndex) {
3591 js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex];
3592 uint32_t ngcthings = scriptStencil.gcThingsLength;
3593
3594 MOZ_ASSERT(ngcthings <= INDEX_LIMIT);
3595
3596 // Create and initialize PrivateScriptData
3597 if (!JSScript::createPrivateScriptData(cx, script, ngcthings)) {
3598 return false;
3599 }
3600
3601 js::PrivateScriptData* data = script->data_;
3602 if (ngcthings) {
3603 if (!EmitScriptThingsVector(cx, input, stencil, gcOutput,
3604 scriptStencil.gcthings(stencil),
3605 data->gcthings())) {
3606 return false;
3607 }
3608 }
3609
3610 return true;
3611 }
3612
trace(JSTracer * trc)3613 void PrivateScriptData::trace(JSTracer* trc) {
3614 for (JS::GCCellPtr& elem : gcthings()) {
3615 gc::Cell* thing = elem.asCell();
3616 TraceManuallyBarrieredGenericPointerEdge(trc, &thing, "script-gcthing");
3617 if (MOZ_UNLIKELY(!thing)) {
3618 // NOTE: If we are clearing edges, also erase the type. This can happen
3619 // due to OOM triggering the ClearEdgesTracer.
3620 elem = JS::GCCellPtr();
3621 } else if (thing != elem.asCell()) {
3622 elem = JS::GCCellPtr(thing, elem.kind());
3623 }
3624 }
3625 }
3626
3627 /*static*/
Create(JSContext * cx,js::HandleObject functionOrGlobal,js::HandleScriptSourceObject sourceObject,const SourceExtent & extent,js::ImmutableScriptFlags flags)3628 JSScript* JSScript::Create(JSContext* cx, js::HandleObject functionOrGlobal,
3629 js::HandleScriptSourceObject sourceObject,
3630 const SourceExtent& extent,
3631 js::ImmutableScriptFlags flags) {
3632 return static_cast<JSScript*>(
3633 BaseScript::New(cx, functionOrGlobal, sourceObject, extent, flags));
3634 }
3635
3636 #ifdef MOZ_VTUNE
vtuneMethodID()3637 uint32_t JSScript::vtuneMethodID() {
3638 if (!zone()->scriptVTuneIdMap) {
3639 auto map = MakeUnique<ScriptVTuneIdMap>();
3640 if (!map) {
3641 MOZ_CRASH("Failed to allocate ScriptVTuneIdMap");
3642 }
3643
3644 zone()->scriptVTuneIdMap = std::move(map);
3645 }
3646
3647 ScriptVTuneIdMap::AddPtr p = zone()->scriptVTuneIdMap->lookupForAdd(this);
3648 if (p) {
3649 return p->value();
3650 }
3651
3652 MOZ_ASSERT(this->hasBytecode());
3653
3654 uint32_t id = vtune::GenerateUniqueMethodID();
3655 if (!zone()->scriptVTuneIdMap->add(p, this, id)) {
3656 MOZ_CRASH("Failed to add vtune method id");
3657 }
3658
3659 return id;
3660 }
3661 #endif
3662
3663 /* static */
createPrivateScriptData(JSContext * cx,HandleScript script,uint32_t ngcthings)3664 bool JSScript::createPrivateScriptData(JSContext* cx, HandleScript script,
3665 uint32_t ngcthings) {
3666 cx->check(script);
3667
3668 UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings));
3669 if (!data) {
3670 return false;
3671 }
3672
3673 script->swapData(data);
3674 MOZ_ASSERT(!data);
3675
3676 return true;
3677 }
3678
3679 /* static */
fullyInitFromStencil(JSContext * cx,const js::frontend::CompilationInput & input,const js::frontend::CompilationStencil & stencil,frontend::CompilationGCOutput & gcOutput,HandleScript script,const js::frontend::ScriptIndex scriptIndex)3680 bool JSScript::fullyInitFromStencil(
3681 JSContext* cx, const js::frontend::CompilationInput& input,
3682 const js::frontend::CompilationStencil& stencil,
3683 frontend::CompilationGCOutput& gcOutput, HandleScript script,
3684 const js::frontend::ScriptIndex scriptIndex) {
3685 MutableScriptFlags lazyMutableFlags;
3686 RootedScope lazyEnclosingScope(cx);
3687
3688 // A holder for the lazy PrivateScriptData that we must keep around in case
3689 // this process fails and we must return the script to its original state.
3690 //
3691 // This is initialized by BaseScript::swapData() which will run pre-barriers
3692 // for us. On successful conversion to non-lazy script, the old script data
3693 // here will be released by the UniquePtr.
3694 Rooted<UniquePtr<PrivateScriptData>> lazyData(cx);
3695
3696 #ifndef JS_CODEGEN_NONE
3697 // Whether we are a newborn script or an existing lazy script, we should
3698 // already be pointing to the interpreter trampoline.
3699 MOZ_ASSERT(script->isUsingInterpreterTrampoline(cx->runtime()));
3700 #endif
3701
3702 // If we are using an existing lazy script, record enough info to be able to
3703 // rollback on failure.
3704 if (script->isReadyForDelazification()) {
3705 lazyMutableFlags = script->mutableFlags_;
3706 lazyEnclosingScope = script->releaseEnclosingScope();
3707 script->swapData(lazyData.get());
3708 MOZ_ASSERT(script->sharedData_ == nullptr);
3709 }
3710
3711 // Restore the script to lazy state on failure. If this was a fresh script, we
3712 // just need to clear bytecode to mark script as incomplete.
3713 auto rollbackGuard = mozilla::MakeScopeExit([&] {
3714 if (lazyEnclosingScope) {
3715 script->mutableFlags_ = lazyMutableFlags;
3716 script->warmUpData_.initEnclosingScope(lazyEnclosingScope);
3717 script->swapData(lazyData.get());
3718 script->sharedData_ = nullptr;
3719
3720 MOZ_ASSERT(script->isReadyForDelazification());
3721 } else {
3722 script->sharedData_ = nullptr;
3723 }
3724 });
3725
3726 // The counts of indexed things must be checked during code generation.
3727 MOZ_ASSERT(stencil.scriptData[scriptIndex].gcThingsLength <= INDEX_LIMIT);
3728
3729 // Note: These flags should already be correct when the BaseScript was
3730 // allocated.
3731 MOZ_ASSERT_IF(stencil.isInitialStencil(),
3732 script->immutableFlags() ==
3733 stencil.scriptExtra[scriptIndex].immutableFlags);
3734
3735 // Create and initialize PrivateScriptData
3736 if (!PrivateScriptData::InitFromStencil(cx, script, input, stencil, gcOutput,
3737 scriptIndex)) {
3738 return false;
3739 }
3740
3741 // Member-initializer data is computed in initial parse only. If we are
3742 // delazifying, make sure to copy it off the `lazyData` before we throw it
3743 // away.
3744 if (script->useMemberInitializers()) {
3745 if (stencil.isInitialStencil()) {
3746 MemberInitializers initializers(
3747 stencil.scriptExtra[scriptIndex].memberInitializers());
3748 script->setMemberInitializers(initializers);
3749 } else {
3750 script->setMemberInitializers(lazyData.get()->getMemberInitializers());
3751 }
3752 }
3753
3754 script->initSharedData(stencil.sharedData.get(scriptIndex));
3755
3756 // NOTE: JSScript is now constructed and should be linked in.
3757 rollbackGuard.release();
3758
3759 // Link Scope -> JSFunction -> BaseScript.
3760 if (script->isFunction()) {
3761 JSFunction* fun = gcOutput.functions[scriptIndex];
3762 script->bodyScope()->as<FunctionScope>().initCanonicalFunction(fun);
3763 if (fun->isIncomplete()) {
3764 fun->initScript(script);
3765 } else {
3766 // We are delazifying in-place.
3767 MOZ_ASSERT(fun->baseScript() == script);
3768 }
3769 }
3770
3771 // NOTE: The caller is responsible for linking ModuleObjects if this is a
3772 // module script.
3773
3774 #ifdef JS_STRUCTURED_SPEW
3775 // We want this to happen after line number initialization to allow filtering
3776 // to work.
3777 script->setSpewEnabled(cx->spewer().enabled(script));
3778 #endif
3779
3780 #ifdef DEBUG
3781 script->assertValidJumpTargets();
3782 #endif
3783
3784 if (coverage::IsLCovEnabled()) {
3785 if (!coverage::InitScriptCoverage(cx, script)) {
3786 return false;
3787 }
3788 }
3789
3790 return true;
3791 }
3792
fromStencil(JSContext * cx,frontend::CompilationInput & input,const frontend::CompilationStencil & stencil,frontend::CompilationGCOutput & gcOutput,frontend::ScriptIndex scriptIndex)3793 JSScript* JSScript::fromStencil(JSContext* cx,
3794 frontend::CompilationInput& input,
3795 const frontend::CompilationStencil& stencil,
3796 frontend::CompilationGCOutput& gcOutput,
3797 frontend::ScriptIndex scriptIndex) {
3798 js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex];
3799 js::frontend::ScriptStencilExtra& scriptExtra =
3800 stencil.scriptExtra[scriptIndex];
3801 MOZ_ASSERT(scriptStencil.hasSharedData(),
3802 "Need generated bytecode to use JSScript::fromStencil");
3803
3804 RootedObject functionOrGlobal(cx, cx->global());
3805 if (scriptStencil.isFunction()) {
3806 functionOrGlobal = gcOutput.functions[scriptIndex];
3807 }
3808
3809 Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject);
3810 RootedScript script(
3811 cx, Create(cx, functionOrGlobal, sourceObject, scriptExtra.extent,
3812 scriptExtra.immutableFlags));
3813 if (!script) {
3814 return nullptr;
3815 }
3816
3817 if (!fullyInitFromStencil(cx, input, stencil, gcOutput, script,
3818 scriptIndex)) {
3819 return nullptr;
3820 }
3821
3822 return script;
3823 }
3824
3825 #ifdef DEBUG
assertValidJumpTargets() const3826 void JSScript::assertValidJumpTargets() const {
3827 BytecodeLocation mainLoc = mainLocation();
3828 BytecodeLocation endLoc = endLocation();
3829 AllBytecodesIterable iter(this);
3830 for (BytecodeLocation loc : iter) {
3831 // Check jump instructions' target.
3832 if (loc.isJump()) {
3833 BytecodeLocation target = loc.getJumpTarget();
3834 MOZ_ASSERT(mainLoc <= target && target < endLoc);
3835 MOZ_ASSERT(target.isJumpTarget());
3836
3837 // All backward jumps must be to a JSOp::LoopHead op. This is an invariant
3838 // we want to maintain to simplify JIT compilation and bytecode analysis.
3839 MOZ_ASSERT_IF(target < loc, target.is(JSOp::LoopHead));
3840 MOZ_ASSERT_IF(target < loc, IsBackedgePC(loc.toRawBytecode()));
3841
3842 // All forward jumps must be to a JSOp::JumpTarget op.
3843 MOZ_ASSERT_IF(target > loc, target.is(JSOp::JumpTarget));
3844
3845 // Jumps must not cross scope boundaries.
3846 MOZ_ASSERT(loc.innermostScope(this) == target.innermostScope(this));
3847
3848 // Check fallthrough of conditional jump instructions.
3849 if (loc.fallsThrough()) {
3850 BytecodeLocation fallthrough = loc.next();
3851 MOZ_ASSERT(mainLoc <= fallthrough && fallthrough < endLoc);
3852 MOZ_ASSERT(fallthrough.isJumpTarget());
3853 }
3854 }
3855
3856 // Check table switch case labels.
3857 if (loc.is(JSOp::TableSwitch)) {
3858 BytecodeLocation target = loc.getTableSwitchDefaultTarget();
3859
3860 // Default target.
3861 MOZ_ASSERT(mainLoc <= target && target < endLoc);
3862 MOZ_ASSERT(target.is(JSOp::JumpTarget));
3863
3864 int32_t low = loc.getTableSwitchLow();
3865 int32_t high = loc.getTableSwitchHigh();
3866
3867 for (int i = 0; i < high - low + 1; i++) {
3868 BytecodeLocation switchCase = loc.getTableSwitchCaseTarget(this, i);
3869 MOZ_ASSERT(mainLoc <= switchCase && switchCase < endLoc);
3870 MOZ_ASSERT(switchCase.is(JSOp::JumpTarget));
3871 }
3872 }
3873 }
3874
3875 // Check catch/finally blocks as jump targets.
3876 for (const TryNote& tn : trynotes()) {
3877 if (tn.kind() != TryNoteKind::Catch && tn.kind() != TryNoteKind::Finally) {
3878 continue;
3879 }
3880
3881 jsbytecode* tryStart = offsetToPC(tn.start);
3882 jsbytecode* tryPc = tryStart - JSOpLength_Try;
3883 MOZ_ASSERT(JSOp(*tryPc) == JSOp::Try);
3884
3885 jsbytecode* tryTarget = tryStart + tn.length;
3886 MOZ_ASSERT(main() <= tryTarget && tryTarget < codeEnd());
3887 MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget)));
3888 }
3889 }
3890 #endif
3891
addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf,size_t * sizeOfJitScript,size_t * sizeOfBaselineFallbackStubs) const3892 void JSScript::addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf,
3893 size_t* sizeOfJitScript,
3894 size_t* sizeOfBaselineFallbackStubs) const {
3895 if (!hasJitScript()) {
3896 return;
3897 }
3898
3899 jitScript()->addSizeOfIncludingThis(mallocSizeOf, sizeOfJitScript,
3900 sizeOfBaselineFallbackStubs);
3901 }
3902
uninlinedGlobal() const3903 js::GlobalObject& JSScript::uninlinedGlobal() const { return global(); }
3904
3905 static const uint32_t GSN_CACHE_THRESHOLD = 100;
3906
purge()3907 void GSNCache::purge() {
3908 code = nullptr;
3909 map.clearAndCompact();
3910 }
3911
GetSrcNote(GSNCache & cache,JSScript * script,jsbytecode * pc)3912 const js::SrcNote* js::GetSrcNote(GSNCache& cache, JSScript* script,
3913 jsbytecode* pc) {
3914 size_t target = pc - script->code();
3915 if (target >= script->length()) {
3916 return nullptr;
3917 }
3918
3919 if (cache.code == script->code()) {
3920 GSNCache::Map::Ptr p = cache.map.lookup(pc);
3921 return p ? p->value() : nullptr;
3922 }
3923
3924 size_t offset = 0;
3925 const js::SrcNote* result;
3926 for (SrcNoteIterator iter(script->notes());; ++iter) {
3927 auto sn = *iter;
3928 if (sn->isTerminator()) {
3929 result = nullptr;
3930 break;
3931 }
3932 offset += sn->delta();
3933 if (offset == target && sn->isGettable()) {
3934 result = sn;
3935 break;
3936 }
3937 }
3938
3939 if (cache.code != script->code() && script->length() >= GSN_CACHE_THRESHOLD) {
3940 unsigned nsrcnotes = 0;
3941 for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
3942 auto sn = *iter;
3943 if (sn->isGettable()) {
3944 ++nsrcnotes;
3945 }
3946 }
3947 if (cache.code) {
3948 cache.map.clear();
3949 cache.code = nullptr;
3950 }
3951 if (cache.map.reserve(nsrcnotes)) {
3952 pc = script->code();
3953 for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
3954 auto sn = *iter;
3955 pc += sn->delta();
3956 if (sn->isGettable()) {
3957 cache.map.putNewInfallible(pc, sn);
3958 }
3959 }
3960 cache.code = script->code();
3961 }
3962 }
3963
3964 return result;
3965 }
3966
GetSrcNote(JSContext * cx,JSScript * script,jsbytecode * pc)3967 const js::SrcNote* js::GetSrcNote(JSContext* cx, JSScript* script,
3968 jsbytecode* pc) {
3969 return GetSrcNote(cx->caches().gsnCache, script, pc);
3970 }
3971
PCToLineNumber(unsigned startLine,unsigned startCol,SrcNote * notes,jsbytecode * code,jsbytecode * pc,unsigned * columnp)3972 unsigned js::PCToLineNumber(unsigned startLine, unsigned startCol,
3973 SrcNote* notes, jsbytecode* code, jsbytecode* pc,
3974 unsigned* columnp) {
3975 unsigned lineno = startLine;
3976 unsigned column = startCol;
3977
3978 /*
3979 * Walk through source notes accumulating their deltas, keeping track of
3980 * line-number notes, until we pass the note for pc's offset within
3981 * script->code.
3982 */
3983 ptrdiff_t offset = 0;
3984 ptrdiff_t target = pc - code;
3985 for (SrcNoteIterator iter(notes); !iter.atEnd(); ++iter) {
3986 auto sn = *iter;
3987 offset += sn->delta();
3988 if (offset > target) {
3989 break;
3990 }
3991
3992 SrcNoteType type = sn->type();
3993 if (type == SrcNoteType::SetLine) {
3994 lineno = SrcNote::SetLine::getLine(sn, startLine);
3995 column = 0;
3996 } else if (type == SrcNoteType::NewLine) {
3997 lineno++;
3998 column = 0;
3999 } else if (type == SrcNoteType::ColSpan) {
4000 ptrdiff_t colspan = SrcNote::ColSpan::getSpan(sn);
4001 MOZ_ASSERT(ptrdiff_t(column) + colspan >= 0);
4002 column += colspan;
4003 }
4004 }
4005
4006 if (columnp) {
4007 *columnp = column;
4008 }
4009
4010 return lineno;
4011 }
4012
PCToLineNumber(JSScript * script,jsbytecode * pc,unsigned * columnp)4013 unsigned js::PCToLineNumber(JSScript* script, jsbytecode* pc,
4014 unsigned* columnp) {
4015 /* Cope with InterpreterFrame.pc value prior to entering Interpret. */
4016 if (!pc) {
4017 return 0;
4018 }
4019
4020 return PCToLineNumber(script->lineno(), script->column(), script->notes(),
4021 script->code(), pc, columnp);
4022 }
4023
LineNumberToPC(JSScript * script,unsigned target)4024 jsbytecode* js::LineNumberToPC(JSScript* script, unsigned target) {
4025 ptrdiff_t offset = 0;
4026 ptrdiff_t best = -1;
4027 unsigned lineno = script->lineno();
4028 unsigned bestdiff = SrcNote::MaxOperand;
4029 for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
4030 auto sn = *iter;
4031 /*
4032 * Exact-match only if offset is not in the prologue; otherwise use
4033 * nearest greater-or-equal line number match.
4034 */
4035 if (lineno == target && offset >= ptrdiff_t(script->mainOffset())) {
4036 goto out;
4037 }
4038 if (lineno >= target) {
4039 unsigned diff = lineno - target;
4040 if (diff < bestdiff) {
4041 bestdiff = diff;
4042 best = offset;
4043 }
4044 }
4045 offset += sn->delta();
4046 SrcNoteType type = sn->type();
4047 if (type == SrcNoteType::SetLine) {
4048 lineno = SrcNote::SetLine::getLine(sn, script->lineno());
4049 } else if (type == SrcNoteType::NewLine) {
4050 lineno++;
4051 }
4052 }
4053 if (best >= 0) {
4054 offset = best;
4055 }
4056 out:
4057 return script->offsetToPC(offset);
4058 }
4059
GetScriptLineExtent(JSScript * script)4060 JS_PUBLIC_API unsigned js::GetScriptLineExtent(JSScript* script) {
4061 unsigned lineno = script->lineno();
4062 unsigned maxLineNo = lineno;
4063 for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
4064 auto sn = *iter;
4065 SrcNoteType type = sn->type();
4066 if (type == SrcNoteType::SetLine) {
4067 lineno = SrcNote::SetLine::getLine(sn, script->lineno());
4068 } else if (type == SrcNoteType::NewLine) {
4069 lineno++;
4070 }
4071
4072 if (maxLineNo < lineno) {
4073 maxLineNo = lineno;
4074 }
4075 }
4076
4077 return 1 + maxLineNo - script->lineno();
4078 }
4079
4080 #ifdef JS_CACHEIR_SPEW
maybeUpdateWarmUpCount(JSScript * script)4081 void js::maybeUpdateWarmUpCount(JSScript* script) {
4082 if (script->needsFinalWarmUpCount()) {
4083 ScriptFinalWarmUpCountMap* map =
4084 script->zone()->scriptFinalWarmUpCountMap.get();
4085 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
4086 // already been created and thus must be asserted.
4087 MOZ_ASSERT(map);
4088 ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script);
4089 MOZ_ASSERT(p);
4090
4091 mozilla::Get<0>(p->value()) += script->jitScript()->warmUpCount();
4092 }
4093 }
4094
maybeSpewScriptFinalWarmUpCount(JSScript * script)4095 void js::maybeSpewScriptFinalWarmUpCount(JSScript* script) {
4096 if (script->needsFinalWarmUpCount()) {
4097 ScriptFinalWarmUpCountMap* map =
4098 script->zone()->scriptFinalWarmUpCountMap.get();
4099 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
4100 // already been created and thus must be asserted.
4101 MOZ_ASSERT(map);
4102 ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script);
4103 MOZ_ASSERT(p);
4104 uint32_t warmUpCount;
4105 const char* scriptName;
4106 mozilla::Tie(warmUpCount, scriptName) = p->value();
4107
4108 JSContext* cx = TlsContext.get();
4109 cx->spewer().enableSpewing();
4110
4111 // In the case that we care about a script's final warmup count but the
4112 // spewer is not enabled, AutoSpewChannel automatically sets and unsets
4113 // the proper channel for the duration of spewing a health report's warm
4114 // up count.
4115 AutoSpewChannel channel(cx, SpewChannel::CacheIRHealthReport, script);
4116 jit::CacheIRHealth cih;
4117 cih.spewScriptFinalWarmUpCount(cx, scriptName, script, warmUpCount);
4118
4119 script->zone()->scriptFinalWarmUpCountMap->remove(script);
4120 script->setNeedsFinalWarmUpCount(false);
4121 }
4122 }
4123 #endif
4124
DescribeScriptedCallerForDirectEval(JSContext * cx,HandleScript script,jsbytecode * pc,const char ** file,unsigned * linenop,uint32_t * pcOffset,bool * mutedErrors)4125 void js::DescribeScriptedCallerForDirectEval(JSContext* cx, HandleScript script,
4126 jsbytecode* pc, const char** file,
4127 unsigned* linenop,
4128 uint32_t* pcOffset,
4129 bool* mutedErrors) {
4130 MOZ_ASSERT(script->containsPC(pc));
4131
4132 static_assert(JSOpLength_SpreadEval == JSOpLength_StrictSpreadEval,
4133 "next op after a spread must be at consistent offset");
4134 static_assert(JSOpLength_Eval == JSOpLength_StrictEval,
4135 "next op after a direct eval must be at consistent offset");
4136
4137 MOZ_ASSERT(JSOp(*pc) == JSOp::Eval || JSOp(*pc) == JSOp::StrictEval ||
4138 JSOp(*pc) == JSOp::SpreadEval ||
4139 JSOp(*pc) == JSOp::StrictSpreadEval);
4140
4141 bool isSpread =
4142 (JSOp(*pc) == JSOp::SpreadEval || JSOp(*pc) == JSOp::StrictSpreadEval);
4143 jsbytecode* nextpc =
4144 pc + (isSpread ? JSOpLength_SpreadEval : JSOpLength_Eval);
4145 MOZ_ASSERT(JSOp(*nextpc) == JSOp::Lineno);
4146
4147 *file = script->filename();
4148 *linenop = GET_UINT32(nextpc);
4149 *pcOffset = script->pcToOffset(pc);
4150 *mutedErrors = script->mutedErrors();
4151 }
4152
DescribeScriptedCallerForCompilation(JSContext * cx,MutableHandleScript maybeScript,const char ** file,unsigned * linenop,uint32_t * pcOffset,bool * mutedErrors)4153 void js::DescribeScriptedCallerForCompilation(
4154 JSContext* cx, MutableHandleScript maybeScript, const char** file,
4155 unsigned* linenop, uint32_t* pcOffset, bool* mutedErrors) {
4156 NonBuiltinFrameIter iter(cx, cx->realm()->principals());
4157
4158 if (iter.done()) {
4159 maybeScript.set(nullptr);
4160 *file = nullptr;
4161 *linenop = 0;
4162 *pcOffset = 0;
4163 *mutedErrors = false;
4164 return;
4165 }
4166
4167 *file = iter.filename();
4168 *linenop = iter.computeLine();
4169 *mutedErrors = iter.mutedErrors();
4170
4171 // These values are only used for introducer fields which are debugging
4172 // information and can be safely left null for wasm frames.
4173 if (iter.hasScript()) {
4174 maybeScript.set(iter.script());
4175 *pcOffset = iter.pc() - maybeScript->code();
4176 } else {
4177 maybeScript.set(nullptr);
4178 *pcOffset = 0;
4179 }
4180 }
4181
CloneInnerInterpretedFunction(JSContext * cx,HandleScope enclosingScope,HandleFunction srcFun,Handle<ScriptSourceObject * > sourceObject)4182 static JSObject* CloneInnerInterpretedFunction(
4183 JSContext* cx, HandleScope enclosingScope, HandleFunction srcFun,
4184 Handle<ScriptSourceObject*> sourceObject) {
4185 /* NB: Keep this in sync with XDRInterpretedFunction. */
4186 RootedObject cloneProto(cx);
4187 if (!GetFunctionPrototype(cx, srcFun->generatorKind(), srcFun->asyncKind(),
4188 &cloneProto)) {
4189 return nullptr;
4190 }
4191
4192 RootedAtom atom(cx, srcFun->displayAtom());
4193 if (atom) {
4194 cx->markAtom(atom);
4195 }
4196 RootedFunction clone(
4197 cx, NewFunctionWithProto(cx, nullptr, srcFun->nargs(), srcFun->flags(),
4198 nullptr, atom, cloneProto,
4199 srcFun->getAllocKind(), TenuredObject));
4200 if (!clone) {
4201 return nullptr;
4202 }
4203
4204 JSScript::AutoDelazify srcScript(cx, srcFun);
4205 if (!srcScript) {
4206 return nullptr;
4207 }
4208 JSScript* cloneScript = CloneScriptIntoFunction(cx, enclosingScope, clone,
4209 srcScript, sourceObject);
4210 if (!cloneScript) {
4211 return nullptr;
4212 }
4213
4214 MOZ_ASSERT(cloneScript->hasBytecode());
4215
4216 return clone;
4217 }
4218
CloneScriptObject(JSContext * cx,PrivateScriptData * srcData,HandleObject obj,Handle<ScriptSourceObject * > sourceObject,JS::HandleVector<JS::GCCellPtr> gcThings)4219 static JSObject* CloneScriptObject(JSContext* cx, PrivateScriptData* srcData,
4220 HandleObject obj,
4221 Handle<ScriptSourceObject*> sourceObject,
4222 JS::HandleVector<JS::GCCellPtr> gcThings) {
4223 if (obj->is<RegExpObject>()) {
4224 return CloneScriptRegExpObject(cx, obj->as<RegExpObject>());
4225 }
4226
4227 if (obj->is<JSFunction>()) {
4228 HandleFunction innerFun = obj.as<JSFunction>();
4229 if (innerFun->isNativeFun()) {
4230 if (cx->realm() != innerFun->realm()) {
4231 MOZ_ASSERT(innerFun->isAsmJSNative());
4232 JS_ReportErrorASCII(cx, "AsmJS modules do not yet support cloning.");
4233 return nullptr;
4234 }
4235 return innerFun;
4236 }
4237
4238 if (!innerFun->hasBytecode()) {
4239 MOZ_ASSERT(!innerFun->isSelfHostedOrIntrinsic(),
4240 "Cannot enter realm of self-hosted functions");
4241 AutoRealm ar(cx, innerFun);
4242 if (!JSFunction::getOrCreateScript(cx, innerFun)) {
4243 return nullptr;
4244 }
4245 }
4246
4247 Scope* enclosing = innerFun->enclosingScope();
4248 uint32_t scopeIndex = FindScopeIndex(srcData->gcthings(), *enclosing);
4249 RootedScope enclosingClone(cx, &gcThings[scopeIndex].get().as<Scope>());
4250 return CloneInnerInterpretedFunction(cx, enclosingClone, innerFun,
4251 sourceObject);
4252 }
4253
4254 return DeepCloneObjectLiteral(cx, obj);
4255 }
4256
4257 /* static */
Clone(JSContext * cx,HandleScript src,HandleScript dst,MutableHandle<GCVector<Scope * >> scopes)4258 bool PrivateScriptData::Clone(JSContext* cx, HandleScript src, HandleScript dst,
4259 MutableHandle<GCVector<Scope*>> scopes) {
4260 PrivateScriptData* srcData = src->data_;
4261 uint32_t ngcthings = srcData->gcthings().size();
4262
4263 // Clone GC things.
4264 JS::RootedVector<JS::GCCellPtr> gcThings(cx);
4265 size_t scopeIndex = 0;
4266 Rooted<ScriptSourceObject*> sourceObject(cx, dst->sourceObject());
4267 RootedObject obj(cx);
4268 RootedScope scope(cx);
4269 RootedScope enclosingScope(cx);
4270 RootedBigInt bigint(cx);
4271 for (JS::GCCellPtr gcThing : srcData->gcthings()) {
4272 if (gcThing.is<JSObject>()) {
4273 obj = &gcThing.as<JSObject>();
4274 JSObject* clone =
4275 CloneScriptObject(cx, srcData, obj, sourceObject, gcThings);
4276 if (!clone || !gcThings.append(JS::GCCellPtr(clone))) {
4277 return false;
4278 }
4279 } else if (gcThing.is<Scope>()) {
4280 // The passed in scopes vector contains body scopes that needed to be
4281 // cloned especially, depending on whether the script is a function or
4282 // global scope. Clone all other scopes.
4283 if (scopeIndex < scopes.length()) {
4284 if (!gcThings.append(JS::GCCellPtr(scopes[scopeIndex].get()))) {
4285 return false;
4286 }
4287 } else {
4288 scope = &gcThing.as<Scope>();
4289 uint32_t enclosingScopeIndex =
4290 FindScopeIndex(srcData->gcthings(), *scope->enclosing());
4291 enclosingScope = &gcThings[enclosingScopeIndex].get().as<Scope>();
4292 Scope* clone = Scope::clone(cx, scope, enclosingScope);
4293 if (!clone || !gcThings.append(JS::GCCellPtr(clone))) {
4294 return false;
4295 }
4296 }
4297 scopeIndex++;
4298 } else if (gcThing.is<JSString>()) {
4299 JSAtom* atom = &gcThing.as<JSString>().asAtom();
4300 if (cx->zone() != atom->zone()) {
4301 cx->markAtom(atom);
4302 }
4303 if (!gcThings.append(JS::GCCellPtr(atom))) {
4304 return false;
4305 }
4306 } else {
4307 bigint = &gcThing.as<BigInt>();
4308 BigInt* clone = bigint;
4309 if (cx->zone() != bigint->zone()) {
4310 clone = BigInt::copy(cx, bigint, gc::TenuredHeap);
4311 if (!clone) {
4312 return false;
4313 }
4314 }
4315 if (!gcThings.append(JS::GCCellPtr(clone))) {
4316 return false;
4317 }
4318 }
4319 }
4320
4321 // Create the new PrivateScriptData on |dst| and fill it in.
4322 if (!JSScript::createPrivateScriptData(cx, dst, ngcthings)) {
4323 return false;
4324 }
4325 PrivateScriptData* dstData = dst->data_;
4326
4327 dstData->memberInitializers_ = srcData->memberInitializers_;
4328
4329 {
4330 auto array = dstData->gcthings();
4331 for (uint32_t i = 0; i < ngcthings; ++i) {
4332 array[i] = gcThings[i].get();
4333 }
4334 }
4335
4336 return true;
4337 }
4338
CopyScriptImpl(JSContext * cx,HandleScript src,HandleObject functionOrGlobal,HandleScriptSourceObject sourceObject,MutableHandle<GCVector<Scope * >> scopes,SourceExtent * maybeClassExtent=nullptr)4339 static JSScript* CopyScriptImpl(JSContext* cx, HandleScript src,
4340 HandleObject functionOrGlobal,
4341 HandleScriptSourceObject sourceObject,
4342 MutableHandle<GCVector<Scope*>> scopes,
4343 SourceExtent* maybeClassExtent = nullptr) {
4344 if (src->treatAsRunOnce()) {
4345 MOZ_ASSERT(!src->isFunction());
4346 JS_ReportErrorASCII(cx, "No cloning toplevel run-once scripts");
4347 return nullptr;
4348 }
4349
4350 /* NB: Keep this in sync with XDRScript. */
4351
4352 // Some embeddings are not careful to use ExposeObjectToActiveJS as needed.
4353 JS::AssertObjectIsNotGray(sourceObject);
4354
4355 SourceExtent extent = src->extent();
4356
4357 ImmutableScriptFlags flags = src->immutableFlags();
4358 flags.setFlag(JSScript::ImmutableFlags::HasNonSyntacticScope,
4359 scopes[0]->hasOnChain(ScopeKind::NonSyntactic));
4360
4361 // FunctionFlags and ImmutableScriptFlags should agree on self-hosting status.
4362 MOZ_ASSERT_IF(functionOrGlobal->is<JSFunction>(),
4363 functionOrGlobal->as<JSFunction>().isSelfHostedBuiltin() ==
4364 flags.hasFlag(JSScript::ImmutableFlags::SelfHosted));
4365
4366 // Create a new JSScript to fill in.
4367 RootedScript dst(
4368 cx, JSScript::Create(cx, functionOrGlobal, sourceObject, extent, flags));
4369 if (!dst) {
4370 return nullptr;
4371 }
4372
4373 // Clone the PrivateScriptData into dst
4374 if (!PrivateScriptData::Clone(cx, src, dst, scopes)) {
4375 return nullptr;
4376 }
4377
4378 // The SharedImmutableScriptData can be reused by any zone in the Runtime.
4379 dst->initSharedData(src->sharedData());
4380
4381 return dst;
4382 }
4383
CloneGlobalScript(JSContext * cx,HandleScript src)4384 JSScript* js::CloneGlobalScript(JSContext* cx, HandleScript src) {
4385 MOZ_ASSERT(src->realm() != cx->realm(),
4386 "js::CloneGlobalScript should only be used for for realm "
4387 "mismatches. Otherwise just share the script directly.");
4388
4389 Rooted<ScriptSourceObject*> sourceObject(cx, src->sourceObject());
4390 if (cx->compartment() != sourceObject->compartment()) {
4391 sourceObject = ScriptSourceObject::clone(cx, sourceObject);
4392 if (!sourceObject) {
4393 return nullptr;
4394 }
4395 }
4396
4397 MOZ_ASSERT(src->bodyScopeIndex() == GCThingIndex::outermostScopeIndex());
4398 Rooted<GCVector<Scope*>> scopes(cx, GCVector<Scope*>(cx));
4399 Rooted<GlobalScope*> original(cx, &src->bodyScope()->as<GlobalScope>());
4400 GlobalScope* clone = GlobalScope::clone(cx, original);
4401 if (!clone || !scopes.append(clone)) {
4402 return nullptr;
4403 }
4404
4405 RootedObject global(cx, cx->global());
4406 RootedScript dst(cx, CopyScriptImpl(cx, src, global, sourceObject, &scopes));
4407 if (!dst) {
4408 return nullptr;
4409 }
4410
4411 if (coverage::IsLCovEnabled()) {
4412 if (!coverage::InitScriptCoverage(cx, dst)) {
4413 return nullptr;
4414 }
4415 }
4416
4417 DebugAPI::onNewScript(cx, dst);
4418
4419 return dst;
4420 }
4421
CloneScriptIntoFunction(JSContext * cx,HandleScope enclosingScope,HandleFunction fun,HandleScript src,Handle<ScriptSourceObject * > sourceObject)4422 JSScript* js::CloneScriptIntoFunction(
4423 JSContext* cx, HandleScope enclosingScope, HandleFunction fun,
4424 HandleScript src, Handle<ScriptSourceObject*> sourceObject) {
4425 MOZ_ASSERT(src->realm() != cx->realm(),
4426 "js::CloneScriptIntoFunction should only be used for for realm "
4427 "mismatches. Otherwise just share the script directly.");
4428
4429 // We are either delazifying a self-hosted lazy function or the function
4430 // should be in an inactive state.
4431 MOZ_ASSERT(fun->isIncomplete() || fun->hasSelfHostedLazyScript());
4432
4433 // Clone the non-intra-body scopes.
4434 Rooted<GCVector<Scope*>> scopes(cx, GCVector<Scope*>(cx));
4435 RootedScope original(cx);
4436 RootedScope enclosingClone(cx);
4437 for (uint32_t i = 0; i <= src->bodyScopeIndex().index; i++) {
4438 original = src->getScope(GCThingIndex(i));
4439
4440 if (i == 0) {
4441 enclosingClone = enclosingScope;
4442 } else {
4443 MOZ_ASSERT(src->getScope(GCThingIndex(i - 1)) == original->enclosing());
4444 enclosingClone = scopes[i - 1];
4445 }
4446
4447 Scope* clone;
4448 if (original->is<FunctionScope>()) {
4449 clone = FunctionScope::clone(cx, original.as<FunctionScope>(), fun,
4450 enclosingClone);
4451 } else {
4452 clone = Scope::clone(cx, original, enclosingClone);
4453 }
4454
4455 if (!clone || !scopes.append(clone)) {
4456 return nullptr;
4457 }
4458 }
4459
4460 // Save flags in case we need to undo the early mutations.
4461 const FunctionFlags preservedFlags = fun->flags();
4462 RootedScript dst(cx, CopyScriptImpl(cx, src, fun, sourceObject, &scopes));
4463 if (!dst) {
4464 fun->setFlags(preservedFlags);
4465 return nullptr;
4466 }
4467
4468 // Finally set the script after all the fallible operations.
4469 if (fun->isIncomplete()) {
4470 fun->initScript(dst);
4471 } else {
4472 MOZ_ASSERT(fun->hasSelfHostedLazyScript());
4473 fun->clearSelfHostedLazyScript();
4474 fun->initScript(dst);
4475 }
4476
4477 if (coverage::IsLCovEnabled()) {
4478 if (!coverage::InitScriptCoverage(cx, dst)) {
4479 return nullptr;
4480 }
4481 }
4482
4483 return dst;
4484 }
4485
4486 template <typename SourceSpan, typename TargetSpan>
CopySpan(const SourceSpan & source,TargetSpan target)4487 void CopySpan(const SourceSpan& source, TargetSpan target) {
4488 MOZ_ASSERT(source.size() == target.size());
4489 std::copy(source.cbegin(), source.cend(), target.begin());
4490 }
4491
4492 /* static */
new_(JSContext * cx,uint32_t mainOffset,uint32_t nfixed,uint32_t nslots,GCThingIndex bodyScopeIndex,uint32_t numICEntries,bool isFunction,uint16_t funLength,mozilla::Span<const jsbytecode> code,mozilla::Span<const SrcNote> notes,mozilla::Span<const uint32_t> resumeOffsets,mozilla::Span<const ScopeNote> scopeNotes,mozilla::Span<const TryNote> tryNotes)4493 js::UniquePtr<ImmutableScriptData> ImmutableScriptData::new_(
4494 JSContext* cx, uint32_t mainOffset, uint32_t nfixed, uint32_t nslots,
4495 GCThingIndex bodyScopeIndex, uint32_t numICEntries, bool isFunction,
4496 uint16_t funLength, mozilla::Span<const jsbytecode> code,
4497 mozilla::Span<const SrcNote> notes,
4498 mozilla::Span<const uint32_t> resumeOffsets,
4499 mozilla::Span<const ScopeNote> scopeNotes,
4500 mozilla::Span<const TryNote> tryNotes) {
4501 MOZ_RELEASE_ASSERT(code.Length() <= frontend::MaxBytecodeLength);
4502
4503 // There are 1-4 copies of SrcNoteType::Null appended after the source
4504 // notes. These are a combination of sentinel and padding values.
4505 static_assert(frontend::MaxSrcNotesLength <= UINT32_MAX - CodeNoteAlign,
4506 "Length + CodeNoteAlign shouldn't overflow UINT32_MAX");
4507 size_t noteLength = notes.Length();
4508 MOZ_RELEASE_ASSERT(noteLength <= frontend::MaxSrcNotesLength);
4509
4510 size_t nullLength = ComputeNotePadding(code.Length(), noteLength);
4511
4512 // Allocate ImmutableScriptData
4513 js::UniquePtr<ImmutableScriptData> data(ImmutableScriptData::new_(
4514 cx, code.Length(), noteLength + nullLength, resumeOffsets.Length(),
4515 scopeNotes.Length(), tryNotes.Length()));
4516 if (!data) {
4517 return data;
4518 }
4519
4520 // Initialize POD fields
4521 data->mainOffset = mainOffset;
4522 data->nfixed = nfixed;
4523 data->nslots = nslots;
4524 data->bodyScopeIndex = bodyScopeIndex;
4525 data->numICEntries = numICEntries;
4526
4527 if (isFunction) {
4528 data->funLength = funLength;
4529 }
4530
4531 // Initialize trailing arrays
4532 CopySpan(code, data->codeSpan());
4533 CopySpan(notes, data->notesSpan().To(noteLength));
4534 std::fill_n(data->notes() + noteLength, nullLength, SrcNote::terminator());
4535 CopySpan(resumeOffsets, data->resumeOffsets());
4536 CopySpan(scopeNotes, data->scopeNotes());
4537 CopySpan(tryNotes, data->tryNotes());
4538
4539 return data;
4540 }
4541
trace(JSTracer * trc)4542 void ScriptWarmUpData::trace(JSTracer* trc) {
4543 uintptr_t tag = data_ & TagMask;
4544 switch (tag) {
4545 case EnclosingScriptTag: {
4546 BaseScript* enclosingScript = toEnclosingScript();
4547 TraceManuallyBarrieredEdge(trc, &enclosingScript, "enclosingScript");
4548 setTaggedPtr<EnclosingScriptTag>(enclosingScript);
4549 break;
4550 }
4551
4552 case EnclosingScopeTag: {
4553 Scope* enclosingScope = toEnclosingScope();
4554 TraceManuallyBarrieredEdge(trc, &enclosingScope, "enclosingScope");
4555 setTaggedPtr<EnclosingScopeTag>(enclosingScope);
4556 break;
4557 }
4558
4559 case JitScriptTag: {
4560 toJitScript()->trace(trc);
4561 break;
4562 }
4563
4564 default: {
4565 MOZ_ASSERT(isWarmUpCount());
4566 break;
4567 }
4568 }
4569 }
4570
calculateLiveFixed(jsbytecode * pc)4571 size_t JSScript::calculateLiveFixed(jsbytecode* pc) {
4572 size_t nlivefixed = numAlwaysLiveFixedSlots();
4573
4574 if (nfixed() != nlivefixed) {
4575 Scope* scope = lookupScope(pc);
4576 if (scope) {
4577 scope = MaybeForwarded(scope);
4578 }
4579
4580 // Find the nearest LexicalScope in the same script.
4581 while (scope && scope->is<WithScope>()) {
4582 scope = scope->enclosing();
4583 if (scope) {
4584 scope = MaybeForwarded(scope);
4585 }
4586 }
4587
4588 if (scope) {
4589 if (scope->is<LexicalScope>()) {
4590 nlivefixed = scope->as<LexicalScope>().nextFrameSlot();
4591 } else if (scope->is<VarScope>()) {
4592 nlivefixed = scope->as<VarScope>().nextFrameSlot();
4593 } else if (scope->is<ClassBodyScope>()) {
4594 nlivefixed = scope->as<ClassBodyScope>().nextFrameSlot();
4595 }
4596 }
4597 }
4598
4599 MOZ_ASSERT(nlivefixed <= nfixed());
4600 MOZ_ASSERT(nlivefixed >= numAlwaysLiveFixedSlots());
4601
4602 return nlivefixed;
4603 }
4604
lookupScope(jsbytecode * pc) const4605 Scope* JSScript::lookupScope(jsbytecode* pc) const {
4606 MOZ_ASSERT(containsPC(pc));
4607
4608 size_t offset = pc - code();
4609
4610 auto notes = scopeNotes();
4611 Scope* scope = nullptr;
4612
4613 // Find the innermost block chain using a binary search.
4614 size_t bottom = 0;
4615 size_t top = notes.size();
4616
4617 while (bottom < top) {
4618 size_t mid = bottom + (top - bottom) / 2;
4619 const ScopeNote* note = ¬es[mid];
4620 if (note->start <= offset) {
4621 // Block scopes are ordered in the list by their starting offset, and
4622 // since blocks form a tree ones earlier in the list may cover the pc even
4623 // if later blocks end before the pc. This only happens when the earlier
4624 // block is a parent of the later block, so we need to check parents of
4625 // |mid| in the searched range for coverage.
4626 size_t check = mid;
4627 while (check >= bottom) {
4628 const ScopeNote* checkNote = ¬es[check];
4629 MOZ_ASSERT(checkNote->start <= offset);
4630 if (offset < checkNote->start + checkNote->length) {
4631 // We found a matching block chain but there may be inner ones
4632 // at a higher block chain index than mid. Continue the binary search.
4633 if (checkNote->index == ScopeNote::NoScopeIndex) {
4634 scope = nullptr;
4635 } else {
4636 scope = getScope(checkNote->index);
4637 }
4638 break;
4639 }
4640 if (checkNote->parent == UINT32_MAX) {
4641 break;
4642 }
4643 check = checkNote->parent;
4644 }
4645 bottom = mid + 1;
4646 } else {
4647 top = mid;
4648 }
4649 }
4650
4651 return scope;
4652 }
4653
innermostScope(jsbytecode * pc) const4654 Scope* JSScript::innermostScope(jsbytecode* pc) const {
4655 if (Scope* scope = lookupScope(pc)) {
4656 return scope;
4657 }
4658 return bodyScope();
4659 }
4660
SetFrameArgumentsObject(JSContext * cx,AbstractFramePtr frame,HandleScript script,JSObject * argsobj)4661 void js::SetFrameArgumentsObject(JSContext* cx, AbstractFramePtr frame,
4662 HandleScript script, JSObject* argsobj) {
4663 /*
4664 * If the arguments object was optimized out by scalar replacement,
4665 * we must recreate it when we bail out. Because 'arguments' may have
4666 * already been overwritten, we must check to see if the slot already
4667 * contains a value.
4668 */
4669
4670 Rooted<BindingIter> bi(cx, BindingIter(script));
4671 while (bi && bi.name() != cx->names().arguments) {
4672 bi++;
4673 }
4674 if (!bi) {
4675 return;
4676 }
4677
4678 if (bi.location().kind() == BindingLocation::Kind::Environment) {
4679 #ifdef DEBUG
4680 /*
4681 * If |arguments| lives in the call object, we should not have
4682 * optimized it. Scan the script to find the slot in the call
4683 * object that |arguments| is assigned to and verify that it
4684 * already exists.
4685 */
4686 jsbytecode* pc = script->code();
4687 while (JSOp(*pc) != JSOp::Arguments) {
4688 pc += GetBytecodeLength(pc);
4689 }
4690 pc += JSOpLength_Arguments;
4691 MOZ_ASSERT(JSOp(*pc) == JSOp::SetAliasedVar);
4692
4693 EnvironmentObject& env = frame.callObj().as<EnvironmentObject>();
4694 MOZ_ASSERT(!env.aliasedBinding(bi).isMagic(JS_OPTIMIZED_OUT));
4695 #endif
4696 return;
4697 }
4698
4699 MOZ_ASSERT(bi.location().kind() == BindingLocation::Kind::Frame);
4700 uint32_t frameSlot = bi.location().slot();
4701 if (frame.unaliasedLocal(frameSlot).isMagic(JS_OPTIMIZED_OUT)) {
4702 frame.unaliasedLocal(frameSlot) = ObjectValue(*argsobj);
4703 }
4704 }
4705
formalIsAliased(unsigned argSlot)4706 bool JSScript::formalIsAliased(unsigned argSlot) {
4707 if (functionHasParameterExprs()) {
4708 return false;
4709 }
4710
4711 for (PositionalFormalParameterIter fi(this); fi; fi++) {
4712 if (fi.argumentSlot() == argSlot) {
4713 return fi.closedOver();
4714 }
4715 }
4716 MOZ_CRASH("Argument slot not found");
4717 }
4718
4719 // Returns true if any formal argument is mapped by the arguments
4720 // object, but lives in the call object.
anyFormalIsForwarded()4721 bool JSScript::anyFormalIsForwarded() {
4722 if (!argsObjAliasesFormals()) {
4723 return false;
4724 }
4725
4726 for (PositionalFormalParameterIter fi(this); fi; fi++) {
4727 if (fi.closedOver()) {
4728 return true;
4729 }
4730 }
4731 return false;
4732 }
4733
formalLivesInArgumentsObject(unsigned argSlot)4734 bool JSScript::formalLivesInArgumentsObject(unsigned argSlot) {
4735 return argsObjAliasesFormals() && !formalIsAliased(argSlot);
4736 }
4737
4738 /* static */
New(JSContext * cx,HandleObject functionOrGlobal,HandleScriptSourceObject sourceObject,const SourceExtent & extent,uint32_t immutableFlags)4739 BaseScript* BaseScript::New(JSContext* cx, HandleObject functionOrGlobal,
4740 HandleScriptSourceObject sourceObject,
4741 const SourceExtent& extent,
4742 uint32_t immutableFlags) {
4743 void* script = Allocate<BaseScript>(cx);
4744 if (!script) {
4745 return nullptr;
4746 }
4747
4748 #ifndef JS_CODEGEN_NONE
4749 uint8_t* stubEntry = cx->runtime()->jitRuntime()->interpreterStub().value;
4750 #else
4751 uint8_t* stubEntry = nullptr;
4752 #endif
4753
4754 return new (script) BaseScript(stubEntry, functionOrGlobal, sourceObject,
4755 extent, immutableFlags);
4756 }
4757
4758 /* static */
CreateRawLazy(JSContext * cx,uint32_t ngcthings,HandleFunction fun,HandleScriptSourceObject sourceObject,const SourceExtent & extent,uint32_t immutableFlags)4759 BaseScript* BaseScript::CreateRawLazy(JSContext* cx, uint32_t ngcthings,
4760 HandleFunction fun,
4761 HandleScriptSourceObject sourceObject,
4762 const SourceExtent& extent,
4763 uint32_t immutableFlags) {
4764 cx->check(fun);
4765
4766 BaseScript* lazy = New(cx, fun, sourceObject, extent, immutableFlags);
4767 if (!lazy) {
4768 return nullptr;
4769 }
4770
4771 // Allocate a PrivateScriptData if it will not be empty. Lazy class
4772 // constructors that use member initializers also need PrivateScriptData for
4773 // field data.
4774 if (ngcthings || lazy->useMemberInitializers()) {
4775 UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings));
4776 if (!data) {
4777 return nullptr;
4778 }
4779 lazy->swapData(data);
4780 MOZ_ASSERT(!data);
4781 }
4782
4783 return lazy;
4784 }
4785
updateJitCodeRaw(JSRuntime * rt)4786 void JSScript::updateJitCodeRaw(JSRuntime* rt) {
4787 MOZ_ASSERT(rt);
4788 if (hasBaselineScript() && baselineScript()->hasPendingIonCompileTask()) {
4789 MOZ_ASSERT(!isIonCompilingOffThread());
4790 setJitCodeRaw(rt->jitRuntime()->lazyLinkStub().value);
4791 } else if (hasIonScript()) {
4792 jit::IonScript* ion = ionScript();
4793 setJitCodeRaw(ion->method()->raw());
4794 } else if (hasBaselineScript()) {
4795 setJitCodeRaw(baselineScript()->method()->raw());
4796 } else if (hasJitScript() && js::jit::IsBaselineInterpreterEnabled()) {
4797 setJitCodeRaw(rt->jitRuntime()->baselineInterpreter().codeRaw());
4798 } else {
4799 setJitCodeRaw(rt->jitRuntime()->interpreterStub().value);
4800 }
4801 MOZ_ASSERT(jitCodeRaw());
4802 }
4803
hasLoops()4804 bool JSScript::hasLoops() {
4805 for (const TryNote& tn : trynotes()) {
4806 if (tn.isLoop()) {
4807 return true;
4808 }
4809 }
4810 return false;
4811 }
4812
mayReadFrameArgsDirectly()4813 bool JSScript::mayReadFrameArgsDirectly() {
4814 return needsArgsObj() || hasRest();
4815 }
4816
resetWarmUpCounterToDelayIonCompilation()4817 void JSScript::resetWarmUpCounterToDelayIonCompilation() {
4818 // Reset the warm-up count only if it's greater than the BaselineCompiler
4819 // threshold. We do this to ensure this has no effect on Baseline compilation
4820 // because we don't want scripts to get stuck in the (Baseline) interpreter in
4821 // pathological cases.
4822
4823 if (getWarmUpCount() > jit::JitOptions.baselineJitWarmUpThreshold) {
4824 incWarmUpResetCounter();
4825 uint32_t newCount = jit::JitOptions.baselineJitWarmUpThreshold;
4826 if (warmUpData_.isWarmUpCount()) {
4827 warmUpData_.resetWarmUpCount(newCount);
4828 } else {
4829 warmUpData_.toJitScript()->resetWarmUpCount(newCount);
4830 }
4831 }
4832 }
4833
createAllocSite()4834 gc::AllocSite* JSScript::createAllocSite() {
4835 return jitScript()->createAllocSite(this);
4836 }
4837
holdScript(JS::HandleFunction fun)4838 void JSScript::AutoDelazify::holdScript(JS::HandleFunction fun) {
4839 if (fun) {
4840 if (fun->realm()->isSelfHostingRealm()) {
4841 // The self-hosting realm is shared across runtimes, so we can't use
4842 // JSAutoRealm: it could cause races. Functions in the self-hosting
4843 // realm will never be lazy, so we can safely assume we don't have
4844 // to delazify.
4845 script_ = fun->nonLazyScript();
4846 } else {
4847 JSAutoRealm ar(cx_, fun);
4848 script_ = JSFunction::getOrCreateScript(cx_, fun);
4849 if (script_) {
4850 oldAllowRelazify_ = script_->allowRelazify();
4851 script_->clearAllowRelazify();
4852 }
4853 }
4854 }
4855 }
4856
dropScript()4857 void JSScript::AutoDelazify::dropScript() {
4858 // Don't touch script_ if it's in the self-hosting realm, see the comment
4859 // in holdScript.
4860 if (script_ && !script_->realm()->isSelfHostingRealm()) {
4861 script_->setAllowRelazify(oldAllowRelazify_);
4862 }
4863 script_ = nullptr;
4864 }
4865
size(mozilla::MallocSizeOf mallocSizeOf) const4866 JS::ubi::Base::Size JS::ubi::Concrete<BaseScript>::size(
4867 mozilla::MallocSizeOf mallocSizeOf) const {
4868 BaseScript* base = &get();
4869
4870 Size size = gc::Arena::thingSize(base->getAllocKind());
4871 size += base->sizeOfExcludingThis(mallocSizeOf);
4872
4873 // Include any JIT data if it exists.
4874 if (base->hasJitScript()) {
4875 JSScript* script = base->asJSScript();
4876
4877 size_t jitScriptSize = 0;
4878 size_t fallbackStubSize = 0;
4879 script->addSizeOfJitScript(mallocSizeOf, &jitScriptSize, &fallbackStubSize);
4880 size += jitScriptSize;
4881 size += fallbackStubSize;
4882
4883 size_t baselineSize = 0;
4884 jit::AddSizeOfBaselineData(script, mallocSizeOf, &baselineSize);
4885 size += baselineSize;
4886
4887 size += jit::SizeOfIonData(script, mallocSizeOf);
4888 }
4889
4890 MOZ_ASSERT(size > 0);
4891 return size;
4892 }
4893
scriptFilename() const4894 const char* JS::ubi::Concrete<BaseScript>::scriptFilename() const {
4895 return get().filename();
4896 }
4897