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