/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: sw=2 ts=4 et : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef _QUEUEPARAMTRAITS_H_ #define _QUEUEPARAMTRAITS_H_ 1 #include "ipc/EnumSerializer.h" #include "mozilla/gfx/2D.h" #include "mozilla/Assertions.h" #include "mozilla/IntegerRange.h" #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/ipc/SharedMemoryBasic.h" #include "mozilla/Logging.h" #include "mozilla/TimeStamp.h" #include "nsExceptionHandler.h" #include "nsString.h" #include "WebGLTypes.h" #include namespace mozilla::webgl { template struct RemoveCVR { using Type = typename std::remove_reference::type>::type; }; /** * QueueParamTraits provide the user with a way to implement PCQ argument * (de)serialization. It uses a PcqView, which permits the system to * abandon all changes to the underlying PCQ if any operation fails. * * The transactional nature of PCQ operations make the ideal behavior a bit * complex. Since the PCQ has a fixed amount of memory available to it, * TryInsert operations operations are expected to sometimes fail and be * re-issued later. We want these failures to be inexpensive. The same * goes for TryRemove, which fails when there isn't enough data in * the queue yet for them to complete. * * Their expected interface is: * * template<> struct QueueParamTraits::Type> { * // Write data from aArg into the PCQ. * static QueueStatus Write(ProducerView& aProducerView, const Arg& aArg) * {...}; * * // Read data from the PCQ into aArg, or just skip the data if aArg is null. * static QueueStatus Read(ConsumerView& aConsumerView, Arg* aArg) {...} * }; */ template struct QueueParamTraits; // Todo: s/QueueParamTraits/SizedParamTraits/ template inline Range AsRange(T* const begin, T* const end) { const auto size = MaybeAs(end - begin); MOZ_RELEASE_ASSERT(size); return {begin, *size}; } // - // BytesAlwaysValidT template struct BytesAlwaysValidT { using non_cv = typename std::remove_cv::type; static constexpr bool value = std::is_arithmetic::value && !std::is_same::value; }; static_assert(BytesAlwaysValidT::value); static_assert(!BytesAlwaysValidT::value); static_assert(!BytesAlwaysValidT::value); static_assert(!BytesAlwaysValidT::value); static_assert(BytesAlwaysValidT::value); template struct BytesAlwaysValidT> { static constexpr bool value = BytesAlwaysValidT::value; }; static_assert(BytesAlwaysValidT>::value); static_assert(!BytesAlwaysValidT>::value); template struct BytesAlwaysValidT { static constexpr bool value = BytesAlwaysValidT::value; }; static_assert(BytesAlwaysValidT::value); static_assert(!BytesAlwaysValidT::value); // - template <> struct BytesAlwaysValidT { static constexpr bool value = true; }; // - /** * Used to give QueueParamTraits a way to write to the Producer without * actually altering it, in case the transaction fails. * THis object maintains the error state of the transaction and * discards commands issued after an error is encountered. */ template class ProducerView { public: using Producer = _Producer; explicit ProducerView(Producer* aProducer) : mProducer(aProducer) {} template bool WriteFromRange(const Range& src) { static_assert(BytesAlwaysValidT::value); if (MOZ_LIKELY(mOk)) { mOk &= mProducer->WriteFromRange(src); } return mOk; } /** * Copy bytes from aBuffer to the producer if there is enough room. * aBufferSize must not be 0. */ template inline bool Write(const T* begin, const T* end) { MOZ_RELEASE_ASSERT(begin <= end); return WriteFromRange(AsRange(begin, end)); } /** * Serialize aArg using Arg's QueueParamTraits. */ template bool WriteParam(const Arg& aArg) { return mozilla::webgl::QueueParamTraits< typename RemoveCVR::Type>::Write(*this, aArg); } bool Ok() const { return mOk; } private: Producer* const mProducer; bool mOk = true; }; /** * Used to give QueueParamTraits a way to read from the Consumer without * actually altering it, in case the transaction fails. */ template class ConsumerView { public: using Consumer = _Consumer; explicit ConsumerView(Consumer* aConsumer) : mConsumer(aConsumer) {} /** * Read bytes from the consumer if there is enough data. aBuffer may * be null (in which case the data is skipped) */ template inline bool Read(T* const destBegin, T* const destEnd) { MOZ_ASSERT(destBegin); MOZ_RELEASE_ASSERT(destBegin <= destEnd); const auto dest = AsRange(destBegin, destEnd); const auto view = ReadRange(dest.length()); if (MOZ_LIKELY(view)) { const auto byteSize = ByteSize(dest); if (MOZ_LIKELY(byteSize)) { memcpy(dest.begin().get(), view->begin().get(), byteSize); } } return mOk; } /// Return a view wrapping the shmem. template inline Maybe> ReadRange(const size_t elemCount) { static_assert(BytesAlwaysValidT::value); if (MOZ_UNLIKELY(!mOk)) return {}; const auto view = mConsumer->template ReadRange(elemCount); mOk &= bool(view); return view; } /** * Deserialize aArg using Arg's QueueParamTraits. * If the return value is not Success then aArg is not changed. */ template bool ReadParam(Arg* aArg) { MOZ_ASSERT(aArg); return mozilla::webgl::QueueParamTraits>::Read(*this, aArg); } bool Ok() const { return mOk; } private: Consumer* const mConsumer; bool mOk = true; }; // - template struct QueueParamTraits { template static bool Write(ProducerView& aProducerView, const Arg& aArg) { static_assert(BytesAlwaysValidT::value, "No QueueParamTraits specialization was found for this type " "and it does not satisfy BytesAlwaysValid."); // Write self as binary const auto pArg = &aArg; return aProducerView.Write(pArg, pArg + 1); } template static bool Read(ConsumerView& aConsumerView, Arg* aArg) { static_assert(BytesAlwaysValidT::value, "No QueueParamTraits specialization was found for this type " "and it does not satisfy BytesAlwaysValid."); // Read self as binary return aConsumerView.Read(aArg, aArg + 1); } }; // --------------------------------------------------------------- template <> struct QueueParamTraits { using ParamType = bool; template static auto Write(ProducerView& aProducerView, const ParamType& aArg) { uint8_t temp = aArg ? 1 : 0; return aProducerView.WriteParam(temp); } template static auto Read(ConsumerView& aConsumerView, ParamType* aArg) { uint8_t temp; if (aConsumerView.ReadParam(&temp)) { MOZ_ASSERT(temp == 1 || temp == 0); *aArg = temp ? true : false; } return aConsumerView.Ok(); } }; // --------------------------------------------------------------- template Maybe AsValidEnum(const std::underlying_type_t raw_val) { const auto raw_enum = T{raw_val}; // This is the risk we prevent! if (!IsEnumCase(raw_enum)) return {}; return Some(raw_enum); } // - template struct QueueParamTraits_IsEnumCase { template static bool Write(ProducerView& aProducerView, const T& aArg) { MOZ_ASSERT(IsEnumCase(aArg)); const auto shadow = static_cast>(aArg); aProducerView.WriteParam(shadow); return true; } template static bool Read(ConsumerView& aConsumerView, T* aArg) { auto shadow = std::underlying_type_t{}; aConsumerView.ReadParam(&shadow); const auto e = AsValidEnum(shadow); if (!e) return false; *aArg = *e; return true; } }; // --------------------------------------------------------------- // We guarantee our robustness via these requirements: // * Object.MutTiedFields() gives us a tuple, // * where the combined sizeofs all field types sums to sizeof(Object), // * (thus we know we are exhaustively listing all fields) // * where feeding each field back into ParamTraits succeeds, // * and ParamTraits is only automated for BytesAlwaysValidT types. // (BytesAlwaysValidT rejects bool and enum types, and only accepts int/float // types, or array or std::arrays of such types) // (Yes, bit-field fields are rejected by MutTiedFields too) template struct QueueParamTraits_TiedFields { template static bool Write(ProducerView& aProducerView, const T& aArg) { const auto fields = TiedFields(aArg); static_assert(AreAllBytesTiedFields(), "Are there missing fields or padding between fields?"); bool ok = true; MapTuple(fields, [&](const auto& field) { ok &= aProducerView.WriteParam(field); return true; }); return ok; } template static bool Read(ConsumerView& aConsumerView, T* aArg) { const auto fields = TiedFields(*aArg); static_assert(AreAllBytesTiedFields()); bool ok = true; MapTuple(fields, [&](auto& field) { ok &= aConsumerView.ReadParam(&field); return true; }); return ok; } }; // --------------------------------------------------------------- // Adapted from IPC::EnumSerializer, this class safely handles enum values, // validating that they are in range using the same EnumValidators as IPDL // (namely ContiguousEnumValidator and ContiguousEnumValidatorInclusive). template struct EnumSerializer { using ParamType = E; using DataType = typename std::underlying_type::type; template static auto Write(ProducerView& aProducerView, const ParamType& aValue) { MOZ_RELEASE_ASSERT( EnumValidator::IsLegalValue(static_cast(aValue))); return aProducerView.WriteParam(DataType(aValue)); } template static bool Read(ConsumerView& aConsumerView, ParamType* aResult) { DataType value; if (!aConsumerView.ReadParam(&value)) { CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns); return false; } if (!EnumValidator::IsLegalValue(static_cast(value))) { CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"_ns); return false; } *aResult = ParamType(value); return true; } }; using IPC::ContiguousEnumValidator; using IPC::ContiguousEnumValidatorInclusive; template struct ContiguousEnumSerializer : EnumSerializer> {}; template struct ContiguousEnumSerializerInclusive : EnumSerializer> { }; // --------------------------------------------------------------- template <> struct QueueParamTraits { using ParamType = webgl::TexUnpackBlobDesc; template static bool Write(ProducerView& view, const ParamType& in) { MOZ_RELEASE_ASSERT(!in.image); MOZ_RELEASE_ASSERT(!in.sd); const bool isDataSurf = bool(in.dataSurf); if (!view.WriteParam(in.imageTarget) || !view.WriteParam(in.size) || !view.WriteParam(in.srcAlphaType) || !view.WriteParam(in.unpacking) || !view.WriteParam(in.cpuData) || !view.WriteParam(in.pboOffset) || !view.WriteParam(in.structuredSrcSize) || !view.WriteParam(in.applyUnpackTransforms) || !view.WriteParam(isDataSurf)) { return false; } if (isDataSurf) { const auto& surf = in.dataSurf; gfx::DataSourceSurface::ScopedMap map(surf, gfx::DataSourceSurface::READ); if (!map.IsMapped()) { return false; } const auto& surfSize = surf->GetSize(); const auto stride = *MaybeAs(map.GetStride()); if (!view.WriteParam(surfSize) || !view.WriteParam(surf->GetFormat()) || !view.WriteParam(stride)) { return false; } const size_t dataSize = stride * surfSize.height; const auto& begin = map.GetData(); const auto range = Range{begin, dataSize}; if (!view.WriteFromRange(range)) { return false; } } return true; } template static bool Read(ConsumerView& view, ParamType* const out) { bool isDataSurf; if (!view.ReadParam(&out->imageTarget) || !view.ReadParam(&out->size) || !view.ReadParam(&out->srcAlphaType) || !view.ReadParam(&out->unpacking) || !view.ReadParam(&out->cpuData) || !view.ReadParam(&out->pboOffset) || !view.ReadParam(&out->structuredSrcSize) || !view.ReadParam(&out->applyUnpackTransforms) || !view.ReadParam(&isDataSurf)) { return false; } if (isDataSurf) { gfx::IntSize surfSize; gfx::SurfaceFormat format; size_t stride; if (!view.ReadParam(&surfSize) || !view.ReadParam(&format) || !view.ReadParam(&stride)) { return false; } const size_t dataSize = stride * surfSize.height; const auto range = view.template ReadRange(dataSize); if (!range) return false; // DataSourceSurface demands pointer-to-mutable. const auto bytes = const_cast(range->begin().get()); out->dataSurf = gfx::Factory::CreateWrappingDataSourceSurface( bytes, stride, surfSize, format); MOZ_ASSERT(out->dataSurf); } return true; } }; // --------------------------------------------------------------- template <> struct QueueParamTraits { using ParamType = nsACString; template static bool Write(ProducerView& aProducerView, const ParamType& aArg) { if ((!aProducerView.WriteParam(aArg.IsVoid())) || aArg.IsVoid()) { return false; } uint32_t len = aArg.Length(); if ((!aProducerView.WriteParam(len)) || (len == 0)) { return false; } return aProducerView.Write(aArg.BeginReading(), len); } template static bool Read(ConsumerView& aConsumerView, ParamType* aArg) { bool isVoid = false; if (!aConsumerView.ReadParam(&isVoid)) { return false; } aArg->SetIsVoid(isVoid); if (isVoid) { return true; } uint32_t len = 0; if (!aConsumerView.ReadParam(&len)) { return false; } if (len == 0) { *aArg = ""; return true; } char* buf = new char[len + 1]; if (!buf) { return false; } if (!aConsumerView.Read(buf, len)) { return false; } buf[len] = '\0'; aArg->Adopt(buf, len); return true; } }; template <> struct QueueParamTraits { using ParamType = nsAString; template static bool Write(ProducerView& aProducerView, const ParamType& aArg) { if ((!aProducerView.WriteParam(aArg.IsVoid())) || (aArg.IsVoid())) { return false; } // DLP: No idea if this includes null terminator uint32_t len = aArg.Length(); if ((!aProducerView.WriteParam(len)) || (len == 0)) { return false; } constexpr const uint32_t sizeofchar = sizeof(typename ParamType::char_type); return aProducerView.Write(aArg.BeginReading(), len * sizeofchar); } template static bool Read(ConsumerView& aConsumerView, ParamType* aArg) { bool isVoid = false; if (!aConsumerView.ReadParam(&isVoid)) { return false; } aArg->SetIsVoid(isVoid); if (isVoid) { return true; } // DLP: No idea if this includes null terminator uint32_t len = 0; if (!aConsumerView.ReadParam(&len)) { return false; } if (len == 0) { *aArg = nsString(); return true; } uint32_t sizeofchar = sizeof(typename ParamType::char_type); typename ParamType::char_type* buf = nullptr; buf = static_cast( malloc((len + 1) * sizeofchar)); if (!buf) { return false; } if (!aConsumerView.Read(buf, len * sizeofchar)) { return false; } buf[len] = L'\0'; aArg->Adopt(buf, len); return true; } }; template <> struct QueueParamTraits : public QueueParamTraits { using ParamType = nsCString; }; template <> struct QueueParamTraits : public QueueParamTraits { using ParamType = nsString; }; // --------------------------------------------------------------- template ::value> struct NSArrayQueueParamTraits; // For ElementTypes that are !BytesAlwaysValidT template struct NSArrayQueueParamTraits, false> { using ElementType = _ElementType; using ParamType = nsTArray; template static bool Write(ProducerView& aProducerView, const ParamType& aArg) { aProducerView.WriteParam(aArg.Length()); for (auto& elt : aArg) { aProducerView.WriteParam(elt); } return true; } template static bool Read(ConsumerView& aConsumerView, ParamType* aArg) { size_t arrayLen; if (!aConsumerView.ReadParam(&arrayLen)) { return false; } if (!aArg->AppendElements(arrayLen, fallible)) { return false; } for (auto i : IntegerRange(arrayLen)) { ElementType& elt = aArg->ElementAt(i); aConsumerView.ReadParam(elt); } return aConsumerView.Ok(); } }; // For ElementTypes that are BytesAlwaysValidT template struct NSArrayQueueParamTraits, true> { using ElementType = _ElementType; using ParamType = nsTArray; // TODO: Are there alignment issues? template static bool Write(ProducerView& aProducerView, const ParamType& aArg) { size_t arrayLen = aArg.Length(); aProducerView.WriteParam(arrayLen); return aProducerView.Write(&aArg[0], aArg.Length() * sizeof(ElementType)); } template static bool Read(ConsumerView& aConsumerView, ParamType* aArg) { size_t arrayLen; if (!aConsumerView.ReadParam(&arrayLen)) { return false; } if (!aArg->AppendElements(arrayLen, fallible)) { return false; } return aConsumerView.Read(aArg->Elements(), arrayLen * sizeof(ElementType)); } }; template struct QueueParamTraits> : public NSArrayQueueParamTraits> { using ParamType = nsTArray; }; // --------------------------------------------------------------- template ::value> struct ArrayQueueParamTraits; // For ElementTypes that are !BytesAlwaysValidT template struct ArrayQueueParamTraits, false> { using ElementType = _ElementType; using ParamType = Array; template static auto Write(ProducerView& aProducerView, const ParamType& aArg) { for (const auto& elt : aArg) { aProducerView.WriteParam(elt); } return aProducerView.Ok(); } template static auto Read(ConsumerView& aConsumerView, ParamType* aArg) { for (auto& elt : *aArg) { aConsumerView.ReadParam(elt); } return aConsumerView.Ok(); } }; // For ElementTypes that are BytesAlwaysValidT template struct ArrayQueueParamTraits, true> { using ElementType = _ElementType; using ParamType = Array; template static auto Write(ProducerView& aProducerView, const ParamType& aArg) { return aProducerView.Write(aArg.begin(), sizeof(ElementType[Length])); } template static auto Read(ConsumerView& aConsumerView, ParamType* aArg) { return aConsumerView.Read(aArg->begin(), sizeof(ElementType[Length])); } }; template struct QueueParamTraits> : public ArrayQueueParamTraits> { using ParamType = Array; }; // --------------------------------------------------------------- template struct QueueParamTraits> { using ParamType = Maybe; template static bool Write(ProducerView& aProducerView, const ParamType& aArg) { aProducerView.WriteParam(static_cast(aArg)); if (aArg) { aProducerView.WriteParam(aArg.ref()); } return aProducerView.Ok(); } template static bool Read(ConsumerView& aConsumerView, ParamType* aArg) { bool isSome; if (!aConsumerView.ReadParam(&isSome)) { return false; } if (!isSome) { aArg->reset(); return true; } aArg->emplace(); return aConsumerView.ReadParam(aArg->ptr()); } }; // --------------------------------------------------------------- template struct QueueParamTraits> { using ParamType = std::pair; template static bool Write(ProducerView& aProducerView, const ParamType& aArg) { aProducerView.WriteParam(aArg.first()); return aProducerView.WriteParam(aArg.second()); } template static bool Read(ConsumerView& aConsumerView, ParamType* aArg) { aConsumerView.ReadParam(aArg->first()); return aConsumerView.ReadParam(aArg->second()); } }; } // namespace mozilla::webgl #endif // _QUEUEPARAMTRAITS_H_