Commit 98e6519e authored by Kenton Varda's avatar Kenton Varda

Refactor value expression compilation code using new Orphan<DynamicValue>.

parent c1f3e3ff
This diff is collapsed.
......@@ -109,6 +109,7 @@ public:
private:
const Resolver& resolver;
const ErrorReporter& errorReporter;
Orphanage orphanage;
bool compileAnnotations;
Orphan<schema::Node> wipNode;
......@@ -168,15 +169,23 @@ private:
schema::Value::Builder target, bool isBootstrap);
// Interprets the value expression and initializes `target` with the result.
class DynamicSlot;
kj::Maybe<Orphan<DynamicValue>> compileValue(
ValueExpression::Reader src, schema::Type::Reader type, bool isBootstrap);
// Compile the given value as the given type. Returns null if there was an error, including
// if the value doesn't match the type.
void compileValue(ValueExpression::Reader src, DynamicSlot& dst, bool isBootstrap);
// Fill in `dst` (which effectively points to a struct field or list element) with the given
// value.
void compileValueInner(ValueExpression::Reader src, DynamicSlot& dst, bool isBootstrap);
Orphan<DynamicValue> compileValueInner(ValueExpression::Reader src, schema::Type::Reader type,
bool isBootstrap);
// Helper for compileValue().
void fillStructValue(DynamicStruct::Builder builder,
List<ValueExpression::FieldAssignment>::Reader assignments,
bool isBootstrap);
// Interprets the given assignments and uses them to fill in the given struct builder.
kj::String makeNodeName(uint64_t id);
kj::String makeTypeName(schema::Type::Reader type);
kj::Maybe<DynamicValue::Reader> readConstant(DeclName::Reader name, bool isBootstrap,
ValueExpression::Reader errorLocation);
// Get the value of the given constant. May return null if some error occurs, which will already
......
......@@ -558,10 +558,12 @@ void DynamicStruct::Builder::set(StructSchema::Field field, const DynamicValue::
return;
case schema::Type::LIST:
// TODO(soon): Type check.
builder.setListField(nonGroup.getOffset() * POINTERS, value.as<DynamicList>().reader);
return;
case schema::Type::STRUCT:
// TODO(soon): Type check.
builder.setStructField(
nonGroup.getOffset() * POINTERS, value.as<DynamicStruct>().reader);
return;
......@@ -697,11 +699,11 @@ void DynamicStruct::Builder::adopt(StructSchema::Field field, Orphan<DynamicValu
return;
case schema::Type::TEXT:
orphan.getReader().as<Text>(); // type check
KJ_REQUIRE(orphan.getType() == DynamicValue::TEXT, "Value type mismatch.");
break;
case schema::Type::DATA:
orphan.getReader().as<Data>(); // type check
KJ_REQUIRE(orphan.getType() == DynamicValue::DATA, "Value type mismatch.");
break;
case schema::Type::LIST: {
......@@ -869,7 +871,7 @@ void DynamicStruct::Builder::clear(StructSchema::Field field) {
case schema::Type::LIST:
case schema::Type::STRUCT:
case schema::Type::OBJECT:
builder.disown(nonGroup.getOffset() * POINTERS);
builder.clearPointer(nonGroup.getOffset() * POINTERS);
return;
case schema::Type::INTERFACE:
......@@ -1199,17 +1201,16 @@ void DynamicList::Builder::set(uint index, const DynamicValue::Reader& value) {
return;
case schema::Type::LIST: {
// TODO(soon): Type check.
builder.setListElement(index * ELEMENTS, value.as<DynamicList>().reader);
return;
}
case schema::Type::STRUCT:
// Not supported for the same reason List<struct> doesn't support it -- the space for the
// element is already allocated, and if it's smaller than the input value the copy would
// have to be lossy.
KJ_FAIL_ASSERT("DynamicList of structs does not support set().") {
return;
}
case schema::Type::STRUCT: {
// TODO(soon): Type check.
builder.getStructElement(index * ELEMENTS).copyContentFrom(value.as<DynamicStruct>().reader);
return;
}
case schema::Type::ENUM: {
uint16_t rawValue;
......@@ -1297,6 +1298,114 @@ DynamicValue::Builder DynamicList::Builder::init(uint index, uint size) {
return nullptr;
}
void DynamicList::Builder::adopt(uint index, Orphan<DynamicValue>&& orphan) {
switch (schema.whichElementType()) {
case schema::Type::VOID:
case schema::Type::BOOL:
case schema::Type::INT8:
case schema::Type::INT16:
case schema::Type::INT32:
case schema::Type::INT64:
case schema::Type::UINT8:
case schema::Type::UINT16:
case schema::Type::UINT32:
case schema::Type::UINT64:
case schema::Type::FLOAT32:
case schema::Type::FLOAT64:
case schema::Type::ENUM:
set(index, orphan.getReader());
return;
case schema::Type::TEXT:
KJ_REQUIRE(orphan.getType() == DynamicValue::TEXT, "Value type mismatch.");
builder.adopt(index * ELEMENTS, kj::mv(orphan.builder));
return;
case schema::Type::DATA:
KJ_REQUIRE(orphan.getType() == DynamicValue::DATA, "Value type mismatch.");
builder.adopt(index * ELEMENTS, kj::mv(orphan.builder));
return;
case schema::Type::LIST: {
ListSchema elementType = schema.getListElementType();
KJ_REQUIRE(orphan.getType() == DynamicValue::LIST && orphan.listSchema == elementType,
"Value type mismatch.");
builder.adopt(index * ELEMENTS, kj::mv(orphan.builder));
return;
}
case schema::Type::STRUCT: {
auto elementType = schema.getStructElementType();
KJ_REQUIRE(orphan.getType() == DynamicValue::STRUCT && orphan.structSchema == elementType,
"Value type mismatch.");
builder.getStructElement(index * ELEMENTS).transferContentFrom(
orphan.builder.asStruct(structSizeFromSchema(elementType)));
return;
}
case schema::Type::OBJECT:
KJ_FAIL_ASSERT("List(Object) not supported.");
case schema::Type::INTERFACE:
KJ_FAIL_ASSERT("Interfaces not yet implemented.");
}
KJ_UNREACHABLE;
}
Orphan<DynamicValue> DynamicList::Builder::disown(uint index) {
switch (schema.whichElementType()) {
case schema::Type::VOID:
case schema::Type::BOOL:
case schema::Type::INT8:
case schema::Type::INT16:
case schema::Type::INT32:
case schema::Type::INT64:
case schema::Type::UINT8:
case schema::Type::UINT16:
case schema::Type::UINT32:
case schema::Type::UINT64:
case schema::Type::FLOAT32:
case schema::Type::FLOAT64:
case schema::Type::ENUM: {
auto result = Orphan<DynamicValue>(operator[](index), _::OrphanBuilder());
switch (elementSizeFor(schema.whichElementType())) {
case _::FieldSize::VOID: break;
case _::FieldSize::BIT: builder.setDataElement<bool>(index * ELEMENTS, false); break;
case _::FieldSize::BYTE: builder.setDataElement<uint8_t>(index * ELEMENTS, 0); break;
case _::FieldSize::TWO_BYTES: builder.setDataElement<uint16_t>(index * ELEMENTS, 0); break;
case _::FieldSize::FOUR_BYTES: builder.setDataElement<uint32_t>(index * ELEMENTS, 0); break;
case _::FieldSize::EIGHT_BYTES: builder.setDataElement<uint64_t>(index * ELEMENTS, 0);break;
case _::FieldSize::POINTER:
case _::FieldSize::INLINE_COMPOSITE:
KJ_UNREACHABLE;
}
return kj::mv(result);
}
case schema::Type::TEXT:
case schema::Type::DATA:
case schema::Type::LIST:
case schema::Type::OBJECT:
case schema::Type::INTERFACE: {
auto value = operator[](index);
return Orphan<DynamicValue>(value, builder.disown(index * ELEMENTS));
}
case schema::Type::STRUCT: {
// We have to make a copy.
Orphan<DynamicStruct> result =
Orphanage::getForMessageContaining(*this).newOrphan(schema.getStructElementType());
auto element = builder.getStructElement(index * ELEMENTS);
result.get().builder.transferContentFrom(element);
element.clearAll();
return kj::mv(result);
}
}
KJ_UNREACHABLE;
}
void DynamicList::Builder::copyFrom(std::initializer_list<DynamicValue::Reader> value) {
KJ_REQUIRE(value.size() == size(), "DynamicList::copyFrom() argument had different size.");
uint i = 0;
......@@ -1793,12 +1902,50 @@ DynamicValue::Reader Orphan<DynamicValue>::getReader() const {
template <>
Orphan<DynamicStruct> Orphan<DynamicValue>::releaseAs<DynamicStruct>() {
KJ_REQUIRE(type == DynamicValue::STRUCT, "Value type mismatch.");
type = DynamicValue::UNKNOWN;
return Orphan<DynamicStruct>(structSchema, kj::mv(builder));
}
template <>
Orphan<DynamicList> Orphan<DynamicValue>::releaseAs<DynamicList>() {
KJ_REQUIRE(type == DynamicValue::LIST, "Value type mismatch.");
type = DynamicValue::UNKNOWN;
return Orphan<DynamicList>(listSchema, kj::mv(builder));
}
template <>
Orphan<DynamicObject> Orphanage::newOrphanCopy<DynamicObject::Reader>(
const DynamicObject::Reader& copyFrom) const {
switch (copyFrom.reader.kind) {
case _::ObjectKind::NULL_POINTER:
return Orphan<DynamicObject>();
case _::ObjectKind::STRUCT:
return Orphan<DynamicObject>(_::OrphanBuilder::copy(arena, copyFrom.reader.structReader));
case _::ObjectKind::LIST:
return Orphan<DynamicObject>(_::OrphanBuilder::copy(arena, copyFrom.reader.listReader));
}
KJ_UNREACHABLE;
}
template <>
Orphan<DynamicValue> Orphanage::newOrphanCopy<DynamicValue::Reader>(
const DynamicValue::Reader& copyFrom) const {
switch (copyFrom.getType()) {
case DynamicValue::UNKNOWN: return nullptr;
case DynamicValue::VOID: return copyFrom.voidValue;
case DynamicValue::BOOL: return copyFrom.boolValue;
case DynamicValue::INT: return copyFrom.intValue;
case DynamicValue::UINT: return copyFrom.uintValue;
case DynamicValue::FLOAT: return copyFrom.floatValue;
case DynamicValue::ENUM: return copyFrom.enumValue;
case DynamicValue::TEXT: return newOrphanCopy(copyFrom.textValue);
case DynamicValue::DATA: return newOrphanCopy(copyFrom.dataValue);
case DynamicValue::LIST: return newOrphanCopy(copyFrom.listValue);
case DynamicValue::STRUCT: return newOrphanCopy(copyFrom.structValue);
case DynamicValue::INTERFACE: KJ_FAIL_ASSERT("Interfaces not implemented.");
case DynamicValue::OBJECT: return newOrphanCopy(copyFrom.objectValue);
}
KJ_UNREACHABLE;
}
} // namespace capnp
......@@ -150,6 +150,8 @@ class DynamicObject::Reader {
// Represents an "Object" field of unknown type.
public:
typedef DynamicObject Reads;
Reader() = default;
template <typename T>
......@@ -171,6 +173,7 @@ private:
friend class DynamicObject::Builder;
friend class Orphan<DynamicObject>;
friend class Orphan<DynamicValue>;
friend class Orphanage;
};
class DynamicObject::Builder: public kj::DisallowConstCopy {
......@@ -182,6 +185,8 @@ class DynamicObject::Builder: public kj::DisallowConstCopy {
// DynamicStruct::Builder::{get,set,init}Object() and pass a type schema to build object fields.
public:
typedef DynamicObject Builds;
Builder() = default;
Builder(Builder&) = default;
Builder(Builder&&) = default;
......@@ -313,8 +318,6 @@ public:
// Clear a field, setting it to its default value. For pointer fields, this actually makes the
// field null.
// TODO(someday): Implement adopt() and disown().
DynamicStruct::Builder getObject(StructSchema::Field field, StructSchema type);
DynamicList::Builder getObject(StructSchema::Field field, ListSchema type);
Text::Builder getObjectAsText(StructSchema::Field field);
......@@ -437,7 +440,8 @@ public:
DynamicValue::Builder operator[](uint index);
void set(uint index, const DynamicValue::Reader& value);
DynamicValue::Builder init(uint index, uint size);
// TODO(someday): Implement adopt() and disown().
void adopt(uint index, Orphan<DynamicValue>&& orphan);
Orphan<DynamicValue> disown(uint index);
typedef _::IndexingIterator<Builder, DynamicStruct::Builder> Iterator;
inline Iterator begin() { return Iterator(this, 0); }
......@@ -484,7 +488,7 @@ class DynamicValue::Reader {
public:
typedef DynamicValue Reads;
inline Reader(std::nullptr_t n = nullptr); // UNKNOWN
inline Reader(decltype(nullptr) n = nullptr); // UNKNOWN
inline Reader(Void value);
inline Reader(bool value);
inline Reader(char value);
......@@ -557,13 +561,15 @@ private:
template <typename T, Kind kind = kind<T>()> struct AsImpl;
// Implementation backing the as() method. Needs to be a struct to allow partial
// specialization. Has a method apply() which does the work.
friend class Orphanage; // to speed up newOrphanCopy(DynamicValue::Reader)
};
class DynamicValue::Builder {
public:
typedef DynamicValue Builds;
inline Builder(std::nullptr_t n = nullptr); // UNKNOWN
inline Builder(decltype(nullptr) n = nullptr); // UNKNOWN
inline Builder(Void value);
inline Builder(bool value);
inline Builder(char value);
......@@ -757,16 +763,34 @@ private:
template <typename, Kind>
friend struct _::PointerHelpers;
friend class Orphan<DynamicValue>;
friend class Orphanage;
};
template <>
class Orphan<DynamicValue> {
public:
Orphan() = default;
KJ_DISALLOW_COPY(Orphan);
inline Orphan(decltype(nullptr) n = nullptr): type(DynamicValue::UNKNOWN) {}
inline Orphan(Void value);
inline Orphan(bool value);
inline Orphan(char value);
inline Orphan(signed char value);
inline Orphan(short value);
inline Orphan(int value);
inline Orphan(long value);
inline Orphan(long long value);
inline Orphan(unsigned char value);
inline Orphan(unsigned short value);
inline Orphan(unsigned int value);
inline Orphan(unsigned long value);
inline Orphan(unsigned long long value);
inline Orphan(float value);
inline Orphan(double value);
inline Orphan(DynamicEnum value);
Orphan(Orphan&&) = default;
template <typename T>
Orphan(Orphan<T>&&);
KJ_DISALLOW_COPY(Orphan);
Orphan& operator=(Orphan&&) = default;
inline DynamicValue::Type getType() { return type; }
......@@ -780,9 +804,6 @@ public:
// the original Orphan<DynamicStruct> is no longer valid after this call; ownership is
// transferred to the returned Orphan<T>.
inline bool operator==(decltype(nullptr)) const { return builder == nullptr; }
inline bool operator!=(decltype(nullptr)) const { return builder != nullptr; }
private:
DynamicValue::Type type;
union {
......@@ -851,6 +872,7 @@ Orphan<T> Orphan<DynamicList>::releaseAs() {
template <typename T>
Orphan<T> Orphan<DynamicValue>::releaseAs() {
get().as<T>(); // type check
type = DynamicValue::UNKNOWN;
return Orphan<T>(kj::mv(builder));
}
......@@ -886,6 +908,14 @@ inline Orphan<DynamicList> Orphanage::newOrphanCopy<DynamicList::Reader>(
return Orphan<DynamicList>(copyFrom.getSchema(), _::OrphanBuilder::copy(arena, copyFrom.reader));
}
template <>
Orphan<DynamicObject> Orphanage::newOrphanCopy<DynamicObject::Reader>(
const DynamicObject::Reader& copyFrom) const;
template <>
Orphan<DynamicValue> Orphanage::newOrphanCopy<DynamicValue::Reader>(
const DynamicValue::Reader& copyFrom) const;
// -------------------------------------------------------------------
// Inject the ability to use DynamicStruct for message roots and Dynamic{Struct,List} for
// generated Object accessors.
......@@ -1001,11 +1031,13 @@ DynamicTypeFor<TypeIfEnum<T>> toDynamic(T&& value) {
inline DynamicValue::Reader::Reader(std::nullptr_t n): type(UNKNOWN) {}
inline DynamicValue::Builder::Builder(std::nullptr_t n): type(UNKNOWN) {}
#define CAPNP_DECLARE_DYNAMIC_VALUE_CONSTRUCTOR(cppType, typeTag, fieldName) \
#define CAPNP_DECLARE_DYNAMIC_VALUE_CONSTRUCTOR(cppType, typeTag, fieldName) \
inline DynamicValue::Reader::Reader(cppType value) \
: type(typeTag), fieldName##Value(value) {} \
inline DynamicValue::Builder::Builder(cppType value) \
: type(typeTag), fieldName##Value(value) {}
: type(typeTag), fieldName##Value(value) {} \
inline Orphan<DynamicValue>::Orphan(cppType value) \
: type(DynamicValue::typeTag), fieldName##Value(value) {}
CAPNP_DECLARE_DYNAMIC_VALUE_CONSTRUCTOR(Void, VOID, void);
CAPNP_DECLARE_DYNAMIC_VALUE_CONSTRUCTOR(bool, BOOL, bool);
......
......@@ -1558,19 +1558,19 @@ struct WireHelpers {
WirePointer* tag = reinterpret_cast<WirePointer*>(ptr);
tag->setKindAndInlineCompositeListElementCount(WirePointer::STRUCT, value.elementCount);
tag->structRef.set(dataSize, pointerCount);
ptr += POINTER_SIZE_IN_WORDS;
word* dst = ptr + POINTER_SIZE_IN_WORDS;
const word* src = reinterpret_cast<const word*>(value.ptr);
for (uint i = 0; i < value.elementCount / ELEMENTS; i++) {
memcpy(ptr, src, value.structDataSize / BITS_PER_BYTE / BYTES);
ptr += dataSize;
memcpy(dst, src, value.structDataSize / BITS_PER_BYTE / BYTES);
dst += dataSize;
src += dataSize;
for (uint j = 0; j < pointerCount / POINTERS; j++) {
setObjectPointer(segment, reinterpret_cast<WirePointer*>(ptr), readObjectPointer(
setObjectPointer(segment, reinterpret_cast<WirePointer*>(dst), readObjectPointer(
value.segment, reinterpret_cast<const WirePointer*>(src), nullptr,
value.nestingLimit));
ptr += POINTER_SIZE_IN_WORDS;
dst += POINTER_SIZE_IN_WORDS;
src += POINTER_SIZE_IN_WORDS;
}
}
......@@ -2152,6 +2152,19 @@ void StructBuilder::clearPointer(WirePointerCount ptrIndex) {
memset(pointers + ptrIndex, 0, sizeof(WirePointer));
}
void StructBuilder::clearAll() {
if (dataSize == 1 * BITS) {
setDataField<bool>(1 * ELEMENTS, false);
} else {
memset(data, 0, dataSize / BITS_PER_BYTE / BYTES);
}
for (uint i = 0; i < pointerCount / POINTERS; i++) {
WireHelpers::zeroObject(segment, pointers + i);
}
memset(pointers, 0, pointerCount * BYTES_PER_POINTER / BYTES);
}
void StructBuilder::transferContentFrom(StructBuilder other) {
// Determine the amount of data the builders have in common.
BitCount sharedDataSize = kj::min(dataSize, other.dataSize);
......@@ -2679,6 +2692,8 @@ ListBuilder OrphanBuilder::asStructList(StructSize elementSize) {
// Watch out, the pointer could have been updated if the object had to be relocated.
if (tagAsPtr()->kind() == WirePointer::FAR) {
location = nullptr;
} else if (result.step * ELEMENTS <= BITS_PER_WORD * WORDS) {
location = reinterpret_cast<word*>(result.ptr);
} else {
location = reinterpret_cast<word*>(result.ptr) - POINTER_SIZE_IN_WORDS;
}
......@@ -2748,16 +2763,24 @@ ObjectReader OrphanBuilder::asObjectReader() const {
}
void OrphanBuilder::euthanize() {
auto ref = reinterpret_cast<WirePointer*>(&tag);
if (ref->kind() == WirePointer::FAR) {
WireHelpers::zeroObject(segment, ref);
} else {
WireHelpers::zeroObject(segment, reinterpret_cast<WirePointer*>(&tag), location);
}
// Carefully catch any exceptions and rethrow them as recoverable exceptions since we may be in
// a destructor.
auto exception = kj::runCatchingExceptions([&]() {
auto ref = reinterpret_cast<WirePointer*>(&tag);
if (ref->kind() == WirePointer::FAR) {
WireHelpers::zeroObject(segment, ref);
} else {
WireHelpers::zeroObject(segment, reinterpret_cast<WirePointer*>(&tag), location);
}
memset(ref, 0, sizeof(*ref));
segment = nullptr;
location = nullptr;
memset(ref, 0, sizeof(*ref));
segment = nullptr;
location = nullptr;
});
KJ_IF_MAYBE(e, exception) {
kj::getExceptionCallback().onRecoverableException(kj::mv(*e));
}
}
} // namespace _ (private)
......
......@@ -376,6 +376,9 @@ public:
void clearPointer(WirePointerCount ptrIndex);
// Equivalent to calling disown() and letting the result simply be destroyed.
void clearAll();
// Clear all pointers and data.
void transferContentFrom(StructBuilder other);
// Adopt all pointers from `other`, and also copy all data. If `other`'s sections are larger
// than this, the extra data is not transferred, meaning there is a risk of data loss when
......
......@@ -446,14 +446,14 @@ TEST(Orphans, DynamicStructAs) {
Orphan<DynamicValue> orphan =
root.disownObjectField<DynamicStruct>(Schema::from<TestAllTypes>());
EXPECT_FALSE(orphan == nullptr);
EXPECT_EQ(DynamicValue::STRUCT, orphan.getType());
checkTestMessage(orphan.getReader().as<TestAllTypes>());
checkTestMessage(orphan.get().as<TestAllTypes>());
{
Orphan<DynamicStruct> structOrphan = orphan.releaseAs<DynamicStruct>();
EXPECT_TRUE(orphan == nullptr);
EXPECT_EQ(DynamicValue::UNKNOWN, orphan.getType());
EXPECT_FALSE(structOrphan == nullptr);
checkDynamicTestMessage(structOrphan.getReader());
checkDynamicTestMessage(structOrphan.get());
......@@ -467,7 +467,7 @@ TEST(Orphans, DynamicStructAs) {
checkTestMessage(typedOrphan.getReader());
checkTestMessage(typedOrphan.get());
orphan = kj::mv(typedOrphan);
EXPECT_FALSE(orphan == nullptr);
EXPECT_EQ(DynamicValue::STRUCT, orphan.getType());
EXPECT_TRUE(typedOrphan == nullptr);
}
}
......@@ -487,14 +487,14 @@ TEST(Orphans, DynamicListAs) {
EXPECT_TRUE(root.hasObjectField());
Orphan<DynamicValue> orphan = root.disownObjectField<DynamicList>(Schema::from<List<uint32_t>>());
EXPECT_FALSE(orphan == nullptr);
EXPECT_EQ(DynamicValue::LIST, orphan.getType());
checkList(orphan.getReader().as<List<uint32_t>>(), {12, 34, 56});
checkList(orphan.get().as<List<uint32_t>>(), {12, 34, 56});
{
Orphan<DynamicList> listOrphan = orphan.releaseAs<DynamicList>();
EXPECT_TRUE(orphan == nullptr);
EXPECT_EQ(DynamicValue::UNKNOWN, orphan.getType());
EXPECT_FALSE(listOrphan == nullptr);
checkList<uint32_t>(listOrphan.getReader(), {12, 34, 56});
checkList<uint32_t>(listOrphan.get(), {12, 34, 56});
......@@ -508,7 +508,7 @@ TEST(Orphans, DynamicListAs) {
checkList(typedOrphan.getReader(), {12, 34, 56});
checkList(typedOrphan.get(), {12, 34, 56});
orphan = kj::mv(typedOrphan);
EXPECT_FALSE(orphan == nullptr);
EXPECT_EQ(DynamicValue::LIST, orphan.getType());
EXPECT_TRUE(typedOrphan == nullptr);
}
}
......@@ -528,7 +528,7 @@ TEST(Orphans, DynamicObject) {
EXPECT_TRUE(root.hasObjectField());
Orphan<DynamicValue> orphan = root.disownObjectField<DynamicObject>();
EXPECT_FALSE(orphan == nullptr);
EXPECT_EQ(DynamicValue::OBJECT, orphan.getType());
checkTestMessage(orphan.getReader().as<DynamicObject>().as<TestAllTypes>());
......
......@@ -326,7 +326,12 @@ public:
#if KJ_NO_EXCEPTIONS
logException(mv(exception));
#else
throw ExceptionImpl(mv(exception));
if (std::uncaught_exception()) {
// Bad time to throw an exception. Just log instead.
logException(mv(exception));
} else {
throw ExceptionImpl(mv(exception));
}
#endif
}
......
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