void c_Continuation::copyContinuationVars(ActRec* fp) { // For functions that contain only named locals, we can copy TVs // right to the local space. static const StringData* thisStr = s_this.get(); bool skipThis; if (fp->hasVarEnv()) { Stats::inc(Stats::Cont_CreateVerySlow); Array definedVariables = fp->getVarEnv()->getDefinedVariables(); skipThis = definedVariables.exists(s_this, true); for (ArrayIter iter(definedVariables); !iter.end(); iter.next()) { dupContVar(iter.first().getStringData(), const_cast<TypedValue *>(iter.secondRef().asTypedValue())); } } else { const Func *genFunc = actRec()->m_func; skipThis = genFunc->lookupVarId(thisStr) != kInvalidId; for (Id i = 0; i < genFunc->numNamedLocals(); ++i) { dupContVar(genFunc->localVarName(i), frame_local(fp, i)); } } // If $this is used as a local inside the body and is not provided // by our containing environment, just prefill it here instead of // using InitThisLoc inside the body if (!skipThis && fp->hasThis()) { Id id = actRec()->m_func->lookupVarId(thisStr); if (id != kInvalidId) { tvAsVariant(frame_local(actRec(), id)) = fp->getThis(); } } }
void checkFrame(ActRec* fp, Cell* sp, bool checkLocals) { const Func* func = fp->m_func; func->validate(); if (func->cls()) { assert(!func->cls()->isZombie()); } if (fp->hasVarEnv()) { assert(fp->getVarEnv()->getCfp() == fp); } // TODO: validate this pointer from actrec int numLocals = func->numLocals(); assert(sp <= (Cell*)fp - func->numSlotsInFrame() || func->isGenerator()); if (checkLocals) { int numParams = func->numParams(); for (int i=0; i < numLocals; i++) { if (i >= numParams && func->isGenerator() && i < func->numNamedLocals()) { continue; } assert(tvIsPlausible(*frame_local(fp, i))); } } // We unfortunately can't do the same kind of check for the stack // without knowing about FPI regions, because it may contain // ActRecs. }
bool SetVariableCommand::setLocalVariable( DebuggerSession* session, const std::string& name, const std::string& value, ScopeObject* scope, folly::dynamic* result ) { VMRegAnchor regAnchor; const auto fp = g_context->getFrameAtDepth(scope->m_frameDepth); const auto func = fp->m_func; const auto localCount = func->numNamedLocals(); for (Id id = 0; id < localCount; id++) { TypedValue* frameValue = frame_local(fp, id); const std::string localName = func->localVarName(id)->toCppString(); if (localName == name) { setVariableValue( session, name, value, frameValue, scope->m_requestId, result ); return true; } } return false; }
RefData* closureStaticLocInit(StringData* name, ActRec* fp, TypedValue val) { auto const func = fp->m_func; assert(func->isClosureBody() || func->isGeneratorFromClosure()); auto const closureLoc = LIKELY(func->isClosureBody()) ? frame_local(fp, func->numParams()) : frame_local(fp, frame_continuation(fp)->m_origFunc->numParams()); bool inited; auto const refData = lookupStaticFromClosure( closureLoc->m_data.pobj, name, inited); if (!inited) { cellCopy(val, *refData->tv()); } refData->incRefCount(); return refData; }
c_Continuation::c_Continuation(const ObjectStaticCallbacks *cb) : ExtObjectData(cb), #ifndef HHVM LABEL_INIT, #endif m_index(-1LL), m_value(Variant::nullInit), m_received(Variant::nullInit), m_done(false), m_running(false), m_should_throw(false), m_isMethod(false), m_callInfo(NULL) #ifdef HHVM , LABEL_INIT #endif { } #undef LABEL_INIT c_Continuation::~c_Continuation() { if (hhvm) { VM::ActRec* ar = actRec(); // The first local is the object itself, and it wasn't increffed at creation // time (see createContinuation()). Overwrite its type to exempt it from // refcounting here. TypedValue* contLocal = frame_local(ar, 0); ASSERT(contLocal->m_data.pobj == this); contLocal->m_type = KindOfNull; if (ar->hasVarEnv()) { VM::VarEnv::destroy(ar->getVarEnv()); } else { frame_free_locals_inl(ar, m_vmFunc->numLocals()); } } } void c_Continuation::t___construct( int64 func, int64 extra, bool isMethod, CStrRef origFuncName, CVarRef obj, CArrRef args) { INSTANCE_METHOD_INJECTION_BUILTIN(Continuation, Continuation::__construct); if (hhvm) { m_vmFunc = (VM::Func*) extra; ASSERT(m_vmFunc); } else { m_callInfo = (const CallInfo*) func; ASSERT(m_callInfo); } m_isMethod = isMethod; m_origFuncName = origFuncName; if (!obj.isNull()) { m_obj = obj.toObject(); ASSERT(!m_obj.isNull()); } else { ASSERT(m_obj.isNull()); } m_args = args; }
// Use the address of the c_Continuation object as a tag for this stepping // operation, to ensure we only stop once we're back to the same continuation. // Since we'll either stop when we get out of whatever is driving this // continuation, or we'll stop when we get back into it, we know the object // will remain alive. void* CmdNext::getContinuationTag(ActRec* fp) { TypedValue* tv = frame_local(fp, 0); assert(tv->m_type == HPHP::KindOfObject); assert(dynamic_cast<c_Continuation*>(tv->m_data.pobj)); c_Continuation* cont = static_cast<c_Continuation*>(tv->m_data.pobj); TRACE(2, "CmdNext: continuation tag %p for %s\n", cont, cont->t_getorigfuncname()->data()); return cont; }
void VerifyParamTypeFail(int paramNum) { VMRegAnchor _; const ActRec* ar = curFrame(); const Func* func = ar->m_func; const TypeConstraint& tc = func->params()[paramNum].typeConstraint(); TypedValue* tv = frame_local(ar, paramNum); assert(!tc.check(tv, func)); tc.verifyFail(func, paramNum, tv); }
void c_Continuation::dupContVar(const StringData* name, TypedValue* src) { ActRec *fp = actRec(); Id destId = fp->m_func->lookupVarId(name); if (destId != kInvalidId) { // Copy the value of the local to the cont object. tvDupFlattenVars(src, frame_local(fp, destId)); } else { if (!fp->hasVarEnv()) { fp->setVarEnv(VarEnv::createLocal(fp)); } fp->getVarEnv()->setWithRef(name, src); } }
void Generator::copyVars(const ActRec* srcFp) { const auto dstFp = actRec(); const auto func = dstFp->func(); assert(srcFp->func() == dstFp->func()); for (Id i = 0; i < func->numLocals(); ++i) { tvDupFlattenVars(frame_local(srcFp, i), frame_local(dstFp, i)); } if (dstFp->hasThis()) { dstFp->getThis()->incRefCount(); } if (LIKELY(!(srcFp->func()->attrs() & AttrMayUseVV))) return; if (LIKELY(srcFp->m_varEnv == nullptr)) return; if (srcFp->hasExtraArgs()) { dstFp->setExtraArgs(srcFp->getExtraArgs()->clone(dstFp)); } else { assert(srcFp->hasVarEnv()); dstFp->setVarEnv(srcFp->getVarEnv()->clone(dstFp)); } }
void c_Continuation::copyContinuationVars(ActRec* srcFp) { const auto dstFp = actRec(); const auto func = dstFp->func(); assert(srcFp->func() == dstFp->func()); for (Id i = 0; i < func->numLocals(); ++i) { tvDupFlattenVars(frame_local(srcFp, i), frame_local(dstFp, i)); } if (dstFp->hasThis()) { dstFp->getThis()->incRefCount(); } if (LIKELY(srcFp->m_varEnv == nullptr)) { return; } if (srcFp->hasExtraArgs()) { dstFp->setExtraArgs(srcFp->getExtraArgs()->clone(dstFp)); } else { assert(srcFp->hasVarEnv()); dstFp->setVarEnv(srcFp->getVarEnv()->clone(dstFp)); } }
void c_Continuation::dupContVar(const StringData* name, TypedValue* src) { ActRec *fp = actRec(); Id destId = fp->m_func->lookupVarId(name); if (destId != kInvalidId) { // Copy the value of the local to the cont object. tvDupFlattenVars(src, frame_local(fp, destId)); } else { if (!fp->hasVarEnv()) { // This VarEnv may potentially outlive the most recently stack-allocated // VarEnv, so we need to heap allocate it. fp->setVarEnv(VarEnv::createLocalOnHeap(fp)); } fp->getVarEnv()->setWithRef(name, src); } }
c_Continuation::~c_Continuation() { VM::ActRec* ar = actRec(); // The first local is the object itself, and it wasn't increffed at creation // time (see createContinuation()). Overwrite its type to exempt it from // refcounting here. TypedValue* contLocal = frame_local(ar, 0); assert(contLocal->m_data.pobj == this); contLocal->m_type = KindOfNull; if (ar->hasVarEnv()) { VM::VarEnv::destroy(ar->getVarEnv()); } else { frame_free_locals_inl(ar, m_vmFunc->numLocals()); } }
void NameValueTable::detach(ActRec* fp) { assert(m_fp == fp); m_fp = nullptr; const auto func = fp->m_func; const Id numNames = func->numNamedLocals(); TypedValue* loc = frame_local(fp, 0); for (Id i = 0; i < numNames; ++i, --loc) { assert(func->lookupVarId(func->localVarName(i)) == i); auto elm = findElm(func->localVarName(i)); assert(elm && elm->m_tv.m_type == KindOfNamedLocal); tvCopy(*loc, elm->m_tv); tvDebugTrash(loc); } }
static Array getVariables(const ActRec *fp) { if (fp->hasVarEnv()) { return fp->m_varEnv->getDefinedVariables(); } else { const Func *func = fp->m_func; auto numLocals = func->numNamedLocals(); ArrayInit ret(numLocals, ArrayInit::Map{}); for (Id id = 0; id < numLocals; ++id) { TypedValue* ptv = frame_local(fp, id); if (ptv->m_type == KindOfUninit) { continue; } Variant name(func->localVarName(id), Variant::StaticStrInit{}); ret.add(name, tvAsVariant(ptv)); } return ret.toArray(); } }
void HHVM_FUNCTION(set_frame_metadata, const Variant& metadata) { VMRegAnchor _; auto fp = vmfp(); if (fp && fp->skipFrame()) fp = g_context->getPrevVMState(fp); if (UNLIKELY(!fp)) return; if (LIKELY(!(fp->func()->attrs() & AttrMayUseVV)) || LIKELY(!fp->hasVarEnv())) { auto const local = fp->func()->lookupVarId(s_86metadata.get()); if (LIKELY(local != kInvalidId)) { cellSet(*metadata.asCell(), *tvAssertCell(frame_local(fp, local))); } else { SystemLib::throwInvalidArgumentExceptionObject( "Unsupported dynamic call of set_frame_metadata()"); } } else { fp->getVarEnv()->set(s_86metadata.get(), metadata.asTypedValue()); } }
void NameValueTable::attach(ActRec* fp) { assert(m_fp == nullptr); m_fp = fp; const auto func = fp->m_func; const Id numNames = func->numNamedLocals(); TypedValue* loc = frame_local(fp, 0); for (Id i = 0; i < numNames; ++i, --loc) { assert(func->lookupVarId(func->localVarName(i)) == i); auto elm = insert(func->localVarName(i)); assert(elm); if (elm->m_tv.m_type != KindOfInvalid) { assert(elm->m_tv.m_type != KindOfNamedLocal); tvCopy(elm->m_tv, *loc); } elm->m_tv.m_type = KindOfNamedLocal; elm->m_tv.m_data.num = i; } }
Array createBacktrace(const BacktraceArgs& btArgs) { Array bt = Array::Create(); // If there is a parser frame, put it at the beginning of // the backtrace if (btArgs.m_parserFrame) { bt.append( make_map_array( s_file, btArgs.m_parserFrame->filename, s_line, btArgs.m_parserFrame->lineNumber ) ); } VMRegAnchor _; if (!vmfp()) { // If there are no VM frames, we're done return bt; } int depth = 0; ActRec* fp = nullptr; Offset pc = 0; // Get the fp and pc of the top frame (possibly skipping one frame) { if (btArgs.m_skipTop) { fp = g_context->getPrevVMState(vmfp(), &pc); if (!fp) { // We skipped over the only VM frame, we're done return bt; } } else { fp = vmfp(); Unit *unit = vmfp()->m_func->unit(); assert(unit); pc = unit->offsetOf(vmpc()); } // Handle the top frame if (btArgs.m_withSelf) { // Builtins don't have a file and line number if (!fp->m_func->isBuiltin()) { Unit* unit = fp->m_func->unit(); assert(unit); const char* filename = fp->m_func->filename()->data(); Offset off = pc; ArrayInit frame(btArgs.m_parserFrame ? 4 : 2, ArrayInit::Map{}); frame.set(s_file, filename); frame.set(s_line, unit->getLineNumber(off)); if (btArgs.m_parserFrame) { frame.set(s_function, s_include); frame.set(s_args, Array::Create(btArgs.m_parserFrame->filename)); } bt.append(frame.toVariant()); depth++; } } } // Handle the subsequent VM frames Offset prevPc = 0; for (ActRec* prevFp = g_context->getPrevVMState(fp, &prevPc); fp != nullptr && (btArgs.m_limit == 0 || depth < btArgs.m_limit); fp = prevFp, pc = prevPc, prevFp = g_context->getPrevVMState(fp, &prevPc)) { // do not capture frame for HPHP only functions if (fp->m_func->isNoInjection()) { continue; } ArrayInit frame(7, ArrayInit::Map{}); auto const curUnit = fp->m_func->unit(); auto const curOp = *reinterpret_cast<const Op*>(curUnit->at(pc)); auto const isReturning = curOp == Op::RetC || curOp == Op::RetV || curOp == Op::CreateCont || curOp == Op::Await || fp->localsDecRefd(); // Builtins and generators don't have a file and line number if (prevFp && !prevFp->m_func->isBuiltin() && !fp->resumed()) { auto const prevUnit = prevFp->m_func->unit(); auto prevFile = prevUnit->filepath(); if (prevFp->m_func->originalFilename()) { prevFile = prevFp->m_func->originalFilename(); } assert(prevFile); frame.set(s_file, const_cast<StringData*>(prevFile)); // In the normal method case, the "saved pc" for line number printing is // pointing at the cell conversion (Unbox/Pop) instruction, not the call // itself. For multi-line calls, this instruction is associated with the // subsequent line which results in an off-by-n. We're subtracting one // in order to look up the line associated with the FCall/FCallArray // instruction. Exception handling and the other opcodes (ex. BoxR) // already do the right thing. The emitter associates object access with // the subsequent expression and this would be difficult to modify. auto const opAtPrevPc = *reinterpret_cast<const Op*>(prevUnit->at(prevPc)); Offset pcAdjust = 0; if (opAtPrevPc == OpPopR || opAtPrevPc == OpUnboxR) { pcAdjust = 1; } frame.set(s_line, prevFp->m_func->unit()->getLineNumber(prevPc - pcAdjust)); } // check for include String funcname = const_cast<StringData*>(fp->m_func->name()); if (fp->m_func->isClosureBody()) { static StringData* s_closure_label = makeStaticString("{closure}"); funcname = s_closure_label; } // check for pseudomain if (funcname.empty()) { if (!prevFp) continue; funcname = s_include; } frame.set(s_function, funcname); if (!funcname.same(s_include)) { // Closures have an m_this but they aren't in object context Class* ctx = arGetContextClass(fp); if (ctx != nullptr && !fp->m_func->isClosureBody()) { frame.set(s_class, ctx->name()->data()); if (fp->hasThis() && !isReturning) { if (btArgs.m_withThis) { frame.set(s_object, Object(fp->getThis())); } frame.set(s_type, "->"); } else { frame.set(s_type, "::"); } } } Array args = Array::Create(); if (btArgs.m_ignoreArgs) { // do nothing } else if (funcname.same(s_include)) { if (depth) { args.append(const_cast<StringData*>(curUnit->filepath())); frame.set(s_args, args); } } else if (!RuntimeOption::EnableArgsInBacktraces || isReturning) { // Provide an empty 'args' array to be consistent with hphpc frame.set(s_args, args); } else { const int nparams = fp->m_func->numNonVariadicParams(); int nargs = fp->numArgs(); int nformals = std::min(nparams, nargs); if (UNLIKELY(fp->hasVarEnv() && fp->getVarEnv()->getFP() != fp)) { // VarEnv is attached to eval or debugger frame, other than the current // frame. Access locals thru VarEnv. auto varEnv = fp->getVarEnv(); auto func = fp->func(); for (int i = 0; i < nformals; i++) { TypedValue *arg = varEnv->lookup(func->localVarName(i)); args.append(tvAsVariant(arg)); } } else { for (int i = 0; i < nformals; i++) { TypedValue *arg = frame_local(fp, i); args.append(tvAsVariant(arg)); } } /* builtin extra args are not stored in varenv */ if (nargs > nparams && fp->hasExtraArgs()) { for (int i = nparams; i < nargs; i++) { TypedValue *arg = fp->getExtraArg(i - nparams); args.append(tvAsVariant(arg)); } } frame.set(s_args, args); } bt.append(frame.toVariant()); depth++; } return bt; }
Array createBacktrace(const BacktraceArgs& btArgs) { auto bt = Array::Create(); // If there is a parser frame, put it at the beginning of the backtrace. if (btArgs.m_parserFrame) { bt.append( make_map_array( s_file, btArgs.m_parserFrame->filename, s_line, btArgs.m_parserFrame->lineNumber ) ); } VMRegAnchor _; // If there are no VM frames, we're done. if (!rds::header() || !vmfp()) return bt; int depth = 0; ActRec* fp = nullptr; Offset pc = 0; // Get the fp and pc of the top frame (possibly skipping one frame). if (btArgs.m_skipTop) { fp = getPrevActRec(vmfp(), &pc); // We skipped over the only VM frame, we're done. if (!fp) return bt; } else { fp = vmfp(); auto const unit = fp->func()->unit(); assert(unit); pc = unit->offsetOf(vmpc()); } // Handle the top frame. if (btArgs.m_withSelf) { // Builtins don't have a file and line number. if (!fp->func()->isBuiltin()) { auto const unit = fp->func()->unit(); assert(unit); auto const filename = fp->func()->filename(); ArrayInit frame(btArgs.m_parserFrame ? 4 : 2, ArrayInit::Map{}); frame.set(s_file, Variant{const_cast<StringData*>(filename)}); frame.set(s_line, unit->getLineNumber(pc)); if (btArgs.m_parserFrame) { frame.set(s_function, s_include); frame.set(s_args, Array::Create(btArgs.m_parserFrame->filename)); } bt.append(frame.toVariant()); depth++; } } // Handle the subsequent VM frames. Offset prevPc = 0; for (auto prevFp = getPrevActRec(fp, &prevPc); fp != nullptr && (btArgs.m_limit == 0 || depth < btArgs.m_limit); fp = prevFp, pc = prevPc, prevFp = getPrevActRec(fp, &prevPc)) { // Do not capture frame for HPHP only functions. if (fp->func()->isNoInjection()) continue; ArrayInit frame(7, ArrayInit::Map{}); auto const curUnit = fp->func()->unit(); auto const curOp = *reinterpret_cast<const Op*>(curUnit->at(pc)); auto const isReturning = curOp == Op::RetC || curOp == Op::RetV || curOp == Op::CreateCont || curOp == Op::Await || fp->localsDecRefd(); // Builtins and generators don't have a file and line number if (prevFp && !prevFp->func()->isBuiltin()) { auto const prevUnit = prevFp->func()->unit(); auto prevFile = prevUnit->filepath(); if (prevFp->func()->originalFilename()) { prevFile = prevFp->func()->originalFilename(); } assert(prevFile); frame.set(s_file, Variant{const_cast<StringData*>(prevFile)}); // In the normal method case, the "saved pc" for line number printing is // pointing at the cell conversion (Unbox/Pop) instruction, not the call // itself. For multi-line calls, this instruction is associated with the // subsequent line which results in an off-by-n. We're subtracting one // in order to look up the line associated with the FCall/FCallArray // instruction. Exception handling and the other opcodes (ex. BoxR) // already do the right thing. The emitter associates object access with // the subsequent expression and this would be difficult to modify. auto const opAtPrevPc = *reinterpret_cast<const Op*>(prevUnit->at(prevPc)); Offset pcAdjust = 0; if (opAtPrevPc == Op::PopR || opAtPrevPc == Op::UnboxR || opAtPrevPc == Op::UnboxRNop) { pcAdjust = 1; } frame.set(s_line, prevFp->func()->unit()->getLineNumber(prevPc - pcAdjust)); } // Check for include. String funcname{const_cast<StringData*>(fp->func()->name())}; if (fp->func()->isClosureBody()) { // Strip the file hash from the closure name. String fullName{const_cast<StringData*>(fp->func()->baseCls()->name())}; funcname = fullName.substr(0, fullName.find(';')); } // Check for pseudomain. if (funcname.empty()) { if (!prevFp && !btArgs.m_withPseudoMain) continue; else if (!prevFp) funcname = s_main; else funcname = s_include; } frame.set(s_function, funcname); if (!funcname.same(s_include)) { // Closures have an m_this but they aren't in object context. auto ctx = arGetContextClass(fp); if (ctx != nullptr && !fp->func()->isClosureBody()) { frame.set(s_class, Variant{const_cast<StringData*>(ctx->name())}); if (fp->hasThis() && !isReturning) { if (btArgs.m_withThis) { frame.set(s_object, Object(fp->getThis())); } frame.set(s_type, s_arrow); } else { frame.set(s_type, s_double_colon); } } } bool const mayUseVV = fp->func()->attrs() & AttrMayUseVV; auto const withNames = btArgs.m_withArgNames; auto const withValues = btArgs.m_withArgValues; if (!btArgs.m_withArgNames && !btArgs.m_withArgValues) { // do nothing } else if (funcname.same(s_include)) { if (depth != 0) { auto filepath = const_cast<StringData*>(curUnit->filepath()); frame.set(s_args, make_packed_array(filepath)); } } else if (!RuntimeOption::EnableArgsInBacktraces || isReturning) { // Provide an empty 'args' array to be consistent with hphpc. frame.set(s_args, empty_array()); } else { auto args = Array::Create(); auto const nparams = fp->func()->numNonVariadicParams(); auto const nargs = fp->numArgs(); auto const nformals = std::min<int>(nparams, nargs); if (UNLIKELY(mayUseVV) && UNLIKELY(fp->hasVarEnv() && fp->getVarEnv()->getFP() != fp)) { // VarEnv is attached to eval or debugger frame, other than the current // frame. Access locals thru VarEnv. auto varEnv = fp->getVarEnv(); auto func = fp->func(); for (int i = 0; i < nformals; i++) { auto const argname = func->localVarName(i); auto const tv = varEnv->lookup(argname); Variant val; if (tv != nullptr) { // the variable hasn't been unset val = withValues ? tvAsVariant(tv) : ""; } if (withNames) { args.set(String(const_cast<StringData*>(argname)), val); } else { args.append(val); } } } else { for (int i = 0; i < nformals; i++) { Variant val = withValues ? tvAsVariant(frame_local(fp, i)) : ""; if (withNames) { auto const argname = fp->func()->localVarName(i); args.set(String(const_cast<StringData*>(argname)), val); } else { args.append(val); } } } // Builtin extra args are not stored in varenv. if (UNLIKELY(mayUseVV) && nargs > nparams && fp->hasExtraArgs()) { for (int i = nparams; i < nargs; i++) { auto arg = fp->getExtraArg(i - nparams); args.append(tvAsVariant(arg)); } } frame.set(s_args, args); } if (btArgs.m_withMetadata && !isReturning) { if (UNLIKELY(mayUseVV) && UNLIKELY(fp->hasVarEnv())) { auto tv = fp->getVarEnv()->lookup(s_86metadata.get()); if (tv != nullptr && tv->m_type != KindOfUninit) { frame.set(s_metadata, tvAsVariant(tv)); } } else { auto local = fp->func()->lookupVarId(s_86metadata.get()); if (local != kInvalidId) { auto tv = frame_local(fp, local); if (tv->m_type != KindOfUninit) { frame.set(s_metadata, tvAsVariant(tv)); } } } } bt.append(frame.toVariant()); depth++; } return bt; }
// Find a symbol given by string key of the specified symbol type. Context // information can be specified through ctx, sym, and ar. // // StaticRoot, StaticProp // * Search for a static property given by key in class ctx // * A class name may be encoded with the property as *classname*propname // to indicate a class that should be used as the visibility context for // loading the property static Variant xdebug_lookup_symbol(SymbolType type, String key, Class*& ctx, Variant& sym, ActRec* ar) { char* name = key.get()->mutableData(); char* end = key.get()->mutableData() + key.size(); assert(name != end); switch (type) { case SymbolType::StaticRoot: case SymbolType::StaticProp: { TypedValue* ret = nullptr; if (!ctx) return uninit_null(); Class* newCtx = nullptr; char* secStar; bool vis, acc; if ((ret = ctx->getSProp(ctx, key.get(), vis, acc))) { return tvAsVariant(ret); } for (secStar = name + 1; secStar != end && *secStar != '*'; ++secStar); if (secStar != end && *name == '*' && *secStar == '*') { String clsKey(name + 1, secStar - name - 1, CopyStringMode()); String newKey(secStar + 1, end - secStar - 1, CopyStringMode()); newCtx = Unit::lookupClass(clsKey.get()); if (newCtx && (ret = ctx->getSProp(newCtx, newKey.get(), vis, acc))) { return tvAsVariant(ret); } } return uninit_null(); } break; case SymbolType::Root: { const Func* func = ar->func(); if (key.size() == 4 && strncmp(name, "this", 4) == 0) { return ar->hasThis() ? ar->getThis() : nullptr; } Id localId = func->lookupVarId(key.get()); if (localId != kInvalidId) { TypedValue* tv = frame_local(ar, localId); return tv ? tvAsVariant(tv) : uninit_null(); } Class* tmp = Unit::lookupClass(key.get()); if (tmp) ctx = tmp; return uninit_null(); } break; case SymbolType::ArrayIndexAssoc: { return sym.isArray() ? sym.asArrRef().rvalAt(key) : sym.isObject() ? sym.asObjRef().o_get(key, false) : uninit_null(); } case SymbolType::ArrayIndexNum: { int64_t iKey = key.toInt64(); return sym.isArray() ? sym.asArrRef().rvalAt(iKey) : uninit_null(); } case SymbolType::ObjProp: { char* secStar; if (!sym.is(KindOfObject)) return uninit_null(); Object obj = sym.toObject(); Variant v = obj->o_get(key, false); if(!v.isNull()) return v; for (secStar = name + 1; secStar != end && *secStar != '*'; ++secStar); if (secStar != end && *name == '*' && *secStar == '*') { String clsKey(name + 1, secStar - name - 1, CopyStringMode()); String newKey(secStar + 1, end - secStar - 1, CopyStringMode()); v = obj.o_get(key, false, clsKey); } return v; } } not_reached(); }
TypedValue* NameValueTable::derefNamedLocal(TypedValue* tv) const { return tv->m_type == KindOfNamedLocal ? frame_local(m_fp, tv->m_data.num) : tv; }