From e8fc9628ba431049bdd13ebcaead3e954c2cd7e8 Mon Sep 17 00:00:00 2001 From: Alessandro Di Federico Date: Thu, 21 May 2026 17:04:33 +0200 Subject: [PATCH] Add model drop-types tool --- tests/filecheck/CMakeLists.txt | 7 +- .../model/drop-types/ArrayPropagation.yml | 51 ++++++++++ .../model/drop-types/DropStructField.yml | 47 +++++++++ .../model/drop-types/DropViaPointer.yml | 47 +++++++++ .../model/drop-types/UnionCascade.yml | 67 +++++++++++++ .../model/drop-types/UnionPartialDrop.yml | 46 +++++++++ tools/model/CMakeLists.txt | 1 + tools/model/drop-types/CMakeLists.txt | 7 ++ tools/model/drop-types/Main.cpp | 96 +++++++++++++++++++ 9 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 tests/filecheck/model/drop-types/ArrayPropagation.yml create mode 100644 tests/filecheck/model/drop-types/DropStructField.yml create mode 100644 tests/filecheck/model/drop-types/DropViaPointer.yml create mode 100644 tests/filecheck/model/drop-types/UnionCascade.yml create mode 100644 tests/filecheck/model/drop-types/UnionPartialDrop.yml create mode 100644 tools/model/drop-types/CMakeLists.txt create mode 100644 tools/model/drop-types/Main.cpp diff --git a/tests/filecheck/CMakeLists.txt b/tests/filecheck/CMakeLists.txt index 99f939f22..93c0f9109 100644 --- a/tests/filecheck/CMakeLists.txt +++ b/tests/filecheck/CMakeLists.txt @@ -411,7 +411,12 @@ set(FILECHECK_TESTS llvm-passes/select-scope.ll llvm-passes/switch-to-statements.ll llvm-passes/ternary-reduction.ll - llvm-passes/twoscomplement-normalization.ll) + llvm-passes/twoscomplement-normalization.ll + model/drop-types/ArrayPropagation.yml + model/drop-types/DropStructField.yml + model/drop-types/DropViaPointer.yml + model/drop-types/UnionCascade.yml + model/drop-types/UnionPartialDrop.yml) foreach(F IN LISTS FILECHECK_TESTS) add_filecheck_test(${F}) diff --git a/tests/filecheck/model/drop-types/ArrayPropagation.yml b/tests/filecheck/model/drop-types/ArrayPropagation.yml new file mode 100644 index 000000000..b15dabe62 --- /dev/null +++ b/tests/filecheck/model/drop-types/ArrayPropagation.yml @@ -0,0 +1,51 @@ +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# +# Arrays do not break the dependency chain: a field of type `Array` +# is treated as a direct reference to `victim`, so dropping `victim` removes +# the field from the containing struct. +# +# RUN: %root/bin/revng model drop-types %s 1001-StructDefinition | FileCheck %s + +Architecture: x86_64 + +TypeDefinitions: + # This type is dropped. + # CHECK-NOT: 1001-StructDefinition + - ID: 1001 + Kind: StructDefinition + Name: "victim" + Size: 4 + Fields: + - Offset: 0 + Type: + Kind: PrimitiveType + PrimitiveKind: Generic + Size: 4 + + # Container survives; the array field (offset 0) is removed, only the + # primitive field at offset 16 stays. + # CHECK: ID: 1002 + # CHECK: Name: container_with_array + # CHECK: Fields: + # CHECK-NEXT: - Offset: 16 + # CHECK-NEXT: Type: + # CHECK-NEXT: Kind: PrimitiveType + # CHECK-NOT: - Offset: 0 + - ID: 1002 + Kind: StructDefinition + Name: "container_with_array" + Size: 20 + Fields: + - Offset: 0 + Type: + Kind: ArrayType + ElementCount: 4 + ElementType: + Kind: DefinedType + Definition: "/TypeDefinitions/1001-StructDefinition" + - Offset: 16 + Type: + Kind: PrimitiveType + PrimitiveKind: Generic + Size: 4 diff --git a/tests/filecheck/model/drop-types/DropStructField.yml b/tests/filecheck/model/drop-types/DropStructField.yml new file mode 100644 index 000000000..ca20b68a6 --- /dev/null +++ b/tests/filecheck/model/drop-types/DropStructField.yml @@ -0,0 +1,47 @@ +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# +# Dropping a struct that is used as a direct (non-pointer) field type should +# remove that field from the containing struct, but the containing struct +# itself must survive (its other fields are unaffected). +# +# RUN: %root/bin/revng model drop-types %s 1001-StructDefinition | FileCheck %s + +Architecture: x86_64 + +TypeDefinitions: + # This type is dropped — it must not appear in the output. + # CHECK-NOT: 1001-StructDefinition + # CHECK-NOT: Name: victim + - ID: 1001 + Kind: StructDefinition + Name: "victim" + Size: 4 + Fields: + - Offset: 0 + Type: + Kind: PrimitiveType + PrimitiveKind: Generic + Size: 4 + + # The container survives; only the primitive field at offset 4 remains. + # CHECK: ID: 1002 + # CHECK: Name: container + # CHECK: Fields: + # CHECK-NEXT: Offset: 4 + # CHECK-NEXT: Type: + # CHECK-NEXT: Kind: PrimitiveType + - ID: 1002 + Kind: StructDefinition + Name: "container" + Size: 8 + Fields: + - Offset: 0 + Type: + Kind: DefinedType + Definition: "/TypeDefinitions/1001-StructDefinition" + - Offset: 4 + Type: + Kind: PrimitiveType + PrimitiveKind: Generic + Size: 4 diff --git a/tests/filecheck/model/drop-types/DropViaPointer.yml b/tests/filecheck/model/drop-types/DropViaPointer.yml new file mode 100644 index 000000000..1f581c0ce --- /dev/null +++ b/tests/filecheck/model/drop-types/DropViaPointer.yml @@ -0,0 +1,47 @@ +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# +# Dropping a struct that is only reached via a pointer should NOT remove the +# containing struct's field. Instead, the pointer's pointee is replaced with +# void. +# +# RUN: %root/bin/revng model drop-types %s 1001-StructDefinition | FileCheck %s + +Architecture: x86_64 + +TypeDefinitions: + # This type is dropped — it must not appear in the output. + # CHECK-NOT: 1001-StructDefinition + - ID: 1001 + Kind: StructDefinition + Name: "victim" + Size: 4 + Fields: + - Offset: 0 + Type: + Kind: PrimitiveType + PrimitiveKind: Generic + Size: 4 + + # The container survives with its pointer field; the pointee is now void. + # CHECK: ID: 1002 + # CHECK: Name: container_with_ptr + # CHECK: Fields: + # CHECK-NEXT: Offset: 0 + # CHECK-NEXT: Type: + # CHECK-NEXT: Kind: PointerType + # CHECK: PointeeType: + # CHECK-NEXT: Kind: PrimitiveType + # CHECK-NEXT: PrimitiveKind: Void + - ID: 1002 + Kind: StructDefinition + Name: "container_with_ptr" + Size: 8 + Fields: + - Offset: 0 + Type: + Kind: PointerType + PointerSize: 8 + PointeeType: + Kind: DefinedType + Definition: "/TypeDefinitions/1001-StructDefinition" diff --git a/tests/filecheck/model/drop-types/UnionCascade.yml b/tests/filecheck/model/drop-types/UnionCascade.yml new file mode 100644 index 000000000..20c4331c0 --- /dev/null +++ b/tests/filecheck/model/drop-types/UnionCascade.yml @@ -0,0 +1,67 @@ +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# +# When ALL fields of a union reference deleted types, the union itself must +# also be dropped and deletion cascades to its dependents (here, the field +# in `uses_u` referencing the union). +# +# RUN: %root/bin/revng model drop-types %s 1001-StructDefinition 1002-StructDefinition | FileCheck %s + +Architecture: x86_64 + +TypeDefinitions: + # Dropped directly. + # CHECK-NOT: 1001-StructDefinition + - ID: 1001 + Kind: StructDefinition + Name: "v1" + Size: 4 + Fields: + - Offset: 0 + Type: + Kind: PrimitiveType + PrimitiveKind: Generic + Size: 4 + + # Dropped directly. + # CHECK-NOT: 1002-StructDefinition + - ID: 1002 + Kind: StructDefinition + Name: "v2" + Size: 4 + Fields: + - Offset: 0 + Type: + Kind: PrimitiveType + PrimitiveKind: Generic + Size: 4 + + # Both entries reference deleted types, so the union itself is cascaded. + # CHECK-NOT: 1003-UnionDefinition + # CHECK-NOT: UnionDefinition + - ID: 1003 + Kind: UnionDefinition + Name: "u" + Fields: + - Index: 0 + Type: + Kind: DefinedType + Definition: "/TypeDefinitions/1001-StructDefinition" + - Index: 1 + Type: + Kind: DefinedType + Definition: "/TypeDefinitions/1002-StructDefinition" + + # Survives but loses its only field (which referenced the cascaded union). + # CHECK: ID: 1004 + # CHECK: Name: uses_u + # CHECK-NOT: Fields: + - ID: 1004 + Kind: StructDefinition + Name: "uses_u" + Size: 4 + Fields: + - Offset: 0 + Type: + Kind: DefinedType + Definition: "/TypeDefinitions/1003-UnionDefinition" diff --git a/tests/filecheck/model/drop-types/UnionPartialDrop.yml b/tests/filecheck/model/drop-types/UnionPartialDrop.yml new file mode 100644 index 000000000..d3d247723 --- /dev/null +++ b/tests/filecheck/model/drop-types/UnionPartialDrop.yml @@ -0,0 +1,46 @@ +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# +# When only SOME of a union's fields reference deleted types, the union +# itself survives. The deleted entries are removed and the remaining ones +# are reindexed starting from zero. +# +# RUN: %root/bin/revng model drop-types %s 1001-StructDefinition | FileCheck %s + +Architecture: x86_64 + +TypeDefinitions: + # This type is dropped. + # CHECK-NOT: 1001-StructDefinition + - ID: 1001 + Kind: StructDefinition + Name: "victim" + Size: 4 + Fields: + - Offset: 0 + Type: + Kind: PrimitiveType + PrimitiveKind: Generic + Size: 4 + + # The union survives with only the primitive entry, reindexed to 0. + # CHECK: ID: 1002 + # CHECK: Kind: UnionDefinition + # CHECK: Fields: + # CHECK-NEXT: - Index: 0 + # CHECK-NEXT: Type: + # CHECK-NEXT: Kind: PrimitiveType + # CHECK-NOT: - Index: 1 + - ID: 1002 + Kind: UnionDefinition + Name: "u" + Fields: + - Index: 0 + Type: + Kind: DefinedType + Definition: "/TypeDefinitions/1001-StructDefinition" + - Index: 1 + Type: + Kind: PrimitiveType + PrimitiveKind: Generic + Size: 4 diff --git a/tools/model/CMakeLists.txt b/tools/model/CMakeLists.txt index b1343d6a5..e56454799 100644 --- a/tools/model/CMakeLists.txt +++ b/tools/model/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(apply) add_subdirectory(diff) +add_subdirectory(drop-types) add_subdirectory(export) add_subdirectory(import) add_subdirectory(merge) diff --git a/tools/model/drop-types/CMakeLists.txt b/tools/model/drop-types/CMakeLists.txt new file mode 100644 index 000000000..a6296be12 --- /dev/null +++ b/tools/model/drop-types/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# This file is distributed under the MIT License. See LICENSE.md for details. +# + +revng_add_executable(model-drop-types Main.cpp) + +target_link_libraries(model-drop-types revngModel) diff --git a/tools/model/drop-types/Main.cpp b/tools/model/drop-types/Main.cpp new file mode 100644 index 000000000..ab17ac97f --- /dev/null +++ b/tools/model/drop-types/Main.cpp @@ -0,0 +1,96 @@ +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +#include + +#include "llvm/Support/CommandLine.h" + +#include "revng/Model/Binary.h" +#include "revng/Model/Processing.h" +#include "revng/Support/Debug.h" +#include "revng/Support/InitRevng.h" +#include "revng/Support/YAMLTraits.h" + +namespace cl = llvm::cl; + +static cl::OptionCategory ThisToolCategory("model-drop-types options", ""); + +static cl::opt InputFilename(cl::Positional, + cl::cat(ThisToolCategory), + cl::desc(""), + cl::init("-"), + cl::value_desc("model")); + +static cl::opt OutputFilename("o", + cl::cat(ThisToolCategory), + cl::init("-"), + cl::desc("Override output " + "filename"), + cl::value_desc("filename")); + +static cl::list TypeKeys(cl::Positional, + cl::cat(ThisToolCategory), + cl::desc(""), + cl::OneOrMore); + +int main(int Argc, char *Argv[]) { + revng::InitRevng X(Argc, Argv, "", { &ThisToolCategory }); + + llvm::ExitOnError ExitOnError; + + auto MaybeModel = TupleTree::fromFileOrSTDIN(InputFilename); + if (not MaybeModel) + ExitOnError(MaybeModel.takeError()); + + auto &Model = *MaybeModel; + + // Resolve the type keys into TypeDefinition pointers + std::set TypesToDrop; + for (const std::string &KeyString : TypeKeys) { + // Keys have the form "ID-Kind", e.g. "42-StructDefinition" + auto SplitPoint = KeyString.find('-'); + if (SplitPoint == std::string::npos) { + dbg << "Invalid type key (expected ID-Kind): " << KeyString << "\n"; + return EXIT_FAILURE; + } + + uint64_t ID = 0; + if (llvm::StringRef(KeyString).substr(0, SplitPoint).getAsInteger(10, ID)) { + dbg << "Invalid ID in type key: " << KeyString << "\n"; + return EXIT_FAILURE; + } + + llvm::StringRef KindName = llvm::StringRef(KeyString).substr(SplitPoint + + 1); + auto Kind = model::TypeDefinitionKind::fromName(KindName); + if (Kind == model::TypeDefinitionKind::Invalid) { + dbg << "Unknown type kind: " << KindName.str() << "\n"; + return EXIT_FAILURE; + } + + // Look up the type in the model + bool Found = false; + for (const model::UpcastableTypeDefinition &Type : + Model->TypeDefinitions()) { + if (Type->ID() == ID && Type->Kind() == Kind) { + TypesToDrop.insert(Type.get()); + Found = true; + break; + } + } + + if (not Found) { + dbg << "Type not found in model: " << KeyString << "\n"; + return EXIT_FAILURE; + } + } + + unsigned Dropped = model::dropTypesDependingOnDefinitions(Model, TypesToDrop); + + dbg << "Dropped " << Dropped << " type(s)\n"; + + ExitOnError(Model.toFile(OutputFilename)); + + return EXIT_SUCCESS; +}