From 73be86c39e6c13139e0755c8ff2f8e95751d4a72 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Thu, 22 Oct 2026 09:11:44 +0800 Subject: [PATCH] fix(android): decode guardian output as UTF-8 streams --- src/android/JadxWorkspaceRoot.test.ts | 89 +++++++++++++++++++++++++++ src/android/JadxWorkspaceRoot.ts | 10 +-- 2 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/android/JadxWorkspaceRoot.test.ts diff --git a/src/android/JadxWorkspaceRoot.test.ts b/src/android/JadxWorkspaceRoot.test.ts new file mode 100644 index 0000000000..5d86018ef3 --- /dev/null +++ b/src/android/JadxWorkspaceRoot.test.ts @@ -0,0 +1,89 @@ +import { EventEmitter } from "node:events"; +import { win32 } from "node:path"; +import { PassThrough } from "node:stream"; + +import { afterEach, expect, it, vi } from "vitest"; + +const { spawnMock, tmpdirMock, lstatMock } = vi.hoisted(() => ({ + spawnMock: vi.fn(), + tmpdirMock: vi.fn(), + lstatMock: vi.fn(), +})); + +vi.mock("node:child_process", async (importOriginal) => ({ + ...(await importOriginal()), + spawn: spawnMock, +})); +vi.mock("node:os", async (importOriginal) => ({ + ...(await importOriginal()), + tmpdir: tmpdirMock, +})); +vi.mock("node:fs/promises", async (importOriginal) => ({ + ...(await importOriginal()), + lstat: lstatMock, +})); + +import { createJadxWorkspaceRoot } from "./JadxWorkspaceRoot.js"; + +afterEach(() => { + vi.restoreAllMocks(); + vi.clearAllMocks(); +}); + +it("decodes a Windows guardian path across split UTF-8 stdout writes", async () => { + const previousPlatform = process.platform; + Object.defineProperty(process, "platform", { + configurable: true, + value: "win32", + }); + const parent = "C:\\Users\\研究者\\AppData\\Local\\Temp"; + const path = win32.join(parent, `rea-android-${"a".repeat(32)}`); + const child = Object.assign(new EventEmitter(), { + stdin: new PassThrough(), + stdout: new PassThrough(), + stderr: new PassThrough(), + unref: vi.fn(), + }); + child.stdin.once("finish", () => { + child.stdout.end(); + child.stderr.end(); + child.emit("close", 0, null); + }); + tmpdirMock.mockReturnValue(parent); + const missingPath = Object.assign(new Error("missing after cleanup"), { + code: "ENOENT", + }); + lstatMock.mockRejectedValue(missingPath); + spawnMock.mockReturnValue(child); + const ready = Buffer.from( + `${JSON.stringify({ + path, + volumeSerial: "0123456789abcdef", + fileId: "0123456789abcdef0123456789abcdef", + privateDacl: true, + })}\n`, + ); + const split = ready.indexOf(Buffer.from("研")) + 1; + + try { + const start = createJadxWorkspaceRoot(); + setImmediate(() => { + child.stdout.write(ready.subarray(0, split), () => { + setImmediate(() => child.stdout.write(ready.subarray(split))); + }); + }); + + const root = await start; + try { + expect(root.path).toBe(path); + } finally { + await expect(root.close()).resolves.toBeUndefined(); + } + expect(lstatMock).toHaveBeenCalledWith(path); + } finally { + Object.defineProperty(process, "platform", { + configurable: true, + value: previousPlatform, + }); + } +}); diff --git a/src/android/JadxWorkspaceRoot.ts b/src/android/JadxWorkspaceRoot.ts index 15b862320c..d92727e99a 100644 --- a/src/android/JadxWorkspaceRoot.ts +++ b/src/android/JadxWorkspaceRoot.ts @@ -105,8 +105,9 @@ class WindowsJadxWorkspaceRoot implements JadxWorkspaceRoot { }, }); let diagnostics = ""; - child.stderr.on("data", (chunk: Buffer) => { - diagnostics = (diagnostics + chunk.toString()).slice(-8192); + child.stderr.setEncoding("utf8"); + child.stderr.on("data", (chunk: string) => { + diagnostics = (diagnostics + chunk).slice(-8192); }); const exit = new Promise((resolve) => { child.once("error", (error: Error) => @@ -116,8 +117,9 @@ class WindowsJadxWorkspaceRoot implements JadxWorkspaceRoot { }); const ready = new Promise((resolve, reject) => { let frame = ""; - child.stdout.on("data", (chunk: Buffer) => { - frame += chunk.toString(); + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (chunk: string) => { + frame += chunk; if (frame.length > 4096) { reject( new Error("Android workspace guardian readiness exceeded 4 KiB"),