diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index 9c81d1269..122ea5754 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -90,11 +90,22 @@ static bool LoadConfigFiles(const String& appType, ValidationType validate) int warnings = 0, errors = 0; BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) { + std::ostringstream locbuf; + ShowCodeFragment(locbuf, message.Location); + String location = locbuf.str(); + + String logmsg; + + if (!location.IsEmpty()) + logmsg = "Location:\n" + location; + + logmsg += String("\nConfig ") + (message.Error ? "error" : "warning") + ": " + message.Text; + if (message.Error) { - Log(LogCritical, "config", "Config error: " + message.Text); + Log(LogCritical, "config", logmsg); errors++; } else { - Log(LogWarning, "config", "Config warning: " + message.Text); + Log(LogWarning, "config", logmsg); warnings++; } } diff --git a/lib/config/CMakeLists.txt b/lib/config/CMakeLists.txt index 6f9dbeb50..deb99ac26 100644 --- a/lib/config/CMakeLists.txt +++ b/lib/config/CMakeLists.txt @@ -28,9 +28,9 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) add_library(config SHARED aexpression.cpp applyrule.cpp avalue.cpp base-type.conf base-type.cpp - configcompilercontext.cpp configcompiler.cpp configitembuilder.cpp + configcompilercontext.cpp configcompiler.cpp configerror.cpp configitembuilder.cpp configitem.cpp ${FLEX_config_lexer_OUTPUTS} ${BISON_config_parser_OUTPUTS} - configtype.cpp expression.cpp expressionlist.cpp typerule.cpp typerulelist.cpp + configtype.cpp debuginfo.cpp expression.cpp expressionlist.cpp typerule.cpp typerulelist.cpp ) target_link_libraries(config ${Boost_LIBRARIES} base) diff --git a/lib/config/config_parser.yy b/lib/config/config_parser.yy index caab7f116..4f19e5f10 100644 --- a/lib/config/config_parser.yy +++ b/lib/config/config_parser.yy @@ -26,6 +26,7 @@ #include "config/configitembuilder.h" #include "config/configcompiler.h" #include "config/configcompilercontext.h" +#include "config/configerror.h" #include "config/typerule.h" #include "config/typerulelist.h" #include "config/aexpression.h" @@ -173,7 +174,7 @@ void yyerror(YYLTYPE *locp, ConfigCompiler *, const char *err) { std::ostringstream message; message << *locp << ": " << err; - ConfigCompilerContext::GetInstance()->AddMessage(true, message.str()); + ConfigCompilerContext::GetInstance()->AddMessage(true, message.str(), *locp); } int yyparse(ConfigCompiler *context); @@ -188,6 +189,9 @@ void ConfigCompiler::Compile(void) { try { yyparse(this); + } catch (const ConfigError& ex) { + ShowCodeFragment(std::cout, ex.GetDebugInfo()); + ConfigCompilerContext::GetInstance()->AddMessage(true, DiagnosticInformation(ex)); } catch (const std::exception& ex) { ConfigCompilerContext::GetInstance()->AddMessage(true, DiagnosticInformation(ex)); } @@ -282,7 +286,7 @@ type: partial_specifier T_TYPE identifier if (!m_Type) { if ($1) - BOOST_THROW_EXCEPTION(std::invalid_argument("Partial type definition for unknown type '" + name + "'")); + BOOST_THROW_EXCEPTION(ConfigError("Partial type definition for unknown type '" + name + "'", DebugInfoRange(@1, @3))); m_Type = make_shared(name, DebugInfoRange(@1, @3)); m_Type->Register(); @@ -403,7 +407,7 @@ object: free($3); free($4); delete $5; - BOOST_THROW_EXCEPTION(std::invalid_argument(msgbuf.str())); + BOOST_THROW_EXCEPTION(ConfigError(msgbuf.str(), di)); } item->SetType($3); @@ -412,7 +416,7 @@ object: std::ostringstream msgbuf; msgbuf << "Name for object '" << $4 << "' of type '" << $3 << "' is invalid: Object names may not contain '!'"; free($3); - BOOST_THROW_EXCEPTION(std::invalid_argument(msgbuf.str())); + BOOST_THROW_EXCEPTION(ConfigError(msgbuf.str(), @4)); } free($3); @@ -751,7 +755,7 @@ optional_template: /* empty */ apply: T_APPLY optional_template identifier identifier T_TO identifier T_WHERE aexpression { if (!ApplyRule::IsValidCombination($3, $6)) { - BOOST_THROW_EXCEPTION(std::invalid_argument("'apply' cannot be used with types '" + String($3) + "' and '" + String($6) + "'.")); + BOOST_THROW_EXCEPTION(ConfigError("'apply' cannot be used with types '" + String($3) + "' and '" + String($6) + "'.", @1)); } Array::Ptr arguments = make_shared(); diff --git a/lib/config/configcompilercontext.cpp b/lib/config/configcompilercontext.cpp index e715ad666..19909a548 100644 --- a/lib/config/configcompilercontext.cpp +++ b/lib/config/configcompilercontext.cpp @@ -25,11 +25,11 @@ using namespace icinga; -void ConfigCompilerContext::AddMessage(bool error, const String& message) +void ConfigCompilerContext::AddMessage(bool error, const String& message, const DebugInfo& di) { boost::mutex::scoped_lock lock(m_Mutex); - m_Messages.push_back(ConfigCompilerMessage(error, message)); + m_Messages.push_back(ConfigCompilerMessage(error, message, di)); } std::vector ConfigCompilerContext::GetMessages(void) const diff --git a/lib/config/configcompilercontext.h b/lib/config/configcompilercontext.h index dc75504e9..85c6a44df 100644 --- a/lib/config/configcompilercontext.h +++ b/lib/config/configcompilercontext.h @@ -31,9 +31,10 @@ struct I2_CONFIG_API ConfigCompilerMessage { bool Error; String Text; + DebugInfo Location; - ConfigCompilerMessage(bool error, const String& text) - : Error(error), Text(text) + ConfigCompilerMessage(bool error, const String& text, const DebugInfo& di) + : Error(error), Text(text), Location(di) { } }; @@ -43,7 +44,7 @@ struct I2_CONFIG_API ConfigCompilerMessage class I2_CONFIG_API ConfigCompilerContext { public: - void AddMessage(bool error, const String& message); + void AddMessage(bool error, const String& message, const DebugInfo& di = DebugInfo()); std::vector GetMessages(void) const; bool HasErrors(void) const; diff --git a/lib/config/configerror.cpp b/lib/config/configerror.cpp new file mode 100644 index 000000000..a4b232c35 --- /dev/null +++ b/lib/config/configerror.cpp @@ -0,0 +1,39 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "config/configerror.h" + +using namespace icinga; + +ConfigError::ConfigError(const String& message, const DebugInfo& di) + : m_Message(message), m_DebugInfo(di) +{ } + +ConfigError::~ConfigError(void) throw() +{ } + +const char *ConfigError::what(void) const throw() +{ + return m_Message.CStr(); +} + +DebugInfo ConfigError::GetDebugInfo(void) const +{ + return m_DebugInfo; +} \ No newline at end of file diff --git a/lib/config/configerror.h b/lib/config/configerror.h new file mode 100644 index 000000000..10bccfe4e --- /dev/null +++ b/lib/config/configerror.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#ifndef CONFIGERROR_H +#define CONFIGERROR_H + +#include "config/i2-config.h" +#include "config/debuginfo.h" + +namespace icinga +{ + +/* + * @ingroup config + */ +class I2_CONFIG_API ConfigError : public std::exception +{ +public: + ConfigError(const String& message, const DebugInfo& di); + ~ConfigError(void) throw(); + + const char *what(void) const throw(); + DebugInfo GetDebugInfo(void) const; + +private: + String m_Message; + DebugInfo m_DebugInfo; +}; + +} + +#endif /* CONFIGERROR_H */ diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index edca199f2..95ca34f5a 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -121,7 +121,7 @@ ExpressionList::Ptr ConfigItem::GetLinkedExpressionList(void) std::ostringstream message; message << "Parent object '" << name << "' does not" " exist (" << m_DebugInfo << ")"; - ConfigCompilerContext::GetInstance()->AddMessage(true, message.str()); + ConfigCompilerContext::GetInstance()->AddMessage(true, message.str(), m_DebugInfo); } else { ExpressionList::Ptr pexprl; diff --git a/lib/config/debuginfo.cpp b/lib/config/debuginfo.cpp new file mode 100644 index 000000000..b100c34f0 --- /dev/null +++ b/lib/config/debuginfo.cpp @@ -0,0 +1,97 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "config/debuginfo.h" +#include "base/convert.h" +#include + +using namespace icinga; + +/** + * Outputs a DebugInfo struct to a stream. + * + * @param out The output stream. + * @param val The DebugInfo struct. + * @returns The output stream. + */ +std::ostream& icinga::operator<<(std::ostream& out, const DebugInfo& val) +{ + out << "in " << val.Path << ": " + << val.FirstLine << ":" << val.FirstColumn + << "-" + << val.LastLine << ":" << val.LastColumn; + + return out; +} + +DebugInfo icinga::DebugInfoRange(const DebugInfo& start, const DebugInfo& end) +{ + DebugInfo result; + result.Path = start.Path; + result.FirstLine = start.FirstLine; + result.FirstColumn = start.FirstColumn; + result.LastLine = end.LastLine; + result.LastColumn = end.LastColumn; + return result; +} + +void icinga::ShowCodeFragment(std::ostream& out, const DebugInfo& di) +{ + if (di.Path.IsEmpty()) + return; + + std::ifstream ifs; + ifs.open(di.Path.CStr(), std::ifstream::in); + + int lineno = 1; + char line[1024]; + + while (ifs.good() && lineno <= di.LastLine) { + ifs.getline(line, sizeof(line)); + + for (int i = 0; line[i]; i++) + if (line[i] == '\t') + line[i] = ' '; + + if (lineno >= di.FirstLine && lineno <= di.LastLine) { + String pathInfo = di.Path + "(" + Convert::ToString(lineno) + "): "; + out << pathInfo; + out << line << "\n"; + + int start, end; + + start = 0; + end = strlen(line); + + if (lineno == di.FirstLine) + start = di.FirstColumn - 1; + + if (lineno == di.LastLine) + end = di.LastColumn; + + out << String(pathInfo.GetLength(), ' '); + out << String(start, ' '); + out << String(end - start, '^'); + + out << "\n"; + } + + lineno++; + } +} diff --git a/lib/config/debuginfo.h b/lib/config/debuginfo.h index 393e39524..057def8b3 100644 --- a/lib/config/debuginfo.h +++ b/lib/config/debuginfo.h @@ -59,33 +59,11 @@ struct DebugInfo }; }; -/** - * Outputs a DebugInfo struct to a stream. - * - * @param out The output stream. - * @param val The DebugInfo struct. - * @returns The output stream. - */ -inline std::ostream& operator<<(std::ostream& out, const DebugInfo& val) -{ - out << "in " << val.Path << ": " - << val.FirstLine << ":" << val.FirstColumn - << "-" - << val.LastLine << ":" << val.LastColumn; +std::ostream& operator<<(std::ostream& out, const DebugInfo& val); - return out; -} +DebugInfo DebugInfoRange(const DebugInfo& start, const DebugInfo& end); -inline DebugInfo DebugInfoRange(const DebugInfo& start, const DebugInfo& end) -{ - DebugInfo result; - result.Path = start.Path; - result.FirstLine = start.FirstLine; - result.FirstColumn = start.FirstColumn; - result.LastLine = end.LastLine; - result.LastColumn = end.LastColumn; - return result; -} +void ShowCodeFragment(std::ostream& out, const DebugInfo& di); }