#pragma once #include "V8Tree/Instance.h" #include "V8Tree/Service.h" #include "script/ThreadRef.h" #include "script/ScriptContext.h" struct lua_State; struct lua_Debug; struct Table; namespace RBX { class Script; class ModuleScript; class DataModel; namespace Scripting { class ScriptDebugger; enum BreakOnErrorMode { BreakOnErrorMode_Never = 0, BreakOnErrorMode_AllExceptions, BreakOnErrorMode_UnhandledExceptions }; enum ExecutionMode { ExecutionMode_Continue = 0, ExecutionMode_Break }; struct ISpecialBreakpoint { virtual ~ISpecialBreakpoint() {} virtual bool hitTest(lua_State* L, lua_Debug *ar) = 0; lua_State* baseThread; }; extern const char* const sDebuggerManager; // Contains all data related to Lua debugging of Scripts class DebuggerManager : public DescribedNonCreatable { typedef DescribedNonCreatable Super; public: typedef boost::unordered_map Debuggers; private: bool enabled; Debuggers debuggers; rbx::signals::connection errorSignalConnection; rbx::signals::connection descendantAddedSignalConnection; typedef boost::unordered_map > UnaddedDebuggers; UnaddedDebuggers unaddedDebuggers; typedef boost::unordered_map DebuggersLookup; DebuggersLookup debuggersLookup; RBX::DataModel *dataModel; BreakOnErrorMode breakOnErrorMode; boost::scoped_ptr specialBreakpoint; ExecutionMode executionMode; std::list pausedThreads, resumingPausedThreads, errorThreads; bool resuming; bool scriptAutoResume; public: DebuggerManager(); ~DebuggerManager(); static DebuggerManager& singleton(); void setDataModel(RBX::DataModel *pDataModel); RBX::DataModel* getDataModel(); void enableDebugging(); bool getEnabled() const { return enabled; } BreakOnErrorMode getBreakOnErrorMode() const { return breakOnErrorMode; } void setBreakOnErrorMode(BreakOnErrorMode mode); static Reflection::Variant readWatchValue(std::string expression, int stackFrame, lua_State* L); const Debuggers& getDebuggers() { return debuggers; } shared_ptr getDebuggers_Reflection(); ScriptDebugger* findDebugger(lua_State* L); ScriptDebugger* findDebugger(Instance* script); shared_ptr addDebugger(Instance* script); shared_ptr addDebugger_Reflection(shared_ptr script); void addDebugger(shared_ptr debugger); void populateForLookup(lua_State* L, ScriptDebugger* debugger); void pause(); void resume(); void stepOver(); void stepInto(); void stepOut(); void reset(); void setScriptAutoResume(bool state) { scriptAutoResume = state; } static void hook(lua_State* L, lua_Debug *ar); rbx::signal)> debuggerAdded; rbx::signal)> debuggerRemoved; protected: /*override*/ bool askForbidChild(const Instance* instance) const; /*override*/ void verifyAddChild(const Instance* newChild) const; /*override*/ void onChildAdded(Instance* child); /*override*/ void onChildRemoved(Instance* child); /*override*/ void onChildChanged(Instance* instance, const PropertyChanged& event); void addScriptDebugger(Instance* instance); void onErrorSignal(lua_State* L); void onHook(lua_State* L, lua_Debug *ar); void addUnaddedDebuggerForAddedDescendant(shared_ptr instance); }; class DebuggerBreakpoint; class DebuggerWatch; extern const char* const sScriptDebugger; // Debugs an RBX::Script class ScriptDebugger : public DescribedCreatable { public: typedef boost::unordered_map Breakpoints; typedef std::vector Watches; struct PausedThreadData; typedef boost::unordered_map PausedThreads; private: typedef DescribedCreatable Super; Breakpoints breakpoints; Watches watches; boost::scoped_ptr specialBreakpoint; shared_ptr script; rbx::signals::scoped_connection scriptStartedConnection; rbx::signals::scoped_connection scriptStoppedConnection; rbx::signals::scoped_connection scriptParentChangedConnection; rbx::signals::scoped_connection scriptClonedConnection; Lua::WeakThreadRef rootThread; // The root thread of script. Set when the Script starts and reset when it stops typedef boost::function HookFunction; HookFunction hookFunction; // used to overload the hook function Lua::WeakThreadRef pausedThread; // the thread that a breakpoint hit Lua::WeakThreadRef errorThread; // the thread that has error lua_Debug *breakpointHookData; // set for a short period of time during the hook when we encounter a breakpoint void* globalRawScriptPtr; Table* prevFuncTable; void* prevRawScriptPtr; int currentLine; // the current line when a breakpoint is hit bool ignoreDebuggerBreak; // whether to ignore breakpoint at the current line long currentThreadID; PausedThreads pausedThreads; bool rootThreadResumed; public: ScriptDebugger() :currentLine(0) ,breakpointHookData(NULL) ,globalRawScriptPtr(NULL) ,prevFuncTable(NULL) ,prevRawScriptPtr(NULL) ,ignoreDebuggerBreak(false) ,currentThreadID(0) ,rootThreadResumed(false) {} ScriptDebugger(Instance& script); ~ScriptDebugger(); Instance* getScript() const { return script.get(); } void setScript(Script* value); void setScript(ModuleScript* value); std::string getScriptPath() const; void setScriptPath(std::string scriptPath); void setIgnoreDebuggerBreak(bool state) { ignoreDebuggerBreak = state; } DebuggerBreakpoint* findBreakpoint(int line); shared_ptr setBreakpoint(int line); shared_ptr setBreakpoint_Reflection(int line); const Breakpoints& getBreakpoints() { return breakpoints; } shared_ptr getBreakpoints_Reflection(); shared_ptr addWatch(std::string expression); shared_ptr addWatch_Reflection(std::string expression); const Watches& getWatches() { return watches; } shared_ptr getWatches_Reflection(); Reflection::Variant getWatchValue(DebuggerWatch* watch, int stackFrame = 0); Reflection::Variant getWatchValue_Reflection(shared_ptr watch); Reflection::Variant getKeyValue(std::string key, int stackFrame); bool isDebugging() const { return !rootThread.empty(); } bool isPaused() const; bool hasError() const { return !errorThread.empty(); } int getCurrentLine() const { return currentLine; } void pause(); void resume(); void resumeTo(int line); void stepOver(); void stepInto(); void stepOut(); struct FunctionInfo { boost::shared_ptr script; int frame; std::string name; std::string what; std::string namewhat; std::string short_src; int currentline; int linedefined; int lastlinedefined; }; typedef std::vector Stack; Stack getStack(); shared_ptr getStack_Reflection(); shared_ptr getLocals(int stackIndex); shared_ptr getUpvalues(int stackIndex); shared_ptr getGlobals(); void setLocal(std::string name, Reflection::Variant value, int stackFrame = 0); void setUpvalue(std::string name, Reflection::Variant value, int stackFrame = 0); void setGlobal(std::string name, Reflection::Variant value); void handleError(lua_State* L); void updateHook(); ScriptContext::Result resumeThread(lua_State* L, bool evalLineHookForCurrentLine = false); bool handleHook(lua_State* L, lua_Debug *ar); bool onLineHook(lua_State* L, lua_Debug *ar); void debuggerBreak(lua_State* L, lua_Debug *ar); struct PausedThreadData { int pausedLine; Lua::WeakThreadRef thread; bool hasError; Stack callStack; std::string errorMessage; PausedThreadData() :hasError(false) ,pausedLine(0) { } }; const PausedThreads& getPausedThreads() { return pausedThreads; } bool isPausedThread(long threadID); bool isErrorThread(long threadID); bool isRootThread(long threadID); bool isRootThreadResumed() { return rootThreadResumed; } void setCurrentThread(long threadID); long getCurrentThread() { return currentThreadID; } rbx::signal encounteredBreak; rbx::signal resuming; rbx::signal)> breakpointAdded; rbx::signal)> breakpointRemoved; rbx::signal)> watchAdded; rbx::signal)> watchRemoved; rbx::signal scriptErrorDetected; protected: /*override*/ bool askForbidChild(const Instance* instance) const; /*override*/ void verifySetParent(const Instance* newParent) const; /*override*/ void verifyAddChild(const Instance* newChild) const; /*override*/ void onChildAdded(Instance* child); /*override*/ void onChildRemoved(Instance* child); private: void onScriptStarting(lua_State* L); void onScriptStopped(); void onScriptParentChanged(shared_ptr newParent); void onScriptCloned(boost::shared_ptr clonedScript); bool shouldBreak(DebuggerBreakpoint* bp, lua_State* L); bool hasDifferentScriptInstances(lua_State* L); void handleError(std::string errorMessage, const Stack& stack = Stack()); void setScript(Instance* value); boost::shared_ptr createClone(boost::shared_ptr clonedScript); template void withPausedThreadHook(lua_State* L, lua_Debug *ar, boost::function f, R& r, shared_ptr& error); // TODO: template specialization for R=void template R withPausedThread(boost::function f); static shared_ptr readLocals(int stackIndex, lua_State* L); static shared_ptr readUpvalues(int stackIndex, lua_State* L); static shared_ptr readGlobals(lua_State* L); static Stack readStack(lua_State* L); static RBX::Instance* getScriptForLuaState(lua_State* L); static void updateRootThread(ScriptDebugger* scriptDebugger, lua_State *L); static void setLuaHook(ScriptDebugger* scriptDebugger, int hookMask, lua_State *L); }; extern const char* const sDebuggerBreakpoint; class DebuggerBreakpoint : public DescribedCreatable { bool enabled; int line; std::string condition; public: DebuggerBreakpoint(); DebuggerBreakpoint(int line); ~DebuggerBreakpoint(); int getLine() const { return line; } bool isEnabled() const { return enabled; } const std::string& getCondition() const { return condition; } static Reflection::BoundProp prop_Enabled; static Reflection::BoundProp prop_Condition; protected: /*override*/ void verifySetParent(const Instance* newParent) const; /*override*/ bool askForbidChild(const Instance* instance) const { return true; } /*override*/ void verifyAddChild(const Instance* newChild) const { throw std::runtime_error("DebuggerBreakpoint can have no children"); } private: void setLine(int line); static Reflection::BoundProp prop_Line_Data; }; extern const char* const sDebuggerWatch; class DebuggerWatch : public DescribedCreatable { std::string expression; public: DebuggerWatch() {} DebuggerWatch(std::string expression); const std::string& getCondition() const { return expression; } void checkExpressionSyntax(); const std::string& getExpression() const { return expression; } static Reflection::BoundProp prop_Expression; protected: /*override*/ void verifySetParent(const Instance* newParent) const; /*override*/ bool askForbidChild(const Instance* instance) const { return true; } /*override*/ void verifyAddChild(const Instance* newChild) const { throw std::runtime_error("DebuggerWatch can have no children"); } }; } }