Object c_GenArrayWaitHandle::ti_create(const char* cls, CArrRef dependencies) { Array deps = dependencies->copy(); for (ssize_t iter_pos = deps->iter_begin(); iter_pos != ArrayData::invalid_index; iter_pos = deps->iter_advance(iter_pos)) { TypedValue* current = deps->nvGetValueRef(iter_pos); if (UNLIKELY(current->m_type == KindOfRef)) { tvUnbox(current); } if (!c_WaitHandle::fromTypedValue(current) && !IS_NULL_TYPE(current->m_type)) { Object e(SystemLib::AllocInvalidArgumentExceptionObject( "Expected dependencies to be an array of WaitHandle instances")); throw e; } } Object exception; for (ssize_t iter_pos = deps->iter_begin(); iter_pos != ArrayData::invalid_index; iter_pos = deps->iter_advance(iter_pos)) { TypedValue* current = deps->nvGetValueRef(iter_pos); if (IS_NULL_TYPE(current->m_type)) { // {uninit,null} yields null tvWriteNull(current); continue; } assert(current->m_type == KindOfObject); assert(dynamic_cast<c_WaitHandle*>(current->m_data.pobj)); auto child = static_cast<c_WaitHandle*>(current->m_data.pobj); if (child->isSucceeded()) { tvSetIgnoreRef(child->getResult(), current); } else if (child->isFailed()) { putException(exception, child->getException()); } else { assert(dynamic_cast<c_WaitableWaitHandle*>(child)); auto child_wh = static_cast<c_WaitableWaitHandle*>(child); c_GenArrayWaitHandle* my_wh = NEWOBJ(c_GenArrayWaitHandle)(); my_wh->initialize(exception, deps, iter_pos, child_wh); return my_wh; } } if (exception.isNull()) { TypedValue tv; tv.m_type = KindOfArray; tv.m_data.parr = deps.get(); return c_StaticResultWaitHandle::Create(&tv); } else { return c_StaticExceptionWaitHandle::Create(exception.get()); } }
bool objOffsetIsset(TypedValue& tvRef, ObjectData* base, const Variant& offset, bool validate /* = true */) { auto exists = objOffsetExists(base, offset); // Unless we called ArrayObject::offsetExists, there's nothing more to do if (exists != OffsetExistsResult::IssetIfNonNull) { return (int)exists; } // For ArrayObject::offsetExists, we need to check the value at `offset`. // If it's null, then we return false. TypedValue tvResult; tvWriteUninit(&tvResult); // We can't call the offsetGet method on `base` because users aren't // expecting offsetGet to be called for `isset(...)` expressions, so call // the method on the base ArrayObject class. auto const method = SystemLib::s_ArrayObjectClass->lookupMethod(s_offsetGet.get()); assert(method != nullptr); g_context->invokeFuncFew(&tvResult, method, base, nullptr, 1, offset.asCell()); auto const result = !IS_NULL_TYPE(tvResult.m_type); tvRefcountedDecRef(&tvResult); return result; }
bool TypeConstraint::checkPrimitive(DataType dt) const { assert(m_type.m_dt != KindOfObject); assert(dt != KindOfRef); if (nullable() && IS_NULL_TYPE(dt)) return true; return equivDataTypes(m_type.m_dt, dt); }
bool TypeConstraint::checkTypedefNonObj(const TypedValue* tv) const { assert(tv->m_type != KindOfObject); // this checks when tv is not an object assert(!isSelf() && !isParent()); auto const td = getTypedefWithAutoload(m_namedEntity, m_typeName); if (!td) return false; if (td->nullable && IS_NULL_TYPE(tv->m_type)) return true; return td->kind == KindOfAny || equivDataTypes(td->kind, tv->m_type); }
bool cellSame(Cell c1, Cell c2) { assert(cellIsPlausible(c1)); assert(cellIsPlausible(c2)); bool const null1 = IS_NULL_TYPE(c1.m_type); bool const null2 = IS_NULL_TYPE(c2.m_type); if (null1 && null2) return true; if (null1 || null2) return false; switch (c1.m_type) { case KindOfBoolean: case KindOfInt64: if (c2.m_type != c1.m_type) return false; return c1.m_data.num == c2.m_data.num; case KindOfDouble: if (c2.m_type != c1.m_type) return false; return c1.m_data.dbl == c2.m_data.dbl; case KindOfStaticString: case KindOfString: if (!IS_STRING_TYPE(c2.m_type)) return false; return c1.m_data.pstr->same(c2.m_data.pstr); case KindOfArray: if (c2.m_type != KindOfArray) return false; return c1.m_data.parr->equal(c2.m_data.parr, true); case KindOfObject: return c2.m_type == KindOfObject && c1.m_data.pobj == c2.m_data.pobj; case KindOfResource: return c2.m_type == KindOfResource && c1.m_data.pres == c2.m_data.pres; case KindOfUninit: case KindOfNull: case KindOfRef: case KindOfClass: break; } not_reached(); }
void c_GenArrayWaitHandle::enterContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // already in the more specific context? if (LIKELY(getContextIdx() >= ctx_idx)) { return; } assert(getState() == STATE_BLOCKED); // recursively import current child { assert(m_iterPos != ArrayData::invalid_index); TypedValue* current = m_deps->nvGetValueRef(m_iterPos); assert(current->m_type == KindOfObject); assert(dynamic_cast<c_WaitableWaitHandle*>(current->m_data.pobj)); auto child_wh = static_cast<c_WaitableWaitHandle*>(current->m_data.pobj); child_wh->enterContext(ctx_idx); } // import ourselves setContextIdx(ctx_idx); // try to import other children try { for (ssize_t iter_pos = m_deps->iter_advance(m_iterPos); iter_pos != ArrayData::invalid_index; iter_pos = m_deps->iter_advance(iter_pos)) { TypedValue* current = m_deps->nvGetValueRef(iter_pos); if (IS_NULL_TYPE(current->m_type)) { continue; } assert(current->m_type == KindOfObject); assert(dynamic_cast<c_WaitHandle*>(current->m_data.pobj)); auto child = static_cast<c_WaitHandle*>(current->m_data.pobj); if (child->isFinished()) { continue; } assert(dynamic_cast<c_WaitableWaitHandle*>(child)); auto child_wh = static_cast<c_WaitableWaitHandle*>(child); child_wh->enterContext(ctx_idx); } } catch (const Object& cycle_exception) { // exception will be eventually processed by onUnblocked() } }
bool tvCoerceParamToNullableObjectInPlace(TypedValue* tv) { assert(tvIsPlausible(*tv)); tvUnboxIfNeeded(tv); if (IS_NULL_TYPE(tv->m_type)) { // See comment in tvCastToNullableObjectInPlace tv->m_data.pobj = nullptr; return true; } return tv->m_type == KindOfObject; }
bool TypeConstraint::checkTypedefObj(const TypedValue* tv) const { assert(tv->m_type == KindOfObject); // this checks when tv is an object assert(!isSelf() && !isParent() && !isCallable()); auto const td = getTypedefWithAutoload(m_namedEntity, m_typeName); if (!td) return false; if (td->nullable && IS_NULL_TYPE(tv->m_type)) return true; if (td->kind != KindOfObject) return td->kind == KindOfAny; return td->klass && tv->m_data.pobj->instanceof(td->klass); }
void c_ContinuationWaitHandle::run() { // may happen if scheduled in multiple contexts if (getState() != STATE_SCHEDULED) { return; } try { setState(STATE_RUNNING); do { // iterate continuation if (m_child.isNull()) { // first iteration or null dependency m_continuation->call_next(); } else if (m_child->isSucceeded()) { // child succeeded, pass the result to the continuation m_continuation->call_send(m_child->getResult()); } else if (m_child->isFailed()) { // child failed, raise the exception inside continuation m_continuation->call_raise(m_child->getException()); } else { throw FatalErrorException( "Invariant violation: child neither succeeded nor failed"); } // continuation finished, retrieve result from its m_value if (m_continuation->m_done) { markAsSucceeded(m_continuation->m_value.asTypedValue()); return; } // set up dependency TypedValue* value = m_continuation->m_value.asTypedValue(); if (IS_NULL_TYPE(value->m_type)) { // null dependency m_child = nullptr; } else { c_WaitHandle* child = c_WaitHandle::fromTypedValue(value); if (UNLIKELY(!child)) { Object e(SystemLib::AllocInvalidArgumentExceptionObject( "Expected yield argument to be an instance of WaitHandle")); throw e; } m_child = child; } } while (m_child.isNull() || m_child->isFinished()); // we are blocked on m_child so it must be WaitableWaitHandle assert(dynamic_cast<c_WaitableWaitHandle*>(m_child.get())); blockOn(static_cast<c_WaitableWaitHandle*>(m_child.get())); } catch (Object exception) { // process exception thrown by generator or blockOn cycle detection markAsFailed(exception); } }
void tvCastToNullableObjectInPlace(TypedValue* tv) { if (IS_NULL_TYPE(tv->m_type)) { // XXX(t3879280) This happens immediately before calling an extension // function that takes an optional Object argument. We want to end up // passing const Object& holding nullptr, so by clearing out m_data.pobj we // can unconditionally treat &tv->m_data.pobj as a const Object& in the // function being called. This violates the invariant that the value of // m_data doesn't matter in a KindOfNull TypedValue. tv->m_data.pobj = nullptr; } else { tvCastToObjectInPlace(tv); } }
bool cellSame(const Cell* c1, const Cell* c2) { assert(cellIsPlausible(c1)); assert(cellIsPlausible(c2)); bool const null1 = IS_NULL_TYPE(c1->m_type); bool const null2 = IS_NULL_TYPE(c2->m_type); if (null1 && null2) return true; if (null1 || null2) return false; switch (c1->m_type) { case KindOfInt64: case KindOfBoolean: if (c2->m_type != c1->m_type) return false; return c1->m_data.num == c2->m_data.num; case KindOfDouble: if (c2->m_type != c1->m_type) return false; return c1->m_data.dbl == c2->m_data.dbl; case KindOfStaticString: case KindOfString: if (!IS_STRING_TYPE(c2->m_type)) return false; return c1->m_data.pstr->same(c2->m_data.pstr); case KindOfArray: if (c2->m_type != KindOfArray) return false; return c1->m_data.parr->equal(c2->m_data.parr, true); case KindOfObject: return c2->m_type == KindOfObject && c1->m_data.pobj == c2->m_data.pobj; default: break; } not_reached(); }
bool tvSame(const TypedValue* tv1, const TypedValue* tv2) { bool const null1 = IS_NULL_TYPE(tv1->m_type); bool const null2 = IS_NULL_TYPE(tv2->m_type); if (null1 && null2) return true; if (null1 || null2) return false; if (tv1->m_type == KindOfRef) tv1 = tv1->m_data.pref->tv(); if (tv2->m_type == KindOfRef) tv2 = tv2->m_data.pref->tv(); switch (tv1->m_type) { case KindOfInt64: case KindOfBoolean: if (tv2->m_type != tv1->m_type) return false; return tv1->m_data.num == tv2->m_data.num; case KindOfDouble: if (tv2->m_type != tv1->m_type) return false; return tv1->m_data.dbl == tv2->m_data.dbl; case KindOfStaticString: case KindOfString: if (!IS_STRING_TYPE(tv2->m_type)) return false; return tv1->m_data.pstr->same(tv2->m_data.pstr); case KindOfArray: if (tv2->m_type != KindOfArray) return false; return tv1->m_data.parr->equal(tv2->m_data.parr, true); case KindOfObject: return tv2->m_type == KindOfObject && tv1->m_data.pobj == tv2->m_data.pobj; default: break; } not_reached(); }
void c_GenArrayWaitHandle::onUnblocked() { for (; m_iterPos != ArrayData::invalid_index; m_iterPos = m_deps->iter_advance(m_iterPos)) { TypedValue* current = m_deps->nvGetValueRef(m_iterPos); if (IS_NULL_TYPE(current->m_type)) { // {uninit,null} yields null tvWriteNull(current); continue; } assert(current->m_type == KindOfObject); assert(dynamic_cast<c_WaitHandle*>(current->m_data.pobj)); auto child = static_cast<c_WaitHandle*>(current->m_data.pobj); if (child->isSucceeded()) { tvSetIgnoreRef(child->getResult(), current); } else if (child->isFailed()) { putException(m_exception, child->getException()); } else { assert(dynamic_cast<c_WaitableWaitHandle*>(child)); auto child_wh = static_cast<c_WaitableWaitHandle*>(child); try { blockOn(child_wh); return; } catch (const Object& cycle_exception) { putException(m_exception, cycle_exception.get()); } } } if (m_exception.isNull()) { TypedValue result; result.m_type = KindOfArray; result.m_data.parr = m_deps.get(); setResult(&result); m_deps = nullptr; } else { setException(m_exception.get()); m_exception = nullptr; m_deps = nullptr; } }
bool TypeConstraint::check(const TypedValue* tv, const Func* func) const { assert(hasConstraint()); // This is part of the interpreter runtime; perf matters. if (tv->m_type == KindOfRef) { tv = tv->m_data.pref->tv(); } if (nullable() && IS_NULL_TYPE(tv->m_type)) return true; if (tv->m_type == KindOfObject) { if (!isObjectOrTypedef()) return false; // Perfect match seems common enough to be worth skipping the hash // table lookup. if (m_typeName->isame(tv->m_data.pobj->getVMClass()->name())) { if (shouldProfile()) Class::profileInstanceOf(m_typeName); return true; } const Class *c = nullptr; const bool selfOrParentOrCallable = isSelf() || isParent() || isCallable(); if (selfOrParentOrCallable) { if (isSelf()) { selfToClass(func, &c); } else if (isParent()) { parentToClass(func, &c); } else { assert(isCallable()); return f_is_callable(tvAsCVarRef(tv)); } } else { // We can't save the Class* since it moves around from request // to request. assert(m_namedEntity); c = Unit::lookupClass(m_namedEntity); } if (shouldProfile() && c) { Class::profileInstanceOf(c->preClass()->name()); } if (c && tv->m_data.pobj->instanceof(c)) { return true; } return !selfOrParentOrCallable && checkTypedefObj(tv); } if (isObjectOrTypedef()) { switch (tv->m_type) { case KindOfArray: if (interface_supports_array(m_typeName)) { return true; } break; case KindOfString: case KindOfStaticString: if (interface_supports_string(m_typeName)) { return true; } break; case KindOfInt64: if (interface_supports_int(m_typeName)) { return true; } break; case KindOfDouble: if (interface_supports_double(m_typeName)) { return true; } break; default: break; } if (isCallable()) { return f_is_callable(tvAsCVarRef(tv)); } return isPrecise() && checkTypedefNonObj(tv); } return equivDataTypes(m_type.m_dt, tv->m_type); }