From 8fc93d0d507c30b79cfd8afd11b611907ca9f25c Mon Sep 17 00:00:00 2001 From: ghidragon <106987263+ghidragon@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:43:40 -0400 Subject: [PATCH] GP-5886 Added ability to toggle the display of function variables in the listing. --- .../gui/listing/DebuggerListingPlugin.java | 10 +- Ghidra/Features/Base/certification.manifest | 1 - .../Base/data/base.listing.theme.properties | 1 + .../topics/CodeBrowserPlugin/CodeBrowser.htm | 25 +- .../AbstractCodeBrowserPlugin.java | 1 + .../core/codebrowser/CodeBrowserPlugin.java | 2 + .../core/codebrowser/CodeViewerProvider.java | 88 +++++- .../viewer/field/AbstractOpenCloseField.java | 258 ++++++++++++++++++ .../app/util/viewer/field/IndentField.java | 6 +- .../app/util/viewer/field/OpenCloseField.java | 225 +-------------- .../field/OpenCloseFieldMouseHandler.java | 9 +- .../viewer/field/VariableOpenCloseField.java | 109 ++++++++ .../field/VariablesOpenCloseFieldFactory.java | 114 ++++++++ .../app/util/viewer/format/FormatManager.java | 8 +- .../listingpanel/EmptyListingModel.java | 19 +- .../viewer/listingpanel/ListingModel.java | 60 +++- .../listingpanel/ProgramBigListingModel.java | 92 +++++-- .../multilisting/ListingModelConverter.java | 19 +- .../multilisting/MultiListingLayoutModel.java | 15 + .../viewer/proxy/ClosedVariableProxy.java | 34 +++ .../app/util/viewer/proxy/VariableProxy.java | 30 +- .../util/AddressBasedOpenCloseManager.java | 93 +++++++ ...Manager.java => DataOpenCloseManager.java} | 102 ++----- .../viewer/util/ProgramOpenCloseManager.java | 167 ++++++++++++ .../util/VariablesOpenCloseLocation.java | 38 +++ 25 files changed, 1165 insertions(+), 361 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AbstractOpenCloseField.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableOpenCloseField.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariablesOpenCloseFieldFactory.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/proxy/ClosedVariableProxy.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressBasedOpenCloseManager.java rename Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/{OpenCloseManager.java => DataOpenCloseManager.java} (79%) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/ProgramOpenCloseManager.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/VariablesOpenCloseLocation.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java index 105c9d9ad6..bd4e9be4fe 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.gui.listing; -import static ghidra.app.plugin.core.debug.gui.DebuggerResources.GROUP_TRANSIENT_VIEWS; +import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import java.util.List; import java.util.function.Consumer; @@ -32,7 +32,8 @@ import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractNewListingAction; -import ghidra.app.plugin.core.debug.gui.action.*; +import ghidra.app.plugin.core.debug.gui.action.DebuggerProgramLocationActionContext; +import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec; import ghidra.app.services.*; import ghidra.app.util.viewer.format.FormatManager; import ghidra.app.util.viewer.listingpanel.ListingPanel; @@ -81,7 +82,8 @@ import ghidra.trace.model.program.TraceProgramView; }, servicesProvided = { DebuggerListingService.class, - }) + } +) public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin implements DebuggerListingService { private static final String KEY_CONNECTED_PROVIDER = "connectedProvider"; @@ -450,6 +452,7 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin - + +

Opening/Closing Function Variables Display

+
+ The display of parameters and local variables for a function can be toggled on or off. + Normally, these are displayed just below the function signature, but they can be turned off + to conserve screen space. There is an open/close control (+/-) + on the first parameter/variable line that can be used to toggle them on or off.

+

Actions for Opening/Closing Function Variables Display

+
+

In addition to the control widget in the display, there are also several actions that + can be used to control the display of function variables.

+
    +
  • Show/Hide All Variables - This toggle + action can be used to globally control whether or not function variables are displayed. + Individual functions can still be toggled on or off, but this sets the default for all + functions.
  • +
  • Show/Hide Variables - This action toggles + the display of the variables. If they are showing, this will turn them off. And if they + are not showing, this action will turn them on. The default keybinding is + Space so if you are on a function or variable, pressing the space bar will + toggle the display state.
  • +
+
+

Cursor Text Highlight

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java index ffc4d1ca5a..7f95da12f0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java @@ -181,6 +181,7 @@ public abstract class AbstractCodeBrowserPlugin

ex public P createNewDisconnectedProvider() { P newProvider = createProvider(formatMgr.createClone(), false); newProvider.setClipboardService(tool.getService(ClipboardService.class)); + disconnectedProviders.add(newProvider); if (dndProvider != null) { newProvider.addProgramDropProvider(dndProvider); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java index 542e5ae16d..3d42d9a5b0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java @@ -274,12 +274,14 @@ public class CodeBrowserPlugin extends AbstractCodeBrowserPlugin showVariablesForAllFunctions(toggleVariablesAction.isSelected())) + .buildAndInstallLocal(this); + + new ActionBuilder("Toggle Show Function Variables", plugin.getName()) + .popupMenuPath("Show/Hide Variables") + .popupMenuGroup("Variables") + .helpLocation(new HelpLocation("CodeBrowserPlugin", "Show_Variables")) + .keyBinding("SPACE") + .withContext(ProgramLocationActionContext.class) + .enabledWhen(this::isInFunctionArea) + .onAction(c -> toggleShowVariables(c.getAddress())) + .buildAndInstallLocal(this); + buildQuickTogleFieldActions(); } + private void toggleShowVariables(Address address) { + ListingModel model = listingPanel.getListingModel(); + boolean open = model.areFunctionVariablesOpen(address); + model.setAllFunctionVariablesOpen(!open); + setLocation(new VariablesOpenCloseLocation(program, address)); + } + + private void showVariablesForAllFunctions(boolean selected) { + ListingModel model = listingPanel.getListingModel(); + model.setAllFunctionVariablesOpen(selected); + } + + private boolean isInFunctionArea(ProgramLocationActionContext context) { + ProgramLocation location = context.getLocation(); + return location instanceof FunctionLocation || + location instanceof VariablesOpenCloseLocation; + } + private void buildQuickTogleFieldActions() { List quickToggleFieldNames = formatMgr.getQuickToggleFieldNames(); int count = 0; for (String fieldName : quickToggleFieldNames) { - DockingAction toggleAction = new ActionBuilder("Toggle " + fieldName, plugin.getName()) + String keyBinding = null; + if (count < 5) { + char c = (char) ('1' + count); + keyBinding = "control shift " + c; + } + else { + Msg.debug(this, + "Excessive Field Toggle actions . No keybinding assigned for field: " + + fieldName); + } + + new ActionBuilder("Toggle " + fieldName, plugin.getName()) .popupMenuPath("Toggle Field", fieldName) .popupMenuGroup("Field", "" + count) + .keyBinding(keyBinding) .helpLocation(new HelpLocation("CodeBrowserPlugin", "Toggle_Field")) // only show this action when over the listing field header .popupWhen(c -> c.getContextObject() instanceof FieldHeaderLocation) @@ -488,19 +549,10 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter .buildAndInstallLocal(this); // automatically assign keybindings to the first 5 toggle fields. - if (count < 5) { - char c = (char) ('1' + count); - toggleAction.setKeyBindingData( - new KeyBindingData(c, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); - } - else { - Msg.debug(this, - "Excessive Field Toggle actions . No keybinding assigned for field: " + - fieldName); - } count++; } tool.setMenuGroup(new String[] { "Toggle Field" }, "Disassembly"); + } public ListingPanel getListingPanel() { @@ -820,12 +872,19 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter void saveState(SaveState saveState) { saveState.putInt(DIVIDER_LOCATION, getListingPanel().getDividerLocation()); saveState.putBoolean(HOVER_MODE, toggleHoverAction.isSelected()); + saveState.putBoolean(SHOW_FUNCITON_VARS_OPTIONS_NAME, toggleVariablesAction.isSelected()); } void readState(SaveState saveState) { getListingPanel().setDividerLocation( saveState.getInt(DIVIDER_LOCATION, ListingPanel.DEFAULT_DIVIDER_LOCATION)); toggleHoverAction.setSelected(saveState.getBoolean(HOVER_MODE, true)); + boolean showVariables = saveState.getBoolean(SHOW_FUNCITON_VARS_OPTIONS_NAME, true); + toggleVariablesAction.setSelected(showVariables); + ListingModel listingModel = listingPanel.getListingModel(); + if (listingModel != null) { + listingModel.setAllFunctionVariablesOpen(showVariables); + } } private void setHoverEnabled(boolean enabled) { @@ -951,9 +1010,12 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter // (its done in an invoke later) Swing.runLater(() -> { newProvider.doSetProgram(program); + SaveState saveState = new SaveState(); + saveState(saveState); + newProvider.readState(saveState); + newProvider.setLocation(currentLocation); newProvider.listingPanel.getFieldPanel() .setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset()); - newProvider.setLocation(currentLocation); }); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AbstractOpenCloseField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AbstractOpenCloseField.java new file mode 100644 index 0000000000..ae32eefb3c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AbstractOpenCloseField.java @@ -0,0 +1,258 @@ +/* ### + * 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.util.viewer.field; + +import java.awt.*; + +import docking.widgets.fieldpanel.support.*; +import generic.theme.GIcon; +import ghidra.app.util.viewer.proxy.EmptyProxy; +import ghidra.app.util.viewer.proxy.ProxyObj; + +/** + * FactoryField class for displaying the open/close field. + */ +public abstract class AbstractOpenCloseField implements ListingField { + protected static final GIcon OPEN_ICON = + new GIcon("icon.base.util.viewer.fieldfactory.openclose.open"); + protected static final GIcon CLOSED_ICON = + new GIcon("icon.base.util.viewer.fieldfactory.openclose.closed"); + + private FieldFactory factory; + protected int startX; + protected int startY; + protected int fieldWidth; + protected int heightAbove; + protected int heightBelow; + protected ProxyObj proxy; + + protected boolean isOpen; + + protected int toggleHandleSize; + + /** + * Constructor + * @param factory the FieldFactory that created this field. + * @param proxy the object associated with this field. + * @param metrics the FontMetrics used to render this field. + * @param x the starting x position of this field. + * @param width the width of this field. + */ + public AbstractOpenCloseField(FieldFactory factory, ProxyObj proxy, + FontMetrics metrics, int x, int width) { + this.factory = factory; + this.proxy = proxy; + this.fieldWidth = width; + this.startX = x; + + this.heightAbove = metrics.getAscent(); + this.heightBelow = metrics.getLeading() + metrics.getDescent(); + this.toggleHandleSize = AbstractOpenCloseField.getOpenCloseHandleSize(); + } + + @Override + public FieldFactory getFieldFactory() { + return factory; + } + + @Override + public ProxyObj getProxy() { + if (proxy == null) { + return EmptyProxy.EMPTY_PROXY; + } + return proxy; + } + + @Override + public int getHeightAbove() { + return heightAbove; + } + + @Override + public int getHeightBelow() { + return heightBelow; + } + + /** + * Sets the yPos relative to the overall layout. + * @param yPos the starting Y position of the layout row. + * @param heightAbove the heightAbove the alignment line in the layout row. + * @param heightBelow the heightBelow the alignment line in the layout row. + */ + public void setYPos(int yPos, int heightAbove, int heightBelow) { + this.startY = yPos; + this.heightAbove = heightAbove; + this.heightBelow = heightBelow; + } + + @Override + public int getPreferredWidth() { + return getWidth(); // does the width of this field vary? + } + + @Override + public int getHeight() { + return heightAbove + heightBelow; + } + + @Override + public int getStartX() { + return startX; + } + + /** + * Returns the vertical position of this field. + * @return the position + */ + public int getStartY() { + return startY; + } + + /** + * Sets the starting vertical position of this field. + * @param startY the starting vertical position. + */ + public void setStartY(int startY) { + this.startY = startY; + } + + protected void paintCursor(Graphics g, Color cursorColor, RowColLocation cursorLoc) { + if (cursorLoc != null) { + g.setColor(cursorColor); + Rectangle cursorBounds = getCursorBounds(cursorLoc.row(), cursorLoc.col()); + g.fillRect(cursorBounds.x, cursorBounds.y, cursorBounds.width, cursorBounds.height); + } + } + + @Override + public boolean contains(int x, int y) { + if ((x < startX) || (x >= startX + fieldWidth) || (y < startY) || + (y >= startY + heightAbove + heightBelow)) { + return false; + } + return true; + } + + @Override + public int getNumDataRows() { + return 1; + } + + @Override + public int getNumRows() { + return 1; + } + + @Override + public int getNumCols(int row) { + return 0; + } + + @Override + public int getX(int row, int col) { + return startX; + } + + @Override + public int getY(int row) { + return startY; + } + + @Override + public int getRow(int y) { + return 0; + } + + @Override + public int getCol(int row, int x) { + return 0; + } + + @Override + public boolean isValid(int row, int col) { + return ((row == 0) && (col == 0)); + } + + @Override + public Rectangle getCursorBounds(int row, int col) { + if (!isValid(row, col)) { + return null; + } + + return new Rectangle(startX, -heightAbove, 2, heightAbove + heightBelow); + } + + @Override + public int getScrollableUnitIncrement(int topOfScreen, int direction, int max) { + if ((topOfScreen < startY) || (topOfScreen > startY + heightAbove + heightBelow)) { + return max; + } + + if (direction > 0) { // if scrolling down + return heightAbove + heightBelow - (topOfScreen - startY); + } + return startY - topOfScreen; + } + + @Override + public boolean isPrimary() { + return false; + } + + @Override + public void rowHeightChanged(int newHeightAbove, int newHeightBelow) { + this.heightAbove = newHeightAbove; + this.heightBelow = newHeightBelow; + } + + @Override + public String getText() { + return ""; + } + + @Override + public String getTextWithLineSeparators() { + return ""; + } + + @Override + public RowColLocation textOffsetToScreenLocation(int textOffset) { + return new DefaultRowColLocation(); + } + + @Override + public int screenLocationToTextOffset(int row, int col) { + return 0; + } + + @Override + public Object getClickedObject(FieldLocation fieldLocation) { + return this; + } + + /** + * Toggles the open state of this field. + */ + public abstract void toggleOpenCloseState(); + +//================================================================================================== +// Static Methods +//================================================================================================== + + static int getOpenCloseHandleSize() { + return OPEN_ICON.getIconWidth(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/IndentField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/IndentField.java index 55ed45f763..fefdd816ec 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/IndentField.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/IndentField.java @@ -4,9 +4,9 @@ * 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. @@ -68,7 +68,7 @@ public class IndentField implements ListingField { // this class is dependent upon the OpenClosedField in that they work together to perform // painting - toggleHandleSize = OpenCloseField.getOpenCloseHandleSize(); + toggleHandleSize = AbstractOpenCloseField.getOpenCloseHandleSize(); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseField.java index 12896ee1f9..d65012cecb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseField.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseField.java @@ -4,9 +4,9 @@ * 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. @@ -15,41 +15,25 @@ */ package ghidra.app.util.viewer.field; +import static ghidra.app.util.viewer.field.AbstractOpenCloseField.*; + import java.awt.*; import javax.swing.JComponent; import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager; import docking.widgets.fieldpanel.internal.PaintContext; -import docking.widgets.fieldpanel.support.*; -import generic.theme.GIcon; +import docking.widgets.fieldpanel.support.RowColLocation; import generic.theme.GThemeDefaults.Colors.Palette; -import ghidra.app.util.viewer.proxy.EmptyProxy; import ghidra.app.util.viewer.proxy.ProxyObj; import ghidra.program.model.listing.Data; /** * FactoryField class for displaying the open/close field. */ -public class OpenCloseField implements ListingField { - private static final GIcon OPEN_ICON = - new GIcon("icon.base.util.viewer.fieldfactory.openclose.open"); - private static final GIcon CLOSED_ICON = - new GIcon("icon.base.util.viewer.fieldfactory.openclose.closed"); - - private FieldFactory factory; - private int startX; - private int startY; - private int fieldWidth; - private int heightAbove; - private int heightBelow; - private ProxyObj proxy; - - private boolean isOpen; +public class OpenCloseField extends AbstractOpenCloseField { private int indentLevel; private boolean isLast; - - private int toggleHandleSize; private int insetSpace = 1; /** @@ -64,51 +48,10 @@ public class OpenCloseField implements ListingField { */ public OpenCloseField(FieldFactory factory, ProxyObj proxy, int indentLevel, FontMetrics metrics, int x, int width, boolean isLast) { - this.factory = factory; - this.proxy = proxy; + super(factory, proxy, metrics, x, width); this.isOpen = proxy.getListingLayoutModel().isOpen((Data) proxy.getObject()); - this.fieldWidth = width; - this.startX = x; this.indentLevel = indentLevel; this.isLast = isLast; - this.heightAbove = metrics.getAscent(); - this.heightBelow = metrics.getLeading() + metrics.getDescent(); - this.toggleHandleSize = OpenCloseField.getOpenCloseHandleSize(); - } - - @Override - public FieldFactory getFieldFactory() { - return factory; - } - - @Override - public ProxyObj getProxy() { - if (proxy == null) { - return EmptyProxy.EMPTY_PROXY; - } - return proxy; - } - - @Override - public int getHeightAbove() { - return heightAbove; - } - - @Override - public int getHeightBelow() { - return heightBelow; - } - - /** - * Sets the yPos relative to the overall layout. - * @param yPos the starting Y position of the layout row. - * @param heightAbove the heightAbove the alignment line in the layout row. - * @param heightBelow the heightBelow the alignment line in the layout row. - */ - public void setYPos(int yPos, int heightAbove, int heightBelow) { - this.startY = yPos; - this.heightAbove = heightAbove; - this.heightBelow = heightBelow; } @Override @@ -116,37 +59,6 @@ public class OpenCloseField implements ListingField { return (indentLevel + 1) * fieldWidth; } - @Override - public int getPreferredWidth() { - return getWidth(); // does the width of this field vary? - } - - @Override - public int getHeight() { - return heightAbove + heightBelow; - } - - @Override - public int getStartX() { - return startX; - } - - /** - * Returns the vertical position of this field. - * @return the position - */ - public int getStartY() { - return startY; - } - - /** - * Sets the starting vertical position of this field. - * @param startY the starting vertical position. - */ - public void setStartY(int startY) { - this.startY = startY; - } - @Override public void paint(JComponent c, Graphics g, PaintContext context, Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc, @@ -211,132 +123,11 @@ public class OpenCloseField implements ListingField { paintCursor(g, context.getCursorColor(), cursorLoc); } - private void paintCursor(Graphics g, Color cursorColor, RowColLocation cursorLoc) { - if (cursorLoc != null) { - g.setColor(cursorColor); - Rectangle cursorBounds = getCursorBounds(cursorLoc.row(), cursorLoc.col()); - g.fillRect(cursorBounds.x, cursorBounds.y, cursorBounds.width, cursorBounds.height); - } - } - - @Override - public boolean contains(int x, int y) { - if ((x < startX) || (x >= startX + fieldWidth) || (y < startY) || - (y >= startY + heightAbove + heightBelow)) { - return false; - } - return true; - } - - @Override - public int getNumDataRows() { - return 1; - } - - @Override - public int getNumRows() { - return 1; - } - - @Override - public int getNumCols(int row) { - return 0; - } - - @Override - public int getX(int row, int col) { - return startX; - } - - @Override - public int getY(int row) { - return startY; - } - - @Override - public int getRow(int y) { - return 0; - } - - @Override - public int getCol(int row, int x) { - return 0; - } - - @Override - public boolean isValid(int row, int col) { - return ((row == 0) && (col == 0)); - } - - @Override - public Rectangle getCursorBounds(int row, int col) { - if (!isValid(row, col)) { - return null; - } - - return new Rectangle(startX, -heightAbove, 2, heightAbove + heightBelow); - } - - @Override - public int getScrollableUnitIncrement(int topOfScreen, int direction, int max) { - if ((topOfScreen < startY) || (topOfScreen > startY + heightAbove + heightBelow)) { - return max; - } - - if (direction > 0) { // if scrolling down - return heightAbove + heightBelow - (topOfScreen - startY); - } - return startY - topOfScreen; - } - - @Override - public boolean isPrimary() { - return false; - } - - @Override - public void rowHeightChanged(int newHeightAbove, int newHeightBelow) { - this.heightAbove = newHeightAbove; - this.heightBelow = newHeightBelow; - } - - @Override - public String getText() { - return ""; - } - - @Override - public String getTextWithLineSeparators() { - return ""; - } - - @Override - public RowColLocation textOffsetToScreenLocation(int textOffset) { - return new DefaultRowColLocation(); - } - - @Override - public int screenLocationToTextOffset(int row, int col) { - return 0; - } - - @Override - public Object getClickedObject(FieldLocation fieldLocation) { - return this; - } - /** * Toggles the open state of this field. */ + @Override public void toggleOpenCloseState() { proxy.getListingLayoutModel().toggleOpen((Data) proxy.getObject()); } - -//================================================================================================== -// Static Methods -//================================================================================================== - - static int getOpenCloseHandleSize() { - return OPEN_ICON.getIconWidth(); - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseFieldMouseHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseFieldMouseHandler.java index 4cc0329431..a5602efbf5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseFieldMouseHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OpenCloseFieldMouseHandler.java @@ -15,14 +15,15 @@ */ package ghidra.app.util.viewer.field; +import java.awt.event.MouseEvent; + import ghidra.app.nav.Navigatable; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.util.ProgramLocation; -import java.awt.event.MouseEvent; - public class OpenCloseFieldMouseHandler implements FieldMouseHandlerExtension { - private final static Class[] SUPPORTED_CLASSES = new Class[] { OpenCloseField.class }; + private final static Class[] SUPPORTED_CLASSES = + new Class[] { OpenCloseField.class, VariableOpenCloseField.class }; @Override public boolean fieldElementClicked(Object clickedObject, Navigatable sourceNavigatable, @@ -32,7 +33,7 @@ public class OpenCloseFieldMouseHandler implements FieldMouseHandlerExtension { return false; } - OpenCloseField field = (OpenCloseField) clickedObject; + AbstractOpenCloseField field = (AbstractOpenCloseField) clickedObject; field.toggleOpenCloseState(); return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableOpenCloseField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableOpenCloseField.java new file mode 100644 index 0000000000..19e675a6ed --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableOpenCloseField.java @@ -0,0 +1,109 @@ +/* ### + * 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.util.viewer.field; + +import java.awt.*; + +import javax.swing.JComponent; + +import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager; +import docking.widgets.fieldpanel.internal.PaintContext; +import docking.widgets.fieldpanel.support.RowColLocation; +import generic.theme.GThemeDefaults.Colors.Palette; +import generic.theme.Gui; +import ghidra.app.util.viewer.proxy.ProxyObj; +import ghidra.app.util.viewer.proxy.VariableProxy; +import ghidra.program.model.address.Address; + +/** + * FactoryField class for displaying the open/close field widget for function variables. + */ +public class VariableOpenCloseField extends AbstractOpenCloseField { + private static final Font HIDDEN_FONT = Gui.getFont("font.listing.base.hidden.field"); + + /** + * Constructor + * @param factory the FieldFactory that created this field. + * @param proxy the object associated with this field. + * @param metrics the FontMetrics used to render this field. + * @param x the starting x position of this field. + * @param width the width of this field. + */ + public VariableOpenCloseField(FieldFactory factory, ProxyObj proxy, + FontMetrics metrics, int x, int width) { + super(factory, proxy, metrics, x, width); + if (proxy instanceof VariableProxy variableProxy) { + Address functionAddress = variableProxy.getFunctionAddress(); + this.isOpen = proxy.getListingLayoutModel().areFunctionVariablesOpen(functionAddress); + } + } + + @Override + public int getWidth() { + if (isOpen) { + return fieldWidth; + } + return fieldWidth + 200; + } + + @Override + public void paint(JComponent c, Graphics g, PaintContext context, + Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc, + int rowHeight) { + + // center in the heightAbove area (negative, since 0 is the baseline of text, which is at + // the bottom of the heightAbove) + int toggleHandleStartY = -((heightAbove / 2) + (toggleHandleSize / 2)); + int toggleHandleStartX = startX; + + // If we're in printing mode, trying to render these open/close images + // causes the JVM to bomb. We'd like to eventually figure out why but in + // the meantime we can safely comment this out and still generate an acceptable + // image. + // + if (!context.isPrinting()) { + if (isOpen) { + g.drawImage(OPEN_ICON.getImageIcon().getImage(), toggleHandleStartX, + toggleHandleStartY, context.getBackground(), null); + } + else { + g.drawImage(CLOSED_ICON.getImageIcon().getImage(), toggleHandleStartX, + toggleHandleStartY, context.getBackground(), null); + } + } + + g.setColor(Palette.LIGHT_GRAY); + + if (!isOpen) { + Font font = g.getFont(); + g.setFont(HIDDEN_FONT); + g.drawString("Variables", startX + fieldWidth + 10, 0); + g.setFont(font); + } + paintCursor(g, context.getCursorColor(), cursorLoc); + } + + /** + * Toggles the open state of this field. + */ + @Override + public void toggleOpenCloseState() { + if (proxy instanceof VariableProxy variableProxy) { + Address functionAddress = variableProxy.getFunctionAddress(); + proxy.getListingLayoutModel().setFunctionVariablesOpen(functionAddress, !isOpen); + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariablesOpenCloseFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariablesOpenCloseFieldFactory.java new file mode 100644 index 0000000000..655e5296d3 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariablesOpenCloseFieldFactory.java @@ -0,0 +1,114 @@ +/* ### + * 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.util.viewer.field; + +import java.math.BigInteger; + +import docking.widgets.fieldpanel.support.FieldLocation; +import ghidra.app.util.ListingHighlightProvider; +import ghidra.app.util.viewer.format.FieldFormatModel; +import ghidra.app.util.viewer.proxy.ProxyObj; +import ghidra.app.util.viewer.proxy.VariableProxy; +import ghidra.framework.options.Options; +import ghidra.framework.options.ToolOptions; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.Variable; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.VariablesOpenCloseLocation; + +/** + * Generates Open/Close Fields for variables under functions. + */ +public class VariablesOpenCloseFieldFactory extends FieldFactory { + + public static final String FIELD_NAME = "+"; + + public VariablesOpenCloseFieldFactory() { + super(FIELD_NAME); + } + + /** + * Constructor + * @param model the model that the field belongs to. + * @param hlProvider the HightLightStringProvider. + * @param displayOptions the Options for display properties. + * @param fieldOptions the Options for field specific properties. + */ + private VariablesOpenCloseFieldFactory(FieldFormatModel model, + ListingHighlightProvider hlProvider, + Options displayOptions, Options fieldOptions) { + super(FIELD_NAME, model, hlProvider, displayOptions, fieldOptions); + servicesChanged(); + } + + /** + * Returns the FactoryField for the given object at index index. + * @param varWidth the amount of variable width spacing for any fields + * before this one. + * @param proxy the object whose properties should be displayed. + */ + @Override + public ListingField getField(ProxyObj proxy, int varWidth) { + + if (!enabled) { + return null; + } + if (proxy instanceof VariableProxy variableProxy) { + if (variableProxy.isFirst()) { + return new VariableOpenCloseField(this, proxy, getMetrics(), startX + varWidth, + width); + } + } + return null; + } + + @Override + public ProgramLocation getProgramLocation(int row, int col, ListingField bf) { + ProxyObj proxy = bf.getProxy(); + if (proxy instanceof VariableProxy variableProxy) { + Program program = variableProxy.getProgram(); + Address functionAddress = variableProxy.getFunctionAddress(); + return new VariablesOpenCloseLocation(program, functionAddress); + } + return null; + } + + @Override + public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum, + ProgramLocation programLoc) { + + if (!(programLoc instanceof VariablesOpenCloseLocation)) { + return null; + } + return new FieldLocation(index, fieldNum, 0, 0); + } + + @Override + public boolean acceptsType(int category, Class proxyObjectClass) { + if (!Variable.class.isAssignableFrom(proxyObjectClass)) { + return false; + } + return (category == FieldFormatModel.FUNCTION_VARS); + } + + @Override + public FieldFactory newInstance(FieldFormatModel fieldModel, ListingHighlightProvider provider, + ToolOptions displayOptions, ToolOptions fieldOptions) { + return new VariablesOpenCloseFieldFactory(fieldModel, provider, displayOptions, + fieldOptions); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java index 5a9161c6ce..dcc2ea52c9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java @@ -516,7 +516,13 @@ public class FormatManager implements OptionsChangeListener { Element rowElem = new Element("ROW"); Element colElem = new Element("FIELD"); - colElem.setAttribute("WIDTH", "90"); + colElem.setAttribute("NAME", "+"); + colElem.setAttribute("WIDTH", "20"); + colElem.setAttribute("ENABLED", "true"); + rowElem.addContent(colElem); + + colElem = new Element("FIELD"); + colElem.setAttribute("WIDTH", "70"); colElem.setAttribute("ENABLED", "true"); rowElem.addContent(colElem); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/EmptyListingModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/EmptyListingModel.java index fe882b82e0..9191175f36 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/EmptyListingModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/EmptyListingModel.java @@ -4,9 +4,9 @@ * 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. @@ -73,6 +73,21 @@ public class EmptyListingModel implements ListingModel { // stub } + @Override + public void setFunctionVariablesOpen(Address functionAddress, boolean open) { + // stub + } + + @Override + public void setAllFunctionVariablesOpen(boolean open) { + // stub + } + + @Override + public boolean areFunctionVariablesOpen(Address FunctionAddress) { + return false; + } + @Override public void openAllData(Data data, TaskMonitor monitor) { // stub diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingModel.java index c950fdc2a1..d2ebb1bdd1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingModel.java @@ -4,9 +4,9 @@ * 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. @@ -15,6 +15,7 @@ */ package ghidra.app.util.viewer.listingpanel; +import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.Layout; import ghidra.app.util.viewer.format.FormatManager; import ghidra.framework.options.Options; @@ -23,6 +24,9 @@ import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Program; import ghidra.util.task.TaskMonitor; +/** + * Model displaying program data in a {@link FieldPanel} + */ public interface ListingModel { static final String FUNCTION_POINTER_OPTION_GROUP_NAME = "Function Pointers"; @@ -42,6 +46,9 @@ public interface ListingModel { public Layout getLayout(Address address, boolean isGapAddress); + /** + * {@return the width of the longest layout this model can produce.} + */ public int getMaxWidth(); /** @@ -59,6 +66,28 @@ public interface ListingModel { */ public void toggleOpen(Data data); + /** + * Sets whether or not to display function variables for the function at the given address. + * @param FunctionAddress the address of the function + * @param open if true, the variables are displayed, otherwise they are hidden + */ + public void setFunctionVariablesOpen(Address FunctionAddress, boolean open); + + /** + * Checks if the function variables are being displayed at the given address + * @param FunctionAddress the address of the function + * @return true if the variables are being displayed for the function at the given address + */ + public boolean areFunctionVariablesOpen(Address FunctionAddress); + + /** + * Sets the display of variables for all functions. This basically sets the default state, + * but the state can be overridden for individual functions. Changing this value erases all + * individually set values. + * @param open if true, show function variables + */ + public void setAllFunctionVariablesOpen(boolean open); + /** * Opens the given data, but not any sub-components. * @@ -106,18 +135,45 @@ public interface ListingModel { */ public void closeAllData(AddressSetView addresses, TaskMonitor monitor); + /** + * Adds a listener for changes to this model. + * @param listener the listener to be notified + */ public void addListener(ListingModelListener listener); + /** + * Removes a listener from those being notified of model changes. + * @param listener the listener to be removed + */ public void removeListener(ListingModelListener listener); + /** + * {@return the program being displayed by this model.} + */ public Program getProgram(); + /** + * {@return true if the program being displayed by this listing has been closed (and therefor + * the model is invalid.)} + */ public boolean isClosed(); + /** + * Sets the {@link FormatManager} for this model which determines the layout of the fields. + * @param formatManager the new FormatManager to use + */ public void setFormatManager(FormatManager formatManager); + /** + * Disposes this model + */ public void dispose(); + /** + * Adjusts each range in the given address set to be on code unit boundaries. + * @param addressSet the address set to be adjusted + * @return a new AddressSet where each range is on a code unit boundary + */ public AddressSet adjustAddressSetToCodeUnitBoundaries(AddressSet addressSet); /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramBigListingModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramBigListingModel.java index 6140eb79c2..60769c89f1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramBigListingModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramBigListingModel.java @@ -27,7 +27,7 @@ import ghidra.app.util.viewer.field.DummyFieldFactory; import ghidra.app.util.viewer.field.ListingField; import ghidra.app.util.viewer.format.*; import ghidra.app.util.viewer.proxy.*; -import ghidra.app.util.viewer.util.OpenCloseManager; +import ghidra.app.util.viewer.util.ProgramOpenCloseManager; import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectListener; import ghidra.framework.options.OptionsChangeListener; @@ -43,7 +43,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener DomainObjectListener, ChangeListener, OptionsChangeListener { protected final Program program; - private OpenCloseManager openCloseMgr = new OpenCloseManager(); + private ProgramOpenCloseManager openCloseMgr = new ProgramOpenCloseManager(); private FormatManager formatMgr; private ToolOptions fieldOptions; private boolean showExternalFunctionPointerFormat; @@ -172,17 +172,17 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener if (function != null) { format = formatMgr.getFunctionFormat(); format.addLayouts(list, 0, new FunctionProxy(this, program, addr, function)); - Parameter[] params = function.getParameters(); format = formatMgr.getFunctionVarFormat(); - format.addLayouts(list, 0, - new VariableProxy(this, program, addr, function, function.getReturn())); - for (Parameter param : params) { - format.addLayouts(list, 0, new VariableProxy(this, program, addr, function, param)); + boolean variablesOpen = openCloseMgr.isFunctionVariablesOpen(function.getEntryPoint()); + if (variablesOpen) { + addReturn(addr, list, format, function); + addParameters(addr, list, format, function); + addLocals(addr, list, format, function); } - Variable[] vars = function.getLocalVariables(); - for (Variable var : vars) { - format.addLayouts(list, 0, new VariableProxy(this, program, addr, function, var)); + else { + format.addLayouts(list, 0, new ClosedVariableProxy(this, program, addr, function)); } + } if (cu != null) { format = formatMgr.getCodeUnitFormat(); @@ -213,6 +213,30 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener } + private void addReturn(Address addr, List list, FieldFormatModel format, + Function function) { + format.addLayouts(list, 0, + new VariableProxy(this, program, addr, function, function.getReturn(), true)); + } + + private void addLocals(Address addr, List list, FieldFormatModel format, + Function function) { + Variable[] vars = function.getLocalVariables(); + for (Variable var : vars) { + format.addLayouts(list, 0, + new VariableProxy(this, program, addr, function, var, false)); + } + } + + private void addParameters(Address addr, List list, FieldFormatModel format, + Function function) { + Parameter[] params = function.getParameters(); + for (Parameter param : params) { + format.addLayouts(list, 0, + new VariableProxy(this, program, addr, function, param, false)); + } + } + private Function getPointerReferencedFunction(Data data) { Reference ref = data.getPrimaryReference(0); @@ -239,7 +263,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener if (cu instanceof Data) { Data data = (Data) cu; if (data.getNumComponents() > 0) { - if (openCloseMgr.isOpen(data.getMinAddress())) { + if (openCloseMgr.isDataOpen(data.getMinAddress())) { Address openAddr = findOpenDataAfter(address, data); if (openAddr != null) { return openAddr; @@ -255,8 +279,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener Data data; DataType dt = parent.getBaseDataType(); if (dt instanceof Union) { - int index = - openCloseMgr.getOpenIndex(parent.getMinAddress(), parent.getComponentPath()); + int index = openCloseMgr.getOpenDataIndex(parent); if (index < 0) { return null; } @@ -288,7 +311,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener return null; } if (data.getNumComponents() > 0) { - if (openCloseMgr.isOpen(data.getMinAddress(), data.getComponentPath())) { + if (openCloseMgr.isDataOpen(data)) { Address openAddr = findOpenDataAfter(address, data); if (openAddr != null) { return openAddr; @@ -344,7 +367,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener if (cu instanceof Data) { Data data = (Data) cu; if (data.getNumComponents() > 0) { - if (openCloseMgr.isOpen(data.getMinAddress())) { + if (openCloseMgr.isDataOpen(data.getMinAddress())) { return true; } } @@ -359,7 +382,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener Data data; if (parent.getBaseDataType() instanceof Union) { int index = - openCloseMgr.getOpenIndex(parent.getMinAddress(), parent.getComponentPath()); + openCloseMgr.getOpenDataIndex(parent); if (index < 0) { return null; } @@ -376,7 +399,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener } if (data.getNumComponents() > 0) { - if (openCloseMgr.isOpen(data.getMinAddress(), data.getComponentPath())) { + if (openCloseMgr.isDataOpen(data)) { Address openAddr = findOpenDataBefore(addr, data); if (openAddr != null) { return openAddr; @@ -396,11 +419,11 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener private void addOpenData(List list, Data data, Address addr) { Address dataAddr = data.getMinAddress(); - if (openCloseMgr.isOpen(dataAddr, data.getComponentPath())) { + if (openCloseMgr.isDataOpen(data)) { DataType dt = data.getBaseDataType(); if (dt instanceof Union) { int openIndex = - openCloseMgr.getOpenIndex(data.getMinAddress(), data.getComponentPath()); + openCloseMgr.getOpenDataIndex(data); int numComps = ((Union) dt).getNumComponents(); if (openIndex < 0) { openIndex = numComps; @@ -437,8 +460,8 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener DataType dt = data.getBaseDataType(); if (dt instanceof Union) { Address dataAddr = data.getMinAddress(); - if (openCloseMgr.isOpen(dataAddr, data.getComponentPath())) { - int openIndex = openCloseMgr.getOpenIndex(dataAddr, data.getComponentPath()); + if (openCloseMgr.isDataOpen(data)) { + int openIndex = openCloseMgr.getOpenDataIndex(data); int i = openIndex; int numComps = ((Union) dt).getNumComponents(); if (i < 0) { @@ -459,22 +482,37 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener @Override public boolean isOpen(Data data) { - return openCloseMgr.isOpen(data); + return openCloseMgr.isDataOpen(data); } @Override public void toggleOpen(Data data) { - openCloseMgr.toggleOpen(data); + openCloseMgr.toggleDataOpen(data); + } + + @Override + public void setFunctionVariablesOpen(Address functionAddress, boolean open) { + openCloseMgr.setFunctionVariablesOpen(functionAddress, open); + } + + @Override + public void setAllFunctionVariablesOpen(boolean open) { + openCloseMgr.setAllFunctionVariablesOpen(open); + } + + @Override + public boolean areFunctionVariablesOpen(Address FunctionAddress) { + return openCloseMgr.isFunctionVariablesOpen(FunctionAddress); } @Override public void openAllData(Data data, TaskMonitor monitor) { - openCloseMgr.openAllData(data, monitor); + openCloseMgr.openDataRecursively(data, monitor); } @Override public void closeAllData(Data data, TaskMonitor monitor) { - openCloseMgr.closeAllData(data, monitor); + openCloseMgr.closeDataRecursively(data, monitor); } @Override @@ -494,8 +532,8 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener @Override public boolean openData(Data data) { - return openCloseMgr.openData(data); - + openCloseMgr.openData(data); + return true; } protected void notifyDataChanged(boolean updateImmediately) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/ListingModelConverter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/ListingModelConverter.java index af1afb7dab..83558a3155 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/ListingModelConverter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/ListingModelConverter.java @@ -4,9 +4,9 @@ * 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. @@ -177,6 +177,21 @@ public class ListingModelConverter implements ListingModel { model.toggleOpen(data); } + @Override + public void setFunctionVariablesOpen(Address functionAddress, boolean open) { + model.setFunctionVariablesOpen(functionAddress, open); + } + + @Override + public void setAllFunctionVariablesOpen(boolean open) { + model.setAllFunctionVariablesOpen(open); + } + + @Override + public boolean areFunctionVariablesOpen(Address FunctionAddress) { + return model.areFunctionVariablesOpen(FunctionAddress); + } + @Override public AddressSet adjustAddressSetToCodeUnitBoundaries(AddressSet addressSet) { AddressSet compatibleAddressSet = DiffUtility.getCompatibleAddressSet(addressSet, program); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/MultiListingLayoutModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/MultiListingLayoutModel.java index 93de2ec169..c04de749a2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/MultiListingLayoutModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/MultiListingLayoutModel.java @@ -271,6 +271,21 @@ public class MultiListingLayoutModel implements ListingModelListener, FormatMode models[modelID].toggleOpen(data); } + @Override + public void setFunctionVariablesOpen(Address functionAddress, boolean open) { + models[modelID].setFunctionVariablesOpen(functionAddress, open); + } + + @Override + public void setAllFunctionVariablesOpen(boolean open) { + models[modelID].setAllFunctionVariablesOpen(open); + } + + @Override + public boolean areFunctionVariablesOpen(Address FunctionAddress) { + return models[modelID].areFunctionVariablesOpen(FunctionAddress); + } + @Override public boolean openData(Data data) { return models[modelID].openData(data); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/proxy/ClosedVariableProxy.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/proxy/ClosedVariableProxy.java new file mode 100644 index 0000000000..33d0b6d3e9 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/proxy/ClosedVariableProxy.java @@ -0,0 +1,34 @@ +/* ### + * 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.util.viewer.proxy; + +import ghidra.app.util.viewer.listingpanel.ListingModel; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +/** + * Variable Proxy for when the function variables aren't being shown. It doesn't include + * a variable object which prevents the various variable field factories from triggering. + */ +public class ClosedVariableProxy extends VariableProxy { + + public ClosedVariableProxy(ListingModel model, Program program, Address locationAddr, + Function fun) { + super(model, program, locationAddr, fun, null, true); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/proxy/VariableProxy.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/proxy/VariableProxy.java index 12c0e36f71..f77c18c781 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/proxy/VariableProxy.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/proxy/VariableProxy.java @@ -4,9 +4,9 @@ * 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. @@ -37,6 +37,7 @@ public class VariableProxy extends ProxyObj { private int firstUseOffset; private Variable var; private int ordinal = -1; + private boolean isFirst; /** * Constructs a proxy for a variable. @@ -45,20 +46,25 @@ public class VariableProxy extends ProxyObj { * @param locationAddr the listing address at which the function exists or was inferred via reference * @param fun the function containing the variable. * @param var the variable to proxy. + * @param isFirst true if this is the first parameter or variable */ public VariableProxy(ListingModel model, Program program, Address locationAddr, Function fun, - Variable var) { + Variable var, boolean isFirst) { super(model); this.program = program; this.locationAddr = locationAddr; this.var = var; + this.isFirst = isFirst; this.functionAddr = fun.getEntryPoint(); - if (var instanceof Parameter) { - ordinal = ((Parameter) var).getOrdinal(); + if (var != null) { + if (var instanceof Parameter) { + ordinal = ((Parameter) var).getOrdinal(); + } + + Varnode firstVarnode = var.getFirstStorageVarnode(); + storageAddr = firstVarnode != null ? firstVarnode.getAddress() : null; + firstUseOffset = var.getFirstUseOffset(); } - Varnode firstVarnode = var.getFirstStorageVarnode(); - storageAddr = firstVarnode != null ? firstVarnode.getAddress() : null; - firstUseOffset = var.getFirstUseOffset(); } @Override @@ -126,6 +132,10 @@ public class VariableProxy extends ProxyObj { return functionAddr; } + public Program getProgram() { + return program; + } + @Override public boolean contains(Address a) { Variable v = getObject(); @@ -134,4 +144,8 @@ public class VariableProxy extends ProxyObj { } return Objects.equals(v.getMinAddress(), a); } + + public boolean isFirst() { + return isFirst; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressBasedOpenCloseManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressBasedOpenCloseManager.java new file mode 100644 index 0000000000..08fcc7edf7 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressBasedOpenCloseManager.java @@ -0,0 +1,93 @@ +/* ### + * 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.util.viewer.util; + +import java.util.HashSet; +import java.util.Set; + +import ghidra.program.model.address.Address; + +/** + * Class to maintain a simple open close/state for address locations. The default open/close + * state can be set and then a set of address is kept for the locations that are the opposite + * of the default. + */ +public class AddressBasedOpenCloseManager { + private boolean openByDefault = true; + private Set

addresses = new HashSet<>(); + + /** + * Checks if the state is "open" for the given address. + * @param address the address to test + * @return true if the state of the given address is "open" + */ + public boolean isOpen(Address address) { + boolean contains = addresses.contains(address); + return openByDefault ? !contains : contains; + } + + /** + * Sets the state at the given address to be "open". + * @param address the address to set "open" + */ + public void open(Address address) { + if (openByDefault) { + addresses.remove(address); + } + else { + addresses.add(address); + } + } + + /** + * Sets the state at the given address to be "closed". + * @param address the address to set "closed" + */ + public void close(Address address) { + if (openByDefault) { + addresses.add(address); + } + else { + addresses.remove(address); + } + } + + /** + * Checks if the default state is "open". + * @return true if the default state for addresses is "open" + */ + public boolean isOpenByDefault() { + return openByDefault; + } + + /** + * Sets all address to "open" (Makes "open" the default state and clears all individual + * settings. + */ + public void openAll() { + openByDefault = true; + addresses.clear(); + } + + /** + * Sets all address to "closed" (Makes "closed" the default state and clears all individual + * settings. + */ + public void closeAll() { + openByDefault = false; + addresses.clear(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/OpenCloseManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/DataOpenCloseManager.java similarity index 79% rename from Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/OpenCloseManager.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/DataOpenCloseManager.java index dd07fa25bb..9be31fe002 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/OpenCloseManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/DataOpenCloseManager.java @@ -4,9 +4,9 @@ * 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. @@ -15,9 +15,8 @@ */ package ghidra.app.util.viewer.util; -import java.util.*; - -import javax.swing.event.ChangeListener; +import java.util.HashMap; +import java.util.Map; import ghidra.program.model.address.*; import ghidra.program.model.listing.*; @@ -27,33 +26,27 @@ import ghidra.util.task.TaskMonitorAdapter; /** * Manages the open/close state of structures and arrays at specific addresses. */ -public class OpenCloseManager { +public class DataOpenCloseManager { /** * The map stores an int[] for each address that has something open. * If map.get(address) returns null then outermost level is closed. */ private Map map = new HashMap<>(); - private List listeners = new ArrayList<>(); - /** * Marks the given data as open. This method notifies listeners of changes. * @param data The data to open. - * @return true if the data location was opened (false if already open or can't be opened) */ - public boolean openData(Data data) { + public void openData(Data data) { if (data.getComponent(0) == null) { - return false; + return; } Address addr = data.getMinAddress(); int[] path = data.getComponentPath(); - if (isOpen(addr, path)) { - return false; + if (!isDataOpen(addr, path)) { + openData(addr, path); } - open(addr, path); - notifyDataToggled(); - return true; } /** @@ -66,11 +59,10 @@ public class OpenCloseManager { } Address addr = data.getMinAddress(); int[] path = data.getComponentPath(); - if (!isOpen(addr, path)) { + if (!isDataOpen(addr, path)) { return; } close(addr, path); - notifyDataToggled(); } /** @@ -78,11 +70,11 @@ public class OpenCloseManager { * @param address the address to open * @param path the data component path to open */ - private void open(Address address, int[] path) { + private void openData(Address address, int[] path) { int pathSize = path.length; int[] levels = map.get(address); if ((levels == null) || (pathSize >= levels.length)) { - exactOpen(address, path); + exactOpenData(address, path); return; } levels[0] = 0; @@ -90,7 +82,7 @@ public class OpenCloseManager { for (; i < pathSize; i++) { if (levels[i + 1] != path[i]) { if (levels[i + 1] != -1) { - exactOpen(address, path); + exactOpenData(address, path); return; } levels[i + 1] = path[i]; @@ -99,7 +91,7 @@ public class OpenCloseManager { map.put(address, levels); } - private void exactOpen(Address address, int[] path) { + private void exactOpenData(Address address, int[] path) { int pathSize = path.length; int[] newLevels = new int[pathSize + 1]; newLevels[0] = 0; @@ -158,20 +150,13 @@ public class OpenCloseManager { return 0; } - /** - * Tests if the data at the given address is open - * @param address the address to test if open - */ - public boolean isOpen(Address address) { - return isOpen(address, null); - } - /** * Test is the data at the given address and component path is open * @param address the address to test * @param path the component path to test. + * @return true if the data with the given component path is open */ - public boolean isOpen(Address address, int[] path) { + public boolean isDataOpen(Address address, int[] path) { int[] levels = map.get(address); if (levels == null) { return false; @@ -199,8 +184,9 @@ public class OpenCloseManager { * Returns the index of the component that is open at the given address. * @param address the address to find the open index. * @param path the component path. + * @return the index of the component that is open at the given address. */ - public int getOpenIndex(Address address, int[] path) { + public int getOpenDataIndex(Address address, int[] path) { int[] levels = map.get(address); if ((levels == null) || (levels.length == 0)) { return -1; @@ -216,14 +202,8 @@ public class OpenCloseManager { return -1; } - public boolean isOpen(Data data) { - return isOpen(data.getMinAddress(), data.getComponentPath()); - } - public void toggleOpen(Data data) { toggleTopLevelData(data); - - notifyDataToggled(); } private void toggleTopLevelData(Data data) { @@ -233,17 +213,11 @@ public class OpenCloseManager { Address addr = data.getMinAddress(); int[] path = data.getComponentPath(); - if (isOpen(addr, path)) { + if (isDataOpen(addr, path)) { close(addr, path); } else { - open(addr, path); - } - } - - private void notifyDataToggled() { - for (ChangeListener l : listeners) { - l.stateChanged(null); + openData(addr, path); } } @@ -276,32 +250,22 @@ public class OpenCloseManager { long progress = addresses.getNumAddresses() - unprocessed.getNumAddresses(); monitor.setProgress(progress); } - - notifyDataToggled(); - } - - public void openAllData(Data data, TaskMonitor monitor) { - toggleDataRecursively(data, true, monitor); - - notifyDataToggled(); } public void closeAllData(Program program, AddressSetView addresses, TaskMonitor monitor) { toggleAllDataInAddresses(false, program, addresses, monitor); } - public void closeAllData(Data data, TaskMonitor monitor) { - toggleDataRecursively(data, false, monitor); - - notifyDataToggled(); + public boolean isDataOpen(Data data) { + return isDataOpen(data.getMinAddress(), data.getComponentPath()); } - private void toggleDataRecursively(Data data, boolean openState, TaskMonitor monitor) { + public void toggleDataRecursively(Data data, boolean openState, TaskMonitor monitor) { if (data == null && !monitor.isCancelled()) { return; } - if (isOpen(data) != openState) { + if (isDataOpen(data) != openState) { toggleTopLevelData(data); } int componentCount = data.getNumComponents(); @@ -326,7 +290,7 @@ public class OpenCloseManager { return; } - if (isOpen(data) != openState) { + if (isDataOpen(data) != openState) { toggleTopLevelData(data); } @@ -336,22 +300,6 @@ public class OpenCloseManager { } } - /** - * Adds a change listener to be notified when a location is open or closed. - * @param l the listener to be notified. - */ - public void addChangeListener(ChangeListener l) { - listeners.add(l); - } - - /** - * Removes the listener. - * @param l the listener to remove. - */ - public void removeChangeListener(ChangeListener l) { - listeners.remove(l); - } - //================================================================================================== // Inner Classes //================================================================================================== diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/ProgramOpenCloseManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/ProgramOpenCloseManager.java new file mode 100644 index 0000000000..05bbb53226 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/ProgramOpenCloseManager.java @@ -0,0 +1,167 @@ +/* ### + * 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.util.viewer.util; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.event.ChangeListener; + +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Program; +import ghidra.util.task.TaskMonitor; + +/** + * Manages the open/close state of various listing regions. This includes structures, arrays, and + * function variables. + */ +public class ProgramOpenCloseManager { + private DataOpenCloseManager dataOpenCloseManager = new DataOpenCloseManager(); + private List listeners = new ArrayList<>(); + private AddressBasedOpenCloseManager variablesOpenCloseManager = new AddressBasedOpenCloseManager(); + + /** + * Sets whether or not to display function variables at the given address. + * @param functionAddress the address of the function + * @param open true to display function variables, false to to hide them + */ + public void setFunctionVariablesOpen(Address functionAddress, boolean open) { + if (open) { + variablesOpenCloseManager.open(functionAddress); + } + else { + variablesOpenCloseManager.close(functionAddress); + } + notifyListeners(); + } + + /** + * Checks if the function variables are being shown at the given function address. + * @param functionAddress the address of the function to check + * @return true if the variables are being displayed + */ + public boolean isFunctionVariablesOpen(Address functionAddress) { + return variablesOpenCloseManager.isOpen(functionAddress); + } + + /** + * Sets whether or not function variables are being shown by globally. This essentially sets + * the default state, but the state can be overridden at specific functions. + * @param open if true, then the function variables are displayed + */ + public void setAllFunctionVariablesOpen(boolean open) { + if (open) { + variablesOpenCloseManager.openAll(); + } + else { + variablesOpenCloseManager.closeAll(); + } + notifyListeners(); + } + + public boolean isAllFunctionVariablesOpen() { + return variablesOpenCloseManager.isOpenByDefault(); + } + + /** + * Marks the given data as open. This method notifies listeners of changes. + * @param data The data to open. + */ + public void openData(Data data) { + dataOpenCloseManager.openData(data); + notifyListeners(); + } + + /** + * Marks the given data as open. This method notifies listeners of changes. + * @param data The data to open. + */ + public void closeData(Data data) { + dataOpenCloseManager.closeData(data); + notifyListeners(); + } + + /** + * Tests if the data at the given address is open + * @param address the address to test if open + * @return true if the data at the address is open. + */ + public boolean isDataOpen(Address address) { + return dataOpenCloseManager.isDataOpen(address, null); + } + + public boolean isDataOpen(Data data) { + return dataOpenCloseManager.isDataOpen(data); + } + + /** + * Returns the index of the component that is open at the given address. + * @param data the data to get the index for + * @return the index of the component that is open for the given data + */ + public int getOpenDataIndex(Data data) { + return dataOpenCloseManager.getOpenDataIndex(data.getMinAddress(), data.getComponentPath()); + } + + public void toggleDataOpen(Data data) { + dataOpenCloseManager.toggleOpen(data); + notifyListeners(); + } + + public void openAllData(Program program, AddressSetView addresses, TaskMonitor monitor) { + dataOpenCloseManager.openAllData(program, addresses, monitor); + notifyListeners(); + } + + public void closeAllData(Program program, AddressSetView addresses, TaskMonitor monitor) { + dataOpenCloseManager.closeAllData(program, addresses, monitor); + notifyListeners(); + } + + public void openDataRecursively(Data data, TaskMonitor monitor) { + dataOpenCloseManager.toggleDataRecursively(data, true, monitor); + notifyListeners(); + } + + public void closeDataRecursively(Data data, TaskMonitor monitor) { + dataOpenCloseManager.toggleDataRecursively(data, false, monitor); + notifyListeners(); + } + + /** + * Adds a change listener to be notified when a location is open or closed. + * @param l the listener to be notified. + */ + public void addChangeListener(ChangeListener l) { + listeners.add(l); + } + + /** + * Removes the listener. + * @param l the listener to remove. + */ + public void removeChangeListener(ChangeListener l) { + listeners.remove(l); + } + + private void notifyListeners() { + for (ChangeListener l : listeners) { + l.stateChanged(null); + } + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/VariablesOpenCloseLocation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/VariablesOpenCloseLocation.java new file mode 100644 index 0000000000..9649211491 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/VariablesOpenCloseLocation.java @@ -0,0 +1,38 @@ +/* ### + * 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.program.util; + +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; + +/** + * ProgramLocation that represents the cursor being on the variables open/close widget + */ +public class VariablesOpenCloseLocation extends CodeUnitLocation { + /** + * Constructor + * + * @param program the program of the location + * @param addr address of the location + */ + public VariablesOpenCloseLocation(Program program, Address addr) { + super(program, addr, null, 0, 0, 0); + } + + public VariablesOpenCloseLocation() { + + } +}