]> granicus.if.org Git - icinga2/commitdiff
Implement a debugger for Icinga scripts
authorGunnar Beutner <gunnar@beutner.name>
Thu, 5 Nov 2015 13:29:45 +0000 (14:29 +0100)
committerGunnar Beutner <gunnar@beutner.name>
Thu, 5 Nov 2015 13:29:45 +0000 (14:29 +0100)
fixes #10547

icinga-app/icinga.cpp
lib/base/application.cpp
lib/base/application.hpp
lib/base/exception.cpp
lib/base/exception.hpp
lib/cli/consolecommand.cpp
lib/cli/consolecommand.hpp
lib/config/config_lexer.ll
lib/config/config_parser.yy
lib/config/expression.cpp
lib/config/expression.hpp

index f09df27a16840cee14b1918c4cb755ab4952cecb..2b154d1ce4e549d45aa30ed3ece65fb8cedc9223 100644 (file)
@@ -193,9 +193,9 @@ int Main(void)
                ("app,a", po::value<std::string>(), "application library name (default: icinga)")
                ("library,l", po::value<std::vector<std::string> >(), "load a library")
                ("include,I", po::value<std::vector<std::string> >(), "add include search directory")
-               ("log-level,x", po::value<std::string>()
-                       , "specify the log level for the console log.\n"
-                       "The valid value is either debug, notice, information (default), warning, or critical");
+               ("log-level,x", po::value<std::string>(), "specify the log level for the console log.\n"
+                   "The valid value is either debug, notice, information (default), warning, or critical")
+               ("script-debugger,X", "whether to enable the script debugger");
 
        po::options_description hiddenDesc("Hidden options");
 
@@ -264,6 +264,9 @@ int Main(void)
                }
        }
 
+       if (vm.count("script-debugger"))
+               Application::SetScriptDebuggerEnabled(true);
+
        Application::DeclareStatePath(Application::GetLocalStateDir() + "/lib/icinga2/icinga2.state");
        Application::DeclareModAttrPath(Application::GetLocalStateDir() + "/lib/icinga2/modified-attributes.conf");
        Application::DeclareObjectsPath(Application::GetLocalStateDir() + "/cache/icinga2/icinga2.debug");
index 5cc81d3c06b989046a3ce488b6cec01781e9205e..3866836b459f93403c56bdad5b314f23ffcdbde2 100644 (file)
@@ -63,6 +63,7 @@ static bool l_InExceptionHandler = false;
 int Application::m_ArgC;
 char **Application::m_ArgV;
 double Application::m_StartTime;
+bool Application::m_ScriptDebuggerEnabled = false;
 
 /**
  * Constructor for the Application class.
@@ -1450,6 +1451,16 @@ void Application::SetStartTime(double ts)
        m_StartTime = ts;
 }
 
+bool Application::GetScriptDebuggerEnabled(void)
+{
+       return m_ScriptDebuggerEnabled;
+}
+
+void Application::SetScriptDebuggerEnabled(bool enabled)
+{
+       m_ScriptDebuggerEnabled = enabled;
+}
+
 void Application::ValidateName(const String& value, const ValidationUtils& utils)
 {
        ObjectImpl<Application>::ValidateName(value, utils);
index 8f908dfacc53f5bc425d42d2fad2e40d07dc0e9f..f6a53ff4044c3770f08f2f45b99b3662fe063783 100644 (file)
@@ -134,6 +134,9 @@ public:
        static double GetStartTime(void);
        static void SetStartTime(double ts);
 
+       static bool GetScriptDebuggerEnabled(void);
+       static void SetScriptDebuggerEnabled(bool enabled);
+
        static void DisplayInfoMessage(std::ostream& os, bool skipVersion = false);
 
 protected:
@@ -164,6 +167,7 @@ private:
        static bool m_Debugging; /**< Whether debugging is enabled. */
        static LogSeverity m_DebuggingSeverity; /**< Whether debugging severity is set. */
        static double m_StartTime;
+       static bool m_ScriptDebuggerEnabled;
 
 #ifndef _WIN32
        static void SigIntTermHandler(int signum);
