From 907dc96d16c8299514becd5952866bdd40b2bf3e Mon Sep 17 00:00:00 2001 From: Ghidra78 Date: Thu, 30 Nov 2023 13:55:23 +0000 Subject: [PATCH] GP-3919 Decompiler analysis toggle buttons --- .../main/resources/images/write-disabled.png | Bin 733 -> 0 bytes Ghidra/Features/Base/certification.manifest | 2 +- .../Base/src/main/resources/images/slash.png | Bin 0 -> 178 bytes .../Decompiler/certification.manifest | 2 + .../data/decompiler.theme.properties | 3 + .../src/main/doc/decompileplugin.xml | 34 ++ .../DecompilerAnnotations.html | 2 +- .../DecompilePlugin/DecompilerConcepts.html | 2 +- .../DecompilePlugin/DecompilerIntro.html | 4 +- .../DecompilePlugin/DecompilerWindow.html | 30 +- .../app/decompiler/DecompileOptions.java | 15 + .../core/decompile/DecompilerProvider.java | 142 ++++++- .../resources/images/eliminateUnreachable.png | Bin 0 -> 1630 bytes .../src/main/resources/images/readOnly.png | Bin 0 -> 621 bytes .../decompile/DecompilerToggleButtonTest.java | 401 ++++++++++++++++++ 15 files changed, 628 insertions(+), 9 deletions(-) delete mode 100644 Ghidra/Debug/Debugger/src/main/resources/images/write-disabled.png create mode 100644 Ghidra/Features/Base/src/main/resources/images/slash.png create mode 100644 Ghidra/Features/Decompiler/src/main/resources/images/eliminateUnreachable.png create mode 100644 Ghidra/Features/Decompiler/src/main/resources/images/readOnly.png create mode 100644 Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerToggleButtonTest.java diff --git a/Ghidra/Debug/Debugger/src/main/resources/images/write-disabled.png b/Ghidra/Debug/Debugger/src/main/resources/images/write-disabled.png deleted file mode 100644 index bad4477d9617023d369c4280928e3a44d55c5d68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 733 zcmV<30wVp1P)+lt(g(khG`c?H-cRh)Wt$6H>X!QFj_eaQGZ<=T`GFuz~On` z=Xp5ieUGIb7LX_DxTJ&Y(5$4lmSycZU0u|jW!b-8eTv_Z^wZiVEKn?VM}fdOjgH!y zo;E~DiPX@rHgTk6r7V%N_irk#j`uyO$meL;X0eB0Fp1L%(#|_Kmvmr&A6+9XTo2)| zIXsw2CKya&S*FnEgR!wBb8`bMEOal2!;>^M8P2vz5B78OY%?dSi|!hOZ{HqmNiHvk zSS$qCl62I&uRj&N2)Az;-sR#$`2KH6`2zB2ZC%YImzQrbDJ(23xO!oKjHm|^m%#4_ ziM95&HNYjHh001J8XHRziw$K=yk0(rpG+sm%OL78`}IS^)zko-5pa#{Yy-~C^#eBX z_+u(63|3kSlYJ2NLe#_3RIkCEPJk=6B*|Luro*vlS=O40i_a?(38|t&nwpZDyQHkk zl59P7LN~-n`?;9&L!Z~x<|cj+q`_x``<(Yw;K|Pfz4Bx zPbf5xZJPqWA0m-2lM@p!p7z{ZU5_~A#K87sYZoJzq+^l}twRY(?<~toZLj?Yn&IE(6HHP^ P00000NkvXXu0mjfjrvKk diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 6f3bb94e46..241120e215 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -389,7 +389,6 @@ src/main/help/help/topics/MemoryMapPlugin/images/MoveMemory.png||GHIDRA||||END| src/main/help/help/topics/MemoryMapPlugin/images/SetImageBaseDialog.png||GHIDRA||||END| src/main/help/help/topics/MemoryMapPlugin/images/SplitMemoryBlock.png||GHIDRA||||END| src/main/help/help/topics/Misc/Appendix.htm||GHIDRA||||END| -src/main/help/help/topics/Misc/Tips.htm||NONE||||END| src/main/help/help/topics/Misc/Welcome_to_Ghidra_Help.htm||GHIDRA||||END| src/main/help/help/topics/Navigation/Navigation.htm||GHIDRA||||END| src/main/help/help/topics/Navigation/images/GoToDialog.png||GHIDRA||||END| @@ -886,6 +885,7 @@ src/main/resources/images/searchm_obj.gif||GHIDRA||||END| src/main/resources/images/searchm_pink.gif||GHIDRA||||END| src/main/resources/images/settings16.gif||GHIDRA||||END| src/main/resources/images/sitemap_color.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| +src/main/resources/images/slash.png||GHIDRA||||END| src/main/resources/images/smallLeftArrow.png||GHIDRA||||END| src/main/resources/images/smallRightArrow.png||GHIDRA||||END| src/main/resources/images/small_minus.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/resources/images/slash.png b/Ghidra/Features/Base/src/main/resources/images/slash.png new file mode 100644 index 0000000000000000000000000000000000000000..4f248b2b2a8315b08e271a8d23e8cd1e74f77f99 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE(~{oEQYOan(9D`v%n*= zn1O*?7=#%aX3dcR3bL1Y`ns||=Mv<#mT`UE`4lK*@9E+gVsZNIxrL$(1{};6v!AC( zv?c14Eo2Lv+a + + Eliminate Unreachable Code + + + + + + + + +  - toggle button + + + Quickly turn off the decompiler setting. + + + + + Respect Read-only Flags + + + + + + + + +  - toggle button + + + Quickly turn off the decompiler setting. + + + Copy diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerAnnotations.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerAnnotations.html index 7bc42707b6..c9475a6a53 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerAnnotations.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerAnnotations.html @@ -4,7 +4,7 @@ Program Annotations Affecting the Decompiler - + diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerConcepts.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerConcepts.html index a1ac4908f9..da0f13f2e4 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerConcepts.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerConcepts.html @@ -4,7 +4,7 @@ Decompiler Concepts - + diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html index b5e97f2ed8..1880986ce7 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html @@ -4,7 +4,7 @@ Decompiler - + @@ -43,7 +43,7 @@ a Code Browser by selecting the

- File -> Configure + File -> Configure...

menu option, then clicking on the Configure link under the diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html index bfa016a42e..d17dedf881 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html @@ -4,7 +4,7 @@ Decompiler Window - + @@ -303,6 +303,34 @@ +

+

+Eliminate Unreachable Code

+ +

+ + +  - toggle button +

+

+ Quickly turn off the Eliminate unreachable code decompiler setting. +

+
+ +
+

+Respect Read-only Flags

+ +

+ + +  - toggle button +

+

+ Quickly turn off the Respect read-only flags decompiler setting. +

+
+

Copy

diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java index 0800d12c9a..7739929291 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java @@ -1117,6 +1117,21 @@ public class DecompileOptions { this.eliminateUnreachable = eliminateUnreachable; } + /** + * @return true if the decompiler currently respects read-only flags + */ + public boolean isRespectReadOnly() { + return readOnly; + } + + /** + * Set whether the decompiler should respect read-only flags as part of its analysis. + * @param readOnly is true if read-only flags are respected + */ + public void setRespectReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + /** * If the decompiler currently applies transformation rules that identify and * simplify double precision arithmetic operations, true is returned. diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java index e52b6c73b2..8e990744a4 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java @@ -53,6 +53,7 @@ import ghidra.util.Swing; import ghidra.util.bean.field.AnnotatedTextFieldElement; import ghidra.util.task.SwingUpdateManager; import resources.Icons; +import resources.MultiIconBuilder; import utility.function.Callback; public class DecompilerProvider extends NavigatableComponentProviderAdapter @@ -64,9 +65,26 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter private static final Icon REFRESH_ICON = Icons.REFRESH_ICON; private static final Icon C_SOURCE_ICON = new GIcon("icon.decompiler.action.provider"); + private static final Icon SLASH_ICON = new GIcon("icon.decompiler.action.slash"); + + private static final Icon TOGGLE_UNREACHABLE_CODE_ICON = + new GIcon("icon.decompiler.action.provider.unreachable"); + + private static final Icon TOGGLE_UNREACHABLE_CODE_DISABLED_ICON = + new MultiIconBuilder(TOGGLE_UNREACHABLE_CODE_ICON).addCenteredIcon(SLASH_ICON).build(); + + private static final Icon TOGGLE_READ_ONLY_ICON = + new GIcon("icon.decompiler.action.provider.readonly"); + + private static final Icon TOGGLE_READ_ONLY_DISABLED_ICON = + new MultiIconBuilder(TOGGLE_READ_ONLY_ICON).addCenteredIcon(SLASH_ICON).build(); + private DockingAction pcodeGraphAction; private DockingAction astGraphAction; + private ToggleDockingAction displayUnreachableCodeToggle; + private ToggleDockingAction respectReadOnlyFlags; + private final DecompilePlugin plugin; private ClipboardService clipboardService; private DecompilerClipboardProvider clipboardProvider; @@ -142,7 +160,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "DecompilerIntro")); addToTool(); - redecompileUpdater = new SwingUpdateManager(500, 5000, () -> doRefresh()); + redecompileUpdater = new SwingUpdateManager(500, 5000, () -> doRefresh(false)); followUpWorkUpdater = new SwingUpdateManager(() -> doFollowUpWork()); plugin.getTool().addServiceListener(serviceListener); @@ -180,6 +198,9 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter ToolOptions opt = tool.getOptions(OPTIONS_TITLE); decompilerOptions.grabFromToolAndProgram(fieldOptions, opt, program); controller.setOptions(decompilerOptions); + + refreshToggleButtons(); + controller.display(program, currentLocation, null); } } @@ -320,16 +341,39 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter } } - private void doRefresh() { + private void doRefresh(boolean optionsChanged) { ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); ToolOptions opt = tool.getOptions(OPTIONS_TITLE); + + // Current values of toggle buttons + boolean decompilerEliminatesUnreachable = decompilerOptions.isEliminateUnreachable(); + boolean decompilerRespectsReadOnlyFlags = decompilerOptions.isRespectReadOnly(); + decompilerOptions.grabFromToolAndProgram(fieldOptions, opt, program); + + // If the tool options were not changed + if (!optionsChanged) { + // Keep these analysis options the same + decompilerOptions.setEliminateUnreachable(decompilerEliminatesUnreachable); + decompilerOptions.setRespectReadOnly(decompilerRespectsReadOnlyFlags); + } + else { + // Otherwise, keep the new analysis options and update the state of the toggle buttons + refreshToggleButtons(); + } + controller.setOptions(decompilerOptions); + if (currentLocation != null) { controller.refreshDisplay(program, currentLocation, null); } } + private void refreshToggleButtons() { + displayUnreachableCodeToggle.setSelected(!decompilerOptions.isEliminateUnreachable()); + respectReadOnlyFlags.setSelected(!decompilerOptions.isRespectReadOnly()); + } + private void doFollowUpWork() { if (isBusy()) { // try again later @@ -357,7 +401,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter if (options.getName().equals(OPTIONS_TITLE) || options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) { - doRefresh(); + doRefresh(true); } } @@ -465,6 +509,15 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter controller.refreshDisplay(program, currentLocation, null); } + /** + * Update the options from decompilerOptions + */ + void updateOptionsAndRefresh() { + controller.setOptions(decompilerOptions); + + refresh(); + } + @Override public ProgramSelection getSelection() { return currentSelection; @@ -771,6 +824,87 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter refreshAction .setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ToolBarRedecompile")); // just use the default + displayUnreachableCodeToggle = new ToggleDockingAction("Toggle Unreachable Code", owner) { + @Override + public void actionPerformed(ActionContext context) { + boolean isSelected = this.isSelected(); + + // Set the option based on the button state + decompilerOptions.setEliminateUnreachable(!isSelected); + + updateOptionsAndRefresh(); + } + + @Override + public void setSelected(boolean isSelected) { + super.setSelected(isSelected); + + // Update the icon to have a slash or not + if (!isSelected) { + displayUnreachableCodeToggle + .setToolBarData(new ToolBarData(TOGGLE_UNREACHABLE_CODE_ICON, "A")); + } + else { + displayUnreachableCodeToggle.setToolBarData( + new ToolBarData(TOGGLE_UNREACHABLE_CODE_DISABLED_ICON, "A")); + } + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + DecompileData decompileData = controller.getDecompileData(); + if (decompileData == null) { + return false; + } + return decompileData.hasDecompileResults(); + } + }; + displayUnreachableCodeToggle.setDescription("Toggle off to eliminate unreachable code"); + displayUnreachableCodeToggle.setHelpLocation( + new HelpLocation(HelpTopics.DECOMPILER, "ToolBarEliminateUnreachableCode")); + + respectReadOnlyFlags = new ToggleDockingAction("Toggle Respecting Read-only Flags", owner) { + @Override + public void actionPerformed(ActionContext context) { + boolean isSelected = this.isSelected(); + + // Set the option based on the button state + decompilerOptions.setRespectReadOnly(!isSelected); + + updateOptionsAndRefresh(); + } + + @Override + public void setSelected(boolean isSelected) { + super.setSelected(isSelected); + + // Update the icon to have a slash or not + if (!isSelected) { + respectReadOnlyFlags + .setToolBarData(new ToolBarData(TOGGLE_READ_ONLY_ICON, "A")); + } + else { + respectReadOnlyFlags + .setToolBarData(new ToolBarData(TOGGLE_READ_ONLY_DISABLED_ICON, "A")); + } + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + DecompileData decompileData = controller.getDecompileData(); + if (decompileData == null) { + return false; + } + return decompileData.hasDecompileResults(); + } + }; + respectReadOnlyFlags.setDescription("Toggle off to respect readonly flags set on memory"); + respectReadOnlyFlags + .setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ToolBarRespectReadOnly")); + + // Set the selected state and icon for the above two toggle icons + refreshToggleButtons(); + // // Below are actions along with their groups and subgroup information. The comments // for each section indicates the logical group for the actions that follow. @@ -999,6 +1133,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter GoToPreviousBraceAction goToPreviousBraceAction = new GoToPreviousBraceAction(); addLocalAction(refreshAction); + addLocalAction(displayUnreachableCodeToggle); + addLocalAction(respectReadOnlyFlags); addLocalAction(selectAllAction); addLocalAction(defUseHighlightAction); addLocalAction(forwardSliceAction); diff --git a/Ghidra/Features/Decompiler/src/main/resources/images/eliminateUnreachable.png b/Ghidra/Features/Decompiler/src/main/resources/images/eliminateUnreachable.png new file mode 100644 index 0000000000000000000000000000000000000000..ef20f534437c8110f3d54dc85ad326b0294ae8ee GIT binary patch literal 1630 zcmV-k2BG zaB^>EX>4U6ba`-PAZ2)IW&i+q+QnCEawI1V{?93L1oQyJad=kc204B{8oF(dy~*x6 zRcSmHLkmghlZ3{Nzy6%=4?OI>ISt8Tj?v@MteFcY=HH%iX!d@tx#B}-jUFy?A70!u z!uV>PK?PLy;-gQNAbGbTK9s&%r|_~aj~f19k==qAe#u+*&lk zKk*#uKFT!%V}&TWwndDDPry7~BV(8^zBy9gee)HgQ6?twlax9YnlvujsKG?#!3mVP zv0Q5=i8D9xLZVJolqM(1(#3?O@esi(Keo`NH(%-+85DN{CliDbc3i{Vlkbi%16^Od%QjX7D5zU%gu%Z7GJlB8t5&y#9crO7z3#8 z&8Z&&a1nF|7()UCd?X1J$tUJS1V0+^DX^TyDOW&1A#jJ4tRfNM7%$z}lj!2*z24kYY;7QclIh)PhAz zW|pnE%vq4DA;+Au<(x}_(gMi^yNeYpRIXCBh8kM-^<4n_Lp5;O9Le+i1>@#xzj2a!(0GPWlQ!w4A zVL7E+1g&xs7G^+ySVXtnORD-S&i+e*XbR4+(BFcmiZdH;*l`QP05 zZ9&pTT$3`BPDZ+3UkW=dT>}9w0Ie5u&YUWWAeO_Fl4_xg1EKW~)cqU7S2X1uE7-q?*DpE~YwusKfoDF`tc-?F2WK5tm8pwjvK00%Ml5 ziYdNtzLE1#8uPHVmdCWqROIKP?w)*?Eb^p9q8Fgn#?M)`vAKTZ)i=WuNF1*m;<2&=(NleZ0PE0EKz#kZ}Zf=c5 z=|0FTy*z2&O8b$j$>RmJfA_f2K6o<}OWgH4wm7a-aK}ChTfL&}KjxiO$rMvhCuVC& zBYcs1?#?~!Kba2)8ZZ55jNhn7W~UzcJkXGxTr)i$#uX683A+^J9HpUKI(go3&%Kk7 zX)j*_+$RbBu4{F?i9oL)d=|IpXRUD4re zH19Lxpd0=G4bz@8|uz z3YH=&)e=NgK|^<{9K?i*8l)*G3=45cZs6izHbjGw4z{!e3JH}hMM5=HDkX(fgbqSO z+0YMaT%~*O_kQnb@Lp)M%`=|i;qW{h*y@oZ!?CJT1d4!H^tXt75lK2;^+b!C0Sr(O zK?HrM$T(u2qwM!S7|)Do-TweQ5IPt3PG@b`^lH#4h8F_+QnBma3}8zD0!skEfOtRb z#zeH$DD9c-d)31e48bPizd&b6Yr+$`~0 z0oYNNhsPuP@88N^%i&d0n(iqJ~?#{&fkByK!spr1rq9soB$v2|t)X1`Ut{D9VNlfaz@X z8I^N_q@r!RuW0_~FF2N6Bqb!jZO< 4) + { + a = 0; + } + else + { + a = readB(); + } + + return a; + } + */ + + ProgramBuilder builder = + new ProgramBuilder("TestDecompilerToggleButtons", ProgramBuilder._X64); + + // Create the global "b" variable + MemoryBlock bVarBlock = builder.createMemory("bVarBl", "0x104010", 4); + builder.setWrite(bVarBlock, false); + builder.createLabel("0x104010", "b"); + builder.setBytes("0x104010", "01 00 00 00"); + + // Create the "readB" function + MemoryBlock readBFunctionBlock = builder.createMemory("readBFunction", "0x101129", 16); + builder.setWrite(readBFunctionBlock, false); + builder.setBytes("0x101129", "f3 0f 1e fa 55 48 89 e5 8b 05 d9 2e 00 00 5d c3"); + builder.createFunction("0x101129"); + builder.createLabel("0x101129", "readB"); + + // Create the "main" function + MemoryBlock mainFunctionBlock = builder.createMemory("mainFunction", "0x101139", 56); + builder.setWrite(mainFunctionBlock, false); + builder.setBytes("0x101139", + "f3 0f 1e fa 55 48 89 e5 48 83 ec 10 c7 45 fc 05 00 00 00 83 45 fc 01 83 7d fc 04 7e " + + "09 c7 45 fc 00 00 00 00 eb 0d b8 00 00 00 00 e8 c0 ff ff ff 89 45 fc 8b 45 fc c9 c3"); + builder.createFunction("0x101139"); + builder.createLabel("0x101129", "main"); + + builder.analyze(); + + prog = builder.getProgram(); + + return prog; + } + + @Test + public void testUnreachableCodeToggle() { + + DecompilerController controller = provider.getController(); + + // Point the decompiler at the "main" function + decompile("0x101139"); + waitForSwing(); + + // Get the decompiled program as a C code string + String resultingC = getResultingCCode(controller); + + // Check that the resulting decompilation does NOT contain unreachable code + assertNotNull(resultingC); + assertNotEquals("", resultingC); + assertTrue(resultingC.contains("WARNING: Removing unreachable block")); + + ToggleDockingActionIf eliminateUnreachableToggleAction = + (ToggleDockingActionIf) getAction(decompiler, "Toggle Unreachable Code"); + + // Check button state - should not be pressed down + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertFalse(eliminateUnreachableToggleAction.isSelected()); + + // Toggle unreachable code + performAction(eliminateUnreachableToggleAction, provider.getActionContext(null), false); + waitForDecompiler(); + + // Get the decompiled program as a C code string + resultingC = getResultingCCode(controller); + + // Check that the resulting decompilation now contains unreachable code + assertNotNull(resultingC); + assertNotEquals("", resultingC); + assertFalse(resultingC.contains("WARNING: Removing unreachable block")); + + // Check button state - should be pressed down (with slash) + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertTrue(eliminateUnreachableToggleAction.isSelected()); + + } + + @Test + public void testReadOnlyCodeToggle() { + + DecompilerController controller = provider.getController(); + + // Point the decompiler at the "readB" function + decompile("0x101129"); + waitForSwing(); + + // Get the decompiled program as a C code string + String resultingC = getResultingCCode(controller); + + // Check that the resulting decompilation does NOT respect read-only flags + assertNotNull(resultingC); + assertNotEquals("", resultingC); + assertTrue(resultingC.contains("return 1;")); + + ToggleDockingActionIf respectReadonlyToggleAction = + (ToggleDockingActionIf) getAction(decompiler, "Toggle Respecting Read-only Flags"); + + // Check button state - should not be pressed down + assertTrue(respectReadonlyToggleAction.isEnabled()); + assertFalse(respectReadonlyToggleAction.isSelected()); + + // Toggle read-only code visibility + performAction(respectReadonlyToggleAction, provider.getActionContext(null), false); + waitForDecompiler(); + + // Get the decompiled program as a C code string + resultingC = getResultingCCode(controller); + + // Check that the resulting decompilation now respects read-only flags + assertNotNull(resultingC); + assertNotEquals("", resultingC); + assertTrue(resultingC.contains("return b;")); + + // Check button state - should be pressed down (with slash) + assertTrue(respectReadonlyToggleAction.isEnabled()); + assertTrue(respectReadonlyToggleAction.isSelected()); + + } + + @Test + public void unreachableCodeToggleDoesNotUpdateOptions() { + + // Point the decompiler at the "main" function + decompile("0x101139"); + waitForSwing(); + + ToggleDockingActionIf eliminateUnreachableToggleAction = + (ToggleDockingActionIf) getAction(decompiler, "Toggle Unreachable Code"); + + // Check button state - should not be pressed down + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertFalse(eliminateUnreachableToggleAction.isSelected()); + + // Get the (currently default) options + DecompileOptions decompilerOptions = getOptions(); + + // Check default state to be eliminating unreachable code + assertTrue(decompilerOptions.isEliminateUnreachable()); + + // Toggle unreachable code + performAction(eliminateUnreachableToggleAction, provider.getActionContext(null), false); + waitForDecompiler(); + + // Grab new options - should be the same as before + decompilerOptions = getOptions(); + assertTrue(decompilerOptions.isEliminateUnreachable()); + } + + @Test + public void buttonsResetOnOptionChange() { + + // Point the decompiler at the "main" function + decompile("0x101139"); + waitForSwing(); + + ToggleDockingActionIf eliminateUnreachableToggleAction = + (ToggleDockingActionIf) getAction(decompiler, "Toggle Unreachable Code"); + + // Check button state - should not be pressed down + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertFalse(eliminateUnreachableToggleAction.isSelected()); + + // Get the (currently default) options + DecompileOptions decompilerOptions = getOptions(); + + // Check default state to be eliminating unreachable code + assertTrue(decompilerOptions.isEliminateUnreachable()); + + // Set the option to be false (should update the toggle button) + setEliminateUnreachable(false); + + // The button state and decompiler options should have updated automatically + decompilerOptions = getOptions(); + assertFalse(decompilerOptions.isEliminateUnreachable()); + + // Check button state - should be pressed down (with slash) + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertTrue(eliminateUnreachableToggleAction.isSelected()); + } + + @Test + public void buttonStatesRemainOnFunctionSwitch() { + + // Point the decompiler at the "main" function + decompile("0x101139"); + waitForSwing(); + + ToggleDockingActionIf eliminateUnreachableToggleAction = + (ToggleDockingActionIf) getAction(decompiler, "Toggle Unreachable Code"); + ToggleDockingActionIf respectReadonlyToggleAction = + (ToggleDockingActionIf) getAction(decompiler, "Toggle Respecting Read-only Flags"); + + // Check button state - should not be pressed down + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertFalse(eliminateUnreachableToggleAction.isSelected()); + assertTrue(respectReadonlyToggleAction.isEnabled()); + assertFalse(respectReadonlyToggleAction.isSelected()); + + // Toggle unreachable code + performAction(eliminateUnreachableToggleAction, provider.getActionContext(null), false); + waitForDecompiler(); + + // Toggle respecting read-only flags + performAction(respectReadonlyToggleAction, provider.getActionContext(null), false); + waitForDecompiler(); + + // Check button state - should be pressed down (with slash) + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertTrue(eliminateUnreachableToggleAction.isSelected()); + assertTrue(respectReadonlyToggleAction.isEnabled()); + assertTrue(respectReadonlyToggleAction.isSelected()); + + // Switch functions + // Point the decompiler at the "readB" function + decompile("0x101129"); + waitForSwing(); + + // Check button state - should be pressed down (with slash) + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertTrue(eliminateUnreachableToggleAction.isSelected()); + assertTrue(respectReadonlyToggleAction.isEnabled()); + assertTrue(respectReadonlyToggleAction.isSelected()); + + } + + @Test + public void buttonStatesUpdateWhenHidden() { + + // Point the decompiler at the "main" function + decompile("0x101139"); + waitForSwing(); + + ToggleDockingActionIf eliminateUnreachableToggleAction = + (ToggleDockingActionIf) getAction(decompiler, "Toggle Unreachable Code"); + + // Check button state - should not be pressed down + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertFalse(eliminateUnreachableToggleAction.isSelected()); + + // Hide the decompiler panel + tool.showComponentProvider(provider, false); + waitForSwing(); + + // Change the option to a non-default state + setEliminateUnreachable(false); + + // Show the decompiler panel + tool.showComponentProvider(provider, true); + waitForSwing(); + + // Check button state - should not be pressed down + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertTrue(eliminateUnreachableToggleAction.isSelected()); + } + + @Test + public void buttonStatesResetOnReopen() { + + // Point the decompiler at the "main" function + decompile("0x101139"); + waitForSwing(); + + ToggleDockingActionIf eliminateUnreachableToggleAction = + (ToggleDockingActionIf) getAction(decompiler, "Toggle Unreachable Code"); + ToggleDockingActionIf respectReadonlyToggleAction = + (ToggleDockingActionIf) getAction(decompiler, "Toggle Respecting Read-only Flags"); + + // Check button state - should not be pressed down + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertFalse(eliminateUnreachableToggleAction.isSelected()); + assertTrue(respectReadonlyToggleAction.isEnabled()); + assertFalse(respectReadonlyToggleAction.isSelected()); + + // Toggle unreachable code + performAction(eliminateUnreachableToggleAction, provider.getActionContext(null), false); + waitForDecompiler(); + + // Toggle respecting read-only flags + performAction(respectReadonlyToggleAction, provider.getActionContext(null), false); + waitForDecompiler(); + + // Check button state - should be pressed down (with slash) + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertTrue(eliminateUnreachableToggleAction.isSelected()); + assertTrue(respectReadonlyToggleAction.isEnabled()); + assertTrue(respectReadonlyToggleAction.isSelected()); + + // Hide the decompiler panel + tool.showComponentProvider(provider, false); + waitForSwing(); + + // Show the decompiler panel + tool.showComponentProvider(provider, true); + waitForSwing(); + + // Check button states - should have reset to tool option state + assertTrue(eliminateUnreachableToggleAction.isEnabled()); + assertFalse(eliminateUnreachableToggleAction.isSelected()); + assertTrue(respectReadonlyToggleAction.isEnabled()); + assertFalse(respectReadonlyToggleAction.isSelected()); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private String getResultingCCode(DecompilerController controller) { + return controller.getDecompileData().getDecompileResults().getDecompiledFunction().getC(); + } + + private DecompileOptions getOptions() { + ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + ToolOptions opt = tool.getOptions("Decompiler"); + + DecompileOptions decompilerOptions = new DecompileOptions(); + decompilerOptions.registerOptions(fieldOptions, opt, program); + return decompilerOptions; + } + + private void setEliminateUnreachable(boolean enabled) { + ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + ToolOptions opt = tool.getOptions("Decompiler"); + + opt.getOptions("Analysis").setBoolean("Eliminate unreachable code", enabled); + + DecompileOptions decompilerOptions = new DecompileOptions(); + decompilerOptions.registerOptions(fieldOptions, opt, program); + } + +}