Files
react-native/ReactCommon/react/renderer/core/RawPropsParser.cpp
T
Joshua Gross 77accf2380 Log more diagnostics when JSI values cannot be cast to Object
Summary:
There are a few places where we cast JSI values to objects without much validation and without proper error logging, and in some places the crashes aren't symbolicated well. To make debugging easier in the short-term, I'm adding some additional logs.

Changelog: [Internal]

Reviewed By: mdvacca, RSNara

Differential Revision: D23033222

fbshipit-source-id: 9343d693a441f0af728e560a0c245bcc4eb97869
2020-08-10 16:08:50 -07:00

159 lines
5.5 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RawPropsParser.h"
#include <folly/Likely.h>
#include <react/renderer/core/RawProps.h>
#include <glog/logging.h>
namespace facebook {
namespace react {
// During parser initialization, Props structs are used to parse
// "fake"/empty objects, and `at` is called repeatedly which tells us
// which props are accessed during parsing, and in which order.
RawValue const *RawPropsParser::at(
RawProps const &rawProps,
RawPropsKey const &key) const noexcept {
if (UNLIKELY(!ready_)) {
// Check against the same key being inserted more than once.
// This happens commonly with nested Props structs, where the higher-level
// struct may access all fields, and then the nested Props struct may
// access fields a second (or third, etc) time.
// Without this, multiple entries will be created for the same key, but
// only the first access of the key will return a sensible value.
// The complexity of this is (n + (n - 1) + (n - 2) + ... + (n - (n - 1) +
// 1))) or n*n - (1/2)(n*(n+1)). If there are 100 props, this will result in
// 4950 lookups and equality checks on initialization of the parser, which
// happens exactly once per component.
for (int i = 0; i < size_; i++) {
if (keys_[i] == key) {
return nullptr;
}
}
// This is not thread-safe part; this happens only during initialization of
// a `ComponentDescriptor` where it is actually safe.
keys_.push_back(key);
nameToIndex_.insert(key, size_);
size_++;
return nullptr;
}
// Normally, keys are looked up in-order. For performance we can simply
// increment this key counter, and if the key is equal to the key at the next
// index, there's no need to do any lookups. However, it's possible for keys
// to be accessed out-of-order or multiple times, in which case we start
// searching again from index 0.
// To prevent infinite loops (which can occur if
// you look up a key that doesn't exist) we keep track of whether or not we've
// already looped around, and log and return nullptr if so. However, we ONLY
// do this in debug mode, where you're more likely to look up a nonexistent
// key as part of debugging. You can (and must) ensure infinite loops are not
// possible in production by: (1) constructing all props objects without
// conditionals, or (2) if there are conditionals, ensure that in the parsing
// setup case, the Props constructor will access _all_ possible props. To
// ensure this performance optimization is utilized, always access props in
// the same order every time. This is trivial if you have a simple Props
// constructor, but difficult or impossible if you have a shared sub-prop
// Struct that is used by multiple parent Props.
#ifndef NDEBUG
bool resetLoop = false;
#endif
do {
rawProps.keyIndexCursor_++;
if (UNLIKELY(rawProps.keyIndexCursor_ >= size_)) {
#ifndef NDEBUG
if (resetLoop) {
LOG(ERROR) << "Looked up RawProps key that does not exist: "
<< (std::string)key;
return nullptr;
}
resetLoop = true;
#endif
rawProps.keyIndexCursor_ = 0;
}
} while (UNLIKELY(key != keys_[rawProps.keyIndexCursor_]));
auto valueIndex = rawProps.keyIndexToValueIndex_[rawProps.keyIndexCursor_];
return valueIndex == kRawPropsValueIndexEmpty ? nullptr
: &rawProps.values_[valueIndex];
}
void RawPropsParser::postPrepare() noexcept {
ready_ = true;
nameToIndex_.reindex();
}
void RawPropsParser::preparse(RawProps const &rawProps) const noexcept {
rawProps.keyIndexToValueIndex_.resize(size_, kRawPropsValueIndexEmpty);
// Resetting the cursor, the next increment will give `0`.
rawProps.keyIndexCursor_ = size_ - 1;
switch (rawProps.mode_) {
case RawProps::Mode::Empty:
return;
case RawProps::Mode::JSI: {
auto &runtime = *rawProps.runtime_;
if (!rawProps.value_.isObject()) {
LOG(ERROR) << "Preparse props: rawProps value is not object";
}
assert(rawProps.value_.isObject());
auto object = rawProps.value_.asObject(runtime);
auto names = object.getPropertyNames(runtime);
auto count = names.size(runtime);
auto valueIndex = RawPropsValueIndex{0};
for (auto i = 0; i < count; i++) {
auto nameValue = names.getValueAtIndex(runtime, i).getString(runtime);
auto value = object.getProperty(runtime, nameValue);
auto name = nameValue.utf8(runtime);
auto keyIndex = nameToIndex_.at(name.data(), name.size());
if (keyIndex == kRawPropsValueIndexEmpty) {
continue;
}
rawProps.keyIndexToValueIndex_[keyIndex] = valueIndex;
rawProps.values_.push_back(
RawValue(jsi::dynamicFromValue(runtime, value)));
valueIndex++;
}
break;
}
case RawProps::Mode::Dynamic: {
auto const &dynamic = rawProps.dynamic_;
auto valueIndex = RawPropsValueIndex{0};
for (auto const &pair : dynamic.items()) {
auto name = pair.first.getString();
auto keyIndex = nameToIndex_.at(name.data(), name.size());
if (keyIndex == kRawPropsValueIndexEmpty) {
continue;
}
rawProps.keyIndexToValueIndex_[keyIndex] = valueIndex;
rawProps.values_.push_back(RawValue{pair.second});
valueIndex++;
}
break;
}
}
}
} // namespace react
} // namespace facebook