mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-25 14:52:21 +00:00
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:
parent
368ab300fa
commit
f24342959d
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user