Xdr.h (12252B)
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/Assertions.h" // MOZ_ASSERT, MOZ_CRASH 11 #include "mozilla/MaybeOneOf.h" // mozilla::MaybeOneOf 12 #include "mozilla/Try.h" // MOZ_TRY 13 #include "mozilla/Utf8.h" // mozilla::Utf8Unit 14 15 #include <stddef.h> // size_t 16 #include <stdint.h> // uint8_t, uint16_t, uint32_t, uint64_t 17 #include <string.h> // memcpy 18 #include <type_traits> // std::enable_if_t 19 20 #include "js/AllocPolicy.h" // ReportOutOfMemory 21 #include "js/Transcoding.h" // JS::TranscodeResult, JS::TranscodeBuffer, JS::TranscodeRange, IsTranscodingBytecodeAligned, IsTranscodingBytecodeOffsetAligned 22 #include "js/TypeDecls.h" // JS::Latin1Char 23 #include "js/UniquePtr.h" // UniquePtr 24 #include "js/Utility.h" // JS::FreePolicy 25 26 struct JSContext; 27 28 namespace js { 29 30 enum XDRMode { XDR_ENCODE, XDR_DECODE }; 31 32 template <typename T> 33 using XDRResultT = mozilla::Result<T, JS::TranscodeResult>; 34 using XDRResult = XDRResultT<mozilla::Ok>; 35 36 class XDRBufferBase { 37 public: 38 explicit XDRBufferBase(FrontendContext* fc, size_t cursor = 0) 39 : fc_(fc), cursor_(cursor) {} 40 41 FrontendContext* fc() const { return fc_; } 42 43 size_t cursor() const { return cursor_; } 44 45 protected: 46 FrontendContext* const fc_; 47 size_t cursor_; 48 }; 49 50 template <XDRMode mode> 51 class XDRBuffer; 52 53 template <> 54 class XDRBuffer<XDR_ENCODE> : public XDRBufferBase { 55 public: 56 XDRBuffer(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0) 57 : XDRBufferBase(fc, cursor), buffer_(buffer) {} 58 59 uint8_t* write(size_t n) { 60 MOZ_ASSERT(n != 0); 61 if (!buffer_.growByUninitialized(n)) { 62 ReportOutOfMemory(fc()); 63 return nullptr; 64 } 65 uint8_t* ptr = &buffer_[cursor_]; 66 cursor_ += n; 67 return ptr; 68 } 69 70 bool align32() { 71 size_t extra = cursor_ % 4; 72 if (extra) { 73 size_t padding = 4 - extra; 74 if (!buffer_.appendN(0, padding)) { 75 ReportOutOfMemory(fc()); 76 return false; 77 } 78 cursor_ += padding; 79 } 80 return true; 81 } 82 83 bool isAligned32() { return cursor_ % 4 == 0; } 84 85 const uint8_t* read(size_t n) { 86 MOZ_CRASH("Should never read in encode mode"); 87 return nullptr; 88 } 89 90 const uint8_t* peek(size_t n) { 91 MOZ_CRASH("Should never read in encode mode"); 92 return nullptr; 93 } 94 95 uint8_t* bufferAt(size_t cursor) { 96 MOZ_ASSERT(cursor < buffer_.length()); 97 return &buffer_[cursor]; 98 } 99 100 private: 101 JS::TranscodeBuffer& buffer_; 102 }; 103 104 template <> 105 class XDRBuffer<XDR_DECODE> : public XDRBufferBase { 106 public: 107 XDRBuffer(FrontendContext* fc, const JS::TranscodeRange& range) 108 : XDRBufferBase(fc), buffer_(range) {} 109 110 // This isn't used by XDRStencilDecoder. 111 // Defined just for XDRState, shared with XDRStencilEncoder. 112 XDRBuffer(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0) 113 : XDRBufferBase(fc, cursor), buffer_(buffer.begin(), buffer.length()) {} 114 115 bool align32() { 116 size_t extra = cursor_ % 4; 117 if (extra) { 118 size_t padding = 4 - extra; 119 cursor_ += padding; 120 121 // Don't let buggy code read past our buffer 122 if (cursor_ > buffer_.length()) { 123 return false; 124 } 125 } 126 return true; 127 } 128 129 bool isAligned32() { return cursor_ % 4 == 0; } 130 131 const uint8_t* read(size_t n) { 132 MOZ_ASSERT(cursor_ < buffer_.length()); 133 const uint8_t* ptr = &buffer_[cursor_]; 134 cursor_ += n; 135 136 // Don't let buggy code read past our buffer 137 if (cursor_ > buffer_.length()) { 138 return nullptr; 139 } 140 141 return ptr; 142 } 143 144 const uint8_t* peek(size_t n) { 145 MOZ_ASSERT(cursor_ < buffer_.length()); 146 const uint8_t* ptr = &buffer_[cursor_]; 147 148 // Don't let buggy code read past our buffer 149 if (cursor_ + n > buffer_.length()) { 150 return nullptr; 151 } 152 153 return ptr; 154 } 155 156 uint8_t* write(size_t n) { 157 MOZ_CRASH("Should never write in decode mode"); 158 return nullptr; 159 } 160 161 private: 162 const JS::TranscodeRange buffer_; 163 }; 164 165 template <typename CharT> 166 using XDRTranscodeString = 167 mozilla::MaybeOneOf<const CharT*, js::UniquePtr<CharT[], JS::FreePolicy>>; 168 169 class XDRCoderBase { 170 private: 171 #ifdef DEBUG 172 JS::TranscodeResult resultCode_; 173 #endif 174 175 protected: 176 XDRCoderBase() 177 #ifdef DEBUG 178 : resultCode_(JS::TranscodeResult::Ok) 179 #endif 180 { 181 } 182 183 public: 184 #ifdef DEBUG 185 // Record logical failures of XDR. 186 JS::TranscodeResult resultCode() const { return resultCode_; } 187 void setResultCode(JS::TranscodeResult code) { 188 MOZ_ASSERT(resultCode() == JS::TranscodeResult::Ok); 189 resultCode_ = code; 190 } 191 bool validateResultCode(FrontendContext* fc, JS::TranscodeResult code) const; 192 #endif 193 }; 194 195 /* 196 * XDR serialization state. All data is encoded in native endian, except 197 * bytecode. 198 */ 199 template <XDRMode mode> 200 class XDRState : public XDRCoderBase { 201 protected: 202 XDRBuffer<mode> mainBuf; 203 XDRBuffer<mode>* buf; 204 205 public: 206 XDRState(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0) 207 : mainBuf(fc, buffer, cursor), buf(&mainBuf) {} 208 209 template <typename RangeType> 210 XDRState(FrontendContext* fc, const RangeType& range) 211 : mainBuf(fc, range), buf(&mainBuf) {} 212 213 // No default copy constructor or copying assignment, because |buf| 214 // is an internal pointer. 215 XDRState(const XDRState&) = delete; 216 XDRState& operator=(const XDRState&) = delete; 217 218 ~XDRState() = default; 219 220 FrontendContext* fc() const { return mainBuf.fc(); } 221 222 template <typename T = mozilla::Ok> 223 XDRResultT<T> fail(JS::TranscodeResult code) { 224 #ifdef DEBUG 225 MOZ_ASSERT(code != JS::TranscodeResult::Ok); 226 MOZ_ASSERT(validateResultCode(fc(), code)); 227 setResultCode(code); 228 #endif 229 return mozilla::Err(code); 230 } 231 232 XDRResult align32() { 233 if (!buf->align32()) { 234 return fail(JS::TranscodeResult::Throw); 235 } 236 return mozilla::Ok(); 237 } 238 239 bool isAligned32() { return buf->isAligned32(); } 240 241 XDRResult readData(const uint8_t** pptr, size_t length) { 242 const uint8_t* ptr = buf->read(length); 243 if (!ptr) { 244 return fail(JS::TranscodeResult::Failure_BadDecode); 245 } 246 *pptr = ptr; 247 return mozilla::Ok(); 248 } 249 250 // Peek the `sizeof(T)` bytes and return the pointer to `*pptr`. 251 // The caller is responsible for aligning the buffer by calling `align32`. 252 template <typename T> 253 XDRResult peekData(const T** pptr) { 254 static_assert(alignof(T) <= 4); 255 MOZ_ASSERT(isAligned32()); 256 const uint8_t* ptr = buf->peek(sizeof(T)); 257 if (!ptr) { 258 return fail(JS::TranscodeResult::Failure_BadDecode); 259 } 260 *pptr = reinterpret_cast<const T*>(ptr); 261 return mozilla::Ok(); 262 } 263 264 // Peek uint32_t data. 265 XDRResult peekUint32(uint32_t* n) { 266 MOZ_ASSERT(mode == XDR_DECODE); 267 const uint8_t* ptr = buf->peek(sizeof(*n)); 268 if (!ptr) { 269 return fail(JS::TranscodeResult::Failure_BadDecode); 270 } 271 *n = *reinterpret_cast<const uint32_t*>(ptr); 272 return mozilla::Ok(); 273 } 274 275 XDRResult codeUint8(uint8_t* n) { 276 if (mode == XDR_ENCODE) { 277 uint8_t* ptr = buf->write(sizeof(*n)); 278 if (!ptr) { 279 return fail(JS::TranscodeResult::Throw); 280 } 281 *ptr = *n; 282 } else { 283 const uint8_t* ptr = buf->read(sizeof(*n)); 284 if (!ptr) { 285 return fail(JS::TranscodeResult::Failure_BadDecode); 286 } 287 *n = *ptr; 288 } 289 return mozilla::Ok(); 290 } 291 292 private: 293 template <typename T> 294 XDRResult codeUintImpl(T* n) { 295 if (mode == XDR_ENCODE) { 296 uint8_t* ptr = buf->write(sizeof(T)); 297 if (!ptr) { 298 return fail(JS::TranscodeResult::Throw); 299 } 300 memcpy(ptr, n, sizeof(T)); 301 } else { 302 const uint8_t* ptr = buf->read(sizeof(T)); 303 if (!ptr) { 304 return fail(JS::TranscodeResult::Failure_BadDecode); 305 } 306 memcpy(n, ptr, sizeof(T)); 307 } 308 return mozilla::Ok(); 309 } 310 311 public: 312 XDRResult codeUint16(uint16_t* n) { return codeUintImpl(n); } 313 314 XDRResult codeUint32(uint32_t* n) { return codeUintImpl(n); } 315 316 XDRResult codeUint64(uint64_t* n) { return codeUintImpl(n); } 317 318 void codeUint32At(uint32_t* n, size_t cursor) { 319 if constexpr (mode == XDR_ENCODE) { 320 uint8_t* ptr = buf->bufferAt(cursor); 321 memcpy(ptr, n, sizeof(uint32_t)); 322 } else { 323 MOZ_CRASH("not supported."); 324 } 325 } 326 327 const uint8_t* bufferAt(size_t cursor) const { 328 if constexpr (mode == XDR_ENCODE) { 329 return buf->bufferAt(cursor); 330 } 331 332 MOZ_CRASH("not supported."); 333 } 334 335 XDRResult peekArray(size_t n, const uint8_t** p) { 336 if constexpr (mode == XDR_DECODE) { 337 const uint8_t* ptr = buf->peek(n); 338 if (!ptr) { 339 return fail(JS::TranscodeResult::Failure_BadDecode); 340 } 341 342 *p = ptr; 343 344 return mozilla::Ok(); 345 } 346 347 MOZ_CRASH("not supported."); 348 } 349 350 /* 351 * Use SFINAE to refuse any specialization which is not an enum. Uses of 352 * this function do not have to specialize the type of the enumerated field 353 * as C++ will extract the parameterized from the argument list. 354 */ 355 template <typename T> 356 XDRResult codeEnum32(T* val, std::enable_if_t<std::is_enum_v<T>>* = nullptr) { 357 // Mix the enumeration value with a random magic number, such that a 358 // corruption with a low-ranged value (like 0) is less likely to cause a 359 // miss-interpretation of the XDR content and instead cause a failure. 360 const uint32_t MAGIC = 0x21AB218C; 361 uint32_t tmp; 362 if (mode == XDR_ENCODE) { 363 tmp = uint32_t(*val) ^ MAGIC; 364 } 365 MOZ_TRY(codeUint32(&tmp)); 366 if (mode == XDR_DECODE) { 367 *val = T(tmp ^ MAGIC); 368 } 369 return mozilla::Ok(); 370 } 371 372 XDRResult codeDouble(double* dp) { 373 union DoublePun { 374 double d; 375 uint64_t u; 376 } pun; 377 if (mode == XDR_ENCODE) { 378 pun.d = *dp; 379 } 380 MOZ_TRY(codeUint64(&pun.u)); 381 if (mode == XDR_DECODE) { 382 *dp = pun.d; 383 } 384 return mozilla::Ok(); 385 } 386 387 XDRResult codeMarker(uint32_t magic) { 388 uint32_t actual = magic; 389 MOZ_TRY(codeUint32(&actual)); 390 if (actual != magic) { 391 // Fail in debug, but only soft-fail in release 392 MOZ_ASSERT(false, "Bad XDR marker"); 393 return fail(JS::TranscodeResult::Failure_BadDecode); 394 } 395 return mozilla::Ok(); 396 } 397 398 XDRResult codeBytes(void* bytes, size_t len) { 399 if (len == 0) { 400 return mozilla::Ok(); 401 } 402 if (mode == XDR_ENCODE) { 403 uint8_t* ptr = buf->write(len); 404 if (!ptr) { 405 return fail(JS::TranscodeResult::Throw); 406 } 407 memcpy(ptr, bytes, len); 408 } else { 409 const uint8_t* ptr = buf->read(len); 410 if (!ptr) { 411 return fail(JS::TranscodeResult::Failure_BadDecode); 412 } 413 memcpy(bytes, ptr, len); 414 } 415 return mozilla::Ok(); 416 } 417 418 // While encoding, code the given data to the buffer. 419 // While decoding, borrow the buffer and return it to `*data`. 420 // 421 // The data can have extra bytes after `sizeof(T)`, and the caller should 422 // provide the entire data length as `length`. 423 // 424 // The caller is responsible for aligning the buffer by calling `align32`. 425 template <typename T> 426 XDRResult borrowedData(T** data, uint32_t length) { 427 static_assert(alignof(T) <= 4); 428 MOZ_ASSERT(isAligned32()); 429 430 if (mode == XDR_ENCODE) { 431 MOZ_TRY(codeBytes(*data, length)); 432 } else { 433 const uint8_t* cursor = nullptr; 434 MOZ_TRY(readData(&cursor, length)); 435 *data = reinterpret_cast<T*>(const_cast<uint8_t*>(cursor)); 436 } 437 return mozilla::Ok(); 438 } 439 440 // Prefer using a variant below that is encoding aware. 441 XDRResult codeChars(char* chars, size_t nchars); 442 443 XDRResult codeChars(JS::Latin1Char* chars, size_t nchars); 444 XDRResult codeChars(mozilla::Utf8Unit* units, size_t nchars); 445 XDRResult codeChars(char16_t* chars, size_t nchars); 446 447 // Transcode null-terminated strings. When decoding, a new buffer is 448 // allocated and ownership is returned to caller. 449 // 450 // NOTE: Throws if string longer than JSString::MAX_LENGTH. 451 XDRResult codeCharsZ(XDRTranscodeString<char>& buffer); 452 XDRResult codeCharsZ(XDRTranscodeString<char16_t>& buffer); 453 }; 454 455 } /* namespace js */ 456 457 #endif /* vm_Xdr_h */