Skip to content

Commit b7ff833

Browse files
committed
decomp: demote same-width wrapper structs on scalar int-extension ops
Ghidra over-propagates a single-field struct (e.g. CRC32) onto INT_ZEXT/ INT_SEXT temps, which hard-fails Sema. Serializer demotes a same-width record on strictly-scalar ops to a scalar surrogate; emitter also coerces a non-arithmetic int-extension target to a same-width integer.
1 parent aca2162 commit b7ff833

3 files changed

Lines changed: 69 additions & 6 deletions

File tree

include/patchestry/AST/OperationBuilder.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,14 @@ namespace patchestry::ast {
302302
unsigned target_bytes = 0
303303
);
304304

305+
// Coerce a non-arithmetic INT_ZEXT/INT_SEXT target (e.g. a same-width
306+
// wrapper struct Ghidra mis-propagated onto a scalar temp) to a
307+
// same-width unsigned integer so the extension is valid C. Returns the
308+
// type unchanged when it is already integer/pointer/enum/bool.
309+
clang::QualType integer_target_for_int_ext(
310+
clang::ASTContext &ctx, clang::QualType target_type, unsigned op_bytes
311+
);
312+
305313
/// Materialize a non-void call result into a temporary variable.
306314
/// Creates VarDecl + DeclStmt (pushed to pending for hoisting),
307315
/// assignment (call stays in-place), and returns the appropriate

lib/patchestry/AST/OperationStmt.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,32 @@ namespace patchestry::ast {
484484
LOG_FATAL("cast_pointer_to_int: unhandled PtrToIntExtension value");
485485
}
486486

487+
// INT_ZEXT / INT_SEXT are inherently integer-producing, but Ghidra's type
488+
// propagation occasionally labels the result with a same-width record (e.g.
489+
// a single-field wrapper `struct CRC32`). Casting an enum/int to that
490+
// struct hard-fails Sema ("converting 'enum X' to incompatible type 'struct
491+
// Y'"). When the declared target is not a usable arithmetic/pointer type,
492+
// substitute a same-width unsigned integer so the extension is valid C; the
493+
// write side reinterprets back into the record if the output truly needs it.
494+
// (The serializer's #250 scalar-op demotion now prevents this at the source;
495+
// this keeps already-captured JSON working and is defence-in-depth.)
496+
clang::QualType OpBuilder::integer_target_for_int_ext(
497+
clang::ASTContext &ctx, clang::QualType target_type, unsigned op_bytes
498+
) {
499+
if (target_type.isNull()) {
500+
return target_type;
501+
}
502+
if (target_type->isIntegerType() || target_type->isPointerType()
503+
|| target_type->isEnumeralType() || target_type->isBooleanType())
504+
{
505+
return target_type;
506+
}
507+
unsigned bits = op_bytes ? op_bytes * 8u
508+
: static_cast< unsigned >(ctx.getTypeSize(target_type));
509+
auto widened = ctx.getIntTypeForBitwidth(bits, /*Signed=*/false);
510+
return widened.isNull() ? target_type : widened;
511+
}
512+
487513
clang::Expr *OpBuilder::narrow_aggregate_to_integer(
488514
clang::ASTContext &ctx, clang::Expr *expr, clang::SourceLocation loc,
489515
unsigned target_bytes
@@ -2774,6 +2800,7 @@ namespace patchestry::ast {
27742800
return {};
27752801
}
27762802
auto target_type = *target_type_opt;
2803+
target_type = integer_target_for_int_ext(ctx, target_type, op.output ? op.output->size : 0u);
27772804

27782805
if (input_expr->getType()->isPointerType()) {
27792806
// PerformImplicitConversion asserts inside clang 22 (SemaExprCXX.cpp:4681)
@@ -2837,6 +2864,7 @@ namespace patchestry::ast {
28372864
return {};
28382865
}
28392866
auto target_type = *target_type_opt;
2867+
target_type = integer_target_for_int_ext(ctx, target_type, op.output ? op.output->size : 0u);
28402868

28412869
if (input_expr->getType()->isPointerType()) {
28422870
// Sign-extension over a pointer requires the intptr_t intermediate;

scripts/ghidra/util/PcodeSerializer.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,33 @@ static DataType demoteArrayIfMatching(
856856
return surrogate != null ? surrogate : t;
857857
}
858858

859+
// Demote a Composite (struct/union) DataType of exactly `varBytes`
860+
// bytes (after peeling TypeDefs) to a same-width scalar surrogate; else
861+
// return `t` as-is. Companion to demoteArrayIfMatching (#250): a
862+
// strictly-scalar p-code op (INT_ZEXT, INT_NEGATE, ...) can never yield a
863+
// record value, but Ghidra's type propagation occasionally attaches a
864+
// single-field wrapper struct (e.g. `struct CRC32 {uint m_state;}`) to
865+
// such a varnode. Left intact it reaches the emitter as `(struct CRC32)x`
866+
// and hard-fails Sema ("used type 'struct X' where arithmetic or pointer
867+
// type is required").
868+
static DataType demoteRecordIfMatching(
869+
DataType t, int varBytes, DataTypeManager dtm) {
870+
if (t == null) {
871+
return t;
872+
}
873+
DataType base = t;
874+
for (int depth = 0;
875+
depth < TYPEDEF_PEEL_MAX_DEPTH && base instanceof TypeDef;
876+
++depth) {
877+
base = ((TypeDef) base).getBaseDataType();
878+
}
879+
if (!(base instanceof Composite) || t.getLength() != varBytes) {
880+
return t;
881+
}
882+
DataType surrogate = sizedSurrogate(varBytes, dtm);
883+
return surrogate != null ? surrogate : t;
884+
}
885+
859886
// 1-byte `undefined`/`DefaultDataType` only — char/uint8 carry user intent.
860887
private static boolean isUndefinedAtom(DataType t) {
861888
if (t == null) {
@@ -1718,9 +1745,9 @@ void serializeInput(PcodeOp pcodeOp, Varnode node) throws Exception {
17181745
// COPY output and as scalar when it's a PIECE input.
17191746
DataType emitted = chooseEmittedType(node, highVariable);
17201747
if (isScalarPcodeOp(pcodeOp.getOpcode())) {
1721-
emitted = demoteArrayIfMatching(
1722-
emitted, node.getSize(),
1723-
currentProgram.getDataTypeManager());
1748+
DataTypeManager dtm = currentProgram.getDataTypeManager();
1749+
emitted = demoteArrayIfMatching(emitted, node.getSize(), dtm);
1750+
emitted = demoteRecordIfMatching(emitted, node.getSize(), dtm);
17241751
}
17251752
writer.name("type").value(label(emitted));
17261753
writer.name("size").value(node.getSize());
@@ -3370,9 +3397,9 @@ void serializeOutput(PcodeOp pcodeOp) throws Exception {
33703397
// #250: scalar-op output must not carry an Array type.
33713398
DataType emitted = chooseEmittedType(output, outputHighVariable);
33723399
if (isScalarPcodeOp(pcodeOp.getOpcode())) {
3373-
emitted = demoteArrayIfMatching(
3374-
emitted, output.getSize(),
3375-
currentProgram.getDataTypeManager());
3400+
DataTypeManager dtm = currentProgram.getDataTypeManager();
3401+
emitted = demoteArrayIfMatching(emitted, output.getSize(), dtm);
3402+
emitted = demoteRecordIfMatching(emitted, output.getSize(), dtm);
33763403
}
33773404
writer.name("type").value(label(emitted));
33783405
writer.name("size").value(output.getSize());

0 commit comments

Comments
 (0)