From 1df7ac9320cb8cfbabb5fa3b2ca02f3a86b2c376 Mon Sep 17 00:00:00 2001 From: Entrpi <133157614+Entrpi@users.noreply.github.com> Date: Wed, 4 Nov 2026 22:09:33 +1100 Subject: [PATCH] fix(ghidra): update the host no-return probe for the current bridge (#8462) The host no-return verifier, scripts/verify-real-ghidra-noreturn.mjs, fails before its Evidence checks. ReaNoReturnProbe calls private bridge methods through reflection, and two bridge changes left the probe behind: - d4bd65c53 added a Function parameter to noReturnCallLimitations, and 225f26a5f renamed the method to functionBoundaryLimitations. The probe still looked up noReturnCallLimitations(Iterable) and stopped with NoSuchMethodException. - 01a1b3fda made procedureReferences return a function observation envelope. The probe read references and unresolved_calls from the top level of that envelope instead of from its value. Pass each function and its instructions to functionBoundaryLimitations, and read procedureReferences results from the envelope value. The require checks are unchanged. CI does not run this verifier. These edits are the probe repair from robotaro's open draft that treats demangled C++ runtime imports as returning, copied line for line, so either change merges cleanly after the other. Co-authored-by: robotaro <1141230+robotaro@users.noreply.github.com> --- .../conformance/ghidra/ReaNoReturnProbe.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/conformance/ghidra/ReaNoReturnProbe.java b/tests/conformance/ghidra/ReaNoReturnProbe.java index 19f98f346a..c5ca14817f 100644 --- a/tests/conformance/ghidra/ReaNoReturnProbe.java +++ b/tests/conformance/ghidra/ReaNoReturnProbe.java @@ -123,7 +123,8 @@ public class ReaNoReturnProbe extends GhidraScript { query.addProperty("direction", "outgoing"); Method read = bridge.getClass().getDeclaredMethod("procedureReferences", JsonObject.class); read.setAccessible(true); - JsonObject output = (JsonObject) read.invoke(bridge, query); + // procedureReferences returns a function observation envelope. + JsonObject output = ((JsonObject) read.invoke(bridge, query)).getAsJsonObject("value"); require("interior_reference_keeps_known_procedure", output.getAsJsonArray("references").asList().stream().anyMatch( edge -> !edge.getAsJsonObject().get("target_procedure").isJsonNull() && edge.getAsJsonObject().getAsJsonObject("target_procedure").get("name").getAsString().equals(target.getName()))); @@ -185,13 +186,13 @@ public class ReaNoReturnProbe extends GhidraScript { query.addProperty("direction", "outgoing"); Method edges = bridge.getClass().getDeclaredMethod("procedureReferences", JsonObject.class); edges.setAccessible(true); - JsonObject known = (JsonObject) edges.invoke(bridge, query); + JsonObject known = ((JsonObject) edges.invoke(bridge, query)).getAsJsonObject("value"); require("non_call_known_reference_is_not_unresolved", known.getAsJsonArray("unresolved_calls").isEmpty()); references.delete(added); Address unknown = currentProgram.getMemory().getMaxAddress().add(0x1000); added = references.addMemoryReference(source.getAddress(), unknown, RefType.UNCONDITIONAL_CALL, SourceType.USER_DEFINED, -1); - JsonObject unresolved = (JsonObject) edges.invoke(bridge, query); + JsonObject unresolved = ((JsonObject) edges.invoke(bridge, query)).getAsJsonObject("value"); require("non_call_unknown_reference_keeps_site", unresolved.getAsJsonArray("unresolved_calls").size() == 1); } finally { references.delete(added); @@ -253,14 +254,18 @@ public class ReaNoReturnProbe extends GhidraScript { !adjacentCaller.getBody().contains(adjacentEntry)); GhidraScript bridge = script("ReaGhidraBridge.java", new File(directory, "ReaGhidraBridge.java")); - Method warnings = bridge.getClass().getDeclaredMethod("noReturnCallLimitations", Iterable.class); + Method warnings = bridge.getClass().getDeclaredMethod( + "functionBoundaryLimitations", Function.class, Iterable.class); warnings.setAccessible(true); - Object reported = warnings.invoke(bridge, currentProgram.getListing().getInstructions(function("rea_unknown").getBody(), true)); + Object reported = warnings.invoke(bridge, function("rea_unknown"), + currentProgram.getListing().getInstructions(function("rea_unknown").getBody(), true)); require("unknown_boundary_named", reported instanceof List values && values.stream().anyMatch(value -> value.toString().contains("rea_unknown_stop") && value.toString().contains("fallthrough"))); - Object healthy = warnings.invoke(bridge, currentProgram.getListing().getInstructions(function("rea_tls").getBody(), true)); + Object healthy = warnings.invoke(bridge, function("rea_tls"), + currentProgram.getListing().getInstructions(function("rea_tls").getBody(), true)); require("healthy_boundary_no_warning", healthy instanceof List values && values.isEmpty()); - Object guarded = warnings.invoke(bridge, currentProgram.getListing().getInstructions(adjacentCaller.getBody(), true)); + Object guarded = warnings.invoke(bridge, adjacentCaller, + currentProgram.getListing().getInstructions(adjacentCaller.getBody(), true)); require("unrepaired_boundary_named", guarded instanceof List values && values.stream().anyMatch(value -> value.toString().contains("malloc"))); Method initialize = bridge.getClass().getDeclaredMethod("initializeDecompiler");