request_id_t SetVariableCommand::targetThreadId(DebuggerSession* session) { ServerObject* obj = session->getServerObject(m_objectId); if (obj == nullptr) { throw DebuggerCommandException("Invalid variablesReference specified."); } if (obj->objectType() == ServerObjectType::Scope) { ScopeObject* scope = static_cast<ScopeObject*>(obj); return scope->m_requestId; } else if (obj->objectType() == ServerObjectType::Variable) { VariableObject* variable = static_cast<VariableObject*>(obj); return variable->m_requestId; } throw DebuggerCommandException("Unexpected server object type"); }
bool SetVariableCommand::setObjectVariable( DebuggerSession* session, const std::string& name, const std::string& value, VariableObject* object, folly::dynamic* result ) { Variant& var = object->m_variable; assertx(var.isObject()); HPHP::String key(name); ObjectData* obj = var.getObjectData(); Variant currentValue = obj->o_get(key, false); if (!currentValue.isInitialized()) { throw DebuggerCommandException( "Failed to set variable: Property not found on object." ); } TypedValue* propValue = currentValue.asTypedValue(); setVariableValue( session, name, value, propValue, object->m_requestId, result ); obj->o_set(key, currentValue); return true; }
ScopeObject* DebuggerSession::getScopeObject(unsigned int objectId) { auto object = getServerObject(objectId); if (object != nullptr) { if (object->objectType() != ServerObjectType::Scope) { throw DebuggerCommandException( "Object with the specified ID is not a scope!" ); } } return static_cast<ScopeObject*>(object); }
SetVariableCommand::SetVariableCommand( Debugger* debugger, folly::dynamic message ) : VSCommand(debugger, message), m_objectId{0} { const folly::dynamic& args = tryGetObject(message, "arguments", s_emptyArgs); const int variablesReference = tryGetInt(args, "variablesReference", -1); if (variablesReference < 0) { throw DebuggerCommandException("Invalid variablesReference specified."); } m_objectId = variablesReference; }
bool SetExceptionBreakpointsCommand::executeImpl(DebuggerSession* session, folly::dynamic* /*responseMsg*/ ) { folly::dynamic& message = getMessage(); const folly::dynamic& args = tryGetObject(message, "arguments", s_emptyArgs); const auto exceptionOptions = tryGetObject(args, "exceptionOptions", s_emptyArgs); auto breakMode = tryGetString(exceptionOptions, "breakMode", ""); // TODO: Named exception breakpoint filters not supported yet. if (breakMode == "") { const folly::dynamic& filters = args["filters"]; if (filters.isArray()) { for (auto it = filters.begin(); it != filters.end(); it++) { const folly::dynamic& filterName = *it; if (filterName.isString()) { const std::string& filterString = filterName.getString(); breakMode = filterString; break; } } } } ExceptionBreakMode mode = ExceptionBreakMode::BreakNone; if (breakMode == "" || breakMode == "never" || breakMode == "none") { mode = ExceptionBreakMode::BreakNone; } else if (breakMode == "always" || breakMode == "all") { mode = ExceptionBreakMode::BreakAll; } else if (breakMode == "unhandled" || breakMode == "uncaught") { mode = ExceptionBreakMode::BreakUnhandled; } else if (breakMode == "userUnhandled") { mode = ExceptionBreakMode::BreakUserUnhandled; } else { throw DebuggerCommandException( "Invalid exception break mode specified." ); } BreakpointManager* bpMgr = session->getBreakpointManager(); bpMgr->setExceptionBreakMode(mode); // Completion of this command does not resume the target. return false; }
bool SetVariableCommand::getBooleanValue(const std::string& str) { // Trim leading and trailing whitespace. std::string trimmed = trimString(str); // Boolean values in PHP are not case sensitive. for (char& c : trimmed) { c = std::toupper(c); } if (trimmed == "TRUE") { return true; } else if (trimmed == "FALSE") { return false; } throw DebuggerCommandException( "The specified value was not a valid boolean value." ); }
bool SetVariableCommand::setArrayVariable( DebuggerSession* session, const std::string& name, const std::string& value, VariableObject* array, folly::dynamic* result ) { VMRegAnchor regAnchor; Variant& var = array->m_variable; assertx(var.isArray()); Array arr = var.toArray(); for (ArrayIter iter(arr); iter; ++iter) { std::string indexName = iter.first().toString().toCppString(); if (indexName == name) { TypedValue* arrayValue = iter.second().asTypedValue(); setVariableValue( session, name, value, arrayValue, array->m_requestId, result ); auto keyVariant = iter.first(); if (keyVariant.isString()) { HPHP::String key = keyVariant.toString(); arr->set(key, tvToInitCell(*arrayValue), false); } else if (keyVariant.isInteger()) { int64_t key = keyVariant.toInt64(); arr->set(key, tvToInitCell(*arrayValue), false); } else { throw DebuggerCommandException("Unsupported array key type."); } return true; } } return false; }
bool SetVariableCommand::executeImpl( DebuggerSession* session, folly::dynamic* responseMsg ) { folly::dynamic body = folly::dynamic::object; folly::dynamic variables = folly::dynamic::array; auto& args = tryGetObject(getMessage(), "arguments", s_emptyArgs); ServerObject* obj = session->getServerObject(m_objectId); if (obj == nullptr) { throw DebuggerCommandException("Invalid variablesReference specified."); } const std::string& suppliedName = tryGetString(args, "name", ""); if (suppliedName.empty()) { throw DebuggerCommandException("Invalid variable name specified."); } // Remove any prefixes that we would have added to the variable name // before presenting it to the front-end. std::string name = removeVariableNamePrefix(suppliedName); bool success = false; const std::string& strValue = tryGetString(args, "value", ""); try { if (obj->objectType() == ServerObjectType::Scope) { ScopeObject* scope = static_cast<ScopeObject*>(obj); switch (scope->m_scopeType) { case ScopeType::Locals: success = setLocalVariable( session, name, strValue, scope, &body ); break; case ScopeType::ServerConstants: success = setConstant( session, name, strValue, scope, &body ); break; // Superglobals and core constants are provided by the current execution // context, rather than trying to overwrite them here, defer to the PHP // console, which will let the runtime enforce whatever policies are // appropriate. case ScopeType::Superglobals: m_debugger->sendUserMessage( "Could not directly set value of superglobal variable, you may " "be able to set this value by running a Hack/PHP command in the " " console.", DebugTransport::OutputLevelError ); break; default: assertx(false); } } else if (obj->objectType() == ServerObjectType::Variable) { VariableObject* variable = static_cast<VariableObject*>(obj); Variant& variant = variable->m_variable; if (variant.isArray()) { success = setArrayVariable(session, name, strValue, variable, &body); } else if (variant.isObject()) { success = setObjectVariable(session, name, strValue, variable, &body); } else { throw DebuggerCommandException( "Failed to set variable: Unexpected variable type." ); } } } catch (DebuggerCommandException e) { m_debugger->sendUserMessage( e.what(), DebugTransport::OutputLevelError ); throw e; } if (!success) { throw DebuggerCommandException( "Failed to set variable." ); } (*responseMsg)["body"] = body; return false; }
void SetVariableCommand::setVariableValue( DebuggerSession* session, const std::string& name, const std::string& value, TypedValue* typedVariable, request_id_t requestId, folly::dynamic* result ) { switch (typedVariable->m_type) { case KindOfBoolean: { bool boolVal = getBooleanValue(value); typedVariable->m_data.num = boolVal ? 1 : 0; } break; case KindOfInt64: try { typedVariable->m_data.num = std::stoi(value, nullptr, 0); } catch (std::exception e) { throw DebuggerCommandException("Invalid value specified."); } break; case KindOfDouble: try { typedVariable->m_data.dbl = std::stod(value); } catch (std::exception e) { throw DebuggerCommandException("Invalid value specified."); } break; case KindOfPersistentString: case KindOfString: { const auto newSd = StringData::Make( value.c_str(), CopyString ); if (typedVariable->m_type == KindOfString && typedVariable->m_data.pstr != nullptr) { typedVariable->m_data.pstr->decRefCount(); } typedVariable->m_data.pstr = newSd; typedVariable->m_type = KindOfString; break; } case KindOfUninit: case KindOfNull: // In the case of uninit and null, we don't even know how to interpret // the value from the client, because the object has no known type. // Fallthrough. case KindOfResource: case KindOfPersistentVec: case KindOfVec: case KindOfPersistentArray: case KindOfArray: case KindOfPersistentDict: case KindOfDict: case KindOfPersistentKeyset: case KindOfKeyset: case KindOfRef: case KindOfObject: // For complex types, we need to run PHP code to create a new object // or determine what reference to assign, making direct assignment via // reflection impractical, so we defer to the console REPL, which will use // an evaluation command to run PHP. // NOTE: It would be nice in the future to just run an eval command here // on the user's behalf. At the moment, if we are setting a child prop // on an object or array, we don't know the fully qualified name string // to pass to PHP though, since we only have a reference to the container. throw DebuggerCommandException( "Failed to set object value. Please use the console to set the value " "of complex objects." ); default: throw DebuggerCommandException("Unexpected variable type"); } Variant variable = tvAsVariant(typedVariable); *result = VariablesCommand::serializeVariable( session, requestId, name, variable ); }
bool RunToLocationCommand::executeImpl(DebuggerSession* session, folly::dynamic* /*responseMsg*/ ) { folly::dynamic& message = getMessage(); const folly::dynamic& args = tryGetObject(message, "arguments", s_emptyArgs); const folly::dynamic& source = tryGetObject(args, "source", s_emptyArgs); const std::string& filePath = tryGetString(source, "path", ""); if (filePath.empty()) { throw DebuggerCommandException( "Run to location source path not specified." ); } const ClientPreferences& prefs = m_debugger->getClientPreferences(); const std::string& path = File::TranslatePath(String(filePath)).toCppString(); BreakpointManager* bpMgr = session->getBreakpointManager(); int line = tryGetInt(args, "line", -1); if (!prefs.linesStartAt1) { // If client lines start at 0, make them 1 based. line++; } if (line <= 0) { throw DebuggerCommandException( "Invalid continue to line specified." ); } // See if there's already a breakpoint at this file + line. const auto bpIds = bpMgr->getBreakpointIdsByFile(path); for (auto it = bpIds.begin(); it != bpIds.end(); it++) { Breakpoint* bp = bpMgr->getBreakpointById(*it); if (bp->m_line == line) { // There's already a breakpoint installed at the run to location. // Just resume the request thread and let it hit. return true; } } // Find a compilation unit to place a temp bp in. HPHP::String unitPath(path.c_str()); const auto compilationUnit = lookupUnit(unitPath.get(), "", nullptr); if (compilationUnit == nullptr) { throw DebuggerCommandException( "Could not find a loaded compilation unit to run to location in!" ); } std::pair<int, int> runLocation = m_debugger->calibrateBreakpointLineInUnit(compilationUnit, line); if (runLocation.first < 0) { throw DebuggerCommandException( "Could not find a suitable location in the compilation unit to run to!" ); } if (!phpAddBreakPointLine(compilationUnit, runLocation.first)) { throw DebuggerCommandException( "Failed to install temporary breakpoint at location." ); } RequestInfo* ri = m_debugger->getRequestInfo(); ri->m_runToLocationInfo.path = path; ri->m_runToLocationInfo.line = line; // Tell the user where we're running to. Resolving the file path and // calibrating the source line could have modified things a bit. std::string userMsg = "Resuming request "; userMsg += std::to_string(targetThreadId(session)); userMsg += " and running to resolved location "; userMsg += path; userMsg += ":"; userMsg += std::to_string(runLocation.second); m_debugger->sendUserMessage(userMsg.c_str(), DebugTransport::OutputLevelInfo); // Resume only this request thread. return true; }
bool SetBreakpointsCommand::executeImpl( DebuggerSession* session, folly::dynamic* responseMsg ) { folly::dynamic& message = getMessage(); const folly::dynamic& args = tryGetObject(message, "arguments", s_emptyArgs); const folly::dynamic& source = tryGetObject(args, "source", s_emptyArgs); // SourceReference is not supported. int sourceRef = tryGetInt(source, "sourceReference", INT_MIN); if (sourceRef != INT_MIN) { throw DebuggerCommandException("SourceReference breakpoint not supported."); } const std::string& filePath = tryGetString(source, "path", ""); if (filePath.empty()) { throw DebuggerCommandException( "Breakpoint source path not specified." ); } const std::string& path = StatCache::realpath( File::TranslatePath(String(filePath)).toCppString().c_str()); BreakpointManager* bpMgr = session->getBreakpointManager(); // Make a map of line -> breakpoint for all breakpoints in this file before // this set breakpoints operation. std::unordered_map<int, Breakpoint*> oldBpLines; const auto oldBpIds = bpMgr->getBreakpointIdsByFile(path); for (auto it = oldBpIds.begin(); it != oldBpIds.end(); it++) { Breakpoint* bp = bpMgr->getBreakpointById(*it); std::pair<int, Breakpoint*> pair; pair.first = bp->m_line; pair.second = bp; oldBpLines.emplace(pair); } const ClientPreferences& prefs = m_debugger->getClientPreferences(); const std::string empty = ""; (*responseMsg)["body"] = folly::dynamic::object; (*responseMsg)["body"]["breakpoints"] = folly::dynamic::array(); auto& responseBps = (*responseMsg)["body"]["breakpoints"]; try { const folly::dynamic& sourceBps = args["breakpoints"]; if (sourceBps.isArray()) { for (auto it = sourceBps.begin(); it != sourceBps.end(); it++) { const folly::dynamic& sourceBp = *it; if (sourceBp.isObject()) { int line = tryGetInt(sourceBp, "line", -1); if (!prefs.linesStartAt1) { // If client lines start at 0, make them 1 based. line++; } if (line <= 0) { throw DebuggerCommandException( "Breakpoint has invalid line number." ); } int column = tryGetInt(sourceBp, "column", 0); if (!prefs.columnsStartAt1) { // If client cols start at 0, make them 1 based. column++; } if (column < 0) { column = 0; } const std::string& condition = tryGetString(sourceBp, "condition", empty); const std::string& hitCondition = tryGetString(sourceBp, "hitCondition", empty); auto bpIt = oldBpLines.find(line); if (bpIt == oldBpLines.end()) { // Create a new breakpoint. int newBpId = bpMgr->addBreakpoint( line, column, path, condition, hitCondition ); m_debugger->onBreakpointAdded(newBpId); folly::dynamic newBreakpointInfo = folly::dynamic::object; newBreakpointInfo["id"] = newBpId; newBreakpointInfo["verified"] = false; responseBps.push_back(newBreakpointInfo); } else { Breakpoint* bp = bpIt->second; // Update breakpoint conditions in case they've changed. bp->updateConditions(condition, hitCondition); bool verified = bpMgr->isBreakpointResolved(bp->m_id); int responseLine = line; if (verified) { responseLine = bp->m_resolvedLocation.m_startLine; if (!prefs.linesStartAt1) { responseLine--; } } folly::dynamic bpInfo = folly::dynamic::object; bpInfo["id"] = bp->m_id; bpInfo["line"] = responseLine; bpInfo["verified"] = verified; responseBps.push_back(bpInfo); // Remove this bp from oldBpLines so that after processing this // loop, oldBpLines contains any breakpoints that did not match // a bp in the request from the client. oldBpLines.erase(bpIt); } } } // Remove any breakpoints that should no longer exist in this file. for (auto it = oldBpLines.begin(); it != oldBpLines.end(); it++) { const Breakpoint* bp = it->second; bpMgr->removeBreakpoint(bp->m_id); } } } catch (std::out_of_range e) { } // Completion of this command does not resume the target. return false; }