Update spec and two related changes (detailed)

* Added LEB128 validation (ref: https://github.com/WebAssembly/spec/pull/750)
* Rename memory instructions (ref: https://github.com/WebAssembly/spec/pull/720)
This commit is contained in:
Chad Retz 2018-05-07 15:39:31 -05:00
parent 368ab300fa
commit f24342959d
6 changed files with 48 additions and 29 deletions

View File

@ -223,8 +223,8 @@ sealed class Node {
data class I64Store8(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset data class I64Store8(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Store16(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset data class I64Store16(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Store32(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset data class I64Store32(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class CurrentMemory(override val reserved: Boolean) : Instr(), Args.Reserved data class MemorySize(override val reserved: Boolean) : Instr(), Args.Reserved
data class GrowMemory(override val reserved: Boolean) : Instr(), Args.Reserved data class MemoryGrow(override val reserved: Boolean) : Instr(), Args.Reserved
// Constants // Constants
data class I32Const(override val value: Int) : Instr(), Args.Const<Int> data class I32Const(override val value: Int) : Instr(), Args.Const<Int>
@ -511,8 +511,8 @@ sealed class Node {
opMapEntry("i64.store8", 0x3c, ::MemOpAlignOffsetArg, Instr::I64Store8, Instr.I64Store8::class) opMapEntry("i64.store8", 0x3c, ::MemOpAlignOffsetArg, Instr::I64Store8, Instr.I64Store8::class)
opMapEntry("i64.store16", 0x3d, ::MemOpAlignOffsetArg, Instr::I64Store16, Instr.I64Store16::class) opMapEntry("i64.store16", 0x3d, ::MemOpAlignOffsetArg, Instr::I64Store16, Instr.I64Store16::class)
opMapEntry("i64.store32", 0x3e, ::MemOpAlignOffsetArg, Instr::I64Store32, Instr.I64Store32::class) opMapEntry("i64.store32", 0x3e, ::MemOpAlignOffsetArg, Instr::I64Store32, Instr.I64Store32::class)
opMapEntry("current_memory", 0x3f, ::MemOpReservedArg, Instr::CurrentMemory, Instr.CurrentMemory::class) opMapEntry("memory.size", 0x3f, ::MemOpReservedArg, Instr::MemorySize, Instr.MemorySize::class)
opMapEntry("grow_memory", 0x40, ::MemOpReservedArg, Instr::GrowMemory, Instr.GrowMemory::class) opMapEntry("memory.grow", 0x40, ::MemOpReservedArg, Instr::MemoryGrow, Instr.MemoryGrow::class)
opMapEntry("i32.const", 0x41, ::ConstOpIntArg, Instr::I32Const, Instr.I32Const::class) opMapEntry("i32.const", 0x41, ::ConstOpIntArg, Instr::I32Const, Instr.I32Const::class)
opMapEntry("i64.const", 0x42, ::ConstOpLongArg, Instr::I64Const, Instr.I64Const::class) opMapEntry("i64.const", 0x42, ::ConstOpLongArg, Instr::I64Const, Instr.I64Const::class)

View File

@ -148,10 +148,10 @@ open class FuncBuilder {
is Node.Instr.I32Store8, is Node.Instr.I32Store16, is Node.Instr.I64Store8, is Node.Instr.I64Store16, is Node.Instr.I32Store8, is Node.Instr.I32Store16, is Node.Instr.I64Store8, is Node.Instr.I64Store16,
is Node.Instr.I64Store32 -> is Node.Instr.I64Store32 ->
applyStoreOp(ctx, fn, i as Node.Instr.Args.AlignOffset, index) applyStoreOp(ctx, fn, i as Node.Instr.Args.AlignOffset, index)
is Node.Instr.CurrentMemory -> is Node.Instr.MemorySize ->
applyCurrentMemory(ctx, fn) applyMemorySize(ctx, fn)
is Node.Instr.GrowMemory -> is Node.Instr.MemoryGrow ->
applyGrowMemory(ctx, fn) applyMemoryGrow(ctx, fn)
is Node.Instr.I32Const -> is Node.Instr.I32Const ->
fn.addInsns(i.value.const).push(Int::class.ref) fn.addInsns(i.value.const).push(Int::class.ref)
is Node.Instr.I64Const -> is Node.Instr.I64Const ->
@ -1062,14 +1062,14 @@ open class FuncBuilder {
).push(Int::class.ref) ).push(Int::class.ref)
} }
fun applyGrowMemory(ctx: FuncContext, fn: Func) = fun applyMemoryGrow(ctx: FuncContext, fn: Func) =
// Grow mem is a special case where the memory ref is already pre-injected on // Grow mem is a special case where the memory ref is already pre-injected on
// the stack before this call. Result is an int. // the stack before this call. Result is an int.
ctx.cls.assertHasMemory().let { ctx.cls.assertHasMemory().let {
ctx.cls.mem.growMemory(ctx, fn) ctx.cls.mem.growMemory(ctx, fn)
} }
fun applyCurrentMemory(ctx: FuncContext, fn: Func) = fun applyMemorySize(ctx: FuncContext, fn: Func) =
// Curr mem is not specially injected, so we have to put the memory on the // Curr mem is not specially injected, so we have to put the memory on the
// stack since we need it // stack since we need it
ctx.cls.assertHasMemory().let { ctx.cls.assertHasMemory().let {

View File

@ -194,7 +194,7 @@ open class InsnReworker {
is Node.Instr.I64Store32 -> is Node.Instr.I64Store32 ->
injectBeforeLastStackCount(Insn.MemNeededOnStack, 2) injectBeforeLastStackCount(Insn.MemNeededOnStack, 2)
// Grow memory requires "mem" before the single param // Grow memory requires "mem" before the single param
is Node.Instr.GrowMemory -> is Node.Instr.MemoryGrow ->
injectBeforeLastStackCount(Insn.MemNeededOnStack, 1) injectBeforeLastStackCount(Insn.MemNeededOnStack, 1)
else -> { } else -> { }
} }
@ -239,8 +239,8 @@ open class InsnReworker {
is Node.Instr.I32Store, is Node.Instr.I64Store, is Node.Instr.F32Store, is Node.Instr.F64Store, is Node.Instr.I32Store, is Node.Instr.I64Store, is Node.Instr.F32Store, is Node.Instr.F64Store,
is Node.Instr.I32Store8, is Node.Instr.I32Store16, is Node.Instr.I64Store8, is Node.Instr.I64Store16, is Node.Instr.I32Store8, is Node.Instr.I32Store16, is Node.Instr.I64Store8, is Node.Instr.I64Store16,
is Node.Instr.I64Store32 -> POP_PARAM is Node.Instr.I64Store32 -> POP_PARAM
is Node.Instr.CurrentMemory -> PUSH_RESULT is Node.Instr.MemorySize -> PUSH_RESULT
is Node.Instr.GrowMemory -> POP_PARAM + PUSH_RESULT is Node.Instr.MemoryGrow -> POP_PARAM + PUSH_RESULT
is Node.Instr.I32Const, is Node.Instr.I64Const, is Node.Instr.I32Const, is Node.Instr.I64Const,
is Node.Instr.F32Const, is Node.Instr.F64Const -> PUSH_RESULT is Node.Instr.F32Const, is Node.Instr.F64Const -> PUSH_RESULT
is Node.Instr.I32Add, is Node.Instr.I32Sub, is Node.Instr.I32Mul, is Node.Instr.I32DivS, is Node.Instr.I32Add, is Node.Instr.I32Sub, is Node.Instr.I32Mul, is Node.Instr.I32DivS,
@ -288,12 +288,12 @@ open class InsnReworker {
val inc = val inc =
if (lastCouldHaveMem) 0 if (lastCouldHaveMem) 0
else if (insn == Insn.MemNeededOnStack) 1 else if (insn == Insn.MemNeededOnStack) 1
else if (insn is Insn.Node && insn.insn is Node.Instr.CurrentMemory) 1 else if (insn is Insn.Node && insn.insn is Node.Instr.MemorySize) 1
else 0 else 0
val couldSetMemNext = if (insn !is Insn.Node) false else when (insn.insn) { val couldSetMemNext = if (insn !is Insn.Node) false else when (insn.insn) {
is Node.Instr.I32Store, is Node.Instr.I64Store, is Node.Instr.F32Store, is Node.Instr.F64Store, is Node.Instr.I32Store, is Node.Instr.I64Store, is Node.Instr.F32Store, is Node.Instr.F64Store,
is Node.Instr.I32Store8, is Node.Instr.I32Store16, is Node.Instr.I64Store8, is Node.Instr.I64Store16, is Node.Instr.I32Store8, is Node.Instr.I32Store16, is Node.Instr.I64Store8, is Node.Instr.I64Store16,
is Node.Instr.I64Store32, is Node.Instr.GrowMemory -> true is Node.Instr.I64Store32, is Node.Instr.MemoryGrow -> true
else -> false else -> false
} }
(count + inc) to couldSetMemNext (count + inc) to couldSetMemNext

View File

@ -1,12 +1,12 @@
package asmble.io package asmble.io
import asmble.util.toIntExact
import asmble.util.toUnsignedBigInt import asmble.util.toUnsignedBigInt
import asmble.util.toUnsignedLong import asmble.util.toUnsignedLong
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.math.BigInteger import java.math.BigInteger
abstract class ByteReader { abstract class ByteReader {
abstract val isEof: Boolean abstract val isEof: Boolean
@ -34,27 +34,30 @@ abstract class ByteReader {
} }
fun readVarInt7() = readSignedLeb128().let { fun readVarInt7() = readSignedLeb128().let {
require(it >= Byte.MIN_VALUE.toLong() && it <= Byte.MAX_VALUE.toLong()) if (it < Byte.MIN_VALUE.toLong() || it > Byte.MAX_VALUE.toLong()) throw IoErr.InvalidLeb128Number()
it.toByte() it.toByte()
} }
fun readVarInt32() = readSignedLeb128().toIntExact() fun readVarInt32() = readSignedLeb128().let {
if (it < Int.MIN_VALUE.toLong() || it > Int.MAX_VALUE.toLong()) throw IoErr.InvalidLeb128Number()
it.toInt()
}
fun readVarInt64() = readSignedLeb128() fun readVarInt64() = readSignedLeb128(9)
fun readVarUInt1() = readUnsignedLeb128().let { fun readVarUInt1() = readUnsignedLeb128().let {
require(it == 1 || it == 0) if (it != 1 && it != 0) throw IoErr.InvalidLeb128Number()
it == 1 it == 1
} }
fun readVarUInt7() = readUnsignedLeb128().let { fun readVarUInt7() = readUnsignedLeb128().let {
require(it <= 255) if (it > 255) throw IoErr.InvalidLeb128Number()
it.toShort() it.toShort()
} }
fun readVarUInt32() = readUnsignedLeb128().toUnsignedLong() fun readVarUInt32() = readUnsignedLeb128().toUnsignedLong()
protected fun readUnsignedLeb128(): Int { protected fun readUnsignedLeb128(maxCount: Int = 4): Int {
// Taken from Android source, Apache licensed // Taken from Android source, Apache licensed
var result = 0 var result = 0
var cur: Int var cur: Int
@ -63,12 +66,12 @@ abstract class ByteReader {
cur = readByte().toInt() and 0xff cur = readByte().toInt() and 0xff
result = result or ((cur and 0x7f) shl (count * 7)) result = result or ((cur and 0x7f) shl (count * 7))
count++ count++
} while (cur and 0x80 == 0x80 && count < 5) } while (cur and 0x80 == 0x80 && count <= maxCount)
if (cur and 0x80 == 0x80) throw NumberFormatException() if (cur and 0x80 == 0x80) throw IoErr.InvalidLeb128Number()
return result return result
} }
private fun readSignedLeb128(): Long { private fun readSignedLeb128(maxCount: Int = 4): Long {
// Taken from Android source, Apache licensed // Taken from Android source, Apache licensed
var result = 0L var result = 0L
var cur: Int var cur: Int
@ -79,8 +82,20 @@ abstract class ByteReader {
result = result or ((cur and 0x7f).toLong() shl (count * 7)) result = result or ((cur and 0x7f).toLong() shl (count * 7))
signBits = signBits shl 7 signBits = signBits shl 7
count++ count++
} while (cur and 0x80 == 0x80 && count < 10) } while (cur and 0x80 == 0x80 && count <= maxCount)
if (cur and 0x80 == 0x80) throw NumberFormatException() if (cur and 0x80 == 0x80) throw IoErr.InvalidLeb128Number()
// Check for 64 bit invalid, taken from Apache/MIT licensed:
// https://github.com/paritytech/parity-wasm/blob/2650fc14c458c6a252c9dc43dd8e0b14b6d264ff/src/elements/primitives.rs#L351
// TODO: probably need 32 bit checks too, but meh, not in the suite
if (count > maxCount && maxCount == 9) {
if (cur and 0b0100_0000 == 0b0100_0000) {
if ((cur or 0b1000_0000).toByte() != (-1).toByte()) throw IoErr.InvalidLeb128Number()
} else if (cur != 0) {
throw IoErr.InvalidLeb128Number()
}
}
if ((signBits shr 1) and result != 0L) result = result or signBits if ((signBits shr 1) and result != 0L) result = result or signBits
return result return result
} }

View File

@ -1,7 +1,6 @@
package asmble.io package asmble.io
import asmble.AsmErr import asmble.AsmErr
import java.math.BigInteger
sealed class IoErr(message: String, cause: Throwable? = null) : RuntimeException(message, cause), AsmErr { sealed class IoErr(message: String, cause: Throwable? = null) : RuntimeException(message, cause), AsmErr {
class UnexpectedEnd : IoErr("Unexpected EOF") { class UnexpectedEnd : IoErr("Unexpected EOF") {
@ -119,4 +118,9 @@ sealed class IoErr(message: String, cause: Throwable? = null) : RuntimeException
class InvalidUtf8Encoding : IoErr("Some byte sequence was not UTF-8 compatible") { class InvalidUtf8Encoding : IoErr("Some byte sequence was not UTF-8 compatible") {
override val asmErrString get() = "invalid UTF-8 encoding" override val asmErrString get() = "invalid UTF-8 encoding"
} }
class InvalidLeb128Number : IoErr("Invalid LEB128 number") {
override val asmErrString get() = "integer representation too long"
override val asmErrStrings get() = listOf(asmErrString, "integer too large")
}
} }

@ -1 +1 @@
Subproject commit 98b90e2ab22053559ded143768d6559d2ba8d2fd Subproject commit 1f00d57d009ec4098b60fb6ca138e8e2787accef