Skip to content

Commit a621d26

Browse files
JakeChampionJake Champion
and
Jake Champion
authored
feat: Improve console output for all types (#204)
Co-authored-by: Jake Champion <[email protected]>
1 parent 7933ae7 commit a621d26

File tree

3 files changed

+442
-11
lines changed

3 files changed

+442
-11
lines changed

c-dependencies/js-compute-runtime/builtins/console.cpp

+314-9
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,331 @@
11
#include "console.h"
2+
#include "mozilla/Result.h"
3+
4+
JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::HandleValue val,
5+
JS::MutableHandleObjectVector visitedObjects);
6+
7+
/**
8+
* Turn a handle of a Promise into a string which represents the promise.
9+
* - If the promise is pending this will return "Promise { <pending> }"
10+
* - If the promise is rejected this will return "Promise { <rejected> (rejected-value)}"
11+
* where rejected-value would be the ToSource representation of the rejected value.
12+
* - If the promise is resolved this will return "Promise { resolved-value}"
13+
* where resolved-value would be the ToSource representation of the resolved value.
14+
*/
15+
JS::Result<mozilla::Ok> PromiseToSource(JSContext *cx, std::string &sourceOut, JS::HandleObject obj,
16+
JS::MutableHandleObjectVector visitedObjects) {
17+
sourceOut += "Promise { ";
18+
JS::PromiseState state = JS::GetPromiseState(obj);
19+
switch (state) {
20+
case JS::PromiseState::Pending: {
21+
sourceOut += "<pending> }";
22+
break;
23+
}
24+
case JS::PromiseState::Fulfilled: {
25+
JS::RootedValue value(cx, JS::GetPromiseResult(obj));
26+
std::string source;
27+
MOZ_TRY(ToSource(cx, source, value, visitedObjects));
28+
sourceOut += source;
29+
sourceOut += " }";
30+
break;
31+
}
32+
case JS::PromiseState::Rejected: {
33+
sourceOut += "<rejected> ";
34+
JS::RootedValue value(cx, JS::GetPromiseResult(obj));
35+
std::string source;
36+
MOZ_TRY(ToSource(cx, source, value, visitedObjects));
37+
sourceOut += source;
38+
sourceOut += " }";
39+
break;
40+
}
41+
}
42+
return mozilla::Ok();
43+
}
44+
45+
/**
46+
* Turn a handle of a Map into a string which represents the map.
47+
* Each key and value within the map will be converted into it's ToSource representation.
48+
*/
49+
JS::Result<mozilla::Ok> MapToSource(JSContext *cx, std::string &sourceOut, JS::HandleObject obj,
50+
JS::MutableHandleObjectVector visitedObjects) {
51+
sourceOut += "Map(";
52+
uint32_t size = JS::MapSize(cx, obj);
53+
sourceOut += std::to_string(size);
54+
sourceOut += ") { ";
55+
JS::Rooted<JS::Value> iterable(cx);
56+
if (!JS::MapEntries(cx, obj, &iterable)) {
57+
return JS::Result<mozilla::Ok>(JS::Error());
58+
}
59+
JS::ForOfIterator it(cx);
60+
if (!it.init(iterable)) {
61+
return JS::Result<mozilla::Ok>(JS::Error());
62+
}
63+
64+
JS::RootedObject entry(cx);
65+
JS::RootedValue entry_val(cx);
66+
JS::RootedValue name_val(cx);
67+
JS::RootedValue value_val(cx);
68+
bool firstValue = true;
69+
while (true) {
70+
bool done;
71+
if (!it.next(&entry_val, &done)) {
72+
return JS::Result<mozilla::Ok>(JS::Error());
73+
}
74+
75+
if (done) {
76+
break;
77+
}
78+
if (firstValue) {
79+
firstValue = false;
80+
} else {
81+
sourceOut += ", ";
82+
}
83+
84+
entry = &entry_val.toObject();
85+
JS_GetElement(cx, entry, 0, &name_val);
86+
JS_GetElement(cx, entry, 1, &value_val);
87+
std::string name;
88+
MOZ_TRY(ToSource(cx, name, name_val, visitedObjects));
89+
sourceOut += name;
90+
sourceOut += " => ";
91+
std::string value;
92+
MOZ_TRY(ToSource(cx, value, value_val, visitedObjects));
93+
sourceOut += value;
94+
}
95+
sourceOut += " }";
96+
return mozilla::Ok();
97+
}
98+
99+
/**
100+
* Turn a handle of a Set into a string which represents the set.
101+
* Each value within the set will be converted into it's ToSource representation.
102+
*/
103+
JS::Result<mozilla::Ok> SetToSource(JSContext *cx, std::string &sourceOut, JS::HandleObject obj,
104+
JS::MutableHandleObjectVector visitedObjects) {
105+
sourceOut += "Set(";
106+
uint32_t size = JS::SetSize(cx, obj);
107+
sourceOut += std::to_string(size);
108+
sourceOut += ") { ";
109+
JS::Rooted<JS::Value> iterable(cx);
110+
if (!JS::SetValues(cx, obj, &iterable)) {
111+
return JS::Result<mozilla::Ok>(JS::Error());
112+
}
113+
JS::ForOfIterator it(cx);
114+
if (!it.init(iterable)) {
115+
return JS::Result<mozilla::Ok>(JS::Error());
116+
}
117+
118+
JS::RootedValue entry_val(cx);
119+
bool firstValue = true;
120+
while (true) {
121+
bool done;
122+
if (!it.next(&entry_val, &done)) {
123+
return JS::Result<mozilla::Ok>(JS::Error());
124+
}
125+
126+
if (done) {
127+
break;
128+
}
129+
std::string entry;
130+
MOZ_TRY(ToSource(cx, entry, entry_val, visitedObjects));
131+
if (firstValue) {
132+
firstValue = false;
133+
} else {
134+
sourceOut += ", ";
135+
}
136+
sourceOut += entry;
137+
}
138+
sourceOut += " }";
139+
return mozilla::Ok();
140+
}
141+
142+
/**
143+
* Turn a handle of an Object into a string which represents the object.
144+
* This function will go through every property on the object (including non-enumerable properties)
145+
* Each property name and property value within the object will be converted into it's ToSource
146+
* representation. Note: functions and methods within the object are not included in the output
147+
*
148+
* E.G. The object `{ a: 1, b: 2, c: 3, d(){}, get f(){}, g: function bar() {} }`
149+
* would be represented as "{a: 1, b: {c: 2}, c: 3, f: undefined}"
150+
*/
151+
JS::Result<mozilla::Ok> ObjectToSource(JSContext *cx, std::string &sourceOut, JS::HandleObject obj,
152+
JS::MutableHandleObjectVector visitedObjects) {
153+
sourceOut += "{";
154+
JS::RootedIdVector ids(cx);
155+
if (!js::GetPropertyKeys(cx, obj, 0, &ids)) {
156+
return JS::Result<mozilla::Ok>(JS::Error());
157+
}
158+
159+
JS::RootedValue value(cx);
160+
size_t length = ids.length();
161+
bool firstValue = true;
162+
for (size_t i = 0; i < length; ++i) {
163+
const auto &id = ids[i];
164+
if (!JS_GetPropertyById(cx, obj, id, &value)) {
165+
return JS::Result<mozilla::Ok>(JS::Error());
166+
}
167+
168+
if (!value.isObject() || !JS_ObjectIsFunction(&value.toObject())) {
169+
if (firstValue) {
170+
firstValue = false;
171+
} else {
172+
sourceOut += ", ";
173+
}
174+
if (id.isSymbol()) {
175+
JS::RootedValue v(cx, SymbolValue(id.toSymbol()));
176+
std::string source;
177+
MOZ_TRY(ToSource(cx, source, v, visitedObjects));
178+
sourceOut += source;
179+
} else {
180+
JS::RootedValue idValue(cx, js::IdToValue(id));
181+
std::string source;
182+
MOZ_TRY(ToSource(cx, source, idValue, visitedObjects));
183+
sourceOut += source;
184+
}
185+
sourceOut += ": ";
186+
std::string source;
187+
MOZ_TRY(ToSource(cx, source, value, visitedObjects));
188+
sourceOut += source;
189+
}
190+
}
191+
192+
sourceOut += "}";
193+
return mozilla::Ok();
194+
}
195+
196+
/**
197+
* Turn a handle of any value into a string which represents it.
198+
*/
199+
JS::Result<mozilla::Ok> ToSource(JSContext *cx, std::string &sourceOut, JS::HandleValue val,
200+
JS::MutableHandleObjectVector visitedObjects) {
201+
202+
auto type = val.type();
203+
switch (type) {
204+
case JS::ValueType::Undefined: {
205+
sourceOut += "undefined";
206+
return mozilla::Ok();
207+
}
208+
case JS::ValueType::Null: {
209+
sourceOut += "null";
210+
return mozilla::Ok();
211+
}
212+
case JS::ValueType::Object: {
213+
JS::RootedObject obj(cx, &val.toObject());
214+
215+
for (const auto &curObject : visitedObjects) {
216+
if (obj.get() == curObject) {
217+
sourceOut += "<Circular>";
218+
return mozilla::Ok();
219+
}
220+
}
221+
222+
if (!visitedObjects.emplaceBack(obj)) {
223+
return JS::Result<mozilla::Ok>(JS::Error());
224+
}
225+
226+
if (JS_ObjectIsFunction(obj)) {
227+
sourceOut += JS::InformalValueTypeName(val);
228+
return mozilla::Ok();
229+
}
230+
js::ESClass cls;
231+
if (!JS::GetBuiltinClass(cx, obj, &cls)) {
232+
return JS::Result<mozilla::Ok>(JS::Error());
233+
}
234+
235+
if (cls == js::ESClass::Array || cls == js::ESClass::Date || cls == js::ESClass::Error ||
236+
cls == js::ESClass::RegExp) {
237+
JS::RootedString source(cx, JS_ValueToSource(cx, val));
238+
size_t message_len;
239+
auto msg = encode(cx, source, &message_len);
240+
if (!msg) {
241+
return JS::Result<mozilla::Ok>(JS::Error());
242+
}
243+
std::string sourceString(msg.get(), message_len);
244+
sourceOut += sourceString;
245+
return mozilla::Ok();
246+
} else if (cls == js::ESClass::Set) {
247+
std::string sourceString;
248+
MOZ_TRY(SetToSource(cx, sourceString, obj, visitedObjects));
249+
sourceOut += sourceString;
250+
return mozilla::Ok();
251+
} else if (cls == js::ESClass::Map) {
252+
std::string sourceString;
253+
MOZ_TRY(MapToSource(cx, sourceString, obj, visitedObjects));
254+
sourceOut += sourceString;
255+
return mozilla::Ok();
256+
} else if (cls == js::ESClass::Promise) {
257+
std::string sourceString;
258+
MOZ_TRY(PromiseToSource(cx, sourceString, obj, visitedObjects));
259+
sourceOut += sourceString;
260+
return mozilla::Ok();
261+
} else {
262+
if (JS::IsWeakMapObject(obj)) {
263+
std::string sourceString = "WeakMap { <items unknown> }";
264+
sourceOut += sourceString;
265+
return mozilla::Ok();
266+
}
267+
auto cls = JS::GetClass(obj);
268+
std::string className(cls->name);
269+
if (className == "WeakSet") {
270+
std::string sourceString = "WeakSet { <items unknown> }";
271+
sourceOut += sourceString;
272+
return mozilla::Ok();
273+
}
274+
std::string sourceString;
275+
MOZ_TRY(ObjectToSource(cx, sourceString, obj, visitedObjects));
276+
sourceOut += sourceString;
277+
return mozilla::Ok();
278+
}
279+
}
280+
case JS::ValueType::String: {
281+
size_t message_len;
282+
auto msg = encode(cx, val, &message_len);
283+
if (!msg) {
284+
return JS::Result<mozilla::Ok>(JS::Error());
285+
}
286+
std::string sourceString(msg.get(), message_len);
287+
sourceOut += sourceString;
288+
return mozilla::Ok();
289+
}
290+
default: {
291+
JS::RootedString source(cx, JS_ValueToSource(cx, val));
292+
size_t message_len;
293+
auto msg = encode(cx, source, &message_len);
294+
if (!msg) {
295+
return JS::Result<mozilla::Ok>(JS::Error());
296+
}
297+
std::string sourceString(msg.get(), message_len);
298+
sourceOut += sourceString;
299+
return mozilla::Ok();
300+
}
301+
}
302+
}
2303

