APCHandle* ConcurrentTableSharedStore::unserialize(const String& key, StoreValue* sval) { auto const sAddr = sval->data.right(); assert(sAddr != nullptr); try { auto const sType = apcExtension::EnableApcSerialize ? VariableUnserializer::Type::APCSerialize : VariableUnserializer::Type::Serialize; VariableUnserializer vu(sAddr, sval->getSerializedSize(), sType); Variant v; unserializeVariant(v, &vu); auto const pair = APCHandle::Create(v, sval->isSerializedObj()); sval->data = pair.handle; sval->dataSize = pair.size; APCStats::getAPCStats().addAPCValue(pair.handle, pair.size, true); return pair.handle; } catch (ResourceExceededException&) { throw; } catch (Exception& e) { raise_notice("APC Primed fetch failed: key %s (%s).", key.c_str(), e.getMessage().c_str()); return nullptr; } }
static void unserializeProp(VariableUnserializer* uns, ObjectData* obj, const String& key, Class* ctx, const String& realKey, int nProp) { // Do a two-step look up auto const lookup = obj->getProp(ctx, key.get()); Variant* t; if (!lookup.prop || !lookup.accessible) { // Dynamic property. If this is the first, and we're using MixedArray, // we need to pre-allocate space in the array to ensure the elements // dont move during unserialization. // // TODO(#2881866): this assumption means we can't do reallocations // when promoting kPackedKind -> kMixedKind. t = &obj->reserveProperties(nProp).lvalAt(realKey, AccessFlags::Key); } else { t = &tvAsVariant(lookup.prop); } if (UNLIKELY(IS_REFCOUNTED_TYPE(t->getRawType()))) { uns->putInOverwrittenList(*t); } unserializeVariant(*t, uns); if (!RuntimeOption::RepoAuthoritative) return; if (!Repo::get().global().HardPrivatePropInference) return; /* * We assume for performance reasons in repo authoriative mode that * we can see all the sets to private properties in a class. * * It's a hole in this if we don't check unserialization doesn't * violate what we've seen, which we handle by throwing if the repo * was built with this option. */ auto const cls = obj->getVMClass(); auto const slot = cls->lookupDeclProp(key.get()); if (UNLIKELY(slot == kInvalidSlot)) return; auto const repoTy = obj->getVMClass()->declPropRepoAuthType(slot); if (LIKELY(tvMatchesRepoAuthType(*t->asTypedValue(), repoTy))) { return; } auto msg = folly::format( "Property {} for class {} was deserialized with type ({}) that " "didn't match what we inferred in static analysis", key.data(), obj->getVMClass()->name()->data(), tname(t->asTypedValue()->m_type) ).str(); throw Exception(msg); }
Variant VariableUnserializer::unserialize() { Variant v; unserializeVariant(v, this); return v; }
void unserializeVariant(Variant& self, VariableUnserializer *uns, UnserializeMode mode /* = UnserializeMode::Value */) { // NOTE: If you make changes to how serialization and unserialization work, // make sure to update the reserialize() method in "runtime/ext/ext_apc.cpp" // and to update test_apc_reserialize() in "test/ext/test_ext_apc.cpp". char type = uns->readChar(); char sep = uns->readChar(); if (type != 'R') { uns->add(&self, mode); } if (type == 'N') { if (sep != ';') throw Exception("Expected ';' but got '%c'", sep); self.setNull(); // NULL *IS* the value, without we get undefined warnings return; } if (sep != ':') { throw Exception("Expected ':' but got '%c'", sep); } switch (type) { case 'r': { int64_t id = uns->readInt(); Variant *v = uns->getByVal(id); if (v == nullptr) { throw Exception("Id %" PRId64 " out of range", id); } self = *v; } break; case 'R': { int64_t id = uns->readInt(); Variant *v = uns->getByRef(id); if (v == nullptr) { throw Exception("Id %" PRId64 " out of range", id); } self.assignRef(*v); } break; case 'b': { int64_t v = uns->readInt(); self = (bool)v; } break; case 'i': { int64_t v = uns->readInt(); self = v; } break; case 'd': { double v; char ch = uns->peek(); bool negative = false; char buf[4]; if (ch == '-') { negative = true; ch = uns->readChar(); ch = uns->peek(); } if (ch == 'I') { uns->read(buf, 3); buf[3] = '\0'; if (strcmp(buf, "INF")) { throw Exception("Expected 'INF' but got '%s'", buf); } v = atof("inf"); } else if (ch == 'N') { uns->read(buf, 3); buf[3] = '\0'; if (strcmp(buf, "NAN")) { throw Exception("Expected 'NAN' but got '%s'", buf); } v = atof("nan"); } else { v = uns->readDouble(); } self = negative ? -v : v; } break; case 's': { String v; v.unserialize(uns); self = std::move(v); if (!uns->endOfBuffer()) { // Semicolon *should* always be required, // but PHP's implementation allows omitting it // and still functioning. // Worse, it throws it away without any check. // So we'll do the same. Sigh. uns->readChar(); } } return; case 'S': if (uns->type() == VariableUnserializer::Type::APCSerialize) { union { char buf[8]; StringData *sd; } u; uns->read(u.buf, 8); self = u.sd; } else { throw Exception("Unknown type '%c'", type); } break; case 'a': { // Check stack depth to avoid overflow. check_recursion_throw(); auto v = Array::Create(); v.unserialize(uns); self = std::move(v); } return; // array has '}' terminating case 'L': { int64_t id = uns->readInt(); uns->expectChar(':'); String rsrcName; rsrcName.unserialize(uns); uns->expectChar('{'); uns->expectChar('}'); auto rsrc = makeSmartPtr<DummyResource>(); rsrc->o_setResourceId(id); rsrc->m_class_name = rsrcName; self = std::move(rsrc); } return; // resource has '}' terminating case 'O': case 'V': case 'K': { String clsName; clsName.unserialize(uns); uns->expectChar(':'); int64_t size = uns->readInt(); uns->expectChar(':'); uns->expectChar('{'); const bool allowObjectFormatForCollections = true; Class* cls; // If we are potentially dealing with a collection, we need to try to // load the collection class under an alternate name so that we can // deserialize data that was serialized before the migration of // collections to the HH namespace. if (type != 'O') { // Collections are CPP builtins; don't attempt to autoload cls = Unit::getClass(clsName.get(), /* autoload */ false); if (!cls) { cls = tryAlternateCollectionClass(clsName.get()); } } else if (allowObjectFormatForCollections) { // In order to support the legacy {O|V}:{Set|Vector|Map} // serialization, we defer autoloading until we know that there's // no alternate (builtin) collection class. cls = Unit::getClass(clsName.get(), /* autoload */ false); if (!cls) { cls = tryAlternateCollectionClass(clsName.get()); } if (!cls) { cls = Unit::loadClass(clsName.get()); // with autoloading } } else { cls = Unit::loadClass(clsName.get()); // with autoloading } Object obj; if (RuntimeOption::UnserializationWhitelistCheck && (type == 'O') && !uns->isWhitelistedClass(clsName)) { const char* err_msg = "The object being unserialized with class name '%s' " "is not in the given whitelist. " "See http://fburl.com/SafeSerializable for more detail"; if (RuntimeOption::UnserializationWhitelistCheckWarningOnly) { raise_warning(err_msg, clsName.c_str()); } else { raise_error(err_msg, clsName.c_str()); } } if (cls) { // Only unserialize CPP extension types which can actually // support it. Otherwise, we risk creating a CPP object // without having it initialized completely. if (cls->instanceCtor() && !cls->isCppSerializable()) { assert(obj.isNull()); throw_null_pointer_exception(); } else { obj = Object{cls}; if (UNLIKELY(collections::isType(cls, CollectionType::Pair) && (size != 2))) { throw Exception("Pair objects must have exactly 2 elements"); } } } else { obj = Object{SystemLib::s___PHP_Incomplete_ClassClass}; obj->o_set(s_PHP_Incomplete_Class_Name, clsName); } assert(!obj.isNull()); self = obj; if (size > 0) { // Check stack depth to avoid overflow. check_recursion_throw(); if (type == 'O') { // Collections are not allowed if (obj->isCollection()) { throw Exception("%s does not support the 'O' serialization " "format", clsName.data()); } Variant serializedNativeData = init_null(); bool hasSerializedNativeData = false; /* Count backwards so that i is the number of properties remaining (to be used as an estimate for the total number of dynamic properties when we see the first dynamic prop). see getVariantPtr */ for (int64_t i = size; i--; ) { Variant v; unserializeVariant(v, uns, UnserializeMode::Key); String key = v.toString(); int ksize = key.size(); const char *kdata = key.data(); int subLen = 0; if (key == ObjectData::s_serializedNativeDataKey) { unserializeVariant(serializedNativeData, uns); hasSerializedNativeData = true; } else if (kdata[0] == '\0') { if (UNLIKELY(!ksize)) { raise_error("Cannot access empty property"); } // private or protected subLen = strlen(kdata + 1) + 2; if (UNLIKELY(subLen >= ksize)) { if (subLen == ksize) { raise_error("Cannot access empty property"); } else { throw Exception("Mangled private object property"); } } String k(kdata + subLen, ksize - subLen, CopyString); Class* ctx = (Class*)-1; if (kdata[1] != '*') { ctx = Unit::lookupClass( String(kdata + 1, subLen - 2, CopyString).get()); } unserializeProp(uns, obj.get(), k, ctx, key, i + 1); } else { unserializeProp(uns, obj.get(), key, nullptr, key, i + 1); } if (i > 0) { auto lastChar = uns->peekBack(); if ((lastChar != ';') && (lastChar != '}')) { throw Exception("Object property not terminated properly"); } } } // nativeDataWakeup is called last to ensure that all properties are // already unserialized. We also ensure that nativeDataWakeup is // invoked regardless of whether or not serialized native data exists // within the serialized content. if (obj->getAttribute(ObjectData::HasNativeData) && obj->getVMClass()->getNativeDataInfo()->isSerializable()) { Native::nativeDataWakeup(obj.get(), serializedNativeData); } else if (hasSerializedNativeData) { raise_warning("%s does not expect any serialized native data.", clsName.data()); } } else { assert(type == 'V' || type == 'K'); if (!obj->isCollection()) { throw Exception("%s is not a collection class", clsName.data()); } collections::unserialize(obj.get(), uns, size, type); } } uns->expectChar('}'); if (uns->type() != VariableUnserializer::Type::DebuggerSerialize || (cls && cls->instanceCtor() && cls->isCppSerializable())) { // Don't call wakeup when unserializing for the debugger, except for // natively implemented classes. obj->invokeWakeup(); } check_request_surprise_unlikely(); } return; // object has '}' terminating case 'C': { if (uns->type() == VariableUnserializer::Type::DebuggerSerialize) { raise_error("Debugger shouldn't call custom unserialize method"); } String clsName; clsName.unserialize(uns); uns->expectChar(':'); String serialized; serialized.unserialize(uns, '{', '}'); auto const obj = [&]() -> Object { if (auto const cls = Unit::loadClass(clsName.get())) { return Object::attach(g_context->createObject(cls, init_null_variant, false /* init */)); } if (!uns->allowUnknownSerializableClass()) { raise_error("unknown class %s", clsName.data()); } Object ret = create_object_only(s_PHP_Incomplete_Class); ret->o_set(s_PHP_Incomplete_Class_Name, clsName); ret->o_set("serialized", serialized); return ret; }(); if (!obj->instanceof(SystemLib::s_SerializableClass)) { raise_warning("Class %s has no unserializer", obj->getClassName().data()); } else { obj->o_invoke_few_args(s_unserialize, 1, serialized); obj.get()->clearNoDestruct(); } self = std::move(obj); } return; // object has '}' terminating default: throw Exception("Unknown type '%c'", type); } uns->expectChar(';'); }