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 #ifndef vm_Xdr_h
8 #define vm_Xdr_h
9 
10 #include "mozilla/MaybeOneOf.h"
11 #include "mozilla/Utf8.h"
12 
13 #include <type_traits>
14 
15 #include "jsapi.h"
16 #include "jsfriendapi.h"
17 #include "NamespaceImports.h"
18 
19 #include "js/CompileOptions.h"
20 #include "js/Transcoding.h"
21 #include "js/TypeDecls.h"
22 #include "vm/JSAtom.h"
23 
24 namespace js {
25 
26 struct SourceExtent;
27 
28 namespace frontend {
29 struct CompilationStencil;
30 struct ExtensibleCompilationStencil;
31 struct CompilationStencilMerger;
32 struct CompilationInput;
33 }  // namespace frontend
34 
35 class LifoAlloc;
36 
37 enum XDRMode { XDR_ENCODE, XDR_DECODE };
38 
39 template <typename T>
40 using XDRResultT = mozilla::Result<T, JS::TranscodeResult>;
41 using XDRResult = XDRResultT<mozilla::Ok>;
42 
43 class XDRBufferBase {
44  public:
45   explicit XDRBufferBase(JSContext* cx, size_t cursor = 0)
context_(cx)46       : context_(cx), cursor_(cursor) {}
47 
cx()48   JSContext* cx() const { return context_; }
49 
cursor()50   size_t cursor() const { return cursor_; }
51 
52  protected:
53   JSContext* const context_;
54   size_t cursor_;
55 };
56 
57 template <XDRMode mode>
58 class XDRBuffer;
59 
60 template <>
61 class XDRBuffer<XDR_ENCODE> : public XDRBufferBase {
62  public:
63   XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
XDRBufferBase(cx,cursor)64       : XDRBufferBase(cx, cursor), buffer_(buffer) {}
65 
write(size_t n)66   uint8_t* write(size_t n) {
67     MOZ_ASSERT(n != 0);
68     if (!buffer_.growByUninitialized(n)) {
69       ReportOutOfMemory(cx());
70       return nullptr;
71     }
72     uint8_t* ptr = &buffer_[cursor_];
73     cursor_ += n;
74     return ptr;
75   }
76 
align32()77   bool align32() {
78     size_t extra = cursor_ % 4;
79     if (extra) {
80       size_t padding = 4 - extra;
81       if (!buffer_.appendN(0, padding)) {
82         ReportOutOfMemory(cx());
83         return false;
84       }
85       cursor_ += padding;
86     }
87     return true;
88   }
89 
90 #ifdef DEBUG
isAligned32()91   bool isAligned32() { return cursor_ % 4 == 0; }
92 #endif
93 
read(size_t n)94   const uint8_t* read(size_t n) {
95     MOZ_CRASH("Should never read in encode mode");
96     return nullptr;
97   }
98 
peek(size_t n)99   const uint8_t* peek(size_t n) {
100     MOZ_CRASH("Should never read in encode mode");
101     return nullptr;
102   }
103 
104  private:
105   JS::TranscodeBuffer& buffer_;
106 };
107 
108 template <>
109 class XDRBuffer<XDR_DECODE> : public XDRBufferBase {
110  public:
XDRBuffer(JSContext * cx,const JS::TranscodeRange & range)111   XDRBuffer(JSContext* cx, const JS::TranscodeRange& range)
112       : XDRBufferBase(cx), buffer_(range) {}
113 
114   XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
XDRBufferBase(cx,cursor)115       : XDRBufferBase(cx, cursor), buffer_(buffer.begin(), buffer.length()) {}
116 
align32()117   bool align32() {
118     size_t extra = cursor_ % 4;
119     if (extra) {
120       size_t padding = 4 - extra;
121       cursor_ += padding;
122 
123       // Don't let buggy code read past our buffer
124       if (cursor_ > buffer_.length()) {
125         return false;
126       }
127     }
128     return true;
129   }
130 
131 #ifdef DEBUG
isAligned32()132   bool isAligned32() { return cursor_ % 4 == 0; }
133 #endif
134 
read(size_t n)135   const uint8_t* read(size_t n) {
136     MOZ_ASSERT(cursor_ < buffer_.length());
137     const uint8_t* ptr = &buffer_[cursor_];
138     cursor_ += n;
139 
140     // Don't let buggy code read past our buffer
141     if (cursor_ > buffer_.length()) {
142       return nullptr;
143     }
144 
145     return ptr;
146   }
147 
peek(size_t n)148   const uint8_t* peek(size_t n) {
149     MOZ_ASSERT(cursor_ < buffer_.length());
150     const uint8_t* ptr = &buffer_[cursor_];
151 
152     // Don't let buggy code read past our buffer
153     if (cursor_ + n > buffer_.length()) {
154       return nullptr;
155     }
156 
157     return ptr;
158   }
159 
write(size_t n)160   uint8_t* write(size_t n) {
161     MOZ_CRASH("Should never write in decode mode");
162     return nullptr;
163   }
164 
165  private:
166   const JS::TranscodeRange buffer_;
167 };
168 
169 template <typename CharT>
170 using XDRTranscodeString =
171     mozilla::MaybeOneOf<const CharT*, js::UniquePtr<CharT[], JS::FreePolicy>>;
172 
173 class XDRCoderBase {
174  private:
175 #ifdef DEBUG
176   JS::TranscodeResult resultCode_;
177 #endif
178 
179  protected:
XDRCoderBase()180   XDRCoderBase()
181 #ifdef DEBUG
182       : resultCode_(JS::TranscodeResult::Ok)
183 #endif
184   {
185   }
186 
187  public:
188 #ifdef DEBUG
189   // Record logical failures of XDR.
resultCode()190   JS::TranscodeResult resultCode() const { return resultCode_; }
setResultCode(JS::TranscodeResult code)191   void setResultCode(JS::TranscodeResult code) {
192     MOZ_ASSERT(resultCode() == JS::TranscodeResult::Ok);
193     resultCode_ = code;
194   }
195   bool validateResultCode(JSContext* cx, JS::TranscodeResult code) const;
196 #endif
197 };
198 
199 /*
200  * XDR serialization state.  All data is encoded in native endian, except
201  * bytecode.
202  */
203 template <XDRMode mode>
204 class XDRState : public XDRCoderBase {
205  protected:
206   XDRBuffer<mode> mainBuf;
207   XDRBuffer<mode>* buf;
208 
209  public:
210   XDRState(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
mainBuf(cx,buffer,cursor)211       : mainBuf(cx, buffer, cursor), buf(&mainBuf) {}
212 
213   template <typename RangeType>
XDRState(JSContext * cx,const RangeType & range)214   XDRState(JSContext* cx, const RangeType& range)
215       : mainBuf(cx, range), buf(&mainBuf) {}
216 
217   // No default copy constructor or copying assignment, because |buf|
218   // is an internal pointer.
219   XDRState(const XDRState&) = delete;
220   XDRState& operator=(const XDRState&) = delete;
221 
222   virtual ~XDRState() = default;
223 
cx()224   JSContext* cx() const { return mainBuf.cx(); }
225 
isMultiDecode()226   virtual bool isMultiDecode() const { return false; }
227 
hasOptions()228   virtual bool hasOptions() const { return false; }
options()229   virtual const JS::ReadOnlyCompileOptions& options() {
230     MOZ_CRASH("does not have options");
231   }
hasScriptSourceObjectOut()232   virtual bool hasScriptSourceObjectOut() const { return false; }
scriptSourceObjectOut()233   virtual ScriptSourceObject** scriptSourceObjectOut() {
234     MOZ_CRASH("does not have scriptSourceObjectOut.");
235   }
236 
237   template <typename T = mozilla::Ok>
fail(JS::TranscodeResult code)238   XDRResultT<T> fail(JS::TranscodeResult code) {
239 #ifdef DEBUG
240     MOZ_ASSERT(code != JS::TranscodeResult::Ok);
241     MOZ_ASSERT(validateResultCode(cx(), code));
242     setResultCode(code);
243 #endif
244     return mozilla::Err(code);
245   }
246 
align32()247   XDRResult align32() {
248     if (!buf->align32()) {
249       return fail(JS::TranscodeResult::Throw);
250     }
251     return Ok();
252   }
253 
254 #ifdef DEBUG
isAligned32()255   bool isAligned32() { return buf->isAligned32(); }
256 #endif
257 
readData(const uint8_t ** pptr,size_t length)258   XDRResult readData(const uint8_t** pptr, size_t length) {
259     const uint8_t* ptr = buf->read(length);
260     if (!ptr) {
261       return fail(JS::TranscodeResult::Failure_BadDecode);
262     }
263     *pptr = ptr;
264     return Ok();
265   }
266 
267   // Peek the `sizeof(T)` bytes and return the pointer to `*pptr`.
268   // The caller is responsible for aligning the buffer by calling `align32`.
269   template <typename T>
peekData(const T ** pptr)270   XDRResult peekData(const T** pptr) {
271     static_assert(alignof(T) <= 4);
272     MOZ_ASSERT(isAligned32());
273     const uint8_t* ptr = buf->peek(sizeof(T));
274     if (!ptr) {
275       return fail(JS::TranscodeResult::Failure_BadDecode);
276     }
277     *pptr = reinterpret_cast<const T*>(ptr);
278     return Ok();
279   }
280 
281   // Peek uint32_t data.
peekUint32(uint32_t * n)282   XDRResult peekUint32(uint32_t* n) {
283     MOZ_ASSERT(mode == XDR_DECODE);
284     const uint8_t* ptr = buf->peek(sizeof(*n));
285     if (!ptr) {
286       return fail(JS::TranscodeResult::Failure_BadDecode);
287     }
288     *n = *reinterpret_cast<const uint32_t*>(ptr);
289     return Ok();
290   }
291 
codeUint8(uint8_t * n)292   XDRResult codeUint8(uint8_t* n) {
293     if (mode == XDR_ENCODE) {
294       uint8_t* ptr = buf->write(sizeof(*n));
295       if (!ptr) {
296         return fail(JS::TranscodeResult::Throw);
297       }
298       *ptr = *n;
299     } else {
300       const uint8_t* ptr = buf->read(sizeof(*n));
301       if (!ptr) {
302         return fail(JS::TranscodeResult::Failure_BadDecode);
303       }
304       *n = *ptr;
305     }
306     return Ok();
307   }
308 
309  private:
310   template <typename T>
codeUintImpl(T * n)311   XDRResult codeUintImpl(T* n) {
312     if (mode == XDR_ENCODE) {
313       uint8_t* ptr = buf->write(sizeof(T));
314       if (!ptr) {
315         return fail(JS::TranscodeResult::Throw);
316       }
317       memcpy(ptr, n, sizeof(T));
318     } else {
319       const uint8_t* ptr = buf->read(sizeof(T));
320       if (!ptr) {
321         return fail(JS::TranscodeResult::Failure_BadDecode);
322       }
323       memcpy(n, ptr, sizeof(T));
324     }
325     return Ok();
326   }
327 
328  public:
codeUint16(uint16_t * n)329   XDRResult codeUint16(uint16_t* n) { return codeUintImpl(n); }
330 
codeUint32(uint32_t * n)331   XDRResult codeUint32(uint32_t* n) { return codeUintImpl(n); }
332 
codeUint64(uint64_t * n)333   XDRResult codeUint64(uint64_t* n) { return codeUintImpl(n); }
334 
335   /*
336    * Use SFINAE to refuse any specialization which is not an enum.  Uses of
337    * this function do not have to specialize the type of the enumerated field
338    * as C++ will extract the parameterized from the argument list.
339    */
340   template <typename T>
341   XDRResult codeEnum32(T* val, std::enable_if_t<std::is_enum_v<T>>* = nullptr) {
342     // Mix the enumeration value with a random magic number, such that a
343     // corruption with a low-ranged value (like 0) is less likely to cause a
344     // miss-interpretation of the XDR content and instead cause a failure.
345     const uint32_t MAGIC = 0x21AB218C;
346     uint32_t tmp;
347     if (mode == XDR_ENCODE) {
348       tmp = uint32_t(*val) ^ MAGIC;
349     }
350     MOZ_TRY(codeUint32(&tmp));
351     if (mode == XDR_DECODE) {
352       *val = T(tmp ^ MAGIC);
353     }
354     return Ok();
355   }
356 
codeDouble(double * dp)357   XDRResult codeDouble(double* dp) {
358     union DoublePun {
359       double d;
360       uint64_t u;
361     } pun;
362     if (mode == XDR_ENCODE) {
363       pun.d = *dp;
364     }
365     MOZ_TRY(codeUint64(&pun.u));
366     if (mode == XDR_DECODE) {
367       *dp = pun.d;
368     }
369     return Ok();
370   }
371 
codeMarker(uint32_t magic)372   XDRResult codeMarker(uint32_t magic) {
373     uint32_t actual = magic;
374     MOZ_TRY(codeUint32(&actual));
375     if (actual != magic) {
376       // Fail in debug, but only soft-fail in release
377       MOZ_ASSERT(false, "Bad XDR marker");
378       return fail(JS::TranscodeResult::Failure_BadDecode);
379     }
380     return Ok();
381   }
382 
codeBytes(void * bytes,size_t len)383   XDRResult codeBytes(void* bytes, size_t len) {
384     if (len == 0) {
385       return Ok();
386     }
387     if (mode == XDR_ENCODE) {
388       uint8_t* ptr = buf->write(len);
389       if (!ptr) {
390         return fail(JS::TranscodeResult::Throw);
391       }
392       memcpy(ptr, bytes, len);
393     } else {
394       const uint8_t* ptr = buf->read(len);
395       if (!ptr) {
396         return fail(JS::TranscodeResult::Failure_BadDecode);
397       }
398       memcpy(bytes, ptr, len);
399     }
400     return Ok();
401   }
402 
403   // While encoding, code the given data to the buffer.
404   // While decoding, borrow the buffer and return it to `*data`.
405   //
406   // The data can have extra bytes after `sizeof(T)`, and the caller should
407   // provide the entire data length as `length`.
408   //
409   // The caller is responsible for aligning the buffer by calling `align32`.
410   template <typename T>
borrowedData(T ** data,uint32_t length)411   XDRResult borrowedData(T** data, uint32_t length) {
412     static_assert(alignof(T) <= 4);
413     MOZ_ASSERT(isAligned32());
414 
415     if (mode == XDR_ENCODE) {
416       MOZ_TRY(codeBytes(*data, length));
417     } else {
418       const uint8_t* cursor = nullptr;
419       MOZ_TRY(readData(&cursor, length));
420       *data = reinterpret_cast<T*>(const_cast<uint8_t*>(cursor));
421     }
422     return Ok();
423   }
424 
425   // Prefer using a variant below that is encoding aware.
426   XDRResult codeChars(char* chars, size_t nchars);
427 
428   XDRResult codeChars(JS::Latin1Char* chars, size_t nchars);
429   XDRResult codeChars(mozilla::Utf8Unit* units, size_t nchars);
430   XDRResult codeChars(char16_t* chars, size_t nchars);
431 
432   // Transcode null-terminated strings. When decoding, a new buffer is
433   // allocated and ownership is returned to caller.
434   //
435   // NOTE: Throws if string longer than JSString::MAX_LENGTH.
436   XDRResult codeCharsZ(XDRTranscodeString<char>& buffer);
437   XDRResult codeCharsZ(XDRTranscodeString<char16_t>& buffer);
438 
439   XDRResult codeModuleObject(MutableHandleModuleObject modp);
440   XDRResult codeScript(MutableHandleScript scriptp);
441 };
442 
443 using XDREncoder = XDRState<XDR_ENCODE>;
444 using XDRDecoderBase = XDRState<XDR_DECODE>;
445 
446 class XDRDecoder : public XDRDecoderBase {
447  public:
448   XDRDecoder(JSContext* cx, const JS::ReadOnlyCompileOptions* options,
449              JS::TranscodeBuffer& buffer, size_t cursor = 0)
XDRDecoderBase(cx,buffer,cursor)450       : XDRDecoderBase(cx, buffer, cursor), options_(options) {
451     MOZ_ASSERT(options);
452   }
453 
454   template <typename RangeType>
XDRDecoder(JSContext * cx,const JS::ReadOnlyCompileOptions * options,const RangeType & range)455   XDRDecoder(JSContext* cx, const JS::ReadOnlyCompileOptions* options,
456              const RangeType& range)
457       : XDRDecoderBase(cx, range), options_(options) {
458     MOZ_ASSERT(options);
459   }
460 
hasOptions()461   bool hasOptions() const override { return true; }
options()462   const JS::ReadOnlyCompileOptions& options() override { return *options_; }
463 
464  private:
465   const JS::ReadOnlyCompileOptions* options_;
466 };
467 
468 class XDROffThreadDecoder : public XDRDecoder {
469   ScriptSourceObject** sourceObjectOut_;
470   bool isMultiDecode_;
471 
472  public:
473   enum class Type {
474     Single,
475     Multi,
476   };
477 
478   // Note, when providing an JSContext, where isJSContext is false,
479   // then the initialization of the ScriptSourceObject would remain
480   // incomplete. Thus, the sourceObjectOut must be used to finish the
481   // initialization with ScriptSourceObject::initFromOptions after the
482   // decoding.
483   //
484   // When providing a sourceObjectOut pointer, you have to ensure that it is
485   // marked by the GC to avoid dangling pointers.
XDROffThreadDecoder(JSContext * cx,const JS::ReadOnlyCompileOptions * options,Type type,ScriptSourceObject ** sourceObjectOut,const JS::TranscodeRange & range)486   XDROffThreadDecoder(JSContext* cx, const JS::ReadOnlyCompileOptions* options,
487                       Type type, ScriptSourceObject** sourceObjectOut,
488                       const JS::TranscodeRange& range)
489       : XDRDecoder(cx, options, range),
490         sourceObjectOut_(sourceObjectOut),
491         isMultiDecode_(type == Type::Multi) {
492     MOZ_ASSERT(sourceObjectOut);
493     MOZ_ASSERT(*sourceObjectOut == nullptr);
494   }
495 
isMultiDecode()496   bool isMultiDecode() const override { return isMultiDecode_; }
497 
hasScriptSourceObjectOut()498   bool hasScriptSourceObjectOut() const override { return true; }
scriptSourceObjectOut()499   ScriptSourceObject** scriptSourceObjectOut() override {
500     return sourceObjectOut_;
501   }
502 };
503 
504 /*
505  * The structure of the Stencil XDR buffer is:
506  *
507  * 1. Header
508  *   a. Version
509  *   b. ScriptSource
510  *   d. Alignment padding
511  * 2. Stencil
512  *   a. CompilationStencil
513  */
514 
515 /*
516  * The stencil decoder accepts `range` as input.
517  *
518  * The decoded stencils are outputted to the default-initialized
519  * `stencil` parameter of `codeStencil` method.
520  *
521  * The decoded stencils borrow the input `buffer`/`range`, and the consumer
522  * has to keep the buffer alive while the decoded stencils are alive.
523  */
524 class XDRStencilDecoder : public XDRDecoderBase {
525  public:
XDRStencilDecoder(JSContext * cx,JS::TranscodeBuffer & buffer,size_t cursor)526   XDRStencilDecoder(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor)
527       : XDRDecoderBase(cx, buffer, cursor) {
528     MOZ_ASSERT(JS::IsTranscodingBytecodeAligned(buffer.begin()));
529     MOZ_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(cursor));
530   }
531 
XDRStencilDecoder(JSContext * cx,const JS::TranscodeRange & range)532   XDRStencilDecoder(JSContext* cx, const JS::TranscodeRange& range)
533       : XDRDecoderBase(cx, range) {
534     MOZ_ASSERT(JS::IsTranscodingBytecodeAligned(range.begin().get()));
535   }
536 
537   XDRResult codeStencil(frontend::CompilationInput& input,
538                         frontend::CompilationStencil& stencil);
539 
hasOptions()540   bool hasOptions() const override { return !!options_; }
options()541   const JS::ReadOnlyCompileOptions& options() override {
542     MOZ_ASSERT(options_);
543     return *options_;
544   }
545 
546  private:
547   const JS::ReadOnlyCompileOptions* options_ = nullptr;
548 };
549 
550 class XDRStencilEncoder : public XDREncoder {
551  public:
XDRStencilEncoder(JSContext * cx,JS::TranscodeBuffer & buffer)552   XDRStencilEncoder(JSContext* cx, JS::TranscodeBuffer& buffer)
553       : XDREncoder(cx, buffer, buffer.length()) {
554     // NOTE: If buffer is empty, buffer.begin() doesn't point valid buffer.
555     MOZ_ASSERT_IF(!buffer.empty(),
556                   JS::IsTranscodingBytecodeAligned(buffer.begin()));
557     MOZ_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(buffer.length()));
558   }
559 
560  private:
561   XDRResult codeStencil(const JS::ReadOnlyCompileOptions* options,
562                         const RefPtr<ScriptSource>& source,
563                         const frontend::CompilationStencil& stencil);
564 
565  public:
566   XDRResult codeStencil(const frontend::CompilationInput& input,
567                         const frontend::CompilationStencil& stencil);
568 
569   XDRResult codeStencil(const RefPtr<ScriptSource>& source,
570                         const frontend::CompilationStencil& stencil);
571 };
572 
573 class XDRIncrementalStencilEncoder {
574   frontend::CompilationStencilMerger* merger_ = nullptr;
575 
576  public:
577   XDRIncrementalStencilEncoder() = default;
578 
579   ~XDRIncrementalStencilEncoder();
580 
581   XDRResult linearize(JSContext* cx, JS::TranscodeBuffer& buffer,
582                       js::ScriptSource* ss);
583 
584   XDRResult setInitial(
585       JSContext* cx, const JS::ReadOnlyCompileOptions& options,
586       UniquePtr<frontend::ExtensibleCompilationStencil>&& initial);
587   XDRResult addDelazification(
588       JSContext* cx, const frontend::CompilationStencil& delazification);
589 };
590 
591 template <XDRMode mode>
592 XDRResult XDRAtomOrNull(XDRState<mode>* xdr, js::MutableHandleAtom atomp);
593 
594 template <XDRMode mode>
595 XDRResult XDRAtom(XDRState<mode>* xdr, js::MutableHandleAtom atomp);
596 
597 } /* namespace js */
598 
599 #endif /* vm_Xdr_h */
600