index 42ddcd3dbf12f75e77384bb02866977c20abc7a8..4e0693873d97f194fb45fcca67fdfc57518b63f7 100644 (file)
@@ -254,7 +254,7 @@ ScriptError::ScriptError(const String& message)
 { }
 
 ScriptError::ScriptError(const String& message, const DebugInfo& di, bool incompleteExpr)
-       : m_Message(message), m_DebugInfo(di), m_IncompleteExpr(incompleteExpr)
+       : m_Message(message), m_DebugInfo(di), m_IncompleteExpr(incompleteExpr), m_HandledByDebugger(false)
 { }
 
 ScriptError::~ScriptError(void) throw()
@@ -275,6 +275,16 @@ bool ScriptError::IsIncompleteExpression(void) const
        return m_IncompleteExpr;;
 }
 
+bool ScriptError::IsHandledByDebugger(void) const
+{
+       return m_HandledByDebugger;
+}
+
+void ScriptError::SetHandledByDebugger(bool handled)
+{
+       m_HandledByDebugger = handled;
+}
+
 posix_error::posix_error(void)
        : m_Message(NULL)
 { }
index 90e219239915a6c6b58d5aca160ef8b8feb05e46..4f6713df1db1f25a1a8963bf41e90c74ae23f9a2 100644 (file)
@@ -60,10 +60,14 @@ public:
        DebugInfo GetDebugInfo(void) const;
        bool IsIncompleteExpression(void) const;
 
+       bool IsHandledByDebugger(void) const;
+       void SetHandledByDebugger(bool handled);
+
 private:
        String m_Message;
        DebugInfo m_DebugInfo;
        bool m_IncompleteExpr;
