Author SHA1 Message Date
pancake 3cef0ce07a Fix signed 64-bit varint decoding
cs_read_tagged64 biased the final byte by -64 instead of -192, adding
128 << shift to every Mint/Smi value, which inflated field offsets and
decoded constants.
2026-10-09 16:05:25 +02:00
pancake 7d019ea748 Resolve class method entry points
Map each Function's code index (a raw InstructionTable slot, including
discarded-Code slots) to its entry address. Class-derived names only
replace placeholder names when merged into the function list, so
InstructionTable names stay authoritative for deduplicated code.
2026-10-09 16:05:23 +02:00
pancake f7c6cc5b3d Extract class metadata from Dart 2.14-3.3 snapshots
modern_can_extract_classes only accepted OBJECT_HEADER (3.4+) snapshots,
so CID_SHIFT1 apps such as Dart 2.19 fell back to the string heuristic
after the legacy parser failed. Gate on modern_supported instead.
2026-10-09 16:05:20 +02:00
pancake 24e3502737 Complete code xrefs for Dart 2.19 2026-10-06 17:50:40 +02:00
pancake 78719df43e Fix Dart 2.19 ObjectPool xrefs after Type fill parsing 2026-10-06 17:30:45 +02:00
pancake 3fe3c11bac Register the r2flutter pkg 2026-10-06 16:55:12 +02:00
16 changed files with 234 additions and 61 deletions

No files matched your search

