Commit feefd34e authored by Kenton Varda's avatar Kenton Varda

Merge pull request #293 from maurer/canonicalize-pr

Add Canonicalization
parents d18c6ec2 ce5d90bd
...@@ -15,6 +15,7 @@ Kamal Marhubi <kamal@marhubi.com>: JSON parser ...@@ -15,6 +15,7 @@ Kamal Marhubi <kamal@marhubi.com>: JSON parser
Oliver Kuckertz <oliver.kuckertz@mologie.de>: FdObserver POLLPRI support Oliver Kuckertz <oliver.kuckertz@mologie.de>: FdObserver POLLPRI support
Harris Hancock <vortrab@gmail.com>: MSVC support Harris Hancock <vortrab@gmail.com>: MSVC support
Branislav Katreniak <branislav.katreniak@digitalstrom.com>: JSON decode Branislav Katreniak <branislav.katreniak@digitalstrom.com>: JSON decode
Matthew Maurer <matthew.r.maurer@gmail.com>: Canonicalization Support
This file does not list people who maintain their own Cap'n Proto This file does not list people who maintain their own Cap'n Proto
implementations as separate projects. Those people are awesome too! :) implementations as separate projects. Those people are awesome too! :)
...@@ -210,6 +210,7 @@ if(BUILD_TESTING) ...@@ -210,6 +210,7 @@ if(BUILD_TESTING)
orphan-test.c++ orphan-test.c++
serialize-test.c++ serialize-test.c++
serialize-packed-test.c++ serialize-packed-test.c++
canonicalize-test.c++
fuzz-test.c++ fuzz-test.c++
test-util.c++ test-util.c++
${test_capnp_cpp_files} ${test_capnp_cpp_files}
......
...@@ -221,9 +221,14 @@ struct AnyPointer { ...@@ -221,9 +221,14 @@ struct AnyPointer {
inline void setAs(std::initializer_list<ReaderFor<ListElementType<T>>> list); inline void setAs(std::initializer_list<ReaderFor<ListElementType<T>>> list);
// Valid for T = List<?>. // Valid for T = List<?>.
template <typename T>
inline void setCanonicalAs(ReaderFor<T> value);
inline void set(Reader value) { builder.copyFrom(value.reader); } inline void set(Reader value) { builder.copyFrom(value.reader); }
// Set to a copy of another AnyPointer. // Set to a copy of another AnyPointer.
inline void setCanonical(Reader value) { builder.copyFrom(value.reader, true); }
template <typename T> template <typename T>
inline void adopt(Orphan<T>&& orphan); inline void adopt(Orphan<T>&& orphan);
// Valid for T = any generated struct type, List<U>, Text, Data, DynamicList, DynamicStruct, // Valid for T = any generated struct type, List<U>, Text, Data, DynamicList, DynamicStruct,
...@@ -463,6 +468,10 @@ public: ...@@ -463,6 +468,10 @@ public:
return List<AnyPointer>::Reader(_reader.getPointerSectionAsList()); return List<AnyPointer>::Reader(_reader.getPointerSectionAsList());
} }
kj::Array<word> canonicalize() {
return _reader.canonicalize();
}
Equality equals(AnyStruct::Reader right); Equality equals(AnyStruct::Reader right);
bool operator==(AnyStruct::Reader right); bool operator==(AnyStruct::Reader right);
inline bool operator!=(AnyStruct::Reader right) { inline bool operator!=(AnyStruct::Reader right) {
...@@ -791,6 +800,11 @@ inline void AnyPointer::Builder::setAs(ReaderFor<T> value) { ...@@ -791,6 +800,11 @@ inline void AnyPointer::Builder::setAs(ReaderFor<T> value) {
return _::PointerHelpers<T>::set(builder, value); return _::PointerHelpers<T>::set(builder, value);
} }
template <typename T>
inline void AnyPointer::Builder::setCanonicalAs(ReaderFor<T> value) {
return _::PointerHelpers<T>::setCanonical(builder, value);
}
template <typename T> template <typename T>
inline void AnyPointer::Builder::setAs( inline void AnyPointer::Builder::setAs(
std::initializer_list<ReaderFor<ListElementType<T>>> list) { std::initializer_list<ReaderFor<ListElementType<T>>> list) {
......
// Copyright (c) 2016 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "message.h"
#include "any.h"
#include <kj/debug.h>
#include <kj/test.h>
#include "test-util.h"
namespace capnp {
namespace _ { // private
using test::TestLists;
namespace {
KJ_TEST("canonicalize yields cannonical message") {
MallocMessageBuilder builder;
auto root = builder.initRoot<TestAllTypes>();
initTestMessage(root);
canonicalize(root.asReader());
//Will assert if canonicalize failed to do so
}
KJ_TEST("isCanonical requires pointer preorder") {
AlignedData<5> misorderedSegment = {{
//Struct pointer, data immediately follows, two pointer fields, no data
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
//Pointer field 1, pointing to the last entry, data size 1, no pointer
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
//Pointer field 2, pointing to the next entry, data size 2, no pointer
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
//Data for field 2
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
//Data for field 1
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00
}};
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(misorderedSegment.words,
3)};
SegmentArrayMessageReader outOfOrder(kj::arrayPtr(segments, 1));
KJ_ASSERT(!outOfOrder.isCanonical());
}
KJ_TEST("isCanonical requires dense packing") {
AlignedData<3> gapSegment = {{
//Struct pointer, data after a gap
0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
//The gap
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//Data for field 1
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
}};
kj::ArrayPtr<const word> segments[1] = {kj::arrayPtr(gapSegment.words,
3)};
SegmentArrayMessageReader gap(kj::arrayPtr(segments, 1));
KJ_ASSERT(!gap.isCanonical());
}
KJ_TEST("isCanonical rejects multi-segment messages") {
AlignedData<1> farPtr = {{
//Far pointer to next segment
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
}};
AlignedData<2> farTarget = {{
//Struct pointer (needed to make the far pointer legal)
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
//Dummy data
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
}};
kj::ArrayPtr<const word> segments[2] = {
kj::arrayPtr(farPtr.words, 1),
kj::arrayPtr(farTarget.words, 2)
};
SegmentArrayMessageReader multiSegmentMessage(kj::arrayPtr(segments, 2));
KJ_ASSERT(!multiSegmentMessage.isCanonical());
}
KJ_TEST("isCanonical rejects zero segment messages") {
SegmentArrayMessageReader zero(kj::arrayPtr((kj::ArrayPtr<const word>*)NULL,
0));
KJ_ASSERT(!zero.isCanonical());
}
KJ_TEST("isCanonical requires truncation of 0-valued struct fields") {
AlignedData<2> nonTruncatedSegment = {{
//Struct pointer, data immediately follows
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
//Default data value, should have been truncated
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}};
kj::ArrayPtr<const word> segments[1] = {
kj::arrayPtr(nonTruncatedSegment.words, 3)
};
SegmentArrayMessageReader nonTruncated(kj::arrayPtr(segments, 1));
KJ_ASSERT(!nonTruncated.isCanonical());
}
KJ_TEST("upgraded lists can be canonicalized") {
AlignedData<7> upgradedList = {{
//Struct pointer, data immediately follows, 4 pointer fields, no data
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
//Three words of default pointers to get to the int16 list
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//List pointer, 3 int16s.
0x01, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
//First two elements
0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06,
//Last element
0x07, 0x08, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00
}};
kj::ArrayPtr<const word> segments[1] = {
kj::arrayPtr(upgradedList.words, 7)
};
SegmentArrayMessageReader upgraded(kj::arrayPtr(segments, 1));
auto root = upgraded.getRoot<TestLists>();
canonicalize(root);
}
KJ_TEST("isCanonical requires truncation of 0-valued struct fields in all list members") {
AlignedData<6> nonTruncatedList = {{
//List pointer, composite,
0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
//Struct tag word, 2 structs, 2 data words per struct
0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
//Data word non-null
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
//Null trailing word
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//Data word non-null
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
//Null trailing word
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}};
kj::ArrayPtr<const word> segments[1] = {
kj::arrayPtr(nonTruncatedList.words, 6)
};
SegmentArrayMessageReader nonTruncated(kj::arrayPtr(segments, 1));
KJ_ASSERT(!nonTruncated.isCanonical());
}
} // namespace
} // namespace _ (private)
} // namespace capnp
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors // Copyright (c) 2013-2016 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License: // Licensed under the MIT License:
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
...@@ -357,6 +357,7 @@ struct WireHelpers { ...@@ -357,6 +357,7 @@ struct WireHelpers {
word* ptr = segment->allocate(amount); word* ptr = segment->allocate(amount);
if (ptr == nullptr) { if (ptr == nullptr) {
// Need to allocate in a new segment. We'll need to allocate an extra pointer worth of // Need to allocate in a new segment. We'll need to allocate an extra pointer worth of
// space to act as the landing pad for a far pointer. // space to act as the landing pad for a far pointer.
...@@ -1545,23 +1546,75 @@ struct WireHelpers { ...@@ -1545,23 +1546,75 @@ struct WireHelpers {
static SegmentAnd<word*> setStructPointer( static SegmentAnd<word*> setStructPointer(
SegmentBuilder* segment, CapTableBuilder* capTable, WirePointer* ref, StructReader value, SegmentBuilder* segment, CapTableBuilder* capTable, WirePointer* ref, StructReader value,
BuilderArena* orphanArena = nullptr) { BuilderArena* orphanArena = nullptr, bool canonical = false) {
WordCount dataSize = roundBitsUpToWords(value.dataSize); ByteCount dataSize = roundBitsUpToBytes(value.dataSize);
WordCount totalSize = dataSize + value.pointerCount * WORDS_PER_POINTER; WirePointerCount ptrCount = value.pointerCount;
if (canonical) {
// StructReaders should not have bitwidths other than 1, but let's be safe
KJ_REQUIRE((value.dataSize == 1 * BITS)
|| (value.dataSize % BITS_PER_BYTE == 0 * BITS));
// Handle the truncation case where it's a false in a 1-bit struct
if (value.dataSize == 1 * BITS) {
if (!value.getDataField<bool>(0 * ELEMENTS)) {
dataSize = 0 * BYTES;
}
} else {
// Truncate the data section
while (dataSize != 0 * BYTES) {
size_t end = (dataSize - 1 * BYTES) / BYTES;
ByteCount window = dataSize % BYTES_PER_WORD;
if (window == 0 * BYTES) {
window = BYTES_PER_WORD * WORDS;
}
size_t start = end + 1 - window / BYTES;
kj::ArrayPtr<const byte> lastWord = value.getDataSectionAsBlob().slice(start, end);
bool lastWordZero = true;
//TODO(MRM) once this is known to work, replace with fast memcmp
for (auto it = lastWord.begin(); it != lastWord.end(); it++) {
lastWordZero &= (*it == 0);
}
if (!lastWordZero) {
break;
} else {
dataSize -= window;
}
}
}
// Truncate pointer section
while ((ptrCount != 0 * POINTERS) &&
value.getPointerField(ptrCount - 1 * POINTERS).isNull()) {
ptrCount -= 1 * POINTERS;
}
}
WordCount dataWords = roundBytesUpToWords(dataSize);
WordCount totalSize = dataWords + ptrCount * WORDS_PER_POINTER;
word* ptr = allocate(ref, segment, capTable, totalSize, WirePointer::STRUCT, orphanArena); word* ptr = allocate(ref, segment, capTable, totalSize, WirePointer::STRUCT, orphanArena);
ref->structRef.set(dataSize, value.pointerCount); ref->structRef.set(dataWords, ptrCount);
if (value.dataSize == 1 * BITS) { if (value.dataSize == 1 * BITS) {
*reinterpret_cast<char*>(ptr) = value.getDataField<bool>(0 * ELEMENTS); // Data size could be made 0 by truncation
if (dataSize != 0 * BYTES) {
*reinterpret_cast<char*>(ptr) = value.getDataField<bool>(0 * ELEMENTS);
}
} else { } else {
memcpy(ptr, value.data, value.dataSize / BITS_PER_BYTE / BYTES); memcpy(ptr, value.data, dataSize / BYTES);
if (dataSize % BYTES_PER_WORD != 0 * BYTES) {
//Zero-pad the data if it didn't use the entire last word
byte* padStart = reinterpret_cast<byte*>(ptr) + (dataSize / BYTES);
bzero(padStart, (BYTES_PER_WORD * WORDS - (dataSize % BYTES_PER_WORD)) / BYTES);
}
} }
WirePointer* pointerSection = reinterpret_cast<WirePointer*>(ptr + dataSize); WirePointer* pointerSection = reinterpret_cast<WirePointer*>(ptr + dataWords);
for (uint i = 0; i < value.pointerCount / POINTERS; i++) { for (uint i = 0; i < ptrCount / POINTERS; i++) {
copyPointer(segment, capTable, pointerSection + i, copyPointer(segment, capTable, pointerSection + i,
value.segment, value.capTable, value.pointers + i, value.nestingLimit); value.segment, value.capTable, value.pointers + i,
value.nestingLimit, nullptr, canonical);
} }
return { segment, ptr }; return { segment, ptr };
...@@ -1584,7 +1637,7 @@ struct WireHelpers { ...@@ -1584,7 +1637,7 @@ struct WireHelpers {
static SegmentAnd<word*> setListPointer( static SegmentAnd<word*> setListPointer(
SegmentBuilder* segment, CapTableBuilder* capTable, WirePointer* ref, ListReader value, SegmentBuilder* segment, CapTableBuilder* capTable, WirePointer* ref, ListReader value,
BuilderArena* orphanArena = nullptr) { BuilderArena* orphanArena = nullptr, bool canonical = false) {
WordCount totalSize = roundBitsUpToWords(value.elementCount * value.step); WordCount totalSize = roundBitsUpToWords(value.elementCount * value.step);
if (value.elementSize != ElementSize::INLINE_COMPOSITE) { if (value.elementSize != ElementSize::INLINE_COMPOSITE) {
...@@ -1598,7 +1651,7 @@ struct WireHelpers { ...@@ -1598,7 +1651,7 @@ struct WireHelpers {
copyPointer(segment, capTable, reinterpret_cast<WirePointer*>(ptr) + i, copyPointer(segment, capTable, reinterpret_cast<WirePointer*>(ptr) + i,
value.segment, value.capTable, value.segment, value.capTable,
reinterpret_cast<const WirePointer*>(value.ptr) + i, reinterpret_cast<const WirePointer*>(value.ptr) + i,
value.nestingLimit); value.nestingLimit, nullptr, canonical);
} }
} else { } else {
// List of data. // List of data.
...@@ -1610,31 +1663,74 @@ struct WireHelpers { ...@@ -1610,31 +1663,74 @@ struct WireHelpers {
} else { } else {
// List of structs. // List of structs.
KJ_DASSERT(value.structDataSize % BITS_PER_WORD == 0 * BITS); KJ_DASSERT(value.structDataSize % BITS_PER_WORD == 0 * BITS);
WordCount declDataSize = value.structDataSize / BITS_PER_WORD;
WirePointerCount declPointerCount = value.structPointerCount;
WordCount dataSize = 0 * WORDS;
WirePointerCount ptrCount = 0 * POINTERS;
if (canonical) {
for (auto ec = ElementCount(0); ec < value.elementCount; ec += 1 * ELEMENTS) {
auto se = value.getStructElement(ec);
WordCount localDataSize = declDataSize;
while (localDataSize != 0 * WORDS) {
size_t end = (localDataSize * BYTES_PER_WORD - 1 * BYTES) / BYTES;
ByteCount window = BYTES_PER_WORD * WORDS;
size_t start = end + 1 - window / BYTES;
kj::ArrayPtr<const byte> lastWord = se.getDataSectionAsBlob().slice(start, end);
bool lastWordZero = true;
//TODO(MRM) once this is known to work, replace with fast memcmp
for (auto it = lastWord.begin(); it != lastWord.end(); it++) {
lastWordZero &= (*it == 0);
}
if (!lastWordZero) {
break;
} else {
localDataSize -= WORDS;
}
}
if (localDataSize > dataSize) {
dataSize = localDataSize;
}
WirePointerCount localPtrCount = declPointerCount;
while ((localPtrCount != 0 * POINTERS) &&
se.getPointerField(localPtrCount - 1 * POINTERS).isNull()) {
localPtrCount -= 1 * POINTERS;
}
if (localPtrCount > ptrCount) {
ptrCount = localPtrCount;
}
}
totalSize = (dataSize + ptrCount * WORDS_PER_POINTER) / ELEMENTS * value.elementCount;
} else {
dataSize = declDataSize;
ptrCount = declPointerCount;
}
word* ptr = allocate(ref, segment, capTable, totalSize + POINTER_SIZE_IN_WORDS, word* ptr = allocate(ref, segment, capTable, totalSize + POINTER_SIZE_IN_WORDS,
WirePointer::LIST, orphanArena); WirePointer::LIST, orphanArena);
ref->listRef.setInlineComposite(totalSize); ref->listRef.setInlineComposite(totalSize);
WordCount dataSize = roundBitsUpToWords(value.structDataSize);
WirePointerCount pointerCount = value.structPointerCount;
WirePointer* tag = reinterpret_cast<WirePointer*>(ptr); WirePointer* tag = reinterpret_cast<WirePointer*>(ptr);
tag->setKindAndInlineCompositeListElementCount(WirePointer::STRUCT, value.elementCount); tag->setKindAndInlineCompositeListElementCount(WirePointer::STRUCT, value.elementCount);
tag->structRef.set(dataSize, pointerCount); tag->structRef.set(dataSize, ptrCount);
word* dst = ptr + POINTER_SIZE_IN_WORDS; word* dst = ptr + POINTER_SIZE_IN_WORDS;
const word* src = reinterpret_cast<const word*>(value.ptr); const word* src = reinterpret_cast<const word*>(value.ptr);
for (uint i = 0; i < value.elementCount / ELEMENTS; i++) { for (uint i = 0; i < value.elementCount / ELEMENTS; i++) {
memcpy(dst, src, value.structDataSize / BITS_PER_BYTE / BYTES); memcpy(dst, src, dataSize * BYTES_PER_WORD / BYTES);
dst += dataSize; dst += dataSize;
src += dataSize; src += declDataSize;
for (uint j = 0; j < pointerCount / POINTERS; j++) { for (uint j = 0; j < ptrCount / POINTERS; j++) {
copyPointer(segment, capTable, reinterpret_cast<WirePointer*>(dst), copyPointer(segment, capTable, reinterpret_cast<WirePointer*>(dst),
value.segment, value.capTable, reinterpret_cast<const WirePointer*>(src), value.segment, value.capTable, reinterpret_cast<const WirePointer*>(src),
value.nestingLimit); value.nestingLimit, nullptr, canonical);
dst += POINTER_SIZE_IN_WORDS; dst += POINTER_SIZE_IN_WORDS;
src += POINTER_SIZE_IN_WORDS; src += POINTER_SIZE_IN_WORDS;
} }
src += ((declPointerCount - ptrCount) * WORDS_PER_POINTER) / WORDS;
} }
return { segment, ptr }; return { segment, ptr };
...@@ -1644,16 +1740,18 @@ struct WireHelpers { ...@@ -1644,16 +1740,18 @@ struct WireHelpers {
static KJ_ALWAYS_INLINE(SegmentAnd<word*> copyPointer( static KJ_ALWAYS_INLINE(SegmentAnd<word*> copyPointer(
SegmentBuilder* dstSegment, CapTableBuilder* dstCapTable, WirePointer* dst, SegmentBuilder* dstSegment, CapTableBuilder* dstCapTable, WirePointer* dst,
SegmentReader* srcSegment, CapTableReader* srcCapTable, const WirePointer* src, SegmentReader* srcSegment, CapTableReader* srcCapTable, const WirePointer* src,
int nestingLimit, BuilderArena* orphanArena = nullptr)) { int nestingLimit, BuilderArena* orphanArena = nullptr,
bool canonical = false)) {
return copyPointer(dstSegment, dstCapTable, dst, return copyPointer(dstSegment, dstCapTable, dst,
srcSegment, srcCapTable, src, src->target(), srcSegment, srcCapTable, src, src->target(),
nestingLimit, orphanArena); nestingLimit, orphanArena, canonical);
} }
static SegmentAnd<word*> copyPointer( static SegmentAnd<word*> copyPointer(
SegmentBuilder* dstSegment, CapTableBuilder* dstCapTable, WirePointer* dst, SegmentBuilder* dstSegment, CapTableBuilder* dstCapTable, WirePointer* dst,
SegmentReader* srcSegment, CapTableReader* srcCapTable, const WirePointer* src, SegmentReader* srcSegment, CapTableReader* srcCapTable, const WirePointer* src,
const word* srcTarget, int nestingLimit, BuilderArena* orphanArena = nullptr) { const word* srcTarget, int nestingLimit,
BuilderArena* orphanArena = nullptr, bool canonical = false) {
// Deep-copy the object pointed to by src into dst. It turns out we can't reuse // Deep-copy the object pointed to by src into dst. It turns out we can't reuse
// readStructPointer(), etc. because they do type checking whereas here we want to accept any // readStructPointer(), etc. because they do type checking whereas here we want to accept any
// valid pointer. // valid pointer.
...@@ -1690,7 +1788,7 @@ struct WireHelpers { ...@@ -1690,7 +1788,7 @@ struct WireHelpers {
src->structRef.dataSize.get() * BITS_PER_WORD, src->structRef.dataSize.get() * BITS_PER_WORD,
src->structRef.ptrCount.get(), src->structRef.ptrCount.get(),
nestingLimit - 1), nestingLimit - 1),
orphanArena); orphanArena, canonical);
case WirePointer::LIST: { case WirePointer::LIST: {
ElementSize elementSize = src->listRef.elementSize(); ElementSize elementSize = src->listRef.elementSize();
...@@ -1738,7 +1836,7 @@ struct WireHelpers { ...@@ -1738,7 +1836,7 @@ struct WireHelpers {
tag->structRef.dataSize.get() * BITS_PER_WORD, tag->structRef.dataSize.get() * BITS_PER_WORD,
tag->structRef.ptrCount.get(), ElementSize::INLINE_COMPOSITE, tag->structRef.ptrCount.get(), ElementSize::INLINE_COMPOSITE,
nestingLimit - 1), nestingLimit - 1),
orphanArena); orphanArena, canonical);
} else { } else {
BitCount dataSize = dataBitsPerElement(elementSize) * ELEMENTS; BitCount dataSize = dataBitsPerElement(elementSize) * ELEMENTS;
WirePointerCount pointerCount = pointersPerElement(elementSize) * ELEMENTS; WirePointerCount pointerCount = pointersPerElement(elementSize) * ELEMENTS;
...@@ -1763,7 +1861,7 @@ struct WireHelpers { ...@@ -1763,7 +1861,7 @@ struct WireHelpers {
return setListPointer(dstSegment, dstCapTable, dst, return setListPointer(dstSegment, dstCapTable, dst,
ListReader(srcSegment, srcCapTable, ptr, elementCount, step, dataSize, pointerCount, ListReader(srcSegment, srcCapTable, ptr, elementCount, step, dataSize, pointerCount,
elementSize, nestingLimit - 1), elementSize, nestingLimit - 1),
orphanArena); orphanArena, canonical);
} }
} }
...@@ -1777,6 +1875,11 @@ struct WireHelpers { ...@@ -1777,6 +1875,11 @@ struct WireHelpers {
goto useDefault; goto useDefault;
} }
if (canonical) {
KJ_FAIL_REQUIRE("Cannot create a canonical message with a capability") {
break;
}
}
#if !CAPNP_LITE #if !CAPNP_LITE
KJ_IF_MAYBE(cap, srcCapTable->extractCap(src->capRef.index.get())) { KJ_IF_MAYBE(cap, srcCapTable->extractCap(src->capRef.index.get())) {
setCapabilityPointer(dstSegment, dstCapTable, dst, kj::mv(*cap)); setCapabilityPointer(dstSegment, dstCapTable, dst, kj::mv(*cap));
...@@ -2280,12 +2383,12 @@ Data::Builder PointerBuilder::getBlob<Data>(const void* defaultValue, ByteCount ...@@ -2280,12 +2383,12 @@ Data::Builder PointerBuilder::getBlob<Data>(const void* defaultValue, ByteCount
return WireHelpers::getWritableDataPointer(pointer, segment, capTable, defaultValue, defaultSize); return WireHelpers::getWritableDataPointer(pointer, segment, capTable, defaultValue, defaultSize);
} }
void PointerBuilder::setStruct(const StructReader& value) { void PointerBuilder::setStruct(const StructReader& value, bool canonical) {
WireHelpers::setStructPointer(segment, capTable, pointer, value); WireHelpers::setStructPointer(segment, capTable, pointer, value, nullptr, canonical);
} }
void PointerBuilder::setList(const ListReader& value) { void PointerBuilder::setList(const ListReader& value, bool canonical) {
WireHelpers::setListPointer(segment, capTable, pointer, value); WireHelpers::setListPointer(segment, capTable, pointer, value, nullptr, canonical);
} }
#if !CAPNP_LITE #if !CAPNP_LITE
...@@ -2342,7 +2445,7 @@ void PointerBuilder::transferFrom(PointerBuilder other) { ...@@ -2342,7 +2445,7 @@ void PointerBuilder::transferFrom(PointerBuilder other) {
memset(other.pointer, 0, sizeof(*other.pointer)); memset(other.pointer, 0, sizeof(*other.pointer));
} }
void PointerBuilder::copyFrom(PointerReader other) { void PointerBuilder::copyFrom(PointerReader other, bool canonical) {
if (other.pointer == nullptr) { if (other.pointer == nullptr) {
if (!pointer->isNull()) { if (!pointer->isNull()) {
WireHelpers::zeroObject(segment, capTable, pointer); WireHelpers::zeroObject(segment, capTable, pointer);
...@@ -2350,7 +2453,9 @@ void PointerBuilder::copyFrom(PointerReader other) { ...@@ -2350,7 +2453,9 @@ void PointerBuilder::copyFrom(PointerReader other) {
} }
} else { } else {
WireHelpers::copyPointer(segment, capTable, pointer, WireHelpers::copyPointer(segment, capTable, pointer,
other.segment, other.capTable, other.pointer, other.nestingLimit); other.segment, other.capTable, other.pointer, other.nestingLimit,
nullptr,
canonical);
} }
} }
...@@ -2469,6 +2574,33 @@ PointerReader PointerReader::imbue(CapTableReader* capTable) const { ...@@ -2469,6 +2574,33 @@ PointerReader PointerReader::imbue(CapTableReader* capTable) const {
return result; return result;
} }
bool PointerReader::isCanonical(const word **readHead) {
if (!this->pointer) {
// The pointer is null, so we are canonical and do not read
return true;
}
if (!this->pointer->isPositional()) {
// The pointer is a FAR or OTHER pointer, and is non-canonical
return false;
}
switch (this->getPointerType()) {
case PointerType::NULL_:
// The pointer is null, we are canonical and do not read
return true;
case PointerType::STRUCT:
bool dataTrunc, ptrTrunc;
return (this->getStruct(nullptr).isCanonical(readHead, readHead, &dataTrunc, &ptrTrunc)
&& dataTrunc && ptrTrunc);
case PointerType::LIST:
return this->getListAnySize(nullptr).isCanonical(readHead);
case PointerType::CAPABILITY:
KJ_FAIL_ASSERT("Capabilities are not positional");
}
KJ_UNREACHABLE;
}
// ======================================================================================= // =======================================================================================
// StructBuilder // StructBuilder
...@@ -2600,6 +2732,19 @@ MessageSizeCounts StructReader::totalSize() const { ...@@ -2600,6 +2732,19 @@ MessageSizeCounts StructReader::totalSize() const {
return result; return result;
} }
kj::Array<word> StructReader::canonicalize() {
WordCount size = totalSize().wordCount + POINTER_SIZE_IN_WORDS;
kj::Array<word> backing = kj::heapArray<word>(size / WORDS);
memset(backing.begin(), 0, backing.asBytes().size());
FlatMessageBuilder builder(backing);
_::PointerHelpers<AnyPointer>::getInternalBuilder(builder.initRoot<AnyPointer>()).setStruct(*this, true);
KJ_ASSERT(builder.isCanonical());
auto output = builder.getSegmentsForOutput()[0];
kj::Array<word> trunc = kj::heapArray<word>(output.size());
memcpy(trunc.begin(), output.begin(), output.asBytes().size());
return trunc;
}
CapTableReader* StructReader::getCapTable() { CapTableReader* StructReader::getCapTable() {
return capTable; return capTable;
} }
...@@ -2610,6 +2755,49 @@ StructReader StructReader::imbue(CapTableReader* capTable) const { ...@@ -2610,6 +2755,49 @@ StructReader StructReader::imbue(CapTableReader* capTable) const {
return result; return result;
} }
bool StructReader::isCanonical(const word **readHead,
const word **ptrHead,
bool *dataTrunc,
bool *ptrTrunc) {
if (this->getLocation() != *readHead) {
// Our target area is not at the readHead, preorder fails
return false;
}
if (this->getDataSectionSize() % BITS_PER_WORD != 0 * BITS) {
// Using legacy non-word-size structs, reject
return false;
}
WordCount32 dataSize = this->getDataSectionSize() / BITS_PER_WORD;
// Mark whether the struct is properly truncated
if (dataSize != 0 * WORDS) {
*dataTrunc = this->getDataField<uint64_t>((dataSize - 1 * WORDS) / WORDS * ELEMENTS) != 0;
} else {
*dataTrunc = true;
}
if (this->pointerCount != 0 * POINTERS) {
*ptrTrunc = !this->getPointerField(this->pointerCount - 1 * POINTERS).isNull();
} else {
*ptrTrunc = true;
}
// Advance the read head
*readHead += (dataSize + (this->pointerCount * WORDS_PER_POINTER)) / WORDS;
// Check each pointer field for canonicity
for (WirePointerCount16 ptrIndex = 0 * POINTERS;
ptrIndex < this->pointerCount;
ptrIndex += POINTERS) {
if (!this->getPointerField(ptrIndex).isCanonical(ptrHead)) {
return false;
}
}
return true;
}
// ======================================================================================= // =======================================================================================
// ListBuilder // ListBuilder
...@@ -2749,6 +2937,74 @@ ListReader ListReader::imbue(CapTableReader* capTable) const { ...@@ -2749,6 +2937,74 @@ ListReader ListReader::imbue(CapTableReader* capTable) const {
return result; return result;
} }
bool ListReader::isCanonical(const word **readHead) {
switch (this->getElementSize()) {
case ElementSize::INLINE_COMPOSITE: {
*readHead += 1;
if (reinterpret_cast<const word*>(this->ptr) != *readHead) {
// The next word to read is the tag word, but the pointer is in
// front of it, so our check is slightly different
return false;
}
if (this->structDataSize % BITS_PER_WORD != 0 * BITS) {
return false;
}
auto structSize = (this->structDataSize / BITS_PER_WORD) +
(this->structPointerCount * WORDS_PER_POINTER);
auto listEnd = *readHead + (this->elementCount / ELEMENTS * structSize) / WORDS;
auto pointerHead = listEnd;
bool listDataTrunc = false;
bool listPtrTrunc = false;
for (ElementCount ec = ElementCount(0);
ec < this->elementCount;
ec += 1 * ELEMENTS) {
bool dataTrunc, ptrTrunc;
if (!this->getStructElement(ec).isCanonical(readHead,
&pointerHead,
&dataTrunc,
&ptrTrunc)) {
return false;
}
listDataTrunc |= dataTrunc;
listPtrTrunc |= ptrTrunc;
}
KJ_REQUIRE(*readHead == listEnd, *readHead, listEnd);
*readHead = pointerHead;
return listDataTrunc && listPtrTrunc;
}
case ElementSize::POINTER: {
if (reinterpret_cast<const word*>(this->ptr) != *readHead) {
return false;
}
*readHead += this->elementCount * (POINTERS / ELEMENTS) * WORDS_PER_POINTER / WORDS;
for (ElementCount ec = ElementCount(0);
ec < this->elementCount;
ec += 1 * ELEMENTS) {
if (!this->getPointerElement(ec).isCanonical(readHead)) {
return false;
}
}
return true;
}
default: {
if (reinterpret_cast<const word*>(this->ptr) != *readHead) {
return false;
}
auto bitSize = this->elementCount *
dataBitsPerElement(this->elementSize);
auto wordSize = bitSize / BITS_PER_WORD;
if (bitSize % BITS_PER_WORD != 0 * BITS) {
wordSize = wordSize + 1 * WORDS;
}
*readHead += wordSize;
return true;
}
}
KJ_UNREACHABLE;
}
// ======================================================================================= // =======================================================================================
// OrphanBuilder // OrphanBuilder
......
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors // Copyright (c) 2013-2016 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License: // Licensed under the MIT License:
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
...@@ -339,8 +339,8 @@ public: ...@@ -339,8 +339,8 @@ public:
// Init methods: Initialize the pointer to a newly-allocated object, discarding the existing // Init methods: Initialize the pointer to a newly-allocated object, discarding the existing
// object. // object.
void setStruct(const StructReader& value); void setStruct(const StructReader& value, bool canonical = false);
void setList(const ListReader& value); void setList(const ListReader& value, bool canonical = false);
template <typename T> void setBlob(typename T::Reader value); template <typename T> void setBlob(typename T::Reader value);
#if !CAPNP_LITE #if !CAPNP_LITE
void setCapability(kj::Own<ClientHook>&& cap); void setCapability(kj::Own<ClientHook>&& cap);
...@@ -360,8 +360,10 @@ public: ...@@ -360,8 +360,10 @@ public:
void transferFrom(PointerBuilder other); void transferFrom(PointerBuilder other);
// Equivalent to `adopt(other.disown())`. // Equivalent to `adopt(other.disown())`.
void copyFrom(PointerReader other); void copyFrom(PointerReader other, bool canonical = false);
// Equivalent to `set(other.get())`. // Equivalent to `set(other.get())`.
// If you set the canonical flag, it will attempt to lay the target out
// canonically, provided enough space is available.
PointerReader asReader() const; PointerReader asReader() const;
...@@ -436,6 +438,13 @@ public: ...@@ -436,6 +438,13 @@ public:
PointerReader imbue(CapTableReader* capTable) const; PointerReader imbue(CapTableReader* capTable) const;
// Return a copy of this reader except using the given capability context. // Return a copy of this reader except using the given capability context.
bool isCanonical(const word **readHead);
// Validate this pointer's canonicity, subject to the conditions:
// * All data to the left of readHead has been read thus far (for pointer
// ordering)
// * All pointers in preorder have already been checked
// * This pointer is in the first and only segment of the message
private: private:
SegmentReader* segment; // Memory segment in which the pointer resides. SegmentReader* segment; // Memory segment in which the pointer resides.
CapTableReader* capTable; // Table of capability indexes. CapTableReader* capTable; // Table of capability indexes.
...@@ -562,6 +571,8 @@ public: ...@@ -562,6 +571,8 @@ public:
inline kj::ArrayPtr<const byte> getDataSectionAsBlob(); inline kj::ArrayPtr<const byte> getDataSectionAsBlob();
inline _::ListReader getPointerSectionAsList(); inline _::ListReader getPointerSectionAsList();
kj::Array<word> canonicalize();
template <typename T> template <typename T>
KJ_ALWAYS_INLINE(bool hasDataField(ElementCount offset) const); KJ_ALWAYS_INLINE(bool hasDataField(ElementCount offset) const);
// Return true if the field is set to something other than its default value. // Return true if the field is set to something other than its default value.
...@@ -595,6 +606,21 @@ public: ...@@ -595,6 +606,21 @@ public:
StructReader imbue(CapTableReader* capTable) const; StructReader imbue(CapTableReader* capTable) const;
// Return a copy of this reader except using the given capability context. // Return a copy of this reader except using the given capability context.
bool isCanonical(const word **readHead, const word **ptrHead,
bool *dataTrunc, bool *ptrTrunc);
// Validate this pointer's canonicity, subject to the conditions:
// * All data to the left of readHead has been read thus far (for pointer
// ordering)
// * All pointers in preorder have already been checked
// * This pointer is in the first and only segment of the message
//
// If this function returns false, the struct is non-canonical. If it
// returns true, then:
// * If it is a composite in a list, it is canonical if at least one struct
// in the list outputs dataTrunc = 1, and at least one outputs ptrTrunc = 1
// * If it is derived from a struct pointer, it is canonical if
// dataTrunc = 1 AND ptrTrunc = 1
private: private:
SegmentReader* segment; // Memory segment in which the struct resides. SegmentReader* segment; // Memory segment in which the struct resides.
CapTableReader* capTable; // Table of capability indexes. CapTableReader* capTable; // Table of capability indexes.
...@@ -743,6 +769,13 @@ public: ...@@ -743,6 +769,13 @@ public:
ListReader imbue(CapTableReader* capTable) const; ListReader imbue(CapTableReader* capTable) const;
// Return a copy of this reader except using the given capability context. // Return a copy of this reader except using the given capability context.
bool isCanonical(const word **readHead);
// Validate this pointer's canonicity, subject to the conditions:
// * All data to the left of readHead has been read thus far (for pointer
// ordering)
// * All pointers in preorder have already been checked
// * This pointer is in the first and only segment of the message
private: private:
SegmentReader* segment; // Memory segment in which the list resides. SegmentReader* segment; // Memory segment in which the list resides.
CapTableReader* capTable; // Table of capability indexes. CapTableReader* capTable; // Table of capability indexes.
......
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors // Copyright (c) 2013-2016 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License: // Licensed under the MIT License:
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
...@@ -53,6 +53,34 @@ MessageReader::~MessageReader() noexcept(false) { ...@@ -53,6 +53,34 @@ MessageReader::~MessageReader() noexcept(false) {
} }
} }
bool MessageReader::isCanonical() {
if (!allocatedArena) {
static_assert(sizeof(_::ReaderArena) <= sizeof(arenaSpace),
"arenaSpace is too small to hold a ReaderArena. Please increase it. This will break "
"ABI compatibility.");
new(arena()) _::ReaderArena(this);
allocatedArena = true;
}
_::SegmentReader *segment = arena()->tryGetSegment(_::SegmentId(0));
if (segment == NULL) {
// The message has no segments
return false;
}
if (arena()->tryGetSegment(_::SegmentId(1))) {
// The message has more than one segment
return false;
}
const word* readHead = segment->getStartPtr() + 1;
return _::PointerReader::getRoot(segment, nullptr, segment->getStartPtr(),
this->getOptions().nestingLimit)
.isCanonical(&readHead);
}
AnyPointer::Reader MessageReader::getRootInternal() { AnyPointer::Reader MessageReader::getRootInternal() {
if (!allocatedArena) { if (!allocatedArena) {
static_assert(sizeof(_::ReaderArena) <= sizeof(arenaSpace), static_assert(sizeof(_::ReaderArena) <= sizeof(arenaSpace),
...@@ -132,6 +160,24 @@ Orphanage MessageBuilder::getOrphanage() { ...@@ -132,6 +160,24 @@ Orphanage MessageBuilder::getOrphanage() {
return Orphanage(arena(), arena()->getLocalCapTable()); return Orphanage(arena(), arena()->getLocalCapTable());
} }
bool MessageBuilder::isCanonical() {
_::SegmentReader *segment = getRootSegment();
if (segment == NULL) {
// The message has no segments
return false;
}
if (arena()->tryGetSegment(_::SegmentId(1))) {
// The message has more than one segment
return false;
}
const word* readHead = segment->getStartPtr() + 1;
return _::PointerReader::getRoot(segment, nullptr, segment->getStartPtr(), kj::maxValue)
.isCanonical(&readHead);
}
// ======================================================================================= // =======================================================================================
SegmentArrayMessageReader::SegmentArrayMessageReader( SegmentArrayMessageReader::SegmentArrayMessageReader(
......
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors // Copyright (c) 2013-2016 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License: // Licensed under the MIT License:
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <kj/common.h> #include <kj/common.h>
#include <kj/memory.h> #include <kj/memory.h>
#include <kj/mutex.h> #include <kj/mutex.h>
#include <kj/debug.h>
#include "common.h" #include "common.h"
#include "layout.h" #include "layout.h"
#include "any.h" #include "any.h"
...@@ -118,6 +119,9 @@ public: ...@@ -118,6 +119,9 @@ public:
// RootType in this case must be DynamicStruct, and you must #include <capnp/dynamic.h> to // RootType in this case must be DynamicStruct, and you must #include <capnp/dynamic.h> to
// use this. // use this.
bool isCanonical();
// Returns whether the message encoded in the reader is in canonical form.
private: private:
ReaderOptions options; ReaderOptions options;
...@@ -220,6 +224,9 @@ public: ...@@ -220,6 +224,9 @@ public:
Orphanage getOrphanage(); Orphanage getOrphanage();
bool isCanonical();
// Check whether the message builder is in canonical form
private: private:
void* arenaSpace[22]; void* arenaSpace[22];
// Space in which we can construct a BuilderArena. We don't use BuilderArena directly here // Space in which we can construct a BuilderArena. We don't use BuilderArena directly here
...@@ -491,6 +498,11 @@ static typename Type::Reader defaultValue() { ...@@ -491,6 +498,11 @@ static typename Type::Reader defaultValue() {
return typename Type::Reader(_::StructReader()); return typename Type::Reader(_::StructReader());
} }
template <typename T>
kj::Array<word> canonicalize(T&& reader) {
return _::PointerHelpers<FromReader<T>>::getInternalReader(reader).canonicalize();
}
} // namespace capnp } // namespace capnp
#endif // CAPNP_MESSAGE_H_ #endif // CAPNP_MESSAGE_H_
...@@ -48,6 +48,9 @@ struct PointerHelpers<T, Kind::STRUCT> { ...@@ -48,6 +48,9 @@ struct PointerHelpers<T, Kind::STRUCT> {
static inline void set(PointerBuilder builder, typename T::Reader value) { static inline void set(PointerBuilder builder, typename T::Reader value) {
builder.setStruct(value._reader); builder.setStruct(value._reader);
} }
static inline void setCanonical(PointerBuilder builder, typename T::Reader value) {
builder.setStruct(value._reader, true);
}
static inline typename T::Builder init(PointerBuilder builder) { static inline typename T::Builder init(PointerBuilder builder) {
return typename T::Builder(builder.initStruct(structSize<T>())); return typename T::Builder(builder.initStruct(structSize<T>()));
} }
...@@ -78,6 +81,9 @@ struct PointerHelpers<List<T>, Kind::LIST> { ...@@ -78,6 +81,9 @@ struct PointerHelpers<List<T>, Kind::LIST> {
static inline void set(PointerBuilder builder, typename List<T>::Reader value) { static inline void set(PointerBuilder builder, typename List<T>::Reader value) {
builder.setList(value.reader); builder.setList(value.reader);
} }
static inline void setCanonical(PointerBuilder builder, typename List<T>::Reader value) {
builder.setList(value.reader, true);
}
static void set(PointerBuilder builder, kj::ArrayPtr<const ReaderFor<T>> value) { static void set(PointerBuilder builder, kj::ArrayPtr<const ReaderFor<T>> value) {
auto l = init(builder, value.size()); auto l = init(builder, value.size());
uint i = 0; uint i = 0;
...@@ -117,6 +123,9 @@ struct PointerHelpers<T, Kind::BLOB> { ...@@ -117,6 +123,9 @@ struct PointerHelpers<T, Kind::BLOB> {
static inline void set(PointerBuilder builder, typename T::Reader value) { static inline void set(PointerBuilder builder, typename T::Reader value) {
builder.setBlob<T>(value); builder.setBlob<T>(value);
} }
static inline void setCanonical(PointerBuilder builder, typename T::Reader value) {
builder.setBlob<T>(value);
}
static inline typename T::Builder init(PointerBuilder builder, uint size) { static inline typename T::Builder init(PointerBuilder builder, uint size) {
return builder.initBlob<T>(size * BYTES); return builder.initBlob<T>(size * BYTES);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment