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, &regexp));
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 = &notes[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 = &notes[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