avoid some RVO bugs

This commit is contained in:
Jacek Sieka 2020-05-27 17:04:43 +02:00 committed by zah
parent f53b55cbe0
commit 693bc15919
4 changed files with 48 additions and 42 deletions

View File

@ -12,6 +12,10 @@
# nim-beacon-chain/beacon_chain/ssz.nim(212, 18) Error: can raise an unlisted exception: IOError # nim-beacon-chain/beacon_chain/ssz.nim(212, 18) Error: can raise an unlisted exception: IOError
#{.push raises: [Defect].} #{.push raises: [Defect].}
# TODO Many RVO bugs, careful
# https://github.com/nim-lang/Nim/issues/14470
# https://github.com/nim-lang/Nim/issues/14126
import import
options, algorithm, options, strformat, typetraits, options, algorithm, options, strformat, typetraits,
stew/[bitops2, bitseqs, endians2, objects, varints, ptrops], stew/[bitops2, bitseqs, endians2, objects, varints, ptrops],
@ -65,6 +69,7 @@ serializationFormat SSZ,
template decode*(Format: type SSZ, template decode*(Format: type SSZ,
input: openarray[byte], input: openarray[byte],
RecordType: distinct type): auto = RecordType: distinct type): auto =
# TODO how badly is this affected by RVO bugs?
serialization.decode(SSZ, input, RecordType, maxObjectSize = input.len) serialization.decode(SSZ, input, RecordType, maxObjectSize = input.len)
template loadFile*(Format: type SSZ, template loadFile*(Format: type SSZ,
@ -291,20 +296,20 @@ proc readValue*[T](r: var SszReader, val: var T) {.raises: [Defect, MalformedSsz
when isFixedSize(T): when isFixedSize(T):
const minimalSize = fixedPortionSize(T) const minimalSize = fixedPortionSize(T)
if r.stream.readable(minimalSize): if r.stream.readable(minimalSize):
val = readSszValue(r.stream.read(minimalSize), T) readSszValue(r.stream.read(minimalSize), val)
else: else:
raise newException(MalformedSszError, "SSZ input of insufficient size") raise newException(MalformedSszError, "SSZ input of insufficient size")
else: else:
# TODO Read the fixed portion first and precisely measure the size of # TODO Read the fixed portion first and precisely measure the size of
# the dynamic portion to consume the right number of bytes. # the dynamic portion to consume the right number of bytes.
val = readSszValue(r.stream.read(r.stream.len.get), T) readSszValue(r.stream.read(r.stream.len.get), val)
proc readValue*[T](r: var SszReader, val: var SizePrefixed[T]) {.raises: [Defect].} = proc readValue*[T](r: var SszReader, val: var SizePrefixed[T]) {.raises: [Defect].} =
let length = r.stream.readVarint(uint64) let length = r.stream.readVarint(uint64)
if length > r.maxObjectSize: if length > r.maxObjectSize:
raise newException(SszMaxSizeExceeded, raise newException(SszMaxSizeExceeded,
"Maximum SSZ object size exceeded: " & $length) "Maximum SSZ object size exceeded: " & $length)
val = readSszValue(r.stream.read(length), T) readSszValue(r.stream.read(length), T(val))
const const
zeroChunk = default array[32, byte] zeroChunk = default array[32, byte]

View File

@ -84,11 +84,9 @@ template checkForForbiddenBits(ResulType: type,
if (input[^1] and forbiddenBitsMask) != 0: if (input[^1] and forbiddenBitsMask) != 0:
raiseIncorrectSize ResulType raiseIncorrectSize ResulType
func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} = func readSszValue*(input: openarray[byte], val: var auto) {.raisesssz.} =
mixin fromSszBytes, toSszType mixin fromSszBytes, toSszType
type T {.used.} = type(result)
template readOffsetUnchecked(n: int): int {.used.}= template readOffsetUnchecked(n: int): int {.used.}=
int fromSszBytes(uint32, input.toOpenArray(n, n + offsetSize - 1)) int fromSszBytes(uint32, input.toOpenArray(n, n + offsetSize - 1))
@ -106,42 +104,45 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
# result.checkOutputSize input.len # result.checkOutputSize input.len
# readOpenArray(result, input) # readOpenArray(result, input)
when result is BitList: when val is BitList:
if input.len == 0: if input.len == 0:
raise newException(MalformedSszError, "Invalid empty SSZ BitList value") raise newException(MalformedSszError, "Invalid empty SSZ BitList value")
const maxExpectedSize = (result.maxLen div 8) + 1 const maxExpectedSize = (val.maxLen div 8) + 1
result = T readSszValue(input, List[byte, maxExpectedSize]) # TODO can't cast here..
var v: List[byte, maxExpectedSize]
readSszValue(input, v)
val = (type val)(v)
let resultBytesCount = len bytes(result) let resultBytesCount = len bytes(val)
if bytes(result)[resultBytesCount - 1] == 0: if bytes(val)[resultBytesCount - 1] == 0:
raise newException(MalformedSszError, "SSZ BitList is not properly terminated") raise newException(MalformedSszError, "SSZ BitList is not properly terminated")
if resultBytesCount == maxExpectedSize: if resultBytesCount == maxExpectedSize:
checkForForbiddenBits(T, input, result.maxLen + 1) checkForForbiddenBits(type val, input, val.maxLen + 1)
elif result is List|array: elif val is List|array:
type E = type result[0] type E = type val[0]
when E is byte: when E is byte:
result.setOutputSize input.len val.setOutputSize input.len
if input.len > 0: if input.len > 0:
copyMem(addr result[0], unsafeAddr input[0], input.len) copyMem(addr val[0], unsafeAddr input[0], input.len)
elif isFixedSize(E): elif isFixedSize(E):
const elemSize = fixedPortionSize(E) const elemSize = fixedPortionSize(E)
if input.len mod elemSize != 0: if input.len mod elemSize != 0:
var ex = new SszSizeMismatchError var ex = new SszSizeMismatchError
ex.deserializedType = cstring typetraits.name(T) ex.deserializedType = cstring typetraits.name(type val)
ex.actualSszSize = input.len ex.actualSszSize = input.len
ex.elementSize = elemSize ex.elementSize = elemSize
raise ex raise ex
result.setOutputSize input.len div elemSize val.setOutputSize input.len div elemSize
trs "READING LIST WITH LEN ", result.len trs "READING LIST WITH LEN ", val.len
for i in 0 ..< result.len: for i in 0 ..< val.len:
trs "TRYING TO READ LIST ELEM ", i trs "TRYING TO READ LIST ELEM ", i
let offset = i * elemSize let offset = i * elemSize
result[i] = readSszValue(input.toOpenArray(offset, offset + elemSize - 1), E) readSszValue(input.toOpenArray(offset, offset + elemSize - 1), val[i])
trs "LIST READING COMPLETE" trs "LIST READING COMPLETE"
else: else:
@ -164,36 +165,36 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
# not matching up with its nextOffset properly) # not matching up with its nextOffset properly)
raise newException(MalformedSszError, "SSZ list incorrectly encoded of zero length") raise newException(MalformedSszError, "SSZ list incorrectly encoded of zero length")
result.setOutputSize resultLen val.setOutputSize resultLen
for i in 1 ..< resultLen: for i in 1 ..< resultLen:
let nextOffset = readOffset(i * offsetSize) let nextOffset = readOffset(i * offsetSize)
if nextOffset <= offset: if nextOffset <= offset:
raise newException(MalformedSszError, "SSZ list element offsets are not monotonically increasing") raise newException(MalformedSszError, "SSZ list element offsets are not monotonically increasing")
else: else:
result[i - 1] = readSszValue(input.toOpenArray(offset, nextOffset - 1), E) readSszValue(input.toOpenArray(offset, nextOffset - 1), val[i - 1])
offset = nextOffset offset = nextOffset
result[resultLen - 1] = readSszValue(input.toOpenArray(offset, input.len - 1), E) readSszValue(input.toOpenArray(offset, input.len - 1), val[resultLen - 1])
# TODO: Should be possible to remove BitArray from here # TODO: Should be possible to remove BitArray from here
elif result is UintN|bool|enum: elif val is UintN|bool|enum:
trs "READING BASIC TYPE ", type(result).name, " input=", input.len trs "READING BASIC TYPE ", type(val).name, " input=", input.len
result = fromSszBytes(type(result), input) val = fromSszBytes(type(val), input)
trs "RESULT WAS ", repr(result) trs "RESULT WAS ", repr(val)
elif result is BitArray: elif val is BitArray:
if sizeof(result) != input.len: if sizeof(val) != input.len:
raiseIncorrectSize T raiseIncorrectSize(type val)
checkForForbiddenBits(T, input, result.bits) checkForForbiddenBits(type val, input, val.bits)
copyMem(addr result.bytes[0], unsafeAddr input[0], input.len) copyMem(addr val.bytes[0], unsafeAddr input[0], input.len)
elif result is object|tuple: elif val is object|tuple:
const minimallyExpectedSize = fixedPortionSize(T) const minimallyExpectedSize = fixedPortionSize(type val)
if input.len < minimallyExpectedSize: if input.len < minimallyExpectedSize:
raise newException(MalformedSszError, "SSZ input of insufficient size") raise newException(MalformedSszError, "SSZ input of insufficient size")
enumInstanceSerializedFields(result, fieldName, field): enumInstanceSerializedFields(val, fieldName, field):
const boundingOffsets = T.getFieldBoundingOffsets(fieldName) const boundingOffsets = getFieldBoundingOffsets(type val, fieldName)
trs "BOUNDING OFFSET FOR FIELD ", fieldName, " = ", boundingOffsets trs "BOUNDING OFFSET FOR FIELD ", fieldName, " = ", boundingOffsets
# type FieldType = type field # buggy # type FieldType = type field # buggy
@ -229,9 +230,9 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
# TODO passing in `FieldType` instead of `type(field)` triggers a # TODO passing in `FieldType` instead of `type(field)` triggers a
# bug in the compiler # bug in the compiler
field = readSszValue( readSszValue(
input.toOpenArray(startOffset, endOffset - 1), input.toOpenArray(startOffset, endOffset - 1),
type(field)) field)
trs "READING COMPLETE ", fieldName trs "READING COMPLETE ", fieldName
else: else:
@ -241,4 +242,4 @@ func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
input.toOpenArray(startOffset, endOffset - 1)) input.toOpenArray(startOffset, endOffset - 1))
else: else:
unsupported T unsupported (type val)

View File

@ -121,7 +121,7 @@ func `[]`*[T](n: SszNavigator[T]): T {.raisesssz.} =
mixin toSszType, fromSszBytes mixin toSszType, fromSszBytes
type SszRepr = type toSszType(declval T) type SszRepr = type toSszType(declval T)
when type(SszRepr) is type(T) or T is List: when type(SszRepr) is type(T) or T is List:
readSszValue(toOpenArray(n.m), T) readSszValue(toOpenArray(n.m), result)
else: else:
fromSszBytes(T, toOpenArray(n.m)) fromSszBytes(T, toOpenArray(n.m))

View File

@ -58,7 +58,7 @@ template readFileBytes*(path: string): seq[byte] =
proc sszDecodeEntireInput*(input: openarray[byte], Decoded: type): Decoded = proc sszDecodeEntireInput*(input: openarray[byte], Decoded: type): Decoded =
var stream = unsafeMemoryInput(input) var stream = unsafeMemoryInput(input)
var reader = init(SszReader, stream, input.len) var reader = init(SszReader, stream, input.len)
result = reader.readValue(Decoded) reader.readValue(result)
if stream.readable: if stream.readable:
raise newException(UnconsumedInput, "Remaining bytes in the input") raise newException(UnconsumedInput, "Remaining bytes in the input")