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.
+
+
+
+
+
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);
+ }
+
+}