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 40325982f7..4ac0decc9a 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 @@ -495,12 +495,16 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener } protected void notifyDataChanged(boolean updateImmediately) { + layoutCache.clear(); + for (ListingModelListener listener : listeners) { listener.dataChanged(updateImmediately); } } private void notifyModelSizeChanged() { + layoutCache.clear(); + for (ListingModelListener listener : listeners) { listener.modelSizeChanged(); } @@ -547,8 +551,6 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener return; } - layoutCache.clear(); - boolean updateImmediately = ev.numRecords() <= 5; notifyDataChanged(updateImmediately); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java index 790bbacdbe..e7bd073dd3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java @@ -70,7 +70,7 @@ public abstract class AbstractProgramBasedTest extends AbstractGhidraHeadedInteg * Override this method if you need to build your own program. * * @return the program to use for this test. - * @throws Exception if an exceptioun is thrown opening the program + * @throws Exception if an exception is thrown opening the program */ protected Program getProgram() throws Exception { return env.getProgram(getProgramName()); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java index d1f72eab4a..0cfedee1df 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java @@ -63,7 +63,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field private final DecompilerController controller; private final DecompileOptions options; - private FieldPanel codeViewer; + private DecompilerFieldPanel fieldPanel; private ClangLayoutController layoutMgr; private HighlightFactory hlFactory; private ClangHighlightController highlightController; @@ -94,13 +94,13 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field hlFactory = new SearchHighlightFactory(); layoutMgr = new ClangLayoutController(options, this, metrics, hlFactory); - codeViewer = new FieldPanel(layoutMgr); + fieldPanel = new DecompilerFieldPanel(layoutMgr); setBackground(options.getCodeViewerBackgroundColor()); - IndexedScrollPane scroller = new IndexedScrollPane(codeViewer); - codeViewer.addFieldSelectionListener(this); - codeViewer.addFieldMouseListener(this); - codeViewer.addFieldLocationListener(this); + IndexedScrollPane scroller = new IndexedScrollPane(fieldPanel); + fieldPanel.addFieldSelectionListener(this); + fieldPanel.addFieldMouseListener(this); + fieldPanel.addFieldLocationListener(this); decompilerHoverProvider = new DecompilerHoverProvider(); @@ -125,7 +125,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } public FieldPanel getFieldPanel() { - return codeViewer; + return fieldPanel; } @Override @@ -134,8 +134,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field if (useNonFunctionColor) { bg = NON_FUNCTION_BACKGROUND_COLOR_DEF; } - if (codeViewer != null) { - codeViewer.setBackgroundColor(bg); + if (fieldPanel != null) { + fieldPanel.setBackgroundColor(bg); } super.setBackground(bg); } @@ -208,7 +208,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return; } if (viewerPosition != null) { - codeViewer.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(), + fieldPanel.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(), viewerPosition.getYOffset()); } List tokens = @@ -216,7 +216,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field if (location instanceof DecompilerLocation) { DecompilerLocation decompilerLocation = (DecompilerLocation) location; - codeViewer.goTo(BigInteger.valueOf(decompilerLocation.getLineNumber()), 0, 0, + fieldPanel.goTo(BigInteger.valueOf(decompilerLocation.getLineNumber()), 0, 0, decompilerLocation.getCharPos(), false); } else if (!tokens.isEmpty()) { @@ -231,7 +231,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field private void goToBeginningOfLine(List tokens) { int firstLineNumber = DecompilerUtils.findIndexOfFirstField(tokens, layoutMgr.getFields()); if (firstLineNumber != -1) { - codeViewer.goTo(BigInteger.valueOf(firstLineNumber), 0, 0, 0, false); + fieldPanel.goTo(BigInteger.valueOf(firstLineNumber), 0, 0, 0, false); } } @@ -255,7 +255,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field int distance = getOffscreenDistance(lineNumber); if (distance == 0) { - codeViewer.goTo(BigInteger.valueOf(lineNumber), 0, 0, column, false); + fieldPanel.navigateTo(lineNumber, column); return; } @@ -265,14 +265,14 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field private int getOffscreenDistance(int line) { - AnchoredLayout start = codeViewer.getVisibleStartLayout(); + AnchoredLayout start = fieldPanel.getVisibleStartLayout(); int visibleStartLine = start.getIndex().intValue(); if (visibleStartLine > line) { // the end is off the top of the screen return visibleStartLine - line; } - AnchoredLayout end = codeViewer.getVisibleEndLayout(); + AnchoredLayout end = fieldPanel.getVisibleEndLayout(); int visibleEndLine = end.getIndex().intValue(); if (visibleEndLine < line) { // the end is off the bottom of the screen @@ -343,7 +343,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field DecompilerUtils.getTokens(layoutMgr.getRoot(), translateSet(selection)); fieldSelection = DecompilerUtils.getFieldSelection(tokens); } - codeViewer.setSelection(fieldSelection); + fieldPanel.setSelection(fieldSelection); } public void setDecompilerHoverProvider(DecompilerHoverProvider provider) { @@ -584,8 +584,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field if (!decompileData.hasDecompileResults()) { return null; } - Field currentField = codeViewer.getCurrentField(); - FieldLocation cursorPosition = codeViewer.getCursorLocation(); + Field currentField = fieldPanel.getCurrentField(); + FieldLocation cursorPosition = fieldPanel.getCursorLocation(); return getProgramLocation(currentField, cursorPosition); } @@ -608,7 +608,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field return; } - if (trigger != EventTrigger.INTERNAL_ONLY) { + // only broadcast when the user is clicking around + if (trigger == EventTrigger.GUI_ACTION) { ProgramLocation programLocation = getProgramLocation(field, location); if (programLocation != null) { controller.locationChanged(programLocation); @@ -699,13 +700,13 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } public FieldLocation getCursorPosition() { - return codeViewer.getCursorLocation(); + return fieldPanel.getCursorLocation(); } public void setCursorPosition(FieldLocation fieldLocation) { - codeViewer.setCursorPosition(fieldLocation.getIndex(), fieldLocation.getFieldNum(), + fieldPanel.setCursorPosition(fieldLocation.getIndex(), fieldLocation.getFieldNum(), fieldLocation.getRow(), fieldLocation.getCol()); - codeViewer.scrollToCursor(); + fieldPanel.scrollToCursor(); } /** @@ -713,7 +714,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field * @return a single selected token; null if there is no selection or multiple tokens selected. */ public ClangToken getSelectedToken() { - FieldSelection selection = codeViewer.getSelection(); + FieldSelection selection = fieldPanel.getSelection(); if (selection.isEmpty()) { return null; } @@ -729,8 +730,8 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } public ClangToken getTokenAtCursor() { - FieldLocation cursorPosition = codeViewer.getCursorLocation(); - Field field = codeViewer.getCurrentField(); + FieldLocation cursorPosition = fieldPanel.getCursorLocation(); + Field field = fieldPanel.getCurrentField(); if (field == null) { return null; } @@ -748,10 +749,10 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field public void setHoverMode(boolean enabled) { decompilerHoverProvider.setHoverEnabled(enabled); if (enabled) { - codeViewer.setHoverProvider(decompilerHoverProvider); + fieldPanel.setHoverProvider(decompilerHoverProvider); } else { - codeViewer.setHoverProvider(null); + fieldPanel.setHoverProvider(null); } } @@ -797,24 +798,24 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } public ViewerPosition getViewerPosition() { - return codeViewer.getViewerPosition(); + return fieldPanel.getViewerPosition(); } public void setViewerPosition(ViewerPosition viewerPosition) { - codeViewer.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(), + fieldPanel.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(), viewerPosition.getYOffset()); } @Override public void requestFocus() { - codeViewer.requestFocus(); + fieldPanel.requestFocus(); } public void selectAll() { BigInteger numIndexes = layoutMgr.getNumIndexes(); FieldSelection selection = new FieldSelection(); selection.addRange(BigInteger.ZERO, numIndexes); - codeViewer.setSelection(selection); + fieldPanel.setSelection(selection); // fake it out that the selection was caused by the field panel GUI. selectionChanged(selection, EventTrigger.GUI_ACTION); } @@ -909,13 +910,33 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field } FieldLocation location = new FieldLocation(BigInteger.valueOf(current)); - codeViewer.scrollTo(location); + fieldPanel.scrollTo(location); } @Override public void done() { - codeViewer.goTo(BigInteger.valueOf(endLine), 0, 0, endColumn, false); + fieldPanel.goTo(BigInteger.valueOf(endLine), 0, 0, endColumn, false); + } + } + + private class DecompilerFieldPanel extends FieldPanel { + + public DecompilerFieldPanel(LayoutModel model) { + super(model); } + /** + * Moves this field panel to the given line and column. Further, this navigation will + * fire an event to the rest of the tool. (This is in contrast to a field panel + * goTo, which we use to simply move the cursor, but not trigger an + * tool-level navigation event.) + * + * @param lineNumber the line number + * @param column the column within the line + */ + void navigateTo(int lineNumber, int column) { + fieldPanel.goTo(BigInteger.valueOf(lineNumber), 0, 0, column, false, + EventTrigger.GUI_ACTION); + } } } diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerNavigationTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerNavigationTest.java new file mode 100644 index 0000000000..f8647cc293 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerNavigationTest.java @@ -0,0 +1,83 @@ +/* ### + * 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.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.util.OperandFieldLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.test.ClassicSampleX86ProgramBuilder; + +public class DecompilerNavigationTest extends AbstractDecompilerTest { + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + CodeViewerProvider cbProvider = codeBrowser.getProvider(); + tool.showComponentProvider(cbProvider, true); + } + + @Override + protected Program getProgram() throws Exception { + return buildProgram(); + } + + private Program buildProgram() throws Exception { + ClassicSampleX86ProgramBuilder builder = + new ClassicSampleX86ProgramBuilder("notepad", false, this); + + // need a default label at 01002cf0, so make up a reference + builder.createMemoryReference("01002ce5", "01002cf0", RefType.FALL_THROUGH, + SourceType.ANALYSIS); + + return builder.getProgram(); + } + + @Test + public void testNavigation_ExternalEventDoesNotTriggerNavigation() { + + // + // Test to make sure that external ProgramLocationEvent notifications to not trigger + // the Decompiler to broadcast a new event. Setup a tool with the Listing and + // the Decompiler open. Then, navigate in the Listing and verify the address does not + // move. (This is somewhat subject to the Code Unit at the address in how the + // Decompiler itself responds to the incoming event.) + // + + // very specific location within the instruction that is known to affect how the + // decompiler responds + String operandPrefix = "dword ptr [EBP + "; + String operandReferenceName = "destStr]"; + OperandFieldLocation operandLocation = new OperandFieldLocation(program, addr("0100416c"), + null, addr("0x8"), operandPrefix + operandReferenceName, 1, 9); + codeBrowser.goTo(operandLocation); + waitForSwing(); + + ProgramLocation currentLocation = codeBrowser.getCurrentLocation(); + assertTrue(currentLocation instanceof OperandFieldLocation); + assertEquals(operandLocation.getAddress(), currentLocation.getAddress()); + } +} diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerTest.java index 7121e1bbb9..1044a1a4e8 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerTest.java @@ -23,16 +23,12 @@ import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.ToyProgramBuilder; -import ghidra.util.task.TaskMonitorAdapter; +import ghidra.util.task.TaskMonitor; public class DecompilerTest extends AbstractGhidraHeadedIntegrationTest { private Program prog; private DecompInterface decompiler; - public DecompilerTest() { - super(); - } - @Before public void setUp() throws Exception { @@ -57,9 +53,8 @@ public class DecompilerTest extends AbstractGhidraHeadedIntegrationTest { public void testDecompileInterfaceReturnsAFunction() throws Exception { Address addr = prog.getAddressFactory().getDefaultAddressSpace().getAddress(0x0); Function func = prog.getListing().getFunctionAt(addr); - DecompileResults decompResults = - decompiler.decompileFunction(func, DecompileOptions.SUGGESTED_DECOMPILE_TIMEOUT_SECS, - TaskMonitorAdapter.DUMMY_MONITOR); + DecompileResults decompResults = decompiler.decompileFunction(func, + DecompileOptions.SUGGESTED_DECOMPILE_TIMEOUT_SECS, TaskMonitor.DUMMY); String decompilation = decompResults.getDecompiledFunction().getC(); Assert.assertNotNull(decompilation); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java index 59d0c3baa8..f1118a4016 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java @@ -764,8 +764,14 @@ public class FieldPanel extends JPanel * cursor if cursor is offscreen. */ public void goTo(BigInteger index, int fieldNum, int row, int col, boolean alwaysCenterCursor) { + goTo(index, fieldNum, row, col, alwaysCenterCursor, EventTrigger.API_CALL); + } - if (!cursorHandler.doSetCursorPosition(index, fieldNum, row, col, EventTrigger.API_CALL)) { + // for subclasses to control the event trigger + protected void goTo(BigInteger index, int fieldNum, int row, int col, + boolean alwaysCenterCursor, EventTrigger trigger) { + + if (!cursorHandler.doSetCursorPosition(index, fieldNum, row, col, trigger)) { return; }