Git commit 244ba3c620a304e2ade00553744bead9bb444470 by Andreas Hartmetz. Committed on 03/12/2016 at 21:29. Pushed by ahartmetz into branch 'master'. Add beginDictEntry() / endDictEntry() and corresponding states. ...as a compile time option. With only a little testing because it is mainly for use in QtDBus which already has tests. That said, the tests pass with and without WITH_DICT_ENTRY defined. M +152 -7 serialization/arguments.cpp M +18 -1 serialization/arguments.h M +96 -2 tests/serialization/tst_arguments.cpp M +5 -0 util/error.h https://commits.kde.org/dferry/244ba3c620a304e2ade00553744bead9bb444470 diff --git a/serialization/arguments.cpp b/serialization/arguments.cpp index 9da1408..5ad3148 100644 --- a/serialization/arguments.cpp +++ b/serialization/arguments.cpp @@ -53,10 +53,10 @@ static byte alignmentLog2(uint32 alignment) = static cstring printableState(Arguments::IoState state) { - if (state < Arguments::NotStarted || state > Arguments::UnixFd) { + if (state < Arguments::NotStarted || state >=3D Arguments::LastState) { return cstring(); } - static const char *strings[Arguments::UnixFd + 1] =3D { + static const char *strings[Arguments::LastState] =3D { "NotStarted", "Finished", "NeedMoreData", @@ -84,6 +84,11 @@ static cstring printableState(Arguments::IoState state) "ObjectPath", "Signature", "UnixFd" +#ifdef WITH_DICT_ENTRY + , + "BeginDictEntry", + "EndDictEntry" +#endif }; return cstring(strings[state]); } @@ -143,6 +148,16 @@ void Arguments::copyOneElement(Arguments::Reader *read= er, Arguments::Writer *wri reader->endDict(); writer->endDict(); break; +#ifdef WITH_DICT_ENTRY + case Arguments::BeginDictEntry: + reader->beginDictEntry(); + writer->beginDictEntry(); + break; + case Arguments::EndDictEntry: + reader->endDictEntry(); + writer->endDictEntry(); + break; +#endif case Arguments::Byte: writer->writeByte(reader->readByte()); break; @@ -356,9 +371,21 @@ public: MaxFullSignatureLength =3D MaxSignatureLength + 1 }; = +#ifdef WITH_DICT_ENTRY + enum DictEntryState : uint32 + { + RequireBeginDictEntry =3D 0, + InDictEntry, + RequireEndDictEntry, + AfterEndDictEntry + }; +#endif struct ArrayInfo { uint32 containedTypeBegin; // to rewind when reading the next elem= ent +#ifdef WITH_DICT_ENTRY + DictEntryState dictEntryState; +#endif }; = struct VariantInfo @@ -1312,8 +1339,19 @@ void Arguments::Reader::advanceState() if (likely(!d->m_nilArrayNesting) && d->m_dataPosition < a= rrayInfo.dataEnd) { d->m_dataPosition =3D align(d->m_dataPosition, 8); // = align to dict entry d->m_signaturePosition =3D arrayInfo.containedTypeBegi= n; +#ifdef WITH_DICT_ENTRY + d->m_signaturePosition--; + m_state =3D EndDictEntry; + m_u.Uint32 =3D 0; // meaning: more dict entries follow= (state after next is BeginDictEntry) + return; +#endif break; } +#ifdef WITH_DICT_ENTRY + m_state =3D EndDictEntry; + m_u.Uint32 =3D 1; // meaning: array end reached (state aft= er next is EndDict) + return; +#endif m_state =3D EndDict; return; } @@ -1661,9 +1699,18 @@ bool Arguments::Reader::beginDict(EmptyArrayOption o= ption) = if (unlikely(d->m_nilArrayNesting && option =3D=3D SkipIfEmpty)) { skipArrayOrDictSignature(true); +#ifdef WITH_DICT_ENTRY + const bool ret =3D !d->m_nilArrayNesting; + advanceState(); + endDictEntry(); + return ret; + } + m_state =3D BeginDictEntry; +#else } = advanceState(); +#endif return !d->m_nilArrayNesting; } = @@ -1693,6 +1740,24 @@ void Arguments::Reader::endDict() advanceState(); } = +#ifdef WITH_DICT_ENTRY +void Arguments::Reader::beginDictEntry() +{ + VALID_IF(m_state =3D=3D BeginDictEntry, Error::ReadWrongType); + advanceState(); +} + +void Arguments::Reader::endDictEntry() +{ + VALID_IF(m_state =3D=3D EndDictEntry, Error::ReadWrongType); + if (m_u.Uint32 =3D=3D 0) { + m_state =3D BeginDictEntry; + } else { + m_state =3D EndDict; + } +} +#endif + void Arguments::Reader::beginStruct() { VALID_IF(m_state =3D=3D BeginStruct, Error::ReadWrongType); @@ -1816,6 +1881,14 @@ void Arguments::Reader::skipCurrentElement() case Arguments::BeginDict: skipDict(); break; +#ifdef WITH_DICT_ENTRY + case Arguments::BeginDictEntry: + beginDictEntry(); + break; + case Arguments::EndDictEntry: + endDictEntry(); + break; +#endif case Arguments::EndDict: assert(stateOnEntry =3D=3D EndDict); // only way this can happ= en - we gracefully "skip" EndDict // and DON'T decrease nesting= Level b/c it would go negative. @@ -2160,9 +2233,8 @@ void Arguments::Writer::advanceState(cstring signatur= eFragment, IoState newState VALID_IF(d->m_signaturePosition + signatureFragment.length <=3D Ma= xSignatureLength, Error::SignatureTooLong); } - if (!d->m_aggregateStack.empty()) { - const Private::AggregateInfo &aggregateInfo =3D d->m_aggregateStac= k.back(); + Private::AggregateInfo &aggregateInfo =3D d->m_aggregateStack.back= (); switch (aggregateInfo.aggregateType) { case BeginVariant: // arrays and variants may contain just one single complete ty= pe; note that this will @@ -2183,8 +2255,43 @@ void Arguments::Writer::advanceState(cstring signatu= reFragment, IoState newState break; case BeginDict: if (d->m_signaturePosition =3D=3D aggregateInfo.arr.containedT= ypeBegin) { +#ifdef WITH_DICT_ENTRY + if (aggregateInfo.arr.dictEntryState =3D=3D Private::Requi= reBeginDictEntry) { + // This is only reached immediately after beginDict() = so it's kinda wasteful, oh well. + VALID_IF(newState =3D=3D BeginDictEntry, Error::Missin= gBeginDictEntry); + aggregateInfo.arr.dictEntryState =3D Private::InDictEn= try; + m_state =3D DictKey; + return; // BeginDictEntry writes no data + } +#endif VALID_IF(isPrimitiveType || isStringType, Error::InvalidKe= yTypeInDict); } +#ifdef WITH_DICT_ENTRY + // TODO test this part of the state machine + if (d->m_signaturePosition >=3D aggregateInfo.arr.containedTyp= eBegin + 2) { + if (aggregateInfo.arr.dictEntryState =3D=3D Private::Requi= reEndDictEntry) { + VALID_IF(newState =3D=3D EndDictEntry, Error::MissingE= ndDictEntry); + aggregateInfo.arr.dictEntryState =3D Private::AfterEnd= DictEntry; + m_state =3D BeginDictEntry; + return; // EndDictEntry writes no data + } else { + // v should've been caught earlier + assert(aggregateInfo.arr.dictEntryState =3D=3D Private= ::AfterEndDictEntry); + VALID_IF(newState =3D=3D BeginDictEntry || newState = =3D=3D EndDict, Error::MissingBeginDictEntry); + // "fall through", the rest (another iteration or fini= sh) is handled below + } + } else if (d->m_signaturePosition >=3D aggregateInfo.arr.conta= inedTypeBegin + 1) { + assert(aggregateInfo.arr.dictEntryState =3D=3D Private::In= DictEntry); + aggregateInfo.arr.dictEntryState =3D Private::RequireEndDi= ctEntry; + // Setting EndDictEntry after writing a primitive type wor= ks fine, but setting it after + // ending another aggregate would be somewhat involved and= need to happen somewhere + // else, so just don't do that. We still produce an error = when endDictEntry() is not + // used correctly. + // m_state =3D EndDictEntry; + + // continue and write the dict entry's value + } +#endif // first type has been checked already, second must be present= (checked in EndDict // state handler). no third type allowed. if (d->m_signaturePosition >=3D aggregateInfo.arr.containedTyp= eBegin + 2 @@ -2196,7 +2303,13 @@ void Arguments::Writer::advanceState(cstring signatu= reFragment, IoState newState d->m_signaturePosition =3D aggregateInfo.arr.containedType= Begin; isWritingSignature =3D false; m_state =3D DictKey; +#ifdef WITH_DICT_ENTRY + assert(newState =3D=3D BeginDictEntry); + aggregateInfo.arr.dictEntryState =3D Private::InDictEntry; + return; // BeginDictEntry writes no data +#endif } + break; default: break; @@ -2325,18 +2438,24 @@ void Arguments::Writer::advanceState(cstring signat= ureFragment, IoState newState // not re-opened before each element: there is no observable d= ifference for clients VALID_IF(d->m_nesting.beginParen(), Error::ExcessiveNesting); } + aggregateInfo.aggregateType =3D newState; aggregateInfo.arr.containedTypeBegin =3D d->m_signaturePosition; - d->m_aggregateStack.reserve(8); - d->m_aggregateStack.push_back(aggregateInfo); = d->m_elements.push_back(Private::ElementInfo(4, Private::ElementIn= fo::ArrayLengthField)); if (newState =3D=3D BeginDict) { d->m_elements.push_back(Private::ElementInfo(structAlignment, = 0)); // align to dict entry +#ifdef WITH_DICT_ENTRY + m_state =3D BeginDictEntry; + aggregateInfo.arr.dictEntryState =3D Private::RequireBeginDict= Entry; +#else m_state =3D DictKey; +#endif } - break; } = + d->m_aggregateStack.reserve(8); + d->m_aggregateStack.push_back(aggregateInfo); + break; } case EndDict: case EndArray: { const bool isDict =3D newState =3D=3D EndDict; @@ -2367,6 +2486,11 @@ void Arguments::Writer::advanceState(cstring signatu= reFragment, IoState newState // due to alignment changes from a different start address d->m_elements.push_back(Private::ElementInfo(1, Private::ElementIn= fo::ArrayLengthEndMark)); break; } +#ifdef WITH_DICT_ENTRY + case BeginDictEntry: + case EndDictEntry: + break; +#endif default: VALID_IF(false, Error::InvalidType); break; @@ -2442,6 +2566,27 @@ void Arguments::Writer::endDict() advanceState(cstring("}", strlen("}")), EndDict); } = +#ifdef WITH_DICT_ENTRY +void Arguments::Writer::beginDictEntry() +{ + VALID_IF(m_state =3D=3D BeginDictEntry, Error::MisplacedBeginDictEntry= ); + advanceState(cstring(), BeginDictEntry); +} + +void Arguments::Writer::endDictEntry() +{ + if (!d->m_aggregateStack.empty()) { + Private::AggregateInfo &aggregateInfo =3D d->m_aggregateStack.back= (); + if (aggregateInfo.aggregateType =3D=3D BeginDict + && aggregateInfo.arr.dictEntryState =3D=3D Private::RequireEnd= DictEntry) { + advanceState(cstring(), EndDictEntry); + return; + } + } + VALID_IF(false, Error::MisplacedEndDictEntry); +} +#endif + void Arguments::Writer::beginStruct() { advanceState(cstring("(", strlen("(")), BeginStruct); diff --git a/serialization/arguments.h b/serialization/arguments.h index 85d3f09..faa8c1e 100644 --- a/serialization/arguments.h +++ b/serialization/arguments.h @@ -34,6 +34,8 @@ class Error; class Message; = +//#define WITH_DICT_ENTRY + class DFERRY_EXPORT Arguments { public: @@ -90,7 +92,12 @@ public: String, ObjectPath, Signature, - UnixFd + UnixFd, +#ifdef WITH_DICT_ENTRY + BeginDictEntry, + EndDictEntry, +#endif + LastState }; = Arguments(); // constructs an empty argument list @@ -258,6 +265,11 @@ public: // instead of the type of primitive. Arguments::IoState peekPrimitiveArray(EmptyArrayOption option =3D = SkipIfEmpty) const; = +#ifdef WITH_DICT_ENTRY + void beginDictEntry(); + void endDictEntry(); +#endif + private: class Private; friend class Private; @@ -342,6 +354,11 @@ public: = void writePrimitiveArray(IoState type, chunk data); = +#ifdef WITH_DICT_ENTRY + void beginDictEntry(); + void endDictEntry(); +#endif + class Private; friend class Private; = diff --git a/tests/serialization/tst_arguments.cpp b/tests/serialization/ts= t_arguments.cpp index 9771a2b..340ab09 100644 --- a/tests/serialization/tst_arguments.cpp +++ b/tests/serialization/tst_arguments.cpp @@ -64,6 +64,22 @@ static bool stringsEqual(cstring s1, cstring s2) return chunksEqual(chunk(s1.ptr, s1.length), chunk(s2.ptr, s2.length)); } = +static void maybeBeginDictEntry(Arguments::Writer *writer) +{ + (void) writer; +#ifdef WITH_DICT_ENTRY + writer->beginDictEntry(); +#endif +} + +static void maybeEndDictEntry(Arguments::Writer *writer) +{ + (void) writer; +#ifdef WITH_DICT_ENTRY + writer->endDictEntry(); +#endif +} + // This class does: // 1) iterates over the full Arguments with m_reader // 2) skips whole aggregates at and below nesting level m_skipAggregatesFr= omLevel with m_skippingReader @@ -98,7 +114,24 @@ public: } } } +#ifdef WITH_DICT_ENTRY + void beginDictEntry() + { + m_reader->beginDictEntry(); + if (m_nestingLevel < m_skipAggregatesFromLevel && m_nilArrayNestin= g < m_skipNilArraysFromLevel) { + m_skippingReader->beginDictEntry(); + } + } + + void endDictEntry() + { + m_reader->endDictEntry(); + if (m_nestingLevel < m_skipAggregatesFromLevel && m_nilArrayNestin= g < m_skipNilArraysFromLevel) { + m_skippingReader->endDictEntry(); + } = + } +#endif template void beginAggregate(F beginFunc, G skipFunc) { @@ -225,6 +258,14 @@ static void testReadWithSkip(const Arguments &arg, boo= l debugPrint) case Arguments::BeginDict: checker.beginArrayAggregate(&Arguments::Reader::beginD= ict, &Arguments::Reader::skipDict); break; +#ifdef WITH_DICT_ENTRY + case Arguments::BeginDictEntry: + checker.beginDictEntry(); + break; + case Arguments::EndDictEntry: + checker.endDictEntry(); + break; +#endif case Arguments::EndDict: checker.endAggregate(&Arguments::Reader::endDict, true= ); break; @@ -292,6 +333,10 @@ static void defaultReadToWrite(Arguments::Reader *read= er, Arguments::Writer *wri case Arguments::BeginVariant: case Arguments::EndVariant: case Arguments::EndArray: +#ifdef WITH_DICT_ENTRY + case Arguments::BeginDictEntry: + case Arguments::EndDictEntry: +#endif case Arguments::EndDict: case Arguments::Byte: case Arguments::Boolean: @@ -696,6 +741,7 @@ static void test_nesting() Arguments::Writer writer; for (int i =3D 0; i < 32; i++) { writer.beginDict(); + maybeBeginDictEntry(&writer); writer.writeInt32(i); // key, next nested dict is value } TEST(writer.state() !=3D Arguments::InvalidData); @@ -706,6 +752,7 @@ static void test_nesting() Arguments::Writer writer; for (int i =3D 0; i < 32; i++) { writer.beginDict(); + maybeBeginDictEntry(&writer); writer.writeInt32(i); // key, next nested dict is value } TEST(writer.state() !=3D Arguments::InvalidData); @@ -904,17 +951,22 @@ static void test_writerMisuse() { Arguments::Writer writer; writer.beginDict(); + maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); + maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() !=3D Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); + maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); + maybeEndDictEntry(&writer); // second key-value pair + maybeBeginDictEntry(&writer); TEST(writer.state() !=3D Arguments::InvalidData); writer.writeUint16(3); // wrong, incompatible with first element TEST(writer.state() =3D=3D Arguments::InvalidData); @@ -922,9 +974,12 @@ static void test_writerMisuse() { Arguments::Writer writer; writer.beginDict(); + maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); + maybeEndDictEntry(&writer); // second key-value pair + maybeBeginDictEntry(&writer); writer.writeByte(3); TEST(writer.state() !=3D Arguments::InvalidData); writer.writeUint16(4); // wrong, incompatible with first element @@ -934,6 +989,7 @@ static void test_writerMisuse() { Arguments::Writer writer; writer.beginDict(); + maybeBeginDictEntry(&writer); writer.beginVariant(); // wrong, key type must be basic TEST(writer.state() =3D=3D Arguments::InvalidData); } @@ -1022,16 +1078,21 @@ static void test_complicated() writer.writeByte(115); writer.beginVariant(); writer.beginDict(); + maybeBeginDictEntry(&writer); writer.writeByte(23); writer.beginVariant(); writer.writeString(cstring("twenty-three")); writer.endVariant(); + maybeEndDictEntry(&writer); // key-value pair 2 + maybeBeginDictEntry(&writer); writer.writeByte(83); writer.beginVariant(); writer.writeObjectPath(cstring("/foo/bar/object")); writer.endVariant(); + maybeEndDictEntry(&writer); // key-value pair 3 + maybeBeginDictEntry(&writer); writer.writeByte(234); writer.beginVariant(); writer.beginArray(); @@ -1040,11 +1101,14 @@ static void test_complicated() writer.writeUint16(234); writer.endArray(); writer.endVariant(); + maybeEndDictEntry(&writer); // key-value pair 4 + maybeBeginDictEntry(&writer); writer.writeByte(25); writer.beginVariant(); addSomeVariantStuff(&writer); writer.endVariant(); + maybeEndDictEntry(&writer); writer.endDict(); writer.endVariant(); writer.writeString("Hello D-Bus!"); @@ -1171,8 +1235,10 @@ static void test_isWritingSignatureBug() writer.beginArray(); writer.beginStruct(); writer.beginDict(); + maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); + maybeEndDictEntry(&writer); writer.endDict(); // Must add more stuff after the inner dict to ensure that= the signature position of the // dict's value is well inside the existing signature in t= he second dict entry. @@ -1182,11 +1248,14 @@ static void test_isWritingSignatureBug() writer.endStruct(); writer.beginStruct(); writer.beginDict(); + maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); + maybeEndDictEntry(&writer); // In the second pass, we are definitely NOT writing a= new part of the dict signature, // which used to go (that was the bug!!) through a dif= ferent code path in // Arguments::Writer::advanceState(). + maybeBeginDictEntry(&writer); writer.writeByte(1); TEST(writer.state() !=3D Arguments::InvalidData); writer.writeUint16(2); @@ -1564,7 +1633,8 @@ static void test_emptyArrayAndDict() // Test RestartEmptyArrayToWriteTypes and writing an empty array i= nside the >1st iteration of another array Arguments::Writer writer; writer.beginArray((i & 2) ? Arguments::Writer::WriteTypesOfEmptyAr= ray : Arguments::Writer::NonEmptyArray); - writer.beginArray(Arguments::Writer::NonEmptyArray); // don't = care, the logic error is only in the second iteration + // v don't care, the logic error is only in the second iterati= on + writer.beginArray(Arguments::Writer::NonEmptyArray); writer.writeString(cstring("a")); writer.endArray(); if (i & 1) { @@ -1610,8 +1680,10 @@ static void test_emptyArrayAndDict() { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); + maybeBeginDictEntry(&writer); writer.writeByte(0); writer.writeString(cstring("a")); + maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() !=3D Arguments::InvalidData); Arguments arg =3D writer.finish(); @@ -1621,9 +1693,11 @@ static void test_emptyArrayAndDict() { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); + maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); writer.endVariant(); + maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() !=3D Arguments::InvalidData); Arguments arg =3D writer.finish(); @@ -1633,12 +1707,16 @@ static void test_emptyArrayAndDict() { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); + maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); writer.endVariant(); + maybeEndDictEntry(&writer); + maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); writer.endVariant(); + maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() !=3D Arguments::InvalidData); Arguments arg =3D writer.finish(); @@ -1648,6 +1726,7 @@ static void test_emptyArrayAndDict() { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); + maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); TEST(writer.state() !=3D Arguments::InvalidData); @@ -1655,6 +1734,7 @@ static void test_emptyArrayAndDict() // variants in nil arrays may contain data but it will be discarde= d, i.e. there will only be an // empty variant in the output writer.endVariant(); + maybeEndDictEntry(&writer); writer.endDict(); Arguments arg =3D writer.finish(); TEST(writer.state() =3D=3D Arguments::Finished); @@ -1664,21 +1744,31 @@ static void test_emptyArrayAndDict() // Test RestartEmptyArrayToWriteTypes and writing an empty dict in= side the >1st iteration of another dict Arguments::Writer writer; writer.beginDict((i & 2) ? Arguments::Writer::WriteTypesOfEmptyArr= ay : Arguments::Writer::NonEmptyArray); + maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); - writer.beginDict(Arguments::Writer::NonEmptyArray); // don't c= are, the logic error is only in the second iteration + // v don't care, the logic error is only in the second iterati= on + writer.beginDict(Arguments::Writer::NonEmptyArray); + maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.writeInt32(1234); + maybeEndDictEntry(&writer); writer.endDict(); + maybeEndDictEntry(&writer); + maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); if (i & 1) { writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray= ); + maybeBeginDictEntry(&writer); } else { writer.beginDict(Arguments::Writer::NonEmptyArray); writer.beginDict(Arguments::Writer::RestartEmptyArrayToWri= teTypes); + maybeBeginDictEntry(&writer); } writer.writeString(cstring("a")); writer.writeInt32(1234); + maybeEndDictEntry(&writer); writer.endDict(); + maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() !=3D Arguments::InvalidData); Arguments arg =3D writer.finish(); @@ -1690,6 +1780,7 @@ static void test_emptyArrayAndDict() Arguments::Writer writer; for (int j =3D 0; j <=3D i; j++) { writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray= ); + maybeBeginDictEntry(&writer); if (j =3D=3D 32) { TEST(writer.state() =3D=3D Arguments::InvalidData); } @@ -1701,6 +1792,7 @@ static void test_emptyArrayAndDict() } writer.writeUint16(52345); for (int j =3D 0; j <=3D i; j++) { + maybeEndDictEntry(&writer); writer.endDict(); } TEST(writer.state() !=3D Arguments::InvalidData); @@ -1734,6 +1826,8 @@ int main(int, char *[]) test_signatureLengths(); test_emptyArrayAndDict(); = + // TODO (maybe): specific tests for begin/endDictEntry() for both Read= er and Writer. + // TODO many more misuse tests for Writer and maybe some for Reader std::cout << "Passed!\n"; } diff --git a/util/error.h b/util/error.h index cba439f..eeed6ae 100644 --- a/util/error.h +++ b/util/error.h @@ -75,6 +75,11 @@ public: InvalidKeyTypeInDict, GreaterTwoTypesInDict, ArrayOrDictTooLong, + + MissingBeginDictEntry =3D 1019, + MisplacedBeginDictEntry, + MissingEndDictEntry, + MisplacedEndDictEntry, // we have a lot of error codes at our disposal, so reserve some f= or easy classification // by range MaxArgumentsError =3D 1023,