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 #include "vm/Xdr.h"
8 
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/ScopeExit.h"
11 #include "mozilla/Utf8.h"
12 
13 #include <algorithm>  // std::transform
14 #include <string.h>
15 #include <type_traits>  // std::is_same
16 #include <utility>      // std::move
17 
18 #include "jsapi.h"
19 
20 #include "builtin/ModuleObject.h"
21 #include "debugger/DebugAPI.h"
22 #include "frontend/CompilationStencil.h"  // frontend::{CompilationStencil, ExtensibleCompilationStencil, CompilationStencilMerger, BorrowingCompilationStencil}
23 #include "frontend/StencilXdr.h"          // frontend::StencilXDR
24 #include "js/BuildId.h"                   // JS::BuildIdCharVector
25 #include "vm/JSContext.h"
26 #include "vm/JSScript.h"
27 #include "vm/SharedStencil.h"  // js::SourceExtent
28 #include "vm/TraceLogging.h"
29 
30 using namespace js;
31 
32 using mozilla::ArrayEqual;
33 using mozilla::Utf8Unit;
34 
35 #ifdef DEBUG
validateResultCode(JSContext * cx,JS::TranscodeResult code) const36 bool XDRCoderBase::validateResultCode(JSContext* cx,
37                                       JS::TranscodeResult code) const {
38   // NOTE: This function is called to verify that we do not have a pending
39   // exception on the JSContext at the same time as a TranscodeResult failure.
40   if (cx->isHelperThreadContext()) {
41     return true;
42   }
43   return cx->isExceptionPending() == bool(code == JS::TranscodeResult::Throw);
44 }
45 #endif
46 
47 template <XDRMode mode>
codeChars(char * chars,size_t nchars)48 XDRResult XDRState<mode>::codeChars(char* chars, size_t nchars) {
49   return codeBytes(chars, nchars);
50 }
51 
52 template <XDRMode mode>
codeChars(Latin1Char * chars,size_t nchars)53 XDRResult XDRState<mode>::codeChars(Latin1Char* chars, size_t nchars) {
54   static_assert(sizeof(Latin1Char) == 1,
55                 "Latin1Char must be 1 byte for nchars below to be the "
56                 "proper count of bytes");
57   static_assert(std::is_same_v<Latin1Char, unsigned char>,
58                 "Latin1Char must be unsigned char to C++-safely reinterpret "
59                 "the bytes generically copied below as Latin1Char");
60   return codeBytes(chars, nchars);
61 }
62 
63 template <XDRMode mode>
codeChars(Utf8Unit * units,size_t count)64 XDRResult XDRState<mode>::codeChars(Utf8Unit* units, size_t count) {
65   if (count == 0) {
66     return Ok();
67   }
68 
69   if (mode == XDR_ENCODE) {
70     uint8_t* ptr = buf->write(count);
71     if (!ptr) {
72       return fail(JS::TranscodeResult::Throw);
73     }
74 
75     std::transform(units, units + count, ptr,
76                    [](const Utf8Unit& unit) { return unit.toUint8(); });
77   } else {
78     const uint8_t* ptr = buf->read(count);
79     if (!ptr) {
80       return fail(JS::TranscodeResult::Failure_BadDecode);
81     }
82 
83     std::transform(ptr, ptr + count, units,
84                    [](const uint8_t& value) { return Utf8Unit(value); });
85   }
86 
87   return Ok();
88 }
89 
90 template <XDRMode mode>
codeChars(char16_t * chars,size_t nchars)91 XDRResult XDRState<mode>::codeChars(char16_t* chars, size_t nchars) {
92   if (nchars == 0) {
93     return Ok();
94   }
95 
96   size_t nbytes = nchars * sizeof(char16_t);
97   if (mode == XDR_ENCODE) {
98     uint8_t* ptr = buf->write(nbytes);
99     if (!ptr) {
100       return fail(JS::TranscodeResult::Throw);
101     }
102 
103     // |mozilla::NativeEndian| correctly handles writing into unaligned |ptr|.
104     mozilla::NativeEndian::copyAndSwapToLittleEndian(ptr, chars, nchars);
105   } else {
106     const uint8_t* ptr = buf->read(nbytes);
107     if (!ptr) {
108       return fail(JS::TranscodeResult::Failure_BadDecode);
109     }
110 
111     // |mozilla::NativeEndian| correctly handles reading from unaligned |ptr|.
112     mozilla::NativeEndian::copyAndSwapFromLittleEndian(chars, ptr, nchars);
113   }
114   return Ok();
115 }
116 
117 template <XDRMode mode, typename CharT>
XDRCodeCharsZ(XDRState<mode> * xdr,XDRTranscodeString<CharT> & buffer)118 static XDRResult XDRCodeCharsZ(XDRState<mode>* xdr,
119                                XDRTranscodeString<CharT>& buffer) {
120   MOZ_ASSERT_IF(mode == XDR_ENCODE, !buffer.empty());
121   MOZ_ASSERT_IF(mode == XDR_DECODE, buffer.empty());
122 
123   using OwnedString = js::UniquePtr<CharT[], JS::FreePolicy>;
124   OwnedString owned;
125 
126   static_assert(JSString::MAX_LENGTH <= INT32_MAX,
127                 "String length must fit in int32_t");
128 
129   uint32_t length = 0;
130   CharT* chars = nullptr;
131 
132   if (mode == XDR_ENCODE) {
133     chars = const_cast<CharT*>(buffer.template ref<const CharT*>());
134 
135     // Set a reasonable limit on string length.
136     size_t lengthSizeT = std::char_traits<CharT>::length(chars);
137     if (lengthSizeT > JSString::MAX_LENGTH) {
138       ReportAllocationOverflow(xdr->cx());
139       return xdr->fail(JS::TranscodeResult::Throw);
140     }
141     length = static_cast<uint32_t>(lengthSizeT);
142   }
143   MOZ_TRY(xdr->codeUint32(&length));
144 
145   if (mode == XDR_DECODE) {
146     owned = xdr->cx()->template make_pod_array<CharT>(length + 1);
147     if (!owned) {
148       return xdr->fail(JS::TranscodeResult::Throw);
149     }
150     chars = owned.get();
151   }
152 
153   MOZ_TRY(xdr->codeChars(chars, length));
154   if (mode == XDR_DECODE) {
155     // Null-terminate and transfer ownership to caller.
156     owned[length] = '\0';
157     buffer.template construct<OwnedString>(std::move(owned));
158   }
159 
160   return Ok();
161 }
162 
163 template <XDRMode mode>
codeCharsZ(XDRTranscodeString<char> & buffer)164 XDRResult XDRState<mode>::codeCharsZ(XDRTranscodeString<char>& buffer) {
165   return XDRCodeCharsZ(this, buffer);
166 }
167 
168 template <XDRMode mode>
codeCharsZ(XDRTranscodeString<char16_t> & buffer)169 XDRResult XDRState<mode>::codeCharsZ(XDRTranscodeString<char16_t>& buffer) {
170   return XDRCodeCharsZ(this, buffer);
171 }
172 
173 enum class XDRFormatType : uint8_t {
174   UseOption,
175   JSScript,
176   Stencil,
177 };
178 
GetScriptTranscodingBuildId(XDRFormatType formatType,JS::BuildIdCharVector * buildId)179 static bool GetScriptTranscodingBuildId(XDRFormatType formatType,
180                                         JS::BuildIdCharVector* buildId) {
181   MOZ_ASSERT(buildId->empty());
182   MOZ_ASSERT(GetBuildId);
183 
184   if (!GetBuildId(buildId)) {
185     return false;
186   }
187 
188   // Note: the buildId returned here is also used for the bytecode cache MIME
189   // type so use plain ASCII characters.
190 
191   if (!buildId->reserve(buildId->length() + 4)) {
192     return false;
193   }
194 
195   buildId->infallibleAppend('-');
196 
197   // XDR depends on pointer size and endianness.
198   static_assert(sizeof(uintptr_t) == 4 || sizeof(uintptr_t) == 8);
199   buildId->infallibleAppend(sizeof(uintptr_t) == 4 ? '4' : '8');
200   buildId->infallibleAppend(MOZ_LITTLE_ENDIAN() ? 'l' : 'b');
201 
202   // '0': Stencil
203   // '1': JSScript.
204   char formatChar = '0';
205   switch (formatType) {
206     case XDRFormatType::UseOption:
207       // If off-thread parse global isn't used for single script decoding,
208       // we use stencil XDR instead of JSScript XDR.
209       formatChar = js::UseOffThreadParseGlobal() ? '1' : '0';
210       break;
211     case XDRFormatType::JSScript:
212       formatChar = '1';
213       break;
214     case XDRFormatType::Stencil:
215       formatChar = '0';
216       break;
217   }
218   buildId->infallibleAppend(formatChar);
219 
220   return true;
221 }
222 
GetScriptTranscodingBuildId(JS::BuildIdCharVector * buildId)223 JS_PUBLIC_API bool JS::GetScriptTranscodingBuildId(
224     JS::BuildIdCharVector* buildId) {
225   return GetScriptTranscodingBuildId(XDRFormatType::UseOption, buildId);
226 }
227 
228 template <XDRMode mode>
VersionCheck(XDRState<mode> * xdr,XDRFormatType formatType)229 static XDRResult VersionCheck(XDRState<mode>* xdr, XDRFormatType formatType) {
230   JS::BuildIdCharVector buildId;
231   if (!GetScriptTranscodingBuildId(formatType, &buildId)) {
232     ReportOutOfMemory(xdr->cx());
233     return xdr->fail(JS::TranscodeResult::Throw);
234   }
235   MOZ_ASSERT(!buildId.empty());
236 
237   uint32_t buildIdLength;
238   if (mode == XDR_ENCODE) {
239     buildIdLength = buildId.length();
240   }
241 
242   MOZ_TRY(xdr->codeUint32(&buildIdLength));
243 
244   if (mode == XDR_DECODE && buildIdLength != buildId.length()) {
245     return xdr->fail(JS::TranscodeResult::Failure_BadBuildId);
246   }
247 
248   if (mode == XDR_ENCODE) {
249     MOZ_TRY(xdr->codeBytes(buildId.begin(), buildIdLength));
250   } else {
251     JS::BuildIdCharVector decodedBuildId;
252 
253     // buildIdLength is already checked against the length of current
254     // buildId.
255     if (!decodedBuildId.resize(buildIdLength)) {
256       ReportOutOfMemory(xdr->cx());
257       return xdr->fail(JS::TranscodeResult::Throw);
258     }
259 
260     MOZ_TRY(xdr->codeBytes(decodedBuildId.begin(), buildIdLength));
261 
262     // We do not provide binary compatibility with older scripts.
263     if (!ArrayEqual(decodedBuildId.begin(), buildId.begin(), buildIdLength)) {
264       return xdr->fail(JS::TranscodeResult::Failure_BadBuildId);
265     }
266   }
267 
268   return Ok();
269 }
270 
271 template <XDRMode mode>
codeModuleObject(MutableHandleModuleObject modp)272 XDRResult XDRState<mode>::codeModuleObject(MutableHandleModuleObject modp) {
273 #ifdef DEBUG
274   auto sanityCheck = mozilla::MakeScopeExit(
275       [&] { MOZ_ASSERT(validateResultCode(cx(), resultCode())); });
276 #endif
277   if (mode == XDR_DECODE) {
278     modp.set(nullptr);
279   } else {
280     MOZ_ASSERT(modp->status() < MODULE_STATUS_LINKING);
281   }
282 
283   MOZ_TRY(XDRModuleObject(this, modp));
284   return Ok();
285 }
286 
287 template <XDRMode mode>
codeScript(MutableHandleScript scriptp)288 XDRResult XDRState<mode>::codeScript(MutableHandleScript scriptp) {
289   TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx());
290   TraceLoggerTextId event =
291       mode == XDR_DECODE ? TraceLogger_DecodeScript : TraceLogger_EncodeScript;
292   AutoTraceLog tl(logger, event);
293 
294 #ifdef DEBUG
295   auto sanityCheck = mozilla::MakeScopeExit(
296       [&] { MOZ_ASSERT(validateResultCode(cx(), resultCode())); });
297 #endif
298   auto guard = mozilla::MakeScopeExit([&] { scriptp.set(nullptr); });
299 
300   if (mode == XDR_DECODE) {
301     scriptp.set(nullptr);
302   } else {
303     MOZ_ASSERT(!scriptp->enclosingScope());
304   }
305 
306   MOZ_TRY(VersionCheck(this, XDRFormatType::JSScript));
307   MOZ_TRY(XDRScript(this, nullptr, nullptr, nullptr, scriptp));
308 
309   guard.release();
310   return Ok();
311 }
312 
313 template <XDRMode mode>
XDRStencilHeader(XDRState<mode> * xdr,const JS::ReadOnlyCompileOptions * maybeOptions,RefPtr<ScriptSource> & source)314 static XDRResult XDRStencilHeader(
315     XDRState<mode>* xdr, const JS::ReadOnlyCompileOptions* maybeOptions,
316     RefPtr<ScriptSource>& source) {
317   // The XDR-Stencil header is inserted at beginning of buffer, but it is
318   // computed at the end the incremental-encoding process.
319 
320   MOZ_TRY(VersionCheck(xdr, XDRFormatType::Stencil));
321   MOZ_TRY(ScriptSource::XDR(xdr, maybeOptions, source));
322 
323   return Ok();
324 }
325 
326 template class js::XDRState<XDR_ENCODE>;
327 template class js::XDRState<XDR_DECODE>;
328 
codeStencil(const JS::ReadOnlyCompileOptions * options,const RefPtr<ScriptSource> & source,const frontend::CompilationStencil & stencil)329 XDRResult XDRStencilEncoder::codeStencil(
330     const JS::ReadOnlyCompileOptions* options,
331     const RefPtr<ScriptSource>& source,
332     const frontend::CompilationStencil& stencil) {
333 #ifdef DEBUG
334   auto sanityCheck = mozilla::MakeScopeExit(
335       [&] { MOZ_ASSERT(validateResultCode(cx(), resultCode())); });
336 #endif
337 
338   MOZ_TRY(frontend::StencilXDR::checkCompilationStencil(this, stencil));
339 
340   MOZ_TRY(XDRStencilHeader(this, options,
341                            const_cast<RefPtr<ScriptSource>&>(source)));
342   MOZ_TRY(frontend::StencilXDR::codeCompilationStencil(
343       this, const_cast<frontend::CompilationStencil&>(stencil)));
344 
345   return Ok();
346 }
347 
codeStencil(const frontend::CompilationInput & input,const frontend::CompilationStencil & stencil)348 XDRResult XDRStencilEncoder::codeStencil(
349     const frontend::CompilationInput& input,
350     const frontend::CompilationStencil& stencil) {
351   return codeStencil(&input.options, stencil.source, stencil);
352 }
353 
codeStencil(const RefPtr<ScriptSource> & source,const frontend::CompilationStencil & stencil)354 XDRResult XDRStencilEncoder::codeStencil(
355     const RefPtr<ScriptSource>& source,
356     const frontend::CompilationStencil& stencil) {
357   return codeStencil(nullptr, source, stencil);
358 }
359 
~XDRIncrementalStencilEncoder()360 XDRIncrementalStencilEncoder::~XDRIncrementalStencilEncoder() {
361   if (merger_) {
362     js_delete(merger_);
363   }
364 }
365 
setInitial(JSContext * cx,const JS::ReadOnlyCompileOptions & options,UniquePtr<frontend::ExtensibleCompilationStencil> && initial)366 XDRResult XDRIncrementalStencilEncoder::setInitial(
367     JSContext* cx, const JS::ReadOnlyCompileOptions& options,
368     UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) {
369   MOZ_TRY(frontend::StencilXDR::checkCompilationStencil(*initial));
370 
371   merger_ = cx->new_<frontend::CompilationStencilMerger>();
372   if (!merger_) {
373     return mozilla::Err(JS::TranscodeResult::Throw);
374   }
375 
376   if (!merger_->setInitial(
377           cx, std::forward<UniquePtr<frontend::ExtensibleCompilationStencil>>(
378                   initial))) {
379     return mozilla::Err(JS::TranscodeResult::Throw);
380   }
381 
382   return Ok();
383 }
384 
addDelazification(JSContext * cx,const frontend::CompilationStencil & delazification)385 XDRResult XDRIncrementalStencilEncoder::addDelazification(
386     JSContext* cx, const frontend::CompilationStencil& delazification) {
387   if (!merger_->addDelazification(cx, delazification)) {
388     return mozilla::Err(JS::TranscodeResult::Throw);
389   }
390 
391   return Ok();
392 }
393 
linearize(JSContext * cx,JS::TranscodeBuffer & buffer,ScriptSource * ss)394 XDRResult XDRIncrementalStencilEncoder::linearize(JSContext* cx,
395                                                   JS::TranscodeBuffer& buffer,
396                                                   ScriptSource* ss) {
397   XDRStencilEncoder encoder(cx, buffer);
398   RefPtr<ScriptSource> source(ss);
399   {
400     frontend::BorrowingCompilationStencil borrowingStencil(
401         merger_->getResult());
402     MOZ_TRY(encoder.codeStencil(source, borrowingStencil));
403   }
404 
405   return Ok();
406 }
407 
codeStencil(frontend::CompilationInput & input,frontend::CompilationStencil & stencil)408 XDRResult XDRStencilDecoder::codeStencil(
409     frontend::CompilationInput& input, frontend::CompilationStencil& stencil) {
410 #ifdef DEBUG
411   auto sanityCheck = mozilla::MakeScopeExit(
412       [&] { MOZ_ASSERT(validateResultCode(cx(), resultCode())); });
413 #endif
414 
415   auto resetOptions = mozilla::MakeScopeExit([&] { options_ = nullptr; });
416   options_ = &input.options;
417 
418   MOZ_TRY(XDRStencilHeader(this, options_, stencil.source));
419   MOZ_TRY(frontend::StencilXDR::codeCompilationStencil(this, stencil));
420 
421   return Ok();
422 }
423