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 bad4477d96..0000000000
Binary files a/Ghidra/Debug/Debugger/src/main/resources/images/write-disabled.png and /dev/null differ
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 0000000000..4f248b2b2a
Binary files /dev/null and b/Ghidra/Features/Base/src/main/resources/images/slash.png differ
diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest
index 67a824a71d..03f9ad66d1 100644
--- a/Ghidra/Features/Decompiler/certification.manifest
+++ b/Ghidra/Features/Decompiler/certification.manifest
@@ -96,3 +96,5 @@ src/main/help/help/topics/DecompilePlugin/images/EditFunctionSignature.png||GHID
src/main/help/help/topics/DecompilePlugin/images/ForwardSlice.png||GHIDRA||||END|
src/main/help/help/topics/DecompilePlugin/images/Undefined.png||GHIDRA||||END|
src/main/resources/images/decompileFunction.gif||GHIDRA||reviewed||END|
+src/main/resources/images/eliminateUnreachable.png||GHIDRA||||END|
+src/main/resources/images/readOnly.png||GHIDRA||||END|
diff --git a/Ghidra/Features/Decompiler/data/decompiler.theme.properties b/Ghidra/Features/Decompiler/data/decompiler.theme.properties
index d0d1ad0c24..6bb0daaa45 100644
--- a/Ghidra/Features/Decompiler/data/decompiler.theme.properties
+++ b/Ghidra/Features/Decompiler/data/decompiler.theme.properties
@@ -34,7 +34,10 @@ color.bg.decompiler.pcode.dfg.edge.within.block = color.palette.black
color.bg.decompiler.pcode.dfg.edge.between.blocks = color.palette.red
icon.decompiler.action.provider = decompileFunction.gif
+icon.decompiler.action.slash = slash.png
icon.decompiler.action.provider.clone = icon.provider.clone
+icon.decompiler.action.provider.unreachable = eliminateUnreachable.png
+icon.decompiler.action.provider.readonly = readOnly.png
icon.decompiler.action.export = page_edit.png
font.decompiler = font.monospaced
diff --git a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml
index 11c1c94a03..691a68e1d4 100644
--- a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml
+++ b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml
@@ -4114,6 +4114,40 @@
+
+ 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
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 0000000000..ef20f53443
Binary files /dev/null and b/Ghidra/Features/Decompiler/src/main/resources/images/eliminateUnreachable.png differ
diff --git a/Ghidra/Features/Decompiler/src/main/resources/images/readOnly.png b/Ghidra/Features/Decompiler/src/main/resources/images/readOnly.png
new file mode 100644
index 0000000000..c31377fc87
Binary files /dev/null and b/Ghidra/Features/Decompiler/src/main/resources/images/readOnly.png differ
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerToggleButtonTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerToggleButtonTest.java
new file mode 100644
index 0000000000..4435e94377
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerToggleButtonTest.java
@@ -0,0 +1,401 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.decompile;
+
+import static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.Test;
+
+import docking.action.ToggleDockingActionIf;
+import ghidra.GhidraOptions;
+import ghidra.app.decompiler.DecompileOptions;
+import ghidra.app.decompiler.component.DecompilerController;
+import ghidra.framework.options.ToolOptions;
+import ghidra.program.database.ProgramBuilder;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.MemoryBlock;
+
+public class DecompilerToggleButtonTest extends AbstractDecompilerTest {
+
+ private Program prog;
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Override
+ protected Program getProgram() throws Exception {
+ return buildProgram();
+ }
+
+ private Program buildProgram() throws Exception {
+
+ /*
+ int b = 1;
+
+ int readB(){
+ return b;
+ }
+
+ int main()
+ {
+ int a = 5;
+
+ a++;
+
+ if (a > 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);
+ }
+
+}