Skip to content

Commit 02e8566

Browse files
implement iteration on bytes object
1 parent 38e94ed commit 02e8566

File tree

3 files changed

+337
-12
lines changed

3 files changed

+337
-12
lines changed

include/PyBytesProxyHandler.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* @file PyBytesProxyHandler.hh
33
* @author Philippe Laporte ([email protected])
4-
* @brief Struct for creating JS proxy objects for immutable bytes objects
4+
* @brief Struct for creating JS Uint8Array-like proxy objects for immutable bytes objects
55
* @date 2024-07-23
66
*
77
* @copyright Copyright (c) 2024 Distributive Corp.
@@ -39,7 +39,7 @@ public:
3939
* @brief An array of method definitions for bytes prototype methods
4040
*
4141
*/
42-
static JSMethodDef bytes_methods[];
42+
static JSMethodDef array_methods[];
4343
};
4444

4545
#endif

src/PyBytesProxyHandler.cc

Lines changed: 204 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* @file PyBytesProxyHandler.cc
33
* @author Philippe Laporte ([email protected])
4-
* @brief Struct for creating JS proxy objects for immutable bytes objects
4+
* @brief Struct for creating JS Uint8Array-like proxy objects for immutable bytes objects
55
* @date 2024-07-23
66
*
77
* @copyright Copyright (c) 2024 Distributive Corp.
@@ -20,7 +20,7 @@
2020
const char PyBytesProxyHandler::family = 0;
2121

2222

23-
static bool bytes_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) {
23+
static bool array_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) {
2424
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
2525

2626
JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv()));
@@ -50,13 +50,193 @@ static bool bytes_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) {
5050
return true;
5151
}
5252

