diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/wasm/WasmValidate.h | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/js/src/wasm/WasmValidate.h b/js/src/wasm/WasmValidate.h new file mode 100644 index 0000000000..8bb2720fad --- /dev/null +++ b/js/src/wasm/WasmValidate.h @@ -0,0 +1,308 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * + * Copyright 2016 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef wasm_validate_h +#define wasm_validate_h + +#include <type_traits> + +#include "js/Utility.h" +#include "js/WasmFeatures.h" + +#include "wasm/WasmBinary.h" +#include "wasm/WasmCompile.h" +#include "wasm/WasmCompileArgs.h" +#include "wasm/WasmModuleTypes.h" +#include "wasm/WasmProcess.h" +#include "wasm/WasmTypeDef.h" + +namespace js { +namespace wasm { + +using mozilla::Some; + +// ModuleEnvironment contains all the state necessary to process or render +// functions, and all of the state necessary to validate all aspects of the +// functions. +// +// A ModuleEnvironment is created by decoding all the sections before the wasm +// code section and then used immutably during. When compiling a module using a +// ModuleGenerator, the ModuleEnvironment holds state shared between the +// ModuleGenerator thread and background compile threads. All the threads +// are given a read-only view of the ModuleEnvironment, thus preventing race +// conditions. + +struct ModuleEnvironment { + // Constant parameters for the entire compilation: + const ModuleKind kind; + const FeatureArgs features; + + // Module fields decoded from the module environment (or initialized while + // validating an asm.js module) and immutable during compilation: + Maybe<uint32_t> dataCount; + Maybe<MemoryDesc> memory; + MutableTypeContext types; + FuncDescVector funcs; + uint32_t numFuncImports; + GlobalDescVector globals; + TagDescVector tags; + TableDescVector tables; + Uint32Vector asmJSSigToTableIndex; + ImportVector imports; + ExportVector exports; + Maybe<uint32_t> startFuncIndex; + ElemSegmentVector elemSegments; + MaybeSectionRange codeSection; + + // The start offset of the FuncImportInstanceData[] section of the instance + // data. There is one entry for every imported function. + uint32_t funcImportsOffsetStart; + // The start offset of the TypeDefInstanceData[] section of the instance + // data. There is one entry for every type. + uint32_t typeDefsOffsetStart; + // The start offset of the TableInstanceData[] section of the instance data. + // There is one entry for every table. + uint32_t tablesOffsetStart; + // The start offset of the tag section of the instance data. There is one + // entry for every tag. + uint32_t tagsOffsetStart; + + // Fields decoded as part of the wasm module tail: + DataSegmentEnvVector dataSegments; + CustomSectionEnvVector customSections; + Maybe<uint32_t> nameCustomSectionIndex; + Maybe<Name> moduleName; + NameVector funcNames; + + explicit ModuleEnvironment(FeatureArgs features, + ModuleKind kind = ModuleKind::Wasm) + : kind(kind), + features(features), + memory(Nothing()), + numFuncImports(0), + funcImportsOffsetStart(UINT32_MAX), + typeDefsOffsetStart(UINT32_MAX), + tablesOffsetStart(UINT32_MAX), + tagsOffsetStart(UINT32_MAX) {} + + [[nodiscard]] bool init() { + types = js_new<TypeContext>(features); + return types; + } + + size_t numTables() const { return tables.length(); } + size_t numTypes() const { return types->length(); } + size_t numFuncs() const { return funcs.length(); } + size_t numFuncDefs() const { return funcs.length() - numFuncImports; } + + bool funcIsImport(uint32_t funcIndex) const { + return funcIndex < numFuncImports; + } + +#define WASM_FEATURE(NAME, SHORT_NAME, ...) \ + bool SHORT_NAME##Enabled() const { return features.SHORT_NAME; } + JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE) +#undef WASM_FEATURE + Shareable sharedMemoryEnabled() const { return features.sharedMemory; } + bool hugeMemoryEnabled() const { + return !isAsmJS() && usesMemory() && + IsHugeMemoryEnabled(memory->indexType()); + } + bool simdAvailable() const { return features.simd; } + bool intrinsicsEnabled() const { return features.intrinsics; } + + bool isAsmJS() const { return kind == ModuleKind::AsmJS; } + + bool usesMemory() const { return memory.isSome(); } + bool usesSharedMemory() const { + return memory.isSome() && memory->isShared(); + } + + void declareFuncExported(uint32_t funcIndex, bool eager, bool canRefFunc) { + FuncFlags flags = funcs[funcIndex].flags; + + // Set the `Exported` flag, if not set. + flags = FuncFlags(uint8_t(flags) | uint8_t(FuncFlags::Exported)); + + // Merge in the `Eager` and `CanRefFunc` flags, if they're set. Be sure + // to not unset them if they've already been set. + if (eager) { + flags = FuncFlags(uint8_t(flags) | uint8_t(FuncFlags::Eager)); + } + if (canRefFunc) { + flags = FuncFlags(uint8_t(flags) | uint8_t(FuncFlags::CanRefFunc)); + } + + funcs[funcIndex].flags = flags; + } + + uint32_t offsetOfFuncImportInstanceData(uint32_t funcIndex) const { + MOZ_ASSERT(funcIndex < numFuncImports); + return funcImportsOffsetStart + funcIndex * sizeof(FuncImportInstanceData); + } + + uint32_t offsetOfTypeDefInstanceData(uint32_t typeIndex) const { + MOZ_ASSERT(typeIndex < types->length()); + return typeDefsOffsetStart + typeIndex * sizeof(TypeDefInstanceData); + } + + uint32_t offsetOfTypeDef(uint32_t typeIndex) const { + return offsetOfTypeDefInstanceData(typeIndex) + + offsetof(TypeDefInstanceData, typeDef); + } + uint32_t offsetOfSuperTypeVector(uint32_t typeIndex) const { + return offsetOfTypeDefInstanceData(typeIndex) + + offsetof(TypeDefInstanceData, superTypeVector); + } + + uint32_t offsetOfTableInstanceData(uint32_t tableIndex) const { + MOZ_ASSERT(tableIndex < tables.length()); + return tablesOffsetStart + tableIndex * sizeof(TableInstanceData); + } + + uint32_t offsetOfTagInstanceData(uint32_t tagIndex) const { + MOZ_ASSERT(tagIndex < tags.length()); + return tagsOffsetStart + tagIndex * sizeof(TagInstanceData); + } +}; + +// ElemSegmentFlags provides methods for decoding and encoding the flags field +// of an element segment. This is needed as the flags field has a non-trivial +// encoding that is effectively split into independent `kind` and `payload` +// enums. +class ElemSegmentFlags { + enum class Flags : uint32_t { + Passive = 0x1, + WithIndexOrDeclared = 0x2, + ElemExpression = 0x4, + // Below this line are convenient combinations of flags + KindMask = Passive | WithIndexOrDeclared, + PayloadMask = ElemExpression, + AllFlags = Passive | WithIndexOrDeclared | ElemExpression, + }; + uint32_t encoded_; + + explicit ElemSegmentFlags(uint32_t encoded) : encoded_(encoded) {} + + public: + ElemSegmentFlags(ElemSegmentKind kind, ElemSegmentPayload payload) { + encoded_ = uint32_t(kind) | uint32_t(payload); + } + + static Maybe<ElemSegmentFlags> construct(uint32_t encoded) { + if (encoded > uint32_t(Flags::AllFlags)) { + return Nothing(); + } + return Some(ElemSegmentFlags(encoded)); + } + + uint32_t encoded() const { return encoded_; } + + ElemSegmentKind kind() const { + return static_cast<ElemSegmentKind>(encoded_ & uint32_t(Flags::KindMask)); + } + ElemSegmentPayload payload() const { + return static_cast<ElemSegmentPayload>(encoded_ & + uint32_t(Flags::PayloadMask)); + } +}; + +// OpIter specialized for validation. + +class NothingVector { + Nothing unused_; + + public: + bool resize(size_t length) { return true; } + Nothing& operator[](size_t) { return unused_; } + Nothing& back() { return unused_; } + size_t length() const { return 0; } + bool append(Nothing& nothing) { return true; } +}; + +struct ValidatingPolicy { + using Value = Nothing; + using ValueVector = NothingVector; + using ControlItem = Nothing; +}; + +template <typename Policy> +class OpIter; + +using ValidatingOpIter = OpIter<ValidatingPolicy>; + +// Shared subtyping function across validation. + +[[nodiscard]] bool CheckIsSubtypeOf(Decoder& d, const ModuleEnvironment& env, + size_t opcodeOffset, FieldType subType, + FieldType superType); + +// The local entries are part of function bodies and thus serialized by both +// wasm and asm.js and decoded as part of both validation and compilation. + +[[nodiscard]] bool EncodeLocalEntries(Encoder& e, const ValTypeVector& locals); + +// This performs no validation; the local entries must already have been +// validated by an earlier pass. + +[[nodiscard]] bool DecodeValidatedLocalEntries(const TypeContext& types, + Decoder& d, + ValTypeVector* locals); + +// This validates the entries. + +[[nodiscard]] bool DecodeLocalEntries(Decoder& d, const TypeContext& types, + const FeatureArgs& features, + ValTypeVector* locals); + +// Returns whether the given [begin, end) prefix of a module's bytecode starts a +// code section and, if so, returns the SectionRange of that code section. +// Note that, even if this function returns 'false', [begin, end) may actually +// be a valid module in the special case when there are no function defs and the +// code section is not present. Such modules can be valid so the caller must +// handle this special case. + +[[nodiscard]] bool StartsCodeSection(const uint8_t* begin, const uint8_t* end, + SectionRange* codeSection); + +// Calling DecodeModuleEnvironment decodes all sections up to the code section +// and performs full validation of all those sections. The client must then +// decode the code section itself, reusing ValidateFunctionBody if necessary, +// and finally call DecodeModuleTail to decode all remaining sections after the +// code section (again, performing full validation). + +[[nodiscard]] bool DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env); + +[[nodiscard]] bool ValidateFunctionBody(const ModuleEnvironment& env, + uint32_t funcIndex, uint32_t bodySize, + Decoder& d); + +[[nodiscard]] bool DecodeModuleTail(Decoder& d, ModuleEnvironment* env); + +// Validate an entire module, returning true if the module was validated +// successfully. If Validate returns false: +// - if *error is null, the caller should report out-of-memory +// - otherwise, there was a legitimate error described by *error + +[[nodiscard]] bool Validate(JSContext* cx, const ShareableBytes& bytecode, + const FeatureOptions& options, UniqueChars* error); + +} // namespace wasm +} // namespace js + +#endif // namespace wasm_validate_h |