3304
namespace builtins {
4305

5306
template <const char *prefix, uint8_t prefix_len>
6307
static bool console_out(JSContext *cx, unsigned argc, JS::Value *vp) {
7308
JS::CallArgs args = CallArgsFromVp(argc, vp);
8-
std::string message = "";
309+
std::string fullLogLine = "";
9310
auto length = args.length();
311+
JS::RootedObjectVector visitedObjects(cx);
10312
for (int i = 0; i < length; i++) {
11-
size_t msg_len;
12-
JS::UniqueChars msg = encode(cx, args.get(i), &msg_len);
13-
if (!msg)
313+
JS::HandleValue arg = args.get(i);
314+
std::string source = "";
315+
auto result = ToSource(cx, source, arg, &visitedObjects);
316+
if (result.isErr()) {
14317
return false;
15-
if (message.length()) {
16-
message += " ";
17-
message += msg.get();
318+
}
319+
std::string message = source;
320+
if (fullLogLine.length()) {
321+
fullLogLine += " ";
322+
fullLogLine += message;
18323
} else {
19-
message += msg.get();
324+
fullLogLine += message;
20325
}
21326
}
22327

23-
printf("%s: %s\n", prefix, message.c_str());
328+
printf("%s: %s\n", prefix, fullLogLine.c_str());
24329
fflush(stdout);
25330

26331
args.rval().setUndefined();

0 commit comments

Comments
 (0)