55import ghidra .app .cmd .function .ApplyFunctionSignatureCmd ;
66import ghidra .app .cmd .label .RenameLabelCmd ;
77import ghidra .framework .cmd .Command ;
8- import ghidra .framework .model .DomainObject ;
9- import ghidra .program .model .data .DataTypeDependencyException ;
10- import ghidra .program .model .data .FunctionDefinitionDataType ;
118import ghidra .program .model .listing .CircularDependencyException ;
129import ghidra .program .model .listing .Program ;
1310import ghidra .program .model .symbol .Namespace ;
1411import ghidra .program .model .symbol .SourceType ;
15- import ghidra .util .Msg ;
1612import ghidra .util .exception .DuplicateNameException ;
1713import ghidra .util .exception .InvalidInputException ;
1814import org .jetbrains .annotations .NotNull ;
2117
2218import static ai .reveng .toolkit .ghidra .plugins .BinarySimilarityPlugin .REVENG_AI_NAMESPACE ;
2319
24- // We can't use Command<Program> because that breaks compatibility with Ghidra 11.0
25- public class ApplyMatchCmd implements Command {
20+ /// The central command to apply a function match to a {@link Program}
21+ /// It centralizes product design decisions about how to apply a match, like moving it to a namespace,
22+ /// renaming it, applying the signature, etc.
23+ /// It will apply the function signature if available, otherwise it will just rename the function
24+ /// There are various considerations when ap
25+ public class ApplyMatchCmd implements Command <Program > {
2626
27- private final Program program ;
27+ private final GhidraRevengService . AnalysedProgram analyzedProgram ;
2828 private final GhidraFunctionMatchWithSignature match ;
2929 @ Nullable private final GhidraRevengService service ;
30+ private final Boolean includeBinaryNameInNameSpace ;
3031
3132 public ApplyMatchCmd (
3233 @ Nullable GhidraRevengService service ,
33- @ NotNull GhidraFunctionMatchWithSignature match ) {
34+ @ NotNull GhidraRevengService .AnalysedProgram program ,
35+ @ NotNull GhidraFunctionMatchWithSignature match ,
36+ Boolean includeBinaryNameInNameSpace
37+
38+ ) {
3439 super ();
35- this .program = match . function (). getProgram () ;
40+ this .analyzedProgram = program ;
3641 this .match = match ;
3742 this .service = service ;
43+ this .includeBinaryNameInNameSpace = includeBinaryNameInNameSpace ;
44+ }
45+
46+ private boolean shouldApplyMatch () {
47+ var func = match .function ();
48+ return func != null &&
49+ // Do not override user-defined function names
50+ func .getSymbol ().getSource () != SourceType .USER_DEFINED &&
51+ // Exclude thunks and external functions
52+ !func .isThunk () &&
53+ !func .isExternal () &&
54+ // Only accept valid names (no spaces)
55+ !match .functionMatch ().nearest_neighbor_mangled_function_name ().contains (" " ) &&
56+ !match .functionMatch ().nearest_neighbor_function_name ().contains (" " )
57+ // Only rename if the function ID is known (boundaries matched)
58+ && analyzedProgram .getIDForFunction (func ).map (id -> id .functionID () != match .functionMatch ().origin_function_id ()).orElse (false );
3859 }
60+
3961 @ Override
40- public boolean applyTo (DomainObject obj ) {
62+ public boolean applyTo (Program obj ) {
4163 // Check that this is the same program
42- if (obj != this .program ) {
64+ if (obj != this .analyzedProgram . program () ) {
4365 throw new IllegalArgumentException ("This command can only be applied to the same program as the one provided in the constructor" );
4466 }
45- var libraryNamespace = getLibraryNameSpaceForName (match .functionMatch ().nearest_neighbor_binary_name ());
67+ if (!shouldApplyMatch ()) {
68+ return false ;
69+ }
70+
71+ var nameSpace = includeBinaryNameInNameSpace ? getLibraryNameSpaceForName (match .functionMatch ().nearest_neighbor_binary_name ()): getRevEngAINameSpace ();
4672 var function = match .function ();
4773 try {
48- function .setParentNamespace (libraryNamespace );
74+ function .setParentNamespace (nameSpace );
4975 } catch (DuplicateNameException e ) {
5076 throw new RuntimeException (e );
5177 } catch (InvalidInputException e ) {
@@ -54,43 +80,36 @@ public boolean applyTo(DomainObject obj) {
5480 throw new RuntimeException (e );
5581 }
5682
57- FunctionDefinitionDataType signature = null ;
58- if (match .signature ().isPresent ()) {
59- try {
60- signature = GhidraRevengService .getFunctionSignature (match .signature ().get ());
61- } catch (DataTypeDependencyException e ) {
62- Msg .showError (this , null ,"Failed to create function signature" ,
63- "Failed to create signature for match function with type %s"
64- .formatted (match .signature ().get ().func_types ().getSignature ()),
65- e );
66- }
67- }
83+ this .analyzedProgram .setMangledNameForFunction (function , match .functionMatch ().nearest_neighbor_mangled_function_name ());
6884
85+ var signature = match .signature ();
6986 if (signature != null ) {
7087 var cmd = new ApplyFunctionSignatureCmd (function .getEntryPoint (), signature , SourceType .USER_DEFINED );
71- cmd .applyTo (program );
88+ cmd .applyTo (analyzedProgram . program () );
7289 }
7390 else {
7491 var renameCmd = new RenameLabelCmd (match .function ().getSymbol (), match .functionMatch ().name (), SourceType .USER_DEFINED );
75- renameCmd .applyTo (program );
92+ renameCmd .applyTo (analyzedProgram . program () );
7693 }
7794 // If we have a service then push the name. If not then it was explicitly not provided, i.e. the caller
7895 // is responsible for pushing the names in batch
7996 if (service != null ) {
80- service .getApi ().renameFunction (match .functionMatch ().origin_function_id (), match .functionMatch ().name ());
97+ service .getApi ().renameFunction (match .functionMatch ().origin_function_id (), match .functionMatch ().nearest_neighbor_function_name ());
8198 }
8299
83100
84- return false ;
101+ return true ;
85102 }
86103
87104 public void applyWithTransaction () {
105+ var program = this .analyzedProgram .program ();
88106 var tID = program .startTransaction ("RevEng.AI: Apply Match" );
89107 var status = applyTo (program );
90108 program .endTransaction (tID , status );
91109 }
92110
93111 private Namespace getRevEngAINameSpace () {
112+ var program = this .analyzedProgram .program ();
94113 Namespace revengMatchNamespace = null ;
95114 try {
96115 revengMatchNamespace = program .getSymbolTable ().getOrCreateNameSpace (
@@ -105,6 +124,7 @@ private Namespace getRevEngAINameSpace() {
105124 }
106125
107126 private Namespace getLibraryNameSpaceForName (String name ) {
127+ var program = this .analyzedProgram .program ();
108128 Namespace libraryNamespace = null ;
109129 try {
110130 libraryNamespace = program .getSymbolTable ().getOrCreateNameSpace (
0 commit comments