53-
static bool bytes_toString(JSContext *cx, unsigned argc, JS::Value *vp) {
54-
return bytes_valueOf(cx, argc, vp);
53+
static bool array_toString(JSContext *cx, unsigned argc, JS::Value *vp) {
54+
return array_valueOf(cx, argc, vp);
5555
}
5656

57-
JSMethodDef PyBytesProxyHandler::bytes_methods[] = {
58-
{"toString", bytes_toString, 0},
59-
{"valueOf", bytes_valueOf, 0},
57+
58+
// BytesIterator
59+
60+
61+
#define ITEM_KIND_KEY 0
62+
#define ITEM_KIND_VALUE 1
63+
#define ITEM_KIND_KEY_AND_VALUE 2
64+
65+
enum {
66+
BytesIteratorSlotIteratedObject,
67+
BytesIteratorSlotNextIndex,
68+
BytesIteratorSlotItemKind,
69+
BytesIteratorSlotCount
70+
};
71+
72+
static JSClass bytesIteratorClass = {"BytesIterator", JSCLASS_HAS_RESERVED_SLOTS(BytesIteratorSlotCount)};
73+
74+
static bool iterator_next(JSContext *cx, unsigned argc, JS::Value *vp) {
75+
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
76+
JS::RootedObject thisObj(cx);
77+
if (!args.computeThis(cx, &thisObj)) return false;
78+
79+
JS::PersistentRootedObject* arrayBuffer = JS::GetMaybePtrFromReservedSlot<JS::PersistentRootedObject>(thisObj, BytesIteratorSlotIteratedObject);
80+
JS::RootedObject rootedArrayBuffer(cx, arrayBuffer->get());
81+
82+
JS::RootedValue rootedNextIndex(cx, JS::GetReservedSlot(thisObj, BytesIteratorSlotNextIndex));
83+
JS::RootedValue rootedItemKind(cx, JS::GetReservedSlot(thisObj, BytesIteratorSlotItemKind));
84+
85+
int32_t nextIndex;
86+
int32_t itemKind;
87+
if (!JS::ToInt32(cx, rootedNextIndex, &nextIndex) || !JS::ToInt32(cx, rootedItemKind, &itemKind)) return false;
88+
89+
JS::RootedObject result(cx, JS_NewPlainObject(cx));
90+
91+
Py_ssize_t len = JS::GetArrayBufferByteLength(rootedArrayBuffer);
92+
93+
if (nextIndex >= len) {
94+
// UnsafeSetReservedSlot(obj, ITERATOR_SLOT_TARGET, null); // TODO lose ref
95+
JS::RootedValue done(cx, JS::BooleanValue(true));
96+
if (!JS_SetProperty(cx, result, "done", done)) return false;
97+
args.rval().setObject(*result);
98+
return result;
99+
}
100+
101+
JS::SetReservedSlot(thisObj, BytesIteratorSlotNextIndex, JS::Int32Value(nextIndex + 1));
102+
103+
JS::RootedValue done(cx, JS::BooleanValue(false));
104+
if (!JS_SetProperty(cx, result, "done", done)) return false;
105+
106+
if (itemKind == ITEM_KIND_VALUE) {
107+
bool isSharedMemory;
108+
JS::AutoCheckCannotGC autoNoGC(cx);
109+
uint8_t *data = JS::GetArrayBufferData(rootedArrayBuffer, &isSharedMemory, autoNoGC);
110+
111+
JS::RootedValue value(cx, JS::Int32Value(data[nextIndex]));
112+
if (!JS_SetProperty(cx, result, "value", value)) return false;
113+
}
114+
else if (itemKind == ITEM_KIND_KEY_AND_VALUE) {
115+
JS::Rooted<JS::ValueArray<2>> items(cx);
116+
117+
JS::RootedValue rootedNextIndex(cx, JS::Int32Value(nextIndex));
118+
items[0].set(rootedNextIndex);
119+
120+
bool isSharedMemory;
121+
JS::AutoCheckCannotGC autoNoGC(cx);
122+
uint8_t *data = JS::GetArrayBufferData(rootedArrayBuffer, &isSharedMemory, autoNoGC);
123+
124+
JS::RootedValue value(cx, JS::Int32Value(data[nextIndex]));
125+
items[1].set(value);
126+
127+
JS::RootedValue pair(cx);
128+
JSObject *array = JS::NewArrayObject(cx, items);
129+
pair.setObject(*array);
130+
if (!JS_SetProperty(cx, result, "value", pair)) return false;
131+
}
132+
else { // itemKind == ITEM_KIND_KEY
133+
JS::RootedValue value(cx, JS::Int32Value(nextIndex));
134+
if (!JS_SetProperty(cx, result, "value", value)) return false;
135+
}
136+
137+
args.rval().setObject(*result);
138+
return true;
139+
}
140+
141+
static JSFunctionSpec bytes_iterator_methods[] = {
142+
JS_FN("next", iterator_next, 0, JSPROP_ENUMERATE),
143+
JS_FS_END
144+
};
145+
146+
static bool BytesIteratorConstructor(JSContext *cx, unsigned argc, JS::Value *vp) {
147+
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
148+
149+
if (!args.isConstructing()) {
150+
JS_ReportErrorASCII(cx, "You must call this constructor with 'new'");
151+
return false;
152+
}
153+
154+
JS::RootedObject thisObj(cx, JS_NewObjectForConstructor(cx, &bytesIteratorClass, args));
155+
if (!thisObj) {
156+
return false;
157+
}
158+
159+
args.rval().setObject(*thisObj);
160+
return true;
161+
}
162+
163+
static bool DefineBytesIterator(JSContext *cx, JS::HandleObject global) {
164+
JS::RootedObject iteratorPrototype(cx);
165+
if (!JS_GetClassPrototype(cx, JSProto_Iterator, &iteratorPrototype)) {
166+
return false;
167+
}
168+
169+
JS::RootedObject protoObj(cx,
170+
JS_InitClass(cx, global,
171+
nullptr, iteratorPrototype,
172+
"BytesIterator",
173+
BytesIteratorConstructor, 0,
174+
nullptr, bytes_iterator_methods,
175+
nullptr, nullptr)
176+
);
177+
178+
return protoObj; // != nullptr
179+
}
180+
181+
/// private util
182+
static bool array_iterator_func(JSContext *cx, unsigned argc, JS::Value *vp, int itemKind) {
183+
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
184+
185+
JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv()));
186+
if (!proxy) {
187+
return false;
188+
}
189+
190+
JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(proxy));
191+
192+
JS::RootedValue constructor_val(cx);
193+
if (!JS_GetProperty(cx, global, "BytesIterator", &constructor_val)) return false;
194+
if (!constructor_val.isObject()) {
195+
if (!DefineBytesIterator(cx, global)) {
196+
return false;
197+
}
198+
199+
if (!JS_GetProperty(cx, global, "BytesIterator", &constructor_val)) return false;
200+
if (!constructor_val.isObject()) {
201+
JS_ReportErrorASCII(cx, "BytesIterator is not a constructor");
202+
return false;
203+
}
204+
}
205+
JS::RootedObject constructor(cx, &constructor_val.toObject());
206+
207+
JS::RootedObject obj(cx);
208+
if (!JS::Construct(cx, constructor_val, JS::HandleValueArray::empty(), &obj)) return false;
209+
if (!obj) return false;
210+
211+
JS::PersistentRootedObject* arrayBuffer = JS::GetMaybePtrFromReservedSlot<JS::PersistentRootedObject>(proxy, OtherSlot);
212+
213+
JS::SetReservedSlot(obj, BytesIteratorSlotIteratedObject, JS::PrivateValue(arrayBuffer));
214+
JS::SetReservedSlot(obj, BytesIteratorSlotNextIndex, JS::Int32Value(0));
215+
JS::SetReservedSlot(obj, BytesIteratorSlotItemKind, JS::Int32Value(itemKind));
216+
217+
args.rval().setObject(*obj);
218+
return true;
219+
}
220+
221+
static bool array_entries(JSContext *cx, unsigned argc, JS::Value *vp) {
222+
return array_iterator_func(cx, argc, vp, ITEM_KIND_KEY_AND_VALUE);
223+
}
224+
225+
static bool array_keys(JSContext *cx, unsigned argc, JS::Value *vp) {
226+
return array_iterator_func(cx, argc, vp, ITEM_KIND_KEY);
227+
}
228+
229+
static bool array_values(JSContext *cx, unsigned argc, JS::Value *vp) {
230+
return array_iterator_func(cx, argc, vp, ITEM_KIND_VALUE);
231+
}
232+
233+
234+
JSMethodDef PyBytesProxyHandler::array_methods[] = {
235+
{"toString", array_toString, 0},
236+
{"valueOf", array_valueOf, 0},
237+
{"entries", array_entries, 0},
238+
{"keys", array_keys, 0},
239+
{"values", array_values, 0},
60240
{NULL, NULL, 0}
61241
};
62242

