#include "stdafx.h" #include "script/DebuggerManager.h" #include "script/ScriptContext.h" #include "script/Script.h" #include "script/ModuleScript.h" #include "script/LuaArguments.h" #include "util/ProtectedString.h" #include "lua/lua.hpp" #include "lstate.h" #include "ltable.h" #include "v8datamodel/DataModel.h" #include "boost/tokenizer.hpp" #include "boost/algorithm/string.hpp" FASTFLAGVARIABLE(LuaDebugger, false) FASTFLAGVARIABLE(LuaDebuggerBreakOnError, false) namespace RBX { namespace Scripting { const char* const sDebuggerManager = "DebuggerManager"; const char* const sScriptDebugger = "ScriptDebugger"; const char* const sDebuggerBreakpoint = "DebuggerBreakpoint"; const char* const sDebuggerWatch = "DebuggerWatch"; static void checkDebugging() { if (!FFlag::LuaDebugger) throw std::runtime_error("debugging is disabled"); } class PauseNowBreakpoint : public ISpecialBreakpoint { public: PauseNowBreakpoint() { baseThread = NULL; } virtual bool hitTest(lua_State* L, lua_Debug *ar) { return ar->event == LUA_HOOKLINE; } }; class StepInBreakpoint : public ISpecialBreakpoint { bool hitPausedLine; public: StepInBreakpoint():hitPausedLine(false) { baseThread = NULL; } virtual bool hitTest(lua_State* L, lua_Debug *ar) { if (baseThread && (baseThread != L)) return false; if (!hitPausedLine) { hitPausedLine = ar->event == LUA_HOOKLINE; return false; } // The next line we hit is either a step-in or a step-over. Either is fine if (ar->event == LUA_HOOKLINE) return true; return false; } }; class StepOutBreakpoint : public ISpecialBreakpoint { int depth; bool readyForLine; public: StepOutBreakpoint():depth(0),readyForLine(false) { baseThread = NULL; } virtual bool hitTest(lua_State* L, lua_Debug *ar) { if (baseThread && (baseThread != L)) return false; if (readyForLine) return ar->event == LUA_HOOKLINE; RBXASSERT(depth >= 0); if (strcmp(ar->what, "C") != 0) { if (ar->event == LUA_HOOKRET) // what about LUA_HOOKTAILRET? depth--; else if (ar->event == LUA_HOOKCALL) depth++; } RBXASSERT(depth >= -1); readyForLine = depth == -1; return false; } }; class StepOverBreakpoint : public ISpecialBreakpoint { int depth; bool hitPausedLine; public: StepOverBreakpoint():depth(0),hitPausedLine(false) { baseThread = NULL; } virtual bool hitTest(lua_State* L, lua_Debug *ar) { if (baseThread && (baseThread != L)) return false; if (!hitPausedLine) { hitPausedLine = ar->event == LUA_HOOKLINE; return false; } if (depth == 0 && ar->event == LUA_HOOKLINE) return true; if (depth < 0) // We stepped out, so all we are waiting for is the next line return ar->event == LUA_HOOKLINE; RBXASSERT(depth >= 0); //if (strcmp(ar->what, "C") != 0) { if (ar->event == LUA_HOOKRET) // what about LUA_HOOKTAILRET? depth--; else if (ar->event == LUA_HOOKCALL) depth++; } RBXASSERT(depth >= -1); return false; } }; } } using namespace RBX; using namespace Scripting; REFLECTION_BEGIN(); static Reflection::BoundFuncDesc(shared_ptr)> func_AddDebugger(&DebuggerManager::addDebugger_Reflection, "AddDebugger", "script", Security::None); static Reflection::BoundFuncDesc func_Enable(&DebuggerManager::enableDebugging, "EnableDebugging", Security::LocalUser); static Reflection::PropDescriptor prop_Enabled("DebuggingEnabled", category_Data, &DebuggerManager::getEnabled, NULL); static Reflection::BoundFuncDesc func_Resume(&DebuggerManager::resume, "Resume", Security::None); static Reflection::BoundFuncDesc func_StepOver(&DebuggerManager::stepOver, "StepOver", Security::None); static Reflection::BoundFuncDesc func_StepInto(&DebuggerManager::stepInto, "StepIn", Security::None); static Reflection::BoundFuncDesc func_StepOut(&DebuggerManager::stepOut, "StepOut", Security::None); static Reflection::BoundFuncDesc()> func_GetDebuggers(&DebuggerManager::getDebuggers_Reflection, "GetDebuggers", Security::None); static Reflection::EventDesc)> event_DebuggerAdded(&DebuggerManager::debuggerAdded, "DebuggerAdded", "debugger"); static Reflection::EventDesc)> event_DebuggerRemoved(&DebuggerManager::debuggerRemoved, "DebuggerRemoved", "debugger"); static Reflection::BoundFuncDesc(int)> func_SetBreakpoint(&ScriptDebugger::setBreakpoint_Reflection, "SetBreakpoint", "line", Security::None); static Reflection::BoundFuncDesc()> func_GetBreakpoints(&ScriptDebugger::getBreakpoints_Reflection, "GetBreakpoints", Security::None); static Reflection::BoundFuncDesc(std::string)> func_AddWatch(&ScriptDebugger::addWatch_Reflection, "AddWatch", "expression", Security::None); static Reflection::BoundFuncDesc()> func_GetWatches(&ScriptDebugger::getWatches_Reflection, "GetWatches", Security::None); static Reflection::BoundFuncDesc)> func_GetValue(&ScriptDebugger::getWatchValue_Reflection, "GetWatchValue", "watch", Security::None); static Reflection::BoundFuncDesc func_Resume_dep(&ScriptDebugger::resume, "Resume", Security::None, Reflection::Descriptor::Attributes::deprecated()); static Reflection::BoundFuncDesc func_StepOver_dep(&ScriptDebugger::stepOver, "StepOver", Security::None, Reflection::Descriptor::Attributes::deprecated()); static Reflection::BoundFuncDesc func_StepInto_dep(&ScriptDebugger::stepInto, "StepIn", Security::None, Reflection::Descriptor::Attributes::deprecated()); static Reflection::BoundFuncDesc func_StepOut_dep(&ScriptDebugger::stepOut, "StepOut", Security::None, Reflection::Descriptor::Attributes::deprecated()); static Reflection::BoundFuncDesc()> func_GetStack(&ScriptDebugger::getStack_Reflection, "GetStack", Security::None); static Reflection::BoundFuncDesc(int)> func_GetLocals(&ScriptDebugger::getLocals, "GetLocals", "stackFrame", 0, Security::None); static Reflection::BoundFuncDesc(int)> func_GetUpvalues(&ScriptDebugger::getUpvalues, "GetUpvalues", "stackFrame", 0, Security::None); static Reflection::BoundFuncDesc()> func_GetGlobals(&ScriptDebugger::getGlobals, "GetGlobals", Security::None); static Reflection::BoundFuncDesc func_SetLocal(&ScriptDebugger::setLocal, "SetLocal", "name", "value", "stackFrame", 0, Security::None); static Reflection::BoundFuncDesc func_SetUpvalue(&ScriptDebugger::setUpvalue, "SetUpvalue", "name", "value", "stackFrame", 0, Security::None); static Reflection::BoundFuncDesc func_SetGlobal(&ScriptDebugger::setGlobal, "SetGlobal", "name", "value", Security::None); // The Script property is read-only by design. If we want to make it writable, then we need to watch for changes and update DebuggerManager::debuggers const Reflection::RefPropDescriptor prop_Script("Script", category_Data, &ScriptDebugger::getScript, NULL, Reflection::PropertyDescriptor::UI); const Reflection::PropDescriptor prop_ScriptPath("ScriptPath", category_Data, &ScriptDebugger::getScriptPath, &ScriptDebugger::setScriptPath, Reflection::PropertyDescriptor::STREAMING); static Reflection::PropDescriptor prop_IsDebugging("IsDebugging", category_Data, &ScriptDebugger::isDebugging, NULL, Reflection::PropertyDescriptor::UI); static Reflection::PropDescriptor prop_IsPaused("IsPaused", category_Data, &ScriptDebugger::isPaused, NULL, Reflection::PropertyDescriptor::UI); static Reflection::PropDescriptor prop_CurrentLine("CurrentLine", category_Data, &ScriptDebugger::getCurrentLine, NULL, Reflection::PropertyDescriptor::UI); static Reflection::EventDesc event_EncounteredBreak(&ScriptDebugger::encounteredBreak, "EncounteredBreak", "line"); static Reflection::EventDesc event_Resuming(&ScriptDebugger::resuming, "Resuming"); static Reflection::EventDesc)> event_BreakpointAdded(&ScriptDebugger::breakpointAdded, "BreakpointAdded", "breakpoint"); static Reflection::EventDesc)> event_BreakpointRemoved(&ScriptDebugger::breakpointRemoved, "BreakpointRemoved", "breakpoint"); static Reflection::EventDesc)> event_WatchAdded(&ScriptDebugger::watchAdded, "WatchAdded", "watch"); static Reflection::EventDesc)> event_WatchRemoved(&ScriptDebugger::watchRemoved, "WatchRemoved", "watch"); // The Line property is read-only by design. If we want to make it writable, then we need to watch for changes and update ScriptDebugger::breakpoints static Reflection::PropDescriptor prop_Line("Line", category_Data, &DebuggerBreakpoint::getLine, NULL, Reflection::PropertyDescriptor::UI); Reflection::BoundProp DebuggerBreakpoint::prop_Line_Data("line", category_Data, &DebuggerBreakpoint::line, Reflection::PropertyDescriptor::STREAMING); Reflection::BoundProp DebuggerBreakpoint::prop_Enabled("IsEnabled", category_Data, &DebuggerBreakpoint::enabled); Reflection::BoundProp DebuggerBreakpoint::prop_Condition("Condition", category_Data, &DebuggerBreakpoint::condition); Reflection::BoundProp DebuggerWatch::prop_Expression("Expression", category_Data, &DebuggerWatch::expression); static Reflection::BoundFuncDesc func_CheckExpressionSyntax(&DebuggerWatch::checkExpressionSyntax, "CheckSyntax", Security::None); REFLECTION_END(); static DebuggerManager* gDebuggerManager = NULL; static void doBasicSingleton() { static shared_ptr sing = Creatable::create(); gDebuggerManager = sing.get(); } static void initDebuggerManagerSingleton() { doBasicSingleton(); } DebuggerManager& DebuggerManager::singleton() { static boost::once_flag flag = BOOST_ONCE_INIT; boost::call_once(&initDebuggerManagerSingleton, flag); return *gDebuggerManager; } DebuggerManager::DebuggerManager(void) :enabled(false) ,dataModel(NULL) ,executionMode(ExecutionMode_Continue) ,breakOnErrorMode(BreakOnErrorMode_Never) ,resuming(false) ,scriptAutoResume(true) { setName(sDebuggerManager); } DebuggerManager::~DebuggerManager(void) { } void DebuggerManager::setDataModel(RBX::DataModel *pDataModel) { if (pDataModel == dataModel) return; if (dataModel) removeAllChildren(); errorSignalConnection.disconnect(); descendantAddedSignalConnection.disconnect(); breakOnErrorMode = BreakOnErrorMode_Never; debuggersLookup.clear(); debuggers.clear(); unaddedDebuggers.clear(); reset(); dataModel = pDataModel; if (dataModel) descendantAddedSignalConnection = dataModel->getOrCreateDescendantAddedSignal()->connect(boost::bind(&DebuggerManager::addUnaddedDebuggerForAddedDescendant, this, _1)); } RBX::DataModel* DebuggerManager::getDataModel() { return dataModel; } void DebuggerManager::reset() { specialBreakpoint.reset(); pausedThreads.clear(); executionMode = ExecutionMode_Continue; resuming = false; scriptAutoResume = true; } void DebuggerManager::enableDebugging() { enabled = true; raiseChanged(prop_Enabled); } void DebuggerManager::setBreakOnErrorMode(BreakOnErrorMode mode) { if (!FFlag::LuaDebuggerBreakOnError || (mode == breakOnErrorMode) || !dataModel) return; errorSignalConnection.disconnect(); breakOnErrorMode = mode; // connect with ScriptContext signal, using this we will catch errors which are reported in Output window if (breakOnErrorMode != BreakOnErrorMode_Never) { ScriptContext* pScriptContext = ServiceProvider::find(dataModel); if (pScriptContext) errorSignalConnection = pScriptContext->scriptErrorDetected.connect(boost::bind(&DebuggerManager::onErrorSignal, this, _1)); } // update hooks: this will make sure correct mask is set for lua threads for (Debuggers::iterator iter = debuggers.begin(); iter != debuggers.end(); ++iter) iter->second->updateHook(); } shared_ptr DebuggerManager::getDebuggers_Reflection() { shared_ptr result = rbx::make_shared(debuggers.size()); int i = 0; for (Debuggers::const_iterator iter = debuggers.begin(); iter != debuggers.end(); ++iter, ++i) (*result)[i] = shared_from(iter->second); return result; } ScriptDebugger* DebuggerManager::findDebugger( lua_State* L ) { DebuggersLookup::const_iterator iter = debuggersLookup.find(L); if (iter != debuggersLookup.end()) return iter->second; ScriptDebugger* scriptDebugger = findDebugger(RobloxExtraSpace::get(L)->script.lock().get()); if (scriptDebugger) debuggersLookup[L] = scriptDebugger; return scriptDebugger; } bool DebuggerManager::askForbidChild(const Instance* newChild) const { return !Instance::fastDynamicCast(newChild); } void DebuggerManager::verifyAddChild(const Instance* newChild) const { if (!askForbidChild(newChild)) return; throw std::runtime_error("Only ScriptDebuggers can be children of DebuggerManager"); } shared_ptr DebuggerManager::addDebugger_Reflection( shared_ptr script ) { if (Script* s = Instance::fastDynamicCast