| // Copyright 2017 the V8 project authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef V8_WASM_STREAMING_DECODER_H_ | |
| #define V8_WASM_STREAMING_DECODER_H_ | |
| #include <vector> | |
| #include "src/isolate.h" | |
| #include "src/wasm/module-decoder.h" | |
| #include "src/wasm/wasm-objects.h" | |
| namespace v8 { | |
| namespace internal { | |
| namespace wasm { | |
| // This class is an interface for the StreamingDecoder to start the processing | |
| // of the incoming module bytes. | |
| class V8_EXPORT_PRIVATE StreamingProcessor { | |
| public: | |
| virtual ~StreamingProcessor() = default; | |
| // Process the first 8 bytes of a WebAssembly module. Returns true if the | |
| // processing finished successfully and the decoding should continue. | |
| virtual bool ProcessModuleHeader(Vector<const uint8_t> bytes, | |
| uint32_t offset) = 0; | |
| // Process all sections but the code section. Returns true if the processing | |
| // finished successfully and the decoding should continue. | |
| virtual bool ProcessSection(SectionCode section_code, | |
| Vector<const uint8_t> bytes, uint32_t offset) = 0; | |
| // Process the start of the code section. Returns true if the processing | |
| // finished successfully and the decoding should continue. | |
| virtual bool ProcessCodeSectionHeader(size_t num_functions, | |
| uint32_t offset) = 0; | |
| // Process a function body. Returns true if the processing finished | |
| // successfully and the decoding should continue. | |
| virtual bool ProcessFunctionBody(Vector<const uint8_t> bytes, | |
| uint32_t offset) = 0; | |
| // Report the end of a chunk. | |
| virtual void OnFinishedChunk() = 0; | |
| // Report the end of the stream. If the stream was successful, all | |
| // received bytes are passed by parameter. If there has been an error, an | |
| // empty array is passed. | |
| virtual void OnFinishedStream(OwnedVector<uint8_t> bytes) = 0; | |
| // Report an error detected in the StreamingDecoder. | |
| virtual void OnError(DecodeResult result) = 0; | |
| // Report the abortion of the stream. | |
| virtual void OnAbort() = 0; | |
| }; | |
| // The StreamingDecoder takes a sequence of byte arrays, each received by a call | |
| // of {OnBytesReceived}, and extracts the bytes which belong to section payloads | |
| // and function bodies. | |
| class V8_EXPORT_PRIVATE StreamingDecoder { | |
| public: | |
| explicit StreamingDecoder(std::unique_ptr<StreamingProcessor> processor); | |
| // The buffer passed into OnBytesReceived is owned by the caller. | |
| void OnBytesReceived(Vector<const uint8_t> bytes); | |
| void Finish(); | |
| void Abort(); | |
| // Notify the StreamingDecoder that compilation ended and the | |
| // StreamingProcessor should not be called anymore. | |
| void NotifyCompilationEnded() { | |
| // We set {ok_} to false to turn all future calls to the StreamingDecoder | |
| // into no-ops. | |
| ok_ = false; | |
| } | |
| private: | |
| // TODO(ahaas): Put the whole private state of the StreamingDecoder into the | |
| // cc file (PIMPL design pattern). | |
| // The SectionBuffer is the data object for the content of a single section. | |
| // It stores all bytes of the section (including section id and section | |
| // length), and the offset where the actual payload starts. | |
| class SectionBuffer { | |
| public: | |
| // id: The section id. | |
| // payload_length: The length of the payload. | |
| // length_bytes: The section length, as it is encoded in the module bytes. | |
| SectionBuffer(uint32_t module_offset, uint8_t id, size_t payload_length, | |
| Vector<const uint8_t> length_bytes) | |
| : // ID + length + payload | |
| module_offset_(module_offset), | |
| length_(1 + length_bytes.length() + payload_length), | |
| bytes_(new uint8_t[length_]), | |
| payload_offset_(1 + length_bytes.length()) { | |
| bytes_[0] = id; | |
| memcpy(bytes_.get() + 1, &length_bytes.first(), length_bytes.length()); | |
| } | |
| SectionCode section_code() const { | |
| return static_cast<SectionCode>(bytes_[0]); | |
| } | |
| uint32_t module_offset() const { return module_offset_; } | |
| uint8_t* bytes() const { return bytes_.get(); } | |
| size_t length() const { return length_; } | |
| size_t payload_offset() const { return payload_offset_; } | |
| size_t payload_length() const { return length_ - payload_offset_; } | |
| Vector<const uint8_t> payload() const { | |
| return Vector<const uint8_t>(bytes() + payload_offset(), | |
| payload_length()); | |
| } | |
| private: | |
| uint32_t module_offset_; | |
| size_t length_; | |
| std::unique_ptr<uint8_t[]> bytes_; | |
| size_t payload_offset_; | |
| }; | |
| // The decoding of a stream of wasm module bytes is organized in states. Each | |
| // state provides a buffer to store the bytes required for the current state, | |
| // information on how many bytes have already been received, how many bytes | |
| // are needed, and a {Next} function which starts the next state once all | |
| // bytes of the current state were received. | |
| // | |
| // The states change according to the following state diagram: | |
| // | |
| // Start | |
| // | | |
| // | | |
| // v | |
| // DecodeModuleHeader | |
| // | _________________________________________ | |
| // | | | | |
| // v v | | |
| // DecodeSectionID --> DecodeSectionLength --> DecodeSectionPayload | |
| // A | | |
| // | | (if the section id == code) | |
| // | v | |
| // | DecodeNumberOfFunctions -- > DecodeFunctionLength | |
| // | A | | |
| // | | | | |
| // | (after all functions were read) | v | |
| // ------------------------------------- DecodeFunctionBody | |
| // | |
| class DecodingState { | |
| public: | |
| virtual ~DecodingState() = default; | |
| // Reads the bytes for the current state and returns the number of read | |
| // bytes. | |
| virtual size_t ReadBytes(StreamingDecoder* streaming, | |
| Vector<const uint8_t> bytes); | |
| // Returns the next state of the streaming decoding. | |
| virtual std::unique_ptr<DecodingState> Next( | |
| StreamingDecoder* streaming) = 0; | |
| // The number of bytes to be received. | |
| virtual size_t size() const = 0; | |
| // The buffer to store the received bytes. | |
| virtual uint8_t* buffer() = 0; | |
| // The number of bytes which were already received. | |
| size_t offset() const { return offset_; } | |
| void set_offset(size_t value) { offset_ = value; } | |
| // The number of bytes which are still needed. | |
| size_t remaining() const { return size() - offset(); } | |
| bool is_finished() const { return offset() == size(); } | |
| // A flag to indicate if finishing the streaming decoder is allowed without | |
| // error. | |
| virtual bool is_finishing_allowed() const { return false; } | |
| private: | |
| size_t offset_ = 0; | |
| }; | |
| // Forward declarations of the concrete states. This is needed so that they | |
| // can access private members of the StreamingDecoder. | |
| class DecodeVarInt32; | |
| class DecodeModuleHeader; | |
| class DecodeSectionID; | |
| class DecodeSectionLength; | |
| class DecodeSectionPayload; | |
| class DecodeNumberOfFunctions; | |
| class DecodeFunctionLength; | |
| class DecodeFunctionBody; | |
| // Creates a buffer for the next section of the module. | |
| SectionBuffer* CreateNewBuffer(uint32_t module_offset, uint8_t id, | |
| size_t length, | |
| Vector<const uint8_t> length_bytes) { | |
| // Check the order of sections. Unknown sections can appear at any position. | |
| if (id != kUnknownSectionCode) { | |
| if (id < next_section_id_) { | |
| Error("Unexpected section"); | |
| return nullptr; | |
| } | |
| next_section_id_ = id + 1; | |
| } | |
| section_buffers_.emplace_back( | |
| new SectionBuffer(module_offset, id, length, length_bytes)); | |
| return section_buffers_.back().get(); | |
| } | |
| std::unique_ptr<DecodingState> Error(DecodeResult result) { | |
| if (ok_) processor_->OnError(std::move(result)); | |
| ok_ = false; | |
| return std::unique_ptr<DecodingState>(nullptr); | |
| } | |
| std::unique_ptr<DecodingState> Error(std::string message) { | |
| DecodeResult result(nullptr); | |
| result.error(module_offset_ - 1, std::move(message)); | |
| return Error(std::move(result)); | |
| } | |
| void ProcessModuleHeader() { | |
| if (!ok_) return; | |
| if (!processor_->ProcessModuleHeader( | |
| Vector<const uint8_t>(state_->buffer(), | |
| static_cast<int>(state_->size())), | |
| 0)) { | |
| ok_ = false; | |
| } | |
| } | |
| void ProcessSection(SectionBuffer* buffer) { | |
| if (!ok_) return; | |
| if (!processor_->ProcessSection( | |
| buffer->section_code(), buffer->payload(), | |
| buffer->module_offset() + | |
| static_cast<uint32_t>(buffer->payload_offset()))) { | |
| ok_ = false; | |
| } | |
| } | |
| void StartCodeSection(size_t num_functions) { | |
| if (!ok_) return; | |
| // The offset passed to {ProcessCodeSectionHeader} is an error offset and | |
| // not the start offset of a buffer. Therefore we need the -1 here. | |
| if (!processor_->ProcessCodeSectionHeader(num_functions, | |
| module_offset() - 1)) { | |
| ok_ = false; | |
| } | |
| } | |
| void ProcessFunctionBody(Vector<const uint8_t> bytes, | |
| uint32_t module_offset) { | |
| if (!ok_) return; | |
| if (!processor_->ProcessFunctionBody(bytes, module_offset)) ok_ = false; | |
| } | |
| bool ok() const { return ok_; } | |
| uint32_t module_offset() const { return module_offset_; } | |
| std::unique_ptr<StreamingProcessor> processor_; | |
| bool ok_ = true; | |
| std::unique_ptr<DecodingState> state_; | |
| std::vector<std::unique_ptr<SectionBuffer>> section_buffers_; | |
| uint32_t module_offset_ = 0; | |
| size_t total_size_ = 0; | |
| uint8_t next_section_id_ = kFirstSectionInModule; | |
| DISALLOW_COPY_AND_ASSIGN(StreamingDecoder); | |
| }; | |
| } // namespace wasm | |
| } // namespace internal | |
| } // namespace v8 | |
| #endif // V8_WASM_STREAMING_DECODER_H_ |