+       bool m_HandledByDebugger;
 };
 
 /*
index 32162b35d51c356341cb9714b003512d8e63e89f..435a58cba6c6f1067f1a2a4043c7001b4c27ff18 100644 (file)
@@ -46,6 +46,36 @@ static String l_Session;
 
 REGISTER_CLICOMMAND("console", ConsoleCommand);
 
+INITIALIZE_ONCE(&ConsoleCommand::StaticInitialize);
+
+void ConsoleCommand::BreakpointHandler(ScriptFrame& frame, ScriptError *ex, const DebugInfo& di)
+{
+       static boost::mutex mutex;
+       boost::mutex::scoped_lock lock(mutex);
+
+       if (!Application::GetScriptDebuggerEnabled())
+               return;
+
+       if (ex && ex->IsHandledByDebugger())
+               return;
+
+       std::cout << "Breakpoint encountered in '" << di.Path << "' at " << di << "\n";
+
+       if (ex) {
+               std::cout << "Exception: " << DiagnosticInformation(*ex);
+               ex->SetHandledByDebugger(true);
+       }
+
+       std::cout << "You can leave the debugger and continue the program with \"$quit\".\n";
+
+       ConsoleCommand::RunScriptConsole(frame);
+}
+
+void ConsoleCommand::StaticInitialize(void)
+{
+       Expression::OnBreakpoint.connect(&ConsoleCommand::BreakpointHandler);
+}
+
 String ConsoleCommand::GetDescription(void) const
 {
        return "Interprets Icinga script expressions.";
@@ -118,19 +148,15 @@ char *ConsoleCommand::ConsoleCompleteHelper(const char *word, int state)
  */
 int ConsoleCommand::Run(const po::variables_map& vm, const std::vector<std::string>& ap) const
 {
-       std::map<String, String> lines;
-       int next_line = 1;
-
 #ifdef HAVE_EDITLINE
        rl_completion_entry_function = ConsoleCommand::ConsoleCompleteHelper;
        rl_completion_append_character = '\0';
 #endif /* HAVE_EDITLINE */
 
-       String addr;
+       String addr, session;
        ScriptFrame scriptFrame;
 
-       l_ScriptFrame = &scriptFrame;
-       l_Session = Utility::NewUniqueID();
+       session = Utility::NewUniqueID();
 
        if (vm.count("sandbox"))
                scriptFrame.Sandboxed = true;
@@ -144,9 +170,24 @@ int ConsoleCommand::Run(const po::variables_map& vm, const std::vector<std::stri
        if (addrEnv)
                addr = addrEnv;
 
-       if (vm.count("connect")) {
+       if (vm.count("connect"))
                addr = vm["connect"].as<std::string>();
-       }
+
+       String command;
+
+       if (vm.count("eval"))
+               command = vm["eval"].as<std::string>();
+
+       return RunScriptConsole(scriptFrame, addr, session, command);;
+}
+
+int ConsoleCommand::RunScriptConsole(ScriptFrame& scriptFrame, const String& addr, const String& session, const String& commandOnce)
+{
+       std::map<String, String> lines;
+       int next_line = 1;
+
+       l_ScriptFrame = &scriptFrame;
+       l_Session = session;
 
        if (!addr.IsEmpty()) {
                Url::Ptr url;
@@ -182,7 +223,7 @@ int ConsoleCommand::Run(const po::variables_map& vm, const std::vector<std::stri
 incomplete:
                std::string line;
 
-               if (!vm.count("eval")) {
+               if (commandOnce.IsEmpty()) {
 #ifdef HAVE_EDITLINE
                        std::ostringstream promptbuf;
                        std::ostream& os = promptbuf;
@@ -215,7 +256,14 @@ incomplete:
                        std::getline(std::cin, line);
 #endif /* HAVE_EDITLINE */
                } else
-                       line = vm["eval"].as<std::string>();
+                       line = commandOnce;
+
+               if (!line.empty() && line[0] == '$') {
+                       if (line == "$quit")
+                               break;
+
+                       std::cout << "Unknown debugger command: " << line;
+               }
 
                if (!command.empty())
                        command += "\n";
@@ -254,7 +302,7 @@ incomplete:
                                        boost::rethrow_exception(eptr);
                        }
 
-                       if (!vm.count("eval")) {
+                       if (commandOnce.IsEmpty()) {
                                std::cout << ConsoleColorTag(Console_ForegroundCyan);
                                ConfigWriter::EmitValue(std::cout, 1, result);
                                std::cout << ConsoleColorTag(Console_Normal) << "\n";
@@ -308,12 +356,12 @@ incomplete:
 
                        std::cout << ex.what() << "\n";
 
-                       if (vm.count("eval"))
+                       if (!commandOnce.IsEmpty())
                                return EXIT_FAILURE;
                } catch (const std::exception& ex) {
                        std::cout << "Error: " << DiagnosticInformation(ex) << "\n";
 
-                       if (vm.count("eval"))
+                       if (!commandOnce.IsEmpty())
                                return EXIT_FAILURE;
                }
 
index f144e9a042e5ad5b3698f48f6213c29b1bdd077e..3928f2bfd141fb2fa9b2fd75a3abebfbde0bc902 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "cli/clicommand.hpp"
 #include "base/exception.hpp"
+#include "base/scriptframe.hpp"
 
 namespace icinga
 {
@@ -36,6 +37,8 @@ class ConsoleCommand : public CLICommand
 public:
        DECLARE_PTR_TYPEDEFS(ConsoleCommand);
 
+       static void StaticInitialize(void);
+
        virtual String GetDescription(void) const override;
        virtual String GetShortDescription(void) const override;
        virtual ImpersonationLevel GetImpersonationLevel(void) const override;
@@ -43,6 +46,9 @@ public:
            boost::program_options::options_description& hiddenDesc) const override;
        virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const override;
 
+       static int RunScriptConsole(ScriptFrame& scriptFrame, const String& addr = String(),
+           const String& session = String(), const String& commandOnce = String());
+
 private:
        mutable boost::mutex m_Mutex;
        mutable boost::condition_variable m_CV;
@@ -58,6 +64,8 @@ private:
        static char *ConsoleCompleteHelper(const char *word, int state);
 #endif /* HAVE_EDITLINE */
 
+       static void BreakpointHandler(ScriptFrame& frame, ScriptError *ex, const DebugInfo& di);
+
 };
 
 }
index 7343a7635b64e2ea3a9f4866e55a906286d4ff05..98078c59268cd0994d5a3b3167bd53209838ea85 100644 (file)
@@ -198,6 +198,7 @@ throw                               return T_THROW;
 ignore_on_error                        return T_IGNORE_ON_ERROR;
 current_filename               return T_CURRENT_FILENAME;
 current_line                   return T_CURRENT_LINE;
+debugger                       return T_DEBUGGER;
 =\>                            return T_FOLLOWS;
 \<\<                           return T_SHIFT_LEFT;
 \>\>                           return T_SHIFT_RIGHT;
index 271a22830ade379eedf974397b35e46b90b54824..1b5e5ed096919a3b68324d9f9df3fafc309c411a 100644 (file)
@@ -146,6 +146,7 @@ static void MakeRBinaryOp(Expression** result, Expression *left, Expression *rig
 %token T_IGNORE_ON_ERROR "ignore_on_error (T_IGNORE_ON_ERROR)"
 %token T_CURRENT_FILENAME "current_filename (T_CURRENT_FILENAME)"
 %token T_CURRENT_LINE "current_line (T_CURRENT_LINE)"
+%token T_DEBUGGER "debugger (T_DEBUGGER)"
 %token T_USE "use (T_USE)"
 %token T_OBJECT "object (T_OBJECT)"
 %token T_TEMPLATE "template (T_TEMPLATE)"
@@ -514,6 +515,10 @@ lterm: T_LIBRARY rterm
        {
                $$ = new ContinueExpression(@$);
        }
+       | T_DEBUGGER
+       {
+               $$ = new BreakpointExpression(@$);
+       }
        | apply
        | object
        | T_FOR '(' identifier T_FOLLOWS identifier T_IN rterm ')' rterm_scope_require_side_effect
index a13c760644b4f44d9e29963059eb78775c9af21e..0aa9a484fc03daa04bbe6b2b6e51194a4d0a297d 100644 (file)
 
 using namespace icinga;
 
+boost::signals2::signal<void (ScriptFrame&, ScriptError *ex, const DebugInfo&)> Expression::OnBreakpoint;
+boost::thread_specific_ptr<bool> l_InBreakpointHandler;
+
 Expression::~Expression(void)
 { }
 
+void Expression::ScriptBreakpoint(ScriptFrame& frame, ScriptError *ex, const DebugInfo& di)
+{
+       bool *inHandler = l_InBreakpointHandler.get();
+       if (!inHandler || !*inHandler) {
+               inHandler = new bool(true);
+               l_InBreakpointHandler.reset(inHandler);
+               OnBreakpoint(frame, ex, di);
+               *inHandler = false;
+       }
+}
+
 ExpressionResult Expression::Evaluate(ScriptFrame& frame, DebugHint *dhint) const
 {
        try {
@@ -48,7 +62,8 @@ ExpressionResult Expression::Evaluate(ScriptFrame& frame, DebugHint *dhint) cons
 #endif /* I2_DEBUG */
 
                return DoEvaluate(frame, dhint);
-       } catch (const ScriptError& ex) {
+       } catch (ScriptError& ex) {
+               ScriptBreakpoint(frame, &ex, GetDebugInfo());
                throw;
        } catch (const std::exception& ex) {
                BOOST_THROW_EXCEPTION(ScriptError("Error while evaluating expression: " + String(ex.what()), GetDebugInfo())
@@ -859,3 +874,11 @@ ExpressionResult IncludeExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dh
 
        return res;
 }
+
+ExpressionResult BreakpointExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
+{
+       ScriptBreakpoint(frame, NULL, GetDebugInfo());
+
+       return Empty;
+}
+
index 506c67c620a794d30b956154de169db334f1c638..ea77fd7526470b3803037e155509bd9c171f6cbd 100644 (file)
@@ -198,6 +198,10 @@ public:
        virtual const DebugInfo& GetDebugInfo(void) const;
 
        virtual ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const = 0;
+
+       static boost::signals2::signal<void (ScriptFrame& frame, ScriptError *ex, const DebugInfo& di)> OnBreakpoint;
+
+       static void ScriptBreakpoint(ScriptFrame& frame, ScriptError *ex, const DebugInfo& di);
 };
 
 I2_CONFIG_API Expression *MakeIndexer(ScopeSpecifier scopeSpec, const String& index);
@@ -937,6 +941,17 @@ private:
        String m_Package;
 };
 
+class I2_CONFIG_API BreakpointExpression : public DebuggableExpression
+{
+public:
+       BreakpointExpression(const DebugInfo& debugInfo = DebugInfo())
+           : DebuggableExpression(debugInfo)
+       { }
+
+protected:
+       virtual ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override;
+};
+
 }
 
 #endif /* EXPRESSION_H */