diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java index 16269cb74d..41a493d535 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java @@ -102,6 +102,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList private int searchLimit; private SearchTask currentTask; private String lastSearchedText; + private LastSearchHit lastSearchHit; private boolean doHighlight; private Navigatable navigatable; @@ -150,15 +151,18 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList if (result == null) { searchDialog.setStatusText("Not found"); } - else if (result.programLocation().equals(currentLocation)) { - searchNext(searchTask.getProgram(), searchNavigatable, textSearcher); - } else { searchDialog.setStatusText(""); ProgramLocation loc = result.programLocation(); if (goToService.goTo(searchNavigatable, loc, program)) { new SearchTextHighlightProvider(searchNavigatable, searchOptions, null, program, result); + + // The navigatable may change its location if it does not have the search result + // location visible. We store that here so we can later detect that case in order + // to keep the search from getting stuck. + ProgramLocation navigatableLoc = navigatable.getLocation(); + lastSearchHit = new LastSearchHit(loc, navigatableLoc); } } @@ -262,7 +266,20 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList } private ProgramLocation getStartLocation() { - return currentLocation = navigatable.getLocation(); + + ProgramLocation currentNavLoc = navigatable.getLocation(); + if (lastSearchHit != null) { + ProgramLocation lastNavLoc = lastSearchHit.navigatableLocation(); + + // The navigatable's location has not changed since the last search. Use the last search + // location as the start point for the next search. This ensures the searching does not + // get stuck when the navigatable cannot display the last search hit. + if (lastNavLoc.equals(currentNavLoc)) { + return lastSearchHit.searchLocation(); + } + } + + return currentNavLoc; } private void searchNext(Program program, Navigatable searchNavigatable, Searcher textSearcher) { @@ -534,7 +551,12 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList // Inner Classes //================================================================================================== - class TableLoadingListener implements ThreadedTableModelListener { + private record LastSearchHit(ProgramLocation searchLocation, + ProgramLocation navigatableLocation) { + // + } + + private class TableLoadingListener implements ThreadedTableModelListener { private ThreadedTableModel model; private TableComponentProvider provider; @@ -572,8 +594,9 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList "Stopped search after finding " + matchCount + " matches.\n" + "The search limit can be changed at Edit->Tool Options, under Search."); } + // there was a suggestion that the dialog should not go way after a search all -// searchDialog.close(); + // searchDialog.close(); } private Component getParentComponent() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentAddressSupplier.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentAddressSupplier.java new file mode 100644 index 0000000000..a4048f5bd5 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentAddressSupplier.java @@ -0,0 +1,100 @@ +/* ### + * 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.searchtext.databasesearcher; + +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; + +/** + * Shared supplier of comments at addresses as they are traversed in address order (or reverse + * address order). + *

+ * This object is used as a part of a larger search operation and is shared by the various comment + * searchers for efficiency since all comment types are actually stored in the same database + * record. + *

+ * At any given time, this supplier has a current address, which represents the next address at + * or beyond the overall search operation's address. When asked to advance, a current search + * address is passed, which is the last address that has been fully searched. So if our current + * address is at that address, we need to advance our current address to the next comment address. + * Otherwise, we simply return our current address which the higher level search can use + * to determine the next overall search address. + */ +public class CommentAddressSupplier { + + private Address currentAddress; + private CodeUnitComments currentComments; + private AddressIterator iterator; + private Listing listing; + + public CommentAddressSupplier(Program program, AddressSetView addresses, boolean forward) { + listing = program.getListing(); + iterator = listing.getCommentAddressIterator(addresses, forward); + doAdvance(); + } + + /** + * {@return the address of the currently available comments} + */ + public Address getCurrentAddress() { + return currentAddress; + } + + /** + * Returns the comment of the specified type at the current address or null if no comment of + * that type exists at the current address. + * @param type the type of comment to retrieve + * @return the comment of the specified type at the current address + */ + public String getCurrentComment(CommentType type) { + if (currentComments != null) { + return currentComments.getComment(type); + } + return null; + } + + /** + * Advance the current address to the next address that contains any type of comment if the + * passed in address is null or equal to the current address. The idea is that we are part + * of a larger search operation that is marching through the address space. We only want to + * advance our current address if the overall address of the search matches our address. + * (meaning we have already served up comments for the given address so we can advance to the + * next address that has comments, but may not be the next address of the overall search + * operation is considering.) + * @param address the address that has been already processed, so we need to make sure our + * current address is past this address. + * @return an address that is past the given address. Could be our current address our + * address hasn't been reached yet, or we search forward to the next address containing a + * comment. + */ + public Address advance(Address address) { + if (address != null && address.equals(currentAddress)) { + doAdvance(); + } + return currentAddress; + } + + private void doAdvance() { + if (iterator.hasNext()) { + currentAddress = iterator.next(); + currentComments = listing.getAllComments(currentAddress); + } + else { + currentAddress = null; + currentComments = null; + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentFieldSearcher.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentFieldSearcher.java index 5466860c63..01ac7976a6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentFieldSearcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/CommentFieldSearcher.java @@ -21,64 +21,62 @@ import java.util.regex.Pattern; import ghidra.app.plugin.core.searchtext.Searcher.TextSearchResult; import ghidra.app.util.viewer.field.CommentUtils; -import ghidra.program.model.address.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.*; import ghidra.program.util.*; import ghidra.util.StringUtilities; +/** + * Field searcher for comments. Since all comments types for an address are stored in the same + * database record, comment searchers share a common {@link CommentAddressSupplier} which the + * overall searcher will manually advance. The advance here will just assume the the comment + * supplier has been advanced and it will just return the suppliers current address. + */ public class CommentFieldSearcher extends ProgramDatabaseFieldSearcher { - private AddressIterator iterator; - private final CommentType commentType; + private CommentType commentType; + private CommentAddressSupplier supplier; private Program program; - public CommentFieldSearcher(Program program, ProgramLocation startLoc, AddressSetView set, - boolean forward, Pattern pattern, CommentType commentType) { + public CommentFieldSearcher(CommentAddressSupplier supplier, Program program, + ProgramLocation startLoc, + AddressSetView set, boolean forward, Pattern pattern, CommentType commentType) { super(pattern, forward, startLoc, set); - this.commentType = commentType; + this.supplier = supplier; this.program = program; - if (set != null) { - iterator = program.getListing().getCommentAddressIterator(commentType, set, forward); - } - else { - AddressSetView addressSet = program.getMemory(); - if (forward) { - addressSet.intersectRange(startLoc.getAddress(), addressSet.getMaxAddress()); - } - else { - addressSet.intersectRange(addressSet.getMinAddress(), startLoc.getAddress()); - } - iterator = - program.getListing().getCommentAddressIterator(commentType, addressSet, forward); - } + this.commentType = commentType; } @Override protected Address advance(List currentMatches) { - Address nextAddress = iterator.next(); - if (nextAddress != null) { - findMatchesForCurrentAddress(nextAddress, currentMatches); + Address address = supplier.getCurrentAddress(); + if (address == null) { + return null; // we are at the end of the iterator } - return nextAddress; + findMatchesForCurrentAddress(address, currentMatches); + return address; } private void findMatchesForCurrentAddress(Address address, List currentMatches) { - String comment = program.getListing().getComment(commentType, address); + String comment = supplier.getCurrentComment(commentType); if (comment == null) { return; } // Remove newlines; turn any annotations into the display version so the screen positions // of the program locations work correctly. - String cleanedUpComment = comment.replace('\n', ' '); + String cleanedUpComment = comment.replaceAll("\n", ""); String updatedLine = CommentUtils.getDisplayString(cleanedUpComment, program); Matcher matcher = pattern.matcher(updatedLine); - while (matcher.find()) { + int pos = 0; + while (matcher.find(pos)) { int index = matcher.start(); ProgramLocation commentLocation = getCommentLocation(comment, index, address); currentMatches.add(new TextSearchResult(commentLocation, index)); + pos = index + 1; } } @@ -97,7 +95,7 @@ public class CommentFieldSearcher extends ProgramDatabaseFieldSearcher { comments, rowIndex); case REPEATABLE: return new RepeatableCommentFieldLocation(program, address, dataPath, comments, - rowIndex, charOffset, rowIndex); // TODO One of searchStrIndex parameters is wrong. + rowIndex, charOffset, rowIndex); case POST: return new PostCommentFieldLocation(program, address, dataPath, comments, rowIndex, charOffset); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/ProgramDatabaseSearcher.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/ProgramDatabaseSearcher.java index 94d61bea72..ea1eb4ff71 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/ProgramDatabaseSearcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/databasesearcher/ProgramDatabaseSearcher.java @@ -64,6 +64,7 @@ public class ProgramDatabaseSearcher implements Searcher { private long totalSearchCount; private AddressSet remainingAddresses; private TaskMonitor monitor; + private CommentAddressSupplier commentSupplier; public ProgramDatabaseSearcher(ServiceProvider serviceProvider, Program program, ProgramLocation startLoc, AddressSetView set, SearchOptions options, @@ -131,7 +132,7 @@ public class ProgramDatabaseSearcher implements Searcher { } private Address findNextSignificantAddress() { - Address nextAddress = null; + Address nextAddress = commentSupplier.advance(currentAddress); for (ProgramDatabaseFieldSearcher searcher : searchers) { if (monitor.isCancelled()) { return null; @@ -179,17 +180,22 @@ public class ProgramDatabaseSearcher implements Searcher { UserSearchUtils.createSearchPattern(options.getText(), options.isCaseSensitive()); BrowserCodeUnitFormat format = new BrowserCodeUnitFormat(serviceProvider, false); + // this comment supplier will be used in all the comment searchers and is explicitly + // advanced in the findNextSignificantAddress() method + commentSupplier = new CommentAddressSupplier(program, trimmedSet, forward); + + // create the searchers in the order as displayed in the default listing panel field layout if (options.searchComments()) { - searchers.add(new CommentFieldSearcher(program, adjustedStart, trimmedSet, forward, - pattern, CommentType.PLATE)); + searchers.add(new CommentFieldSearcher(commentSupplier, program, adjustedStart, + trimmedSet, forward, pattern, CommentType.PLATE)); } if (options.searchFunctions()) { searchers.add( new FunctionFieldSearcher(program, adjustedStart, trimmedSet, forward, pattern)); } if (options.searchComments()) { - searchers.add(new CommentFieldSearcher(program, adjustedStart, trimmedSet, forward, - pattern, CommentType.PRE)); + searchers.add(new CommentFieldSearcher(commentSupplier, program, adjustedStart, + trimmedSet, forward, pattern, CommentType.PRE)); } if (options.searchLabels()) { searchers.add( @@ -224,12 +230,12 @@ public class ProgramDatabaseSearcher implements Searcher { program, adjustedStart, trimmedSet, forward, pattern, format)); } if (options.searchComments()) { - searchers.add(new CommentFieldSearcher(program, adjustedStart, trimmedSet, forward, - pattern, CommentType.EOL)); - searchers.add(new CommentFieldSearcher(program, adjustedStart, trimmedSet, forward, - pattern, CommentType.REPEATABLE)); - searchers.add(new CommentFieldSearcher(program, adjustedStart, trimmedSet, forward, - pattern, CommentType.POST)); + searchers.add(new CommentFieldSearcher(commentSupplier, program, adjustedStart, + trimmedSet, forward, pattern, CommentType.EOL)); + searchers.add(new CommentFieldSearcher(commentSupplier, program, adjustedStart, + trimmedSet, forward, pattern, CommentType.REPEATABLE)); + searchers.add(new CommentFieldSearcher(commentSupplier, program, adjustedStart, + trimmedSet, forward, pattern, CommentType.POST)); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin2Test.java index ec2c6179d1..b19d866d45 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin2Test.java @@ -306,26 +306,39 @@ public class SearchTextPlugin2Test extends AbstractGhidraHeadedIntegrationTest { public void testWilcardEntry2() throws Exception { Address addr = getAddr(0x1002d6d); - int transactionID = program.startTransaction("test"); - CodeUnit cu = program.getListing().getCodeUnitAt(addr); - try { - cu.setComment(CommentType.POST, "********** my entry Exit **********"); - } - finally { - program.endTransaction(transactionID, true); - } + tx(program, () -> { + CodeUnit cu = program.getListing().getCodeUnitAt(addr); + cu.setComment(CommentType.POST, "** my entry Exit **"); + }); JTextField tf = findComponent(container, JTextField.class); assertNotNull(tf); - setTextAndPressEnter(tf, "********** entry Exit **********"); + setTextAndPressEnter(tf, "** entry Exit **"); waitForSearchTasks(dialog); - waitForSwing(); - cbPlugin.updateNow(); ProgramLocation loc = cbPlugin.getCurrentLocation(); - assertEquals(addr, loc.getAddress()); + + assertEquals(addr, loc.getAddress()); // ** my entry Exit ** + + pressSearchButton(); + assertEquals(addr, loc.getAddress()); // * my entry Exit ** + + pressSearchButton(); + assertEquals(addr, loc.getAddress()); // my entry Exit ** + + pressSearchButton(); + assertEquals(addr, loc.getAddress()); // my entry Exit ** + + pressSearchButton(); + assertEquals(addr, loc.getAddress()); // y entry Exit ** + + pressSearchButton(); + assertEquals(addr, loc.getAddress()); // entry Exit ** + + pressSearchButton(); + assertEquals(addr, loc.getAddress()); // entry Exit ** pressSearchButton(); @@ -333,35 +346,6 @@ public class SearchTextPlugin2Test extends AbstractGhidraHeadedIntegrationTest { } - @Test - public void testWilcardEntry3() throws Exception { - - Address addr = getAddr(0x1002d6d); - int transactionID = program.startTransaction("test"); - CodeUnit cu = program.getListing().getCodeUnitAt(addr); - try { - cu.setComment(CommentType.POST, "********** ___sbh_find_block Exit **********"); - } - finally { - program.endTransaction(transactionID, true); - } - - JTextField tf = findComponent(container, JTextField.class); - assertNotNull(tf); - setTextAndPressEnter(tf, "********** Exit **********"); - - waitForSearchTasks(dialog); - - waitForSwing(); - cbPlugin.updateNow(); - ProgramLocation loc = cbPlugin.getCurrentLocation(); - assertEquals(addr, loc.getAddress()); - - pressSearchButton(); - - assertEquals("Not found", dialog.getStatusText()); - } - @Test public void testWilcardEntry4() throws Exception { @@ -650,6 +634,7 @@ public class SearchTextPlugin2Test extends AbstractGhidraHeadedIntegrationTest { t = dialog1.getTaskScheduler().getCurrentThread(); } waitForSwing(); + cbPlugin.updateNow(); } private void selectRadioButton(Container guiContainer, String buttonText) throws Exception { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/quicksearch/ProgramDatabaseSearchIteratorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/quicksearch/ProgramDatabaseSearchIteratorTest.java index b89293b52c..e53ae7b7ca 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/quicksearch/ProgramDatabaseSearchIteratorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/searchtext/quicksearch/ProgramDatabaseSearchIteratorTest.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.searchtext.quicksearch; +import static ghidra.program.model.listing.CommentType.*; import static org.junit.Assert.*; import java.util.ArrayList; @@ -54,6 +55,7 @@ public class ProgramDatabaseSearchIteratorTest extends AbstractGhidraHeadedInteg private Address currentAddress; private ToyProgramBuilder builder; private TaskMonitor monitor = TaskMonitor.DUMMY; + private CommentAddressSupplier commentSupplier; private void createIMM(long address) throws MemoryAccessException { builder.addBytesMoveImmediate(address, (short) 5); @@ -118,6 +120,13 @@ public class ProgramDatabaseSearchIteratorTest extends AbstractGhidraHeadedInteg if (searcher.hasMatch(currentAddress)) { return searcher.getMatch().programLocation(); } + + // for tests that search comments, we have to advance the comment supplier explicitly + // just like the searcher algorithm does + if (commentSupplier != null) { + commentSupplier.advance(currentAddress); + } + currentAddress = searcher.getNextSignificantAddress(currentAddress); } return null; @@ -127,23 +136,20 @@ public class ProgramDatabaseSearchIteratorTest extends AbstractGhidraHeadedInteg public void testEOLCommentIterator() { Pattern pattern = UserSearchUtils.createSearchPattern("XXZ*", false); - ProgramLocation startLocation = new ProgramLocation(program, program.getMinAddress()); - CommentFieldSearcher searcher = - new CommentFieldSearcher(program, startLocation, null, true, pattern, CommentType.EOL); + ProgramLocation start = new ProgramLocation(program, program.getMinAddress()); + CommentFieldSearcher searcher = createCommentSearcher(pattern, start, CommentType.EOL); currentAddress = searcher.getNextSignificantAddress(null); assertNull(getNextMatch(searcher)); // add a comment with no match addEolComment(0x1005146L, "Test EOL comments..."); - searcher = - new CommentFieldSearcher(program, startLocation, null, true, pattern, CommentType.EOL); + searcher = createCommentSearcher(pattern, start, CommentType.EOL); currentAddress = searcher.getNextSignificantAddress(null); assertNull(getNextMatch(searcher)); // add a comment that has one match addEolComment(0x1005d4bL, "Test something with eXXZabc"); - searcher = - new CommentFieldSearcher(program, startLocation, null, true, pattern, CommentType.EOL); + searcher = createCommentSearcher(pattern, start, CommentType.EOL); currentAddress = searcher.getNextSignificantAddress(null); ProgramLocation loc = getNextMatch(searcher); assertNotNull(loc); @@ -151,8 +157,7 @@ public class ProgramDatabaseSearchIteratorTest extends AbstractGhidraHeadedInteg // add a comment with two matches for a total of 3 matches addEolComment(0x100595f, "Hit found: eXXZabc followed by XXZabc"); - searcher = - new CommentFieldSearcher(program, startLocation, null, true, pattern, CommentType.EOL); + searcher = createCommentSearcher(pattern, start, CommentType.EOL); currentAddress = searcher.getNextSignificantAddress(null); loc = getNextMatch(searcher); @@ -168,20 +173,27 @@ public class ProgramDatabaseSearchIteratorTest extends AbstractGhidraHeadedInteg } + private CommentFieldSearcher createCommentSearcher(Pattern pattern, ProgramLocation start, + CommentType type) { + AddressSetView set = program.getMemory().getAllInitializedAddressSet(); + commentSupplier = new CommentAddressSupplier(program, set, true); + return new CommentFieldSearcher(commentSupplier, program, start, null, true, pattern, type); + } + @Test public void testSingleWildcard() { - addEolComment(0x100101cL, "Test EOL comments..."); - addEolComment(0x100101dL, "Test something with eXXZabc"); - addEolComment(0x100101fL, "Hit found: eXXZabc followed by XXZabc"); + addEolComment(0x100101cL, "Hi"); + addEolComment(0x100101dL, "Jo"); Pattern pattern = UserSearchUtils.createSearchPattern("*", false); ProgramLocation startLocation = new ProgramLocation(program, program.getMinAddress()); - CommentFieldSearcher searcher = - new CommentFieldSearcher(program, startLocation, null, true, pattern, CommentType.EOL); - currentAddress = searcher.getNextSignificantAddress(null); + + CommentFieldSearcher searcher = createCommentSearcher(pattern, startLocation, EOL); int count = 0; - Address[] addrs = - new Address[] { getAddr(0x100101cL), getAddr(0x100101dL), getAddr(0x100101fL) }; + // '*' will find a match of the full comment starting at the current char being searched + // (meaning we get a hit starting at every character) + Address[] addrs = new Address[] { getAddr(0x100101cL), getAddr(0x100101cL), + getAddr(0x100101dL), getAddr(0x100101dL) }; ProgramLocation loc = null; while ((loc = getNextMatch(searcher)) != null) { @@ -197,9 +209,8 @@ public class ProgramDatabaseSearchIteratorTest extends AbstractGhidraHeadedInteg addEolComment(0x100101fL, "Hit found: ABCxyzvvXXZ123 followed by ABCxqa123"); Pattern pattern = UserSearchUtils.createSearchPattern("ABC*123", false); - ProgramLocation startLocation = new ProgramLocation(program, program.getMinAddress()); - CommentFieldSearcher searcher = - new CommentFieldSearcher(program, startLocation, null, true, pattern, CommentType.EOL); + ProgramLocation start = new ProgramLocation(program, program.getMinAddress()); + CommentFieldSearcher searcher = createCommentSearcher(pattern, start, CommentType.EOL); currentAddress = searcher.getNextSignificantAddress(null); ProgramLocation loc = getNextMatch(searcher); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java index bd5673e49a..5af472d8c1 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java @@ -2940,6 +2940,9 @@ public class CodeManager implements ErrorHandler, ManagerDB { try { long addr = addrMap.getKey(address, false); DBRecord commentRec = getCommentAdapter().getRecord(addr); + if (commentRec == null) { + return null; + } CommentType[] types = CommentType.values(); String[] comments = new String[types.length];