Browse Source

[master] Merge branch 'trac2382'
with fixing Conflicts for
src/lib/dns/gen-rdatacode.py.in
src/lib/dns/rdata.cc
src/lib/dns/rrparamregistry-placeholder.cc
src/lib/dns/tests/rdata_hinfo_unittest.cc
src/lib/dns/tests/rdata_txt_like_unittest.cc

JINMEI Tatuya 12 years ago
parent
commit
a6865a7686

+ 24 - 5
src/lib/dns/gen-rdatacode.py.in

@@ -32,7 +32,7 @@ import sys
 #
 # Example:
 #     new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = []
+new_rdata_factory_users = [('aaaa', 'in')]
 
 re_typecode = re.compile('([\da-z]+)_(\d+)')
 classcode2txt = {}
@@ -126,6 +126,9 @@ class AbstractMessageRenderer;\n\n'''
     explicit ''' + type_utxt + '''(const std::string& type_str);
     ''' + type_utxt + '''(isc::util::InputBuffer& buffer, size_t rdata_len);
     ''' + type_utxt + '''(const ''' + type_utxt + '''& other);
+    ''' + type_utxt + '''(
+        MasterLexer& lexer, const Name* name,
+        MasterLoader::Options options, MasterLoaderCallbacks& callbacks);
     virtual std::string toText() const;
     virtual void toWire(isc::util::OutputBuffer& buffer) const;
     virtual void toWire(AbstractMessageRenderer& renderer) const;
@@ -213,17 +216,33 @@ def generate_rdatadef(file, basemtime):
     rdata_deffile.write(class_definitions)
     rdata_deffile.close()
 
-def generate_rdatahdr(file, declarations, basemtime):
+def generate_rdatahdr(file, heading, declarations, basemtime):
     if not need_generate(file, basemtime):
         print('skip generating ' + file);
         return
+    heading += '''
+#ifndef DNS_RDATACLASS_H
+#define DNS_RDATACLASS_H 1
+
+#include <dns/master_loader.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class MasterLexer;
+class MasterLoaderCallbacks;
+}
+}
+'''
     declarations += '''
