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

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