Index: include/llvm/Support/YAMLTraits.h =================================================================== --- include/llvm/Support/YAMLTraits.h +++ include/llvm/Support/YAMLTraits.h @@ -684,14 +684,21 @@ /// class Input : public IO { public: - // Construct a yaml Input object from a StringRef and optional user-data. - Input(StringRef InputContent, void *Ctxt=NULL); + // Construct a yaml Input object from a StringRef and optional + // user-data. The DiagHandler can also be set in the constructor, + // which means that it can be called from the parsing taking place + // in the construct of Input. + Input(StringRef InputContent, + void *Ctxt=NULL, + SourceMgr::DiagHandlerTy Handler=NULL, + void *HandlerCtxt=NULL); ~Input(); // Check if there was an syntax or semantic error during parsing. llvm::error_code error(); // To set alternate error reporting. + // Overrides the any handler set in the constructor of Input. void setDiagHandler(llvm::SourceMgr::DiagHandlerTy Handler, void *Ctxt = 0); private: @@ -961,8 +968,8 @@ inline typename llvm::enable_if_c::value,Input &>::type operator>>(Input &yin, T &docSeq) { - yin.setCurrentDocument(); - yamlize(yin, docSeq, true); + if (yin.setCurrentDocument()) + yamlize(yin, docSeq, true); return yin; } Index: lib/Support/YAMLTraits.cpp =================================================================== --- lib/Support/YAMLTraits.cpp +++ lib/Support/YAMLTraits.cpp @@ -41,10 +41,15 @@ // Input //===----------------------------------------------------------------------===// -Input::Input(StringRef InputContent, void *Ctxt) +Input::Input(StringRef InputContent, + void *Ctxt, + SourceMgr::DiagHandlerTy Handler, + void *HandlerCtxt) : IO(Ctxt), Strm(new Stream(InputContent, SrcMgr)), CurrentNode(NULL) { + if (Handler) + setDiagHandler(Handler, HandlerCtxt); DocIterator = Strm->begin(); } @@ -66,6 +71,12 @@ bool Input::setCurrentDocument() { if (DocIterator != Strm->end()) { Node *N = DocIterator->getRoot(); + if (!N) { + assert(Strm->failed() && "Root is NULL iff parsing failed"); + EC = make_error_code(errc::invalid_argument); + return false; + } + if (isa(N)) { // Empty files are allowed and ignored ++DocIterator; @@ -85,7 +96,8 @@ void Input::beginMapping() { if (EC) return; - MapHNode *MN = dyn_cast(CurrentNode); + // CurrentNode can be null if the document is empty + MapHNode *MN = dyn_cast_or_null(CurrentNode); if (MN) { MN->ValidKeys.clear(); } @@ -96,6 +108,19 @@ UseDefault = false; if (EC) return false; + + // If CurrentNode is null (for example, the document is an empty + // string), then we check if the key is required, and if so, set the + // error code. This somewhat convoluted logic is due to the fact + // that mappings without required keys can be validly deserialized + // from an empty YAML document, and so we must defer the decision of + // when to fail until we reach this point. + if (!CurrentNode) { + if (Required) + EC = make_error_code(errc::invalid_argument); + return false; + } + MapHNode *MN = dyn_cast(CurrentNode); if (!MN) { setError(CurrentNode, "not a mapping"); @@ -122,7 +147,8 @@ void Input::endMapping() { if (EC) return; - MapHNode *MN = dyn_cast(CurrentNode); + // CurrentNode can be null if the document is empty + MapHNode *MN = dyn_cast_or_null(CurrentNode); if (!MN) return; for (MapHNode::NameToNode::iterator i = MN->Mapping.begin(), @@ -263,6 +289,7 @@ } void Input::setError(HNode *hnode, const Twine &message) { + assert(hnode && "HNode must not be NULL"); this->setError(hnode->_node, message); } Index: unittests/Support/YAMLIOTest.cpp =================================================================== --- unittests/Support/YAMLIOTest.cpp +++ unittests/Support/YAMLIOTest.cpp @@ -58,15 +58,25 @@ // TEST(YAMLIO, TestMapRead) { FooBar doc; - Input yin("---\nfoo: 3\nbar: 5\n...\n"); - yin >> doc; + { + Input yin("---\nfoo: 3\nbar: 5\n...\n"); + yin >> doc; - EXPECT_FALSE(yin.error()); - EXPECT_EQ(doc.foo, 3); - EXPECT_EQ(doc.bar,5); + EXPECT_FALSE(yin.error()); + EXPECT_EQ(doc.foo, 3); + EXPECT_EQ(doc.bar, 5); + } + + { + Input yin("{foo: 3, bar: 5}"); + yin >> doc; + + EXPECT_FALSE(yin.error()); + EXPECT_EQ(doc.foo, 3); + EXPECT_EQ(doc.bar, 5); + } } - // // Test the reading of a yaml sequence of mappings // @@ -1354,6 +1364,24 @@ EXPECT_TRUE(yin.error()); } +TEST(YAMLIO, TestMalformedMapFailsGracefully) { + FooBar doc; + { + // We pass the suppressErrorMessages handler to handle the error + // message generated in the constructor of Input. + Input yin("{foo:3, bar: 5}", NULL /* ctxt */, suppressErrorMessages); + yin >> doc; + EXPECT_TRUE(yin.error()); + } + + { + Input yin("---\nfoo:3\nbar: 5\n...\n"); + yin.setDiagHandler(suppressErrorMessages); + yin >> doc; + EXPECT_TRUE(yin.error()); + } +} + struct OptionalTest { std::vector Numbers; }; @@ -1417,3 +1445,27 @@ EXPECT_TRUE(Seq2.Tests[3].Numbers.empty()); } + +TEST(YAMLIO, TestEmptyStringFailsForRequiredFields) { + FooBar doc; + Input yin(""); + yin >> doc; + EXPECT_TRUE(yin.error()); +} + +TEST(YAMLIO, TestEmptyStringSuccedsForOptionalFields) { + OptionalTest doc; + Input yin(""); + yin >> doc; + EXPECT_FALSE(yin.error()); +} + +TEST(YAMLIO, TestEmptyStringSuccessForSequence) { + std::vector seq; + Input yin(""); + yin.setDiagHandler(suppressErrorMessages); + yin >> seq; + + EXPECT_FALSE(yin.error()); + EXPECT_TRUE(seq.empty()); +}