tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SyncedContextInlines.h (11809B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef mozilla_dom_SyncedContextInlines_h
      8 #define mozilla_dom_SyncedContextInlines_h
      9 
     10 #include "mozilla/dom/SyncedContext.h"
     11 #include "mozilla/dom/BrowsingContextGroup.h"
     12 #include "mozilla/dom/ContentParent.h"
     13 #include "mozilla/dom/ContentChild.h"
     14 #include "nsReadableUtils.h"
     15 #include "mozilla/HalIPCUtils.h"
     16 
     17 namespace mozilla {
     18 namespace dom {
     19 namespace syncedcontext {
     20 
     21 template <typename T>
     22 struct IsMozillaMaybe : std::false_type {};
     23 template <typename T>
     24 struct IsMozillaMaybe<Maybe<T>> : std::true_type {};
     25 
     26 // A super-sketchy logging only function for generating a human-readable version
     27 // of the value of a synced context field.
     28 template <typename T>
     29 void FormatFieldValue(nsACString& aStr, const T& aValue) {
     30  if constexpr (std::is_same_v<bool, T>) {
     31    if (aValue) {
     32      aStr.AppendLiteral("true");
     33    } else {
     34      aStr.AppendLiteral("false");
     35    }
     36  } else if constexpr (std::is_same_v<nsID, T>) {
     37    aStr.Append(nsIDToCString(aValue).get());
     38  } else if constexpr (std::is_integral_v<T>) {
     39    aStr.AppendInt(aValue);
     40  } else if constexpr (std::is_enum_v<T>) {
     41    aStr.AppendInt(static_cast<std::underlying_type_t<T>>(aValue));
     42  } else if constexpr (std::is_floating_point_v<T>) {
     43    aStr.AppendFloat(aValue);
     44  } else if constexpr (std::is_base_of_v<nsAString, T>) {
     45    AppendUTF16toUTF8(aValue, aStr);
     46  } else if constexpr (std::is_base_of_v<nsACString, T>) {
     47    aStr.Append(aValue);
     48  } else if constexpr (IsMozillaMaybe<T>::value) {
     49    if (aValue) {
     50      aStr.AppendLiteral("Some(");
     51      FormatFieldValue(aStr, aValue.ref());
     52      aStr.AppendLiteral(")");
     53    } else {
     54      aStr.AppendLiteral("Nothing");
     55    }
     56  } else {
     57    aStr.AppendLiteral("???");
     58  }
     59 }
     60 
     61 // Sketchy logging-only logic to generate a human-readable output of the actions
     62 // a synced context transaction is going to perform.
     63 template <typename Context>
     64 nsAutoCString FormatTransaction(
     65    typename Transaction<Context>::IndexSet aModified,
     66    const typename Context::FieldValues& aOldValues,
     67    const typename Context::FieldValues& aNewValues) {
     68  nsAutoCString result;
     69  Context::FieldValues::EachIndex([&](auto idx) {
     70    if (aModified.contains(idx)) {
     71      result.Append(Context::FieldIndexToName(idx));
     72      result.AppendLiteral("(");
     73      FormatFieldValue(result, aOldValues.Get(idx));
     74      result.AppendLiteral("->");
     75      FormatFieldValue(result, aNewValues.Get(idx));
     76      result.AppendLiteral(") ");
     77    }
     78  });
     79  return result;
     80 }
     81 
     82 template <typename Context>
     83 nsCString FormatValidationError(
     84    typename Transaction<Context>::IndexSet aFailedFields, const char* prefix) {
     85  MOZ_ASSERT(!aFailedFields.isEmpty());
     86  return nsDependentCString{prefix} +
     87         StringJoin(", "_ns, aFailedFields,
     88                    [](nsACString& dest, const auto& idx) {
     89                      dest.Append(Context::FieldIndexToName(idx));
     90                    });
     91 }
     92 
     93 template <typename Context>
     94 nsresult Transaction<Context>::Commit(Context* aOwner) {
     95  if (NS_WARN_IF(aOwner->IsDiscarded())) {
     96    return NS_ERROR_DOM_INVALID_STATE_ERR;
     97  }
     98 
     99  IndexSet failedFields = Validate(aOwner, nullptr);
    100  if (!failedFields.isEmpty()) {
    101    nsCString error = FormatValidationError<Context>(
    102        failedFields, "CanSet failed for field(s): ");
    103    MOZ_CRASH_UNSAFE_PRINTF("%s", error.get());
    104  }
    105 
    106  if (mModified.isEmpty()) {
    107    return NS_OK;
    108  }
    109 
    110  if (XRE_IsContentProcess()) {
    111    ContentChild* cc = ContentChild::GetSingleton();
    112 
    113    // Increment the field epoch for fields affected by this transaction.
    114    uint64_t epoch = cc->NextBrowsingContextFieldEpoch();
    115    EachIndex([&](auto idx) {
    116      if (mModified.contains(idx)) {
    117        FieldEpoch(idx, aOwner) = epoch;
    118      }
    119    });
    120 
    121    // Tell our derived class to send the correct "Commit" IPC message.
    122    aOwner->SendCommitTransaction(cc, *this, epoch);
    123  } else {
    124    MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
    125 
    126    // Tell our derived class to send the correct "Commit" IPC messages.
    127    BrowsingContextGroup* group = aOwner->Group();
    128    group->EachParent([&](ContentParent* aParent) {
    129      aOwner->SendCommitTransaction(aParent, *this,
    130                                    aParent->GetBrowsingContextFieldEpoch());
    131    });
    132  }
    133 
    134  Apply(aOwner, /* aFromIPC */ false);
    135  return NS_OK;
    136 }
    137 
    138 template <typename Context>
    139 mozilla::ipc::IPCResult Transaction<Context>::CommitFromIPC(
    140    const MaybeDiscarded<Context>& aOwner, ContentParent* aSource) {
    141  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
    142  if (aOwner.IsNullOrDiscarded()) {
    143    MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug,
    144            ("IPC: Trying to send a message to dead or detached context"));
    145    return IPC_OK();
    146  }
    147  Context* owner = aOwner.get();
    148 
    149  // Validate that the set from content is allowed before continuing.
    150  IndexSet failedFields = Validate(owner, aSource);
    151  if (!failedFields.isEmpty()) {
    152    nsCString error = FormatValidationError<Context>(
    153        failedFields,
    154        "Invalid Transaction from Child - CanSet failed for field(s): ");
    155    // data-review+ at https://bugzilla.mozilla.org/show_bug.cgi?id=1618992#c7
    156    return IPC_FAIL_UNSAFE_PRINTF(aSource, "%s", error.get());
    157  }
    158 
    159  // Validate may have dropped some fields from the transaction, check it's not
    160  // empty before continuing.
    161  if (mModified.isEmpty()) {
    162    return IPC_OK();
    163  }
    164 
    165  BrowsingContextGroup* group = owner->Group();
    166  group->EachOtherParent(aSource, [&](ContentParent* aParent) {
    167    owner->SendCommitTransaction(aParent, *this,
    168                                 aParent->GetBrowsingContextFieldEpoch());
    169  });
    170 
    171  Apply(owner, /* aFromIPC */ true);
    172  return IPC_OK();
    173 }
    174 
    175 template <typename Context>
    176 mozilla::ipc::IPCResult Transaction<Context>::CommitFromIPC(
    177    const MaybeDiscarded<Context>& aOwner, uint64_t aEpoch,
    178    ContentChild* aSource) {
    179  MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
    180  if (aOwner.IsNullOrDiscarded()) {
    181    MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug,
    182            ("ChildIPC: Trying to send a message to dead or detached context"));
    183    return IPC_OK();
    184  }
    185  Context* owner = aOwner.get();
    186 
    187  // Clear any fields which have been obsoleted by the epoch.
    188  EachIndex([&](auto idx) {
    189    if (mModified.contains(idx) && FieldEpoch(idx, owner) > aEpoch) {
    190      MOZ_LOG(
    191          Context::GetSyncLog(), LogLevel::Debug,
    192          ("Transaction::Obsoleted(#%" PRIx64 ", %" PRIu64 ">%" PRIu64 "): %s",
    193           owner->Id(), FieldEpoch(idx, owner), aEpoch,
    194           Context::FieldIndexToName(idx)));
    195      mModified -= idx;
    196    }
    197  });
    198 
    199  if (mModified.isEmpty()) {
    200    return IPC_OK();
    201  }
    202 
    203  Apply(owner, /* aFromIPC */ true);
    204  return IPC_OK();
    205 }
    206 
    207 template <typename Context>
    208 void Transaction<Context>::Apply(Context* aOwner, bool aFromIPC) {
    209  MOZ_ASSERT(!mModified.isEmpty());
    210 
    211  MOZ_LOG(
    212      Context::GetSyncLog(), LogLevel::Debug,
    213      ("Transaction::Apply(#%" PRIx64 ", %s): %s", aOwner->Id(),
    214       aFromIPC ? "ipc" : "local",
    215       FormatTransaction<Context>(mModified, aOwner->mFields.mValues, mValues)
    216           .get()));
    217 
    218  EachIndex([&](auto idx) {
    219    if (mModified.contains(idx)) {
    220      auto& txnField = mValues.Get(idx);
    221      auto& ownerField = aOwner->mFields.mValues.Get(idx);
    222      std::swap(ownerField, txnField);
    223      aOwner->DidSet(idx);
    224      aOwner->DidSet(idx, std::move(txnField));
    225    }
    226  });
    227  mModified.clear();
    228 }
    229 
    230 template <typename Context>
    231 void Transaction<Context>::CommitWithoutSyncing(Context* aOwner) {
    232  MOZ_LOG(
    233      Context::GetSyncLog(), LogLevel::Debug,
    234      ("Transaction::CommitWithoutSyncing(#%" PRIx64 "): %s", aOwner->Id(),
    235       FormatTransaction<Context>(mModified, aOwner->mFields.mValues, mValues)
    236           .get()));
    237 
    238  EachIndex([&](auto idx) {
    239    if (mModified.contains(idx)) {
    240      aOwner->mFields.mValues.Get(idx) = std::move(mValues.Get(idx));
    241    }
    242  });
    243  mModified.clear();
    244 }
    245 
    246 inline CanSetResult AsCanSetResult(CanSetResult aValue) { return aValue; }
    247 inline CanSetResult AsCanSetResult(bool aValue) {
    248  return aValue ? CanSetResult::Allow : CanSetResult::Deny;
    249 }
    250 
    251 template <typename Context>
    252 typename Transaction<Context>::IndexSet Transaction<Context>::Validate(
    253    Context* aOwner, ContentParent* aSource) {
    254  IndexSet failedFields;
    255  Transaction<Context> revertTxn;
    256 
    257  EachIndex([&](auto idx) {
    258    if (!mModified.contains(idx)) {
    259      return;
    260    }
    261 
    262    switch (AsCanSetResult(aOwner->CanSet(idx, mValues.Get(idx), aSource))) {
    263      case CanSetResult::Allow:
    264        break;
    265      case CanSetResult::Deny:
    266        failedFields += idx;
    267        break;
    268      case CanSetResult::Revert:
    269        revertTxn.mValues.Get(idx) = aOwner->mFields.mValues.Get(idx);
    270        revertTxn.mModified += idx;
    271        break;
    272    }
    273  });
    274 
    275  // If any changes need to be reverted, log them, remove them from this
    276  // transaction, and optionally send a message to the source process.
    277  if (!revertTxn.mModified.isEmpty()) {
    278    // NOTE: Logging with modified IndexSet from revert transaction, and values
    279    // from this transaction, so we log the failed values we're going to revert.
    280    MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug,
    281            ("Transaction::PartialRevert(#%" PRIx64 ", childid %d, pid %" PRIPID
    282             "): %s",
    283             aOwner->Id(), aSource ? aSource->OtherChildID() : -1,
    284             aSource ? aSource->OtherPid() : base::kInvalidProcessId,
    285             FormatTransaction<Context>(revertTxn.mModified, mValues,
    286                                        revertTxn.mValues)
    287                 .get()));
    288 
    289    mModified -= revertTxn.mModified;
    290 
    291    if (aSource) {
    292      aOwner->SendCommitTransaction(aSource, revertTxn,
    293                                    aSource->GetBrowsingContextFieldEpoch());
    294    }
    295  }
    296  return failedFields;
    297 }
    298 
    299 template <typename Context>
    300 void Transaction<Context>::Write(IPC::MessageWriter* aWriter) const {
    301  // Record which field indices will be included, and then write those fields
    302  // out.
    303  typename IndexSet::serializedType modified = mModified.serialize();
    304  IPC::WriteParam(aWriter, modified);
    305  EachIndex([&](auto idx) {
    306    if (mModified.contains(idx)) {
    307      IPC::WriteParam(aWriter, mValues.Get(idx));
    308    }
    309  });
    310 }
    311 
    312 template <typename Context>
    313 bool Transaction<Context>::Read(IPC::MessageReader* aReader) {
    314  // Read in which field indices were sent by the remote, followed by the fields
    315  // identified by those indices.
    316  typename IndexSet::serializedType modified =
    317      typename IndexSet::serializedType{};
    318  if (!IPC::ReadParam(aReader, &modified)) {
    319    return false;
    320  }
    321  mModified.deserialize(modified);
    322 
    323  bool ok = true;
    324  EachIndex([&](auto idx) {
    325    if (ok && mModified.contains(idx)) {
    326      ok = IPC::ReadParam(aReader, &mValues.Get(idx));
    327    }
    328  });
    329  return ok;
    330 }
    331 
    332 template <typename Base, size_t Count>
    333 void FieldValues<Base, Count>::Write(IPC::MessageWriter* aWriter) const {
    334  // XXX The this-> qualification is necessary to work around a bug in older gcc
    335  // versions causing an ICE.
    336  EachIndex([&](auto idx) { IPC::WriteParam(aWriter, this->Get(idx)); });
    337 }
    338 
    339 template <typename Base, size_t Count>
    340 bool FieldValues<Base, Count>::Read(IPC::MessageReader* aReader) {
    341  bool ok = true;
    342  EachIndex([&](auto idx) {
    343    if (ok) {
    344      // XXX The this-> qualification is necessary to work around a bug in older
    345      // gcc versions causing an ICE.
    346      ok = IPC::ReadParam(aReader, &this->Get(idx));
    347    }
    348  });
    349  return ok;
    350 }
    351 
    352 }  // namespace syncedcontext
    353 }  // namespace dom
    354 }  // namespace mozilla
    355 
    356 #endif  // !defined(mozilla_dom_SyncedContextInlines_h)