void XDebugServer::parseInput(folly::StringPiece in, String& cmd, Array& args) { // Always start with a blank array args = Array::Create(); // Find the first space in the command. Everything before is assumed to be the // command string auto ptr = strchr(const_cast<char*>(in.data()), ' '); if (ptr != nullptr) { size_t size = ptr - in.data(); cmd = String::attach(StringData::Make(in.data(), size, CopyString)); } else if (in[0] != '\0') { // There are no spaces, the entire string is the command cmd = String::attach(StringData::Make(in.data(), CopyString)); return; } else { throw_exn(Error::Parse); } // Loop starting after the space until the end of the string char opt; bool escaped = false; char* value = nullptr; ParseState state = ParseState::NORMAL; do { ptr++; switch (state) { // A new option which is prefixed with "-" is expected case ParseState::NORMAL: if (*ptr != '-') { throw_exn(Error::Parse); } else { state = ParseState::OPT_FOLLOWS; } break; // The option key follows case ParseState::OPT_FOLLOWS: opt = *ptr; state = ParseState::SEP_FOLLOWS; break; // Expect a " " separator to follow case ParseState::SEP_FOLLOWS: if (*ptr != ' ') { throw_exn(Error::Parse); } else { state = ParseState::VALUE_FOLLOWS_FIRST_CHAR; value = ptr + 1; } break; // Expect the option value's first character to follow. This character // could be either '"'or '-' case ParseState::VALUE_FOLLOWS_FIRST_CHAR: if (*ptr == '"' && opt != '-') { value = ptr + 1; state = ParseState::QUOTED; } else { state = ParseState::VALUE_FOLLOWS; } break; // The option's value should follow case ParseState::VALUE_FOLLOWS: if ((*ptr == ' ' && opt != '-') || *ptr == '\0') { if (args[opt].isNull()) { size_t size = ptr - value; StringData* val_data = StringData::Make(value, size, CopyString); args.set(opt, String::attach(val_data)); state = ParseState::NORMAL; } else { throw_exn(Error::DupArg); } } break; // State when we are within a quoted string case ParseState::QUOTED: // if the quote is escaped, remain in ParseState::QUOTED. This // will also handle other escaped chars, or an instance of // an escaped slash followed by a quote: \\" if (*ptr == '\\') { escaped = !escaped; break; } else if (*ptr != '"') { break; } else if (escaped) { escaped = false; break; } // Need to strip slashes before adding option if (args[opt].isNull()) { size_t size = ptr - value; StringData* val_data = StringData::Make(value, size, CopyString); args.set(opt, HHVM_FN(stripcslashes)(String::attach(val_data))); state = ParseState::SKIP_CHAR; } else { throw_exn(Error::DupArg); } break; // Do nothing case ParseState::SKIP_CHAR: state = ParseState::NORMAL; break; } } while (*ptr != '\0'); }
// Adding a breakpoint. Returns a unique id for the breakpoint. int XDebugThreadBreakpoints::addBreakpoint(XDebugBreakpoint& bp) { auto const id = s_xdebug_breakpoints->m_nextBreakpointId; // php5 xdebug only accepts multiple breakpoints of the same type for // line breakpoints. A useful addition might be to allow multiple of all // types, but for now, multiple function or exception breakpoints results // in the most recent silently being used (matching php5 xdebug). switch (bp.type) { case BreakType::EXCEPTION: { // Remove duplicates then insert the name auto exceptionName = bp.exceptionName.toCppString(); auto iter = EXCEPTION_MAP.find(exceptionName); if (iter != EXCEPTION_MAP.end()) { XDEBUG_REMOVE_BREAKPOINT(iter->second); } EXCEPTION_MAP[exceptionName] = id; break; } // Attempt to find the unit/line combo case BreakType::LINE: { const Unit* unit = find_unit(bp.fileName); if (unit == nullptr) { UNMATCHED.insert(id); break; } // If the file/line combo is invalid, throw an error if (!phpAddBreakPointLine(unit, bp.line)) { throw_exn(XDebugError::BreakpointInvalid); } add_line_breakpoint(id, bp, unit); break; } // Try to find the breakpoint's function case BreakType::CALL: case BreakType::RETURN: { const Class* cls = nullptr; const Func* func = nullptr; if (bp.className.isNull()) { func = Unit::lookupFunc(bp.funcName.get()); } else { cls = Unit::lookupClass(bp.className.toString().get()); if (cls != nullptr) { func = cls->lookupMethod(bp.funcName.get()); } } // Either add the breakpoint or store it as unmatched. If the class // exists, we can verify that the method is valid. if (func != nullptr) { add_func_breakpoint(id, bp, func); } else if (!bp.className.isNull() && cls != nullptr) { throw_exn(XDebugError::BreakpointInvalid); } else { UNMATCHED.insert(id); } break; } } // Success, store the breakpoint and increment the id. BREAKPOINT_MAP[id] = bp; s_xdebug_breakpoints->m_nextBreakpointId++; return id; }