+#endif // DNS_RDATACLASS_H
+
 // Local Variables:
 // mode: c++
 // End:
 '''
     rdata_header = open(file, 'w')
-    rdata_header.write(heading_txt)
+    rdata_header.write(heading)
     rdata_header.write(declarations)
     rdata_header.close()
 
@@ -320,8 +339,8 @@ if __name__ == "__main__":
     try:
         import_definitions(classcode2txt, typecode2txt, typeandclass)
         generate_rdatadef('@builddir@/rdataclass.cc', rdatadef_mtime)
-        generate_rdatahdr('@builddir@/rdataclass.h', rdata_declarations,
-                          rdatahdr_mtime)
+        generate_rdatahdr('@builddir@/rdataclass.h', heading_txt,
+                          rdata_declarations, rdatahdr_mtime)
         generate_typeclasscode('rrtype', rdatahdr_mtime, typecode2txt, 'Type')
         generate_typeclasscode('rrclass', classdir_mtime,
                                classcode2txt, 'Class')

+ 12 - 5
src/lib/dns/master_lexer.cc

@@ -458,8 +458,11 @@ String::handle(MasterLexer& lexer) const {
 
         if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
             getLexerImpl(lexer)->source_->ungetChar();
+            // make sure it nul-terminated as a c-str (excluded from token
+            // data).
+            data.push_back('\0');
             getLexerImpl(lexer)->token_ =
-                MasterToken(&data.at(0), data.size());
+                MasterToken(&data.at(0), data.size() - 1);
             return;
         }
         escaped = (c == '\\' && !escaped);
@@ -486,7 +489,10 @@ QString::handle(MasterLexer& lexer) const {
                 escaped = false;
                 data.back() = '"';
             } else {
-                token = MasterToken(&data.at(0), data.size(), true);
+                // make sure it nul-terminated as a c-str (excluded from token
+                // data).  This also simplifies the case of an empty string.
+                data.push_back('\0');
+                token = MasterToken(&data.at(0), data.size() - 1, true);
                 return;
             }
         } else if (c == '\n' && !escaped) {
@@ -516,9 +522,10 @@ Number::handle(MasterLexer& lexer) const {
             getLexerImpl(lexer)->source_->getChar(), escaped);
         if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
             getLexerImpl(lexer)->source_->ungetChar();
+            // We need to close the string whether it's digits-only (for
+            // lexical_cast) or not (see String::handle()).
+            data.push_back('\0');
             if (digits_only) {
-                // Close the string for lexical_cast
-                data.push_back('\0');
                 try {
                     const uint32_t number32 =
                         boost::lexical_cast<uint32_t, const char*>(&data[0]);
@@ -529,7 +536,7 @@ Number::handle(MasterLexer& lexer) const {
                     token = MasterToken(MasterToken::NUMBER_OUT_OF_RANGE);
                 }
             } else {
-                token = MasterToken(&data.at(0), data.size());
+                token = MasterToken(&data.at(0), data.size() - 1);
             }
             return;
         }

+ 7 - 0
src/lib/dns/master_lexer.h

@@ -90,6 +90,13 @@ public:
     /// the region.  On the other hand, it is not ensured that the string
     /// is nul-terminated.  So the usual string manipulation API may not work
     /// as expected.
+    ///
+    /// The `MasterLexer` implementation ensures that there are at least
+    /// len + 1 bytes of valid memory region starting from beg, and that
+    /// beg[len] is \0.  This means the application can use the bytes as a
+    /// validly nul-terminated C string if there is no intermediate nul
+    /// character.  Note also that due to this property beg is always non
+    /// NULL; for an empty string len will be set to 0 and beg[0] is \0.
     struct StringRegion {
         const char* beg;        ///< The start address of the string
         size_t len;             ///< The length of the string in bytes

+ 91 - 17
src/lib/dns/rdata.cc

@@ -12,6 +12,20 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/master_lexer.h>
+#include <dns/rdata.h>
+#include <dns/rrparamregistry.h>
+#include <dns/rrtype.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
 #include <algorithm>
 #include <cctype>
 #include <string>
@@ -24,16 +38,6 @@
 #include <stdint.h>
 #include <string.h>
 
-#include <boost/lexical_cast.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <util/buffer.h>
-#include <dns/name.h>
-#include <dns/messagerenderer.h>
-#include <dns/rdata.h>
-#include <dns/rrparamregistry.h>
-#include <dns/rrtype.h>
-
 using namespace std;
 using boost::lexical_cast;
 using namespace isc::util;
@@ -81,23 +85,92 @@ createRdata(const RRType& rrtype, const RRClass& rrclass, const Rdata& source)
                                                        source));
 }
 
+namespace {
+void
+fromtextError(bool& error_issued, const MasterLexer& lexer,
+              MasterLoaderCallbacks& callbacks,
+              const MasterToken* token, const char* reason)
+{
+    // Don't be too noisy if there are many issues for single RDATA
+    if (error_issued) {
+        return;
+    }
+    error_issued = true;
+
+    if (token == NULL) {
+        callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+                        "createRdata from text failed: " + string(reason));
+        return;
+    }
+
+    switch (token->getType()) {
+    case MasterToken::STRING:
+    case MasterToken::QSTRING:
+        callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+                        "createRdata from text failed near '" +
+                        token->getString() + "': " + string(reason));
+        break;
+    case MasterToken::ERROR:
+        callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+                        "createRdata from text failed: " +
+                        token->getErrorText());
+        break;
+    default:
+        // This case shouldn't happen based on how we use MasterLexer in
+        // createRdata(), so we could assert() that here.  But since it
+        // depends on detailed behavior of other classes, we treat the case
+        // in a bit less harsh way.
+        isc_throw(Unexpected, "bug: createRdata() saw unexpected token type");
+    }
+}
+}
+
 RdataPtr
 createRdata(const RRType& rrtype, const RRClass& rrclass,
             MasterLexer& lexer, const Name* origin,
             MasterLoader::Options options,
             MasterLoaderCallbacks& callbacks)
 {
-    RdataPtr ret;
+    RdataPtr rdata;
 
+    bool error_issued = false;
     try {
-        ret = RRParamRegistry::getRegistry().createRdata(rrtype, rrclass,
-                                                         lexer, origin,
-                                                         options, callbacks);
-    } catch (...) {
-        // ret is NULL here.
+        rdata = RRParamRegistry::getRegistry().createRdata(
+            rrtype, rrclass, lexer, origin, options, callbacks);
+    } catch (const MasterLexer::LexerError& error) {
+        fromtextError(error_issued, lexer, callbacks, &error.token_, "");
+    } catch (const Exception& ex) {
+        // Catching all isc::Exception is too broad, but right now we don't
+        // have better granularity.  When we complete #2518 we can make this
+        // finer.
+        fromtextError(error_issued, lexer, callbacks, NULL, ex.what());
     }
+    // Other exceptions mean a serious implementation bug or fatal system
+    // error; it doesn't make sense to catch and try to recover from them
+    // here.  Just propagate.
+
+    // Consume to end of line / file.
+    // Call callback via fromtextError once if there was an error.
+    do {
+        const MasterToken& token = lexer.getNextToken();
+        switch (token.getType()) {
+        case MasterToken::END_OF_LINE:
+            return (rdata);
+        case MasterToken::END_OF_FILE:
+            callbacks.warning(lexer.getSourceName(), lexer.getSourceLine(),
+                              "file does not end with newline");
+            return (rdata);
+        default:
+            rdata.reset();      // we'll return NULL
+            fromtextError(error_issued, lexer, callbacks, &token,
+                          "extra input text");
+            // Continue until we see EOL or EOF
+        }
+    } while (true);
 
-    return (ret);
+    // We shouldn't reach here
+    assert(false);
+    return (RdataPtr()); // add explicit return to silence some compilers
 }
 
 int
@@ -214,6 +287,7 @@ Generic::Generic(MasterLexer& lexer, const Name*,
         const MasterToken& token = lexer.getNextToken();
         if ((token.getType() == MasterToken::END_OF_FILE) ||
             (token.getType() == MasterToken::END_OF_LINE)) {
+            lexer.ungetToken(); // let the upper layer handle the end-of token
             break;
         }
 

+ 40 - 1
src/lib/dns/rdata.h

@@ -485,8 +485,47 @@ RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
 RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
                      const Rdata& source);
 
-/// \brief Create RDATA of a given pair of RR type and class from the
+/// \brief Create RDATA of a given pair of RR type and class using the
 /// master lexer.
+///
+/// This is a more generic form of factory from textual RDATA, and is mainly
+/// intended to be used internally by the master file parser (\c MasterLoader)
+/// of this library.
+///
+/// The \c lexer is expected to be at the beginning of textual RDATA of the
+/// specified type and class.  This function (and its underlying Rdata
+/// implementations) extracts necessary tokens from the lexer and constructs
+/// the RDATA from them.
+///
+/// Due to the intended usage of this version, this function handles error
+/// cases quite differently from other versions.  It internally catches
+/// most of syntax and semantics errors of the input (reported as exceptions),
+/// calls the corresponding callback specified by the \c callbacks parameters,
+/// and returns a NULL smart pointer.  If the caller rather wants to get
+/// an exception in these cases, it can pass a callback that internally
+/// throws on error.  Some critical exceptions such as \c std::bad_alloc are
+/// still propagated to the upper layer as it doesn't make sense to try
+/// recovery from such a situation within this function.
+///
+/// Whether or not the creation succeeds, this function updates the lexer
+/// until it reaches either the end of line or file, starting from the end of
+/// the RDATA text (or the point of failure if the parsing fails in the
+/// middle of it).  The caller can therefore assume it's ready for reading
+/// the next data (which is normally a subsequent RR in the zone file) on
+/// return, whether or not this function succeeds.
+///
+/// \param rrtype An \c RRType object specifying the type/class pair.
+/// \param rrclass An \c RRClass object specifying the type/class pair.
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of any domain name fields
+/// of the RDATA that are non absolute.
+/// \param options Master loader options controlling how to deal with errors
+/// or non critical issues in the parsed RDATA.
+/// \param callbacks Callback to be called when an error or non critical issue
+/// is found.
+/// \return An \c RdataPtr object pointing to the created
+/// \c Rdata object.  Will be NULL if parsing fails.
 RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
                      MasterLexer& lexer, const Name* origin,
                      MasterLoader::Options options,

+ 19 - 8
src/lib/dns/rdata/in_1/aaaa_28.cc

@@ -12,6 +12,15 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+
 #include <stdint.h>
 #include <string.h>
 
@@ -20,14 +29,6 @@
 #include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
 #include <sys/socket.h> // for AF_INET/AF_INET6
 
-#include <exceptions/exceptions.h>
-
-#include <util/buffer.h>
-#include <dns/exceptions.h>
-#include <dns/messagerenderer.h>
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-
 using namespace std;
 using namespace isc::util;
 
@@ -42,6 +43,16 @@ AAAA::AAAA(const std::string& addrstr) {
     }
 }
 
+AAAA::AAAA(MasterLexer& lexer, const Name*,
+           MasterLoader::Options, MasterLoaderCallbacks&)
+{
+    const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
+    if (inet_pton(AF_INET6, token.getStringRegion().beg, &addr_) != 1) {
+        isc_throw(InvalidRdataText, "Failed to convert '"
+                  << token.getString() << "' to IN/AAAA RDATA");
+    }
+}
+
 AAAA::AAAA(InputBuffer& buffer, size_t rdata_len) {
     if (rdata_len != sizeof(addr_)) {
         isc_throw(DNSMessageFORMERR,

+ 5 - 0
src/lib/dns/rdata/template.cc

@@ -34,6 +34,11 @@ using namespace isc::util;
 // If you added member functions specific to this derived class, you'll need
 // to implement them here, of course.
 
+MyType::MyType(MasterLexer& lexer, const Name* origin,
+               MasterLoader::Options options, MasterLoaderCallbacks& callbacks)
+{
+}
+
 MyType::MyType(const string& type_str) {
 }
 

+ 1 - 0
src/lib/dns/rrparamregistry-placeholder.cc

@@ -54,6 +54,7 @@ AbstractRdataFactory::create(MasterLexer& lexer, const Name*,
         const MasterToken& token = lexer.getNextToken();
         if ((token.getType() == MasterToken::END_OF_FILE) ||
             (token.getType() == MasterToken::END_OF_LINE)) {
+            lexer.ungetToken(); // let the upper layer handle the end-of token
             break;
         }
 

+ 29 - 6
src/lib/dns/rrparamregistry.h

@@ -119,10 +119,22 @@ public:
     /// \return An \c RdataPtr object pointing to the created \c Rdata object.
     virtual RdataPtr create(const rdata::Rdata& source) const = 0;
 
-    /// \brief Create RDATA from MasterLexer
-    virtual RdataPtr create(MasterLexer& lexer, const Name*,
-                            MasterLoader::Options,
-                            MasterLoaderCallbacks&) const;
+    /// \brief Create RDATA using MasterLexer.
+    ///
+    /// This version of the method defines the entry point of factory
+    /// of a specific RR type and class for \c RRParamRegistry::createRdata()
+    /// that uses \c MasterLexer.  See its description for the expected
+    /// behavior and meaning of the parameters.
+    ///
+    /// \note Right now this is not defined as a pure virtual method and
+    /// provides the default implementation.  This is an intermediate
+    /// workaround until we implement the underlying constructor for all
+    /// supported \c Rdata classes; once it's completed the workaround
+    /// default implementation should be removed and this method should become
+    /// pure virtual.
+    virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
+                            MasterLoader::Options options,
+                            MasterLoaderCallbacks& callbacks) const;
     //@}
 };
 
@@ -504,9 +516,20 @@ public:
     rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
                                 const rdata::Rdata& source);
 
-    /// \brief Create RDATA from MasterLexer
+    /// \brief Create RDATA using MasterLexer
+    ///
+    /// This method is expected to be used as the underlying implementation
+    /// of the same signature of \c rdata::createRdata().  One main
+    /// difference is that this method is only responsible for constructing
+    /// the Rdata; it doesn't update the lexer to reach the end of line or
+    /// file or doesn't care about whether there's an extra (garbage) token
+    /// after the textual RDATA representation.  Another difference is that
+    /// this method can throw on error and never returns a NULL pointer.
+    ///
+    /// For other details and parameters, see the description of
+    /// \c rdata::createRdata().
     rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
-                                MasterLexer& lexer, const Name* name,
+                                MasterLexer& lexer, const Name* origin,
                                 MasterLoader::Options options,
                                 MasterLoaderCallbacks& callbacks);
     //@}

+ 10 - 0
src/lib/dns/tests/master_lexer_state_unittest.cc

@@ -269,6 +269,10 @@ stringTokenCheck(const std::string& expected, const MasterToken& token,
                              token.getStringRegion().beg +
                              token.getStringRegion().len);
     EXPECT_EQ(expected, actual);
+
+    // There should be "hidden" nul-terminator after the string data.
+    ASSERT_NE(static_cast<const char*>(NULL), token.getStringRegion().beg);
+    EXPECT_EQ(0, *(token.getStringRegion().beg + token.getStringRegion().len));
 }
 
 TEST_F(MasterLexerStateTest, string) {
@@ -365,6 +369,7 @@ TEST_F(MasterLexerStateTest, stringEscape) {
 TEST_F(MasterLexerStateTest, quotedString) {
     ss << "\"ignore-quotes\"\n";
     ss << "\"quoted string\" "; // space is part of the qstring
+    ss << "\"\" "; // empty quoted string
     // also check other separator characters. note that \r doesn't cause
     // UNBALANCED_QUOTES.  Not sure if it's intentional, but that's how the
     // BIND 9 version works, so we follow it (it should be too minor to matter
@@ -391,6 +396,11 @@ TEST_F(MasterLexerStateTest, quotedString) {
     s_qstring.handle(lexer);
     stringTokenCheck("quoted string", s_string.getToken(lexer), true);
 
+    // Empty string is okay as qstring
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    stringTokenCheck("", s_string.getToken(lexer), true);
+
     // Also checks other separator characters within a qstring
     EXPECT_EQ(&s_qstring, State::start(lexer, options));
     s_qstring.handle(lexer);

+ 133 - 0
src/lib/dns/tests/rdata_unittest.cc

@@ -29,6 +29,7 @@
 #include <dns/tests/rdata_unittest.h>
 
 #include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
 
 using isc::UnitTestUtil;
 using namespace std;
@@ -82,6 +83,138 @@ createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass,
 
 } // end of namespace isc::dns::rdata::test
 
+// A mock class to check parameters passed via loader callbacks.  Its callback
+// records the passed parameters, allowing the test to check them later via
+// the check() method.
+class CreateRdataCallback {
+public:
+    enum CallbackType { NONE, ERROR, WARN };
+    CreateRdataCallback() : type_(NONE), line_(0) {}
+    void callback(CallbackType type, const string& source, size_t line,
+                  const string& reason_txt) {
+        type_ = type;
+        source_ = source;
+        line_ = line;
+        reason_txt_ = reason_txt;
+    }
+
+    void clear() {
+        type_ = NONE;
+        source_.clear();
+        line_ = 0;
+        reason_txt_.clear();
+    }
+
+    // Return if callback is called since the previous call to clear().
+    bool isCalled() const { return (type_ != NONE); }
+
+    void check(const string& expected_srcname, size_t expected_line,
+               CallbackType expected_type, const string& expected_reason)
+        const
+    {
+        EXPECT_EQ(expected_srcname, source_);
+        EXPECT_EQ(expected_line, line_);
+        EXPECT_EQ(expected_type, type_);
+        EXPECT_EQ(expected_reason, reason_txt_);
+    }
+
+private:
+    CallbackType type_;
+    string source_;
+    size_t line_;
+    string reason_txt_;
+};
+
+// Test class/type-independent behavior of createRdata().
+TEST_F(RdataTest, createRdataWithLexer) {
+    const in::AAAA aaaa_rdata("2001:db8::1");
+
+    stringstream ss;
+    const string src_name = "stream-" + boost::lexical_cast<string>(&ss);
+    ss << aaaa_rdata.toText() << "\n"; // valid case
+    ss << aaaa_rdata.toText() << "; comment, should be ignored\n";
+    ss << aaaa_rdata.toText() << " extra-token\n"; // extra token
+    ss << aaaa_rdata.toText() << " extra token\n"; // 2 extra tokens
+    ss << ")\n"; // causing lexer error in parsing the RDATA text
+    ss << "192.0.2.1\n"; // semantics error: IPv4 address is given for AAAA
+    ss << aaaa_rdata.toText();  // valid, but end with EOF, not EOL
+    lexer.pushSource(ss);
+
+    CreateRdataCallback callback;
+    MasterLoaderCallbacks callbacks(
+        boost::bind(&CreateRdataCallback::callback, &callback,
+                    CreateRdataCallback::ERROR, _1, _2, _3),
+        boost::bind(&CreateRdataCallback::callback, &callback,
+                    CreateRdataCallback::WARN,  _1, _2, _3));
+
+    size_t line = 0;
+
+    // Valid case.
+    ++line;
+    ConstRdataPtr rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer,
+                                      NULL, MasterLoader::MANY_ERRORS,
+                                      callbacks);
+    EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+    EXPECT_FALSE(callback.isCalled());
+
+    // Similar to the previous case, but RDATA is followed by a comment.
+    // It should cause any confusion.
+    ++line;
+    callback.clear();
+    rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                        MasterLoader::MANY_ERRORS, callbacks);
+    EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+    EXPECT_FALSE(callback.isCalled());
+
+    // Broken RDATA text: extra token.  createRdata() returns NULL, error
+    // callback is called.
+    ++line;
+    callback.clear();
+    EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                             MasterLoader::MANY_ERRORS, callbacks));
+    callback.check(src_name, line, CreateRdataCallback::ERROR,
+                   "createRdata from text failed near 'extra-token': "
+                   "extra input text");
+
+    // Similar to the previous case, but only the first extra token triggers
+    // callback.
+    ++line;
+    callback.clear();
+    EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                             MasterLoader::MANY_ERRORS, callbacks));
+    callback.check(src_name, line, CreateRdataCallback::ERROR,
+                   "createRdata from text failed near 'extra': "
+                   "extra input text");
+
+    // Lexer error will happen, corresponding error callback will be triggered.
+    ++line;
+    callback.clear();
+    EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                             MasterLoader::MANY_ERRORS, callbacks));
+    callback.check(src_name, line, CreateRdataCallback::ERROR,
+                   "createRdata from text failed: unbalanced parentheses");
+
+    // Semantics level error will happen, corresponding error callback will be
+    // triggered.
+    ++line;
+    callback.clear();
+    EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                             MasterLoader::MANY_ERRORS, callbacks));
+    callback.check(src_name, line, CreateRdataCallback::ERROR,
+                   "createRdata from text failed: Failed to convert "
+                   "'192.0.2.1' to IN/AAAA RDATA");
+
+    // Input is valid and parse will succeed, but with a warning that the
+    // file is not ended with a newline.
+    ++line;
+    callback.clear();
+    rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+                        MasterLoader::MANY_ERRORS, callbacks);
+    EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+    callback.check(src_name, line, CreateRdataCallback::WARN,
+                   "file does not end with newline");
+}
+
 }
 }
 }

+ 2 - 0
src/lib/dns/tests/rdata_unittest.h

@@ -20,6 +20,7 @@
 #include <dns/rrclass.h>
 #include <dns/rrtype.h>
 #include <dns/rdata.h>
+#include <dns/master_lexer.h>
 
 #include <gtest/gtest.h>
 
@@ -40,6 +41,7 @@ protected:
     /// This is an RDATA object of some "unknown" RR type so that it can be
     /// used to test the compare() method against a well-known RR type.
     RdataPtr rdata_nomatch;
+    MasterLexer lexer;
 };
 
 namespace test {