Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

builtins: console logging support improvements #434

Merged
merged 15 commits into from Mar 3, 2023
262 changes: 181 additions & 81 deletions c-dependencies/js-compute-runtime/builtins/shared/console.cpp
@@ -1,5 +1,11 @@
#include "console.h"
#include "mozilla/Result.h"
#include <js/Array.h>
#include <js/PropertyAndElement.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-offsetof"
#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"
#include <js/experimental/TypedData.h>
#pragma clang diagnostic pop

JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::HandleValue val,
JS::MutableHandleObjectVector visitedObjects);
Expand All @@ -23,18 +29,14 @@ JS::Result<mozilla::Ok> PromiseToSource(JSContext *cx, std::string &sourceOut, J
}
case JS::PromiseState::Fulfilled: {
JS::RootedValue value(cx, JS::GetPromiseResult(obj));
std::string source;
MOZ_TRY(ToSource(cx, source, value, visitedObjects));
sourceOut += source;
MOZ_TRY(ToSource(cx, sourceOut, value, visitedObjects));
sourceOut += " }";
break;
}
case JS::PromiseState::Rejected: {
sourceOut += "<rejected> ";
JS::RootedValue value(cx, JS::GetPromiseResult(obj));
std::string source;
MOZ_TRY(ToSource(cx, source, value, visitedObjects));
sourceOut += source;
MOZ_TRY(ToSource(cx, sourceOut, value, visitedObjects));
sourceOut += " }";
break;
}
Expand Down Expand Up @@ -84,13 +86,9 @@ JS::Result<mozilla::Ok> MapToSource(JSContext *cx, std::string &sourceOut, JS::H
entry = &entry_val.toObject();
JS_GetElement(cx, entry, 0, &name_val);
JS_GetElement(cx, entry, 1, &value_val);
std::string name;
MOZ_TRY(ToSource(cx, name, name_val, visitedObjects));
sourceOut += name;
MOZ_TRY(ToSource(cx, sourceOut, name_val, visitedObjects));
sourceOut += " => ";
std::string value;
MOZ_TRY(ToSource(cx, value, value_val, visitedObjects));
sourceOut += value;
MOZ_TRY(ToSource(cx, sourceOut, value_val, visitedObjects));
}
sourceOut += " }";
return mozilla::Ok();
Expand Down Expand Up @@ -126,73 +124,138 @@ JS::Result<mozilla::Ok> SetToSource(JSContext *cx, std::string &sourceOut, JS::H
if (done) {
break;
}
std::string entry;
MOZ_TRY(ToSource(cx, entry, entry_val, visitedObjects));
if (firstValue) {
firstValue = false;
} else {
sourceOut += ", ";
}
sourceOut += entry;
MOZ_TRY(ToSource(cx, sourceOut, entry_val, visitedObjects));
}
sourceOut += " }";
return mozilla::Ok();
}

