rea/tests/conformance/swift/ReaSwiftDecoderRegressions....

146 lines
7.6 KiB
Java

// Producer regressions run inside a real, ephemeral Ghidra analysis of the
// source-owned AArch64 fixture. No target code is executed and no decoded JSON
// fixture substitutes for the Java decoder under test.
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import ghidra.app.util.headless.HeadlessScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.lang.Register;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolIterator;
public class ReaSwiftDecoderRegressions extends HeadlessScript {
private ReaGhidraBridge decoder;
private Class<?> budgetClass;
@Override
public void run() throws Exception {
decoder = new ReaGhidraBridge();
setInherited(decoder, "currentProgram", currentProgram);
setInherited(decoder, "monitor", monitor);
setInherited(decoder, "sessionDefaultAddressSpace", currentProgram.getAddressFactory().getDefaultAddressSpace());
budgetClass = Class.forName("ReaGhidraBridge$SwiftLiteralBudget", true, decoder.getClass().getClassLoader());
Function copied = function("rea_swift_decoder_copied");
JsonArray copiedValues = literals(decode(copied, 65536));
require(copiedValues.size() > 0, "copied constant pair was not decoded");
for (var element : copiedValues) {
require(element.getAsJsonObject().get("value").getAsString().equals("ok"), "copied words invented text");
}
for (String name : new String[] {"select", "selected_copy", "padding", "utf8"}) {
Object result = decode(function("rea_swift_decoder_" + name), 65536);
require(literals(result).isEmpty(), name + " produced an invented Swift literal");
require(omissions(result).isEmpty(), name + " was confused with capacity exhaustion");
}
for (String name : new String[] {"select", "selected_copy"}) {
Function selected = function("rea_swift_decoder_" + name);
require((Boolean) invoke("selectedValue", new Class<?>[] {Function.class, Instruction.class, Register.class}, selected, returnInstruction(selected), currentProgram.getRegister("x0")),
name + " did not retain conditional-select provenance");
}
require(invoke("swiftLiteralLayout", new Class<?>[] {long.class, long.class}, 0x6261L, 0xe100000000000000L) == null,
"nonzero small-string padding was accepted");
require(invoke("swiftLiteralLayout", new Class<?>[] {long.class, long.class}, 0xc0L, 0xa100000000000000L) == null,
"malformed small-string UTF-8 was accepted");
rejectLarge("rea_swift_decoder_bad_terminator", 0xd000000000000003L);
rejectLarge("rea_swift_decoder_bad_large_utf8", 0x1000000000000001L);
Object exhausted = decode(copied, 0);
require(literals(exhausted).isEmpty(), "capacity failure published decoded small text");
JsonArray omitted = omissions(exhausted);
require(omitted.size() == 1, "capacity failure did not stop subsequent use sites");
JsonObject omission = omitted.get(0).getAsJsonObject();
require(omission.get("reason").getAsString().equals("capacity"), "capacity omission lost its reason");
require(!omission.has("value"), "omission claimed a retained literal value");
require(getScriptArgs().length == 1, "Expected owned result path");
Files.writeString(Path.of(getScriptArgs()[0]), "REA_SWIFT_DECODER_SAFEGUARDS_OK\n");
}
private Object decode(Function function, long remaining) throws Exception {
Constructor<?> constructor = budgetClass.getDeclaredConstructor(long.class);
constructor.setAccessible(true);
return invoke("swiftStringLiterals", new Class<?>[] {Function.class, budgetClass}, function, constructor.newInstance(remaining));
}
private void rejectLarge(String name, long word) throws Exception {
Address address = symbol(name).getAddress();
long object = 0x8000000000000000L | ((address.getOffset() - 0x20) & 0x00ffffffffffffffL);
Object layout = invoke("swiftLiteralLayout", new Class<?>[] {long.class, long.class}, word, object);
require(layout != null, "large negative fixture did not reach the memory-text decoder");
require(invoke("swiftLargeLiteralText", new Class<?>[] {layout.getClass()}, layout) == null,
name + " was accepted as literal text");
}
private Instruction returnInstruction(Function function) {
InstructionIterator instructions = currentProgram.getListing().getInstructions(function.getBody(), true);
while (instructions.hasNext()) {
Instruction instruction = instructions.next();
if (instruction.getMnemonicString().equalsIgnoreCase("ret")) return instruction;
}
throw new AssertionError("Fixture has no return instruction");
}
private Function function(String name) {
FunctionIterator functions = currentProgram.getFunctionManager().getFunctions(true);
Function found = null;
while (functions.hasNext()) {
Function candidate = functions.next();
if (!candidate.getName().equals(name) && !candidate.getName().equals("_" + name)) continue;
require(found == null, "ambiguous fixture function " + name);
found = candidate;
}
require(found != null, "missing fixture function " + name);
return found;
}
private Symbol symbol(String name) {
SymbolIterator symbols = currentProgram.getSymbolTable().getAllSymbols(true);
Symbol found = null;
while (symbols.hasNext()) {
Symbol candidate = symbols.next();
if (!candidate.getName().equals(name) && !candidate.getName().equals("_" + name)) continue;
require(found == null, "ambiguous fixture symbol " + name);
found = candidate;
}
require(found != null, "missing fixture symbol " + name);
return found;
}
private Object invoke(String name, Class<?>[] types, Object... arguments) throws Exception {
Method method = ReaGhidraBridge.class.getDeclaredMethod(name, types);
method.setAccessible(true);
return method.invoke(decoder, arguments);
}
private JsonArray literals(Object result) throws Exception { return array(result, "literals"); }
private JsonArray omissions(Object result) throws Exception { return array(result, "omissions"); }
private JsonArray array(Object result, String name) throws Exception {
Method method = result.getClass().getDeclaredMethod(name);
method.setAccessible(true);
return (JsonArray) method.invoke(result);
}
private void setInherited(Object target, String name, Object value) throws Exception {
for (Class<?> type = target.getClass(); type != null; type = type.getSuperclass()) {
try {
Field field = type.getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
return;
} catch (NoSuchFieldException nextOwner) { }
}
throw new AssertionError("Missing Ghidra state field " + name);
}
private static void require(boolean condition, String message) {
if (!condition) throw new AssertionError(message);
}
}