+86
View File
@@ -1797,3 +1797,89 @@ misaligned). Not yet handled: the x64 ObjectPool *slot* decoder (`-O pp+off`)
still misreads compressed pool pointers, so a slot that the collector did not
already resolve stays a bare offset; and there is no committed x86_64 test
fixture.
## Dart 2.19 Type fill must not consume the legacy combined byte
The Flutter 3.7.12/Dart 2.19.0 snapshot hash
`adb4292f3ec25074ca70abcd2d5c7251` exposed a one-byte fill-stream drift:
`r2flutter -HHH` stopped after the `TypeArguments` cluster and reported the
later ObjectPool as `fill_not_parsed`. Consequently `-AAA` could not build the
synthetic PP image or apply ObjectPool string xrefs; `axt 0x31903` for
`https://www.game250522linecfg.xyz` was empty.
The cause was treating all Dart 2.x `Type::ReadFill` records alike. Through
2.18 their three references are followed by `ReadUnsigned(type_class_id)` and
a one-byte combined nullability/type-state field. Dart 2.19 replaces that pair
with one `ReadUnsigned(flags)`. Consuming the obsolete byte desynchronizes the
rest of the fill stream. Keep the old rule only before 2.19; with the corrected
rule the ObjectPool reaches cluster 652, `-p` reconstructs 44955 entries, and
`r2flutter -AAA` applies 11560 graph xrefs, including an `axt` result for
0x31903.
## Code xrefs need discarded instructions and correctly shifted PP offsets
The Dart 2.19 sample above has 29622 instruction-table entries, but only 7545
retained `Code` objects. `first_entry_with_code=22077` separates instructions
whose `Code` metadata was discarded from entries with retained metadata; it
does not separate non-executable stubs from application code. The VM writer
in `runtime/vm/app_snapshot.cc` orders discarded Code objects first. Both URL
loaders are in that prefix (IT indices 14623 and 15109).
An unlimited instruction-table extraction must use the table header's length,
not the clustered header's retained Code count. `-AAA` must collect all table
entrypoints in addition to named methods so these functions get basic blocks
and their PP loads are scanned. `has_code` still controls Code-index metadata
lookups; it must not decide whether instructions can be analyzed.
ARM64 decoders can return an already shifted `RArchValue.imm` for
`add x17, x27, 0x46, lsl 12`. Applying the text's shift to that value again
produces `0x46000000`, breaking the subsequent pool lookup. Read the unshifted
immediate from the instruction text and apply the shift exactly once.
Verified with `/tmp/mal/libapp.so` (identical to `libapp.so.noxrefs`):
- `0x85b028` in `fcn.0085afb0` loads `PP+0x46200` and now has a direct string
xref to `0x73986` (`https://reg.gptreg0706.xyz`).
- `0x836f98` in `fcn.00836f4c` loads `PP+0x4e6c8` and now has a direct string
xref to `0x31903` (`https://www.game250522linecfg.xyz`).
The unaligned `0x20022d` and `0x2086f5` sources are synthetic pool slots in
the serialized snapshot address range, not executable use sites. `-AA` keeps
those pool links; `-AAA` adds the instruction-to-string links. Regression tests
cover a cached discarded-Code table on Android, and real shifted PP loads on
Android w0rdle and iOS Runner without depending on the private samples.
## Dart 2.19 class metadata uses the modern fill walker
`-c` printed ~280 `size=0` classes for a Dart 2.19 Android sample because
`modern_can_extract_classes` only accepted `OBJECT_HEADER` (3.4+) snapshots.
The legacy class parser then failed on the first (unified `String`) cluster
and the string heuristic took over. The modern walker already handles
`CID_SHIFT1` (2.14-3.3) for `-H`, pools and xrefs, and the Class fill layout
(13 refs: name=0, library=7, super_type=9) matches 2.19 product snapshots.
Gating on `modern_supported` recovers 6914 classes, 1515 fields and 7398
methods on that sample.
## Class methods resolve entry points from InstructionTable slots
`Function` fills only carry a code index; `DartMethodInfo.entry_point` was
never set, so every class method printed `0x00000000` on all versions. The
index is the raw InstructionTable slot, not the `first_entry_with_code`
relative `DartInstructionTableEntry.code_index`. Slots below
`first_entry_with_code` belong to functions whose Code object was discarded
(`dwarf_stack_traces_mode`), but they still own real instructions, so they are
mapped too. On the 2.19 sample 22077 of 29622 slots are like that; `-f` used
to list only the 7545 Code-backed ones and now also gets the 4614 named
methods in the discarded range through `dart_app_merge_class_methods`.
InstructionTable names stay authoritative there: `dedup_instructions` makes
several methods share one body, so class-derived names only replace
placeholder (`func.*`, `method.fn_*`) names.
## Signed 64-bit varints use the 192 end-byte marker
`cs_read_tagged64` decoded the final varint byte as `raw - 64` instead of
`raw - 192` (`ReadStream::Read<T>` uses `kEndByteMarker`), adding
`128 << shift` to every Mint/Smi. Field offsets (`host_offset_or_field_id`)
came out as 128 + words, e.g. `_Closure._context` at `+0x428` instead of
`+0x28` on 64-bit, and the `SemanticsAction` const map key `tap` decoded as
129 instead of 1. `cs_read_tagged32` was already correct.
+13 -4
View File
@@ -265,6 +265,9 @@ static int dart_function_cmp(const DartFunction *fa, const DartFunction *fb) {
return 0;
}
// Above placeholder names (func.*, method.fn_*) but below any resolved name.
#define DART_CLASS_METHOD_NAME_QUALITY 20
static int dart_function_name_quality(const char *name) {
if (R_STR_ISEMPTY (name)) {
return 0;
@@ -476,14 +479,17 @@ static char *dart_format_method_full_name(const DartClassInfo *ci, const DartMet
if (!mi || R_STR_ISEMPTY (mi->name)) {
return NULL;
}
char *lib = dart_sanitize_component (ci && ci->library_name? ci->library_name: "app", true);
// Without a library name use the InstructionTable form (method.Owner.leaf)
// so both sources agree on the name of the same entry.
char *lib = ci && ci->library_name? dart_sanitize_component (ci->library_name, true): NULL;
char *owner = dart_sanitize_component (ci && ci->name? ci->name: mi->owner_name, false);
char *leaf = dart_format_method_leaf_name (ci? ci->name: mi->owner_name, mi);
const char *name = leaf? leaf: mi->name;
char *out = NULL;
if (R_STR_ISNOTEMPTY (owner)) {
out = r_str_newf ("method.%s.%s.%s", lib, owner, leaf? leaf: mi->name);
out = lib? r_str_newf ("method.%s.%s.%s", lib, owner, name): r_str_newf ("method.%s.%s", owner, name);
} else {
out = r_str_newf ("method.%s..%s", lib, leaf? leaf: mi->name);
out = lib? r_str_newf ("method.%s..%s", lib, name): r_str_newf ("method.%s", name);
}
r_name_filter (out, 0);
free (lib);
@@ -533,11 +539,14 @@ static void dart_app_merge_class_methods(DartApp *app) {
continue;
}
char *fullname = dart_format_method_full_name (ci, mi);
// InstructionTable names stay authoritative: deduplicated code is
// shared by several methods, so class metadata only fills the
// placeholder names the table could not resolve.
dart_app_add_or_update_fn (app,
fullname? fullname: mi->name,
dart_normalize_code_addr (mi->entry_point),
0,
dart_function_name_quality (fullname? fullname: mi->name),
R_MIN (dart_function_name_quality (fullname? fullname: mi->name), DART_CLASS_METHOD_NAME_QUALITY),
mi->signature);
free (fullname);
}
+43
View File
@@ -1488,6 +1488,46 @@ static void resolve_class_and_field_names(DartCtx *ctx, RList *class_list, RList
}
}
// Function fills only carry an InstructionTable slot; decode the table once
// and turn each method's slot into its entry address.
static void resolve_method_entry_points(DartCtx *ctx, RList *class_list) {
const int dump_snapshot_json = ctx->dump_snapshot_json;
const int dump_fns_limit = ctx->dump_fns_limit;
ctx->dump_snapshot_json = 0;
ctx->dump_fns_limit = 0;
RVecDartInstructionTableEntry *it = dart_pool_extract_instruction_table (ctx);
ctx->dump_snapshot_json = dump_snapshot_json;
ctx->dump_fns_limit = dump_fns_limit;
if (!it) {
return;
}
ut64 count = 0;
DartInstructionTableEntry *ie;
R_VEC_FOREACH (it, ie) {
count = R_MAX (count, ie->index + 1);
}
// Function::code_index is the raw InstructionTable slot, not the
// first_entry_with_code-relative index kept in ie->code_index. Slots below
// first_entry_with_code lost their Code object (discarded in AOT) but
// still own real instructions, so map them too.
ut64 *addr_by_code_index = calloc (count + 1, sizeof (ut64));
R_VEC_FOREACH (it, ie) {
addr_by_code_index[ie->index] = ie->address;
}
RListIter *iter, *miter;
DartClassInfo *ci;
DartMethodInfo *mi;
r_list_foreach (class_list, iter, ci) {
r_list_foreach (ci->methods, miter, mi) {
if (!mi->entry_point && mi->code_index < count) {
mi->entry_point = addr_by_code_index[mi->code_index];
}
}
}
free (addr_by_code_index);
dart_instruction_table_list_free (it);
}
RList *dart_pool_extract_classes(DartCtx *ctx) {
if (!ctx || !ctx->core) {
return NULL;
@@ -1693,6 +1733,9 @@ RList *dart_pool_extract_classes(DartCtx *ctx) {
}
}
dart_ctx_fini_layout (ctx, layout_owned);
if (modern_classes) {
resolve_method_entry_points (ctx, class_list);
}
return class_list;
}
+17 -3
View File
@@ -392,6 +392,7 @@ typedef struct {
// handful of clusters. Their readers are gated on this flag; everything
// else is shared.
bool legacy_format;
bool legacy_type_combined_byte;
bool shift1_format;
bool closure_variable;
// every CID this layout defines, indexed by kind; -1 when the kind is absent
@@ -468,6 +469,7 @@ static ModernCidCache modern_cid_cache_init(const DartVerLayout *layout) {
.typed_data_internal_limit = dart_cid_typed_data_limit (layout),
.typed_data_stride = dart_cid_typed_data_stride (layout),
.legacy_format = modern_layout_is_legacy (layout),
.legacy_type_combined_byte = layout && dart_version_compare (layout->dart_version, "2.19.0") == -1,
.shift1_format = layout && layout->tag_style == DART_TAG_STYLE_CID_SHIFT1,
.closure_variable = layout && layout->closure_variable,
};
@@ -1077,14 +1079,26 @@ static ModernFillSpec modern_get_fill_spec(const ModernCidCache *cids, int cid)
static const ModernFillSpecRule legacy_rules[] = {
// FfiTrampolineData: ReadFromTo (4) + ReadUnsigned (callback_id).
{ DART_CID_FFI_TRAMPOLINE_DATA, MODERN_FILL_REFS, 4, -1, -1, 1, { MODERN_SCALAR_UNSIGNED } },
// Type: ReadFromTo (3) + ReadUnsigned (type_class_id) + Read<uint8_t>.
{ DART_CID_TYPE, MODERN_FILL_REFS, 3, -1, -1, 2, { MODERN_SCALAR_UNSIGNED, MODERN_SCALAR_UINT8 } },
// TypeParameter: ReadFromTo (3) + Read<int32_t> + 3x Read<uint8_t>.
{ DART_CID_TYPE_PARAMETER, MODERN_FILL_REFS, 3, -1, -1, 4, { MODERN_SCALAR_TAGGED32, MODERN_SCALAR_UINT8, MODERN_SCALAR_UINT8, MODERN_SCALAR_UINT8 } },
// SubtypeTestCache::ReadFill reads only the cache_ ref.
{ DART_CID_SUBTYPE_TEST_CACHE, MODERN_FILL_REFS, 1, -1, -1, 0, { 0 } },
};
if (cids->legacy_format) {
if (modern_cid_eq (cid, cids->type) && cids->legacy_type_combined_byte) {
// Through Dart 2.18 Type::ReadFill has type_class_id followed by a
// combined nullability/type-state byte. Dart 2.19 replaced those with
// a single flags ReadUnsigned. Keeping the old byte for 2.19 shifts
// every following fill cluster and makes the ObjectPool unreachable.
return modern_fill_spec (&(ModernFillSpecRule){
.kind = MODERN_FILL_REFS,
.num_refs = 3,
.name_idx = -1,
.owner_idx = -1,
.scalar_count = 2,
.scalars = { MODERN_SCALAR_UNSIGNED, MODERN_SCALAR_UINT8 },
});
}
for (size_t i = 0; i < R_ARRAY_SIZE (legacy_rules); i++) {
if (modern_cid_of_kind (cids, legacy_rules[i].cid_kind) == cid) {
return modern_fill_spec (&legacy_rules[i]);
@@ -4450,7 +4464,7 @@ static bool modern_load_vm_base_strings(DartCtx *ctx, char **strings_by_ref, ut6
}
static bool modern_can_extract_classes(DartCtx *ctx) {
if (!ctx || !ctx->layout || ctx->layout->tag_style != DART_TAG_STYLE_OBJECT_HEADER) {
if (!modern_supported (ctx)) {
return false;
}
// Both pointer widths are supported: the class/field fill readers decode
+3 -1
View File
@@ -460,7 +460,9 @@ static int decode_pool_and_emit(DartItEmitRequest *req) {
goto cleanup;
}
const ut64 layout_cap = ctx->layout && ctx->layout->it_cap? ctx->layout->it_cap: 20000;
const ut64 max_entries = req->max_entries > 0? req->max_entries: (req->on_it? itlen: layout_cap);
// itlen counts retained Code objects, not instructions whose Code metadata
// was discarded. Let the table header bound an unlimited IT extraction.
const ut64 max_entries = req->max_entries > 0? req->max_entries: (req->on_it? 0: layout_cap);
req->data_image_base = data_image_base;
req->data_image_end = data_image_end;
req->itlen = itlen;
+10 -19
View File
@@ -483,33 +483,24 @@ bool cs_read_tagged32(ClusterStream *s, ut32 *out) {
return false;
}
// ReadStream::Read<int64_t>: 7-bit groups, then a final byte holding a signed
// value in [-64, 63] biased by kEndByteMarker (192).
bool cs_read_tagged64(ClusterStream *s, int64_t *out) {
ut8 raw = 0;
if (!cs_read_u8 (s, &raw)) {
return false;
}
int8_t b = (int8_t)raw;
if (b < 0) {
if (out) {
*out = (int64_t)b + 192;
}
return true;
}
int64_t v = (int64_t)raw;
int shift = 7;
for (int i = 1; i < 10; i++) {
ut64 v = 0;
int shift = 0;
for (int i = 0; i < 10; i++) {
ut8 raw = 0;
if (!cs_read_u8 (s, &raw)) {
return false;
}
b = (int8_t)raw;
if (b < 0) {
v |= ((int64_t)b + 192) << shift;
if (raw > 0x7f) {
v |= (ut64) ((int64_t)raw - 192) << shift;
if (out) {
*out = v;
*out = (int64_t)v;
}
return true;
}
v |= ((int64_t)raw) << shift;
v |= (ut64)raw << shift;
shift += 7;
}
return false;
+2 -1
View File
@@ -564,6 +564,7 @@ R_API RLibStruct radare_plugin = {
.type = R_LIB_TYPE_CORE,
.data = &r_core_plugin_r2flutter,
.version = R2_VERSION,
.abiversion = R2_ABIVERSION
.abiversion = R2_ABIVERSION,
.pkgname = "r2flutter"
};
#endif
+32 -5
View File
@@ -302,6 +302,22 @@ static bool flutter_parse_shifted_imm(const char *line, ut64 *imm) {
if (!lsl) {
return false;
}
// RArchValue.imm may already include the shift (depending on the ARM
// decoder). Read the unshifted third operand before applying it once.
const char *arg = strchr (line, ',');
arg = arg? strchr (arg + 1, ','): NULL;
if (!arg || arg >= lsl) {
return false;
}
arg++;
while (isspace ((ut8)*arg) || *arg == '#') {
arg++;
}
char *end;
ut64 value = strtoull (arg, &end, 0);
if (end == arg) {
return false;
}
const char *p = lsl + 3;
while (isspace ((ut8)*p)) {
p++;
@@ -312,11 +328,11 @@ static bool flutter_parse_shifted_imm(const char *line, ut64 *imm) {
if (!isdigit ((ut8)*p)) {
return false;
}
int shift = atoi (p);
if (shift <= 0 || shift >= 63) {
ut64 shift = strtoull (p, NULL, 0);
if (shift >= 64) {
return false;
}
*imm <<= shift;
*imm = value << shift;
return true;
}
@@ -440,7 +456,7 @@ static bool flutter_parse_opinfo(const RAnalOp *op, FlutterOpInfo *info) {
if (info->n_regs == 0 && (type == R_ANAL_OP_TYPE_RCALL || type == R_ANAL_OP_TYPE_UCALL || type == R_ANAL_OP_TYPE_ICALL || type == R_ANAL_OP_TYPE_IRCALL)) {
flutter_parse_first_operand_reg (op->mnemonic, info);
}
if (info->has_imm && R_STR_ISNOTEMPTY (op->mnemonic)) {
if (type == R_ANAL_OP_TYPE_ADD && info->has_imm && R_STR_ISNOTEMPTY (op->mnemonic)) {
(void)flutter_parse_shifted_imm (op->mnemonic, &info->imm);
}
return info->n_regs > 0 || info->has_mem || info->has_imm;
@@ -539,7 +555,7 @@ static bool flutter_model_load(FlutterAnalModel *model, RCore *core, DartCtx *dc
}
DartCtx ctx = *dctx;
ctx.core = core;
dart_recovery_model_load (&ctx, model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS | DART_RECOVERY_METHOD_INDEX);
dart_recovery_model_load (&ctx, model, DART_RECOVERY_STRINGS | DART_RECOVERY_CLASSES | DART_RECOVERY_CLASS_FIELDS | DART_RECOVERY_METHOD_INDEX | DART_RECOVERY_IT);
if (!model->strings || !model->classes) {
flutter_model_fini (model);
return false;
@@ -873,6 +889,17 @@ static RVecFlutterEntry *flutter_collect_entries(RCore *core, const DartApp *app
flutter_collect_entries_from_flags (seen, entries, core);
flutter_collect_entries_from_app (seen, entries, app);
flutter_collect_entries_from_model (seen, entries, model);
// Entries before first_entry_with_code still contain executable Dart code;
// only their Code objects were discarded by the precompiler.
if (model->it_entries) {
DartInstructionTableEntry *entry;
R_VEC_FOREACH (model->it_entries, entry) {
if (entry->address && !ht_up_find (seen, entry->address, NULL)) {
RVecFlutterEntry_push_back (entries, &entry->address);
ht_up_insert (seen, entry->address, entries);
}
}
}
ht_up_free (seen);
return entries;
}
+1 -1
View File
@@ -6,7 +6,7 @@
"expect": {
"contains": [
"class _Closure@0150898 (size=64)",
"+0x428 dynamic _context@0150898",
"+0x28 dynamic _context@0150898",
"class _Uri@0150898 (size=96)",
"+0x0 dynamic _portMap@1026248 static final"
],
+4 -4
View File
@@ -8,8 +8,8 @@ EOF
EXPECT=<<EOF
class Pointer (size=32)
methods:
0x00000000 get:hashCode (GetterFunction)
0x00000000 == (RegularFunction)
0x00115f2c get:hashCode (GetterFunction)
0x001299fc == (RegularFunction)
class NativeType (size=16)
class NativeFunction (size=16)
EOF
@@ -27,8 +27,8 @@ EXPECT=<<EOF
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00000000 get:hashCode (GetterFunction)
# method 0x00000000 == (RegularFunction)
# method 0x00115f2c get:hashCode (GetterFunction)
# method 0x001299fc == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+4 -4
View File
@@ -8,8 +8,8 @@ EOF
EXPECT=<<EOF
class Pointer (size=32)
methods:
0x00000000 get:hashCode (GetterFunction)
0x00000000 == (RegularFunction)
0x00c6ab64 get:hashCode (GetterFunction)
0x00d8bde8 == (RegularFunction)
class NativeType (size=16)
class NativeFunction (size=16)
EOF
@@ -43,8 +43,8 @@ EXPECT=<<EOF
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00000000 get:hashCode (GetterFunction)
# method 0x00000000 == (RegularFunction)
# method 0x00c6ab64 get:hashCode (GetterFunction)
# method 0x00d8bde8 == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+9 -9
View File
@@ -33,14 +33,14 @@ ic dart_Future
EOF
EXPECT=<<EOF
class dart_Future
0x00000000 method dart_Future sC Future_error
0x00000000 method dart_Future sC Future_
0x00000000 method dart_Future s _anonymous_closure_
0x00000000 method dart_Future sC Future_value
0x00000000 method dart_Future s wait
0x00000000 method dart_Future s handleError
0x00000000 method dart_Future sC Future_delayed
0x00000000 method dart_Future sC Future_microtask
0x00000000 method dart_Future sC Future_sync
0x005d4f54 method dart_Future sC Future_error
0x005d70cc method dart_Future sC Future_
0x005d71b8 method dart_Future s _anonymous_closure_
0x005e4a98 method dart_Future sC Future_value
0x0060d7d4 method dart_Future s wait
0x0060dfac method dart_Future s handleError
0x0062edcc method dart_Future sC Future_delayed
0x006d5018 method dart_Future sC Future_microtask
0x00943c08 method dart_Future sC Future_sync
EOF
RUN
+1 -1
View File
@@ -25,7 +25,7 @@ map
true
array
48
129
1
instance
SemanticsAction
1
+2 -2
View File
@@ -10,8 +10,8 @@ EXPECT=<<EOF
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00000000 get:hashCode (GetterFunction)
# method 0x00000000 == (RegularFunction)
# method 0x00115f2c get:hashCode (GetterFunction)
# method 0x001299fc == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+4 -4
View File
@@ -8,8 +8,8 @@ EOF
EXPECT=<<EOF
class Pointer (size=32)
methods:
0x00000000 get:hashCode (GetterFunction)
0x00000000 == (RegularFunction)
0x00c6ab64 get:hashCode (GetterFunction)
0x00d8bde8 == (RegularFunction)
class NativeType (size=16)
class NativeFunction (size=16)
EOF
@@ -27,8 +27,8 @@ EXPECT=<<EOF
# class Pointer (size=32)
# type_params: 1
"td struct dart_Pointer { };"
# method 0x00000000 get:hashCode (GetterFunction)
# method 0x00000000 == (RegularFunction)
# method 0x00c6ab64 get:hashCode (GetterFunction)
# method 0x00d8bde8 == (RegularFunction)
# class NativeType (size=16)
EOF
RUN
+3 -3
View File
@@ -8,8 +8,8 @@ EOF
EXPECT=<<EOF
metadata class.name class Pointer [ref=36652] -> string Pointer [ref=151] @ 0x1b24d0
metadata class.library class Pointer [ref=36652] -> library library#41733 [ref=41733]
metadata method.owner method Pointer.get:hashCode [ref=31064] -> class Pointer [ref=36652]
metadata method.name method Pointer.get:hashCode [ref=31064] -> string get:hashCode [ref=7217] @ 0x2b0230
metadata method.owner method Pointer.get:hashCode [ref=31064] @ 0x115f2c -> class Pointer [ref=36652]
metadata method.name method Pointer.get:hashCode [ref=31064] @ 0x115f2c -> string get:hashCode [ref=7217] @ 0x2b0230
EOF
RUN
@@ -21,7 +21,7 @@ L ../src/r2/core_flutter.so
r2flutter -j -l 3 -x
EOF
EXPECT=<<EOF
[{"kind":"class.name","origin":"metadata","src":{"type":"class","name":"Pointer","ref":36652},"dst":{"type":"string","name":"Pointer","ref":151,"addr":1778896}},{"kind":"class.library","origin":"metadata","src":{"type":"class","name":"Pointer","ref":36652},"dst":{"type":"library","name":"library#41733","ref":41733}},{"kind":"method.owner","origin":"metadata","src":{"type":"method","name":"Pointer.get:hashCode","ref":31064},"dst":{"type":"class","name":"Pointer","ref":36652}}]
[{"kind":"class.name","origin":"metadata","src":{"type":"class","name":"Pointer","ref":36652},"dst":{"type":"string","name":"Pointer","ref":151,"addr":1778896}},{"kind":"class.library","origin":"metadata","src":{"type":"class","name":"Pointer","ref":36652},"dst":{"type":"library","name":"library#41733","ref":41733}},{"kind":"method.owner","origin":"metadata","src":{"type":"method","name":"Pointer.get:hashCode","ref":31064,"addr":1138476},"dst":{"type":"class","name":"Pointer","ref":36652}}]
EOF
RUN