/**
* Turn a handle of an Object into a string which represents the object.
* This function will go through every property on the object (including non-enumerable properties)
* Each property name and property value within the object will be converted into it's ToSource
* representation. Note: functions and methods within the object are not included in the output
*
* E.G. The object `{ a: 1, b: 2, c: 3, d(){}, get f(){}, g: function bar() {} }`
* would be represented as "{a: 1, b: {c: 2}, c: 3, f: undefined}"
JS::Result<mozilla::Ok> ArrayToSource(JSContext *cx, std::string &sourceOut, JS::HandleObject obj,
JS::MutableHandleObjectVector visitedObjects) {
sourceOut += "[";
uint32_t len;
if (!JS::GetArrayLength(cx, obj, &len)) {
return JS::Result<mozilla::Ok>(JS::Error());
}

for (int i = 0; i < len; i++) {
JS::RootedValue entry_val(cx);
JS_GetElement(cx, obj, i, &entry_val);
if (i > 0) {
sourceOut += ", ";
}
MOZ_TRY(ToSource(cx, sourceOut, entry_val, visitedObjects));
}
sourceOut += "]";
return mozilla::Ok();
}

/*
* Logs all enumerable properties, except non-own function and symbol properties
* Includes handling for getters and setters
*/
JS::Result<mozilla::Ok> ObjectToSource(JSContext *cx, std::string &sourceOut, JS::HandleObject obj,
JS::MutableHandleObjectVector visitedObjects) {
sourceOut += "{";
JS::RootedIdVector ids(cx);

if (!js::GetPropertyKeys(cx, obj, 0, &ids)) {
return JS::Result<mozilla::Ok>(JS::Error());
}

JS::RootedValue value(cx);
size_t length = ids.length();

sourceOut += "{";
bool firstValue = true;
for (size_t i = 0; i < length; ++i) {
const auto &id = ids[i];
if (!JS_GetPropertyById(cx, obj, id, &value)) {
return JS::Result<mozilla::Ok>(JS::Error());

JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx);
JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc);

bool getter_setter = !desc.isNothing() && (desc->hasGetter() || desc->hasSetter());

// retrive the value if not a getter or setter
if (!getter_setter) {
if (!JS_GetPropertyById(cx, obj, id, &value)) {
return JS::Result<mozilla::Ok>(JS::Error());
}
}

if (!value.isObject() || !JS_ObjectIsFunction(&value.toObject())) {
if (firstValue) {
firstValue = false;
} else {
sourceOut += ", ";
// Skip logging non-own function or getter and setter keys
if (getter_setter || (value.isObject() && JS_ObjectIsFunction(&value.toObject()))) {
bool own_prop;
if (!JS_HasOwnPropertyById(cx, obj, id, &own_prop)) {
return JS::Result<mozilla::Ok>(JS::Error());
}
if (id.isSymbol()) {
JS::RootedValue v(cx, SymbolValue(id.toSymbol()));
std::string source;
MOZ_TRY(ToSource(cx, source, v, visitedObjects));
sourceOut += source;
} else {
JS::RootedValue idValue(cx, js::IdToValue(id));
std::string source;
MOZ_TRY(ToSource(cx, source, idValue, visitedObjects));
sourceOut += source;
if (!own_prop) {
continue;
}
sourceOut += ": ";
std::string source;
MOZ_TRY(ToSource(cx, source, value, visitedObjects));
sourceOut += source;
}

if (firstValue) {
firstValue = false;
sourceOut += " ";
} else {
sourceOut += ", ";
}

// Key
if (id.isSymbol()) {
JS::RootedValue v(cx, SymbolValue(id.toSymbol()));
MOZ_TRY(ToSource(cx, sourceOut, v, visitedObjects));
} else {
size_t message_len;
JS::RootedValue v(cx, js::IdToValue(id));
auto msg = encode(cx, v, &message_len);
if (!msg) {
return JS::Result<mozilla::Ok>(JS::Error());
}
sourceOut += std::string(msg.get(), message_len);
}

sourceOut += ": ";

// Getters and Setters
if (getter_setter) {
sourceOut += "[Getter]";
} else {
MOZ_TRY(ToSource(cx, sourceOut, value, visitedObjects));
}
}

if (!firstValue) {
sourceOut += " ";
}
sourceOut += "}";

return mozilla::Ok();
}

mozilla::Maybe<std::string> get_class_name(JSContext *cx, JS::HandleObject obj) {
mozilla::Maybe<std::string> result = {};
JS::RootedValue constructorVal(cx);
if (JS_GetProperty(cx, obj, "constructor", &constructorVal) && constructorVal.isObject()) {
JS::RootedValue name(cx);
JS::RootedObject constructorObj(cx, &constructorVal.toObject());
if (JS_GetProperty(cx, constructorObj, "name", &name) && name.isString()) {
size_t message_len;
auto msg = encode(cx, name, &message_len);
if (!msg) {
return result;
}
std::string name_str(msg.get(), message_len);
result.emplace(name_str);
}
}
return result;
}

/**
* Turn a handle of any value into a string which represents it.
*/
Expand All @@ -212,6 +275,35 @@ JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::Hand
case JS::ValueType::Object: {
JS::RootedObject obj(cx, &val.toObject());

if (JS_ObjectIsFunction(obj)) {
sourceOut += "[Function";
std::string source;
auto id = JS_GetFunctionId(JS_ValueToFunction(cx, val));
if (id) {
sourceOut += " ";
JS::RootedString name(cx, id);
size_t name_len;
auto msg = encode(cx, name, &name_len);
if (!msg) {
return JS::Result<mozilla::Ok>(JS::Error());
}
sourceOut += std::string(msg.get(), name_len);
}
sourceOut += "]";
return mozilla::Ok();
}

if (JS_IsTypedArrayObject(obj)) {
// Show the typed array type
mozilla::Maybe<std::string> name_str = get_class_name(cx, obj);
if (!name_str.isNothing()) {
sourceOut += *name_str;
sourceOut += " ";
}
MOZ_TRY(ArrayToSource(cx, sourceOut, obj, visitedObjects));
return mozilla::Ok();
}

for (const auto &curObject : visitedObjects) {
if (obj.get() == curObject) {
sourceOut += "<Circular>";
Expand All @@ -223,68 +315,74 @@ JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::Hand
return JS::Result<mozilla::Ok>(JS::Error());
}

if (JS_ObjectIsFunction(obj)) {
sourceOut += JS::InformalValueTypeName(val);
return mozilla::Ok();
}
js::ESClass cls;
if (!JS::GetBuiltinClass(cx, obj, &cls)) {
return JS::Result<mozilla::Ok>(JS::Error());
}

if (cls == js::ESClass::Array || cls == js::ESClass::Date || cls == js::ESClass::Error ||
cls == js::ESClass::RegExp) {
switch (cls) {
case js::ESClass::Date:
case js::ESClass::Error:
case js::ESClass::RegExp: {
JS::RootedString source(cx, JS_ValueToSource(cx, val));
size_t message_len;
auto msg = encode(cx, source, &message_len);
if (!msg) {
return JS::Result<mozilla::Ok>(JS::Error());
}
std::string sourceString(msg.get(), message_len);
sourceOut += sourceString;
sourceOut += std::string(msg.get(), message_len);
return mozilla::Ok();
} else if (cls == js::ESClass::Set) {
std::string sourceString;
MOZ_TRY(SetToSource(cx, sourceString, obj, visitedObjects));
sourceOut += sourceString;
}
case js::ESClass::Array: {
MOZ_TRY(ArrayToSource(cx, sourceOut, obj, visitedObjects));
return mozilla::Ok();
} else if (cls == js::ESClass::Map) {
std::string sourceString;
MOZ_TRY(MapToSource(cx, sourceString, obj, visitedObjects));
sourceOut += sourceString;
}
case js::ESClass::Set: {
MOZ_TRY(SetToSource(cx, sourceOut, obj, visitedObjects));
return mozilla::Ok();
} else if (cls == js::ESClass::Promise) {
std::string sourceString;
MOZ_TRY(PromiseToSource(cx, sourceString, obj, visitedObjects));
sourceOut += sourceString;
}
case js::ESClass::Map: {
MOZ_TRY(MapToSource(cx, sourceOut, obj, visitedObjects));
return mozilla::Ok();
} else {
}
case js::ESClass::Promise: {
MOZ_TRY(PromiseToSource(cx, sourceOut, obj, visitedObjects));
return mozilla::Ok();
}
default: {
std::string sourceString;
if (JS::IsWeakMapObject(obj)) {
std::string sourceString = "WeakMap { <items unknown> }";
sourceOut += sourceString;
sourceOut += "WeakMap { <items unknown> }";
return mozilla::Ok();
}
auto cls = JS::GetClass(obj);
std::string className(cls->name);
if (className == "WeakSet") {
std::string sourceString = "WeakSet { <items unknown> }";
sourceOut += sourceString;
sourceOut += "WeakSet { <items unknown> }";
return mozilla::Ok();
}
std::string sourceString;
MOZ_TRY(ObjectToSource(cx, sourceString, obj, visitedObjects));
sourceOut += sourceString;

// Lookup the class name if a custom class
mozilla::Maybe<std::string> name_str = get_class_name(cx, obj);
if (!name_str.isNothing() && *name_str != "Object") {
sourceOut += *name_str;
sourceOut += " ";
}

MOZ_TRY(ObjectToSource(cx, sourceOut, obj, visitedObjects));
return mozilla::Ok();
}
}
}
case JS::ValueType::String: {
size_t message_len;
auto msg = encode(cx, val, &message_len);
if (!msg) {
return JS::Result<mozilla::Ok>(JS::Error());
}
std::string sourceString(msg.get(), message_len);
sourceOut += sourceString;
sourceOut += '"';
sourceOut += std::string(msg.get(), message_len);
sourceOut += '"';
return mozilla::Ok();
}
default: {
Expand All @@ -294,8 +392,7 @@ JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::Hand
if (!msg) {
return JS::Result<mozilla::Ok>(JS::Error());
}
std::string sourceString(msg.get(), message_len);
sourceOut += sourceString;
sourceOut += std::string(msg.get(), message_len);
return mozilla::Ok();
}
}
Expand All @@ -316,12 +413,15 @@ static bool console_out(JSContext *cx, unsigned argc, JS::Value *vp) {
if (result.isErr()) {
return false;
}
std::string message = source;
// strip quotes for direct string logs
if (source[0] == '"' && source[source.length() - 1] == '"') {
source = source.substr(1, source.length() - 2);
}
if (fullLogLine.length()) {
fullLogLine += " ";
fullLogLine += message;
fullLogLine += source;
} else {
fullLogLine += message;
fullLogLine += source;
}
}

Expand Down
2 changes: 2 additions & 0 deletions integration-tests/js-compute/fixtures/console/bin/index.js
Expand Up @@ -87,4 +87,6 @@ addEventListener("fetch", () => {
console.log('TransformStream:', arg)
arg = new WritableStream
console.log('WritableStream:', arg)
arg = new URL('https://www.test.com:123/asdf?some&params=val')
console.log('URL:', arg)
});