mirror of
https://github.com/frida/frida-tools.git
synced 2026-11-07 09:35:59 -10:00
repl: Render iterators and generators
Evaluating an expression that yields a generator or other iterator object printed an empty object, since such objects have no own enumerable keys. Encode them as a new tagged value by pulling up to 100 items, marking when more remain, and label them by their toStringTag so a generator shows as Generator[...] and a Map iterator as Map Iterator[...]. The iterator is consumed by the display, as it would be by any other means of looking at its items.
This commit is contained in:
1 parent
19048c41ce
commit
24f5b4d214
2 files changed
+56
-1
No files matched your search
@@ -8,6 +8,7 @@ export interface EncodeOptions {
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_DEPTH = 3;
|
||||
const MAX_ITERATOR_ITEMS = 100;
|
||||
|
||||
export type PackedValue = [EncodedValueTree, ArrayBuffer | null];
|
||||
|
||||
@@ -40,6 +41,7 @@ const enum ValueTag {
|
||||
WeakSet = 19,
|
||||
DepthLimit = 20,
|
||||
Circular = 21,
|
||||
Iterator = 22,
|
||||
}
|
||||
|
||||
type TypedArrayName =
|
||||
@@ -145,6 +147,7 @@ function collectTree(
|
||||
const isArray = Array.isArray(obj);
|
||||
const isMap = obj instanceof Map;
|
||||
const isSet = obj instanceof Set;
|
||||
const isIterator = typeof obj.next === "function" && typeof obj[Symbol.iterator] === "function";
|
||||
|
||||
const existingId = state.seen.get(obj);
|
||||
if (existingId !== undefined) {
|
||||
@@ -161,6 +164,9 @@ function collectTree(
|
||||
if (isSet) {
|
||||
return [ValueTag.DepthLimit, ValueTag.Set];
|
||||
}
|
||||
if (isIterator) {
|
||||
return [ValueTag.DepthLimit, ValueTag.Iterator];
|
||||
}
|
||||
return [ValueTag.DepthLimit, ValueTag.Object];
|
||||
}
|
||||
|
||||
@@ -194,6 +200,20 @@ function collectTree(
|
||||
return [ValueTag.Array, id, elements];
|
||||
}
|
||||
|
||||
if (isIterator) {
|
||||
const label = obj[Symbol.toStringTag] ?? "Iterator";
|
||||
const items: EncodedValueTree[] = [];
|
||||
let truncated = false;
|
||||
for (const v of obj as Iterable<unknown>) {
|
||||
if (items.length === MAX_ITERATOR_ITEMS) {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
items.push(collectTree(v, chunks, maxDepth, depth + 1, state));
|
||||
}
|
||||
return [ValueTag.Iterator, id, label, items, truncated];
|
||||
}
|
||||
|
||||
try {
|
||||
const maybeToJSON = obj.toJSON;
|
||||
if (typeof maybeToJSON === "function") {
|
||||
@@ -349,6 +369,15 @@ function patchTree(
|
||||
return [ValueTag.Set, id, patchedItems];
|
||||
}
|
||||
|
||||
case ValueTag.Iterator: {
|
||||
const id = rest[0] as number;
|
||||
const label = rest[1] as string;
|
||||
const items = rest[2] as EncodedValueTree[];
|
||||
const truncated = rest[3] as boolean;
|
||||
const patchedItems = items.map(child => patchTree(child, offsets));
|
||||
return [ValueTag.Iterator, id, label, patchedItems, truncated];
|
||||
}
|
||||
|
||||
default:
|
||||
return tree;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ WEAKMAP = 18
|
||||
WEAKSET = 19
|
||||
DEPTH_LIMIT = 20
|
||||
CIRCULAR = 21
|
||||
ITERATOR = 22
|
||||
|
||||
_CYAN = "fg:#64d2ff"
|
||||
_MINT = "fg:#66d4cf"
|
||||
@@ -47,7 +48,7 @@ _MAX_ITEMS = 100
|
||||
|
||||
_HEX_BYTES_PER_ROW = 16
|
||||
|
||||
_CONTAINER_NAMES = {OBJECT: "Object", ARRAY: "Array", MAP: "Map", SET: "Set"}
|
||||
_CONTAINER_NAMES = {OBJECT: "Object", ARRAY: "Array", MAP: "Map", SET: "Set", ITERATOR: "Iterator"}
|
||||
|
||||
Node = Sequence[Any]
|
||||
Fragment = Tuple[str, str]
|
||||
@@ -147,6 +148,9 @@ def _append(node: Node, level: int, out: List[Fragment], blob: Sequence[int]) ->
|
||||
elif tag == CIRCULAR:
|
||||
out.append((_ORANGE, "⟳ circular *%d" % node[1]))
|
||||
|
||||
elif tag == ITERATOR:
|
||||
_append_iterator(node, level, out, blob)
|
||||
|
||||
|
||||
def _append_object(node: Node, level: int, out: List[Fragment], blob: Sequence[int]) -> None:
|
||||
properties = node[2]
|
||||
@@ -263,6 +267,28 @@ def _append_set(node: Node, level: int, out: List[Fragment], blob: Sequence[int]
|
||||
out.append((_CYAN, "]"))
|
||||
|
||||
|
||||
def _append_iterator(node: Node, level: int, out: List[Fragment], blob: Sequence[int]) -> None:
|
||||
label, elements, truncated = node[2], node[3], node[4]
|
||||
if not elements:
|
||||
out.append((_CYAN, "%s[]" % label))
|
||||
return
|
||||
|
||||
out.append((_CYAN, "%s[" % label))
|
||||
out.append(("", "\n"))
|
||||
for index, element in enumerate(elements):
|
||||
_indent(level + 1, out)
|
||||
_append(element, level + 1, out, blob)
|
||||
if _has_more(index, elements, truncated):
|
||||
out.append(("", ","))
|
||||
out.append(("", "\n"))
|
||||
if truncated:
|
||||
_indent(level + 1, out)
|
||||
out.append((_ORANGE, "… more"))
|
||||
out.append(("", "\n"))
|
||||
_indent(level, out)
|
||||
out.append((_CYAN, "]"))
|
||||
|
||||
|
||||
def _capped(items: Sequence[Any]) -> Tuple[Sequence[Any], int]:
|
||||
shown = items[:_MAX_ITEMS]
|
||||
return shown, len(items) - len(shown)
|
||||
|
||||
Reference in new issue
Block a user