diff --git a/hphp/runtime/debugger/break_point.cpp b/hphp/runtime/debugger/break_point.cpp index dc75163a9..4bf4989f7 100644 --- a/hphp/runtime/debugger/break_point.cpp +++ b/hphp/runtime/debugger/break_point.cpp @@ -189,9 +189,10 @@ BreakPointInfo::~BreakPointInfo() { } } -void BreakPointInfo::sendImpl(DebuggerThriftBuffer &thrift) { +void BreakPointInfo::sendImpl(int version, DebuggerThriftBuffer &thrift) { TRACE(2, "BreakPointInfo::sendImpl\n"); thrift.write((int8_t)m_state); + if (version >= 1) thrift.write((int8_t)m_bindState); thrift.write((int8_t)m_interruptType); thrift.write(m_file); thrift.write(m_line1); @@ -208,10 +209,13 @@ void BreakPointInfo::sendImpl(DebuggerThriftBuffer &thrift) { thrift.write(m_exceptionObject); } -void BreakPointInfo::recvImpl(DebuggerThriftBuffer &thrift) { +void BreakPointInfo::recvImpl(int version, DebuggerThriftBuffer &thrift) { TRACE(2, "BreakPointInfo::recvImpl\n"); int8_t tmp; thrift.read(tmp); m_state = (State)tmp; + if (version >= 1) { + thrift.read(tmp); m_bindState = (BindState)tmp; + } thrift.read(tmp); m_interruptType = (InterruptType)tmp; thrift.read(m_file); thrift.read(m_line1); @@ -970,17 +974,17 @@ bool BreakPointInfo::checkClause() { /////////////////////////////////////////////////////////////////////////////// -void BreakPointInfo::SendImpl(const BreakPointInfoPtrVec &bps, +void BreakPointInfo::SendImpl(int version, const BreakPointInfoPtrVec &bps, DebuggerThriftBuffer &thrift) { TRACE(2, "BreakPointInfo::SendImpl\n"); int16_t size = bps.size(); thrift.write(size); for (int i = 0; i < size; i++) { - bps[i]->sendImpl(thrift); + bps[i]->sendImpl(version, thrift); } } -void BreakPointInfo::RecvImpl(BreakPointInfoPtrVec &bps, +void BreakPointInfo::RecvImpl(int version, BreakPointInfoPtrVec &bps, DebuggerThriftBuffer &thrift) { TRACE(2, "BreakPointInfo::RecvImpl\n"); int16_t size; @@ -988,7 +992,7 @@ void BreakPointInfo::RecvImpl(BreakPointInfoPtrVec &bps, bps.resize(size); for (int i = 0; i < size; i++) { BreakPointInfoPtr bpi(new BreakPointInfo()); - bpi->recvImpl(thrift); + bpi->recvImpl(version, thrift); bps[i] = bpi; } } diff --git a/hphp/runtime/debugger/break_point.h b/hphp/runtime/debugger/break_point.h index 6cd6d73bf..3c702c008 100644 --- a/hphp/runtime/debugger/break_point.h +++ b/hphp/runtime/debugger/break_point.h @@ -115,13 +115,20 @@ DECLARE_BOOST_TYPES(DFunctionInfo); DECLARE_BOOST_TYPES(BreakPointInfo); class BreakPointInfo { public: - // The state of break point + // The state of the break point enum State : int8_t { Always = -1, // always break when reaching this break point Once = 1, // break the first time, then disable Disabled = 0, // carry on with execution when reaching this break point }; + // Does the break point correspond to a known executable location? + enum BindState : int8_t { + KnownToBeValid, // Breakpoint refers to valid location or member + KnownToBeInvalid, // Breakpoint cannot be bound (no such class or line) + Unknown, // The file or class referenced by breakpoint is not loaded + }; + static const char *ErrorClassName; static const char *GetInterruptName(InterruptType interrupt); @@ -154,12 +161,12 @@ public: std::string site() const; std::string regex(const std::string &name) const; - void sendImpl(DebuggerThriftBuffer &thrift); - void recvImpl(DebuggerThriftBuffer &thrift); + void sendImpl(int version, DebuggerThriftBuffer &thrift); + void recvImpl(int version, DebuggerThriftBuffer &thrift); - static void SendImpl(const BreakPointInfoPtrVec &bps, + static void SendImpl(int version, const BreakPointInfoPtrVec &bps, DebuggerThriftBuffer &thrift); - static void RecvImpl(BreakPointInfoPtrVec &bps, + static void RecvImpl(int version, BreakPointInfoPtrVec &bps, DebuggerThriftBuffer &thrift); bool breakable(int stackDepth) const; @@ -170,6 +177,7 @@ public: int16_t m_index; // client side index number State m_state; // Always, Once, Disabled + BindState m_bindState; // KnownToBeValid, KnownToBeInvalid, Unknown bool m_valid; // false if syntactically invalid InterruptType m_interruptType; // Why this break point was reached diff --git a/hphp/runtime/debugger/cmd/cmd_break.cpp b/hphp/runtime/debugger/cmd/cmd_break.cpp index b3cccd254..903eed9c5 100644 --- a/hphp/runtime/debugger/cmd/cmd_break.cpp +++ b/hphp/runtime/debugger/cmd/cmd_break.cpp @@ -28,13 +28,22 @@ void CmdBreak::sendImpl(DebuggerThriftBuffer &thrift) { // via Thrift, m_breakpoints points to a copy that is placed in m_bps. assert(m_breakpoints); DebuggerCommand::sendImpl(thrift); - BreakPointInfo::SendImpl(*m_breakpoints, thrift); + BreakPointInfo::SendImpl(this->m_version, *m_breakpoints, thrift); } // Deserializes a CmdBreak from the given Thrift buffer. void CmdBreak::recvImpl(DebuggerThriftBuffer &thrift) { DebuggerCommand::recvImpl(thrift); - BreakPointInfo::RecvImpl(m_bps, thrift); + BreakPointInfo::RecvImpl(this->m_version, m_bps, thrift); + m_breakpoints = &m_bps; + // Old senders will set version to 0. A new sender sets it to 1 + // and then expects an answer using version 2. + // Note that version 1 is the same format as version 0, so old + // receivers will not break when receiving a version 1 message. + // This code ensures that version 2 messages are received only + // by receivers that previously sent a version 1 message (thus + // indicating their ability to deal with version 2 messages). + if (this->m_version == 1) this->m_version = 2; } // Informs the client of all strings that may follow a break command. @@ -179,10 +188,13 @@ void CmdBreak::help(DebuggerClient &client) { // Carries out the "break list" command. void CmdBreak::processList(DebuggerClient &client) { m_breakpoints = client.getBreakPoints(); + updateServer(client); for (int i = 0; i < (int)m_breakpoints->size(); i++) { BreakPointInfoPtr bpi = m_breakpoints->at(i); - client.print(" %d\t%s %s", bpi->index(), bpi->state(true).c_str(), - bpi->desc().c_str()); + const char* boundStr = + bpi->m_bindState == BreakPointInfo::Unknown ? " (unbound)" : ""; + client.print(" %d\t%s %s%s", bpi->index(), bpi->state(true).c_str(), + bpi->desc().c_str(), boundStr); } if (m_breakpoints->empty()) { client.tutorial( @@ -330,19 +342,70 @@ void CmdBreak::processStatusChange(DebuggerClient &client) { // Uses the client to send this command to the server, which // will update its breakpoint list with the one in this command. // The client will block until the server echoes -// this command back to it. The echoed command is discarded. -bool CmdBreak::updateServer(DebuggerClient &client) { +// this command back to it. The echoed command is discarded. +// If the server checked the validity of the breakpoints, the values +// of the m_bindState flags are copied to the client's breakpoint list. +void CmdBreak::updateServer(DebuggerClient &client) { m_body = "update"; - client.xend(this); - return true; + auto serverReply = client.xend(this); + if (serverReply->m_version == 2) { + // The server will have checked the breakpoint list for validity. + // Transfer the results to the local breakpoint list. + auto cbreakpoints = *client.getBreakPoints(); + auto sbreakpoints = *serverReply->m_breakpoints; + int csize = cbreakpoints.size(); + int ssize = sbreakpoints.size(); + assert(csize == ssize); + if (csize > ssize) csize = ssize; + for (int i = 0; i < csize; i++) { + cbreakpoints[i]->m_bindState = sbreakpoints[i]->m_bindState; + } + } } // Creates a new CmdBreak instance, sets its breakpoints to the client's // list, sends the command to the server and waits for a response. -bool CmdBreak::SendClientBreakpointListToServer(DebuggerClient &client) { +void CmdBreak::SendClientBreakpointListToServer(DebuggerClient &client) { auto cmd = CmdBreak(); cmd.m_breakpoints = client.getBreakPoints(); - return cmd.updateServer(client); + cmd.updateServer(client); +} + + +void ReportBreakpointBindState(DebuggerClient &client, BreakPointInfoPtr bpi) { + switch (bpi->m_bindState) { + case BreakPointInfo::KnownToBeValid: + client.info("Breakpoint %d set %s", bpi->index(), bpi->desc().c_str()); + break; + case BreakPointInfo::KnownToBeInvalid: + client.info("Breakpoint %d not set %s", bpi->index(), bpi->desc().c_str()); + if (!bpi->getClass().empty()) { + client.info("Because method %s does not exist.", + bpi->getFuncName().c_str()); + } else if (!bpi->getExceptionClass().empty()) { + client.info("Because class %s is not an exception.", + bpi->getExceptionClass().c_str()); + } else { + client.info("Because the line does not exist or is not executable code."); + } + break; + case BreakPointInfo::Unknown: + client.info("Breakpoint %d set %s", bpi->index(), bpi->desc().c_str()); + if (!bpi->getClass().empty()) { + client.info("But wont break until class %s has been loaded.", + bpi->getClass().c_str()); + } else if (!bpi->getFuncName().empty()) { + client.info("But wont break until function %s has been loaded.", + bpi->getFuncName().c_str()); + } else if (!bpi->getExceptionClass().empty()) { + client.info("But note that class %s has yet been loaded.", + bpi->getExceptionClass().c_str()); + } else { + client.info("But wont break until file %s has been loaded.", + bpi->m_file.c_str()); + } + break; + } } // Adds conditional or watch clause to the breakpoint info if needed. @@ -368,7 +431,10 @@ bool CmdBreak::addToBreakpointListAndUpdateServer( } m_breakpoints->push_back(bpi); updateServer(client); - client.info("Breakpoint %d set %s", bpi->index(), bpi->desc().c_str()); + ReportBreakpointBindState(client, bpi); + if (bpi->m_bindState == BreakPointInfo::KnownToBeInvalid) { + m_breakpoints->pop_back(); + } return true; } diff --git a/hphp/runtime/debugger/cmd/cmd_break.h b/hphp/runtime/debugger/cmd/cmd_break.h index 16fe1cb8a..93172ef56 100644 --- a/hphp/runtime/debugger/cmd/cmd_break.h +++ b/hphp/runtime/debugger/cmd/cmd_break.h @@ -25,7 +25,9 @@ namespace HPHP { namespace Eval { DECLARE_BOOST_TYPES(CmdBreak); class CmdBreak : public DebuggerCommand { public: - CmdBreak() : DebuggerCommand(KindOfBreak), m_breakpoints(nullptr) {} + CmdBreak() : DebuggerCommand(KindOfBreak), m_breakpoints(nullptr) { + m_version = 1; + } // Informs the client of all strings that may follow a break command. // Used for auto completion. The client uses the prefix of the argument @@ -48,7 +50,7 @@ public: // Creates a new CmdBreak instance, sets its breakpoints to the client's // list, sends the command to the server and waits for a response. - static bool SendClientBreakpointListToServer(DebuggerClient &client); + static void SendClientBreakpointListToServer(DebuggerClient &client); protected: // Carries out the Break command. This always involves an action on the @@ -84,7 +86,7 @@ private: // will update its breakpoint list with the one in this command. // The client will block until the server echoes // this command back to it. The echoed command is discarded. - bool updateServer(DebuggerClient &client); + void updateServer(DebuggerClient &client); // Carries out the "break list" command. void processList(DebuggerClient &client); diff --git a/hphp/runtime/debugger/cmd/cmd_interrupt.cpp b/hphp/runtime/debugger/cmd/cmd_interrupt.cpp index 5a0cee236..e12677c15 100644 --- a/hphp/runtime/debugger/cmd/cmd_interrupt.cpp +++ b/hphp/runtime/debugger/cmd/cmd_interrupt.cpp @@ -56,7 +56,7 @@ void CmdInterrupt::sendImpl(DebuggerThriftBuffer &thrift) { } else { thrift.write(false); } - BreakPointInfo::SendImpl(m_matched, thrift); + BreakPointInfo::SendImpl(0, m_matched, thrift); } void CmdInterrupt::recvImpl(DebuggerThriftBuffer &thrift) { @@ -85,7 +85,7 @@ void CmdInterrupt::recvImpl(DebuggerThriftBuffer &thrift) { thrift.read(m_bpi->m_exceptionClass); thrift.read(m_bpi->m_exceptionObject); } - BreakPointInfo::RecvImpl(m_matched, thrift); + BreakPointInfo::RecvImpl(0, m_matched, thrift); } std::string CmdInterrupt::desc() const { diff --git a/hphp/runtime/debugger/debugger_proxy.cpp b/hphp/runtime/debugger/debugger_proxy.cpp index fb5e4c01e..33575b45a 100644 --- a/hphp/runtime/debugger/debugger_proxy.cpp +++ b/hphp/runtime/debugger/debugger_proxy.cpp @@ -155,8 +155,6 @@ void DebuggerProxy::notifyDummySandbox() { m_dummySandbox->notifySignal(CmdSignal::SignalBreak); } -// Hold the entire set of breakpoints, and sift breakpoints by function and -// class name into separate containers for later. void DebuggerProxy::setBreakPoints(BreakPointInfoPtrVec &breakpoints) { TRACE(2, "DebuggerProxy::setBreakPoints\n"); // Hold the break mutex while we update the proxy's state. There's no need @@ -165,28 +163,8 @@ void DebuggerProxy::setBreakPoints(BreakPointInfoPtrVec &breakpoints) { WriteLock lock(m_breakMutex); m_breakpoints = breakpoints; m_hasBreakPoints = !m_breakpoints.empty(); - m_breaksEnterFunc.clear(); - m_breaksEnterClsMethod.clear(); - for (unsigned int i = 0; i < m_breakpoints.size(); i++) { - BreakPointInfoPtr bp = m_breakpoints[i]; - std::string funcFullName = bp->getFuncName(); - if (funcFullName.empty()) { - continue; - } - { - StringDataMap::accessor acc; - const StringData* sd = StringData::GetStaticString(funcFullName); - m_breaksEnterFunc.insert(acc, sd); - } - std::string clsName = bp->getClass(); - if (!clsName.empty()) { - StringDataMap::accessor acc; - const StringData* sd = StringData::GetStaticString(clsName); - m_breaksEnterClsMethod.insert(acc, sd); - } - } } - phpSetBreakPointsInAllFiles(this); // Apply breakpoints to the code. + phpSetBreakPoints(this); } void DebuggerProxy::getBreakPoints(BreakPointInfoPtrVec &breakpoints) { @@ -195,42 +173,6 @@ void DebuggerProxy::getBreakPoints(BreakPointInfoPtrVec &breakpoints) { breakpoints = m_breakpoints; } -bool DebuggerProxy::couldBreakEnterClsMethod(const StringData* className) { - TRACE(5, "DebuggerProxy::couldBreakEnterClsMethod\n"); - ReadLock lock(m_breakMutex); - StringDataMap::const_accessor acc; - return m_breaksEnterClsMethod.find(acc, className); -} - -bool DebuggerProxy::couldBreakEnterFunc(const StringData* funcFullName) { - TRACE(5, "DebuggerProxy::couldBreakEnterFunc\n"); - ReadLock lock(m_breakMutex); - StringDataMap::const_accessor acc; - return m_breaksEnterFunc.find(acc, funcFullName); -} - -void DebuggerProxy::getBreakClsMethods( - std::vector& classNames) { - TRACE(2, "DebuggerProxy::getBreakClsMethods\n"); - classNames.clear(); - WriteLock lock(m_breakMutex); // Write lock in case iteration causes a re-hash - for (StringDataMap::const_iterator iter = m_breaksEnterClsMethod.begin(); - iter != m_breaksEnterClsMethod.end(); ++iter) { - classNames.push_back(iter->first); - } -} - -void DebuggerProxy::getBreakFuncs( - std::vector& funcFullNames) { - TRACE(2, "DebuggerProxy::getBreakFuncs\n"); - funcFullNames.clear(); - WriteLock lock(m_breakMutex); // Write lock in case iteration causes a re-hash - for (StringDataMap::const_iterator iter = m_breaksEnterFunc.begin(); - iter != m_breaksEnterFunc.end(); ++iter) { - funcFullNames.push_back(iter->first); - } -} - // Proxy needs to be interrupted because it has something setup which may need // processing; breakpoints, flow control commands, a signal. bool DebuggerProxy::needInterrupt() { diff --git a/hphp/runtime/debugger/debugger_proxy.h b/hphp/runtime/debugger/debugger_proxy.h index b28c610ab..05532df77 100644 --- a/hphp/runtime/debugger/debugger_proxy.h +++ b/hphp/runtime/debugger/debugger_proxy.h @@ -83,10 +83,6 @@ public: void setBreakPoints(BreakPointInfoPtrVec &breakpoints); void getBreakPoints(BreakPointInfoPtrVec &breakpoints); - bool couldBreakEnterClsMethod(const StringData* className); - bool couldBreakEnterFunc(const StringData* funcFullName); - void getBreakClsMethods(std::vector& classNames); - void getBreakFuncs(std::vector& funcFullNames); BreakPointInfoPtr getBreakPointAtCmd(CmdInterrupt& cmd); bool needInterrupt(); @@ -134,11 +130,6 @@ private: DThreadInfoPtr m_newThread; std::map m_threads; - typedef tbb::concurrent_hash_map StringDataMap; - StringDataMap m_breaksEnterClsMethod; - StringDataMap m_breaksEnterFunc; - CmdFlowControlPtr m_flow; // c, s, n, o commands that can skip breakpoints Mutex m_signalMutex; // who can talk to client diff --git a/hphp/runtime/vm/debugger_hook.cpp b/hphp/runtime/vm/debugger_hook.cpp index 60e736818..1d15befec 100644 --- a/hphp/runtime/vm/debugger_hook.cpp +++ b/hphp/runtime/vm/debugger_hook.cpp @@ -139,30 +139,35 @@ static PCFilter *getBreakPointFilter() { return g_vmContext->m_breakPointFilter; } +// Looks up the offset range in the given unit, of the given breakpoint. +// If the offset cannot be found, the breakpoint is marked as invalid. +// Otherwise it is marked as valid and the offset is added to the +// breakpoint filter and the offset range is black listed for the JIT. +static void addBreakPointInUnit(Eval::BreakPointInfoPtr bp, Unit* unit) { + OffsetRangeVec offsets; + if (!unit->getOffsetRanges(bp->m_line1, offsets) || offsets.size() == 0) { + bp->m_bindState = Eval::BreakPointInfo::KnownToBeInvalid; + return; + } + bp->m_bindState = Eval::BreakPointInfo::KnownToBeValid; + TRACE(3, "Add to breakpoint filter for %s:%d, unit %p:\n", + unit->filepath()->data(), bp->m_line1, unit); + getBreakPointFilter()->addRanges(unit, offsets); + if (RuntimeOption::EvalJit) { + blacklistRangesInJit(unit, offsets); + } +} + static void addBreakPointsInFile(Eval::DebuggerProxy* proxy, Eval::PhpFile* efile) { Eval::BreakPointInfoPtrVec bps; proxy->getBreakPoints(bps); - for(unsigned int i = 0; i < bps.size(); i++) { + for (unsigned int i = 0; i < bps.size(); i++) { Eval::BreakPointInfoPtr bp = bps[i]; - if (bp->m_line1 == 0 || bp->m_file.empty()) { - // invalid breakpoint for file:line - continue; - } - if (!Eval::BreakPointInfo::MatchFile(bp->m_file, efile->getFileName(), + if (Eval::BreakPointInfo::MatchFile(bp->m_file, efile->getFileName(), efile->getRelPath())) { - continue; - } - Unit* unit = efile->unit(); - OffsetRangeVec offsets; - if (!unit->getOffsetRanges(bp->m_line1, offsets)) { - continue; - } - TRACE(3, "Add to breakpoint filter for %s:%d, unit %p:\n", - efile->getFileName().c_str(), bp->m_line1, unit); - getBreakPointFilter()->addRanges(unit, offsets); - if (RuntimeOption::EvalJit) { - blacklistRangesInJit(unit, offsets); + addBreakPointInUnit(bp, efile->unit()); + break; } } } @@ -182,13 +187,45 @@ static void addBreakPointFuncEntry(const Func* f) { } } -static void addBreakPointsClass(Eval::DebuggerProxy* proxy, - const Class* cls) { +// If the proxy has an enabled breakpoint that matches entry into the given +// function, arrange for the VM to stop execution and notify the debugger +// whenever execution enters the given function. +static void addBreakPointFuncEntry(Eval::DebuggerProxy* proxy, const Func* f) { + Eval::BreakPointInfoPtrVec bps; + proxy->getBreakPoints(bps); + for (unsigned int i = 0; i < bps.size(); i++) { + Eval::BreakPointInfoPtr bp = bps[i]; + if (bp->m_state == Eval::BreakPointInfo::Disabled) continue; + if (bp->getFuncName() != f->fullName()->data()) continue; + bp->m_bindState = Eval::BreakPointInfo::KnownToBeValid; + addBreakPointFuncEntry(f); + return; + } +} + +// If the proxy has enabled breakpoints that match entry into methods of +// the given class, arrange for the VM to stop execution and notify the debugger +// whenever execution enters one of these matched method. +// This function is called once, when a class is first loaded, so it is not +// performance critical. +static void addBreakPointsClass(Eval::DebuggerProxy* proxy, const Class* cls) { size_t numFuncs = cls->numMethods(); - Func* const* funcs = cls->methods(); - for (size_t i = 0; i < numFuncs; ++i) { - if (proxy->couldBreakEnterFunc(funcs[i]->fullName())) { - addBreakPointFuncEntry(funcs[i]); + if (numFuncs == 0) return; + auto clsName = cls->name(); + auto funcs = cls->methods(); + Eval::BreakPointInfoPtrVec bps; + proxy->getBreakPoints(bps); + for (unsigned int i = 0; i < bps.size(); i++) { + Eval::BreakPointInfoPtr bp = bps[i]; + if (bp->m_state == Eval::BreakPointInfo::Disabled) continue; + // TODO: check name space separately + if (bp->getClass() != clsName->data()) continue; + bp->m_bindState = Eval::BreakPointInfo::KnownToBeInvalid; + for (size_t i = 0; i < numFuncs; ++i) { + auto f = funcs[i]; + if (bp->getFunction() != f->name()->data()) continue; + bp->m_bindState = Eval::BreakPointInfo::KnownToBeValid; + addBreakPointFuncEntry(f); } } } @@ -223,57 +260,106 @@ void phpDebuggerEvalHook(const Func* f) { } } -// Hook called by the VM when a file is loaded. Gives the debugger a chance -// to apply any pending breakpoints that might be in the file. +// Called by the VM when a file is loaded. void phpDebuggerFileLoadHook(Eval::PhpFile* efile) { Eval::DebuggerProxyPtr proxy = Eval::Debugger::GetProxy(); - if (!proxy) { - return; - } + if (proxy == nullptr) return; addBreakPointsInFile(proxy.get(), efile); } +// Called by the VM when a class definition is loaded. void phpDebuggerDefClassHook(const Class* cls) { Eval::DebuggerProxyPtr proxy = Eval::Debugger::GetProxy(); - if (!proxy) { - return; - } + if (proxy == nullptr) return; addBreakPointsClass(proxy.get(), cls); } +// Called by the VM when a function definition is loaded. void phpDebuggerDefFuncHook(const Func* func) { Eval::DebuggerProxyPtr proxy = Eval::Debugger::GetProxy(); - if (proxy && proxy->couldBreakEnterFunc(func->fullName())) { - addBreakPointFuncEntry(func); - } + if (proxy == nullptr) return; + addBreakPointFuncEntry(proxy.get(), func); } -// Helper which will look at every loaded file and attempt to see if any -// existing file:line breakpoints should be set. -void phpSetBreakPointsInAllFiles(Eval::DebuggerProxy* proxy) { - for (EvaledFilesMap::const_iterator it = - g_vmContext->m_evaledFiles.begin(); - it != g_vmContext->m_evaledFiles.end(); ++it) { - addBreakPointsInFile(proxy, it->second); - } - - std::vector clsNames; - proxy->getBreakClsMethods(clsNames); - for (unsigned int i = 0; i < clsNames.size(); i++) { - Class* cls = Unit::lookupClass(clsNames[i]); - if (cls) { - addBreakPointsClass(proxy, cls); +// Called by the proxy whenever its breakpoint list is updated. +// Since this intended to be called when user input is received, it is not +// performance critical. Also, in typical scenarios, the list is short. +void phpSetBreakPoints(Eval::DebuggerProxy* proxy) { + Eval::BreakPointInfoPtrVec bps; + proxy->getBreakPoints(bps); + for (unsigned int i = 0; i < bps.size(); i++) { + Eval::BreakPointInfoPtr bp = bps[i]; + bp->m_bindState = Eval::BreakPointInfo::Unknown; + auto className = bp->getClass(); + if (!className.empty()) { + auto clsName = StringData::GetStaticString(className); + auto cls = Unit::lookupClass(clsName); + if (cls == nullptr) continue; + bp->m_bindState = Eval::BreakPointInfo::KnownToBeInvalid; + size_t numFuncs = cls->numMethods(); + if (numFuncs == 0) continue; + auto methodName = bp->getFunction(); + Func* const* funcs = cls->methods(); + for (size_t i = 0; i < numFuncs; ++i) { + auto f = funcs[i]; + if (methodName != f->name()->data()) continue; + bp->m_bindState = Eval::BreakPointInfo::KnownToBeValid; + addBreakPointFuncEntry(f); + break; + } + //TODO: what about superclass methods accessed via the derived class? + //Task 2527229. + continue; } - } - - std::vector funcFullNames; - proxy->getBreakFuncs(funcFullNames); - for (unsigned int i = 0; i < funcFullNames.size(); i++) { - // This list contains class method as well but they shouldn't hit anything - Func* f = Unit::lookupFunc(funcFullNames[i]); - if (f) { + auto funcName = bp->getFuncName(); + if (!funcName.empty()) { + auto fName = StringData::GetStaticString(funcName); + Func* f = Unit::lookupFunc(fName); + if (f == nullptr) continue; + bp->m_bindState = Eval::BreakPointInfo::KnownToBeValid; addBreakPointFuncEntry(f); + continue; } + auto fileName = bp->m_file; + if (!fileName.empty()) { + for (EvaledFilesMap::const_iterator it = + g_vmContext->m_evaledFiles.begin(); + it != g_vmContext->m_evaledFiles.end(); ++it) { + auto efile = it->second; + if (!Eval::BreakPointInfo::MatchFile(fileName, efile->getFileName(), + efile->getRelPath())) continue; + addBreakPointInUnit(bp, efile->unit()); + break; + } + continue; + } + auto exceptionClassName = bp->getExceptionClass(); + if (exceptionClassName == "@") { + bp->m_bindState = Eval::BreakPointInfo::KnownToBeValid; + continue; + } else if (!exceptionClassName.empty()) { + auto expClsName = StringData::GetStaticString(exceptionClassName); + auto cls = Unit::lookupClass(expClsName); + if (cls != nullptr) { + auto baseClsName = StringData::GetStaticString("Exception"); + auto baseCls = Unit::lookupClass(baseClsName); + if (baseCls != nullptr) { + if (cls->classof(baseCls)) { + bp->m_bindState = Eval::BreakPointInfo::KnownToBeValid; + } else { + bp->m_bindState = Eval::BreakPointInfo::KnownToBeInvalid; + } + } + } + continue; + } else { + continue; + } + // If we get here, the break point is of a type that does + // not need to be explicitly enabled in the VM. For example + // a break point that get's triggered when the server starts + // to process a page request. + bp->m_bindState = Eval::BreakPointInfo::KnownToBeValid; } } diff --git a/hphp/runtime/vm/debugger_hook.h b/hphp/runtime/vm/debugger_hook.h index d94cc4c57..28ac00da1 100644 --- a/hphp/runtime/vm/debugger_hook.h +++ b/hphp/runtime/vm/debugger_hook.h @@ -49,8 +49,7 @@ class Func; void phpDebuggerDefClassHook(const Class* cls); void phpDebuggerDefFuncHook(const Func* func); -// Helper to apply pending breakpoints to all files. -void phpSetBreakPointsInAllFiles(Eval::DebuggerProxy* proxy); +void phpSetBreakPoints(Eval::DebuggerProxy* proxy); // Add/remove breakpoints at a specific offset. void phpAddBreakPoint(const Unit* unit, Offset offset); diff --git a/hphp/test/quick/debugger/break1.php.expectf b/hphp/test/quick/debugger/break1.php.expectf index bcbe98f7d..7773cf40f 100644 --- a/hphp/test/quick/debugger/break1.php.expectf +++ b/hphp/test/quick/debugger/break1.php.expectf @@ -4,6 +4,11 @@ break1.php loaded Program %s/break1.php exited normally. break break1.php:7 Breakpoint 1 set on line 7 of break1.php +break break1.php:77 +Breakpoint 2 not set on line 77 of break1.php +Because the line does not exist or is not executable code. +break list + 1 ALWAYS on line 7 of break1.php @ foo('test_break1') Breakpoint 1 reached at foo() on line 7 of %s/break1.php 6 $y = $x.'_suffix'; @@ -13,6 +18,8 @@ Breakpoint 1 reached at foo() on line 7 of %s/break1.php variable $x = "test_break1" $y = "test_break1_suffix" +break list + 1 ALWAYS on line 7 of break1.php break clear all All breakpoints are cleared. continue @@ -77,4 +84,15 @@ Break at cls::pubHardBreak() on line 19 of %s/break1.php continue pubHardBreak:done +break clear all +There is no breakpoint to clear or toggle. +break cls::nosuchMethod() +Breakpoint 1 not set upon entering cls::nosuchMethod() +Because method cls::nosuchMethod does not exist. +break list +break noSuchFunction() +Breakpoint 1 set upon entering noSuchFunction() +But wont break until function noSuchFunction has been loaded. +break list + 1 ALWAYS upon entering noSuchFunction() (unbound) quit diff --git a/hphp/test/quick/debugger/break1.php.in b/hphp/test/quick/debugger/break1.php.in index 6e7cfc031..357f30473 100644 --- a/hphp/test/quick/debugger/break1.php.in +++ b/hphp/test/quick/debugger/break1.php.in @@ -1,7 +1,10 @@ run break break1.php:7 +break break1.php:77 +break list @ foo('test_break1') variable +break list break clear all continue break foo() @@ -23,4 +26,9 @@ continue @ $break5=new cls() @ $break5->pubHardBreak('test_break5') continue +break clear all +break cls::nosuchMethod() +break list +break noSuchFunction() +break list quit diff --git a/hphp/test/quick/debugger/break2.php.expectf b/hphp/test/quick/debugger/break2.php.expectf index d36ccf528..92157e8bb 100644 --- a/hphp/test/quick/debugger/break2.php.expectf +++ b/hphp/test/quick/debugger/break2.php.expectf @@ -1,12 +1,21 @@ Program %s/break2.php loaded. Type '[r]un' or '[c]ontinue' to go. break break2.php:7 Breakpoint 1 set on line 7 of break2.php +But wont break until file break2.php has been loaded. break foo2() Breakpoint 2 set upon entering foo2() +But wont break until function foo2 has been loaded. break cls2::pubObj() Breakpoint 3 set upon entering cls2::pubObj() +But wont break until class cls2 has been loaded. break cls2::pubCls() Breakpoint 4 set upon entering cls2::pubCls() +But wont break until class cls2 has been loaded. +break list + 1 ALWAYS on line 7 of break2.php (unbound) + 2 ALWAYS upon entering foo2() (unbound) + 3 ALWAYS upon entering cls2::pubObj() (unbound) + 4 ALWAYS upon entering cls2::pubCls() (unbound) run break2.php loaded Program %s/break2.php exited normally. @@ -16,6 +25,11 @@ Breakpoint 2 reached at foo2() on line 6 of %s/break2.php 6 $y = $x.'_suffix'; 7 error_log($y); +break list + 1 ALWAYS on line 7 of break2.php + 2 ALWAYS upon entering foo2() + 3 ALWAYS upon entering cls2::pubObj() + 4 ALWAYS upon entering cls2::pubCls() continue Breakpoint 1 reached at foo2() on line 7 of %s/break2.php 6 $y = $x.'_suffix'; diff --git a/hphp/test/quick/debugger/break2.php.in b/hphp/test/quick/debugger/break2.php.in index c8985638a..eb3cb8e23 100644 --- a/hphp/test/quick/debugger/break2.php.in +++ b/hphp/test/quick/debugger/break2.php.in @@ -2,8 +2,10 @@ break break2.php:7 break foo2() break cls2::pubObj() break cls2::pubCls() +break list run @ foo2('test_break6') +break list continue @ $break6=new cls2() @ $break6->pubObj('test_break6') diff --git a/hphp/test/quick/debugger/break4.php.expectf b/hphp/test/quick/debugger/break4.php.expectf index 95a0cb0e7..87e91675f 100644 --- a/hphp/test/quick/debugger/break4.php.expectf +++ b/hphp/test/quick/debugger/break4.php.expectf @@ -1,6 +1,7 @@ Program %s/break4.php loaded. Type '[r]un' or '[c]ontinue' to go. break :fb:my:thing::doIt() Breakpoint 1 set upon entering xhp_fb__my__thing::doIt() +But wont break until class xhp_fb__my__thing has been loaded. continue break4.php loaded Program %s/break4.php exited normally. diff --git a/hphp/test/quick/debugger/error_bad_cmd_type_receive.php.expectf b/hphp/test/quick/debugger/error_bad_cmd_type_receive.php.expectf index ee07c3b2d..07ccb2172 100644 --- a/hphp/test/quick/debugger/error_bad_cmd_type_receive.php.expectf +++ b/hphp/test/quick/debugger/error_bad_cmd_type_receive.php.expectf @@ -1,6 +1,7 @@ Program %s/error_bad_cmd_type_receive.php loaded. Type '[r]un' or '[c]ontinue' to go. break error_bad_cmd_type_receive.php:3 Breakpoint 1 set on line 3 of error_bad_cmd_type_receive.php +But wont break until file error_bad_cmd_type_receive.php has been loaded. run Breakpoint 1 reached on line 3 of %s/error_bad_cmd_type_receive.php 2 diff --git a/hphp/test/quick/debugger/error_bad_cmd_type_send.php.expectf b/hphp/test/quick/debugger/error_bad_cmd_type_send.php.expectf index 15b106ea7..e3cedf286 100644 --- a/hphp/test/quick/debugger/error_bad_cmd_type_send.php.expectf +++ b/hphp/test/quick/debugger/error_bad_cmd_type_send.php.expectf @@ -1,6 +1,7 @@ Program %s/error_bad_cmd_type_send.php loaded. Type '[r]un' or '[c]ontinue' to go. break error_bad_cmd_type_send.php:3 Breakpoint 1 set on line 3 of error_bad_cmd_type_send.php +But wont break until file error_bad_cmd_type_send.php has been loaded. run Breakpoint 1 reached on line 3 of %s/error_bad_cmd_type_send.php 2 diff --git a/hphp/test/quick/debugger/error_short_cmd_receive.php.expectf b/hphp/test/quick/debugger/error_short_cmd_receive.php.expectf index 333776227..d93db501a 100644 --- a/hphp/test/quick/debugger/error_short_cmd_receive.php.expectf +++ b/hphp/test/quick/debugger/error_short_cmd_receive.php.expectf @@ -1,6 +1,7 @@ Program %s/error_short_cmd_receive.php loaded. Type '[r]un' or '[c]ontinue' to go. break error_short_cmd_receive.php:3 Breakpoint 1 set on line 3 of error_short_cmd_receive.php +But wont break until file error_short_cmd_receive.php has been loaded. run Breakpoint 1 reached on line 3 of %s/error_short_cmd_receive.php 2 diff --git a/hphp/test/quick/debugger/error_short_cmd_send.php.expectf b/hphp/test/quick/debugger/error_short_cmd_send.php.expectf index ab6cc2b64..70f567bbf 100644 --- a/hphp/test/quick/debugger/error_short_cmd_send.php.expectf +++ b/hphp/test/quick/debugger/error_short_cmd_send.php.expectf @@ -1,6 +1,7 @@ Program %s/error_short_cmd_send.php loaded. Type '[r]un' or '[c]ontinue' to go. break error_short_cmd_send.php:3 Breakpoint 1 set on line 3 of error_short_cmd_send.php +But wont break until file error_short_cmd_send.php has been loaded. run Breakpoint 1 reached on line 3 of %s/error_short_cmd_send.php 2 diff --git a/hphp/test/quick/debugger/exception1.php b/hphp/test/quick/debugger/exception1.php index cb56c40fa..5abcbb3e5 100644 --- a/hphp/test/quick/debugger/exception1.php +++ b/hphp/test/quick/debugger/exception1.php @@ -5,6 +5,7 @@ namespace { class MyException extends Exception { } + class NotAnException { } function throw_exception() { throw new Exception(); diff --git a/hphp/test/quick/debugger/exception1.php.expectf b/hphp/test/quick/debugger/exception1.php.expectf index aa880c190..5f6504119 100644 --- a/hphp/test/quick/debugger/exception1.php.expectf +++ b/hphp/test/quick/debugger/exception1.php.expectf @@ -1,18 +1,32 @@ Program %s/exception1.php loaded. Type '[r]un' or '[c]ontinue' to go. +exception MyException +Breakpoint 1 set right before throwing MyException +But note that class MyException has yet been loaded. +break list + 1 ALWAYS right before throwing MyException (unbound) run Program %s/exception1.php exited normally. +exception NotAnException +Breakpoint 2 not set right before throwing NotAnException +Because class NotAnException is not an exception. +break list + 1 ALWAYS right before throwing MyException +break clear all +All breakpoints are cleared. exception Exception Breakpoint 1 set right before throwing Exception +break list + 1 ALWAYS right before throwing Exception @ throw_exception() -Breakpoint 1 reached: Throwing Exception at throw_exception() on line 10 of %s/exception1.php - 9 function throw_exception() { - 10 throw new Exception(); - 11 } +Breakpoint 1 reached: Throwing Exception at throw_exception() on line 11 of %s/exception1.php + 10 function throw_exception() { + 11 throw new Exception(); + 12 } break clear all All breakpoints are cleared. continue -Hit a php exception : exception 'Exception' with message '' in %s/exception1.php:10 +Hit a php exception : exception 'Exception' with message '' in %s/exception1.php:11 Stack trace: #0 (1): throw_exception() #1 (1): include() @@ -20,15 +34,15 @@ Stack trace: exception MyException Breakpoint 1 set right before throwing MyException @ throw_myexception() -Breakpoint 1 reached: Throwing MyException at throw_myexception() on line 14 of %s/exception1.php - 13 function throw_myexception() { - 14 throw new MyException(); - 15 } +Breakpoint 1 reached: Throwing MyException at throw_myexception() on line 15 of %s/exception1.php + 14 function throw_myexception() { + 15 throw new MyException(); + 16 } break clear all All breakpoints are cleared. continue -Hit a php exception : exception 'MyException' with message '' in %s/exception1.php:14 +Hit a php exception : exception 'MyException' with message '' in %s/exception1.php:15 Stack trace: #0 (1): throw_myexception() #1 (1): include() @@ -36,32 +50,32 @@ Stack trace: exception error Breakpoint 1 set right after an error @ error_undefined_class() -Breakpoint 1 reached: An error occurred at error_undefined_class() on line 18 of %s/exception1.php - 17 function error_undefined_class() { - 18 $x = new NoSuchClass(); - 19 } +Breakpoint 1 reached: An error occurred at error_undefined_class() on line 19 of %s/exception1.php + 18 function error_undefined_class() { + 19 $x = new NoSuchClass(); + 20 } Error Message: Class undefined: NoSuchClass break clear all All breakpoints are cleared. continue Hit fatal : Class undefined: NoSuchClass - #0 at %s/exception1.php:18] + #0 at %s/exception1.php:19] #1 error_undefined_class(), called at [:1] #2 include(), called at [:1] exception Exception Breakpoint 1 set right before throwing Exception @ \Outer\throw_exception() -Breakpoint 1 reached: Throwing Exception at Outer\throw_exception() on line 28 of %s/exception1.php - 27 function throw_exception() { - 28 throw new \Exception(); - 29 } +Breakpoint 1 reached: Throwing Exception at Outer\throw_exception() on line 29 of %s/exception1.php + 28 function throw_exception() { + 29 throw new \Exception(); + 30 } break clear all All breakpoints are cleared. continue -Hit a php exception : exception 'Exception' with message '' in %s/exception1.php:28 +Hit a php exception : exception 'Exception' with message '' in %s/exception1.php:29 Stack trace: #0 (1): Outer\throw_exception() #1 (1): include() @@ -69,15 +83,15 @@ Stack trace: exception \Outer\MyException Breakpoint 1 set right before throwing Outer\MyException @ Outer\throw_myexception() -Breakpoint 1 reached: Throwing Outer\MyException at Outer\throw_myexception() on line 32 of %s/exception1.php - 31 function throw_myexception() { - 32 throw new MyException(); - 33 } +Breakpoint 1 reached: Throwing Outer\MyException at Outer\throw_myexception() on line 33 of %s/exception1.php + 32 function throw_myexception() { + 33 throw new MyException(); + 34 } break clear all All breakpoints are cleared. continue -Hit a php exception : exception 'Outer\MyException' with message '' in %s/exception1.php:32 +Hit a php exception : exception 'Outer\MyException' with message '' in %s/exception1.php:33 Stack trace: #0 (1): Outer\throw_myexception() #1 (1): include() @@ -85,32 +99,32 @@ Stack trace: exception error Breakpoint 1 set right after an error @ \Outer\error_undefined_class() -Breakpoint 1 reached: An error occurred at Outer\error_undefined_class() on line 36 of %s/exception1.php - 35 function error_undefined_class() { - 36 $x = new NoSuchClass(); - 37 } +Breakpoint 1 reached: An error occurred at Outer\error_undefined_class() on line 37 of %s/exception1.php + 36 function error_undefined_class() { + 37 $x = new NoSuchClass(); + 38 } Error Message: Class undefined: Outer\NoSuchClass break clear all All breakpoints are cleared. continue Hit fatal : Class undefined: Outer\NoSuchClass - #0 at %s/exception1.php:36] + #0 at %s/exception1.php:37] #1 Outer\error_undefined_class(), called at [:1] #2 include(), called at [:1] exception Exception Breakpoint 1 set right before throwing Exception @ \Outer\Inner\throw_exception() -Breakpoint 1 reached: Throwing Exception at Outer\Inner\throw_exception() on line 46 of %s/exception1.php - 45 function throw_exception() { - 46 throw new \Exception(); - 47 } +Breakpoint 1 reached: Throwing Exception at Outer\Inner\throw_exception() on line 47 of %s/exception1.php + 46 function throw_exception() { + 47 throw new \Exception(); + 48 } break clear all All breakpoints are cleared. continue -Hit a php exception : exception 'Exception' with message '' in %s/exception1.php:46 +Hit a php exception : exception 'Exception' with message '' in %s/exception1.php:47 Stack trace: #0 (1): Outer\Inner\throw_exception() #1 (1): include() @@ -118,15 +132,15 @@ Stack trace: exception Outer\Inner\MyException Breakpoint 1 set right before throwing Outer\Inner\MyException @ Outer\Inner\throw_myexception() -Breakpoint 1 reached: Throwing Outer\Inner\MyException at Outer\Inner\throw_myexception() on line 50 of %s/exception1.php - 49 function throw_myexception() { - 50 throw new MyException(); - 51 } +Breakpoint 1 reached: Throwing Outer\Inner\MyException at Outer\Inner\throw_myexception() on line 51 of %s/exception1.php + 50 function throw_myexception() { + 51 throw new MyException(); + 52 } break clear all All breakpoints are cleared. continue -Hit a php exception : exception 'Outer\Inner\MyException' with message '' in %s/exception1.php:50 +Hit a php exception : exception 'Outer\Inner\MyException' with message '' in %s/exception1.php:51 Stack trace: #0 (1): Outer\Inner\throw_myexception() #1 (1): include() @@ -134,17 +148,17 @@ Stack trace: exception error Breakpoint 1 set right after an error @ \Outer\Inner\error_undefined_class() -Breakpoint 1 reached: An error occurred at Outer\Inner\error_undefined_class() on line 54 of %s/exception1.php - 53 function error_undefined_class() { - 54 $x = new NoSuchClass(); - 55 } +Breakpoint 1 reached: An error occurred at Outer\Inner\error_undefined_class() on line 55 of %s/exception1.php + 54 function error_undefined_class() { + 55 $x = new NoSuchClass(); + 56 } Error Message: Class undefined: Outer\Inner\NoSuchClass break clear all All breakpoints are cleared. continue Hit fatal : Class undefined: Outer\Inner\NoSuchClass - #0 at %s/exception1.php:54] + #0 at %s/exception1.php:55] #1 Outer\Inner\error_undefined_class(), called at [:1] #2 include(), called at [:1] diff --git a/hphp/test/quick/debugger/exception1.php.in b/hphp/test/quick/debugger/exception1.php.in index dab4eca8c..97e2c3826 100644 --- a/hphp/test/quick/debugger/exception1.php.in +++ b/hphp/test/quick/debugger/exception1.php.in @@ -1,5 +1,11 @@ +exception MyException +break list run +exception NotAnException +break list +break clear all exception Exception +break list @ throw_exception() break clear all continue diff --git a/hphp/test/quick/debugger/flow.php.expectf b/hphp/test/quick/debugger/flow.php.expectf index 8bd672da9..c38bfcefe 100644 --- a/hphp/test/quick/debugger/flow.php.expectf +++ b/hphp/test/quick/debugger/flow.php.expectf @@ -1,6 +1,7 @@ Program %s/flow.php loaded. Type '[r]un' or '[c]ontinue' to go. %Sbreak cls::pub() Breakpoint 1 set upon entering cls::pub() +But wont break until class cls has been loaded. run flow_t.php loaded Program %s/flow.php exited normally. diff --git a/hphp/test/quick/debugger/flow_excep.php.expectf b/hphp/test/quick/debugger/flow_excep.php.expectf index 09a5ad03a..c8ea9f05f 100644 --- a/hphp/test/quick/debugger/flow_excep.php.expectf +++ b/hphp/test/quick/debugger/flow_excep.php.expectf @@ -1,6 +1,7 @@ Program %s/flow_excep.php loaded. Type '[r]un' or '[c]ontinue' to go. break main() Breakpoint 1 set upon entering main() +But wont break until function main has been loaded. run Breakpoint 1 reached at main() on line 55 of %s/flow_excep.php 54 function main() { diff --git a/hphp/test/quick/debugger/flow_multistep.php.expectf b/hphp/test/quick/debugger/flow_multistep.php.expectf index 9856df2c5..41853d8b6 100644 --- a/hphp/test/quick/debugger/flow_multistep.php.expectf +++ b/hphp/test/quick/debugger/flow_multistep.php.expectf @@ -1,6 +1,7 @@ Program %s/flow_multistep.php loaded. Type '[r]un' or '[c]ontinue' to go. %Sbreak flow_multistep.php:58 Breakpoint 1 set on line 58 of flow_multistep.php +But wont break until file flow_multistep.php has been loaded. run C1 oh hai C2 oh hai diff --git a/hphp/test/quick/debugger/flow_small.php.expectf b/hphp/test/quick/debugger/flow_small.php.expectf index 64006b210..4254528aa 100644 --- a/hphp/test/quick/debugger/flow_small.php.expectf +++ b/hphp/test/quick/debugger/flow_small.php.expectf @@ -4,6 +4,7 @@ SmallStep(ss) set to on. break cls::pub() Breakpoint 1 set upon entering cls::pub() +But wont break until class cls has been loaded. run flow_small.php loaded Program %s/flow_small.php exited normally. diff --git a/hphp/test/quick/debugger/hphpd.hdf b/hphp/test/quick/debugger/hphpd.hdf index e32112326..f24dc5fa5 100644 --- a/hphp/test/quick/debugger/hphpd.hdf +++ b/hphp/test/quick/debugger/hphpd.hdf @@ -64,5 +64,9 @@ Color { } Tutorial = 0 Tutorial { + Visited { + 0 = 376d06037a5af010a610dd885c6817c1 + 1 = d497d02bce18120e9ae2b91992a9ce60 + } } MaxCodeLines = -1 diff --git a/hphp/test/quick/debugger/printThis.php.expectf b/hphp/test/quick/debugger/printThis.php.expectf index 56f88651e..8de854db7 100644 --- a/hphp/test/quick/debugger/printThis.php.expectf +++ b/hphp/test/quick/debugger/printThis.php.expectf @@ -1,6 +1,7 @@ Program %s/printThis.php loaded. Type '[r]un' or '[c]ontinue' to go. %Sbreak printThis.php:5 Breakpoint 1 set on line 5 of printThis.php +But wont break until file printThis.php has been loaded. run Breakpoint 1 reached at Foo::method() on line 5 of %s/printThis.php 4 function method() {