HOT_FUNC_VM int64_t decodeCufIterHelper(Iter* it, TypedValue func) { DECLARE_FRAME_POINTER(framePtr); ObjectData* obj = nullptr; HPHP::Class* cls = nullptr; StringData* invName = nullptr; auto ar = (ActRec*)framePtr->m_savedRbp; if (LIKELY(ar->m_func->isBuiltin())) { ar = g_vmContext->getOuterVMFrame(ar); } const Func* f = vm_decode_function(tvAsVariant(&func), ar, false, obj, cls, invName, false); if (UNLIKELY(!f)) return false; CufIter &cit = it->cuf(); cit.setFunc(f); if (obj) { cit.setCtx(obj); obj->incRefCount(); } else { cit.setCtx(cls); } cit.setName(invName); return true; }
/* * Helper method from converting between a PHP function and a CufIter. */ static bool vm_decode_function_cufiter(const Variant& function, SmartCufIterPtr& cufIter) { ObjectData* obj = nullptr; HPHP::Class* cls = nullptr; HPHP::JIT::CallerFrame cf; StringData* invName = nullptr; // Don't warn here, let the caller decide what to do if the func is nullptr. const HPHP::Func* func = vm_decode_function(function, cf(), false, obj, cls, invName, false); if (func == nullptr) { return false; } cufIter = smart::make_unique<CufIter>(); cufIter->setFunc(func); cufIter->setName(invName); if (obj) { cufIter->setCtx(obj); obj->incRefCount(); } else { cufIter->setCtx(cls); } return true; }
ALWAYS_INLINE typename std::enable_if< std::is_base_of<BaseMap, TMap>::value, Object>::type BaseMap::php_takeWhile(const Variant& fn) { CallCtx ctx; vm_decode_function(fn, nullptr, false, ctx); if (!ctx.func) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter must be a valid callback"); } auto map = req::make<TMap>(); if (!m_size) return Object{std::move(map)}; int32_t version UNUSED; if (std::is_same<c_Map, TMap>::value) { version = m_version; } for (ssize_t pos = iter_begin(); iter_valid(pos); pos = iter_next(pos)) { auto* e = iter_elm(pos); bool b = invokeAndCastToBool(ctx, 1, &e->data); if (std::is_same<c_Map, TMap>::value) { if (UNLIKELY(version != m_version)) { throw_collection_modified(); } } if (!b) break; e = iter_elm(pos); if (e->hasIntKey()) { map->set(e->ikey, &e->data); } else { assert(e->hasStrKey()); map->set(e->skey, &e->data); } } return Object{std::move(map)}; }
void BaseVector::mapwithkey(BaseVector* bvec, CVarRef callback) { CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { Object e(SystemLib::AllocInvalidArgumentExceptionObject( "Parameter must be a valid callback")); throw e; } uint sz = m_size; bvec->reserve(sz); for (uint i = 0; i < sz; ++i) { TypedValue* tv = &bvec->m_data[i]; int32_t version = m_version; TypedValue args[2] = { make_tv<KindOfInt64>(i), m_data[i] }; g_vmContext->invokeFuncFew(tv, ctx, 2, args); if (UNLIKELY(version != m_version)) { tvRefcountedDecRef(tv); throw_collection_modified(); } ++bvec->m_size; } }
void BaseVector::filterwithkey(BaseVector* bvec, CVarRef callback) { CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { Object e(SystemLib::AllocInvalidArgumentExceptionObject( "Parameter must be a valid callback")); throw e; } uint sz = m_size; for (uint i = 0; i < sz; ++i) { Variant ret; int32_t version = m_version; TypedValue args[2] = { make_tv<KindOfInt64>(i), m_data[i] }; g_vmContext->invokeFuncFew(ret.asTypedValue(), ctx, 2, args); if (UNLIKELY(version != m_version)) { throw_collection_modified(); } if (ret.toBoolean()) { bvec->add(&m_data[i]); } } }
Variant f_array_reduce(CVarRef input, CVarRef callback, CVarRef initial /* = null_variant */) { getCheckedArray(input); CallCtx ctx; CallerFrame cf; vm_decode_function(callback, cf(), false, ctx); if (ctx.func == NULL) { return uninit_null(); } return ArrayUtil::Reduce(arr_input, reduce_func, &ctx, initial); }
Variant f_array_filter(CVarRef input, CVarRef callback /* = null_variant */) { getCheckedArray(input); if (callback.isNull()) { return ArrayUtil::Filter(arr_input); } CallCtx ctx; EagerCallerFrame cf; vm_decode_function(callback, cf(), false, ctx); if (ctx.func == NULL) { return uninit_null(); } return ArrayUtil::Filter(arr_input, filter_func, &ctx); }
bool f_ob_start(CVarRef callback /* = uninit_null() */, int chunk_size /* = 0 */, bool erase /* = true */) { // ignoring chunk_size and erase if (!callback.isNull()) { CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { return false; } } g_context->obStart(callback); return true; }
void HHVM_FUNCTION(disable_inlining, const Variant& function) { CallerFrame cf; ObjectData* obj = nullptr; HPHP::Class* cls = nullptr; StringData* invName = nullptr; const HPHP::Func* f = vm_decode_function(function, cf(), false, obj, cls, invName, false); if (f == nullptr || f->isAbstract()) { raise_warning("disable_inlining(): undefined function"); return; } jit::InliningDecider::forbidInliningOf(f); }
void HHVM_STATIC_METHOD(IntlChar, enumCharTypes, const Variant& callback) { CallCtx ctx; ctx.func = nullptr; if (!callback.isNull()) { CallerFrame cf; vm_decode_function(callback, cf(), false, ctx); } if (!ctx.func) { s_intl_error->setError(U_INTERNAL_PROGRAM_ERROR, "enumCharTypes callback failed"); return; } u_enumCharTypes((UCharEnumTypeRange*)enumCharType_callback, &ctx); }
bool f_array_walk(VRefParam input, CVarRef funcname, CVarRef userdata /* = null_variant */) { if (!input.isArray()) { throw_bad_array_exception(); return false; } CallCtx ctx; CallerFrame cf; vm_decode_function(funcname, cf(), false, ctx); if (ctx.func == NULL) { return false; } ArrayUtil::Walk(input, walk_func, &ctx, false, NULL, userdata); return true; }
bool HHVM_FUNCTION(ob_start, const Variant& callback /* = null */, int chunk_size /* = 0 */, bool erase /* = true */) { // ignoring chunk_size and erase if (!callback.isNull()) { CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { return false; } } g_context->obStart(callback); return true; }
bool HHVM_FUNCTION(ob_start, const Variant& callback /* = null */, int chunk_size /* = 0 */, int flags /* = k_PHP_OUTPUT_HANDLER_STDFLAGS */) { // ignoring flags for now if (!callback.isNull()) { CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { return false; } } g_context->obStart(callback, chunk_size); return true; }
Variant vm_call_user_func(const Variant& function, const Variant& params, bool forwarding /* = false */) { ObjectData* obj = nullptr; HPHP::Class* cls = nullptr; HPHP::JIT::CallerFrame cf; StringData* invName = nullptr; const HPHP::Func* f = vm_decode_function(function, cf(), forwarding, obj, cls, invName); if (f == nullptr || (!isContainer(params) && !params.isNull())) { return uninit_null(); } Variant ret; g_context->invokeFunc((TypedValue*)&ret, f, params, obj, cls, nullptr, invName, ExecutionContext::InvokeCuf); return ret; }
bool f_array_walk_recursive(VRefParam input, CVarRef funcname, CVarRef userdata /* = null_variant */) { if (!input.isArray()) { throw_bad_array_exception(); return false; } CallCtx ctx; CallerFrame cf; vm_decode_function(funcname, cf(), false, ctx); if (ctx.func == NULL) { return uninit_null(); } PointerSet seen; ArrayUtil::Walk(input, walk_func, &ctx, true, &seen, userdata); return true; }
Object BaseMap::php_retain(const Variant& callback) { CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter must be a valid callback"); } auto size = m_size; if (!size) { return Object{this}; } constexpr int64_t argc = useKey ? 2 : 1; TypedValue argv[argc]; for (ssize_t pos = iter_begin(); iter_valid(pos); pos = iter_next(pos)) { auto* e = iter_elm(pos); if (useKey) { if (e->hasIntKey()) { argv[0].m_type = KindOfInt64; argv[0].m_data.num = e->ikey; } else { argv[0].m_type = KindOfString; argv[0].m_data.pstr = e->skey; } } argv[argc-1] = e->data; int32_t version = m_version; bool b = invokeAndCastToBool(ctx, argc, argv); if (UNLIKELY(version != m_version)) { throw_collection_modified(); } if (b) { continue; } mutateAndBump(); version = m_version; e = iter_elm(pos); ssize_t pp = (e->hasIntKey() ? findForRemove(e->ikey) : findForRemove(e->skey, e->skey->hash())); eraseNoCompact(pp); if (UNLIKELY(version != m_version)) { throw_collection_modified(); } } assert(m_size <= size); compactOrShrinkIfDensityTooLow(); return Object{this}; }
ALWAYS_INLINE typename std::enable_if< std::is_base_of<BaseMap, TMap>::value, Object>::type BaseMap::php_filter(const Variant& callback) const { VMRegGuard _; CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter must be a valid callback"); } auto map = req::make<TMap>(); if (!m_size) return Object(std::move(map)); map->mutate(); int32_t version = m_version; constexpr int64_t argc = useKey ? 2 : 1; TypedValue argv[argc]; for (ssize_t pos = iter_begin(); iter_valid(pos); pos = iter_next(pos)) { auto* e = iter_elm(pos); if (useKey) { if (e->hasIntKey()) { argv[0].m_type = KindOfInt64; argv[0].m_data.num = e->ikey; } else { argv[0].m_type = KindOfString; argv[0].m_data.pstr = e->skey; } } argv[argc-1] = e->data; bool b = invokeAndCastToBool(ctx, argc, argv); if (UNLIKELY(version != m_version)) { throw_collection_modified(); } if (!b) continue; e = iter_elm(pos); if (e->hasIntKey()) { map->set(e->ikey, &e->data); } else { assert(e->hasStrKey()); map->set(e->skey, &e->data); } } return Object(std::move(map)); }
bool HHVM_FUNCTION(ob_start, const Variant& callback /* = null */, int chunk_size /* = 0 */, int flags /* = k_PHP_OUTPUT_HANDLER_STDFLAGS */) { // Note: the only flag which is implemented is FLUSHABLE if (!callback.isNull()) { CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { return false; } } OBFlags f = OBFlags::None; if (flags & k_PHP_OUTPUT_HANDLER_CLEANABLE) f |= OBFlags::Cleanable; if (flags & k_PHP_OUTPUT_HANDLER_FLUSHABLE) f |= OBFlags::Flushable; if (flags & k_PHP_OUTPUT_HANDLER_REMOVABLE) f |= OBFlags::Removable; g_context->obStart(callback, chunk_size, f); return true; }
void loadArrayFunctionContext(ArrayData* arr, ActRec* preLiveAR, ActRec* fp) { ObjectData* inst = nullptr; HPHP::Class* cls = nullptr; StringData* invName = nullptr; try { // if size != 2, throws exception // if the first element of the array is a string classname, will autoload it const Func* func = vm_decode_function( Variant(arr), fp, /* forwarding */ false, inst, cls, invName, /* warn */ false ); if (UNLIKELY(func == nullptr)) { raise_error("Invalid callable (array)"); } assert(cls != nullptr); // array should resolve only to a method preLiveAR->m_func = func; if (inst) { inst->incRefCount(); preLiveAR->setThis(inst); } else { preLiveAR->setClass(cls); } if (UNLIKELY(invName != nullptr)) { preLiveAR->setInvName(invName); } } catch (...) { // This is extreme shadiness. See the comments of // arPreliveOverwriteCells() for more info on how this code gets the // unwinder to restore the pre-FPush state. auto firstActRecCell = arPreliveOverwriteCells(preLiveAR); firstActRecCell->m_type = KindOfArray; firstActRecCell->m_data.parr = arr; throw; } }
typename std::enable_if< std::is_base_of<BaseSet, TSet>::value, Object>::type BaseSet::php_skipWhile(const Variant& fn) { CallCtx ctx; vm_decode_function(fn, nullptr, false, ctx); if (!ctx.func) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter must be a valid callback"); } auto set = req::make<TSet>(); if (!m_size) return Object(std::move(set)); // we don't reserve(), because we don't know how selective fn will be set->mutate(); int32_t version UNUSED; if (std::is_same<c_Set, TSet>::value) { version = m_version; } uint32_t used = posLimit(); uint32_t i = 0; for (; i < used; ++i) { if (isTombstone(i)) continue; Elm& e = data()[i]; bool b = invokeAndCastToBool(ctx, 1, &e.data); if (std::is_same<c_Set, TSet>::value) { if (UNLIKELY(version != m_version)) { throw_collection_modified(); } } if (!b) break; } for (; i < used; ++i) { if (isTombstone(i)) continue; Elm& e = data()[i]; if (e.hasIntKey()) { set->addRaw(e.data.m_data.num); } else { assert(e.hasStrKey()); set->addRaw(e.data.m_data.pstr); } } return Object(std::move(set)); }
Variant f_array_map(int _argc, CVarRef callback, CVarRef arr1, CArrRef _argv /* = null_array */) { Array inputs; if (!arr1.isArray()) { throw_bad_array_exception(); return uninit_null(); } inputs.append(arr1); if (!_argv.empty()) { inputs = inputs.merge(_argv); } CallCtx ctx; ctx.func = NULL; if (!callback.isNull()) { EagerCallerFrame cf; vm_decode_function(callback, cf(), false, ctx); } if (ctx.func == NULL) { return ArrayUtil::Map(inputs, map_func, NULL); } return ArrayUtil::Map(inputs, map_func, &ctx); }
bool PackedArray::Usort(ArrayData* ad, const Variant& cmp_function) { assert(ad->isPacked()); if (ad->m_size <= 1) { return true; } assert(!ad->hasMultipleRefs()); if (UNLIKELY(strong_iterators_exist())) { free_strong_iterators(ad); } ElmUCompare<TVAccessor> comp; CallCtx ctx; CallerFrame cf; vm_decode_function(cmp_function, cf(), false, ctx); if (!ctx.func) { return false; } comp.ctx = &ctx; auto const data = packedData(ad); Sort::sort(data, data + ad->m_size, comp); return true; }
typename std::enable_if< std::is_base_of<BaseSet, TSet>::value, Object>::type BaseSet::php_map(const Variant& callback) const { VMRegGuard _; CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter must be a valid callback"); } auto set = req::make<TSet>(); if (!m_size) return Object{std::move(set)}; assert(posLimit() != 0); assert(hashSize() > 0); assert(set->arrayData() == staticEmptyMixedArray()); auto oldCap = set->cap(); set->reserve(posLimit()); // presume minimum collisions ... assert(set->canMutateBuffer()); constexpr int64_t argc = useKey ? 2 : 1; TypedValue argv[argc]; for (ssize_t pos = iter_begin(); iter_valid(pos); pos = iter_next(pos)) { auto e = iter_elm(pos); TypedValue tvCbRet; int32_t pVer = m_version; if (useKey) { argv[0] = e->data; } argv[argc-1] = e->data; g_context->invokeFuncFew(&tvCbRet, ctx, argc, argv); // Now that tvCbRet is live, make sure to decref even if we throw. SCOPE_EXIT { tvRefcountedDecRef(&tvCbRet); }; if (UNLIKELY(m_version != pVer)) throw_collection_modified(); set->addRaw(&tvCbRet); } // ... and shrink back if that was incorrect set->shrinkIfCapacityTooHigh(oldCap); return Object{std::move(set)}; }
typename std::enable_if< std::is_base_of<BaseSet, TSet>::value, Object>::type BaseSet::php_filter(const Variant& callback) const { VMRegGuard _; CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter must be a valid callback"); } auto set = req::make<TSet>(); if (!m_size) return Object(std::move(set)); // we don't reserve(), because we don't know how selective callback will be set->mutate(); int32_t version = m_version; constexpr int64_t argc = useKey ? 2 : 1; TypedValue argv[argc]; for (ssize_t pos = iter_begin(); iter_valid(pos); pos = iter_next(pos)) { auto e = iter_elm(pos); if (useKey) { argv[0] = e->data; } argv[argc-1] = e->data; bool b = invokeAndCastToBool(ctx, argc, argv); if (UNLIKELY(version != m_version)) { throw_collection_modified(); } if (!b) continue; e = iter_elm(pos); if (e->hasIntKey()) { set->addRaw(e->data.m_data.num); } else { assert(e->hasStrKey()); set->addRaw(e->data.m_data.pstr); } } return Object(std::move(set)); }
void HHVM_STATIC_METHOD(IntlChar, enumCharNames, const Variant& vStart, const Variant& vLimit, const Variant& callback, int64_t choice) { GETCP_VOID(vStart, start); GETCP_VOID(vLimit, limit); CallCtx ctx; ctx.func = nullptr; if (!callback.isNull()) { CallerFrame cf; vm_decode_function(callback, cf(), false, ctx); } if (!ctx.func) { s_intl_error->setError(U_INTERNAL_PROGRAM_ERROR, "enumCharNames callback failed"); return; } UErrorCode error = U_ZERO_ERROR; u_enumCharNames(start, limit, (UEnumCharNamesFn*)enumCharNames_callback, &ctx, (UCharNameChoice)choice, &error); if (U_FAILURE(error)) { s_intl_error->setError(error); } }
ALWAYS_INLINE typename std::enable_if< std::is_base_of<BaseMap, TMap>::value, Object>::type BaseMap::php_map(const Variant& callback) const { VMRegGuard _; CallCtx ctx; vm_decode_function(callback, nullptr, false, ctx); if (!ctx.func) { SystemLib::throwInvalidArgumentExceptionObject( "Parameter must be a valid callback"); } auto map = req::make<TMap>(); if (!m_size) return Object{std::move(map)}; assert(posLimit() != 0); assert(hashSize() > 0); assert(map->arrayData() == staticEmptyMixedArray()); map->m_arr = MixedArray::asMixed(MixedArray::MakeReserveMixed(cap())); map->setIntLikeStrKeys(intLikeStrKeys()); wordcpy(map->hashTab(), hashTab(), hashSize()); { uint32_t used = posLimit(); int32_t version = m_version; uint32_t i = 0; // When the loop below finishes or when an exception is thrown, // make sure that posLimit() get set to the correct value and // that m_pos gets set to point to the first element. SCOPE_EXIT { map->setPosLimit(i); map->arrayData()->m_pos = map->nthElmPos(0); }; constexpr int64_t argc = useKey ? 2 : 1; TypedValue argv[argc]; for (; i < used; ++i) { const Elm& e = data()[i]; Elm& ne = map->data()[i]; if (isTombstone(i)) { ne.data.m_type = e.data.m_type; continue; } TypedValue* tv = &ne.data; if (useKey) { if (e.hasIntKey()) { argv[0].m_type = KindOfInt64; argv[0].m_data.num = e.ikey; } else { argv[0].m_type = KindOfString; argv[0].m_data.pstr = e.skey; } } argv[argc-1] = e.data; g_context->invokeFuncFew(tv, ctx, argc, argv); if (UNLIKELY(version != m_version)) { tvRefcountedDecRef(tv); throw_collection_modified(); } if (e.hasStrKey()) { e.skey->incRefCount(); } ne.ikey = e.ikey; ne.data.hash() = e.data.hash(); map->incSize(); // Needed so that the new elements are accounted for when GC scanning. map->incPosLimit(); } } return Object{std::move(map)}; }