Skip to content

Commit

Permalink
_log for complex types (#4857)
Browse files Browse the repository at this point in the history
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

## Summary

This PR expands the logic of _log function to print more complex types
in JSON-like style. It adds support for the types listed below:

- JSI types
- JS Objects and Collections

## Test plan

<!-- Provide a minimal but complete code snippet that can be used to
test out this change along with instructions how to run it and a
description of the expected behavior. -->

You can use and example app named `Log test`

---------

Co-authored-by: Michał Mąka <62388446+michalmaka@users.noreply.github.com>
Co-authored-by: Tomek Zawadzki <tomekzawadzki98@gmail.com>
  • Loading branch information
3 people committed Aug 8, 2023
1 parent 639f2bc commit 5dac63a
Show file tree
Hide file tree
Showing 14 changed files with 553 additions and 17 deletions.
340 changes: 340 additions & 0 deletions Common/cpp/Tools/JSISerializer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
#include "JSISerializer.h"

#include <cxxabi.h>
#include <iostream>
#include <sstream>

const std::vector<std::string> SUPPORTED_ERROR_TYPES = {
"Error",
"AggregateError",
"EvalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError",
"InternalError"};

const std::vector<std::string> SUPPORTED_INDEXED_COLLECTION_TYPES = {
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"BigInt64Array",
"BigUint64Array",
"Float32Array",
"Float64Array",
};

const std::vector<std::string> SUPPORTED_STRUCTURED_DATA_TYPES = {
"ArrayBuffer",
"SharedArrayBuffer",
"DataView",
"Atomics",
"JSON",
};

const std::vector<std::string> SUPPORTED_MANAGING_MEMORY_TYPES = {
"WeakRef",
"FinalizationRegistry",
};

const std::vector<std::string> SUPPORTED_ABSTRACTION_OBJECT_TYPES = {
"Iterator",
"AsyncIterator",
"Promise",
"GeneratorFunction",
"AsyncGeneratorFunction",
"Generator",
"AsyncGenerator",
"AsyncFunction",
};

const std::vector<std::string> SUPPORTED_REFLECTION_TYPES = {
"Reflect",
"Proxy",
};

static inline std::string getObjectTypeName(
jsi::Runtime &rt,
const jsi::Object &object) {
return object.getPropertyAsObject(rt, "constructor")
.getProperty(rt, "name")
.toString(rt)
.utf8(rt);
}

static inline bool isInstanceOf(
jsi::Runtime &rt,
const jsi::Object &object,
const std::string &type) {
return getObjectTypeName(rt, object) == type;
}

static inline bool isInstanceOfAny(
jsi::Runtime &rt,
const jsi::Object &object,
const std::vector<std::string> &supportedTypes) {
auto instanceType = getObjectTypeName(rt, object);

return std::find(
supportedTypes.begin(), supportedTypes.end(), instanceType) !=
supportedTypes.end();
}

JSISerializer::JSISerializer(jsi::Runtime &rt)
: rt_(rt),
visitedNodes_(rt_.global()
.getPropertyAsFunction(rt_, "Set")
.callAsConstructor(rt_)
.asObject(rt_)) {}

std::string JSISerializer::stringifyWithName(const jsi::Object &object) {
std::stringstream ss;
ss << '[' << getObjectTypeName(rt_, object) << ']';

return ss.str();
}

std::string JSISerializer::stringifyArray(const jsi::Array &arr) {
std::stringstream ss;
ss << '[';

for (size_t i = 0, length = arr.size(rt_); i < length; i++) {
jsi::Value element = arr.getValueAtIndex(rt_, i);
ss << stringifyJSIValueRecursively(element);
if (i != length - 1) {
ss << ", ";
}
}

ss << ']';

return ss.str();
}

std::string JSISerializer::stringifyFunction(const jsi::Function &func) {
std::stringstream ss;
auto kind = (func.isHostFunction(rt_) ? "jsi::HostFunction" : "Function");
auto name = func.getProperty(rt_, "name").toString(rt_).utf8(rt_);
name = name.empty() ? "anonymous" : name;

ss << '[' << kind << ' ' << name << ']';
return ss.str();
}

std::string JSISerializer::stringifyHostObject(jsi::HostObject &hostObject) {
int status = -1;
char *hostObjClassName =
abi::__cxa_demangle(typeid(hostObject).name(), NULL, NULL, &status);
if (status != 0) {
return "[jsi::HostObject]";
}

std::stringstream ss;
ss << "[jsi::HostObject(" << hostObjClassName << ") ";
std::free(hostObjClassName);

auto props = hostObject.getPropertyNames(rt_);
auto propsCount = props.size();
auto lastKey = props.back().utf8(rt_);

if (propsCount > 0) {
ss << '{';
for (const auto &key : props) {
auto formattedKey = key.utf8(rt_);
auto value = hostObject.get(rt_, key);
ss << '"' << formattedKey << '"' << ": "
<< stringifyJSIValueRecursively(value);
if (formattedKey != lastKey) {
ss << ", ";
}
}
ss << '}';
}
ss << ']';

return ss.str();
}

std::string JSISerializer::stringifyObject(const jsi::Object &object) {
std::stringstream ss;
ss << '{';

auto props = object.getPropertyNames(rt_);

for (size_t i = 0, propsCount = props.size(rt_); i < propsCount; i++) {
jsi::String propName = props.getValueAtIndex(rt_, i).toString(rt_);
ss << '"' << propName.utf8(rt_) << '"' << ": "
<< stringifyJSIValueRecursively(object.getProperty(rt_, propName));
if (i != propsCount - 1) {
ss << ", ";
}
}

ss << '}';

return ss.str();
}

std::string JSISerializer::stringifyError(const jsi::Object &object) {
std::stringstream ss;
ss << '[' << object.getProperty(rt_, "name").toString(rt_).utf8(rt_) << ": "
<< object.getProperty(rt_, "message").toString(rt_).utf8(rt_) << ']';
return ss.str();
}

std::string JSISerializer::stringifySet(const jsi::Object &object) {
std::stringstream ss;
jsi::Function arrayFrom = rt_.global()
.getPropertyAsObject(rt_, "Array")
.getPropertyAsFunction(rt_, "from");
jsi::Object result = arrayFrom.call(rt_, object).asObject(rt_);

if (!result.isArray(rt_)) {
return "[Set]";
}

auto arr = result.asArray(rt_);
ss << "Set {";

for (size_t i = 0, length = arr.size(rt_); i < length; i++) {
ss << stringifyJSIValueRecursively(arr.getValueAtIndex(rt_, i));
if (i != length - 1) {
ss << ", ";
}
}

ss << '}';

return ss.str();
}

std::string JSISerializer::stringifyMap(const jsi::Object &object) {
std::stringstream ss;
jsi::Function arrayFrom = rt_.global()
.getPropertyAsObject(rt_, "Array")
.getPropertyAsFunction(rt_, "from");
jsi::Object result = arrayFrom.call(rt_, object).asObject(rt_);

if (!result.isArray(rt_)) {
return "[Map]";
}

auto arr = result.asArray(rt_);
ss << "Map {";

for (size_t i = 0, length = arr.size(rt_); i < length; i++) {
auto pair = arr.getValueAtIndex(rt_, i).asObject(rt_).getArray(rt_);
auto key = pair.getValueAtIndex(rt_, 0);
auto value = pair.getValueAtIndex(rt_, 1);
ss << stringifyJSIValueRecursively(key) << ": "
<< stringifyJSIValueRecursively(value);
if (i != length - 1) {
ss << ", ";
}
}

ss << '}';

return ss.str();
}

std::string JSISerializer::stringifyRecursiveType(const jsi::Object &object) {
auto type = getObjectTypeName(rt_, object);

if (type == "Array") {
return "[...]";
}
if (type == "Object") {
return "{...}";
}
return "...";
}

std::string JSISerializer::stringifyWithToString(const jsi::Object &object) {
return object.getPropertyAsFunction(rt_, "toString")
.callWithThis(rt_, object)
.toString(rt_)
.utf8(rt_);
}

std::string JSISerializer::stringifyJSIValueRecursively(
const jsi::Value &value,
bool isTopLevel) {
if (value.isBool() || value.isNumber()) {
return value.toString(rt_).utf8(rt_);
}
if (value.isString()) {
return isTopLevel ? value.getString(rt_).utf8(rt_)
: '"' + value.getString(rt_).utf8(rt_) + '"';
}
if (value.isSymbol()) {
return value.getSymbol(rt_).toString(rt_);
}
#if REACT_NATIVE_MINOR_VERSION >= 70
if (value.isBigInt()) {
return value.getBigInt(rt_).toString(rt_).utf8(rt_) + 'n';
}
#endif
if (value.isUndefined()) {
return "undefined";
}
if (value.isNull()) {
return "null";
}
if (value.isObject()) {
jsi::Object object = value.asObject(rt_);

if (hasBeenVisited(object)) {
return stringifyRecursiveType(object);
}
markAsVisited(object);

if (object.isArray(rt_)) {
return stringifyArray(object.getArray(rt_));
}
if (object.isFunction(rt_)) {
return stringifyFunction(object.getFunction(rt_));
}
if (object.isHostObject(rt_)) {
return stringifyHostObject(*object.getHostObject(rt_));
}
if (isInstanceOfAny(rt_, object, SUPPORTED_ERROR_TYPES)) {
return stringifyError(object);
}
if (isInstanceOfAny(rt_, object, SUPPORTED_INDEXED_COLLECTION_TYPES) ||
isInstanceOfAny(rt_, object, SUPPORTED_STRUCTURED_DATA_TYPES) ||
isInstanceOfAny(rt_, object, SUPPORTED_MANAGING_MEMORY_TYPES) ||
isInstanceOfAny(rt_, object, SUPPORTED_ABSTRACTION_OBJECT_TYPES) ||
isInstanceOfAny(rt_, object, SUPPORTED_REFLECTION_TYPES) ||
isInstanceOf(rt_, object, "Intl") ||
isInstanceOf(rt_, object, "WeakMap") ||
isInstanceOf(rt_, object, "WeakSet")) {
// TODO: Consider extending this log info
return stringifyWithName(object);
}
if (isInstanceOf(rt_, object, "Date") ||
isInstanceOf(rt_, object, "RegExp")) {
return stringifyWithToString(object);
}
if (isInstanceOf(rt_, object, "Map")) {
return stringifyMap(object);
}
if (isInstanceOf(rt_, object, "Set")) {
return stringifySet(object);
}
return stringifyObject(object);
}

throw std::runtime_error("unsupported value type");
}

std::string stringifyJSIValue(jsi::Runtime &rt, const jsi::Value &value) {
JSISerializer serializer(rt);

return serializer.stringifyJSIValueRecursively(value, true);
}
45 changes: 45 additions & 0 deletions Common/cpp/Tools/JSISerializer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once

#include <jsi/jsi.h>
#include <string>
#include <vector>

using namespace facebook;

namespace {
class JSISerializer {
public:
explicit JSISerializer(jsi::Runtime &rt);
std::string stringifyJSIValueRecursively(
const jsi::Value &value,
bool isTopLevel = false);

private:
std::string stringifyArray(const jsi::Array &arr);
std::string stringifyFunction(const jsi::Function &func);
std::string stringifyHostObject(jsi::HostObject &hostObject);
std::string stringifyObject(const jsi::Object &object);
std::string stringifyError(const jsi::Object &object);
std::string stringifySet(const jsi::Object &object);
std::string stringifyMap(const jsi::Object &object);
std::string stringifyWithName(const jsi::Object &object);
std::string stringifyWithToString(const jsi::Object &object);
std::string stringifyRecursiveType(const jsi::Object &object);

bool hasBeenVisited(const jsi::Object &object) {
return visitedNodes_.getPropertyAsFunction(rt_, "has")
.callWithThis(rt_, visitedNodes_, object)
.getBool();
}

void markAsVisited(const jsi::Object &object) {
visitedNodes_.getPropertyAsFunction(rt_, "add")
.callWithThis(rt_, visitedNodes_, object);
}

jsi::Runtime &rt_;
jsi::Object visitedNodes_;
};
} // namespace

std::string stringifyJSIValue(jsi::Runtime &rt, const jsi::Value &value);
Loading

0 comments on commit 5dac63a

Please sign in to comment.