@@ -84,12 +264,12 @@ bool PyBytesProxyHandler::getOwnPropertyDescriptor(
84264
if (id.isString()) {
85265
for (size_t index = 0;; index++) {
86266
bool isThatFunction;
87-
const char *methodName = bytes_methods[index].name;
267+
const char *methodName = array_methods[index].name;
88268
if (methodName == NULL) {
89269
break;
90270
}
91271
else if (JS_StringEqualsAscii(cx, id.toString(), methodName, &isThatFunction) && isThatFunction) {
92-
JSFunction *newFunction = JS_NewFunction(cx, bytes_methods[index].call, bytes_methods[index].nargs, 0, NULL);
272+
JSFunction *newFunction = JS_NewFunction(cx, array_methods[index].call, array_methods[index].nargs, 0, NULL);
93273
if (!newFunction) return false;
94274
JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction));
95275
desc.set(mozilla::Some(
@@ -182,6 +362,21 @@ bool PyBytesProxyHandler::getOwnPropertyDescriptor(
182362
}
183363

184364
if (id.isSymbol()) {
365+
JS::RootedSymbol rootedSymbol(cx, id.toSymbol());
366+
367+
if (JS::GetSymbolCode(rootedSymbol) == JS::SymbolCode::iterator) {
368+
JSFunction *newFunction = JS_NewFunction(cx, array_values, 0, 0, NULL);
369+
if (!newFunction) return false;
370+
JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction));
371+
desc.set(mozilla::Some(
372+
JS::PropertyDescriptor::Data(
373+
JS::ObjectValue(*funObj),
374+
{JS::PropertyAttribute::Enumerable}
375+
)
376+
));
377+
return true;
378+
}
379+
185380
return true; // needed for console.log
186381
}
187382

0 commit comments

Comments
 (0)