WasmBinary.cpp (9352B)
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 * 4 * Copyright 2021 Mozilla Foundation 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 #include "wasm/WasmBinary.h" 20 21 #include "js/Printf.h" 22 #include "wasm/WasmMetadata.h" 23 24 using namespace js; 25 using namespace js::wasm; 26 27 // Decoder implementation. 28 29 bool Decoder::failf(const char* msg, ...) { 30 va_list ap; 31 va_start(ap, msg); 32 UniqueChars str(JS_vsmprintf(msg, ap)); 33 va_end(ap); 34 if (!str) { 35 return false; 36 } 37 38 return fail(str.get()); 39 } 40 41 void Decoder::warnf(const char* msg, ...) { 42 if (!warnings_) { 43 return; 44 } 45 46 va_list ap; 47 va_start(ap, msg); 48 UniqueChars str(JS_vsmprintf(msg, ap)); 49 va_end(ap); 50 if (!str) { 51 return; 52 } 53 54 (void)warnings_->append(std::move(str)); 55 } 56 57 bool Decoder::fail(size_t errorOffset, const char* msg) { 58 MOZ_ASSERT(error_); 59 UniqueChars strWithOffset(JS_smprintf("at offset %zu: %s", errorOffset, msg)); 60 if (!strWithOffset) { 61 return false; 62 } 63 64 *error_ = std::move(strWithOffset); 65 return false; 66 } 67 68 bool Decoder::readSectionHeader(uint8_t* id, BytecodeRange* range) { 69 if (!readFixedU8(id)) { 70 return false; 71 } 72 73 uint32_t size; 74 if (!readVarU32(&size)) { 75 return false; 76 } 77 78 return BytecodeRange::fromStartAndSize(currentOffset(), size, range); 79 } 80 81 bool Decoder::startSection(SectionId id, CodeMetadata* codeMeta, 82 MaybeBytecodeRange* range, const char* sectionName) { 83 MOZ_ASSERT(!*range); 84 85 // Record state at beginning of section to allow rewinding to this point 86 // if, after skipping through several custom sections, we don't find the 87 // section 'id'. 88 const uint8_t* const initialCur = cur_; 89 const size_t initialCustomSectionsLength = 90 codeMeta->customSectionRanges.length(); 91 92 // Maintain a pointer to the current section that gets updated as custom 93 // sections are skipped. 94 const uint8_t* currentSectionStart = cur_; 95 96 // Only start a section with 'id', skipping any custom sections before it. 97 98 uint8_t idValue; 99 if (!readFixedU8(&idValue)) { 100 goto rewind; 101 } 102 103 while (idValue != uint8_t(id)) { 104 if (idValue != uint8_t(SectionId::Custom)) { 105 goto rewind; 106 } 107 108 // Rewind to the beginning of the current section since this is what 109 // skipCustomSection() assumes. 110 cur_ = currentSectionStart; 111 if (!skipCustomSection(codeMeta)) { 112 return false; 113 } 114 115 // Having successfully skipped a custom section, consider the next 116 // section. 117 currentSectionStart = cur_; 118 if (!readFixedU8(&idValue)) { 119 goto rewind; 120 } 121 } 122 123 // Don't check the size since the range of bytes being decoded might not 124 // contain the section body. (This is currently the case when streaming: the 125 // code section header is decoded with the module environment bytes, the 126 // body of the code section is streamed in separately.) 127 128 uint32_t size; 129 if (!readVarU32(&size)) { 130 goto fail; 131 } 132 133 range->emplace(); 134 if (!BytecodeRange::fromStartAndSize(currentOffset(), size, range->ptr())) { 135 goto fail; 136 } 137 return true; 138 139 rewind: 140 cur_ = initialCur; 141 codeMeta->customSectionRanges.shrinkTo(initialCustomSectionsLength); 142 return true; 143 144 fail: 145 return failf("failed to start %s section", sectionName); 146 } 147 148 bool Decoder::finishSection(const BytecodeRange& range, 149 const char* sectionName) { 150 if (range.end != currentOffset()) { 151 return failf("byte size mismatch in %s section", sectionName); 152 } 153 return true; 154 } 155 156 bool Decoder::startCustomSection(const char* expected, size_t expectedLength, 157 CodeMetadata* codeMeta, 158 MaybeBytecodeRange* range) { 159 // Record state at beginning of section to allow rewinding to this point 160 // if, after skipping through several custom sections, we don't find the 161 // section 'id'. 162 const uint8_t* const initialCur = cur_; 163 const size_t initialCustomSectionsLength = 164 codeMeta->customSectionRanges.length(); 165 166 while (true) { 167 // Try to start a custom section. If we can't, rewind to the beginning 168 // since we may have skipped several custom sections already looking for 169 // 'expected'. 170 if (!startSection(SectionId::Custom, codeMeta, range, "custom")) { 171 return false; 172 } 173 if (!*range) { 174 goto rewind; 175 } 176 177 if (bytesRemain() < (*range)->size()) { 178 goto fail; 179 } 180 181 uint32_t sectionNameSize; 182 if (!readVarU32(§ionNameSize) || sectionNameSize > bytesRemain()) { 183 goto fail; 184 } 185 186 // A custom section name must be valid UTF-8 187 if (!IsUtf8(AsChars(mozilla::Span(cur_, sectionNameSize)))) { 188 goto fail; 189 } 190 191 CustomSectionRange secRange; 192 secRange.name = BytecodeRange(currentOffset(), sectionNameSize); 193 // The payload starts after the name, and goes to the end of the custom 194 // section. 195 if (secRange.name.end > (*range)->end) { 196 goto fail; 197 } 198 secRange.payload.start = secRange.name.end; 199 secRange.payload.end = (*range)->end; 200 201 // Now that we have a valid custom section, record its offsets in the 202 // metadata which can be queried by the user via Module.customSections. 203 // Note: after an entry is appended, it may be popped if this loop or 204 // the loop in startSection needs to rewind. 205 if (!codeMeta->customSectionRanges.append(secRange)) { 206 return false; 207 } 208 209 // If this is the expected custom section, we're done. 210 if (!expected || (expectedLength == secRange.name.size() && 211 !memcmp(cur_, expected, secRange.name.size()))) { 212 cur_ += secRange.name.size(); 213 return true; 214 } 215 216 // Otherwise, blindly skip the custom section and keep looking. 217 skipAndFinishCustomSection(**range); 218 range->reset(); 219 } 220 MOZ_CRASH("unreachable"); 221 222 rewind: 223 cur_ = initialCur; 224 codeMeta->customSectionRanges.shrinkTo(initialCustomSectionsLength); 225 return true; 226 227 fail: 228 return fail("failed to start custom section"); 229 } 230 231 bool Decoder::finishCustomSection(const char* name, 232 const BytecodeRange& range) { 233 MOZ_ASSERT(cur_ >= beg_); 234 MOZ_ASSERT(cur_ <= end_); 235 236 if (error_ && *error_) { 237 warnf("in the '%s' custom section: %s", name, error_->get()); 238 skipAndFinishCustomSection(range); 239 return false; 240 } 241 242 uint32_t actualSize = currentOffset() - range.start; 243 if (range.size() != actualSize) { 244 if (actualSize < range.size()) { 245 warnf("in the '%s' custom section: %" PRIu32 " unconsumed bytes", name, 246 uint32_t(range.size() - actualSize)); 247 } else { 248 warnf("in the '%s' custom section: %" PRIu32 249 " bytes consumed past the end", 250 name, uint32_t(actualSize - range.size())); 251 } 252 skipAndFinishCustomSection(range); 253 return false; 254 } 255 256 // Nothing to do! (c.f. skipAndFinishCustomSection()) 257 return true; 258 } 259 260 void Decoder::skipAndFinishCustomSection(const BytecodeRange& range) { 261 MOZ_ASSERT(cur_ >= beg_); 262 MOZ_ASSERT(cur_ <= end_); 263 cur_ = beg_ + (range.end - offsetInModule_); 264 MOZ_ASSERT(cur_ <= end_); 265 clearError(); 266 } 267 268 bool Decoder::skipCustomSection(CodeMetadata* codeMeta) { 269 MaybeBytecodeRange range; 270 if (!startCustomSection(nullptr, 0, codeMeta, &range)) { 271 return false; 272 } 273 if (!range) { 274 return fail("expected custom section"); 275 } 276 277 skipAndFinishCustomSection(*range); 278 return true; 279 } 280 281 bool Decoder::startNameSubsection(NameType nameType, 282 mozilla::Maybe<uint32_t>* endOffset) { 283 MOZ_ASSERT(!*endOffset); 284 285 const uint8_t* const initialPosition = cur_; 286 287 uint8_t nameTypeValue; 288 if (!readFixedU8(&nameTypeValue)) { 289 goto rewind; 290 } 291 292 if (nameTypeValue != uint8_t(nameType)) { 293 goto rewind; 294 } 295 296 uint32_t payloadLength; 297 if (!readVarU32(&payloadLength) || payloadLength > bytesRemain()) { 298 return fail("bad name subsection payload length"); 299 } 300 301 *endOffset = mozilla::Some(currentOffset() + payloadLength); 302 return true; 303 304 rewind: 305 cur_ = initialPosition; 306 return true; 307 } 308 309 bool Decoder::finishNameSubsection(uint32_t endOffset) { 310 uint32_t actual = currentOffset(); 311 if (endOffset != actual) { 312 return failf("bad name subsection length (endOffset: %" PRIu32 313 ", actual: %" PRIu32 ")", 314 endOffset, actual); 315 } 316 317 return true; 318 } 319 320 bool Decoder::skipNameSubsection() { 321 uint8_t nameTypeValue; 322 if (!readFixedU8(&nameTypeValue)) { 323 return fail("unable to read name subsection id"); 324 } 325 326 switch (nameTypeValue) { 327 case uint8_t(NameType::Module): 328 case uint8_t(NameType::Function): 329 return fail("out of order name subsections"); 330 default: 331 break; 332 } 333 334 uint32_t payloadLength; 335 if (!readVarU32(&payloadLength) || !readBytes(payloadLength)) { 336 return fail("bad name subsection payload length"); 337 } 338 339 return true; 340 }