diff --git a/Ghidra/Features/Base/ghidra_scripts/AddSourceFileScript.java b/Ghidra/Features/Base/ghidra_scripts/AddSourceFileScript.java
new file mode 100644
index 0000000000..cfdcd2d687
--- /dev/null
+++ b/Ghidra/Features/Base/ghidra_scripts/AddSourceFileScript.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.
+ */
+// Adds a SourceFile with a user-defined path and name to the program.
+//@category SourceMapping
+import java.util.HexFormat;
+
+import org.apache.commons.lang3.StringUtils;
+
+import ghidra.app.script.GhidraScript;
+import ghidra.features.base.values.GhidraValuesMap;
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.database.sourcemap.SourceFileIdType;
+import ghidra.util.MessageType;
+
+public class AddSourceFileScript extends GhidraScript {
+
+ private static final String PATH = "Source File Path";
+ private static final String ID_TYPE = "Id Type";
+ private static final String IDENTIFIER = "Identifier";
+
+ @Override
+ protected void run() throws Exception {
+ if (isRunningHeadless()) {
+ println("This script must be run through the Ghidra GUI");
+ return;
+ }
+ if (currentProgram == null) {
+ popup("This script requires an open program");
+ return;
+ }
+ if (!currentProgram.hasExclusiveAccess()) {
+ popup("This script requires exclusive access to the program");
+ return;
+ }
+
+ GhidraValuesMap values = new GhidraValuesMap();
+ values.defineString(PATH, "/");
+ SourceFileIdType[] idTypes = SourceFileIdType.values();
+ String[] enumNames = new String[idTypes.length];
+ for (int i = 0; i < enumNames.length; ++i) {
+ enumNames[i] = idTypes[i].name();
+ }
+ values.defineChoice(ID_TYPE, SourceFileIdType.NONE.name(), enumNames);
+ values.defineString(IDENTIFIER, StringUtils.EMPTY);
+
+ values.setValidator((valueMap, status) -> {
+ String path = valueMap.getString(PATH);
+ SourceFileIdType idType = SourceFileIdType.valueOf(values.getChoice(ID_TYPE));
+ byte[] identifier = null;
+ if (idType != SourceFileIdType.NONE) {
+ identifier = HexFormat.of().parseHex(values.getString(IDENTIFIER));
+ }
+ try {
+ SourceFile srcFile = new SourceFile(path, idType, identifier);
+ if (currentProgram.getSourceFileManager().containsSourceFile(srcFile)) {
+ status.setStatusText("SourceFile " + srcFile + " already exists",
+ MessageType.ERROR);
+ return false;
+ }
+ }
+ catch (IllegalArgumentException e) {
+ status.setStatusText(e.getMessage(), MessageType.ERROR);
+ return false;
+ }
+ return true;
+ });
+ askValues("Enter (Absolute) Source File URI Path",
+ "e.g.: /usr/bin/echo, /C:/Programs/file.exe", values);
+ String absolutePath = values.getString(PATH);
+ SourceFileIdType idType = SourceFileIdType.valueOf(values.getChoice(ID_TYPE));
+ byte[] identifier = null;
+ if (idType != SourceFileIdType.NONE) {
+ identifier = HexFormat.of().parseHex(values.getString(IDENTIFIER));
+ }
+ SourceFile srcFile = new SourceFile(absolutePath, idType, identifier);
+ currentProgram.getSourceFileManager().addSourceFile(srcFile);
+ printf("Successfully added source file %s%n", srcFile.toString());
+ }
+
+}
diff --git a/Ghidra/Features/Base/ghidra_scripts/AddSourceMapEntryScript.java b/Ghidra/Features/Base/ghidra_scripts/AddSourceMapEntryScript.java
new file mode 100644
index 0000000000..de72e8707e
--- /dev/null
+++ b/Ghidra/Features/Base/ghidra_scripts/AddSourceMapEntryScript.java
@@ -0,0 +1,146 @@
+/* ###
+ * 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.
+ */
+// Add a source map entry for the current selection.
+// The current selection must consist of a single address range.
+// If there is no selection, a length 0 entry will be added at the current address.
+//@category SourceMapping
+import java.util.*;
+
+import ghidra.app.script.GhidraScript;
+import ghidra.features.base.values.GhidraValuesMap;
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressRange;
+import ghidra.program.model.sourcemap.*;
+import ghidra.util.MessageType;
+
+public class AddSourceMapEntryScript extends GhidraScript {
+
+ private static final String LINE_NUM = "Line Number";
+ private static final String SOURCE_FILE = "Source File";
+
+ @Override
+ protected void run() throws Exception {
+ if (isRunningHeadless()) {
+ println("This script must be run through the Ghidra GUI");
+ return;
+ }
+ if (currentProgram == null) {
+ popup("This script requires an open program");
+ return;
+ }
+ if (!currentProgram.hasExclusiveAccess()) {
+ popup("This script requires exclusive access to the program");
+ return;
+ }
+
+ SourceFileManager sourceManager = currentProgram.getSourceFileManager();
+ List sourceFiles = sourceManager.getAllSourceFiles();
+ if (sourceFiles.isEmpty()) {
+ popup("You must first add at least one source file to the program");
+ return;
+ }
+
+ boolean valid = isCurrentSelectionValid();
+ if (!valid) {
+ return; // checkCurrentSelection will tell the user what the problem is
+ }
+
+ Address baseAddr =
+ currentSelection == null ? currentAddress : currentSelection.getMinAddress();
+ long length = currentSelection == null ? 0 : currentSelection.getNumAddresses();
+
+ AddressRange currentRange =
+ currentSelection == null ? null : currentSelection.getAddressRanges().next();
+
+ Map stringsToSourceFiles = new HashMap<>();
+ sourceFiles.forEach(sf -> stringsToSourceFiles.put(sf.toString(), sf));
+
+ GhidraValuesMap values = new GhidraValuesMap();
+ values.defineInt(LINE_NUM, 1);
+ String[] sourceFileArray = stringsToSourceFiles.keySet().toArray(new String[0]);
+ Arrays.sort(sourceFileArray);
+ values.defineChoice(SOURCE_FILE, sourceFileArray[0], sourceFileArray);
+
+ values.setValidator((valueMap, status) -> {
+ int lineNum = values.getInt(LINE_NUM);
+ if (lineNum < 0) {
+ status.setStatusText("Line number cannot be negative", MessageType.ERROR);
+ return false;
+ }
+ if (currentRange == null) {
+ return true; // length 0 entry, nothing else to check
+ }
+ SourceFile source = new SourceFile(values.getChoice(SOURCE_FILE));
+
+ for (SourceMapEntry entry : sourceManager.getSourceMapEntries(source, lineNum)) {
+ // because checkCurrentSelection returned true, if the entries intersect
+ // they must be equal
+ if (entry.getLength() != 0 && entry.getRange().intersects(currentRange)) {
+ status.setStatusText(currentSelection + " is already mapped to " +
+ source.getPath() + ":" + lineNum);
+ return false;
+ }
+ }
+ return true;
+ });
+
+ askValues("Source Map Info", "Enter Source Map Info (length = " + length + ")", values);
+ int lineNum = values.getInt(LINE_NUM);
+ String fileString = values.getChoice(SOURCE_FILE);
+ SourceFile source = stringsToSourceFiles.get(fileString);
+ sourceManager.addSourceMapEntry(source, lineNum, baseAddr, length);
+ }
+
+ // check that the selected range doesn't already intersect a SourceMapEntry with a
+ // conflicting range.
+ private boolean isCurrentSelectionValid() {
+ if (currentSelection == null) {
+ return true;
+ }
+ if (currentSelection.getNumAddressRanges() != 1) {
+ popup("This script requires the current selection to be a single address range");
+ return false;
+ }
+ AddressRange range = currentSelection.getFirstRange();
+ Address end = range.getMaxAddress();
+ SourceMapEntryIterator iter =
+ currentProgram.getSourceFileManager().getSourceMapEntryIterator(end, false);
+ while (iter.hasNext()) {
+ SourceMapEntry entry = iter.next();
+ if (!entry.getBaseAddress().getAddressSpace().equals(range.getAddressSpace())) {
+ return true; // iterator has exhausted entries in the address space, no conflicts
+ // are possible
+ }
+ if (entry.getLength() == 0) {
+ continue; // length 0 entries can't conflict
+ }
+ AddressRange entryRange = entry.getRange();
+ if (entryRange.equals(range)) {
+ return true; // range is the same as a range already in the db, so no problems
+ }
+ if (entryRange.intersects(range)) {
+ popup("Selection conflicts with existing entry " + entry.toString());
+ return false;
+ }
+ if (entryRange.getMaxAddress().compareTo(range.getMinAddress()) < 0) {
+ return true; // no conflicting entries exists
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoScript.java b/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoCommentScript.java
similarity index 93%
rename from Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoScript.java
rename to Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoCommentScript.java
index aeee1140d7..540c10f730 100644
--- a/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoScript.java
+++ b/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoCommentScript.java
@@ -4,16 +4,18 @@
* 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.
*/
-// Adds DWARF source file line number info to the current binary
+// Adds DWARF source file line number info to the current binary as EOL comments.
+// Note that you can run this script on a program that has already been analyzed by the
+// DWARF analyzer.
//@category DWARF
import java.io.IOException;
import java.util.List;
@@ -29,7 +31,7 @@ import ghidra.program.model.listing.CodeUnit;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
-public class DWARFLineInfoScript extends GhidraScript {
+public class DWARFLineInfoCommentScript extends GhidraScript {
@Override
protected void run() throws Exception {
DWARFSectionProvider dsp =
diff --git a/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoSourceMapScript.java b/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoSourceMapScript.java
new file mode 100644
index 0000000000..92d3cd901d
--- /dev/null
+++ b/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoSourceMapScript.java
@@ -0,0 +1,154 @@
+/* ###
+ * 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.
+ */
+// Adds DWARF source file line number info to the current program as source map entries.
+// Note that you can run this script on a program that has already been analyzed by the
+// DWARF analyzer.
+//@category DWARF
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import ghidra.app.script.GhidraScript;
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.format.dwarf.*;
+import ghidra.app.util.bin.format.dwarf.line.DWARFLine.SourceFileAddr;
+import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProvider;
+import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProviderFactory;
+import ghidra.framework.store.LockException;
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.database.sourcemap.SourceFileIdType;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressOverflowException;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.util.Msg;
+import ghidra.util.exception.CancelledException;
+
+public class DWARFLineInfoSourceMapScript extends GhidraScript {
+ public static final int ENTRY_MAX_LENGTH = 1000;
+
+ @Override
+ protected void run() throws Exception {
+
+ if (!currentProgram.hasExclusiveAccess()) {
+ Msg.showError(this, null, "Exclusive Access Required",
+ "Must have exclusive access to a program to add source map info");
+ return;
+ }
+ DWARFSectionProvider dsp =
+ DWARFSectionProviderFactory.createSectionProviderFor(currentProgram, monitor);
+ if (dsp == null) {
+ printerr("Unable to find DWARF information");
+ return;
+ }
+
+ DWARFImportOptions importOptions = new DWARFImportOptions();
+ try (DWARFProgram dprog = new DWARFProgram(currentProgram, importOptions, monitor, dsp)) {
+ dprog.init(monitor);
+ addSourceLineInfo(dprog);
+ }
+ }
+
+ private void addSourceLineInfo(DWARFProgram dprog)
+ throws CancelledException, IOException, LockException, AddressOverflowException {
+ BinaryReader reader = dprog.getDebugLineBR();
+ if (reader == null) {
+ popup("Unable to get reader for debug line info");
+ return;
+ }
+ int entryCount = 0;
+ monitor.initialize(reader.length(), "DWARF Source Map Info");
+ List compUnits = dprog.getCompilationUnits();
+ SourceFileManager sourceManager = currentProgram.getSourceFileManager();
+ List sourceInfo = new ArrayList<>();
+ for (DWARFCompilationUnit cu : compUnits) {
+ sourceInfo.addAll(cu.getLine().getAllSourceFileAddrInfo(cu, reader));
+ }
+ sourceInfo.sort((i, j) -> Long.compareUnsigned(i.address(), j.address()));
+ monitor.initialize(sourceInfo.size());
+ for (int i = 0; i < sourceInfo.size(); i++) {
+ monitor.checkCancelled();
+ monitor.increment(1);
+ SourceFileAddr sourceFileAddr = sourceInfo.get(i);
+ if (sourceFileAddr.isEndSequence()) {
+ continue;
+ }
+ Address addr = dprog.getCodeAddress(sourceFileAddr.address());
+ if (!currentProgram.getMemory().getExecuteSet().contains(addr)) {
+ printerr(
+ "entry for non-executable address; skipping: file %s line %d address: %s %x"
+ .formatted(sourceFileAddr.fileName(), sourceFileAddr.lineNum(),
+ addr.toString(), sourceFileAddr.address()));
+ continue;
+ }
+
+ long length = getLength(i, sourceInfo);
+ if (length < 0) {
+ println(
+ "Error computing entry length for file %s line %d address %s %x; replacing" +
+ " with length 0 entry".formatted(sourceFileAddr.fileName(),
+ sourceFileAddr.lineNum(), addr.toString(), sourceFileAddr.address()));
+ length = 0;
+ }
+ if (length > ENTRY_MAX_LENGTH) {
+ println(
+ ("entry for file %s line %d address: %s %x length %d too large, replacing " +
+ "with length 0 entry").formatted(sourceFileAddr.fileName(),
+ sourceFileAddr.lineNum(), addr.toString(), sourceFileAddr.address(),
+ length));
+ length = 0;
+ }
+ if (sourceFileAddr.fileName() == null) {
+ continue;
+ }
+ SourceFile source = null;
+ try {
+ SourceFileIdType type =
+ sourceFileAddr.md5() == null ? SourceFileIdType.NONE : SourceFileIdType.MD5;
+ source = new SourceFile(sourceFileAddr.fileName(), type, sourceFileAddr.md5());
+ sourceManager.addSourceFile(source);
+ }
+ catch (IllegalArgumentException e) {
+ printerr("Exception creating source file %s".formatted(e.getMessage()));
+ continue;
+ }
+ try {
+ sourceManager.addSourceMapEntry(source, sourceFileAddr.lineNum(), addr, length);
+ }
+ catch (IllegalArgumentException e) {
+ printerr(e.getMessage());
+ continue;
+ }
+ entryCount++;
+ }
+ println("Added " + entryCount + " source map entries");
+ }
+
+ private long getLength(int i, List allSFA) {
+ SourceFileAddr iAddr = allSFA.get(i);
+ long iOffset = iAddr.address();
+ for (int j = i + 1; j < allSFA.size(); j++) {
+ SourceFileAddr current = allSFA.get(j);
+ long currentAddr = current.address();
+ if (current.isEndSequence()) {
+ return currentAddr + 1 - iOffset;
+ }
+ if (currentAddr != iOffset) {
+ return currentAddr - iOffset;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/Ghidra/Features/Base/ghidra_scripts/RemoveSourceMapEntryScript.java b/Ghidra/Features/Base/ghidra_scripts/RemoveSourceMapEntryScript.java
new file mode 100644
index 0000000000..00a2c15935
--- /dev/null
+++ b/Ghidra/Features/Base/ghidra_scripts/RemoveSourceMapEntryScript.java
@@ -0,0 +1,63 @@
+/* ###
+ * 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.
+ */
+// Select and remove a source map entry at the current address.
+//@category SourceMapping
+import java.util.*;
+
+import ghidra.app.script.GhidraScript;
+import ghidra.features.base.values.GhidraValuesMap;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+
+public class RemoveSourceMapEntryScript extends GhidraScript {
+
+ @Override
+ protected void run() throws Exception {
+ if (isRunningHeadless()) {
+ println("This script must be run through the Ghidra GUI");
+ return;
+ }
+ if (currentProgram == null) {
+ popup("This script requires an open program");
+ return;
+ }
+ if (!currentProgram.hasExclusiveAccess()) {
+ popup("Modifying the source map requires exclusive access to the program");
+ return;
+ }
+ List entries =
+ currentProgram.getSourceFileManager().getSourceMapEntries(currentAddress);
+ if (entries.isEmpty()) {
+ popup("No source map entries at " + currentAddress);
+ return;
+ }
+
+ Map stringToEntry = new HashMap<>();
+ List choices = new ArrayList<>();
+ for (SourceMapEntry entry : entries) {
+ String entryString = entry.toString();
+ stringToEntry.put(entryString, entry);
+ choices.add(entryString);
+ }
+ GhidraValuesMap values = new GhidraValuesMap();
+ String[] choiceArray = choices.toArray(new String[] {});
+ values.defineChoice("Entry", choiceArray[0], choiceArray);
+ askValues("Select SourceMapEntry to remove", null, values);
+ String selectedString = values.getChoice("Entry");
+ SourceMapEntry toDelete = stringToEntry.get(selectedString);
+ currentProgram.getSourceFileManager().removeSourceMapEntry(toDelete);
+ }
+
+}
diff --git a/Ghidra/Features/Base/ghidra_scripts/SelectAddressesMappedToSourceFileScript.java b/Ghidra/Features/Base/ghidra_scripts/SelectAddressesMappedToSourceFileScript.java
new file mode 100644
index 0000000000..c555f557b8
--- /dev/null
+++ b/Ghidra/Features/Base/ghidra_scripts/SelectAddressesMappedToSourceFileScript.java
@@ -0,0 +1,113 @@
+/* ###
+ * 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.
+ */
+// Sets the current selection based on source file and line number parameters
+//@category SourceMapping
+import java.util.*;
+
+import ghidra.app.script.GhidraScript;
+import ghidra.features.base.values.GhidraValuesMap;
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.model.address.AddressSet;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+import ghidra.util.MessageType;
+import ghidra.util.SourceFileUtils;
+import ghidra.util.SourceFileUtils.SourceLineBounds;
+
+public class SelectAddressesMappedToSourceFileScript extends GhidraScript {
+
+ private static final String SOURCE_FILE = "Source File";
+ private static final String MIN_LINE = "Minimum Source Line";
+ private static final String MAX_LINE = " Maximum Source Line";
+
+ @Override
+ protected void run() throws Exception {
+ if (isRunningHeadless()) {
+ println("This script must be run through the Ghidra GUI");
+ return;
+ }
+ if (currentProgram == null) {
+ popup("This script requires an open program");
+ return;
+ }
+
+ SourceFileManager sourceManager = currentProgram.getSourceFileManager();
+
+ List sourceFiles = sourceManager.getMappedSourceFiles();
+ if (sourceFiles.isEmpty()) {
+ popup(currentProgram.getName() + " contains no mapped source files");
+ return;
+ }
+
+ GhidraValuesMap sourceFileValue = new GhidraValuesMap();
+
+ Map stringsToSourceFiles = new HashMap<>();
+ sourceFiles.forEach(sf -> stringsToSourceFiles.put(sf.toString(), sf));
+ String[] sourceFileArray = stringsToSourceFiles.keySet().toArray(new String[0]);
+ Arrays.sort(sourceFileArray);
+
+ sourceFileValue.defineChoice(SOURCE_FILE, sourceFileArray[0], sourceFileArray);
+ askValues("Select Source File", "Source File", sourceFileValue);
+ String sfString = sourceFileValue.getChoice(SOURCE_FILE);
+
+ SourceFile mappedSourceFile = stringsToSourceFiles.get(sfString);
+ SourceLineBounds bounds =
+ SourceFileUtils.getSourceLineBounds(currentProgram, mappedSourceFile);
+ GhidraValuesMap boundValues = new GhidraValuesMap();
+ boundValues.defineInt(MIN_LINE, bounds.min());
+ boundValues.setInt(MIN_LINE, bounds.min());
+ boundValues.defineInt(MAX_LINE, bounds.max());
+ boundValues.setInt(MAX_LINE, bounds.max());
+
+ boundValues.setValidator((valueMap, status) -> {
+ int minLine = boundValues.getInt(MIN_LINE);
+ if (minLine < 0) {
+ status.setStatusText("Line number cannot be negative", MessageType.ERROR);
+ return false;
+ }
+ int maxLine = boundValues.getInt(MAX_LINE);
+ if (maxLine < minLine) {
+ status.setStatusText("Max line cannot be less than min line", MessageType.ERROR);
+ return false;
+ }
+ return true;
+ });
+
+ setReusePreviousChoices(false);
+ askValues("Select Line Bounds", "Bounds for " + mappedSourceFile.getFilename(),
+ boundValues);
+ setReusePreviousChoices(true);
+
+ int minLine = boundValues.getInt(MIN_LINE);
+ int maxLine = boundValues.getInt(MAX_LINE);
+ AddressSet selection = new AddressSet();
+ List entries =
+ sourceManager.getSourceMapEntries(mappedSourceFile, minLine, maxLine);
+ for (SourceMapEntry entry : entries) {
+ if (entry.getLength() == 0) {
+ selection.add(entry.getBaseAddress());
+ continue;
+ }
+ selection.add(entry.getRange());
+ }
+ if (selection.isEmpty()) {
+ popup("No addresses for the selected file and range");
+ return;
+ }
+ setCurrentSelection(selection);
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm
index f37777b799..d620ba7539 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm
@@ -993,7 +993,7 @@
Option to display the raw PCode directly in the Code Browser (i.e., detailed varnode
specifications are provided).
- Maximum Lines to Display - The maximum number of lines used to display PCode. Any
+
Maximum Lines to Display - The maximum number of lines used to display PCode. Any
additional lines of PCode will not be shown.
@@ -1009,6 +1009,24 @@
Selection Color - Set the Browser Selection color.
+
+ Source Map Field
+
+ Show Filename Only -
+ If selected, only the file name will be shown in the Listing field (rather than the full
+ path).
+
+ Show Source Info at Every Address - If selected, source map information will be
+ displayed for each address in the Listing. Otherwise the source information for a given
+ source map entry will only be displayed at the minimum address of the corresponding
+ range.
+
+ Maximum Number of Source Map Entries to Display - Maximum number of source
+ map entries to display per address.
+
+ Show Identifier - If selected, the source file identifier (md5, sha1,...) will
+ be shown in the Listing. Note that a source file might not have an identifier.
+
Template Display Options
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java
index 215256cceb..0a0bbd0a46 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImportOptions.java
@@ -38,10 +38,10 @@ public class DWARFImportOptions {
"Include source code location info (filename:linenumber) in comments attached to the " +
"Ghidra datatype or function or variable created.";
- private static final String OPTION_SOURCE_LINEINFO = "Output Source Line Info";
+ private static final String OPTION_SOURCE_LINEINFO = "Import Source Line Info";
private static final String OPTION_SOURCE_LINEINFO_DESC =
- "Place end-of-line comments containg the source code filename and line number at " +
- "each location provided in the DWARF data";
+ "Create source map entries containing the source code filename, line number, address, and" +
+ " length at each location provided in the DWARF data";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO = "Output DWARF DIE Info";
private static final String OPTION_OUTPUT_DWARF_DIE_INFO_DESC =
@@ -78,6 +78,10 @@ public class DWARFImportOptions {
private static final String OPTION_DEFAULT_CC_DESC =
"Name of default calling convention to assign to functions (e.g. __cdecl, __stdcall, etc), or leave blank.";
+ private static final String OPTION_MAX_SOURCE_ENTRY_LENGTH = "Maximum Source Map Entry Length";
+ private static final String OPTION_MAX_SOURCE_ENTRY_LENGTH_DESC =
+ "Maximum length for a source map entry. Longer lengths will be replaced with 0";
+
//==================================================================================================
// Old Option Names - Should stick around for multiple major versions after 10.2
//==================================================================================================
@@ -111,9 +115,10 @@ public class DWARFImportOptions {
private boolean specialCaseSizedBaseTypes = true;
private boolean importLocalVariables = true;
private boolean useBookmarks = true;
- private boolean outputSourceLineInfo = false;
+ private boolean outputSourceLineInfo = true;
private boolean ignoreParamStorage = false;
private String defaultCC = "";
+ private long maxSourceMapEntryLength = 2000;
/**
* Create new instance
@@ -376,10 +381,20 @@ public class DWARFImportOptions {
return useBookmarks;
}
+ /**
+ * Option to control whether source map info from DWARF is stored in the Program.
+ *
+ * @return {@code true} if option turned on
+ */
public boolean isOutputSourceLineInfo() {
return outputSourceLineInfo;
}
+ /**
+ * Option to control whether source map info from DWARF is stored in the Program.
+ *
+ * @param outputSourceLineInfo true to turn option on, false to turn off
+ */
public void setOutputSourceLineInfo(boolean outputSourceLineInfo) {
this.outputSourceLineInfo = outputSourceLineInfo;
}
@@ -400,6 +415,29 @@ public class DWARFImportOptions {
this.defaultCC = defaultCC;
}
+ /**
+ * Option to control the maximum length of a source map entry. If a longer length is calculated
+ * it will be replaced with 0.
+ *
+ * @return max source map entry length
+ */
+ public long getMaxSourceMapEntryLength() {
+ return maxSourceMapEntryLength;
+ }
+
+ /**
+ * Option to control the maximum length of a source map entry. If a longer length is calculated
+ * it will be replaced with 0.
+ *
+ * @param maxLength new max source entry length
+ */
+ public void setMaxSourceMapEntryLength(long maxLength) {
+ if (maxLength < 0) {
+ maxLength = 0;
+ }
+ maxSourceMapEntryLength = maxLength;
+ }
+
/**
* See {@link Analyzer#registerOptions(Options, ghidra.program.model.listing.Program)}
*
@@ -440,6 +478,8 @@ public class DWARFImportOptions {
OPTION_IGNORE_PARAM_STORAGE_DESC);
options.registerOption(OPTION_DEFAULT_CC, getDefaultCC(), null, OPTION_DEFAULT_CC_DESC);
+ options.registerOption(OPTION_MAX_SOURCE_ENTRY_LENGTH, maxSourceMapEntryLength, null,
+ OPTION_MAX_SOURCE_ENTRY_LENGTH_DESC);
}
/**
@@ -467,6 +507,8 @@ public class DWARFImportOptions {
setIgnoreParamStorage(
options.getBoolean(OPTION_IGNORE_PARAM_STORAGE, isIgnoreParamStorage()));
setDefaultCC(options.getString(OPTION_DEFAULT_CC, getDefaultCC()));
+ setMaxSourceMapEntryLength(
+ options.getLong(OPTION_MAX_SOURCE_ENTRY_LENGTH, getMaxSourceMapEntryLength()));
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImporter.java
index cc20a69628..87ce51f13b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImporter.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFImporter.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.
@@ -16,19 +16,22 @@
package ghidra.app.util.bin.format.dwarf;
import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.dwarf.line.DWARFLine.SourceFileAddr;
+import ghidra.app.util.bin.format.dwarf.line.DWARFLineProgramExecutor;
+import ghidra.framework.store.LockException;
+import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.data.*;
-import ghidra.program.model.listing.CodeUnit;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.util.Msg;
import ghidra.util.Swing;
-import ghidra.util.exception.CancelledException;
-import ghidra.util.exception.DuplicateNameException;
+import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
import utility.function.Dummy;
@@ -81,8 +84,8 @@ public class DWARFImporter {
monitor.checkCancelled();
monitor.incrementProgress(1);
- if ( (monitor.getProgress() % 5) == 0 ) {
- /* balance between getting work done and pampering the swing thread */
+ if ((monitor.getProgress() % 5) == 0) {
+ /* balance between getting work done and pampering the swing thread */
Swing.runNow(Dummy.runnable());
}
@@ -174,28 +177,124 @@ public class DWARFImporter {
return new CategoryPath(newRoot, cpParts.subList(origRootParts.size(), cpParts.size()));
}
- private void addSourceLineInfo(BinaryReader reader) throws CancelledException, IOException {
- if ( reader == null ) {
+ private void addSourceLineInfo(BinaryReader reader)
+ throws CancelledException, IOException, LockException {
+ if (reader == null) {
+ Msg.warn(this, "Can't add source line info - reader is null");
return;
}
- monitor.initialize(reader.length(), "DWARF Source Line Info");
+ int entryCount = 0;
+ Program ghidraProgram = prog.getGhidraProgram();
+ BookmarkManager bookmarkManager = ghidraProgram.getBookmarkManager();
+ long maxLength = prog.getImportOptions().getMaxSourceMapEntryLength();
+ boolean errorBookmarks = prog.getImportOptions().isUseBookmarks();
List compUnits = prog.getCompilationUnits();
+ monitor.initialize(compUnits.size(), "Reading DWARF Source Map Info");
+ SourceFileManager sourceManager = ghidraProgram.getSourceFileManager();
+ List sourceInfo = new ArrayList<>();
for (DWARFCompilationUnit cu : compUnits) {
- try {
- monitor.checkCancelled();
- monitor.setProgress(cu.getLine().getStartOffset());
- List allSFA = cu.getLine().getAllSourceFileAddrInfo(cu, reader);
- for (SourceFileAddr sfa : allSFA) {
- Address addr = prog.getCodeAddress(sfa.address());
- DWARFUtil.appendComment(prog.getGhidraProgram(), addr, CodeUnit.EOL_COMMENT, "",
- "%s:%d".formatted(sfa.fileName(), sfa.lineNum()), ";");
- }
+ monitor.increment();
+ sourceInfo.addAll(cu.getLine().getAllSourceFileAddrInfo(cu, reader));
+ }
+ sourceInfo.sort((i, j) -> Long.compareUnsigned(i.address(), j.address()));
+ monitor.initialize(sourceInfo.size(), "Applying DWARF Source Map Info");
+ for (int i = 0; i < sourceInfo.size() - 1; i++) {
+ monitor.checkCancelled();
+ monitor.increment(1);
+ SourceFileAddr sfa = sourceInfo.get(i);
+ if (sfa.isEndSequence()) {
+ continue;
}
- catch (IOException e) {
- Msg.error(this,
- "Failed to read DWARF line info for cu %d".formatted(cu.getUnitNumber()), e);
+ Address addr = prog.getCodeAddress(sfa.address());
+ if (!ghidraProgram.getMemory().getExecuteSet().contains(addr)) {
+ String errorString =
+ "entry for non-executable address; skipping: file %s line %d address: %s %x"
+ .formatted(sfa.fileName(), sfa.lineNum(), addr.toString(),
+ sfa.address());
+
+ reportError(bookmarkManager, errorString, addr, errorBookmarks);
+ continue;
+ }
+
+ long length = getLength(i, sourceInfo);
+ if (length < 0) {
+ length = 0;
+ String errorString =
+ "Error calculating entry length for file %s line %d address %s %x; replacing " +
+ "with length 0 entry".formatted(sfa.fileName(), sfa.lineNum(),
+ addr.toString(), sfa.address());
+ reportError(bookmarkManager, errorString, addr, errorBookmarks);
+ }
+ if (length > maxLength) {
+ String errorString = ("entry for file %s line %d address: %s %x length %d too" +
+ " large, replacing with length 0 entry").formatted(sfa.fileName(),
+ sfa.lineNum(), addr.toString(), sfa.address(), length);
+ length = 0;
+ reportError(bookmarkManager, errorString, addr, errorBookmarks);
+ }
+
+ SourceFile source = null;
+ try {
+ source = new SourceFile(sfa.fileName());
+ sourceManager.addSourceFile(source);
+ }
+ catch (IllegalArgumentException e) {
+ Msg.error(this, "Exception creating source file: %s".formatted(e.getMessage()));
+ continue;
+ }
+ try {
+ sourceManager.addSourceMapEntry(source, sfa.lineNum(), addr, length);
+ }
+ catch (AddressOverflowException e) {
+ String errorString = "AddressOverflowException for source map entry %s %d %s %x %d"
+ .formatted(source.getFilename(), sfa.lineNum(), addr.toString(),
+ sfa.address(), length);
+ reportError(bookmarkManager, errorString, addr, errorBookmarks);
+ continue;
+ }
+ entryCount++;
+ }
+ Msg.info(this, "Added %d source map entries".formatted(entryCount));
+ }
+
+ private void reportError(BookmarkManager bManager, String errorString, Address addr,
+ boolean errorBookmarks) {
+ if (errorBookmarks) {
+ bManager.setBookmark(addr, BookmarkType.ERROR, DWARFProgram.DWARF_BOOKMARK_CAT,
+ errorString);
+ }
+ else {
+ Msg.error(this, errorString);
+ }
+ }
+
+ /**
+ * In the DWARF format, source line info is only recorded for an address x
+ * if the info for x differs from the info for address x-1.
+ * To calculate the length of a source map entry, we need to look for the next
+ * SourceFileAddr with a different address (there can be multiple records per address
+ * so there's no guarantee that the SourceFileAddr at position i+1 has a different
+ * address). Special end-of-sequence markers are used to mark the end of a function,
+ * so if we find one of these we stop searching. These markers have their addresses tweaked
+ * by one, which we undo (see {@link DWARFLineProgramExecutor#executeExtended}).
+ * @param i starting index
+ * @param allSFA sorted list of SourceFileAddr
+ * @return computed length or -1 on error
+ */
+ private long getLength(int i, List allSFA) {
+ SourceFileAddr iAddr = allSFA.get(i);
+ long iOffset = iAddr.address();
+ for (int j = i + 1; j < allSFA.size(); j++) {
+ SourceFileAddr current = allSFA.get(j);
+ long currentAddr = current.address();
+ if (current.isEndSequence()) {
+ return currentAddr + 1 - iOffset;
+ }
+ if (currentAddr != iOffset) {
+ return currentAddr - iOffset;
}
}
+ return -1;
}
/**
@@ -205,7 +304,8 @@ public class DWARFImporter {
* @throws DWARFException
* @throws CancelledException
*/
- public DWARFImportSummary performImport() throws IOException, DWARFException, CancelledException {
+ public DWARFImportSummary performImport()
+ throws IOException, DWARFException, CancelledException {
monitor.setIndeterminate(false);
monitor.setShowProgressValue(true);
@@ -231,7 +331,18 @@ public class DWARFImporter {
}
if (importOptions.isOutputSourceLineInfo()) {
- addSourceLineInfo(prog.getDebugLineBR());
+ if (!prog.getGhidraProgram().hasExclusiveAccess()) {
+ Msg.showError(this, null, "Unable to add source map info",
+ "Exclusive access to the program is required to add source map info");
+ }
+ else {
+ try {
+ addSourceLineInfo(prog.getDebugLineBR());
+ }
+ catch (LockException e) {
+ throw new AssertException("LockException after exclusive access verified");
+ }
+ }
}
importSummary.totalElapsedMS = System.currentTimeMillis() - start_ts;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java
index d8304b878d..de3f7a4db8 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/DWARFProgram.java
@@ -54,7 +54,7 @@ public class DWARFProgram implements Closeable {
public static final CategoryPath DWARF_ROOT_CATPATH = CategoryPath.ROOT.extend(DWARF_ROOT_NAME);
public static final CategoryPath UNCAT_CATPATH = DWARF_ROOT_CATPATH.extend("_UNCATEGORIZED_");
- private static final String DWARF_BOOKMARK_CAT = "DWARF";
+ public static final String DWARF_BOOKMARK_CAT = "DWARF";
private static final int NAME_HASH_REPLACEMENT_SIZE = 8 + 2 + 2;
private static final String ELLIPSES_STR = "...";
protected static final EnumSet REF_ATTRS =
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLine.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLine.java
index a0b8eb45fb..d98aefd5ec 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLine.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/line/DWARFLine.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.
@@ -270,14 +270,20 @@ public class DWARFLine {
return lpe;
}
- public record SourceFileAddr(long address, String fileName, int lineNum) {}
+ public record SourceFileAddr(long address, String fileName, byte[] md5, int lineNum,
+ boolean isEndSequence) {}
public List getAllSourceFileAddrInfo(DWARFCompilationUnit cu,
BinaryReader reader) throws IOException {
try (DWARFLineProgramExecutor lpe = getLineProgramexecutor(cu, reader)) {
List results = new ArrayList<>();
for (DWARFLineProgramState row : lpe.allRows()) {
- results.add(new SourceFileAddr(row.address, getFilePath(row.file, true), row.line));
+ byte[] md5 = null;
+ if (cu.getDWARFVersion() >= 5 && (row.file < cu.getLine().getNumFiles())) {
+ md5 = cu.getLine().getFile(row.file).getMD5();
+ }
+ results.add(new SourceFileAddr(row.address, getFilePath(row.file, true), md5,
+ row.line, row.isEndSequence));
}
return results;
@@ -315,6 +321,14 @@ public class DWARFLine {
"Invalid file index %d for line table at 0x%x: ".formatted(index, startOffset));
}
+ /**
+ * Returns the number of indexed files
+ * @return num files
+ */
+ public int getNumFiles() {
+ return files.size();
+ }
+
public String getFilePath(int index, boolean includePath) {
try {
DWARFFile f = getFile(index);
@@ -322,9 +336,7 @@ public class DWARFLine {
return f.getName();
}
- String dir = f.getDirectoryIndex() >= 0
- ? getDir(f.getDirectoryIndex()).getName()
- : "";
+ String dir = f.getDirectoryIndex() >= 0 ? getDir(f.getDirectoryIndex()).getName() : "";
return FSUtilities.appendPath(dir, f.getName());
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/SourceMapFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/SourceMapFieldFactory.java
new file mode 100644
index 0000000000..880504f2c7
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/SourceMapFieldFactory.java
@@ -0,0 +1,259 @@
+/* ###
+ * 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.Color;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+import docking.widgets.fieldpanel.field.*;
+import docking.widgets.fieldpanel.support.FieldLocation;
+import generic.theme.GThemeDefaults.Colors.Palette;
+import ghidra.GhidraOptions;
+import ghidra.app.util.ListingHighlightProvider;
+import ghidra.app.util.viewer.format.FieldFormatModel;
+import ghidra.app.util.viewer.proxy.ProxyObj;
+import ghidra.framework.options.Options;
+import ghidra.framework.options.ToolOptions;
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.database.sourcemap.SourceFileIdType;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.CodeUnit;
+import ghidra.program.model.sourcemap.*;
+import ghidra.program.util.ProgramLocation;
+import ghidra.program.util.SourceMapFieldLocation;
+import ghidra.util.HelpLocation;
+
+/**
+ * {@link FieldFactory} for showing source and line information in the Listing.
+ */
+public class SourceMapFieldFactory extends FieldFactory {
+
+ static final String FIELD_NAME = "Source Map";
+ private static final String GROUP_TITLE = "Source Map";
+ static final String SHOW_FILENAME_ONLY_OPTION_NAME =
+ GROUP_TITLE + Options.DELIMITER + "Show Filename Only";
+ static final String SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME =
+ GROUP_TITLE + Options.DELIMITER + "Show Source Info at Every Address";
+ static final String MAX_ENTRIES_PER_ADDRESS_OPTION_NAME =
+ GROUP_TITLE + Options.DELIMITER + "Maximum Number of Source Map Entries to Display";
+ static final String SHOW_IDENTIFIER_OPTION_NAME =
+ GROUP_TITLE + Options.DELIMITER + "Show Identifier";
+ static final String NO_SOURCE_INFO = "unknown:??";
+
+ private boolean showOnlyFileNames = true;
+ private boolean showInfoAtAllAddresses = false;
+ private boolean showIdentifier = false;
+ private static final int DEFAULT_MAX_ENTRIES = 4;
+ private int maxEntries = DEFAULT_MAX_ENTRIES;
+ static Color OFFCUT_COLOR = Palette.GRAY;
+
+ /**
+ * Default constructor
+ */
+ public SourceMapFieldFactory() {
+ super(FIELD_NAME);
+ }
+
+ protected SourceMapFieldFactory(FieldFormatModel model,
+ ListingHighlightProvider highlightProvider, Options displayOptions,
+ Options fieldOptions) {
+ super(FIELD_NAME, model, highlightProvider, displayOptions, fieldOptions);
+ registerOptions(fieldOptions);
+ }
+
+ @Override
+ public FieldFactory newInstance(FieldFormatModel formatModel,
+ ListingHighlightProvider highlightProvider, ToolOptions options,
+ ToolOptions fieldOptions) {
+ return new SourceMapFieldFactory(formatModel, highlightProvider, options, fieldOptions);
+ }
+
+ @Override
+ public ListingField getField(ProxyObj> obj, int varWidth) {
+ if (!enabled) {
+ return null;
+ }
+ if (!(obj.getObject() instanceof CodeUnit cu)) {
+ return null;
+ }
+
+ List entriesToShow = getSourceMapEntries(cu);
+ if (entriesToShow.isEmpty()) {
+ if (!showInfoAtAllAddresses) {
+ return null;
+ }
+ AttributedString attrString =
+ new AttributedString(NO_SOURCE_INFO, Palette.BLACK, getMetrics());
+ return ListingTextField.createSingleLineTextField(this, obj,
+ new TextFieldElement(attrString, 0, 0), startX + varWidth, width, hlProvider);
+ }
+ List fieldElements = new ArrayList<>();
+
+ Address cuAddr = cu.getAddress();
+ if (!showInfoAtAllAddresses) {
+ List entriesStartingWithinCu = new ArrayList<>();
+ for (SourceMapEntry entry : entriesToShow) {
+ if (entry.getBaseAddress().compareTo(cuAddr) >= 0) {
+ entriesStartingWithinCu.add(entry);
+ }
+ }
+ if (entriesStartingWithinCu.isEmpty()) {
+ return null;
+ }
+ entriesToShow = entriesStartingWithinCu;
+ }
+
+ for (SourceMapEntry entry : entriesToShow) {
+ StringBuilder sb = new StringBuilder();
+ if (showOnlyFileNames) {
+ sb.append(entry.getSourceFile().getFilename());
+ }
+ else {
+ sb.append(entry.getSourceFile().getPath());
+ }
+ sb.append(":");
+ sb.append(entry.getLineNumber());
+ sb.append(" (");
+ sb.append(entry.getLength());
+ sb.append(")");
+ if (showIdentifier) {
+ SourceFile sourceFile = entry.getSourceFile();
+ if (sourceFile.getIdType().equals(SourceFileIdType.NONE)) {
+ sb.append(" [no id]");
+ }
+ else {
+ sb.append(" [");
+ sb.append(sourceFile.getIdType().name());
+ sb.append("=");
+ sb.append(sourceFile.getIdAsString());
+ sb.append("]");
+ }
+ }
+ // use gray for entries which start "within" the code unit
+ Color color =
+ entry.getBaseAddress().compareTo(cuAddr) <= 0 ? Palette.BLACK : OFFCUT_COLOR;
+ AttributedString attrString = new AttributedString(sb.toString(), color, getMetrics());
+ fieldElements.add(new TextFieldElement(attrString, 0, 0));
+ }
+
+ return ListingTextField.createMultilineTextField(this, obj, fieldElements,
+ startX + varWidth, width, maxEntries, hlProvider);
+ }
+
+ @Override
+ public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum,
+ ProgramLocation loc) {
+ if (loc instanceof SourceMapFieldLocation sourceField) {
+ return new FieldLocation(index, fieldNum, sourceField.getRow(),
+ sourceField.getCharOffset());
+ }
+ return null;
+ }
+
+ @Override
+ public ProgramLocation getProgramLocation(int row, int col, ListingField bf) {
+ Object obj = bf.getProxy().getObject();
+ if (!(obj instanceof CodeUnit cu)) {
+ return null;
+ }
+ List entriesToShow = getSourceMapEntries(cu);
+ if (entriesToShow == null || entriesToShow.size() <= row) {
+ return null;
+ }
+ return new SourceMapFieldLocation(cu.getProgram(), cu.getAddress(), row, col,
+ entriesToShow.get(row));
+ }
+
+ @Override
+ public boolean acceptsType(int category, Class> proxyObjectClass) {
+ return (category == FieldFormatModel.INSTRUCTION_OR_DATA);
+ }
+
+ @Override
+ public void fieldOptionsChanged(Options options, String optionName, Object oldValue,
+ Object newValue) {
+ super.fieldOptionsChanged(options, optionName, oldValue, newValue);
+
+ if (options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) {
+ if (optionName.equals(SHOW_FILENAME_ONLY_OPTION_NAME)) {
+ showOnlyFileNames = options.getBoolean(SHOW_FILENAME_ONLY_OPTION_NAME, true);
+ }
+ if (optionName.equals(SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME)) {
+ showInfoAtAllAddresses =
+ options.getBoolean(SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false);
+ }
+ if (optionName.equals(MAX_ENTRIES_PER_ADDRESS_OPTION_NAME)) {
+ maxEntries =
+ options.getInt(MAX_ENTRIES_PER_ADDRESS_OPTION_NAME, DEFAULT_MAX_ENTRIES);
+ }
+ if (optionName.equals(SHOW_IDENTIFIER_OPTION_NAME)) {
+ showIdentifier = options.getBoolean(SHOW_IDENTIFIER_OPTION_NAME, false);
+ }
+ model.update();
+ }
+ }
+
+ private void registerOptions(Options fieldOptions) {
+ HelpLocation helpLoc = new HelpLocation("CodeBrowserPlugin", "Source_Map_Field");
+
+ fieldOptions.registerOption(SHOW_FILENAME_ONLY_OPTION_NAME, true, helpLoc,
+ "Show only source file name (rather than absolute path)");
+ showOnlyFileNames = fieldOptions.getBoolean(SHOW_FILENAME_ONLY_OPTION_NAME, true);
+
+ fieldOptions.registerOption(SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false, helpLoc,
+ "Show source info at every address " +
+ "(rather than only at beginning of source map entries)");
+ showInfoAtAllAddresses =
+ fieldOptions.getBoolean(SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false);
+
+ fieldOptions.registerOption(MAX_ENTRIES_PER_ADDRESS_OPTION_NAME, 4, helpLoc,
+ "Maximum number of source map entries to display");
+ maxEntries = fieldOptions.getInt(MAX_ENTRIES_PER_ADDRESS_OPTION_NAME, DEFAULT_MAX_ENTRIES);
+
+ fieldOptions.registerOption(SHOW_IDENTIFIER_OPTION_NAME, false, helpLoc,
+ "Show source file identifier info");
+ showIdentifier = fieldOptions.getBoolean(SHOW_IDENTIFIER_OPTION_NAME, false);
+
+ }
+
+ private List getSourceMapEntries(CodeUnit cu) {
+ List entries = new ArrayList<>();
+ SourceFileManager sourceManager = cu.getProgram().getSourceFileManager();
+ // check all addresses in the code unit to handle the (presumably rare) case where
+ // there is an entry associated with an address in the code unit which is not its
+ // minimum address
+ Address cuMinAddr = cu.getMinAddress();
+ Address cuMaxAddr = cu.getMaxAddress();
+ SourceMapEntryIterator entryIter =
+ sourceManager.getSourceMapEntryIterator(cuMaxAddr, false);
+ while (entryIter.hasNext()) {
+ SourceMapEntry entry = entryIter.next();
+ long entryLength = entry.getLength();
+ long adjusted = entryLength == 0 ? 0 : entryLength - 1;
+ if (entry.getBaseAddress().add(adjusted).compareTo(cuMinAddr) >= 0) {
+ entries.add(entry);
+ continue;
+ }
+ if (entryLength != 0) {
+ break;
+ }
+ }
+ return entries.reversed();
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiff.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiff.java
index 2170016a51..e9c4513920 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiff.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiff.java
@@ -19,12 +19,16 @@ import java.util.*;
import javax.help.UnsupportedOperationException;
+import org.apache.commons.collections4.CollectionUtils;
import ghidra.program.database.properties.UnsupportedMapDB;
+import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.program.model.sourcemap.SourceMapEntry;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.PropertyMap;
import ghidra.program.model.util.PropertyMapManager;
@@ -983,6 +987,11 @@ public class ProgramDiff {
monitor.setMessage(monitorMsg);
as = getFunctionTagDifferences(checkAddressSet, monitor);
break;
+ case ProgramDiffFilter.SOURCE_MAP_DIFFS:
+ monitorMsg = "Checking Source Map Differences";
+ monitor.setMessage(monitorMsg);
+ as = getSourceMapDifferences(checkAddressSet, monitor);
+ break;
}
if (as != null) {
diffAddrSets.put(diffType, as);
@@ -1566,6 +1575,199 @@ public class ProgramDiff {
return c.getObjectDiffs(iter1, iter2, monitor);
}
+ /**
+ * Determines the source map differences for the addresses in {@code addressSet}. Source map
+ * entries which intersect but are not contained within {@code addressSet} are ignored.
+ * The returned {@link AddressSet} consists of the minimum addresses of all of the differing
+ * source map entries. This method does not check for differences between non-mapped
+ * {@link SourceFile}s.
+ *
+ * @param addressSet addresses to check for differences (from program1)
+ * @param monitor task monitor
+ * @return minimum addresses of differing ranges
+ * @throws CancelledException if user cancels
+ */
+ private AddressSet getSourceMapDifferences(AddressSetView addressSet, TaskMonitor monitor)
+ throws CancelledException {
+ SourceFileManager p1Manager = program1.getSourceFileManager();
+ SourceFileManager p2Manager = program2.getSourceFileManager();
+
+ List p1Sources = program1.getSourceFileManager().getMappedSourceFiles();
+ List p2Sources = program2.getSourceFileManager().getMappedSourceFiles();
+
+ Collection differingPaths = CollectionUtils.disjunction(p1Sources, p2Sources);
+
+ Collection p1Only = CollectionUtils.intersection(p1Sources, differingPaths);
+ Collection p2Only = CollectionUtils.intersection(p2Sources, differingPaths);
+
+ AddressSet differences =
+ processDifferingSourceFiles(addressSet, p1Only, p2Only, p1Manager, p2Manager, monitor);
+
+ // now consider the mapping info for the source files the programs have in common
+ Collection commonSources = CollectionUtils.intersection(p1Sources, p2Sources);
+
+ // first check the entries in program1
+ // only need to check an address once, even if there are multiple entries based at it
+ AddressSet p1CheckedAddresses = new AddressSet();
+ AddressSet p2CheckedAddresses = new AddressSet();
+
+ for (SourceFile common : commonSources) {
+ for (SourceMapEntry p1Entry : p1Manager.getSourceMapEntries(common)) {
+ monitor.checkCancelled();
+ Address p1Base = p1Entry.getBaseAddress();
+ if (p1CheckedAddresses.contains(p1Base)) {
+ continue;
+ }
+
+ Address p2Base = SimpleDiffUtility.getCompatibleAddress(program1, p1Base, program2);
+ if (p2Base == null) {
+ p1CheckedAddresses.add(p1Base);
+ continue;
+ }
+
+ long p1Length = p1Entry.getLength();
+ if (p1Length != 0) {
+ Address p1End = p1Base.add(p1Length - 1);
+ if (!addressSet.contains(p1Base, p1End)) {
+ if (addressSet.intersects(p1Base, p1End)) {
+ logSkippedEntry(p1Entry, program1);
+ }
+ continue;
+ }
+ }
+ p1CheckedAddresses.add(p1Base);
+ p2CheckedAddresses.add(p2Base);
+ List p1Entries = p1Manager.getSourceMapEntries(p1Base);
+ List p2Entries = p2Manager.getSourceMapEntries(p2Base);
+ if (p1Entries.size() != p2Entries.size()) {
+ differences.add(p1Base);
+ continue;
+ }
+ for (SourceMapEntry e1 : p1Entries) {
+ if (Collections.binarySearch(p2Entries, e1) < 0) {
+ differences.add(p1Base);
+ break;
+ }
+ }
+ }
+ }
+
+ AddressSet unmatchedP2Addrs =
+ findUnmatchedP2Addrs(addressSet, p2CheckedAddresses, commonSources, monitor);
+ differences.add(unmatchedP2Addrs);
+
+ return differences;
+
+ }
+
+ // look for addresses in program2 that have entries where the corresponding
+ // address in program1 does not have any entries
+ private AddressSet findUnmatchedP2Addrs(AddressSetView addressSet,
+ AddressSet p2CheckedAddresses, Collection commonSources,
+ TaskMonitor monitor) throws CancelledException {
+ AddressSet results = new AddressSet();
+ SourceFileManager p2Manager = program2.getSourceFileManager();
+ for (SourceFile p2Source : commonSources) {
+ for (SourceMapEntry p2Entry : p2Manager.getSourceMapEntries(p2Source)) {
+ monitor.checkCancelled();
+ Address p2Base = p2Entry.getBaseAddress();
+ if (p2CheckedAddresses.contains(p2Base)) {
+ continue;
+ }
+ Address p1Base = SimpleDiffUtility.getCompatibleAddress(program2, p2Base, program1);
+ p2CheckedAddresses.add(p2Base);
+ if (p1Base == null) {
+ continue;
+ }
+ if (!addressSet.contains(p1Base)) {
+ continue;
+ }
+ List p1Entries =
+ program1.getSourceFileManager().getSourceMapEntries(p1Base);
+ boolean p1BaseAlreadyChecked = false;
+ for (SourceMapEntry p1Entry : p1Entries) {
+ if (p1Entry.getBaseAddress().equals(p1Base)) {
+ p1BaseAlreadyChecked = true;
+ break;
+ }
+ }
+ if (!p1BaseAlreadyChecked) {
+ results.add(p1Base);
+ }
+ }
+ }
+ return results;
+ }
+
+ // process SourceFiles with mapping info in exactly one program
+ // any associated SourceMapEntry must be a difference
+ // so just check that the range is part of addressSet
+ private AddressSet processDifferingSourceFiles(AddressSetView addressSet,
+ Collection p1Only, Collection p2Only,
+ SourceFileManager p1Manager, SourceFileManager p2Manager, TaskMonitor monitor)
+ throws CancelledException {
+ AddressSet result = new AddressSet();
+
+ for (SourceFile p1Source : p1Only) {
+ List p1Entries = p1Manager.getSourceMapEntries(p1Source);
+ for (SourceMapEntry p1Entry : p1Entries) {
+ monitor.checkCancelled();
+ Address p1Base = p1Entry.getBaseAddress();
+ if (p1Entry.getLength() == 0) {
+ if (addressSet.contains(p1Base)) {
+ result.add(p1Base);
+ }
+ continue;
+ }
+ Address p1End = p1Base.add(p1Entry.getLength() - 1);
+ if (addressSet.contains(p1Base, p1End)) {
+ result.add(p1Base);
+ continue;
+ }
+ if (addressSet.intersects(p1Base, p1End)) {
+ logSkippedEntry(p1Entry, program1);
+ }
+ }
+ }
+
+ for (SourceFile p2Source : p2Only) {
+ List p2Entries = p2Manager.getSourceMapEntries(p2Source);
+ for (SourceMapEntry p2Entry : p2Entries) {
+ monitor.checkCancelled();
+ Address p2Base = p2Entry.getBaseAddress();
+ Address p1Base = SimpleDiffUtility.getCompatibleAddress(program2, p2Base, program1);
+ if (p1Base == null) {
+ continue;
+ }
+ long p2Length = p2Entry.getLength();
+ if (p2Length == 0) {
+ if (addressSet.contains(p1Base)) {
+ result.add(p1Base);
+ }
+ continue;
+ }
+ Address p2End = p2Base.add(p2Length - 1);
+ Address p1End = SimpleDiffUtility.getCompatibleAddress(program2, p2End, program1);
+ if (p1End == null) {
+ continue;
+ }
+ if (addressSet.contains(p1Base, p1End)) {
+ result.add(p1Base);
+ continue;
+ }
+ if (addressSet.intersects(p1Base, p1End)) {
+ logSkippedEntry(p2Entry, program2);
+ }
+ }
+ }
+ return result;
+ }
+
+ private void logSkippedEntry(SourceMapEntry entry, Program program) {
+ Msg.warn(this,
+ "Skipping source map entry " + entry.toString() + " in program " + program.getName());
+ }
+
/**
* Determines the code unit addresses where there are differences of the
* indicated type between program1 and program2.
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiffFilter.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiffFilter.java
index fa7d990014..63a6fe1dc1 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiffFilter.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramDiffFilter.java
@@ -23,29 +23,38 @@ package ghidra.program.util;
* differences of that type between two programs. False indicates no interest
* in this type of program difference.
*
Valid filter types are:
- * BYTE_DIFFS, CODE_UNIT_DIFFS,
- * PLATE_COMMENT_DIFFS, PRE_COMMENT_DIFFS, EOL_COMMENT_DIFFS,
- * REPEATABLE_COMMENT_DIFFS, POST_COMMENT_DIFFS,
+ * BOOKMARK_DIFFS,
+ * BYTE_DIFFS,
+ * CODE_UNIT_DIFFS,
+ * EQUATE_DIFFS,
+ * EOL_COMMENT_DIFFS,
+ * FUNCTION_DIFFS,
+ * FUNCTION_TAG_DIFFS,
+ * PLATE_COMMENT_DIFFS,
+ * POST_COMMENT_DIFFS,
+ * PRE_COMMENT_DIFFS,
+ * PROGRAM_CONTEXT_DIFFS,
* REFERENCE_DIFFS,
- * USER_DEFINED_DIFFS, BOOKMARK_DIFFS,
+ * REPEATABLE_COMMENT_DIFFS,
+ * SOURCE_MAP_DIFFS,
* SYMBOL_DIFFS,
- * EQUATE_DIFFS, FUNCTION_DIFFS, PROGRAM_CONTEXT_DIFFS.
+ * USER_DEFINED_DIFFS.
*
Predefined filter type combinations are:
* COMMENT_DIFFS and ALL_DIFFS.
*/
public class ProgramDiffFilter {
- ////////////////////////////////////////////////////////////////////////
- // The following are definitions for the different types of filtering.
- // Combination filter indicators are created by "OR"ing the individual
- // filter indicators.
- ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+ // The following are definitions for the different types of filtering.
+ // Combination filter indicators are created by "OR"ing the individual
+ // filter indicators.
+ ////////////////////////////////////////////////////////////////////////
/** Indicates the filter for the program context (register) differences. */
public static final int PROGRAM_CONTEXT_DIFFS = 1 << 0;
- /** Indicates the filter for the byte differences. */
- public static final int BYTE_DIFFS = 1 << 1;
- /** Indicates the filter for the code unit differences. */
- public static final int CODE_UNIT_DIFFS = 1 << 2;
+ /** Indicates the filter for the byte differences. */
+ public static final int BYTE_DIFFS = 1 << 1;
+ /** Indicates the filter for the code unit differences. */
+ public static final int CODE_UNIT_DIFFS = 1 << 2;
/** Indicates the filter for the end of line comment differences. */
public static final int EOL_COMMENT_DIFFS = 1 << 3;
/** Indicates the filter for the pre comment differences. */
@@ -60,8 +69,8 @@ public class ProgramDiffFilter {
public static final int REFERENCE_DIFFS = 1 << 8;
/** Indicates the filter for the equates differences. */
public static final int EQUATE_DIFFS = 1 << 9;
- /** Indicates the filter for the symbol differences. */
- public static final int SYMBOL_DIFFS = 1 << 10;
+ /** Indicates the filter for the symbol differences. */
+ public static final int SYMBOL_DIFFS = 1 << 10;
/** Indicates the filter for the function differences. */
public static final int FUNCTION_DIFFS = 1 << 11;
/** Indicates the filter for bookmark differences. */
@@ -70,23 +79,21 @@ public class ProgramDiffFilter {
public static final int USER_DEFINED_DIFFS = 1 << 13;
/** Indicates the filter for the function tag differences. */
public static final int FUNCTION_TAG_DIFFS = 1 << 14;
-
- // NOTE: If you add a new primary type here, make sure to use the
- // next available bit position and update the NUM_PRIMARY_TYPES.
- // ** Also don't forget to add it to ALL_DIFFS below. **
- /** The total number of primary difference types. */
- private static final int NUM_PRIMARY_TYPES = 15;
+ /** Indicates the filter for source map differences */
+ public static final int SOURCE_MAP_DIFFS = 1 << 15;
- //********************************************************
- //* PREDEFINED DIFFERENCE COMBINATIONS
- //********************************************************
+ // NOTE: If you add a new primary type here, make sure to use the
+ // next available bit position and update the NUM_PRIMARY_TYPES.
+ // ** Also don't forget to add it to ALL_DIFFS below. **
+ /** The total number of primary difference types. */
+ private static final int NUM_PRIMARY_TYPES = 16;
+
+ //********************************************************
+ //* PREDEFINED DIFFERENCE COMBINATIONS
+ //********************************************************
/** Indicates all comment filters. */
- public static final int COMMENT_DIFFS =
- EOL_COMMENT_DIFFS
- | PRE_COMMENT_DIFFS
- | POST_COMMENT_DIFFS
- | REPEATABLE_COMMENT_DIFFS
- | PLATE_COMMENT_DIFFS;
+ public static final int COMMENT_DIFFS = EOL_COMMENT_DIFFS | PRE_COMMENT_DIFFS |
+ POST_COMMENT_DIFFS | REPEATABLE_COMMENT_DIFFS | PLATE_COMMENT_DIFFS;
/** Indicates all filters for all defined types of differences. */
//@formatter:off
public static final int ALL_DIFFS =
@@ -100,18 +107,18 @@ public class ProgramDiffFilter {
| FUNCTION_DIFFS
| BOOKMARK_DIFFS
| FUNCTION_TAG_DIFFS
- | PROGRAM_CONTEXT_DIFFS;
+ | PROGRAM_CONTEXT_DIFFS
+ | SOURCE_MAP_DIFFS;
//@formatter:on
- /** filterFlags
holds the actual indicators for each
- * difference type as a bit setting.
- */
- private int filterFlags = 0;
+ /** filterFlags
holds the actual indicators for each
+ * difference type as a bit setting.
+ */
+ private int filterFlags = 0;
-
- /** Creates new ProgramDiffFilter with none of the diff types selected.*/
- public ProgramDiffFilter() {
- }
+ /** Creates new ProgramDiffFilter with none of the diff types selected.*/
+ public ProgramDiffFilter() {
+ }
/** Creates new ProgramDiffFilter equivalent to the specified ProgramDiffFilter.
*
@@ -121,47 +128,47 @@ public class ProgramDiffFilter {
this.filterFlags = filter.filterFlags;
}
- /** Creates new ProgramDiffFilter with the specified diff types selected.
- *
- * @param type one or more of the diff types "OR"ed together.
- *
i.e. CODE_UNIT_DIFFS | SYMBOL_DIFFS
- */
- public ProgramDiffFilter(int type) {
- filterFlags = ALL_DIFFS & type;
- }
+ /** Creates new ProgramDiffFilter with the specified diff types selected.
+ *
+ * @param type one or more of the diff types "OR"ed together.
+ *
i.e. CODE_UNIT_DIFFS | SYMBOL_DIFFS
+ */
+ public ProgramDiffFilter(int type) {
+ filterFlags = ALL_DIFFS & type;
+ }
- /**
- * getFilter determines whether or not the specified type of filter is set.
- *
- * @param type the set bits indicate the type of differences we want to
- * check as being set in the filter.
- *
For example, one or more of the diff types "OR"ed together.
- *
i.e. CODE_UNIT_DIFFS | SYMBOL_DIFFS
- * @return true if filtering for the specified type of differences.
- */
- public boolean getFilter(int type) {
- return ((type & filterFlags) != 0);
- }
+ /**
+ * getFilter determines whether or not the specified type of filter is set.
+ *
+ * @param type the set bits indicate the type of differences we want to
+ * check as being set in the filter.
+ *
For example, one or more of the diff types "OR"ed together.
+ *
i.e. CODE_UNIT_DIFFS | SYMBOL_DIFFS
+ * @return true if filtering for the specified type of differences.
+ */
+ public boolean getFilter(int type) {
+ return ((type & filterFlags) != 0);
+ }
- /** set this filter to look for types of differences in addition to those
- * types where it is already looking for differences.
- * The filter that is passed as a parameter indicates the additional types
- * of differences.
- *
- * @param filter filter indicating the additional types of differences
- * to look for between the programs.
- */
- synchronized public void addToFilter(ProgramDiffFilter filter) {
- filterFlags |= filter.filterFlags;
- }
+ /** set this filter to look for types of differences in addition to those
+ * types where it is already looking for differences.
+ * The filter that is passed as a parameter indicates the additional types
+ * of differences.
+ *
+ * @param filter filter indicating the additional types of differences
+ * to look for between the programs.
+ */
+ synchronized public void addToFilter(ProgramDiffFilter filter) {
+ filterFlags |= filter.filterFlags;
+ }
/** setFilter specifies whether or not the indicated type of difference will be
* included by the filter (true) or not included (false).
*
- * @param type the set bits indicate the type of differences we want to
- * look for in the programs.
- *
For example, one or more of the diff types "OR"ed together.
- *
i.e. CODE_UNIT_DIFFS | SYMBOL_DIFFS
+ * @param type the set bits indicate the type of differences we want to
+ * look for in the programs.
+ *
For example, one or more of the diff types "OR"ed together.
+ *
i.e. CODE_UNIT_DIFFS | SYMBOL_DIFFS
* @param filter true if you want to determine differences of the specified type.
*/
synchronized public void setFilter(int type, boolean filter) {
@@ -173,47 +180,47 @@ public class ProgramDiffFilter {
}
}
- /**
- * Sets all the defined types of differences to false.
- * Filter indicates no interest in any difference types.
- */
- public void clearAll() {
- setFilter(ALL_DIFFS, false);
- }
+ /**
+ * Sets all the defined types of differences to false.
+ * Filter indicates no interest in any difference types.
+ */
+ public void clearAll() {
+ setFilter(ALL_DIFFS, false);
+ }
- /**
- * Sets all the defined types of differences to true.
- * Filter indicates interest in all difference types.
- */
- public void selectAll() {
- setFilter(ALL_DIFFS, true);
- }
+ /**
+ * Sets all the defined types of differences to true.
+ * Filter indicates interest in all difference types.
+ */
+ public void selectAll() {
+ setFilter(ALL_DIFFS, true);
+ }
- /**
- * Gets all the valid individual types of differences for this filter.
- * These are also referred to as primary difference types.
- * @return an array containing all the currently defined difference types
- */
- public static int[] getPrimaryTypes() {
- int[] pt = new int[NUM_PRIMARY_TYPES];
- for (int i=0; itypeToName() returns the name of the difference type.
- * Only predefined types, as specified in ProgramDiffFilter
,
- * will return a name. Otherwise, an empty string is returned.
- * @param type the type of difference whose name is wanted.
- * @return the name of the predefined difference type. Otherwise, the empty string.
- */
- public static String typeToName(int type) {
- switch (type) {
- case ProgramDiffFilter.BYTE_DIFFS:
- return "BYTE_DIFFS";
- case ProgramDiffFilter.CODE_UNIT_DIFFS:
- return "CODE_UNIT_DIFFS";
+ /** typeToName()
returns the name of the difference type.
+ * Only predefined types, as specified in ProgramDiffFilter
,
+ * will return a name. Otherwise, an empty string is returned.
+ * @param type the type of difference whose name is wanted.
+ * @return the name of the predefined difference type. Otherwise, the empty string.
+ */
+ public static String typeToName(int type) {
+ switch (type) {
+ case ProgramDiffFilter.BYTE_DIFFS:
+ return "BYTE_DIFFS";
+ case ProgramDiffFilter.CODE_UNIT_DIFFS:
+ return "CODE_UNIT_DIFFS";
case ProgramDiffFilter.COMMENT_DIFFS:
return "COMMENT_DIFFS";
case ProgramDiffFilter.EOL_COMMENT_DIFFS:
@@ -226,53 +233,55 @@ public class ProgramDiffFilter {
return "PLATE_COMMENT_DIFFS";
case ProgramDiffFilter.REPEATABLE_COMMENT_DIFFS:
return "REPEATABLE_COMMENT_DIFFS";
- case ProgramDiffFilter.REFERENCE_DIFFS:
- return "REFERENCE_DIFFS";
- case ProgramDiffFilter.USER_DEFINED_DIFFS:
- return "USER_DEFINED_DIFFS";
+ case ProgramDiffFilter.REFERENCE_DIFFS:
+ return "REFERENCE_DIFFS";
+ case ProgramDiffFilter.USER_DEFINED_DIFFS:
+ return "USER_DEFINED_DIFFS";
case ProgramDiffFilter.SYMBOL_DIFFS:
return "SYMBOL_DIFFS";
- case ProgramDiffFilter.EQUATE_DIFFS:
- return "EQUATE_DIFFS";
+ case ProgramDiffFilter.EQUATE_DIFFS:
+ return "EQUATE_DIFFS";
case ProgramDiffFilter.FUNCTION_DIFFS:
return "FUNCTION_DIFFS";
case ProgramDiffFilter.BOOKMARK_DIFFS:
return "BOOKMARK_DIFFS";
- case ProgramDiffFilter.PROGRAM_CONTEXT_DIFFS:
- return "PROGRAM_CONTEXT_DIFFS";
- case ProgramDiffFilter.ALL_DIFFS:
- return "ALL_DIFFS";
+ case ProgramDiffFilter.PROGRAM_CONTEXT_DIFFS:
+ return "PROGRAM_CONTEXT_DIFFS";
+ case ProgramDiffFilter.ALL_DIFFS:
+ return "ALL_DIFFS";
case ProgramDiffFilter.FUNCTION_TAG_DIFFS:
return "FUNCTION_TAG_DIFFS";
- default:
- return "";
- }
- }
+ case ProgramDiffFilter.SOURCE_MAP_DIFFS:
+ return "SOURCE_MAP_DIFFS";
+ default:
+ return "";
+ }
+ }
- /**
- * Determines whether or not this filter is equal to the object that
- * is passed in.
- * @param obj the object to compare this one with.
- * @return true if the filter matches this one.
- */
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof ProgramDiffFilter) {
- return ((ProgramDiffFilter)obj).filterFlags == filterFlags;
- }
- return false;
- }
-
- /**
- * Returns a string representation of the current settings for this filter.
- */
- @Override
- public String toString() {
- StringBuffer buf = new StringBuffer();
- buf.append("ProgramDiffFilter:\n");
- for (int i=0; imerge filter for function tags. */
public static final int FUNCTION_TAGS = 1 << MERGE_FUNCTION_TAGS;
+ /** Internal array index for "source map" filter value. */
+ private static final int MERGE_SOURCE_MAP = 17;
+ /** Indicates the merge filter for source map information. */
+ public static final int SOURCE_MAP = 1 << MERGE_SOURCE_MAP;
+
// NOTE: If you add a new primary type here, make sure to use the
// next available integer and update the NUM_PRIMARY_TYPES.
// ** Also don't forget to add it to ALL below. **
/** The total number of primary merge difference types. */
- private static final int NUM_PRIMARY_TYPES = 17;
+ private static final int NUM_PRIMARY_TYPES = 18;
/** Indicates to merge code unit differences. This includes instructions,
* data, and equates.
@@ -152,12 +157,13 @@ public class ProgramMergeFilter {
public static final int CODE_UNITS = INSTRUCTIONS | DATA;
/** Indicates to merge all comment differences. */
- public static final int COMMENTS = PLATE_COMMENTS | PRE_COMMENTS | EOL_COMMENTS |
- REPEATABLE_COMMENTS | POST_COMMENTS;
+ public static final int COMMENTS =
+ PLATE_COMMENTS | PRE_COMMENTS | EOL_COMMENTS | REPEATABLE_COMMENTS | POST_COMMENTS;
/** Indicates all merge filters for all types of differences. */
- public static final int ALL = PROGRAM_CONTEXT | BYTES | CODE_UNITS | EQUATES | REFERENCES |
- COMMENTS | SYMBOLS | PRIMARY_SYMBOL | BOOKMARKS | PROPERTIES | FUNCTIONS | FUNCTION_TAGS;
+ public static final int ALL =
+ PROGRAM_CONTEXT | BYTES | CODE_UNITS | EQUATES | REFERENCES | COMMENTS | SYMBOLS |
+ PRIMARY_SYMBOL | BOOKMARKS | PROPERTIES | FUNCTIONS | FUNCTION_TAGS | SOURCE_MAP;
/** Array holding the filter value for each of the primary merge difference types. */
private int[] filterFlags = new int[NUM_PRIMARY_TYPES];
@@ -185,7 +191,7 @@ public class ProgramMergeFilter {
}
/** getFilter determines whether or not the specified type of filter is set.
- * Valid types are: BYTES, INSTRUCTIONS, DATA,
+ * Valid types are: BYTES, INSTRUCTIONS, DATA, SOURCE_MAP,
* SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS.
* INVALID is returned if combinations of merge types (e.g. ALL) are
* passed in.
@@ -213,6 +219,7 @@ public class ProgramMergeFilter {
case FUNCTIONS:
case FUNCTION_TAGS:
case EQUATES:
+ case SOURCE_MAP:
int bitPos = 0;
int tmpType = type;
while (bitPos < NUM_PRIMARY_TYPES) {
@@ -234,7 +241,8 @@ public class ProgramMergeFilter {
/** validatePredefinedType determines whether or not the indicated type
* of filter item is a valid predefined type.
* Valid types are: BYTES, INSTRUCTIONS, DATA,
- * SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS, ALL.
+ * SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS,
+ * SOURCE_MAP, ALL.
*
* @param type the type of difference to look for between the programs.
* @return true if this is a pre-defined merge type.
@@ -260,6 +268,7 @@ public class ProgramMergeFilter {
case EQUATES:
case CODE_UNITS:
case COMMENTS:
+ case SOURCE_MAP:
case ALL:
return true;
default:
@@ -316,7 +325,8 @@ public class ProgramMergeFilter {
/** isMergeValidForFilter determines whether or not the MERGE
* filter if valid for the indicated primary merge type.
* Possible types are: BYTES, INSTRUCTIONS, DATA, REFERENCES,
- * SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS, ALL.
+ * SYMBOLS, PRIMARY_SYMBOL, COMMENTS, PROGRAM_CONTEXT, PROPERTIES, BOOKMARKS, FUNCTIONS,
+ * SOURCE_MAP, ALL.
*
* @param type the type of difference to merge between the programs.
* @return true if MERGE
is valid for the merge type.
@@ -325,7 +335,7 @@ public class ProgramMergeFilter {
*/
private boolean isMergeValidForFilter(int type) throws IllegalArgumentException {
switch (type) {
- // The following can be MERGE.
+ // The following can be MERGE.
case PLATE_COMMENTS:
case PRE_COMMENTS:
case EOL_COMMENTS:
@@ -334,7 +344,7 @@ public class ProgramMergeFilter {
case SYMBOLS:
case FUNCTION_TAGS:
return true;
- // The following cannot be MERGE.
+ // The following cannot be MERGE.
case PROGRAM_CONTEXT:
case BYTES:
case INSTRUCTIONS:
@@ -345,6 +355,7 @@ public class ProgramMergeFilter {
case FUNCTIONS:
case EQUATES:
case PRIMARY_SYMBOL:
+ case SOURCE_MAP:
return false;
default:
throw new IllegalArgumentException(
@@ -469,6 +480,8 @@ public class ProgramMergeFilter {
return "COMMENTS";
case ALL:
return "ALL";
+ case SOURCE_MAP:
+ return "SOURCE_MAP";
default:
return "";
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMergeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMergeManager.java
index 909cee56b0..13436edb4f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMergeManager.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramMergeManager.java
@@ -15,10 +15,13 @@
*/
package ghidra.program.util;
+import ghidra.framework.store.LockException;
+import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.Msg;
+import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@@ -285,8 +288,8 @@ public class ProgramMergeManager {
* @throws MemoryAccessException if bytes can't be copied.
* @throws CancelledException if user cancels via the monitor.
*/
- public boolean merge(Address p2Address, TaskMonitor monitor) throws MemoryAccessException,
- CancelledException {
+ public boolean merge(Address p2Address, TaskMonitor monitor)
+ throws MemoryAccessException, CancelledException {
return merge(p2Address, mergeFilter, monitor);
}
@@ -451,6 +454,7 @@ public class ProgramMergeManager {
mergeBookmarks(p1MergeSet, filter, monitor);
mergeProperties(p1MergeSet, filter, monitor);
mergeFunctionTags(p1MergeSet, filter, monitor);
+ mergeSourceMap(p1MergeSet, filter, monitor);
merger.reApplyDuplicateEquates();
String dupEquatesMessage = merger.getDuplicateEquatesInfo();
@@ -569,9 +573,8 @@ public class ProgramMergeManager {
AddressSet byteDiffs2 = null;
ProgramDiffFilter byteDiffFilter = new ProgramDiffFilter(ProgramDiffFilter.BYTE_DIFFS);
if (filter.getFilter(ProgramMergeFilter.BYTES) == ProgramMergeFilter.IGNORE) {
- byteDiffs2 =
- DiffUtility.getCompatibleAddressSet(
- programDiff.getDifferences(byteDiffFilter, monitor), program2);
+ byteDiffs2 = DiffUtility.getCompatibleAddressSet(
+ programDiff.getDifferences(byteDiffFilter, monitor), program2);
}
// ** Equates **
@@ -628,6 +631,46 @@ public class ProgramMergeManager {
}
}
+ /**
+ * Merges source map information from program 2 into program 1.
+ *
+ * Note: This method does not consider unmapped {@link SourceFile}s, i.e., those which are
+ * not associated with any addresses in program 2. In particular, it will not create new
+ * unmapped files in program 1.
+ *
+ * @param p1AddressSet address set in program 1 to receive changes
+ * @param filter merge filter
+ * @param monitor monitor
+ */
+ void mergeSourceMap(AddressSetView p1AddressSet, ProgramMergeFilter filter,
+ TaskMonitor monitor) {
+ int setting = filter.getFilter(ProgramMergeFilter.SOURCE_MAP);
+ if (setting == ProgramMergeFilter.IGNORE) {
+ return;
+ }
+ if (setting == ProgramMergeFilter.MERGE) {
+ throw new IllegalStateException("Cannot merge source map information");
+ }
+ int diffType = ProgramDiffFilter.SOURCE_MAP_DIFFS;
+ try {
+ AddressSetView p1DiffSet =
+ programDiff.getDifferences(new ProgramDiffFilter(diffType), monitor);
+ AddressSet p1MergeSet = p1DiffSet.intersect(p1AddressSet);
+ AddressSet p2MergeSet = DiffUtility.getCompatibleAddressSet(p1MergeSet, program2);
+
+ merger.applySourceMapDifferences(p2MergeSet, setting, monitor);
+
+ }
+ catch (CancelledException e1) {
+ // user cancellation
+ }
+ catch (LockException e) {
+ // GUI prevents you from selecting "REPLACE" if you don't have exclusive access
+ // so we shouldn't get here
+ throw new AssertException("Attempting to merge source map without exclusive access");
+ }
+ }
+
/**
* mergeComments
merges all comments
* in the specified address set from the second program
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/SourceFileUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/util/SourceFileUtils.java
new file mode 100644
index 0000000000..bff9d2c53b
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/SourceFileUtils.java
@@ -0,0 +1,174 @@
+/* ###
+ * 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.util;
+
+import java.net.URI;
+import java.util.HexFormat;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+import ghidra.formats.gfilesystem.FSUtilities;
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.database.sourcemap.SourceFileIdType;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+
+/**
+ * A utility class for creating {@link SourceFile}s from native paths, e.g., windows paths.
+ */
+public class SourceFileUtils {
+
+ private static HexFormat hexFormat = HexFormat.of();
+
+ private SourceFileUtils() {
+ // singleton class
+ }
+
+ /**
+ * Creates a {@link SourceFile} from {@code path} with id type {@link SourceFileIdType#NONE}
+ * and empty identifier. The path will be transformed using
+ * {@link FSUtilities#normalizeNativePath(String)} and then {@link URI#normalize}.
+ *
+ * @param path path
+ * @return source file
+ */
+ public static SourceFile getSourceFileFromPathString(String path) {
+ return getSourceFileFromPathString(path, SourceFileIdType.NONE, null);
+ }
+
+ /**
+ * Creates a {@link SourceFile} from {@code path} with the provided id type and identifier.
+ * The path will be transformed using{@link FSUtilities#normalizeNativePath(String)} and
+ * then {@link URI#normalize}.
+ *
+ * @param path path
+ * @param idType id type
+ * @param identifier identifier
+ * @return source file
+ */
+ public static SourceFile getSourceFileFromPathString(String path, SourceFileIdType idType,
+ byte[] identifier) {
+ String standardized = FSUtilities.normalizeNativePath(path);
+ return new SourceFile(standardized, idType, identifier);
+ }
+
+ /**
+ * Converts a {@code long} value to an byte array of length 8. The most significant byte
+ * of the long will be at position 0 of the resulting array.
+ * @param l long
+ * @return byte array
+ */
+ public static byte[] longToByteArray(long l) {
+ byte[] bytes = new byte[8];
+ BigEndianDataConverter.INSTANCE.putLong(bytes, 0, l);
+ return bytes;
+ }
+
+ /**
+ * Converts a byte array of length 8 to a {@code long} value. The byte at position 0
+ * of the array will be the most significant byte of the resulting long.
+ * @param bytes array to convert
+ * @return long
+ * @throws IllegalArgumentException if bytes.length != 8
+ */
+ public static long byteArrayToLong(byte[] bytes) {
+ if (bytes.length != 8) {
+ throw new IllegalArgumentException("bytes must have length 8");
+ }
+ return BigEndianDataConverter.INSTANCE.getLong(bytes);
+ }
+
+ /**
+ * Converts a {@code String} of hexadecimal character to an array of bytes. An initial "0x"
+ * or "0X" is ignored, as is the case of the digits a-f.
+ * @param hexString String to convert
+ * @return byte array
+ */
+ public static byte[] hexStringToByteArray(String hexString) {
+ if (StringUtils.isBlank(hexString)) {
+ return new byte[0];
+ }
+ if (hexString.startsWith("0x") || hexString.startsWith("0X")) {
+ hexString = hexString.substring(2);
+ }
+ return hexFormat.parseHex(hexString);
+ }
+
+ /**
+ * Converts a byte array to a {@code String} of hexadecimal digits.
+ * @param bytes array to convert
+ * @return string
+ */
+ public static String byteArrayToHexString(byte[] bytes) {
+ if (bytes == null || bytes.length == 0) {
+ return StringUtils.EMPTY;
+ }
+ return hexFormat.formatHex(bytes);
+ }
+
+ /**
+ * Returns a {@link SourceLineBounds} record containing the minimum and maximum mapped line
+ * for {@code sourceFile} in {@code program}.
+ * @param program program
+ * @param sourceFile source file
+ * @return source line bounds or null
+ */
+ public static SourceLineBounds getSourceLineBounds(Program program, SourceFile sourceFile) {
+ List entries =
+ program.getSourceFileManager().getSourceMapEntries(sourceFile, 0, Integer.MAX_VALUE);
+ if (entries.isEmpty()) {
+ return null;
+ }
+ int min = Integer.MAX_VALUE;
+ int max = -1;
+ for (SourceMapEntry entry : entries) {
+ int lineNum = entry.getLineNumber();
+ if (lineNum < min) {
+ min = lineNum;
+ }
+ if (lineNum > max) {
+ max = lineNum;
+ }
+ }
+ return new SourceLineBounds(min, max);
+ }
+
+ /**
+ * A record containing the minimum and maximum mapped line numbers
+ * @param min minimum line number
+ * @param max maximum line number
+ */
+ public static record SourceLineBounds(int min, int max) {
+
+ public SourceLineBounds(int min, int max) {
+ if (min < 0) {
+ throw new IllegalArgumentException("min must be greater than or equal to 0");
+ }
+ if (max < 0) {
+ throw new IllegalArgumentException("max must be greater than or equal to 0");
+ }
+ if (max < min) {
+ throw new IllegalArgumentException("max must be greater than or equal to min");
+ }
+ this.min = min;
+ this.max = max;
+
+ }
+
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/SourceMapFieldFactoryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/SourceMapFieldFactoryTest.java
new file mode 100644
index 0000000000..f1494b2a1f
--- /dev/null
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/SourceMapFieldFactoryTest.java
@@ -0,0 +1,249 @@
+/* ###
+ * 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 static org.junit.Assert.*;
+
+import javax.swing.SwingUtilities;
+
+import org.junit.*;
+
+import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
+import ghidra.framework.options.Options;
+import ghidra.framework.store.LockException;
+import ghidra.program.database.ProgramBuilder;
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressOverflowException;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+import ghidra.program.util.SourceMapFieldLocation;
+import ghidra.test.*;
+
+public class SourceMapFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
+
+ private TestEnv env;
+ private ProgramBuilder builder;
+ private Program program;
+ private Function entry;
+ private Address entryPoint;
+ private CodeBrowserPlugin cb;
+ private Options fieldOptions;
+ private SourceFileManager sourceManager;
+ private static final String SOURCE1_NAME = "test1.c";
+ private static final String SOURCE1_PATH = "/dir1/" + SOURCE1_NAME;
+ private static final String SOURCE2_NAME = "test2.c";
+ private static final String SOURCE2_PATH = "/dir2/dir2/" + SOURCE2_NAME;
+ private SourceFile source1;
+ private SourceFile source2;
+ private static final int ROW = 0;
+ private static final int COLUMN = 0;
+
+ @Before
+ public void setUp() throws Exception {
+ init();
+ entryPoint = program.getAddressFactory().getDefaultAddressSpace().getAddress(0x1006420);
+ entry = program.getFunctionManager().getFunctionAt(entryPoint);
+ env = new TestEnv();
+ env.launchDefaultTool(program);
+ cb = env.getPlugin(CodeBrowserPlugin.class);
+ SourceMapFieldFactory factory = new SourceMapFieldFactory();
+ runSwing(() -> cb.getFormatManager().getCodeUnitFormat().addFactory(factory, ROW, COLUMN));
+ fieldOptions = cb.getFormatManager().getFieldOptions();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ env.dispose();
+ }
+
+ @Test
+ public void testNoSourceInfo() throws Exception {
+ assertNotNull(entry);
+ setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false);
+ assertFalse(cb.goToField(entryPoint, SourceMapFieldFactory.FIELD_NAME, ROW, COLUMN));
+ setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, true);
+ ListingTextField textField = getTextField(entryPoint);
+ assertEquals(1, textField.getNumRows());
+ assertEquals(SourceMapFieldFactory.NO_SOURCE_INFO, textField.getText());
+
+ }
+
+ @Test
+ public void testShowFilename() throws Exception {
+ int txID = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, entryPoint, 2);
+ }
+ finally {
+ program.endTransaction(txID, true);
+ }
+ setBooleanOption(SourceMapFieldFactory.SHOW_FILENAME_ONLY_OPTION_NAME, false);
+ ListingTextField textField = getTextField(entryPoint);
+ assertEquals(1, textField.getNumRows());
+ assertEquals(SOURCE1_PATH + ":1 (2)", textField.getText());
+
+ setBooleanOption(SourceMapFieldFactory.SHOW_FILENAME_ONLY_OPTION_NAME, true);
+ textField = getTextField(entryPoint);
+ assertEquals(1, textField.getNumRows());
+ assertEquals(SOURCE1_NAME + ":1 (2)", textField.getText());
+ }
+
+ @Test
+ public void testShowIdentifier() throws Exception {
+ int txID = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, entryPoint, 2);
+ }
+ finally {
+ program.endTransaction(txID, true);
+ }
+ setBooleanOption(SourceMapFieldFactory.SHOW_IDENTIFIER_OPTION_NAME, false);
+ ListingTextField textField = getTextField(entryPoint);
+ assertEquals(1, textField.getNumRows());
+ assertEquals(SOURCE1_NAME + ":1 (2)", textField.getText());
+
+ setBooleanOption(SourceMapFieldFactory.SHOW_IDENTIFIER_OPTION_NAME, true);
+ textField = getTextField(entryPoint);
+ assertEquals(1, textField.getNumRows());
+ assertEquals(SOURCE1_NAME + ":1 (2) [no id]", textField.getText());
+ }
+
+ @Test
+ public void testShowInfoAtAllAddresses() throws Exception {
+ setBooleanOption(SourceMapFieldFactory.SHOW_FILENAME_ONLY_OPTION_NAME, false);
+
+ Address addr = entryPoint.next();
+ Instruction inst = program.getListing().getInstructionAt(addr);
+ assertEquals(2, inst.getLength());
+ Instruction testInst = inst.getNext();
+ Address testAddr = testInst.getAddress();
+
+ int txID = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, addr, 5);
+ }
+ finally {
+ program.endTransaction(txID, true);
+ }
+ setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, true);
+ ListingTextField textField = getTextField(testAddr);
+ assertEquals(1, textField.getNumRows());
+ assertEquals(SOURCE1_PATH + ":1 (5)", textField.getText());
+
+ setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, false);
+ assertFalse(cb.goToField(testAddr, SourceMapFieldFactory.FIELD_NAME, ROW, COLUMN));
+ }
+
+ @Test
+ public void testOffcutSourceMapEntries() throws Exception {
+ setBooleanOption(SourceMapFieldFactory.SHOW_FILENAME_ONLY_OPTION_NAME, false);
+ Address addr = entryPoint.next();
+ Instruction inst = program.getListing().getInstructionAt(addr);
+ assertEquals(2, inst.getLength());
+ Address testAddr = inst.getAddress().next();
+ int txID = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, testAddr, 1);
+ }
+ finally {
+ program.endTransaction(txID, true);
+ }
+
+ setBooleanOption(SourceMapFieldFactory.SHOW_INFO_AT_ALL_ADDRESSES_OPTION_NAME, true);
+ ListingTextField textField = getTextField(inst.getAddress());
+ assertEquals(1, textField.getNumRows());
+ assertEquals(SOURCE1_PATH + ":1 (1)", textField.getText());
+ assertEquals(SourceMapFieldFactory.OFFCUT_COLOR,
+ textField.getFieldElement(0, 0).getColor(0));
+ }
+
+ @Test
+ public void testMaxNumEntries() throws Exception {
+
+ int txID = program.startTransaction("adding source map entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, entryPoint, 1);
+ sourceManager.addSourceMapEntry(source2, 2, entryPoint, 1);
+ }
+ finally {
+ program.endTransaction(txID, true);
+ }
+ ListingTextField textField = getTextField(entryPoint);
+ assertEquals(2, textField.getNumRows());
+
+ SwingUtilities.invokeAndWait(() -> fieldOptions
+ .setInt(SourceMapFieldFactory.MAX_ENTRIES_PER_ADDRESS_OPTION_NAME, 1));
+ waitForSwing();
+ cb.updateNow();
+
+ textField = getTextField(entryPoint);
+ assertEquals(1, textField.getNumRows());
+ }
+
+ @Test
+ public void testFieldLocationSourceMapEntry() throws AddressOverflowException, LockException {
+ int txID = program.startTransaction("adding source map entries");
+ SourceMapEntry entry1 = null;
+ SourceMapEntry entry2 = null;
+ try {
+ entry1 = sourceManager.addSourceMapEntry(source1, 1, entryPoint, 2);
+ entry2 = sourceManager.addSourceMapEntry(source2, 3, entryPoint, 2);
+ }
+ finally {
+ program.endTransaction(txID, true);
+ }
+ ListingTextField textField = getTextField(entryPoint);
+ FieldFactory fieldFactory = textField.getFieldFactory();
+ SourceMapFieldLocation one =
+ (SourceMapFieldLocation) fieldFactory.getProgramLocation(0, 0, textField);
+ assertEquals(entry1, one.getSourceMapEntry());
+
+ SourceMapFieldLocation two =
+ (SourceMapFieldLocation) fieldFactory.getProgramLocation(1, 0, textField);
+ assertEquals(entry2, two.getSourceMapEntry());
+ }
+
+ private void init() throws Exception {
+ builder = new ClassicSampleX86ProgramBuilder();
+ program = builder.getProgram();
+ sourceManager = program.getSourceFileManager();
+ int txId = program.startTransaction("adding source files");
+ source1 = new SourceFile(SOURCE1_PATH);
+ source2 = new SourceFile(SOURCE2_PATH);
+ try {
+ assertTrue(sourceManager.addSourceFile(source1));
+ assertTrue(sourceManager.addSourceFile(source2));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ private void setBooleanOption(final String name, boolean value) throws Exception {
+ SwingUtilities.invokeAndWait(() -> fieldOptions.setBoolean(name, value));
+ waitForSwing();
+ cb.updateNow();
+ }
+
+ private ListingTextField getTextField(Address address) {
+ assertTrue(cb.goToField(address, SourceMapFieldFactory.FIELD_NAME, ROW, COLUMN));
+ ListingTextField tf = (ListingTextField) cb.getCurrentField();
+ return tf;
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/SourceMapDiffTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/SourceMapDiffTest.java
new file mode 100644
index 0000000000..2c1716599c
--- /dev/null
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/SourceMapDiffTest.java
@@ -0,0 +1,496 @@
+/* ###
+ * 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 static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.junit.*;
+
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.database.sourcemap.SourceFileIdType;
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.mem.MemoryAccessException;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+import ghidra.test.ClassicSampleX86ProgramBuilder;
+import ghidra.util.SourceFileUtils;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+public class SourceMapDiffTest extends AbstractProgramDiffTest {
+
+ private static final String COMMON_FILE = "/common";
+
+ // differing files will have the same path but different identifiers
+ private static final String DIFFERENT = "/different";
+ private static final String TEST_FUNC_ADDR_STRING = "0x01002239";
+
+ private SourceFile common;
+ private SourceFile p1Only;
+ private SourceFile p2Only;
+
+ private SourceFileManager p1Manager;
+ private SourceFileManager p2Manager;
+
+ private List p1Insts;
+ private List p2Insts;
+
+ private AddressSet p1InstBodies;
+ private AddressSet p1DiffInsts;
+
+ @Before
+ public void setUp() throws Exception {
+ programBuilder1 = new ClassicSampleX86ProgramBuilder(false);
+ programBuilder2 = new ClassicSampleX86ProgramBuilder(false);
+
+ p1 = programBuilder1.getProgram();
+ p2 = programBuilder2.getProgram();
+
+ p1Insts = new ArrayList<>();
+ p2Insts = new ArrayList<>();
+
+ p1Manager = p1.getSourceFileManager();
+ p2Manager = p2.getSourceFileManager();
+
+ p1InstBodies = new AddressSet();
+ p1DiffInsts = new AddressSet();
+
+ int p1_txID = p1.startTransaction("setup");
+ int p2_txID = p2.startTransaction("setup");
+
+ common = new SourceFile(COMMON_FILE);
+ byte[] p1Val = SourceFileUtils.longToByteArray(0x11111111);
+ p1Only =
+ new SourceFile(DIFFERENT, SourceFileIdType.TIMESTAMP_64, p1Val);
+ byte[] p2Val = SourceFileUtils.longToByteArray(0x22222222);
+ p2Only =
+ new SourceFile(DIFFERENT, SourceFileIdType.TIMESTAMP_64, p2Val);
+
+ try {
+ p1Manager.addSourceFile(common);
+ p1Manager.addSourceFile(p1Only);
+
+ p2Manager.addSourceFile(common);
+ p2Manager.addSourceFile(p2Only);
+ Address prog1start = p1.getFunctionManager()
+ .getFunctionAt(p1.getAddressFactory().getAddress(TEST_FUNC_ADDR_STRING))
+ .getEntryPoint();
+ InstructionIterator p1Iter = p1.getListing().getInstructions(prog1start, true);
+
+ Address prog2start = p2.getFunctionManager()
+ .getFunctionAt(p2.getAddressFactory().getAddress(TEST_FUNC_ADDR_STRING))
+ .getEntryPoint();
+ InstructionIterator p2Iter = p2.getListing().getInstructions(prog2start, true);
+ /**
+ * 0) no entries
+ * 1) p1 yes, p2 no
+ * 2) no entries
+ * 3) p1 no, p2 yes
+ * 4) no entries
+ * 5) files and lines different
+ * 6) no entries
+ * 7) files different, lines not
+ * 8) no entries
+ * 9) files agree, lines different
+ * 10) no entries
+ * 11) files and lines agree
+ * 12) no entries
+ * 13) p1 two entries two files, p2 one of them
+ * 14) no entries
+ * 15) p2 two entries two files, p1 one of them
+ * 16) no entries
+ * 17) p1 two entries one file, p2 two entries one file, one line number diff
+ * 18) no entries
+ * 19) length difference
+ * 20) no entries
+ * 21) length 0 entry in p1, nothing in p2
+ * 22) no entries
+ * 23) nothing in p1, length 0 entry in p2
+ * 24) no entries
+ * 25) equal length 0 entries
+ * 26) no entries
+ *
+ */
+ // 0
+ Instruction inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 1
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1DiffInsts.add(inst.getAddress());
+ p1Manager.addSourceMapEntry(common, 1, getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 2
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 3
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1DiffInsts.add(inst.getAddress());
+ p1Insts.add(inst);
+ inst = p2Iter.next();
+ p2Manager.addSourceMapEntry(common, 3, getBody(inst));
+ p2Insts.add(inst);
+
+ // 4
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 5
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1DiffInsts.add(inst.getAddress());
+ p1Manager.addSourceMapEntry(p1Only, 51, getBody(inst));
+ p1Insts.add(inst);
+ inst = p2Iter.next();
+ p2Manager.addSourceMapEntry(p2Only, 52, getBody(inst));
+ p2Insts.add(inst);
+
+ // 6
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 7
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1DiffInsts.add(inst.getAddress());
+ p1Manager.addSourceMapEntry(p1Only, 7, getBody(inst));
+ p1Insts.add(inst);
+ inst = p2Iter.next();
+ p2Manager.addSourceMapEntry(p2Only, 7, getBody(inst));
+ p2Insts.add(inst);
+
+ // 8
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 9
+ // XOR EAX,EAX length 2
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1DiffInsts.add(inst.getAddress());
+ p1Manager.addSourceMapEntry(common, 91, getBody(inst));
+ p1Insts.add(inst);
+ inst = p2Iter.next();
+ p2Manager.addSourceMapEntry(common, 92, getBody(inst));
+ p2Insts.add(inst);
+
+ // 10
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 11
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ // files,lines, and lengths agree - not a diff
+ p1Manager.addSourceMapEntry(common, 11, getBody(inst));
+ p1Insts.add(inst);
+ inst = p2Iter.next();
+ p2Manager.addSourceMapEntry(common, 11, getBody(inst));
+ p2Insts.add(inst);
+
+ // 12
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 13
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p1DiffInsts.add(inst.getAddress());
+ p1Manager.addSourceMapEntry(common, 13, getBody(inst));
+ p1Manager.addSourceMapEntry(p1Only, 13, getBody(inst));
+ inst = p2Iter.next();
+ p2Insts.add(inst);
+ p2Manager.addSourceMapEntry(common, 13, getBody(inst));
+
+ // 14
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 15
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p1DiffInsts.add(inst.getAddress());
+ p1Manager.addSourceMapEntry(common, 15, getBody(inst));
+ inst = p2Iter.next();
+ p2Insts.add(inst);
+ p2Manager.addSourceMapEntry(common, 15, getBody(inst));
+ p2Manager.addSourceMapEntry(p2Only, 15, getBody(inst));
+
+ // 16
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 17
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p1DiffInsts.add(inst.getAddress());
+ p1Manager.addSourceMapEntry(common, 17, getBody(inst));
+ p1Manager.addSourceMapEntry(common, 18, getBody(inst));
+ inst = p2Iter.next();
+ p2Insts.add(inst);
+ p2Manager.addSourceMapEntry(common, 17, getBody(inst));
+ p1Manager.addSourceMapEntry(common, 19, getBody(inst));
+
+ // 18
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 19
+ inst = p1Iter.next();
+ // length of this instruction is 2
+ p1InstBodies.add(getBody(inst));
+ p1Manager.addSourceMapEntry(common, 1000, inst.getAddress(), 1);
+ p1Insts.add(inst);
+ p1DiffInsts.add(inst.getAddress());
+ inst = p2Iter.next();
+ p2Manager.addSourceMapEntry(common, 1000, inst.getAddress(), 2);
+ p2Insts.add(inst);
+
+ // 20
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 21
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1DiffInsts.add(inst.getAddress());
+ p1Manager.addSourceMapEntry(p1Only, 1, inst.getAddress(), 0);
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 22
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 23
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1DiffInsts.add(inst.getAddress());
+ p1Insts.add(inst);
+ inst = p2Iter.next();
+ p2Manager.addSourceMapEntry(p2Only, 3, inst.getAddress(), 0);
+ p2Insts.add(inst);
+
+ // 24
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ // 25
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ // files,lines, and lengths agree - not a diff
+ p1Manager.addSourceMapEntry(common, 25, inst.getAddress(), 0);
+ p1Insts.add(inst);
+ inst = p2Iter.next();
+ p2Manager.addSourceMapEntry(common, 25, inst.getAddress(), 0);
+ p2Insts.add(inst);
+
+ // 26
+ inst = p1Iter.next();
+ p1InstBodies.add(getBody(inst));
+ p1Insts.add(inst);
+ p2Insts.add(p2Iter.next());
+
+ }
+
+ finally {
+ p1.endTransaction(p1_txID, true);
+ p2.endTransaction(p2_txID, true);
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (programBuilder1 != null) {
+ programBuilder1.dispose();
+ }
+ if (programBuilder2 != null) {
+ programBuilder2.dispose();
+ }
+ }
+
+ @Test
+ public void simpleDiffTest()
+ throws ProgramConflictException, IllegalArgumentException, CancelledException {
+ assertEquals(p1Insts.size(), p2Insts.size());
+ AddressSet testSet = new AddressSet();
+ p1Insts.forEach(i -> testSet.add(getBody(i)));
+ assertEquals(testSet, p1InstBodies);
+ programDiff = new ProgramDiff(p1, p2, p1InstBodies);
+ programDiff.setFilter(new ProgramDiffFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS));
+ // verify that the differences found by progamDiff.getDifferences are exactly
+ // the differences created in the setUp method
+ assertEquals(p1DiffInsts,
+ programDiff.getDifferences(programDiff.getFilter(), TaskMonitor.DUMMY));
+ }
+
+ @Test
+ public void testReplace()
+ throws ProgramConflictException, MemoryAccessException, CancelledException {
+ ProgramMergeManager programMerge =
+ new ProgramMergeManager(p1, p2, p1InstBodies);
+ ProgramMergeFilter mergeFilter =
+ new ProgramMergeFilter(ProgramMergeFilter.SOURCE_MAP, ProgramMergeFilter.REPLACE);
+
+ // for good measure, verify one of the differences before merge
+ SourceMapEntry p1Info = p1Manager.getSourceMapEntries(p1Insts.get(5).getAddress()).get(0);
+ SourceMapEntry p2Info = p2Manager.getSourceMapEntries(p2Insts.get(5).getAddress()).get(0);
+
+ assertNotEquals(p1Info.getSourceFile(), p2Info.getSourceFile());
+ assertNotEquals(p1Info.getLineNumber(), p2Info.getLineNumber());
+
+ int txId = p1.startTransaction("merging");
+ try {
+ boolean success = programMerge.merge(p1InstBodies, mergeFilter, TaskMonitor.DUMMY);
+ assertTrue(success);
+ }
+ finally {
+ p1.endTransaction(txId, true);
+ }
+
+ // now verify that source map info is the same for all addresses (not just beginnings of
+ // instructions)
+ AddressSet p2InstBodies = new AddressSet();
+ p2Insts.forEach(i -> p2InstBodies.add(getBody(i)));
+ assertEquals(p1InstBodies.getNumAddresses(), p2InstBodies.getNumAddresses());
+ AddressIterator p1Iter = p1InstBodies.getAddresses(true);
+ AddressIterator p2Iter = p2InstBodies.getAddresses(true);
+ while (p1Iter.hasNext()) {
+ Address p1Addr = p1Iter.next();
+ Address p2Addr = p2Iter.next();
+ List p1Entries = p1Manager.getSourceMapEntries(p1Addr);
+ List p2Entries = p2Manager.getSourceMapEntries(p2Addr);
+ assertEquals(p1Entries.size(), p2Entries.size());
+ for (SourceMapEntry p1Entry : p1Entries) {
+ int index = Collections.binarySearch(p2Entries, p1Entry);
+ assertTrue(index >= 0);
+ }
+ }
+ }
+
+ @Test
+ public void testIgnoreFilter()
+ throws ProgramConflictException, MemoryAccessException, CancelledException {
+ ProgramMergeManager programMerge =
+ new ProgramMergeManager(p1, p2, p1InstBodies);
+ ProgramMergeFilter mergeFilter =
+ new ProgramMergeFilter(ProgramMergeFilter.SOURCE_MAP, ProgramMergeFilter.IGNORE);
+
+ // verify one of the differences before merge
+ SourceMapEntry p1Info = p1Manager.getSourceMapEntries(p1Insts.get(5).getAddress()).get(0);
+ SourceMapEntry p2Info = p2Manager.getSourceMapEntries(p2Insts.get(5).getAddress()).get(0);
+
+ assertNotEquals(p1Info.getSourceFile(), p2Info.getSourceFile());
+ assertNotEquals(p1Info.getLineNumber(), p2Info.getLineNumber());
+
+ int txId = p1.startTransaction("merging");
+ try {
+ boolean success = programMerge.merge(p1InstBodies, mergeFilter, TaskMonitor.DUMMY);
+ assertTrue(success);
+ }
+ finally {
+ p1.endTransaction(txId, true);
+ }
+
+ // verify the difference is still there
+ p1Info = p1Manager.getSourceMapEntries(p1Insts.get(5).getAddress()).get(0);
+ p2Info = p2Manager.getSourceMapEntries(p2Insts.get(5).getAddress()).get(0);
+
+ assertNotEquals(p1Info.getSourceFile(), p2Info.getSourceFile());
+ assertNotEquals(p1Info.getLineNumber(), p2Info.getLineNumber());
+
+ }
+
+ @Test
+ public void testIgnoreOverlappingEntriesInDiff()
+ throws ProgramConflictException, IllegalArgumentException, CancelledException {
+ Address p1Inst9Addr = p1.getAddressFactory().getDefaultAddressSpace().getAddress(0x1002257);
+ Instruction p1Inst9 = p1.getListing().getInstructionAt(p1Inst9Addr);
+ assertNotNull(p1Inst9);
+ assertEquals(2, p1Inst9.getLength());
+ assertEquals("XOR EAX,EAX", p1Inst9.toString());
+
+ AddressSet testSet = new AddressSet();
+ testSet.add(getBody(p1Inst9));
+ programDiff = new ProgramDiff(p1, p2, testSet);
+ programDiff.setFilter(new ProgramDiffFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS));
+ //verify that there is a difference at p1Inst9Addr
+ assertEquals(new AddressSet(p1Inst9Addr),
+ programDiff.getDifferences(programDiff.getFilter(), TaskMonitor.DUMMY));
+
+ testSet.clear();
+ testSet.add(p1Inst9Addr.add(1));
+
+ programDiff = new ProgramDiff(p1, p2, testSet);
+ programDiff.setFilter(new ProgramDiffFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS));
+ //verify that no differences are reported, since the beginning of the source map entry
+ //is not in testSet
+ assertTrue(
+ programDiff.getDifferences(programDiff.getFilter(), TaskMonitor.DUMMY).isEmpty());
+ }
+
+ @Test
+ public void testMergeFilterChanged() {
+ ProgramMergeFilter mergeFilter =
+ new ProgramMergeFilter(ProgramMergeFilter.SOURCE_MAP, ProgramMergeFilter.MERGE);
+ // MERGE not valid for source files - should be changed to REPLACE by the ProgramMergeFilter
+ // constructor
+ assertEquals(ProgramMergeFilter.REPLACE,
+ mergeFilter.getFilter(ProgramMergeFilter.SOURCE_MAP));
+ }
+
+ private AddressRange getBody(CodeUnit cu) {
+ return new AddressRangeImpl(cu.getMinAddress(), cu.getMaxAddress());
+ }
+
+}
diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb/ApplyLineNumbers.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb/ApplyLineNumbers.java
index 514594a166..d5b4b2184a 100644
--- a/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb/ApplyLineNumbers.java
+++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/util/bin/format/pdb/ApplyLineNumbers.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,12 +15,16 @@
*/
package ghidra.app.util.bin.format.pdb;
-import java.io.File;
-
import ghidra.app.util.importer.MessageLog;
+import ghidra.framework.store.LockException;
+import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.util.Msg;
+import ghidra.util.SourceFileUtils;
import ghidra.util.task.TaskMonitor;
import ghidra.util.xml.XmlUtilities;
import ghidra.xml.XmlElement;
@@ -38,6 +42,12 @@ class ApplyLineNumbers {
}
void applyTo(TaskMonitor monitor, MessageLog log) {
+ if (!program.hasExclusiveAccess()) {
+ Msg.showWarn(this, null, "Cannot Apply SourceMap Information",
+ "Exclusive access to the program is required to apply source map information");
+ return;
+ }
+ SourceFileManager manager = program.getSourceFileManager();
while (xmlParser.hasNext()) {
if (monitor.isCancelled()) {
return;
@@ -47,11 +57,16 @@ class ApplyLineNumbers {
break;
}
elem = xmlParser.next();//line number start tag
- String sourcefileName = elem.getAttribute("source_file");
+ String sourceFilePath = elem.getAttribute("source_file");
int start = XmlUtilities.parseInt(elem.getAttribute("start"));
int addr = XmlUtilities.parseInt(elem.getAttribute("addr"));
Address address = PdbUtil.reladdr(program, addr);
+ long length = 0;
+ String lengthElem = elem.getAttribute("length");
+ if (lengthElem != null) {
+ length = XmlUtilities.parseLong(lengthElem);
+ }
// The following line was changed from getCodeUnitAt(address) to
// getCodeUnitContaining(address) in order to fix an issue where the PDB associates
// a line number with base part of an instruction instead of the prefix part of an
@@ -63,16 +78,29 @@ class ApplyLineNumbers {
CodeUnit cu = program.getListing().getCodeUnitContaining(address);
if (cu == null) {
log.appendMsg("PDB",
- "Could not apply source code line number (no code unit found at " + address +
- ")");
+ "Skipping source map info (no code unit found at " + address + ")");
+ continue;
}
- else {
- cu.setProperty("Source Path", sourcefileName);
- cu.setProperty("Source File", new File(sourcefileName).getName());
- cu.setProperty("Source Line", start);
+
+ try {
+ SourceFile sourceFile =
+ SourceFileUtils.getSourceFileFromPathString(sourceFilePath);
+ manager.addSourceFile(sourceFile);
+ manager.addSourceMapEntry(sourceFile, start, address, length);
}
- //String comment = sourcefile.getName()+":"+start;
- //setComment(CodeUnit.PRE_COMMENT, program.getListing(), address, comment);
+ catch (LockException e) {
+ throw new AssertionError("LockException after exclusive access verified!");
+ }
+ catch (AddressOverflowException e) {
+ log.appendMsg("PDB", "AddressOverflow for source map info: %s, %d, %s, %d"
+ .formatted(sourceFilePath, start, address.toString(), length));
+ }
+ catch (IllegalArgumentException e) {
+ // thrown by SourceFileManager.addSourceMapEntry if the new entry conflicts
+ // with an existing entry
+ log.appendMsg("PDB", e.getMessage());
+ }
+
elem = xmlParser.next();//line number end tag
}
}
diff --git a/Ghidra/Features/PDB/src/pdb/cpp/iterate.cpp b/Ghidra/Features/PDB/src/pdb/cpp/iterate.cpp
index 9ab81b8a07..e2991297a2 100644
--- a/Ghidra/Features/PDB/src/pdb/cpp/iterate.cpp
+++ b/Ghidra/Features/PDB/src/pdb/cpp/iterate.cpp
@@ -322,9 +322,11 @@ void dumpFunctionLines( IDiaSymbol& symbol, IDiaSession& session )
pLine->get_lineNumber( &start );
DWORD end = 0;
pLine->get_lineNumberEnd( &end );
+ DWORD range_length = 0;
+ pLine->get_length( &range_length );
- printf("%S \n",
- indent(12).c_str(), escapeXmlEntities(wsSourceFileName).c_str(), start, end, addr);
+ printf("%S \n",
+ indent(12).c_str(), escapeXmlEntities(wsSourceFileName).c_str(), start, end, addr, range_length);
}
}
diff --git a/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/Diff.htm b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/Diff.htm
index d3062dd82e..274f22e813 100644
--- a/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/Diff.htm
+++ b/Ghidra/Features/ProgramDiff/src/main/help/help/topics/Diff/Diff.htm
@@ -462,6 +462,9 @@
Function tags differ.
+ Source Map
+ - detect any addresses where the source map information is different.
+
When the Determine Program Differences dialog is initially
displayed, all the Differences check boxes are checked. This indicates
@@ -1305,6 +1308,15 @@
Can be: Ignore, Replace, or Merge.
+
+
+ Source Map
+ |
+
+ Controls whether Source Map differences will be applied.
+ Can be: Ignore (the default) or Replace.
+ |
+
diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffApplySettingsOptionManager.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffApplySettingsOptionManager.java
index 5d492e2f63..0f0d1231a8 100644
--- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffApplySettingsOptionManager.java
+++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffApplySettingsOptionManager.java
@@ -43,35 +43,38 @@ class DiffApplySettingsOptionManager {
private static final int PROPERTIES = 1 << 11;
private static final int FUNCTIONS = 1 << 12;
private static final int FUNCTION_TAGS = 1 << 13;
+ private static final int SOURCE_MAP = 1 << 14;
- private static final String OPTION_PROGRAM_CONTEXT = DIFF_APPLY_SETTINGS_OPTIONS +
- Options.DELIMITER + "Program Context";
- private static final String OPTION_BYTES = DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER +
- "Bytes";
- private static final String OPTION_CODE_UNITS = DIFF_APPLY_SETTINGS_OPTIONS +
- Options.DELIMITER + "Code Units";
- private static final String OPTION_REFERENCES = DIFF_APPLY_SETTINGS_OPTIONS +
- Options.DELIMITER + "References";
- private static final String OPTION_PLATE_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
- Options.DELIMITER + "Plate Comments";
- private static final String OPTION_PRE_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
- Options.DELIMITER + "Pre Comments";
- private static final String OPTION_EOL_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
- Options.DELIMITER + "End Of Line Comments";
- private static final String OPTION_REPEATABLE_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
- Options.DELIMITER + "Repeatable Comments";
- private static final String OPTION_POST_COMMENTS = DIFF_APPLY_SETTINGS_OPTIONS +
- Options.DELIMITER + "Post Comments";
- private static final String OPTION_SYMBOLS = DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER +
- "Labels";
- private static final String OPTION_BOOKMARKS = DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER +
- "Bookmarks";
- private static final String OPTION_PROPERTIES = DIFF_APPLY_SETTINGS_OPTIONS +
- Options.DELIMITER + "Properties";
- private static final String OPTION_FUNCTIONS = DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER +
- "Functions";
+ private static final String OPTION_PROGRAM_CONTEXT =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Program Context";
+ private static final String OPTION_BYTES =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Bytes";
+ private static final String OPTION_CODE_UNITS =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Code Units";
+ private static final String OPTION_REFERENCES =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "References";
+ private static final String OPTION_PLATE_COMMENTS =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Plate Comments";
+ private static final String OPTION_PRE_COMMENTS =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Pre Comments";
+ private static final String OPTION_EOL_COMMENTS =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "End Of Line Comments";
+ private static final String OPTION_REPEATABLE_COMMENTS =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Repeatable Comments";
+ private static final String OPTION_POST_COMMENTS =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Post Comments";
+ private static final String OPTION_SYMBOLS =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Labels";
+ private static final String OPTION_BOOKMARKS =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Bookmarks";
+ private static final String OPTION_PROPERTIES =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Properties";
+ private static final String OPTION_FUNCTIONS =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Functions";
private static final String OPTION_FUNCTION_TAGS =
DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Function Tags";
+ private static final String OPTION_SOURCE_MAP =
+ DIFF_APPLY_SETTINGS_OPTIONS + Options.DELIMITER + "Source Map";
// public static final String MERGE = "Merge";
// public static final String MERGE_SYMBOLS_1 = "Merge";
@@ -79,6 +82,7 @@ class DiffApplySettingsOptionManager {
public static enum REPLACE_CHOICE {
IGNORE("Ignore"), REPLACE("Replace");
+
private String description;
REPLACE_CHOICE(String description) {
@@ -93,6 +97,7 @@ class DiffApplySettingsOptionManager {
public static enum MERGE_CHOICE {
IGNORE("Ignore"), REPLACE("Replace"), MERGE("Merge");
+
private String description;
MERGE_CHOICE(String description) {
@@ -111,6 +116,7 @@ class DiffApplySettingsOptionManager {
REPLACE("Replace"),
MERGE_DONT_SET_PRIMARY("Merge"),
MERGE_AND_SET_PRIMARY("Merge & Set Primary");
+
private String description;
SYMBOL_MERGE_CHOICE(String description) {
@@ -146,10 +152,7 @@ class DiffApplySettingsOptionManager {
options.setOptionsHelpLocation(help);
// Set the help strings
- options.registerOption(
- OPTION_PROGRAM_CONTEXT,
- REPLACE_CHOICE.REPLACE,
- help,
+ options.registerOption(OPTION_PROGRAM_CONTEXT, REPLACE_CHOICE.REPLACE, help,
getReplaceDescription("program context register value",
"program context register values"));
options.registerOption(OPTION_BYTES, REPLACE_CHOICE.REPLACE, help,
@@ -180,6 +183,8 @@ class DiffApplySettingsOptionManager {
getReplaceDescription("function", "functions"));
options.registerOption(OPTION_FUNCTION_TAGS, MERGE_CHOICE.MERGE, help,
getReplaceDescription("function tag", "function tags"));
+ options.registerOption(OPTION_SOURCE_MAP, REPLACE_CHOICE.IGNORE, help,
+ getReplaceDescription("source map", "source map"));
getDefaultApplyFilter();
}
@@ -218,6 +223,7 @@ class DiffApplySettingsOptionManager {
REPLACE_CHOICE properties = options.getEnum(OPTION_PROPERTIES, REPLACE_CHOICE.REPLACE);
REPLACE_CHOICE functions = options.getEnum(OPTION_FUNCTIONS, REPLACE_CHOICE.REPLACE);
MERGE_CHOICE functionTags = options.getEnum(OPTION_FUNCTION_TAGS, MERGE_CHOICE.MERGE);
+ REPLACE_CHOICE sourceMap = options.getEnum(OPTION_SOURCE_MAP, REPLACE_CHOICE.IGNORE);
// Convert the options to a merge filter.
ProgramMergeFilter filter = new ProgramMergeFilter();
@@ -238,6 +244,7 @@ class DiffApplySettingsOptionManager {
filter.setFilter(ProgramMergeFilter.FUNCTION_TAGS, functionTags.ordinal());
filter.setFilter(ProgramMergeFilter.PRIMARY_SYMBOL,
convertSymbolMergeChoiceToReplaceChoiceForPrimay(symbols).ordinal());
+ filter.setFilter(ProgramMergeFilter.SOURCE_MAP, sourceMap.ordinal());
return filter;
}
@@ -293,6 +300,7 @@ class DiffApplySettingsOptionManager {
saveReplaceOption(options, newDefaultApplyFilter, BOOKMARKS);
saveReplaceOption(options, newDefaultApplyFilter, PROPERTIES);
saveReplaceOption(options, newDefaultApplyFilter, FUNCTIONS);
+ saveReplaceOption(options, newDefaultApplyFilter, SOURCE_MAP);
saveMergeOption(options, newDefaultApplyFilter, PLATE_COMMENTS);
saveMergeOption(options, newDefaultApplyFilter, PRE_COMMENTS);
@@ -311,8 +319,9 @@ class DiffApplySettingsOptionManager {
private void saveCodeUnitReplaceOption(Options options, ProgramMergeFilter defaultApplyFilter,
int setting) {
int filter =
- (defaultApplyFilter.getFilter(ProgramMergeFilter.INSTRUCTIONS) >= defaultApplyFilter.getFilter(ProgramMergeFilter.DATA)) ? ProgramMergeFilter.INSTRUCTIONS
- : ProgramMergeFilter.DATA;
+ (defaultApplyFilter.getFilter(ProgramMergeFilter.INSTRUCTIONS) >= defaultApplyFilter
+ .getFilter(ProgramMergeFilter.DATA)) ? ProgramMergeFilter.INSTRUCTIONS
+ : ProgramMergeFilter.DATA;
REPLACE_CHOICE defaultSetting = REPLACE_CHOICE.REPLACE;
REPLACE_CHOICE optionSetting = options.getEnum(getOptionName(setting), defaultSetting);
REPLACE_CHOICE diffSetting = convertTypeToReplaceEnum(defaultApplyFilter, filter);
@@ -332,7 +341,8 @@ class DiffApplySettingsOptionManager {
}
}
- private void saveMergeOption(Options options, ProgramMergeFilter defaultApplyFilter, int setting) {
+ private void saveMergeOption(Options options, ProgramMergeFilter defaultApplyFilter,
+ int setting) {
MERGE_CHOICE defaultSetting = MERGE_CHOICE.MERGE;
MERGE_CHOICE optionSetting = options.getEnum(getOptionName(setting), defaultSetting);
MERGE_CHOICE diffSetting =
@@ -507,7 +517,8 @@ class DiffApplySettingsOptionManager {
* @param type the ProgramMergeFilter filter type
* @return the StringEnum
*/
- private REPLACE_CHOICE convertTypeToReplaceEnum(ProgramMergeFilter defaultApplyFilter, int type) {
+ private REPLACE_CHOICE convertTypeToReplaceEnum(ProgramMergeFilter defaultApplyFilter,
+ int type) {
int filter = defaultApplyFilter.getFilter(type);
return REPLACE_CHOICE.values()[filter];
}
diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffApplySettingsProvider.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffApplySettingsProvider.java
index 3473c74320..2521916e21 100644
--- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffApplySettingsProvider.java
+++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffApplySettingsProvider.java
@@ -34,6 +34,7 @@ import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.util.ProgramMergeFilter;
import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
/**
* The DiffSettingsDialog is used to change the types of differences currently
@@ -63,6 +64,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
private Choice propertiesCB;
private Choice functionsCB;
private Choice functionTagsCB;
+ private Choice sourceMapCB;
private int applyProgramContext;
private int applyBytes;
@@ -78,6 +80,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
private int applyProperties;
private int applyFunctions;
private int applyFunctionTags;
+ private int applySourceMap;
private int replacePrimary;
private ProgramMergeFilter applyFilter;
@@ -107,8 +110,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
public void addActions() {
plugin.getTool()
- .addLocalAction(this,
- new SaveApplySettingsAction(this, plugin.applySettingsMgr));
+ .addLocalAction(this, new SaveApplySettingsAction(this, plugin.applySettingsMgr));
plugin.getTool().addLocalAction(this, new DiffIgnoreAllAction(this));
plugin.getTool().addLocalAction(this, new DiffReplaceAllAction(this));
plugin.getTool().addLocalAction(this, new DiffMergeAllAction(this));
@@ -152,7 +154,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(refsCB);
- plateCommentsCB = new Choice("Plate Comments", true);
+ plateCommentsCB = new Choice("Comments, Plate", true);
plateCommentsCB.addActionListener(e -> {
applyPlateComments = plateCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.PLATE_COMMENTS, applyPlateComments);
@@ -160,7 +162,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(plateCommentsCB);
- preCommentsCB = new Choice("Pre Comments", true);
+ preCommentsCB = new Choice("Comments, Pre", true);
preCommentsCB.addActionListener(e -> {
applyPreComments = preCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.PRE_COMMENTS, applyPreComments);
@@ -168,7 +170,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(preCommentsCB);
- eolCommentsCB = new Choice("Eol Comments", true);
+ eolCommentsCB = new Choice("Comments, EOL", true);
eolCommentsCB.addActionListener(e -> {
applyEolComments = eolCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.EOL_COMMENTS, applyEolComments);
@@ -176,7 +178,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(eolCommentsCB);
- repeatableCommentsCB = new Choice("Repeatable Comments", true);
+ repeatableCommentsCB = new Choice("Comments, Repeatable", true);
repeatableCommentsCB.addActionListener(e -> {
applyRepeatableComments = repeatableCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.REPEATABLE_COMMENTS, applyRepeatableComments);
@@ -184,7 +186,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(repeatableCommentsCB);
- postCommentsCB = new Choice("Post Comments", true);
+ postCommentsCB = new Choice("Comments, Post", true);
postCommentsCB.addActionListener(e -> {
applyPostComments = postCommentsCB.getSelectedIndex();
applyFilter.setFilter(ProgramMergeFilter.POST_COMMENTS, applyPostComments);
@@ -240,6 +242,25 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
});
choices.add(functionTagsCB);
+ sourceMapCB = new Choice("Source Map", false);
+ if (!plugin.getFirstProgram().hasExclusiveAccess()) {
+ sourceMapCB.setSelectedIndex(ProgramMergeFilter.IGNORE);
+ }
+ sourceMapCB.addActionListener(e -> {
+ applySourceMap = sourceMapCB.getSelectedIndex();
+ if (!plugin.getFirstProgram().hasExclusiveAccess()) {
+ if (applySourceMap != ProgramMergeFilter.IGNORE) {
+ Msg.showWarn(this, null, "Exclusive Access Required",
+ "Exclusive access required to change source map information");
+ sourceMapCB.setSelectedIndex(ProgramMergeFilter.IGNORE);
+ applySourceMap = ProgramMergeFilter.IGNORE;
+ }
+ }
+ applyFilter.setFilter(ProgramMergeFilter.SOURCE_MAP, applySourceMap);
+ applyFilterChanged();
+ });
+ choices.add(sourceMapCB);
+
int maxLabelWidth = 0;
int maxComboWidth = 0;
for (Choice choice : choices) {
@@ -294,6 +315,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
propertiesCB.setSelectedIndex(applyProperties);
functionsCB.setSelectedIndex(applyFunctions);
functionTagsCB.setSelectedIndex(applyFunctionTags);
+ sourceMapCB.setSelectedIndex(applySourceMap);
}
finally {
adjustingApplyFilter = false;
@@ -340,6 +362,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
applyFunctions = applyFilter.getFilter(ProgramMergeFilter.FUNCTIONS);
applyFunctionTags = applyFilter.getFilter(ProgramMergeFilter.FUNCTION_TAGS);
replacePrimary = applyFilter.getFilter(ProgramMergeFilter.PRIMARY_SYMBOL);
+ applySourceMap = applyFilter.getFilter(ProgramMergeFilter.SOURCE_MAP);
adjustApplyFilter();
}
@@ -402,11 +425,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
new GComboBox<>(allowMerge ? DiffApplySettingsOptionManager.MERGE_CHOICE.values()
: DiffApplySettingsOptionManager.REPLACE_CHOICE.values());
applyCB.setName(type + " Diff Apply CB");
- String typeName = type;
- if (typeName.endsWith(" Comments")) {
- typeName = "Comments, " + typeName.substring(0, typeName.length() - 9);
- }
- label = new GDLabel(" " + typeName + " ");
+ label = new GDLabel(" " + type + " ");
label.setHorizontalAlignment(SwingConstants.RIGHT);
add(applyCB, BorderLayout.EAST);
add(label, BorderLayout.CENTER);
@@ -438,7 +457,7 @@ public class DiffApplySettingsProvider extends ComponentProviderAdapter {
@Override
public int compareTo(Choice o) {
- return label.toString().compareTo(o.label.toString());
+ return type.compareTo(o.type);
}
}
diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ExecuteDiffDialog.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ExecuteDiffDialog.java
index 6cf6559f31..202193c8a7 100644
--- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ExecuteDiffDialog.java
+++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ExecuteDiffDialog.java
@@ -52,6 +52,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
private JCheckBox diffBookmarksCB;
private JCheckBox diffPropertiesCB;
private JCheckBox diffFunctionsCB;
+ private JCheckBox diffSourceMapCB;
private JButton selectAllButton = new JButton("Select All");
private JButton deselectAllButton = new JButton("Deselect All");
private JCheckBox limitToSelectionCB;
@@ -66,6 +67,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
private boolean diffBookmarks;
private boolean diffProperties;
private boolean diffFunctions;
+ private boolean diffSourceMap;
private ProgramDiffFilter diffFilter;
private JPanel diffPanel;
@@ -191,9 +193,8 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
*/
private JPanel createDiffFilterPanel() {
JPanel checkBoxPanel = new JPanel();
- checkBoxPanel.setToolTipText(
- "Check the types of differences between the two " +
- "programs that you want detected and highlighted.");
+ checkBoxPanel.setToolTipText("Check the types of differences between the two " +
+ "programs that you want detected and highlighted.");
createBytesCheckBox();
createLabelsCheckBox();
@@ -204,17 +205,19 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
createBookmarksCheckBox();
createPropertiesCheckBox();
createFunctionsCheckBox();
+ createSourceMapCheckBox();
- checkBoxPanel.setLayout(new GridLayout(3, 3, 5, 0));
- checkBoxPanel.add(diffBytesCB);
- checkBoxPanel.add(diffLabelsCB);
- checkBoxPanel.add(diffCodeUnitsCB);
- checkBoxPanel.add(diffReferencesCB);
- checkBoxPanel.add(diffProgramContextCB);
- checkBoxPanel.add(diffCommentsCB);
+ checkBoxPanel.setLayout(new GridLayout(2, 5, 5, 0));
checkBoxPanel.add(diffBookmarksCB);
- checkBoxPanel.add(diffPropertiesCB);
+ checkBoxPanel.add(diffBytesCB);
+ checkBoxPanel.add(diffCodeUnitsCB);
+ checkBoxPanel.add(diffCommentsCB);
checkBoxPanel.add(diffFunctionsCB);
+ checkBoxPanel.add(diffLabelsCB);
+ checkBoxPanel.add(diffProgramContextCB);
+ checkBoxPanel.add(diffPropertiesCB);
+ checkBoxPanel.add(diffReferencesCB);
+ checkBoxPanel.add(diffSourceMapCB);
JPanel buttonPanel = new JPanel();
createSelectAllButton();
@@ -254,8 +257,8 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
private void createCodeUnitsCheckBox() {
diffCodeUnitsCB = new GCheckBox("Code Units", diffCodeUnits);
diffCodeUnitsCB.setName("CodeUnitsDiffCB");
- diffCodeUnitsCB.setToolTipText(
- "Highlight the instruction, data, " + "and equate differences.");
+ diffCodeUnitsCB
+ .setToolTipText("Highlight the instruction, data, " + "and equate differences.");
diffCodeUnitsCB.addItemListener(event -> {
diffCodeUnits = (event.getStateChange() == ItemEvent.SELECTED);
diffFilter.setFilter(ProgramDiffFilter.CODE_UNIT_DIFFS, diffCodeUnits);
@@ -334,6 +337,17 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
});
}
+ private void createSourceMapCheckBox() {
+ diffSourceMapCB = new GCheckBox("Source Map", diffSourceMap);
+ diffSourceMapCB.setName("SourceMapDiffCB");
+ diffSourceMapCB.setToolTipText("Highlight Source Map Differences");
+ diffSourceMapCB.addItemListener(event -> {
+ diffSourceMap = (event.getStateChange() == ItemEvent.SELECTED);
+ diffFilter.setFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS, diffSourceMap);
+ clearStatusText();
+ });
+ }
+
private void createSelectAllButton() {
selectAllButton.addActionListener(e -> setSelectAll(true));
selectAllButton.setMnemonic('S');
@@ -354,6 +368,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
diffBookmarksCB.setSelected(selected);
diffPropertiesCB.setSelected(selected);
diffFunctionsCB.setSelected(selected);
+ diffSourceMapCB.setSelected(selected);
}
private void adjustDiffFilter() {
@@ -366,7 +381,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
diffBookmarksCB.setSelected(diffBookmarks);
diffPropertiesCB.setSelected(diffProperties);
diffFunctionsCB.setSelected(diffFunctions);
-
+ diffSourceMapCB.setSelected(diffSourceMap);
}
void setPgmContextEnabled(boolean enable) {
@@ -402,6 +417,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
diffBookmarks = diffFilter.getFilter(ProgramDiffFilter.BOOKMARK_DIFFS);
diffProperties = diffFilter.getFilter(ProgramDiffFilter.USER_DEFINED_DIFFS);
diffFunctions = diffFilter.getFilter(ProgramDiffFilter.FUNCTION_DIFFS);
+ diffSourceMap = diffFilter.getFilter(ProgramDiffFilter.SOURCE_MAP_DIFFS);
adjustDiffFilter();
}
@@ -469,7 +485,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
*/
boolean hasDiffSelection() {
return (diffBytes || diffLabels || diffCodeUnits || diffProgramContext || diffReferences ||
- diffComments || diffBookmarks || diffProperties || diffFunctions);
+ diffComments || diffBookmarks || diffProperties || diffFunctions || diffSourceMap);
}
/**
@@ -478,7 +494,7 @@ public class ExecuteDiffDialog extends ReusableDialogComponentProvider {
boolean isMarkingAllDiffs() {
return (diffBytes && diffLabels && diffCodeUnits &&
((!pgmContextEnabled) || diffProgramContext) && diffReferences && diffComments &&
- diffBookmarks && diffProperties && diffFunctions);
+ diffBookmarks && diffProperties && diffFunctions && diffSourceMap);
}
}
diff --git a/Ghidra/Framework/DB/src/main/java/db/RecordIterator.java b/Ghidra/Framework/DB/src/main/java/db/RecordIterator.java
index 45550f30b7..ad20faccd0 100644
--- a/Ghidra/Framework/DB/src/main/java/db/RecordIterator.java
+++ b/Ghidra/Framework/DB/src/main/java/db/RecordIterator.java
@@ -22,31 +22,31 @@ import java.io.IOException;
* data records within a table.
*/
public interface RecordIterator {
-
+
/**
* Return true if a Record is available in the forward direction.
* @throws IOException thrown if an IO error occurs
*/
public boolean hasNext() throws IOException;
-
+
/**
* Return true if a Record is available in the reverse direction
* @throws IOException thrown if an IO error occurs
*/
public boolean hasPrevious() throws IOException;
-
+
/**
- * Return the nexy Record or null if one is not available.
+ * Return the next Record or null if one is not available.
* @throws IOException thrown if an IO error occurs
*/
public DBRecord next() throws IOException;
-
+
/**
* Return the previous Record or null if one is not available.
* @throws IOException thrown if an IO error occurs
*/
public DBRecord previous() throws IOException;
-
+
/**
* Delete the last Record read via the next or previous methods.
* @return true if record was successfully deleted.
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java
index 9503e1c095..bcf87805aa 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.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.
@@ -44,6 +44,7 @@ import ghidra.program.database.properties.DBPropertyMapManager;
import ghidra.program.database.references.ReferenceDBManager;
import ghidra.program.database.register.ProgramRegisterContextDB;
import ghidra.program.database.reloc.RelocationManager;
+import ghidra.program.database.sourcemap.SourceFileManagerDB;
import ghidra.program.database.symbol.*;
import ghidra.program.database.util.AddressSetPropertyMapDB;
import ghidra.program.model.address.*;
@@ -112,8 +113,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
* unused flag bits.
* 19-Oct-2023 - version 28 Revised overlay address space table and eliminated min/max.
* Multiple blocks are permitted within a single overlay space.
+ * 13-Dec-2024 - version 29 Added source file manager.
*/
- static final int DB_VERSION = 28;
+ static final int DB_VERSION = 29;
/**
* UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION anytime the
@@ -184,8 +186,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
private static final int PROPERTY_MGR = 11;
private static final int TREE_MGR = 12;
private static final int RELOC_MGR = 13;
+ private static final int SOURCE_FILE_MGR = 14;
- private static final int NUM_MANAGERS = 14;
+ private static final int NUM_MANAGERS = 15;
private ManagerDB[] managers = new ManagerDB[NUM_MANAGERS];
private OldFunctionManager oldFunctionMgr;
@@ -613,6 +616,11 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
return (RelocationManager) managers[RELOC_MGR];
}
+ @Override
+ public SourceFileManagerDB getSourceFileManager() {
+ return (SourceFileManagerDB) managers[SOURCE_FILE_MGR];
+ }
+
@Override
public String getCompiler() {
String compiler = null;
@@ -1793,6 +1801,14 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
versionExc = e.combine(versionExc);
}
+ try {
+ managers[SOURCE_FILE_MGR] =
+ new SourceFileManagerDB(dbh, addrMap, openMode, lock, monitor);
+ }
+ catch (VersionException e) {
+ versionExc = e.combine(versionExc);
+ }
+
monitor.checkCancelled();
return versionExc;
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFile.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFile.java
new file mode 100644
index 0000000000..2a435d800b
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFile.java
@@ -0,0 +1,261 @@
+/* ###
+ * 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.database.sourcemap;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.HexFormat;
+
+import org.apache.commons.lang3.StringUtils;
+
+import ghidra.util.BigEndianDataConverter;
+
+/**
+ * A SourceFile is an immutable object representing a source file. It contains an
+ * absolute path along with an optional {@link SourceFileIdType} and identifier.
+ * For example, if the id type is {@link SourceFileIdType#MD5}, the identifier would
+ * be the md5 sum of the source file (stored as a byte array).
+ *
+ * Note: path parameters are assumed to be absolute file paths with forward slashes as the
+ * separator. For other cases, e.g. windows paths, consider the static convenience methods in
+ * the {@code SourceFileUtils} class.
+ *
+ * Note: you can use {@code SourceFileUtils.hexStringToByteArray} to convert hex Strings to byte
+ * arrays. You can use {@code SourceFileUtils.longToByteArray} to convert long values to the
+ * appropriate byte arrays.
+ */
+public final class SourceFile implements Comparable {
+
+ private static final String FILE_SCHEME = "file";
+ private static HexFormat hexFormat = HexFormat.of();
+ private final String path;
+ private final String filename;
+ private final SourceFileIdType idType;
+ private final byte[] identifier;
+ private final int hash;
+ private final String idDisplayString;
+
+
+ /**
+ * Constructor requiring only a path. The path will be normalized (see {@link URI#normalize})
+ * The id type will be set to {@code SourceFileIdType.NONE} and the identifier will
+ * be set to an array of length 0.
+ *
+ * @param path path
+ */
+ public SourceFile(String path) {
+ this(path, SourceFileIdType.NONE, null, true);
+ }
+
+ /**
+ * Constructor. The path will be normalized (see {@link URI#normalize}).
+ *
+ * Note: if {@code type} is {@code SourceFileIdType.NONE}, the {@code identifier}
+ * parameter is ignored.
+ *
+ * Note: use {@code SourceFileUtils.longToByteArray} to convert a {@code long} value
+ * to the appropriate {@code byte} array.
+ * @param path path
+ * @param type id type
+ * @param identifier id
+ */
+ public SourceFile(String path, SourceFileIdType type, byte[] identifier) {
+ this(path, type, identifier, true);
+ }
+
+ /**
+ * Constructor. The path will be normalized (see {@link URI#normalize}).
+ *
+ * Note: if {@code type} is {@code SourceFileIdType.NONE}, the {@code identifier}
+ * parameter is ignored.
+ *
+ * IMPORTANT: only pass {@code false} as {@code validate} parameter if you are certain that
+ * validation can be skipped, e.g., you are creating a SourceFile from information
+ * read out of the database which was validated at insertion.
+ * @param pathToValidate path
+ * @param type sourcefile id type
+ * @param identifier identifier
+ * @param validate true if params should be validated
+ */
+ SourceFile(String pathToValidate, SourceFileIdType type, byte[] identifier, boolean validate) {
+ if (validate) {
+ if (StringUtils.isBlank(pathToValidate)) {
+ throw new IllegalArgumentException("pathToValidate cannot be null or blank");
+ }
+ try {
+ URI uri = new URI(FILE_SCHEME, null, pathToValidate, null).normalize();
+ path = uri.getPath();
+ if (path.endsWith("/")) {
+ throw new IllegalArgumentException(
+ "SourceFile URI must represent a file (not a directory)");
+ }
+ }
+ catch (URISyntaxException e) {
+ throw new IllegalArgumentException("path not valid: " + e.getMessage());
+ }
+ }
+ else {
+ path = pathToValidate;
+ }
+ this.idType = type;
+ filename = path.substring(path.lastIndexOf("/") + 1);
+ this.identifier = validateAndCopyIdentifier(identifier);
+ hash = computeHashcode();
+ idDisplayString = computeIdDisplayString();
+ }
+
+ /**
+ * Returns a file URI for this SourceFile.
+ * @return uri
+ */
+ public URI getUri() {
+ try {
+ return new URI(FILE_SCHEME, null, path, null);
+ }
+ catch (URISyntaxException e) {
+ throw new AssertionError("URISyntaxException on validated path");
+ }
+ }
+
+ /**
+ * Returns the path
+ * @return path
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Returns the filename
+ * @return filename
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Returns the source file identifier type
+ * @return id type
+ */
+ public SourceFileIdType getIdType() {
+ return idType;
+ }
+
+ /**
+ * Returns (a copy of) the identifier
+ * @return identifier
+ */
+ public byte[] getIdentifier() {
+ byte[] copy = new byte[identifier.length];
+ System.arraycopy(identifier, 0, copy, 0, identifier.length);
+ return copy;
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof SourceFile otherFile)) {
+ return false;
+ }
+ if (!path.equals(otherFile.path)) {
+ return false;
+ }
+ if (!idType.equals(otherFile.idType)) {
+ return false;
+ }
+ return Arrays.equals(identifier, otherFile.identifier);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(path);
+ if (idType.equals(SourceFileIdType.NONE)) {
+ return sb.toString();
+ }
+ sb.append(" [");
+ sb.append(idType.name());
+ sb.append("=");
+ sb.append(getIdAsString());
+ sb.append("]");
+ return sb.toString();
+ }
+
+ @Override
+ public int compareTo(SourceFile sourceFile) {
+ int comp = path.compareTo(sourceFile.path);
+ if (comp != 0) {
+ return comp;
+ }
+ comp = idType.compareTo(sourceFile.idType);
+ if (comp != 0) {
+ return comp;
+ }
+ return Arrays.compare(identifier, sourceFile.identifier);
+ }
+
+ /**
+ * Returns a String representation of the identifier
+ * @return id display string
+ */
+ public String getIdAsString() {
+ return idDisplayString;
+ }
+
+ // immutable object - compute hash and cache
+ private int computeHashcode() {
+ int result = path.hashCode();
+ result = 31 * result + idType.hashCode();
+ result = 31 * result + Arrays.hashCode(identifier);
+ return result;
+ }
+
+ private byte[] validateAndCopyIdentifier(byte[] array) {
+ if (array == null || idType == SourceFileIdType.NONE) {
+ array = new byte[0];
+ }
+ if (array.length > SourceFileIdType.MAX_LENGTH) {
+ throw new IllegalArgumentException(
+ "identifier array too long; max is " + SourceFileIdType.MAX_LENGTH);
+ }
+ if (idType.getByteLength() != 0 && idType.getByteLength() != array.length) {
+ throw new IllegalArgumentException(
+ "identifier array has wrong length for " + idType.name());
+ }
+ byte[] copy = new byte[array.length];
+ System.arraycopy(array, 0, copy, 0, array.length);
+ return copy;
+ }
+
+ private String computeIdDisplayString() {
+ switch (idType) {
+ case NONE:
+ return StringUtils.EMPTY;
+ case TIMESTAMP_64:
+ return Instant.ofEpochMilli(BigEndianDataConverter.INSTANCE.getLong(identifier))
+ .toString();
+ default:
+ return hexFormat.formatHex(identifier);
+ }
+ }
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileAdapter.java
new file mode 100644
index 0000000000..ae1642a350
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileAdapter.java
@@ -0,0 +1,90 @@
+/* ###
+ * 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.database.sourcemap;
+
+import java.io.IOException;
+
+import db.*;
+import ghidra.framework.data.OpenMode;
+import ghidra.util.exception.VersionException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * Base class for adapters to access the Source File table. The table has one column, which stores
+ * the path of the source file (as a String).
+ */
+abstract class SourceFileAdapter {
+
+ static final String TABLE_NAME = "SourceFiles";
+ static final int PATH_COL = SourceFileAdapterV0.V0_PATH_COL;
+ static final int ID_TYPE_COL = SourceFileAdapterV0.V0_ID_TYPE_COL;
+ static final int ID_COL = SourceFileAdapterV0.V0_ID_COL;
+
+ /**
+ * Creates an adapter for the source file table.
+ * @param handle database handle
+ * @param openMode open mode
+ * @param monitor task monitor
+ * @return adapter for table
+ * @throws VersionException if version incompatible
+ */
+ static SourceFileAdapter getAdapter(DBHandle handle, OpenMode openMode, TaskMonitor monitor)
+ throws VersionException {
+ return new SourceFileAdapterV0(handle, openMode);
+ }
+
+ /**
+ * Returns a {@link RecordIterator} for this table.
+ * @return record iterator
+ * @throws IOException on db error
+ */
+ abstract RecordIterator getRecords() throws IOException;
+
+ /**
+ * Returns the {@link DBRecord} corresponding to {@code sourceFile}, or {@code null} if
+ * no such record exists.
+ * @param sourceFile source file
+ * @return record or null
+ * @throws IOException on db error
+ */
+ abstract DBRecord getRecord(SourceFile sourceFile) throws IOException;
+
+ /**
+ * Returns the {@link DBRecord} with key {@code id}, or {@code null} if no such record exists.
+ * @param id id
+ * @return record or null
+ * @throws IOException on db error
+ */
+ abstract DBRecord getRecord(long id) throws IOException;
+
+ /**
+ * Creates a {@link DBRecord} for {@link SourceFile} {@code sourceFile}. If a record for
+ * that source file already exists, the existing record is returned.
+ * @param sourceFile source file
+ * @return db record
+ * @throws IOException on db error
+ */
+ abstract DBRecord createSourceFileRecord(SourceFile sourceFile) throws IOException;
+
+ /**
+ * Deletes the record with id {@code id} from the database.
+ * @param id id to delete
+ * @return true if deleted successfully
+ * @throws IOException on database error
+ */
+ abstract boolean removeSourceFileRecord(long id) throws IOException;
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileAdapterV0.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileAdapterV0.java
new file mode 100644
index 0000000000..b8be950e7b
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileAdapterV0.java
@@ -0,0 +1,149 @@
+/* ###
+ * 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.database.sourcemap;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import db.*;
+import ghidra.framework.data.OpenMode;
+import ghidra.program.database.util.EmptyRecordIterator;
+import ghidra.util.exception.VersionException;
+
+/**
+ * Initial version of {@link SourceFileAdapter}.
+ */
+class SourceFileAdapterV0 extends SourceFileAdapter implements DBListener {
+
+ final static int SCHEMA_VERSION = 0;
+ static final int V0_PATH_COL = 0;
+ static final int V0_ID_TYPE_COL = 1;
+ static final int V0_ID_COL = 2;
+
+ private final static Schema V0_SCHEMA = new Schema(SCHEMA_VERSION, "ID",
+ new Field[] { StringField.INSTANCE, ByteField.INSTANCE, BinaryField.INSTANCE },
+ new String[] { "Path", "IdType", "Identifier" }, new int[] { V0_PATH_COL });
+
+ private Table table; // lazy creation, null if empty
+ private final DBHandle dbHandle;
+ private static final int[] INDEXED_COLUMNS = new int[] { V0_PATH_COL };
+
+ SourceFileAdapterV0(DBHandle dbHandle, OpenMode openMode) throws VersionException {
+ this.dbHandle = dbHandle;
+
+ // As in FunctionTagAdapterV0, we need to add this as a database listener.
+ // Since the table is created lazily, undoing a transaction which (for example) caused
+ // the table to be created can leave the table in a bad state.
+ // The implementation of dbRestored(DBHandle) solves this issue.
+ this.dbHandle.addListener(this);
+
+ if (!openMode.equals(OpenMode.CREATE)) {
+ table = dbHandle.getTable(TABLE_NAME);
+ if (table == null) {
+ return; // perform lazy table creation
+ }
+ int version = table.getSchema().getVersion();
+ if (version != SCHEMA_VERSION) {
+ throw new VersionException(VersionException.NEWER_VERSION, false);
+ }
+ }
+ }
+
+ @Override
+ public void dbRestored(DBHandle dbh) {
+ table = dbh.getTable(TABLE_NAME);
+ }
+
+ @Override
+ public void dbClosed(DBHandle dbh) {
+ // nothing to do
+ }
+
+ @Override
+ public void tableDeleted(DBHandle dbh, Table deletedTable) {
+ // nothing to do
+ }
+
+ @Override
+ public void tableAdded(DBHandle dbh, Table addedTable) {
+ // nothing to do
+ }
+
+ @Override
+ RecordIterator getRecords() throws IOException {
+ if (table == null) {
+ return new EmptyRecordIterator();
+ }
+ return table.iterator();
+ }
+
+ @Override
+ DBRecord getRecord(long id) throws IOException {
+ if (table == null) {
+ return null;
+ }
+ return table.getRecord(id);
+ }
+
+ @Override
+ DBRecord getRecord(SourceFile sourceFile) throws IOException {
+ if (table == null) {
+ return null;
+ }
+ StringField field = new StringField(sourceFile.getPath());
+ RecordIterator iter = table.indexIterator(V0_PATH_COL, field, field, true);
+ while (iter.hasNext()) {
+ DBRecord rec = iter.next();
+ if (rec.getByteValue(V0_ID_TYPE_COL) != sourceFile.getIdType().getIndex()) {
+ continue;
+ }
+ if (Arrays.equals(sourceFile.getIdentifier(), rec.getBinaryData(V0_ID_COL))) {
+ return rec;
+ }
+ }
+ return null;
+
+ }
+
+ @Override
+ DBRecord createSourceFileRecord(SourceFile sourceFile) throws IOException {
+ DBRecord rec = getRecord(sourceFile);
+ if (rec == null) {
+ rec = V0_SCHEMA.createRecord(getTable().getKey());
+ rec.setString(V0_PATH_COL, sourceFile.getPath());
+ rec.setByteValue(V0_ID_TYPE_COL, sourceFile.getIdType().getIndex());
+ rec.setBinaryData(V0_ID_COL, sourceFile.getIdentifier());
+ getTable().putRecord(rec);
+ }
+ return rec;
+ }
+
+ @Override
+ boolean removeSourceFileRecord(long id) throws IOException {
+ if (table != null) {
+ return table.deleteRecord(id);
+ }
+ return false;
+ }
+
+ private Table getTable() throws IOException {
+ if (table == null) {
+ table = dbHandle.createTable(TABLE_NAME, V0_SCHEMA, INDEXED_COLUMNS);
+ }
+ return table;
+ }
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileIdType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileIdType.java
new file mode 100644
index 0000000000..b4b2dfa22e
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileIdType.java
@@ -0,0 +1,79 @@
+/* ###
+ * 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.database.sourcemap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An enum whose values represent source file id types, such as md5 or sha1.
+ */
+public enum SourceFileIdType {
+ NONE((byte) 0, 0),
+ UNKNOWN((byte) 1, 0),
+ TIMESTAMP_64((byte) 2, 8),
+ MD5((byte) 3, 16),
+ SHA1((byte) 4, 20),
+ SHA256((byte) 5, 32),
+ SHA512((byte) 6, 64);
+
+ public static final int MAX_LENGTH = 64;
+ private static Map valueToEnum;
+
+ static {
+ valueToEnum = new HashMap<>();
+ for (SourceFileIdType type : SourceFileIdType.values()) {
+ valueToEnum.put(type.getIndex(), type);
+ }
+ }
+
+ private final int byteLength;
+ private final byte index;
+
+ private SourceFileIdType(byte index, int length) {
+ byteLength = length;
+ this.index = index;
+ }
+
+ /**
+ * Returns the byte length of the corresponding identifier. A value of 0 indicates
+ * no restriction.
+ *
+ * @return byte length of identifier
+ */
+ public int getByteLength() {
+ return byteLength;
+ }
+
+ /**
+ * Returns the index of the identifier type.
+ * @return index
+ */
+ byte getIndex() {
+ return index;
+ }
+
+ /**
+ * Returns the id type given the index.
+ * @param index index
+ * @return id type
+ */
+ static SourceFileIdType getTypeFromIndex(byte index) {
+ return valueToEnum.get(index);
+
+ }
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileManagerDB.java
new file mode 100644
index 0000000000..0c39f4f012
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceFileManagerDB.java
@@ -0,0 +1,857 @@
+/* ###
+ * 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.database.sourcemap;
+
+import java.io.IOException;
+import java.util.*;
+
+import db.*;
+import db.util.ErrorHandler;
+import ghidra.framework.data.OpenMode;
+import ghidra.framework.store.LockException;
+import ghidra.program.database.ManagerDB;
+import ghidra.program.database.ProgramDB;
+import ghidra.program.database.map.AddressMapDB;
+import ghidra.program.model.address.*;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.sourcemap.*;
+import ghidra.program.util.ProgramEvent;
+import ghidra.util.Lock;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.VersionException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * Database Manager for managing source files and source map information.
+ */
+public class SourceFileManagerDB implements SourceFileManager, ManagerDB, ErrorHandler {
+
+ private ProgramDB program;
+
+ private SourceFileAdapter sourceFileTableAdapter;
+ private SourceMapAdapter sourceMapTableAdapter;
+ private AddressMapDB addrMap;
+
+ protected final Lock lock;
+
+ private Long lastKey;
+ private SourceFile lastSourceFile;
+
+ /**
+ * Constructor
+ * @param dbh database handle
+ * @param addrMap map longs to addresses
+ * @param openMode mode
+ * @param lock program synchronization lock
+ * @param monitor task monitor
+ * @throws VersionException if the database is incompatible with the current schema
+ */
+ public SourceFileManagerDB(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode, Lock lock,
+ TaskMonitor monitor) throws VersionException {
+ this.addrMap = addrMap;
+ sourceFileTableAdapter = SourceFileAdapter.getAdapter(dbh, openMode, monitor);
+ sourceMapTableAdapter = SourceMapAdapter.getAdapter(dbh, addrMap, openMode, monitor);
+ this.lock = lock;
+ }
+
+ @Override
+ public void setProgram(ProgramDB program) {
+ this.program = program;
+ }
+
+ @Override
+ public void programReady(OpenMode openMode, int currentRevision, TaskMonitor monitor)
+ throws IOException, CancelledException {
+ // nothing to do
+ }
+
+ @Override
+ public void invalidateCache(boolean all) throws IOException {
+ lastKey = null;
+ lastSourceFile = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note: this method will split any source map entries that intersect the address
+ * range but are not entirely contained within it. Parts within the range to delete will
+ * be deleted.
+ * @param start first address in range
+ * @param end last address in range
+ * @param monitor task monitor
+ * @throws CancelledException if {@code monitor} is cancelled
+ */
+ @Override
+ public void deleteAddressRange(Address start, Address end, TaskMonitor monitor)
+ throws CancelledException {
+
+ AddressRange.checkValidRange(start, end);
+ AddressRange rangeToDelete = new AddressRangeImpl(start, end);
+ lock.acquire();
+ try {
+
+ RecordIterator recIter = sourceMapTableAdapter.getSourceMapRecordIterator(end, false);
+ boolean sourceMapChanged = false;
+
+ // Note: we iterate backwards since records are stored based on the start address.
+ // We need to delete records stored before the beginning of the delete range that
+ // overlap the delete range, but we will never need to delete records that start after
+ // the delete range.
+ List entriesToCreate = new ArrayList<>();
+ while (recIter.hasPrevious()) {
+ monitor.checkCancelled();
+ DBRecord rec = recIter.previous();
+ Address recStart = getStartAddress(rec);
+ long recLength = getLength(rec);
+
+ if (recLength == 0) {
+ // if length 0 entry is in range to delete, delete entry
+ // otherwise ignore
+ if (rangeToDelete.contains(recStart)) {
+ recIter.delete();
+ }
+ continue;
+ }
+
+ Address recEnd = getEndAddress(recStart,recLength);
+
+ if (!rangeToDelete.intersects(recStart, recEnd)) {
+ // we've found an entry that does not touch the range to delete
+ // this means we've handled all relevant entries and can stop
+ break;
+ }
+
+ long fileAndLine = getFileAndLine(rec);
+ long fileId = fileAndLine >> 32;
+ int lineNum = (int) (fileAndLine & 0xffffffff);
+ if (isLessInSameSpace(recStart, start)) {
+ // rangeToDelete intersects (recStart,recEnd), so
+ // the entry must overlap the left endpoint of rangeToDelete
+ long length = start.subtract(recStart);
+ SourceMapEntryData data =
+ new SourceMapEntryData(fileId, lineNum, recStart, length);
+ entriesToCreate.add(data);
+ }
+
+ if (isLessInSameSpace(end, recEnd)) {
+ // rangeToDelete intersects (recStart,recEnd)
+ // entry must overlap right endpoint of rangeToDelete
+ long length = recEnd.subtract(end);
+ SourceMapEntryData data =
+ new SourceMapEntryData(fileId, lineNum, end.add(1), length);
+ entriesToCreate.add(data);
+ }
+ recIter.delete();
+ }
+ // add the new entries
+ for (SourceMapEntryData data : entriesToCreate) {
+ sourceMapTableAdapter.addMapEntry(data.sourceFileId, data.lineNumber,
+ data.baseAddress, data.length);
+ }
+ if (sourceMapChanged) {
+ program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, null);
+ }
+ }
+ catch (IOException e) {
+ dbError(e);
+ }
+ finally {
+ lock.release();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note: this method will move any source map entry which is entirely contained within the
+ * source address range. Entries which also contain addresses outside of this range will
+ * be split and parts within the range to move will be moved.
+ * @param fromAddr first address of range to be moved
+ * @param toAddr target address
+ * @param length number of addresses to move
+ * @param monitor task monitor
+ * @throws AddressOverflowException if overflow occurs when computing new addresses
+ * @throws CancelledException if {@code monitor} is cancelled
+ */
+ @Override
+ public void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor)
+ throws AddressOverflowException, CancelledException {
+
+ if (length < 0) {
+ throw new IllegalArgumentException(
+ "Invalid negative length for moveAddressRange: " + length);
+ }
+
+ if (length == 0) {
+ return; // nothing to do
+ }
+
+ lock.acquire();
+
+ try {
+ Address rangeToMoveEnd = fromAddr.addNoWrap(length - 1);
+ AddressRange rangeToMove = new AddressRangeImpl(fromAddr, rangeToMoveEnd);
+ RecordIterator recIter =
+ sourceMapTableAdapter.getSourceMapRecordIterator(rangeToMoveEnd, false);
+ boolean mapChanged = false;
+
+ // Note: we iterate backwards since records are stored based on the start address.
+ // We need to move records stored before the beginning of rangeToMove that
+ // overlap rangeToMove but we will never need to move records that start after
+ // rangeToMove.
+ List entriesToCreate = new ArrayList<>();
+ while (recIter.hasPrevious()) {
+ monitor.checkCancelled();
+ DBRecord rec = recIter.previous();
+ long entryLength = getLength(rec);
+ if (entryLength == 0) {
+ continue; // nothing to check
+ }
+ Address recStart = getStartAddress(rec);
+ Address recEnd = getEndAddress(recStart, entryLength);
+
+ if (!rangeToMove.intersects(recStart, recEnd)) {
+ // we've found an entry entirely before rangeToMove
+ // this means we can stop looking
+ break;
+ }
+ long fileAndLine = getFileAndLine(rec);
+ long fileId = fileAndLine >> 32;
+ int lineNum = (int) (fileAndLine & 0xffffffff);
+
+ if (isLessInSameSpace(recStart, fromAddr) &&
+ isLessInSameSpace(rangeToMoveEnd, recEnd)) {
+ // entry extends over left and right endpoint of rangeToMove
+ long newLength = fromAddr.subtract(recStart);
+ SourceMapEntryData left =
+ new SourceMapEntryData(fileId, lineNum, recStart, newLength);
+ entriesToCreate.add(left);
+ SourceMapEntryData middle =
+ new SourceMapEntryData(fileId, lineNum, fromAddr, length);
+ entriesToCreate.add(middle);
+ newLength = recEnd.subtract(rangeToMoveEnd);
+ SourceMapEntryData right =
+ new SourceMapEntryData(fileId, lineNum, rangeToMoveEnd.add(1), newLength);
+ entriesToCreate.add(right);
+ recIter.delete();
+ continue;
+ }
+
+ if (isLessInSameSpace(recStart, fromAddr)) {
+ // entry extends before left endpoint (but not past right)
+ long newLength = fromAddr.subtract(recStart);
+ SourceMapEntryData left =
+ new SourceMapEntryData(fileId, lineNum, recStart, newLength);
+ entriesToCreate.add(left);
+ newLength = recEnd.subtract(fromAddr) + 1;
+ SourceMapEntryData middle =
+ new SourceMapEntryData(fileId, lineNum, fromAddr, newLength);
+ entriesToCreate.add(middle);
+ recIter.delete();
+ continue;
+ }
+
+ if (isLessInSameSpace(rangeToMoveEnd, recEnd)) {
+ // entry extends past right endpoint (but not before left)
+ long newLength = rangeToMoveEnd.subtract(recStart) + 1;
+ SourceMapEntryData middle =
+ new SourceMapEntryData(fileId, lineNum, recStart, newLength);
+ entriesToCreate.add(middle);
+ newLength = recEnd.subtract(rangeToMoveEnd);
+ SourceMapEntryData right =
+ new SourceMapEntryData(fileId, lineNum, rangeToMoveEnd.add(1), newLength);
+ entriesToCreate.add(right);
+ recIter.delete();
+ continue;
+ }
+ // entry is entirely within range to move, no adjustment needed
+ mapChanged = true;
+
+ }
+
+ // add the new entries
+ for (SourceMapEntryData data : entriesToCreate) {
+ sourceMapTableAdapter.addMapEntry(data.sourceFileId, data.lineNumber,
+ data.baseAddress, data.length);
+ }
+
+ mapChanged = mapChanged || !entriesToCreate.isEmpty();
+ sourceMapTableAdapter.moveAddressRange(fromAddr, toAddr, length, monitor);
+
+ if (mapChanged) {
+ program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, null);
+ }
+ }
+ catch (IOException e) {
+ dbError(e);
+ }
+ finally {
+ lock.release();
+ }
+ }
+
+ @Override
+ public boolean addSourceFile(SourceFile sourceFile) throws LockException {
+ Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
+ program.checkExclusiveAccess();
+ lock.acquire();
+ try {
+ Long key = getKeyForSourceFile(sourceFile);
+ if (key != null) {
+ return false;
+ }
+ DBRecord dbRecord = sourceFileTableAdapter.createSourceFileRecord(sourceFile);
+ updateLastSourceFileAndLastKey(dbRecord);
+ program.setObjChanged(ProgramEvent.SOURCE_FILE_ADDED, null, null, sourceFile);
+ return true;
+ }
+ catch (IOException e) {
+ dbError(e);
+ return false;
+ }
+ finally {
+ lock.release();
+ }
+ }
+
+ @Override
+ public boolean removeSourceFile(SourceFile sourceFile) throws LockException {
+ Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
+ boolean mapChanged = false;
+ program.checkExclusiveAccess();
+ lock.acquire();
+ try {
+ Long key = getKeyForSourceFile(sourceFile);
+ if (key == null) {
+ return false;
+ }
+ RecordIterator recIter =
+ sourceMapTableAdapter.getRecordsForSourceFile(key, 0, Integer.MAX_VALUE);
+ while (recIter.hasNext()) {
+ recIter.next();
+ recIter.delete();
+ mapChanged = true;
+ }
+ sourceFileTableAdapter.removeSourceFileRecord(key);
+ lastKey = null;
+ lastSourceFile = null;
+ }
+ catch (IOException e) {
+ dbError(e);
+ }
+ finally {
+ lock.release();
+ }
+ program.setObjChanged(ProgramEvent.SOURCE_FILE_REMOVED, null, sourceFile, null);
+ if (mapChanged) {
+ program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, sourceFile, null);
+ }
+ return true;
+ }
+
+ @Override
+ public List getSourceMapEntries(Address addr) {
+
+ List sourceMapEntries = new ArrayList<>();
+
+ lock.acquire();
+ try {
+ RecordIterator recIter = sourceMapTableAdapter.getSourceMapRecordIterator(addr, false);
+ boolean foundNonZeroLength = false;
+ while (recIter.hasPrevious()) {
+ DBRecord rec = recIter.previous();
+ long entryLength = getLength(rec);
+ Address entryBase = getStartAddress(rec);
+ if (addr.equals(entryBase)) {
+ sourceMapEntries.add(getSourceMapEntry(rec));
+ if (entryLength != 0) {
+ foundNonZeroLength = true;
+ }
+ continue;
+ }
+ if (entryLength == 0) {
+ continue; // only want length zero entries if they are based at addr
+ }
+ if (!foundNonZeroLength &&
+ isLessOrEqualInSameSpace(addr, getEndAddress(entryBase, entryLength))) {
+ sourceMapEntries.add(getSourceMapEntry(rec));
+ continue; // continue in case there are additional entries at entryBase
+ }
+ break;
+ }
+ }
+ catch (IOException e) {
+ dbError(e);
+ }
+ finally {
+ lock.release();
+ }
+ Collections.sort(sourceMapEntries);
+ return sourceMapEntries;
+ }
+
+ @Override
+ public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
+ long length) throws LockException, AddressOverflowException {
+ if (lineNumber < 0) {
+ throw new IllegalArgumentException("lineNumber cannot be negative");
+ }
+ if (length < 0) {
+ throw new IllegalArgumentException("length cannot be negative");
+ }
+ Objects.requireNonNull(sourceFile, "sourceFile cannot be null");
+ Objects.requireNonNull(baseAddr, "baseAddr cannot be null");
+ MemoryBlock startBlock = program.getMemory().getBlock(baseAddr);
+ if (startBlock == null) {
+ throw new AddressOutOfBoundsException(baseAddr + " is not in a defined memory block");
+ }
+ program.checkExclusiveAccess();
+
+ lock.acquire();
+ try {
+ Long sourceFileId = getKeyForSourceFile(sourceFile);
+ if (sourceFileId == null) {
+ throw new IllegalArgumentException(
+ sourceFile.toString() + " not associated with program");
+ }
+ if (length == 0) {
+ return addZeroLengthEntry(sourceFileId, lineNumber, baseAddr);
+ }
+ Address endAddr = baseAddr.addNoWrap(length - 1);
+
+ // check that the entry's range is entirely contained within defined memory blocks
+ if (!startBlock.contains(endAddr)) {
+ if (!program.getMemory().contains(baseAddr, endAddr)) {
+ throw new AddressOutOfBoundsException(
+ baseAddr + "," + endAddr + " spans undefined memory");
+ }
+ }
+ SourceMapEntryDB entry = null;
+
+ RecordIterator recIter =
+ sourceMapTableAdapter.getSourceMapRecordIterator(endAddr, false);
+ while (recIter.hasPrevious()) {
+ DBRecord rec = recIter.previous();
+ long entryLength = getLength(rec);
+ if (entryLength == 0) {
+ continue; // length 0 entries can't conflict
+ }
+ Address entryBase = getStartAddress(rec);
+ if (entryBase.equals(baseAddr)) {
+ if (entryLength != length) {
+ throw new IllegalArgumentException(
+ "new entry must have the same length as existing entry");
+ }
+ if ((sourceFileId << 32 | lineNumber) == getFileAndLine(rec)) {
+ return getSourceMapEntry(rec); // entry is already in the DB
+ }
+ continue; // non-conflicting entry found, continue checking
+ }
+ if (isLessOrEqualInSameSpace(baseAddr, entryBase)) {
+ throw new IllegalArgumentException(
+ "new entry would overlap entry " + getSourceMapEntry(rec).toString());
+ }
+ if (isLessOrEqualInSameSpace(entryBase, baseAddr)) {
+ if (getEndAddress(entryBase, entryLength).compareTo(baseAddr) >= 0) {
+ throw new IllegalArgumentException(
+ "new entry would overlap entry " + getSourceMapEntry(rec).toString());
+ }
+ }
+ break; // safe to add new entry
+ }
+ DBRecord rec =
+ sourceMapTableAdapter.addMapEntry(sourceFileId, lineNumber, baseAddr, length);
+ entry = new SourceMapEntryDB(this, rec, addrMap);
+ program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, entry);
+ return entry;
+ }
+ catch (IOException e) {
+ dbError(e);
+ throw new AssertionError("addSourceMapEntry unsuccessful - possible database error");
+ }
+ finally {
+ lock.release();
+ }
+ }
+
+ @Override
+ public boolean intersectsSourceMapEntry(AddressSetView addrs) {
+ if (addrs == null || addrs.isEmpty()) {
+ return false;
+ }
+ lock.acquire();
+ try {
+ for (AddressRangeIterator rangeIter = addrs.getAddressRanges(); rangeIter.hasNext();) {
+ AddressRange r = rangeIter.next();
+ RecordIterator recIter =
+ sourceMapTableAdapter.getSourceMapRecordIterator(r.getMaxAddress(), false);
+ while (recIter.hasPrevious()) {
+ DBRecord rec = recIter.previous();
+ Address entryStart = getStartAddress(rec);
+ if (r.contains(entryStart)) {
+ return true;
+ }
+ long length = getLength(rec);
+ if (length == 0) {
+ continue;
+ }
+ if (r.intersects(entryStart, getEndAddress(entryStart, length))) {
+ return true;
+ }
+ break;
+ }
+ }
+ return false;
+ }
+ catch (IOException e) {
+ dbError(e);
+ return false;
+ }
+ finally {
+ lock.release();
+ }
+ }
+
+ @Override
+ public void dbError(IOException e) throws RuntimeException {
+ program.dbError(e);
+ }
+
+ @Override
+ public List getMappedSourceFiles() {
+ List sourceFiles = new ArrayList<>();
+ lock.acquire();
+ try {
+ for (RecordIterator sourceFileRecordIter =
+ sourceFileTableAdapter.getRecords(); sourceFileRecordIter
+ .hasNext();) {
+ DBRecord sourceFileRecord = sourceFileRecordIter.next();
+ long key = sourceFileRecord.getKey();
+ RecordIterator sourceMapEntryRecordIter =
+ sourceMapTableAdapter.getRecordsForSourceFile(key, 0, Integer.MAX_VALUE);
+ if (sourceMapEntryRecordIter.hasNext()) {
+ updateLastSourceFileAndLastKey(sourceFileRecord);
+ sourceFiles.add(lastSourceFile);
+ }
+ }
+ }
+ catch (IOException e) {
+ dbError(e);
+ }
+ finally {
+ lock.release();
+ }
+ return sourceFiles;
+ }
+
+ @Override
+ public List getAllSourceFiles() {
+ List sourceFiles = new ArrayList<>();
+ lock.acquire();
+ try {
+ for (RecordIterator recordIter = sourceFileTableAdapter.getRecords(); recordIter
+ .hasNext();) {
+ updateLastSourceFileAndLastKey(recordIter.next());
+ sourceFiles.add(lastSourceFile);
+ }
+ }
+ catch (IOException e) {
+ dbError(e);
+ }
+ finally {
+ lock.release();
+ }
+ return sourceFiles;
+ }
+
+ @Override
+ public void transferSourceMapEntries(SourceFile source, SourceFile target)
+ throws LockException {
+ program.checkExclusiveAccess();
+
+ lock.acquire();
+ try {
+ Long srcKey = getKeyForSourceFile(source);
+ if (srcKey == null) {
+ throw new IllegalArgumentException(
+ source.toString() + " is not associated with program");
+ }
+ Long targetKey = getKeyForSourceFile(target);
+ if (targetKey == null) {
+ throw new IllegalArgumentException(
+ target.toString() + " is not associated with program");
+ }
+ if (source.equals(target)) {
+ return; // transfer redundant
+ }
+ for (SourceMapEntry entry : getSourceMapEntries(source, 0, Integer.MAX_VALUE)) {
+ addSourceMapEntry(target, entry.getLineNumber(), entry.getBaseAddress(),
+ entry.getLength());
+ removeSourceMapEntry(entry); // remove fires a SOURCE_MAP_CHANGED event
+ }
+ }
+ catch (AddressOverflowException e) {
+ // can't happen - entry ranges were validated upon insert
+ throw new AssertionError("bad address range in source map entry table");
+ }
+ finally {
+ lock.release();
+ }
+ }
+
+ @Override
+ public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward) {
+ try {
+ return new SourceMapEntryIteratorDB(this,
+ sourceMapTableAdapter.getSourceMapRecordIterator(address, forward), forward);
+ }
+ catch (IOException e) {
+ dbError(e);
+ }
+ return SourceMapEntryIterator.EMPTY_ITERATOR;
+ }
+
+ @Override
+ public boolean containsSourceFile(SourceFile sourceFile) {
+ if (sourceFile == null) {
+ return false;
+ }
+ lock.acquire();
+ try {
+ return getKeyForSourceFile(sourceFile) != null;
+ }
+ finally {
+ lock.release();
+ }
+ }
+
+ @Override
+ public List getSourceMapEntries(SourceFile sourceFile, int minLine,
+ int maxLine) {
+ if (minLine < 0) {
+ throw new IllegalArgumentException("minLine cannot be negative; was " + minLine);
+ }
+ if (maxLine < 0) {
+ throw new IllegalArgumentException("maxLine cannot be negative; was " + maxLine);
+ }
+ if (maxLine < minLine) {
+ throw new IllegalArgumentException("maxLine cannot be less than minLine");
+ }
+ List entries = new ArrayList<>();
+ lock.acquire();
+ try {
+ Long key = getKeyForSourceFile(sourceFile);
+ if (key == null) {
+ return entries;
+ }
+ try {
+ RecordIterator recIter =
+ sourceMapTableAdapter.getRecordsForSourceFile(key, minLine, maxLine);
+ while (recIter.hasNext()) {
+ DBRecord rec = recIter.next();
+ entries.add(getSourceMapEntry(rec));
+ }
+ }
+ catch (IOException e) {
+ dbError(e);
+ }
+ }
+ finally {
+ lock.release();
+ }
+ Collections.sort(entries);
+ return entries;
+ }
+
+ @Override
+ public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException {
+ Objects.requireNonNull(entry, "entry cannot be null");
+ program.checkExclusiveAccess();
+ lock.acquire();
+ try {
+ RecordIterator recIter =
+ sourceMapTableAdapter.getSourceMapRecordIterator(entry.getBaseAddress(), true);
+ while (recIter.hasNext()) {
+ DBRecord rec = recIter.next();
+ long length = getLength(rec);
+ if (length != entry.getLength()) {
+ continue;
+ }
+ long fileAndLine = getFileAndLine(rec);
+ if (((int) (fileAndLine & 0xffffffff)) != entry.getLineNumber()) {
+ continue;
+ }
+ if (!(entry.getSourceFile().equals(getSourceFileFromKey(fileAndLine >> 32)))) {
+ continue;
+ }
+ sourceMapTableAdapter.removeRecord(rec.getKey());
+ program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, entry, null);
+ return true;
+
+ }
+ }
+ catch (IOException e) {
+ dbError(e);
+ return false;
+ }
+ finally {
+ lock.release();
+ }
+ return false;
+ }
+
+ SourceFile getSourceFile(long key) {
+ lock.acquire();
+ try {
+ return getSourceFileFromKey(key);
+ }
+ finally {
+ lock.release();
+ }
+ }
+
+ SourceMapEntry getSourceMapEntry(DBRecord rec) {
+ return new SourceMapEntryDB(this, rec, addrMap);
+ }
+
+ static boolean isLessOrEqualInSameSpace(Address addr1, Address addr2) {
+ if (!addr1.hasSameAddressSpace(addr2)) {
+ return false;
+ }
+ return addr1.compareTo(addr2) <= 0;
+ }
+
+ static boolean isLessInSameSpace(Address addr1, Address addr2) {
+ if (!addr1.hasSameAddressSpace(addr2)) {
+ return false;
+ }
+ return addr1.compareTo(addr2) < 0;
+ }
+
+ // acquire lock before invoking
+ private SourceMapEntry addZeroLengthEntry(long sourceFileId, int lineNumber, Address baseAddr) {
+ SourceMapEntry entry = null;
+ try {
+ RecordIterator recIter =
+ sourceMapTableAdapter.getSourceMapRecordIterator(baseAddr, true);
+ while (recIter.hasNext()) {
+ DBRecord rec = recIter.next();
+ Address recAddress = getStartAddress(rec);
+ if (!recAddress.equals(baseAddr)) {
+ break;
+ }
+ if (getLength(rec) != 0) {
+ continue;
+ }
+ long fileAndLine = getFileAndLine(rec);
+ if ((sourceFileId << 32 | lineNumber) != fileAndLine) {
+ continue;
+ }
+ return getSourceMapEntry(rec);
+ }
+ DBRecord rec = sourceMapTableAdapter.addMapEntry(sourceFileId, lineNumber, baseAddr, 0);
+ entry = new SourceMapEntryDB(this, rec, addrMap);
+ program.setChanged(ProgramEvent.SOURCE_MAP_CHANGED, null, entry);
+ return entry;
+ }
+ catch (IOException e) {
+ dbError(e);
+ throw new AssertionError("addZeroLengthEntry unsuccessful - possible database error");
+ }
+ }
+
+ private long getLength(DBRecord rec) {
+ return rec.getLongValue(SourceMapAdapter.LENGTH_COL);
+ }
+
+ private Address getStartAddress(DBRecord rec) {
+ return addrMap.decodeAddress(rec.getLongValue(SourceMapAdapter.BASE_ADDR_COL));
+ }
+
+ private long getFileAndLine(DBRecord rec) {
+ return rec.getLongValue(SourceMapAdapter.FILE_LINE_COL);
+ }
+
+ // assumes that start and length are from a valid SourceMapEntry
+ // that is, start.add(length - 1) doesn't wrap and is in the same
+ // space
+ private Address getEndAddress(Address start, long length) {
+ if (length == 0) {
+ return start;
+ }
+ try {
+ return start.addNoWrap(length - 1);
+ }
+ catch (AddressOverflowException e) {
+ // shouldn't happen, but return space max to prevent possibility of wrapping
+ return start.getAddressSpace().getMaxAddress();
+ }
+ }
+
+ // acquire lock before invoking
+ private SourceFile getSourceFileFromKey(long key) {
+ if (lastKey == null || lastKey.longValue() != key) {
+ DBRecord dbRecord = null;
+ try {
+ dbRecord = sourceFileTableAdapter.getRecord(key);
+ }
+ catch (IOException e) {
+ dbError(e);
+ return null;
+ }
+ if (dbRecord == null) {
+ return null;
+ }
+ updateLastSourceFileAndLastKey(dbRecord);
+ }
+ return lastSourceFile;
+ }
+
+ // acquire lock before invoking
+ private Long getKeyForSourceFile(SourceFile sourceFile) {
+ if (lastSourceFile == null || !sourceFile.equals(lastSourceFile)) {
+ DBRecord dbRecord = null;
+ try {
+ dbRecord = sourceFileTableAdapter.getRecord(sourceFile);
+ }
+ catch (IOException e) {
+ dbError(e);
+ return null;
+ }
+ if (dbRecord == null) {
+ return null;
+ }
+ updateLastSourceFileAndLastKey(dbRecord);
+ }
+ return lastKey;
+ }
+
+ private void updateLastSourceFileAndLastKey(DBRecord dbRecord) {
+ lastKey = dbRecord.getKey();
+ String path = dbRecord.getString(SourceFileAdapter.PATH_COL);
+ SourceFileIdType idType =
+ SourceFileIdType.getTypeFromIndex(dbRecord.getByteValue(SourceFileAdapter.ID_TYPE_COL));
+ byte[] identifier = dbRecord.getBinaryData(SourceFileAdapter.ID_COL);
+ lastSourceFile = new SourceFile(path, idType, identifier, false);
+ }
+
+ /**
+ * A record for storing information about new source map entries which must be created
+ * during {@link #moveAddressRange} or {@link #deleteAddressRange}
+ */
+ private record SourceMapEntryData(long sourceFileId, int lineNumber, Address baseAddress,
+ long length) {}
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapAdapter.java
new file mode 100644
index 0000000000..ad588b43b9
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapAdapter.java
@@ -0,0 +1,116 @@
+/* ###
+ * 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.database.sourcemap;
+
+import java.io.IOException;
+
+import db.*;
+import ghidra.framework.data.OpenMode;
+import ghidra.program.database.map.AddressMapDB;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.VersionException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * Base class for adapters to access the Source Map table.
+ *
+ * Each entry in the table corresponds to a single {@link SourceMapEntry} and so records
+ * a {@link SourceFile}, a line number, a base address, and a length.
+ *
+ * There are a number of restrictions on a {@link SourceMapEntry}, which are listed in that
+ * interface's top-level documentation. It is the responsibility of the {@link SourceFileManager}
+ * to enforce these restrictions.
+ */
+abstract class SourceMapAdapter {
+
+ static final String TABLE_NAME = "SourceMap";
+ static final int FILE_LINE_COL = SourceMapAdapterV0.V0_FILE_LINE_COL;
+ static final int BASE_ADDR_COL = SourceMapAdapterV0.V0_BASE_ADDR_COL;
+ static final int LENGTH_COL = SourceMapAdapterV0.V0_LENGTH_COL;
+
+ /**
+ * Creates an adapter for the Source Map table.
+ * @param dbh database handle
+ * @param addrMap address map
+ * @param openMode mode
+ * @param monitor task monitor
+ * @return adapter for table
+ * @throws VersionException if version incompatible
+ */
+ static SourceMapAdapter getAdapter(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode,
+ TaskMonitor monitor) throws VersionException {
+ return new SourceMapAdapterV0(dbh, addrMap, openMode);
+ }
+
+ /**
+ * Removes a record from the table
+ * @param key key of record to remove.
+ * @return true if the record was deleted successfully
+ * @throws IOException if database error occurs
+ */
+ abstract boolean removeRecord(long key) throws IOException;
+
+ /**
+ * Returns a {@link RecordIterator} based at {@code addr}.
+ * @param addr starting address
+ * @param before if true, initial position is before addr, otherwise after
+ * @return iterator
+ * @throws IOException if database error occurs
+ */
+ abstract RecordIterator getSourceMapRecordIterator(Address addr, boolean before)
+ throws IOException;
+
+ /**
+ * Returns a {@link RecordIterator} over all records for the source file with
+ * id {@code id}, subject to the line bounds {@code minLine} and {@code maxLine}
+ * @param fileId id of source file
+ * @param minLine minimum line number
+ * @param maxLine maximum line number
+ * @return iterator
+ * @throws IOException if database error occurs
+ */
+ abstract RecordIterator getRecordsForSourceFile(long fileId, int minLine, int maxLine)
+ throws IOException;
+
+ /**
+ * Adds an entry to the source map table. This method assumes that no address
+ * in the associated range has already been associated with this source file and
+ * line number.
+ * @param fileId source file id
+ * @param lineNum line number
+ * @param baseAddr minimum address of range
+ * @param length number of addresses in range
+ * @return record
+ * @throws IOException if database error occurs
+ */
+ abstract DBRecord addMapEntry(long fileId, int lineNum, Address baseAddr, long length)
+ throws IOException;
+
+ /**
+ * Updates all appropriate entries in the table when an address range is moved.
+ * @param fromAddr from address
+ * @param toAddr to address
+ * @param length number of addresses in moved range
+ * @param monitor task monitor
+ * @throws CancelledException if task cancelled
+ * @throws IOException if database error occurs
+ */
+ abstract void moveAddressRange(Address fromAddr, Address toAddr, long length,
+ TaskMonitor monitor) throws CancelledException, IOException;
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapAdapterV0.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapAdapterV0.java
new file mode 100644
index 0000000000..9f2f79bee0
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapAdapterV0.java
@@ -0,0 +1,160 @@
+/* ###
+ * 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.database.sourcemap;
+
+import java.io.IOException;
+
+import db.*;
+import ghidra.framework.data.OpenMode;
+import ghidra.program.database.map.AddressIndexPrimaryKeyIterator;
+import ghidra.program.database.map.AddressMapDB;
+import ghidra.program.database.util.DatabaseTableUtils;
+import ghidra.program.database.util.EmptyRecordIterator;
+import ghidra.program.model.address.Address;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.VersionException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * Initial version of {@link SourceMapAdapter}
+ */
+class SourceMapAdapterV0 extends SourceMapAdapter implements DBListener {
+
+ final static int SCHEMA_VERSION = 0;
+ static final int V0_FILE_LINE_COL = 0; // indexed
+ static final int V0_BASE_ADDR_COL = 1; // indexed
+ static final int V0_LENGTH_COL = 2;
+
+ // key | ((32-bit source file id << 32) | 32-bit line number) | base addr | length
+ private final static Schema V0_SCHEMA = new Schema(SCHEMA_VERSION, "ID",
+ new Field[] { LongField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE },
+ new String[] { "fileAndLine", "baseAddress", "length" }, null);
+
+ private static final int[] INDEXED_COLUMNS = new int[] { V0_FILE_LINE_COL, V0_BASE_ADDR_COL };
+
+ private Table table; // lazy creation, null if empty
+ private final DBHandle dbHandle;
+ private AddressMapDB addrMap;
+
+ /**
+ * Creates an adapter for version 0 of the source map adapter
+ * @param dbh database handle
+ * @param addrMap address map
+ * @param openMode open mode
+ * @throws VersionException if version incompatible
+ */
+ SourceMapAdapterV0(DBHandle dbh, AddressMapDB addrMap, OpenMode openMode)
+ throws VersionException {
+ this.dbHandle = dbh;
+ this.addrMap = addrMap;
+
+ // As in FunctionTagAdapterV0, we need to add this as a database listener.
+ // Since the table is created lazily, undoing a transaction which (for example) caused
+ // the table to be created can leave the table in a bad state.
+ // The implementation of dbRestored(DBHandle) solves this issue.
+ this.dbHandle.addListener(this);
+
+ if (!openMode.equals(OpenMode.CREATE)) {
+ table = dbHandle.getTable(TABLE_NAME);
+ if (table == null) {
+ return; // perform lazy table creation
+ }
+ int version = table.getSchema().getVersion();
+ if (version != SCHEMA_VERSION) {
+ throw new VersionException(VersionException.NEWER_VERSION, false);
+ }
+ }
+ }
+
+ @Override
+ public void dbRestored(DBHandle dbh) {
+ table = dbh.getTable(TABLE_NAME);
+ }
+
+ @Override
+ public void dbClosed(DBHandle dbh) {
+ // nothing to do
+ }
+
+ @Override
+ public void tableDeleted(DBHandle dbh, Table t) {
+ // nothing to do
+ }
+
+ @Override
+ public void tableAdded(DBHandle dbh, Table t) {
+ // nothing to do
+ }
+
+ @Override
+ boolean removeRecord(long key) throws IOException {
+ if (table != null) {
+ return table.deleteRecord(key);
+ }
+ return false;
+ }
+
+ @Override
+ RecordIterator getSourceMapRecordIterator(Address addr, boolean before) throws IOException {
+ if (table == null || addr == null) {
+ return EmptyRecordIterator.INSTANCE;
+ }
+ AddressIndexPrimaryKeyIterator keyIter =
+ new AddressIndexPrimaryKeyIterator(table, V0_BASE_ADDR_COL, addrMap, addr, before);
+ return new KeyToRecordIterator(table, keyIter);
+ }
+
+ @Override
+ RecordIterator getRecordsForSourceFile(long fileId, int minLine, int maxLine)
+ throws IOException {
+ if (table == null) {
+ return EmptyRecordIterator.INSTANCE;
+ }
+ fileId = fileId << 32;
+ LongField minField = new LongField(fileId | minLine);
+ LongField maxField = new LongField(fileId | maxLine);
+ return table.indexIterator(V0_FILE_LINE_COL, minField, maxField, true);
+ }
+
+ @Override
+ DBRecord addMapEntry(long fileId, int lineNum, Address baseAddr, long length)
+ throws IOException {
+ DBRecord rec = V0_SCHEMA.createRecord(getTable().getKey());
+ rec.setLongValue(V0_FILE_LINE_COL, (fileId << 32) | lineNum);
+ rec.setLongValue(V0_BASE_ADDR_COL, addrMap.getKey(baseAddr, true));
+ rec.setLongValue(V0_LENGTH_COL, length);
+ table.putRecord(rec);
+ return rec;
+ }
+
+ @Override
+ void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor)
+ throws CancelledException, IOException {
+ if (table == null) {
+ return;
+ }
+ DatabaseTableUtils.updateIndexedAddressField(table, V0_BASE_ADDR_COL, addrMap, fromAddr,
+ toAddr, length, null, monitor);
+ }
+
+ private Table getTable() throws IOException {
+ if (table == null) {
+ table = dbHandle.createTable(TABLE_NAME, V0_SCHEMA, INDEXED_COLUMNS);
+ }
+ return table;
+ }
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapEntryDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapEntryDB.java
new file mode 100644
index 0000000000..f4dc4d8e0d
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapEntryDB.java
@@ -0,0 +1,158 @@
+/* ###
+ * 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.database.sourcemap;
+
+import java.util.Objects;
+
+import db.DBRecord;
+import ghidra.program.database.map.AddressMapDB;
+import ghidra.program.model.address.*;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+
+/**
+ * Database implementation of {@link SourceMapEntry} interface.
+ *
+ * Note: clients should drop and reacquire all SourceMapEntryDB objects upon undo/redo,
+ * ProgramEvent.SOURCE_MAP_CHANGED, and ProgramEvent.SOURCE_FILE_REMOVED.
+ */
+public class SourceMapEntryDB implements SourceMapEntry {
+
+ private int lineNumber;
+ private SourceFile sourceFile;
+ private Address baseAddress;
+ private long length;
+ private AddressRange range = null;
+
+ /**
+ * Creates a new SourceMapEntryDB
+ * @param manager source file manager
+ * @param record backing record
+ * @param addrMap address map
+ */
+ SourceMapEntryDB(SourceFileManagerDB manager, DBRecord record, AddressMapDB addrMap) {
+ manager.lock.acquire();
+ try {
+ long fileAndLine = record.getLongValue(SourceMapAdapter.FILE_LINE_COL);
+ lineNumber = (int) (fileAndLine & 0xffffffff);
+ sourceFile = manager.getSourceFile(fileAndLine >> 32);
+ long encodedAddress = record.getLongValue(SourceMapAdapter.BASE_ADDR_COL);
+ baseAddress = addrMap.decodeAddress(encodedAddress);
+ length = record.getLongValue(SourceMapAdapter.LENGTH_COL);
+ if (length != 0) {
+ Address max;
+ try {
+ max = baseAddress.addNoWrap(length - 1);
+ }
+ catch (AddressOverflowException e) {
+ // shouldn't happen, but return space max to prevent possibility of wrapping
+ max = baseAddress.getAddressSpace().getMaxAddress();
+ }
+ range = new AddressRangeImpl(baseAddress, max);
+ }
+ }
+ finally {
+ manager.lock.release();
+ }
+ }
+
+ @Override
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ @Override
+ public SourceFile getSourceFile() {
+ return sourceFile;
+ }
+
+ @Override
+ public Address getBaseAddress() {
+ return baseAddress;
+ }
+
+ @Override
+ public AddressRange getRange() {
+ return range;
+ }
+
+ @Override
+ public long getLength() {
+ return length;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer(getSourceFile().toString());
+ sb.append(":");
+ sb.append(getLineNumber());
+ sb.append(" @ ");
+ sb.append(getBaseAddress().toString());
+ sb.append(" (");
+ sb.append(Long.toString(getLength()));
+ sb.append(")");
+ return sb.toString();
+ }
+
+ @Override
+ public int compareTo(SourceMapEntry o) {
+ int sourceFileCompare = getSourceFile().compareTo(o.getSourceFile());
+ if (sourceFileCompare != 0) {
+ return sourceFileCompare;
+ }
+ int lineCompare = Integer.compare(getLineNumber(), o.getLineNumber());
+ if (lineCompare != 0) {
+ return lineCompare;
+ }
+ int addrCompare = getBaseAddress().compareTo(o.getBaseAddress());
+ if (addrCompare != 0) {
+ return addrCompare;
+ }
+ return Long.compareUnsigned(getLength(), o.getLength());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof SourceMapEntry otherEntry)) {
+ return false;
+ }
+ if (lineNumber != otherEntry.getLineNumber()) {
+ return false;
+ }
+ if (!sourceFile.equals(otherEntry.getSourceFile())) {
+ return false;
+ }
+ if (!baseAddress.equals(otherEntry.getBaseAddress())) {
+ return false;
+ }
+ if (length != otherEntry.getLength()) {
+ return false;
+ }
+ if (!Objects.equals(range, otherEntry.getRange())) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = lineNumber;
+ hashCode = 31 * hashCode + sourceFile.hashCode();
+ hashCode = 31 * hashCode + baseAddress.hashCode();
+ hashCode = 31 * hashCode + Long.hashCode(length);
+ return hashCode;
+ }
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapEntryIteratorDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapEntryIteratorDB.java
new file mode 100644
index 0000000000..9351cce628
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/SourceMapEntryIteratorDB.java
@@ -0,0 +1,95 @@
+/* ###
+ * 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.database.sourcemap;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import db.DBRecord;
+import db.RecordIterator;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+import ghidra.program.model.sourcemap.SourceMapEntryIterator;
+
+/**
+ * Database implementation of {@link SourceMapEntryIterator}
+ */
+public class SourceMapEntryIteratorDB implements SourceMapEntryIterator {
+
+ private boolean forward;
+ private RecordIterator recIter;
+ private SourceFileManagerDB sourceManager;
+ private SourceMapEntry nextEntry;
+
+ /**
+ * Constructor
+ * @param sourceManager source manager
+ * @param recIter record iterator
+ * @param forward direction to iterate
+ */
+ SourceMapEntryIteratorDB(SourceFileManagerDB sourceManager, RecordIterator recIter,
+ boolean forward) {
+ this.sourceManager = sourceManager;
+ this.recIter = recIter;
+ this.forward = forward;
+ this.nextEntry = null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (nextEntry != null) {
+ return true;
+ }
+ sourceManager.lock.acquire();
+ try {
+ boolean recIterNext = forward ? recIter.hasNext() : recIter.hasPrevious();
+ if (!recIterNext) {
+ return false;
+ }
+ DBRecord rec = forward ? recIter.next() : recIter.previous();
+ nextEntry = sourceManager.getSourceMapEntry(rec);
+ return true;
+ }
+ catch (IOException e) {
+ sourceManager.dbError(e);
+ return false;
+ }
+ finally {
+ sourceManager.lock.release();
+ }
+ }
+
+ @Override
+ public SourceMapEntry next() {
+ if (hasNext()) {
+ SourceMapEntry entryToReturn = nextEntry;
+ nextEntry = null;
+ return entryToReturn;
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return this;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java
index b90bfa018d..8b663e2892 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java
@@ -105,7 +105,7 @@ public class AddressRangeMapDB implements DBListener {
* @param errHandler database error handler
* @param valueField specifies the type for the values stored in this map
* @param indexed if true, values will be indexed allowing use of the
- * getValueRangeIterator method
+ * {@link AddressRangeMapDB#getAddressSet(Field)} method.
*/
public AddressRangeMapDB(DBHandle dbHandle, AddressMap addressMap, Lock lock, String name,
ErrorHandler errHandler, Field valueField, boolean indexed) {
@@ -343,7 +343,8 @@ public class AddressRangeMapDB implements DBListener {
}
/**
- * Returns set of addresses where the given value has been set
+ * Returns set of addresses where the given value has been set.
+ * This method may only be invoked on indexed {@link AddressRangeMapDB}s!
* @param value the value to search for
* @return set of addresses where the given value has been set
*/
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Program.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Program.java
index 0ddd9dc536..baafcf0fe4 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Program.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Program.java
@@ -28,6 +28,7 @@ import ghidra.program.model.lang.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.reloc.RelocationTable;
+import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.AddressSetPropertyMap;
import ghidra.program.model.util.PropertyMapManager;
@@ -149,6 +150,14 @@ public interface Program extends DataTypeManagerDomainObject, ProgramArchitectur
*/
public BookmarkManager getBookmarkManager();
+ /**
+ * Returns the program's {@link SourceFileManager}.
+ * @return the source file manager
+ */
+ default public SourceFileManager getSourceFileManager() {
+ return SourceFileManager.DUMMY;
+ }
+
/**
* Gets the default pointer size in bytes as it may be stored within the program listing.
* @return default pointer size.
@@ -535,4 +544,5 @@ public interface Program extends DataTypeManagerDomainObject, ProgramArchitectur
* @return unique program ID
*/
public long getUniqueProgramID();
+
}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/DummySourceFileManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/DummySourceFileManager.java
new file mode 100644
index 0000000000..328bdbc62b
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/DummySourceFileManager.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.program.model.sourcemap;
+
+import java.util.Collections;
+import java.util.List;
+
+import ghidra.framework.store.LockException;
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressSetView;
+
+/**
+ * A "dummy" implementation of {@link SourceFileManager}.
+ */
+public class DummySourceFileManager implements SourceFileManager {
+
+ public DummySourceFileManager() {
+ // nothing to do
+ }
+
+ @Override
+ public List getSourceMapEntries(Address addr) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
+ long length) throws LockException {
+ throw new UnsupportedOperationException("Cannot add source map entries with this manager");
+ }
+
+ @Override
+ public boolean intersectsSourceMapEntry(AddressSetView addrs) {
+ return false;
+ }
+
+ @Override
+ public List getAllSourceFiles() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getMappedSourceFiles() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void transferSourceMapEntries(SourceFile source, SourceFile target) {
+ throw new UnsupportedOperationException(
+ "Dummy source file manager cannot transfer map info");
+
+ }
+
+ @Override
+ public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward) {
+ return SourceMapEntryIterator.EMPTY_ITERATOR;
+ }
+
+ @Override
+ public List getSourceMapEntries(SourceFile sourceFile, int minLine,
+ int maxLine) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean addSourceFile(SourceFile sourceFile) throws LockException {
+ throw new UnsupportedOperationException("cannot add source files to this manager");
+ }
+
+ @Override
+ public boolean removeSourceFile(SourceFile sourceFile) throws LockException {
+ throw new UnsupportedOperationException("cannot remove source files from this manager");
+ }
+
+ @Override
+ public boolean containsSourceFile(SourceFile sourceFile) {
+ return false;
+ }
+
+ @Override
+ public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException {
+ throw new UnsupportedOperationException(
+ "cannot remove source map entries from this manager");
+ }
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourceFileManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourceFileManager.java
new file mode 100644
index 0000000000..c38b23eb66
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourceFileManager.java
@@ -0,0 +1,199 @@
+/* ###
+ * 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.model.sourcemap;
+
+import java.util.List;
+
+import ghidra.framework.store.LockException;
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.model.address.*;
+
+/**
+ * This interface defines methods for managing {@link SourceFile}s and {@link SourceMapEntry}s.
+ */
+public interface SourceFileManager {
+
+ public static final SourceFileManager DUMMY = new DummySourceFileManager();
+
+ /**
+ * Returns a sorted list of {@link SourceMapEntry}s associated with an address {@code addr}.
+ * @param addr address
+ * @return line number
+ */
+ public List getSourceMapEntries(Address addr);
+
+ /**
+ * Adds a {@link SourceMapEntry} with {@link SourceFile} {@code sourceFile},
+ * line number {@code lineNumber}, and {@link AddressRange} {@code range} to the program
+ * database.
+ *
+ * Entries with non-zero lengths must either cover the same address range or be disjoint.
+ * @param sourceFile source file
+ * @param lineNumber line number
+ * @param range address range
+ * @return created SourceMapEntry
+ * @throws LockException if invoked without exclusive access
+ * @throws IllegalArgumentException if the range of the new entry intersects, but does
+ * not equal, the range of an existing entry or if sourceFile was not previously added
+ * to the program.
+ * @throws AddressOutOfBoundsException if the range of the new entry contains addresses
+ * that are not in a defined memory block
+ */
+ public default SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber,
+ AddressRange range) throws LockException {
+ try {
+ return addSourceMapEntry(sourceFile, lineNumber, range.getMinAddress(),
+ range.getLength());
+ }
+ // can't happen
+ catch (AddressOverflowException e) {
+ throw new AssertionError("Address overflow with valid AddressRange");
+ }
+ }
+
+ /**
+ * Creates a {@link SourceMapEntry} with {@link SourceFile} {@code sourceFile},
+ * line number {@code lineNumber}, and non-negative length {@code length} and
+ * adds it to the program database.
+ *
+ * Entries with non-zero lengths must either cover the same address range or be disjoint.
+ * @param sourceFile source file
+ * @param lineNumber line number
+ * @param baseAddr minimum address of range
+ * @param length number of addresses in range
+ * @return created SourceMapEntry
+ * @throws AddressOverflowException if baseAddr + length-1 overflows
+ * @throws LockException if invoked without exclusive access
+ * @throws IllegalArgumentException if the range of the new entry intersects, but does
+ * not equal, the range of an existing entry or if sourceFile was not previously added to the
+ * program.
+ * @throws AddressOutOfBoundsException if the range of the new entry contains addresses
+ * that are not in a defined memory block
+ */
+ public SourceMapEntry addSourceMapEntry(SourceFile sourceFile, int lineNumber, Address baseAddr,
+ long length) throws AddressOverflowException, LockException;
+
+ /**
+ * Returns {@code true} precisely when at least one {@link Address} in {@code addrs} has
+ * source map information.
+ * @param addrs addresses to check
+ * @return true when at least one address has source map info
+ */
+ public boolean intersectsSourceMapEntry(AddressSetView addrs);
+
+ /**
+ * Adds a {@link SourceFile} to this manager. A SourceFile must be added before it can be
+ * associated with any source map information.
+ *
+ * @param sourceFile source file to add (can't be null)
+ * @return true if this manager did not already contain sourceFile
+ * @throws LockException if invoked without exclusive access
+ */
+ public boolean addSourceFile(SourceFile sourceFile) throws LockException;
+
+ /**
+ * Removes a {@link SourceFile} from this manager. Any associated {@link SourceMapEntry}s will
+ * also be removed.
+ * @param sourceFile source file to remove
+ * @return true if sourceFile was in the manager
+ * @throws LockException if invoked without exclusive access
+ */
+ public boolean removeSourceFile(SourceFile sourceFile) throws LockException;
+
+ /**
+ * Returns true precisely when this manager contains {@code sourceFile}.
+ * @param sourceFile source file
+ * @return true if source file already added
+ */
+ public boolean containsSourceFile(SourceFile sourceFile);
+
+ /**
+ * Returns a {@link List} containing all {@link SourceFile}s of the program.
+ * @return source file list
+ */
+ public List getAllSourceFiles();
+
+ /**
+ * Returns a {@link List} containing {@link SourceFile}s which are
+ * mapped to at least one address in the program
+ * @return mapped source file list
+ */
+ public List getMappedSourceFiles();
+
+ /**
+ * Changes the source map so that any {@link SourceMapEntry} associated with {@code source}
+ * is associated with {@code target} instead. Any entries associated with
+ * {@code target} before invocation will still be associated with
+ * {@code target} after invocation. {@code source} will not be associated
+ * with any entries after invocation (unless {@code source} and {@code target}
+ * are the same). Line number information is not changed.
+ * @param source source file to get info from
+ * @param target source file to move info to
+ * @throws LockException if invoked without exclusive access
+ * @throws IllegalArgumentException if source or target has not been added previously
+ */
+ public void transferSourceMapEntries(SourceFile source, SourceFile target) throws LockException;
+
+ /**
+ * Returns a {@link SourceMapEntryIterator} starting at {@code address}.
+ *
+ * @param address starting address
+ * @param forward direction of iterator (true = forward)
+ * @return iterator
+ */
+ public SourceMapEntryIterator getSourceMapEntryIterator(Address address, boolean forward);
+
+ /**
+ * Returns the sorted list of {@link SourceMapEntry}s for {@code sourceFile} with line number
+ * between {@code minLine} and {@code maxLine}, inclusive.
+ * @param sourceFile source file
+ * @param minLine minimum line number
+ * @param maxLine maximum line number
+ * @return source map entries
+ */
+ public List getSourceMapEntries(SourceFile sourceFile, int minLine,
+ int maxLine);
+
+ /**
+ * Returns the sorted list of {@link SourceMapEntry}s for {@code sourceFile} with line number
+ * equal to {@code lineNumber}.
+ * @param sourceFile source file
+ * @param lineNumber line number
+ * @return source map entries
+ */
+ public default List getSourceMapEntries(SourceFile sourceFile, int lineNumber) {
+ return getSourceMapEntries(sourceFile, lineNumber, lineNumber);
+ }
+
+ /**
+ * Returns a sorted of list all {@link SourceMapEntry}s in the program corresponding to
+ * {@code sourceFile}.
+ * @param sourceFile source file
+ * @return source map entries
+ */
+ public default List getSourceMapEntries(SourceFile sourceFile) {
+ return getSourceMapEntries(sourceFile, 0, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Removes a {@link SourceMapEntry} from this manager.
+ * @param entry entry to remove
+ * @return true if entry was in the manager
+ * @throws LockException if invoked without exclusive access
+ */
+ public boolean removeSourceMapEntry(SourceMapEntry entry) throws LockException;
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourceMapEntry.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourceMapEntry.java
new file mode 100644
index 0000000000..7af966ae39
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourceMapEntry.java
@@ -0,0 +1,80 @@
+/* ###
+ * 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.model.sourcemap;
+
+import ghidra.program.database.sourcemap.SourceFile;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressRange;
+
+/**
+ * A SourceMapEntry consists of a {@link SourceFile}, a line number, a base address,
+ * and a length. If the length is positive, the base address and the length determine
+ * an {@link AddressRange}. In this case, the length of a {@code SourceMapEntry} is the
+ * length of the associated {@link AddressRange}, i.e., the number of {@link Address}es in
+ * the range (see {@link AddressRange#getLength()}). The intent is that the range
+ * contains all of the bytes corresponding to a given line of source. The length of a
+ * {@code SourceMapEntry} can be 0, in which case the associated range is null. Negative
+ * lengths are not allowed.
+ *
+ * The baseAddress of a range must occur within a memory block of the program, as must each
+ * address within the range of a {@code SourceMapEntry}. A range may span multiple
+ * (contiguous) memory blocks.
+ *
+ * If the ranges of two entries (with non-zero lengths) intersect, then the ranges must be
+ * identical. The associated {@link SourceFile}s and/or line numbers can be different.
+ *
+ * Entries with length zero do not conflict with other entries and may occur within the
+ * range of another entry.
+ *
+ * For a fixed source file, line number, base address, and length, there must be only one
+ * SourceMapEntry.
+ *
+ * SourceMapEntry objects are created using the {@link SourceFileManager} for a program,
+ * which must enforce the restrictions listed above.
+ */
+public interface SourceMapEntry extends Comparable {
+
+ /**
+ * Returns the line number.
+ * @return line number
+ */
+ public int getLineNumber();
+
+ /**
+ * Returns the source file
+ * @return source file
+ */
+ public SourceFile getSourceFile();
+
+ /**
+ * Returns the base address of the entry
+ * @return base address
+ */
+ public Address getBaseAddress();
+
+ /**
+ * Returns the length of the range (number of addresses)
+ * @return length
+ */
+ public long getLength();
+
+ /**
+ * Returns the address range, or null for length 0 entries
+ * @return address range or null
+ */
+ public AddressRange getRange();
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourceMapEntryIterator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourceMapEntryIterator.java
new file mode 100644
index 0000000000..67c7c7f5f9
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourceMapEntryIterator.java
@@ -0,0 +1,44 @@
+/* ###
+ * 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.model.sourcemap;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Interface for iterating over {@link SourceMapEntry}s.
+ */
+public interface SourceMapEntryIterator extends Iterator, Iterable {
+
+ public static final SourceMapEntryIterator EMPTY_ITERATOR = new SourceMapEntryIterator() {
+
+ @Override
+ public Iterator iterator() {
+ return this;
+ }
+
+ @Override
+ public SourceMapEntry next() {
+ throw new NoSuchElementException("Empty iterator is empty!");
+ }
+
+ @Override
+ public boolean hasNext() {
+ return false;
+ }
+ };
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java
index 1950192e8f..9e6fdf3e38 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolUtilities.java
@@ -17,7 +17,6 @@ package ghidra.program.model.symbol;
import java.util.*;
import java.util.function.Consumer;
-import java.util.stream.Stream;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
@@ -137,7 +136,7 @@ public class SymbolUtilities {
/**
* Check for invalid characters
- * (space, colon, asterisk, plus, bracket)
+ * (space or unprintable ascii below 0x20)
* in labels.
*
* @param str the string to be checked for invalid characters.
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramEvent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramEvent.java
index 8cffb56090..e3870e0f8f 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramEvent.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramEvent.java
@@ -147,7 +147,11 @@ public enum ProgramEvent implements EventType {
CODE_UNIT_USER_DATA_CHANGED, // user data has changed for some code unit
USER_DATA_CHANGED, // general user data has changed at some address
- RELOCATION_ADDED; // a relocation entry was added
+ RELOCATION_ADDED, // a relocation entry was added
+
+ SOURCE_FILE_ADDED, // a source file was added
+ SOURCE_FILE_REMOVED, // a source file was removed
+ SOURCE_MAP_CHANGED; // source map information was changed
private final int id = DomainObjectEventIdGenerator.next();
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/SourceMapFieldLocation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/SourceMapFieldLocation.java
new file mode 100644
index 0000000000..7f8062e5aa
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/SourceMapFieldLocation.java
@@ -0,0 +1,47 @@
+/* ###
+ * 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;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+
+/**
+ * A {@link ProgramLocation} for source map information.
+ */
+public class SourceMapFieldLocation extends ProgramLocation {
+
+ private SourceMapEntry sourceMapEntry;
+
+ public SourceMapFieldLocation(Program program, Address addr, int row, int charOffset,
+ SourceMapEntry sourceMapEntry) {
+ super(program, addr, row, 0, charOffset);
+ this.sourceMapEntry = sourceMapEntry;
+ }
+
+ public SourceMapFieldLocation() {
+ // for deserialization
+ }
+
+ /**
+ * Returns the {@link SourceMapEntry} associated with this location.
+ * @return source map entry
+ */
+ public SourceMapEntry getSourceMapEntry() {
+ return sourceMapEntry;
+ }
+
+}
diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/AbstractSourceFileTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/AbstractSourceFileTest.java
new file mode 100644
index 0000000000..838f1f889f
--- /dev/null
+++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/AbstractSourceFileTest.java
@@ -0,0 +1,108 @@
+/* ###
+ * 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.database.sourcemap;
+
+import org.junit.Before;
+
+import generic.test.AbstractGenericTest;
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.test.ToyProgramBuilder;
+
+public class AbstractSourceFileTest extends AbstractGenericTest {
+
+ protected Program program;
+ protected ToyProgramBuilder builder;
+ protected int baseOffset = 0x1001000;
+ protected Address baseAddress;
+ protected SourceFile source1;
+ protected SourceFile source2;
+ protected SourceFile source3;
+ protected Instruction ret2_1;
+ protected Instruction ret2_2;
+ protected Instruction ret2_3;
+ protected Instruction nop1_1;
+ protected Instruction nop1_2;
+ protected Instruction nop1_3;
+ protected Instruction ret2_4;
+ protected Instruction nop1_4;
+ protected String path1 = "/test1";
+ protected String path2 = "/test2/test2";
+ protected String path3 = "/test3/test3/test3";
+ protected String path4 = "/test4/test4/test4/test4";
+ protected SourceFileManager sourceManager;
+
+ @Before
+ public void setUp() throws Exception {
+ builder = new ToyProgramBuilder("testprogram", true, false, this);
+ int txID = builder.getProgram().startTransaction("create source map test program");
+ long currentOffset = baseOffset;
+ try {
+ builder.createMemory(".text", Integer.toHexString(baseOffset), 64).setExecute(true);
+ builder.addBytesReturn(currentOffset);
+ currentOffset += 2;
+ builder.addBytesNOP(currentOffset++, 1);
+ builder.addBytesReturn(currentOffset);
+ currentOffset += 2;
+ builder.addBytesNOP(currentOffset++, 1);
+ builder.addBytesReturn(currentOffset);
+ currentOffset += 2;
+ builder.addBytesNOP(currentOffset++, 1);
+ builder.addBytesReturn(currentOffset);
+ currentOffset += 2;
+ builder.addBytesNOP(currentOffset++, 1);
+ builder.disassemble(Integer.toHexString(baseOffset),
+ (int) (currentOffset - baseOffset));
+ }
+ finally {
+ builder.getProgram().endTransaction(txID, true);
+ }
+ program = builder.getProgram();
+ baseAddress = program.getAddressFactory().getDefaultAddressSpace().getAddress(baseOffset);
+ InstructionIterator instIter = program.getListing().getInstructions(baseAddress, true);
+ ret2_1 = instIter.next();
+ nop1_1 = instIter.next();
+ ret2_2 = instIter.next();
+ nop1_2 = instIter.next();
+ ret2_3 = instIter.next();
+ nop1_3 = instIter.next();
+ ret2_4 = instIter.next();
+ nop1_4 = instIter.next();
+ sourceManager = program.getSourceFileManager();
+
+ source1 = new SourceFile(path1);
+ source2 = new SourceFile(path2);
+ source3 = new SourceFile(path3);
+ //leave path4 as String without a corresponding source file
+
+ txID = program.startTransaction("adding source files");
+ try {
+ sourceManager.addSourceFile(source1);
+ sourceManager.addSourceFile(source2);
+ sourceManager.addSourceFile(source3);
+ }
+ finally {
+ program.endTransaction(txID, true);
+ }
+ builder.dispose();
+ }
+
+ protected AddressRange getBody(CodeUnit cu) {
+ return new AddressRangeImpl(cu.getMinAddress(), cu.getMaxAddress());
+ }
+
+}
diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/GetSourceFilesTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/GetSourceFilesTest.java
new file mode 100644
index 0000000000..013cdba42a
--- /dev/null
+++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/GetSourceFilesTest.java
@@ -0,0 +1,131 @@
+/* ###
+ * 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.database.sourcemap;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import ghidra.framework.store.LockException;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+
+public class GetSourceFilesTest extends AbstractSourceFileTest {
+
+ @Test
+ public void testGetAllSourceFiles() throws LockException {
+ List sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(3, sourceFiles.size());
+ assertTrue(sourceFiles.contains(source1));
+ assertTrue(sourceFiles.contains(source2));
+ assertTrue(sourceFiles.contains(source3));
+
+ int txId = program.startTransaction("deleting source file 2");
+ try {
+ sourceManager.removeSourceFile(source2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(2, sourceFiles.size());
+ assertTrue(sourceFiles.contains(source1));
+ assertTrue(sourceFiles.contains(source3));
+
+ txId = program.startTransaction("deleting source files");
+ try {
+ sourceManager.removeSourceFile(source1);
+ sourceManager.removeSourceFile(source3);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertTrue(sourceManager.getAllSourceFiles().isEmpty());
+ }
+
+ @Test
+ public void testGetMappedSourceFiles() throws LockException {
+ List sourceFiles = sourceManager.getMappedSourceFiles();
+ assertTrue(sourceFiles.isEmpty());
+
+ int txId = program.startTransaction("adding source map info");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, getBody(ret2_1));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ SourceFile file = sourceFiles.get(0);
+ assertEquals(source1.getPath(), file.getPath());
+
+ txId = program.startTransaction("adding source map info");
+ try {
+ sourceManager.addSourceMapEntry(source2, 1, getBody(ret2_2));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(2, sourceFiles.size());
+ assertTrue(sourceFiles.contains(source1));
+ assertTrue(sourceFiles.contains(source2));
+
+ txId = program.startTransaction("transferring source map entries");
+ try {
+ sourceManager.transferSourceMapEntries(source2, source3);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(2, sourceFiles.size());
+ assertTrue(sourceFiles.contains(source1));
+ assertTrue(sourceFiles.contains(source3));
+
+ txId = program.startTransaction("deleting source1");
+ try {
+ sourceManager.removeSourceFile(source1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ assertTrue(sourceFiles.contains(source3));
+
+ txId = program.startTransaction("clearing mapping info for source3");
+ try {
+ List entries = sourceManager.getSourceMapEntries(source3);
+ for (SourceMapEntry entry : entries) {
+ assertTrue(sourceManager.removeSourceMapEntry(entry));
+ }
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertTrue(sourceFiles.isEmpty());
+ }
+}
diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/MappingSourceFilesTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/MappingSourceFilesTest.java
new file mode 100644
index 0000000000..a314f3d74b
--- /dev/null
+++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/MappingSourceFilesTest.java
@@ -0,0 +1,1150 @@
+/* ###
+ * 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.database.sourcemap;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.HexFormat;
+import java.util.List;
+
+import org.junit.Test;
+import org.python.google.common.primitives.Longs;
+
+import ghidra.framework.store.LockException;
+import ghidra.program.model.address.*;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.sourcemap.SourceFileManager;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+import ghidra.util.SourceFileUtils;
+import ghidra.util.SourceFileUtils.SourceLineBounds;
+
+public class MappingSourceFilesTest extends AbstractSourceFileTest {
+
+ @Test
+ public void testNoSourceInfo() {
+ assertTrue(sourceManager.getMappedSourceFiles().isEmpty());
+ assertEquals(3, sourceManager.getAllSourceFiles().size());
+ assertTrue(sourceManager.getSourceMapEntries(ret2_1.getAddress()).isEmpty());
+ assertTrue(sourceManager.getSourceMapEntries(source1).isEmpty());
+ }
+
+ @Test
+ public void testMappingDummySourceManager() {
+ sourceManager = SourceFileManager.DUMMY;
+ assertTrue(sourceManager.getMappedSourceFiles().isEmpty());
+ assertTrue(sourceManager.getAllSourceFiles().isEmpty());
+
+ assertTrue(sourceManager.getSourceMapEntries(ret2_1.getMinAddress()).isEmpty());
+ assertTrue(sourceManager.getSourceMapEntries(source1, 1, 1).isEmpty());
+ assertTrue(sourceManager.getSourceMapEntries(source1, 0, Integer.MAX_VALUE).isEmpty());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNegativeLength() throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), -1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testAddingEntryNullSourceFile() throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(null, 1, ret2_1.getAddress(), 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testRemovingNullSourceMapEntry() throws LockException {
+ int txId = program.startTransaction("removing source map entry");
+ try {
+ sourceManager.removeSourceMapEntry(null);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testGetSourceInfoNullAddress() {
+ assertTrue(sourceManager.getSourceMapEntries((Address) null).isEmpty());
+ }
+
+ @Test
+ public void testGetSourceInfoNoAddress() {
+ assertTrue(sourceManager.getSourceMapEntries(Address.NO_ADDRESS).isEmpty());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSettingNegativeLineNumber() throws LockException {
+
+ int txId = program.startTransaction("setting bad line number info");
+ try {
+ sourceManager.addSourceMapEntry(source1, -1, getBody(ret2_1));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNegativeMinline() {
+ sourceManager.getSourceMapEntries(source1, -1, 10);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNegativeMaxline() {
+ sourceManager.getSourceMapEntries(source1, 1, -1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMaxLineLessThanMin() {
+ sourceManager.getSourceMapEntries(source1, 10, 5);
+ }
+
+ @Test
+ public void testAddingSameEntryTwice() throws LockException {
+
+ int txId = program.startTransaction("adding source map entry");
+ SourceMapEntry entry = null;
+ try {
+ entry = sourceManager.addSourceMapEntry(source1, 1, getBody(ret2_1));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding redundant source map info");
+ SourceMapEntry entry2 = null;
+ try {
+ entry2 = sourceManager.addSourceMapEntry(source1, 1, getBody(ret2_1));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(entry, entry2);
+ assertEquals(1, sourceManager.getSourceMapEntries(source1).size());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddingSimilarEntriesDifferentLengths()
+ throws LockException, AddressOverflowException {
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding incompatible source map info");
+
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testAddingSameLengthZeroEntryTwice()
+ throws LockException, AddressOverflowException {
+
+ int txId = program.startTransaction("adding source map entry");
+ SourceMapEntry entry = null;
+ try {
+ entry = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding redundant source map info");
+ SourceMapEntry entry2 = null;
+ try {
+ entry2 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(entry, entry2);
+ assertEquals(1, sourceManager.getSourceMapEntries(source1).size());
+ }
+
+ @Test
+ public void simpleSettingGettingTest() throws LockException, IOException {
+ assertTrue(sourceManager.getMappedSourceFiles().isEmpty());
+ assertTrue(sourceManager.getSourceMapEntries(ret2_1.getAddress()).isEmpty());
+
+ int txId = program.startTransaction("adding source map entry");
+ SourceMapEntry source1Entry = null;
+ try {
+ source1Entry = sourceManager.addSourceMapEntry(source1, 1, getBody(ret2_1));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(2, source1Entry.getLength());
+ assertEquals(1, source1Entry.getLineNumber());
+ assertEquals(getBody(ret2_1), source1Entry.getRange());
+ assertEquals(source1, source1Entry.getSourceFile());
+
+ List sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ List entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(1, entries.size());
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress().add(1));
+ assertEquals(1, entries.size());
+ entries = sourceManager.getSourceMapEntries(nop1_1.getAddress());
+ assertEquals(0, entries.size());
+
+ txId = program.startTransaction("adding source map entry");
+ SourceMapEntry source2Entry = null;
+ try {
+ source2Entry = sourceManager.addSourceMapEntry(source2, 2, getBody(ret2_2));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(2, sourceFiles.size());
+ assertTrue(sourceFiles.contains(source1));
+ assertTrue(sourceFiles.contains(source2));
+
+ List entries1 = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(1, entries1.size());
+ assertEquals(source1Entry, entries1.get(0));
+
+ List entries2 = sourceManager.getSourceMapEntries(ret2_2.getAddress());
+ assertEquals(1, entries2.size());
+ assertEquals(source2Entry, entries2.get(0));
+
+ program.undo();
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(1, entries.size());
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress().add(1));
+ assertEquals(1, entries.size());
+ entries = sourceManager.getSourceMapEntries(nop1_1.getAddress());
+ assertEquals(0, entries.size());
+
+ }
+
+ @Test
+ public void testMultipleEntriesOneAddress() throws LockException {
+ assertTrue(sourceManager.getSourceMapEntries(ret2_1.getAddress()).isEmpty());
+ AddressRange range = getBody(ret2_1);
+
+ SourceMapEntry entry_1_1 = null;
+ SourceMapEntry entry_1_2 = null;
+ SourceMapEntry entry_2_1 = null;
+
+ int txId = program.startTransaction("adding first source map entry");
+ try {
+ entry_1_1 = sourceManager.addSourceMapEntry(source1, 1, range);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(0, sourceManager.getSourceMapEntries(ret2_1.getAddress().subtract(1)).size());
+
+ List entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(1, entries.size());
+ assertTrue(entries.contains(entry_1_1));
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress().add(1));
+ assertEquals(1, entries.size());
+ assertTrue(entries.contains(entry_1_1));
+
+ assertEquals(0, sourceManager.getSourceMapEntries(ret2_1.getAddress().add(2)).size());
+
+ txId = program.startTransaction("adding second source map entry");
+ try {
+ entry_1_2 = sourceManager.addSourceMapEntry(source1, 2, range);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(0, sourceManager.getSourceMapEntries(ret2_1.getAddress().subtract(1)).size());
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(2, entries.size());
+ assertTrue(entries.contains(entry_1_1));
+ assertTrue(entries.contains(entry_1_2));
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress().add(1));
+ assertEquals(2, entries.size());
+ assertTrue(entries.contains(entry_1_1));
+ assertTrue(entries.contains(entry_1_2));
+
+ assertEquals(0, sourceManager.getSourceMapEntries(ret2_1.getAddress().add(2)).size());
+
+ txId = program.startTransaction("adding third source map entry");
+ try {
+ entry_2_1 = sourceManager.addSourceMapEntry(source2, 1, range);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(0, sourceManager.getSourceMapEntries(ret2_1.getAddress().subtract(1)).size());
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(3, entries.size());
+ assertTrue(entries.contains(entry_1_1));
+ assertTrue(entries.contains(entry_1_2));
+ assertTrue(entries.contains(entry_2_1));
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress().add(1));
+ assertEquals(3, entries.size());
+ assertTrue(entries.contains(entry_1_1));
+ assertTrue(entries.contains(entry_1_2));
+ assertTrue(entries.contains(entry_2_1));
+
+ assertEquals(0, sourceManager.getSourceMapEntries(ret2_1.getAddress().add(2)).size());
+
+ }
+
+ @Test
+ public void testAddingLengthZeroEntries() throws AddressOverflowException, LockException {
+ assertTrue(sourceManager.getSourceMapEntries(ret2_1.getAddress()).isEmpty());
+ SourceMapEntry entry1 = null;
+ SourceMapEntry entry2 = null;
+ SourceMapEntry entry3 = null;
+ SourceMapEntry entry4 = null;
+
+ int txId = program.startTransaction("adding first source map entry");
+ try {
+ entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding length zero entry");
+ try {
+ entry2 = sourceManager.addSourceMapEntry(source1, 2, ret2_1.getAddress(), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertNull(entry2.getRange());
+
+ txId = program.startTransaction("adding length zero entry");
+ try {
+ entry3 = sourceManager.addSourceMapEntry(source1, 3, ret2_1.getAddress(), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding length zero entry");
+ try {
+ entry4 = sourceManager.addSourceMapEntry(source1, 4, ret2_1.getAddress().add(1L), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List containingEntries =
+ sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(3, containingEntries.size());
+ assertTrue(containingEntries.contains(entry1));
+ assertTrue(containingEntries.contains(entry2));
+ assertTrue(containingEntries.contains(entry3));
+
+ containingEntries = sourceManager.getSourceMapEntries(ret2_1.getAddress().add(1L));
+ assertEquals(2, containingEntries.size());
+ assertTrue(containingEntries.contains(entry1));
+ assertTrue(containingEntries.contains(entry4));
+ }
+
+ @Test
+ public void testRemovingEntries() throws LockException, IOException {
+
+ Address min = ret2_1.getMinAddress();
+ Address max = ret2_1.getMaxAddress();
+ AddressRange range = new AddressRangeImpl(min, max);
+ SourceMapEntry entry = null;
+
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ entry = sourceManager.addSourceMapEntry(source1, 1, range);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ assertNotNull(entry);
+ assertEquals(1, sourceManager.getSourceMapEntries(min).size());
+ assertEquals(1, sourceManager.getSourceMapEntries(max).size());
+
+ txId = program.startTransaction("removing source map entry");
+ try {
+ assertTrue(sourceManager.removeSourceMapEntry(entry));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertTrue(sourceManager.getSourceMapEntries(min).isEmpty());
+ assertTrue(sourceManager.getSourceMapEntries(max).isEmpty());
+
+ program.undo();
+ assertEquals(1, sourceManager.getSourceMapEntries(min).size());
+ assertEquals(1, sourceManager.getSourceMapEntries(max).size());
+
+ program.redo();
+ assertTrue(sourceManager.getSourceMapEntries(min).isEmpty());
+ assertTrue(sourceManager.getSourceMapEntries(max).isEmpty());
+ }
+
+ public void testRemovingRemovedWithLengthZero() throws AddressOverflowException, LockException {
+ SourceMapEntry entry = null;
+
+ int txId = program.startTransaction("adding source map entries");
+ try {
+ entry = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("removing source map entry");
+ try {
+ assertTrue(sourceManager.removeSourceMapEntry(entry));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("removing source map entry");
+ try {
+ assertFalse(sourceManager.removeSourceMapEntry(entry));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddingConflictingEntries() throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding conflicting source map info");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 3);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddingOverlappingEntrySameSourceInfoBefore()
+ throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, ret2_2.getAddress(), ret2_2.getLength());
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding overlapping source map info");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, nop1_1.getAddress(),
+ nop1_1.getLength() + 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddingOverlappingEntryDifferentSourceInfoBefore()
+ throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, ret2_2.getAddress(), ret2_2.getLength());
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding overlapping source map info");
+ try {
+ sourceManager.addSourceMapEntry(source2, 2, nop1_1.getAddress(),
+ nop1_1.getLength() + 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddingOverlappingEntrySameSourceInfoAfter()
+ throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(),
+ ret2_1.getLength() + 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding overlapping source map info");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, nop1_1.getAddress(), nop1_1.getLength());
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddingOverlappingEntryDifferentSourceInfoAfter()
+ throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(),
+ ret2_1.getLength() + 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding overlapping source map info");
+ try {
+ sourceManager.addSourceMapEntry(source2, 2, nop1_1.getAddress(), nop1_1.getLength());
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testAdjacentEntries() throws LockException, AddressOverflowException {
+ int txId = program.startTransaction("setting source map info");
+ SourceMapEntry entry1 = null;
+ SourceMapEntry entry2 = null;
+ SourceMapEntry entry3 = null;
+ SourceMapEntry entry4 = null;
+ SourceMapEntry entry5 = null;
+ try {
+ entry1 = sourceManager.addSourceMapEntry(source1, 1, getBody(ret2_2));
+ entry2 = sourceManager.addSourceMapEntry(source1, 2, getBody(nop1_2));
+ entry3 = sourceManager.addSourceMapEntry(source2, 3, ret2_2.getAddress(), 0);
+ entry4 = sourceManager.addSourceMapEntry(source3, 4, ret2_2.getAddress().add(1), 0);
+ entry5 = sourceManager.addSourceMapEntry(source2, 5, nop1_2.getAddress(), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(ret2_2.getAddress());
+ assertEquals(2, entries.size());
+ assertTrue(entries.contains(entry1));
+ assertTrue(entries.contains(entry3));
+
+ entries = sourceManager.getSourceMapEntries(ret2_2.getAddress().add(1));
+ assertEquals(2, entries.size());
+ assertTrue(entries.contains(entry1));
+ assertTrue(entries.contains(entry4));
+
+ assertEquals(ret2_2.getAddress().add(2), nop1_2.getAddress());
+ entries = sourceManager.getSourceMapEntries(nop1_2.getAddress());
+ assertTrue(entries.contains(entry2));
+ assertTrue(entries.contains(entry5));
+
+ entries = sourceManager.getSourceMapEntries(ret2_2.getAddress().subtract(1));
+ assertEquals(0, entries.size());
+
+ entries = sourceManager.getSourceMapEntries(nop1_2.getAddress().add(1));
+ assertEquals(0, entries.size());
+ }
+
+ @Test
+ public void testClearingAllEntriesForOneSourceFile() throws LockException {
+
+ int txId = program.startTransaction("setting source map info");
+ SourceMapEntry source1Entry = null;
+ SourceMapEntry source3Entry = null;
+ try {
+ source1Entry = sourceManager.addSourceMapEntry(source1, 1, getBody(ret2_1));
+ sourceManager.addSourceMapEntry(source2, 2, getBody(ret2_2));
+ source3Entry = sourceManager.addSourceMapEntry(source3, 3, getBody(ret2_3));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(3, sourceFiles.size());
+
+ List source2Entries =
+ sourceManager.getSourceMapEntries(source2, 0, Integer.MAX_VALUE);
+ assertEquals(1, source2Entries.size());
+ List source2EntriesRestricted =
+ sourceManager.getSourceMapEntries(source2, 2, 2);
+ assertEquals(1, source2EntriesRestricted.size());
+ assertEquals(source2Entries.get(0), source2EntriesRestricted.get(0));
+ assertTrue(sourceManager.getSourceMapEntries(source1, 4, 5).isEmpty());
+
+ txId = program.startTransaction("clearing source info for source2");
+ try {
+ for (SourceMapEntry entry : source2Entries) {
+ assertTrue(sourceManager.removeSourceMapEntry(entry));
+ }
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(2, sourceFiles.size());
+ assertFalse(sourceFiles.contains(source2));
+
+ assertEquals(1, sourceManager.getSourceMapEntries(ret2_1.getAddress()).size());
+ assertEquals(source1Entry, sourceManager.getSourceMapEntries(ret2_1.getAddress()).get(0));
+ assertTrue(sourceManager.getSourceMapEntries(ret2_2.getAddress()).isEmpty());
+ assertEquals(1, sourceManager.getSourceMapEntries(ret2_3.getAddress()).size());
+ assertEquals(source3Entry, sourceManager.getSourceMapEntries(ret2_3.getAddress()).get(0));
+ }
+
+ @Test
+ public void testDeletingSourceFile() throws LockException, IOException {
+
+ SourceMapEntry entry2 = null;
+ int txId = program.startTransaction("setting source map info");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, getBody(ret2_1));
+ entry2 = sourceManager.addSourceMapEntry(source2, 2, getBody(ret2_2));
+ sourceManager.addSourceMapEntry(source1, 3, getBody(ret2_1));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(2, sourceFiles.size());
+ assertTrue(sourceFiles.contains(source1));
+ assertTrue(sourceFiles.contains(source2));
+
+ List source2Entries =
+ sourceManager.getSourceMapEntries(ret2_2.getAddress());
+ assertEquals(1, source2Entries.size());
+ assertEquals(entry2, source2Entries.get(0));
+
+ txId = program.startTransaction("deleting source file");
+ try {
+ assertTrue(sourceManager.removeSourceFile(source2));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ SourceFile mappedFile = sourceFiles.get(0);
+ assertEquals(source1, mappedFile);
+ assertTrue(sourceManager.getSourceMapEntries(ret2_2.getAddress()).isEmpty());
+
+ // undo delete and verify that everything is restored
+ program.undo();
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(2, sourceFiles.size());
+ assertTrue(sourceFiles.contains(source1));
+ assertTrue(sourceFiles.contains(source2));
+
+ assertEquals(1, sourceManager.getSourceMapEntries(ret2_2.getAddress()).size());
+ SourceMapEntry sourceInfo = sourceManager.getSourceMapEntries(ret2_2.getAddress()).get(0);
+ assertEquals(2, sourceInfo.getLineNumber());
+ assertEquals(source2, sourceInfo.getSourceFile());
+
+ // redo delete and verify
+ program.redo();
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ assertEquals(source1, sourceFiles.get(0));
+ assertTrue(sourceManager.getSourceMapEntries(ret2_2.getAddress()).isEmpty());
+ }
+
+ @Test
+ public void testTransferringSourceMapInfo() throws LockException {
+ assertTrue(sourceManager.getMappedSourceFiles().isEmpty());
+
+ AddressRange both = new AddressRangeImpl(ret2_1.getMinAddress(), ret2_2.getMaxAddress());
+
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, both);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ assertEquals(source1, sourceFiles.get(0));
+ assertEquals(1, sourceManager.getSourceMapEntries(source1).size());
+ assertTrue(sourceManager.getSourceMapEntries(source2).isEmpty());
+
+ // test redundant transfer
+ txId = program.startTransaction("transferring source map entries");
+ try {
+ sourceManager.transferSourceMapEntries(source1, new SourceFile(path1));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ assertEquals(source1, sourceFiles.get(0));
+ assertEquals(1, sourceManager.getSourceMapEntries(source1).size());
+ assertTrue(sourceManager.getSourceMapEntries(source2).isEmpty());
+
+ txId = program.startTransaction("transferring source map entries");
+ try {
+ sourceManager.transferSourceMapEntries(source1, source2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ assertEquals(source2, sourceFiles.get(0));
+ assertTrue(sourceManager.getSourceMapEntries(source1).isEmpty());
+ assertEquals(1, sourceManager.getSourceMapEntries(source2).size());
+
+ List entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(1, entries.size());
+ SourceMapEntry entry = entries.get(0);
+ assertEquals(source2, entry.getSourceFile());
+ assertEquals(1, entry.getLineNumber());
+ assertEquals(both, entry.getRange());
+
+ // test transfer of files with no source map entries
+ assertEquals(0, sourceManager.getSourceMapEntries(source1).size());
+ assertEquals(1, sourceManager.getSourceMapEntries(source2).size());
+ assertEquals(0, sourceManager.getSourceMapEntries(source3).size());
+ txId = program.startTransaction("transferring source map entries");
+ try {
+ sourceManager.transferSourceMapEntries(source3, source1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ assertEquals(0, sourceManager.getSourceMapEntries(source1).size());
+ assertEquals(1, sourceManager.getSourceMapEntries(source2).size());
+ assertEquals(0, sourceManager.getSourceMapEntries(source3).size());
+ }
+
+ @Test
+ public void testIntersectsSourceFileEntry() throws LockException, AddressOverflowException {
+ assertTrue(sourceManager.getMappedSourceFiles().isEmpty());
+ AddressSet everything = program.getAddressFactory().getAddressSet();
+ assertFalse(sourceManager.intersectsSourceMapEntry(everything));
+ assertFalse(sourceManager.intersectsSourceMapEntry(null));
+ assertFalse(sourceManager.intersectsSourceMapEntry(new AddressSet()));
+ assertFalse(sourceManager.intersectsSourceMapEntry(new AddressSet(Address.NO_ADDRESS)));
+
+ int txId = program.startTransaction("setting source map info");
+ try {
+ sourceManager.addSourceMapEntry(source2, 2, ret2_2.getAddress(), 0);
+ sourceManager.addSourceMapEntry(source3, 3, getBody(ret2_3));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertTrue(sourceManager.intersectsSourceMapEntry(everything));
+ assertFalse(sourceManager.intersectsSourceMapEntry(new AddressSet(Address.NO_ADDRESS)));
+ assertFalse(sourceManager.intersectsSourceMapEntry(null));
+ assertFalse(sourceManager.intersectsSourceMapEntry(new AddressSet()));
+
+ // test with a length 0 entry
+ assertFalse(sourceManager
+ .intersectsSourceMapEntry(new AddressSet(ret2_2.getAddress().subtract(1))));
+ assertTrue(sourceManager.intersectsSourceMapEntry(new AddressSet(ret2_2.getAddress())));
+ assertFalse(
+ sourceManager.intersectsSourceMapEntry(new AddressSet(ret2_2.getAddress().add(1))));
+
+ assertFalse(sourceManager
+ .intersectsSourceMapEntry(new AddressSet(ret2_3.getAddress().subtract(1))));
+ assertTrue(sourceManager.intersectsSourceMapEntry(new AddressSet(ret2_3.getAddress())));
+ assertTrue(
+ sourceManager.intersectsSourceMapEntry(new AddressSet(ret2_3.getAddress().add(1))));
+ assertFalse(
+ sourceManager.intersectsSourceMapEntry(new AddressSet(ret2_3.getAddress().add(2))));
+
+ assertTrue(sourceManager.intersectsSourceMapEntry(new AddressSet(getBody(ret2_3))));
+
+ }
+
+ @Test
+ public void testSourceLineBounds() throws AddressOverflowException, LockException {
+ assertNull(SourceFileUtils.getSourceLineBounds(program, source1));
+ assertNull(SourceFileUtils.getSourceLineBounds(program, source2));
+ assertNull(SourceFileUtils.getSourceLineBounds(program, source3));
+
+ int txId = program.startTransaction("Adding source map entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 10, ret2_1.getAddress(), 4);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ SourceLineBounds bounds = SourceFileUtils.getSourceLineBounds(program, source1);
+ assertEquals(10, bounds.min());
+ assertEquals(10, bounds.max());
+ assertNull(SourceFileUtils.getSourceLineBounds(program, source2));
+ assertNull(SourceFileUtils.getSourceLineBounds(program, source3));
+
+ txId = program.startTransaction("Adding source map entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 5, ret2_1.getAddress(), 4);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ bounds = SourceFileUtils.getSourceLineBounds(program, source1);
+ assertEquals(5, bounds.min());
+ assertEquals(10, bounds.max());
+ assertNull(SourceFileUtils.getSourceLineBounds(program, source2));
+ assertNull(SourceFileUtils.getSourceLineBounds(program, source3));
+
+ txId = program.startTransaction("Adding source map entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 20, ret2_3.getAddress(), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ bounds = SourceFileUtils.getSourceLineBounds(program, source1);
+ assertEquals(5, bounds.min());
+ assertEquals(20, bounds.max());
+ assertNull(SourceFileUtils.getSourceLineBounds(program, source2));
+ assertNull(SourceFileUtils.getSourceLineBounds(program, source3));
+ }
+
+ @Test
+ public void testUniquenessAfterTransfer() throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("Adding source map entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 10, ret2_1.getAddress(), 4);
+ sourceManager.addSourceMapEntry(source2, 10, ret2_1.getAddress(), 4);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ assertEquals(2, sourceManager.getSourceMapEntries(ret2_1.getAddress()).size());
+
+ txId = program.startTransaction("transferring source map entries");
+ try {
+ sourceManager.transferSourceMapEntries(source1, source2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(1, sourceManager.getSourceMapEntries(ret2_1.getAddress()).size());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testAddingEntryBeforeAddingFile() throws AddressOverflowException, LockException {
+ SourceFile sourceFile = new SourceFile("/src/test/file123.cc");
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(sourceFile, 1, ret2_1.getAddress(), 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransferringSourceFileInfoSourceNotAdded() throws LockException {
+ SourceFile sourceFile = new SourceFile("/src/test/file123.cc");
+ int txId = program.startTransaction("transferring source map entries");
+ try {
+ sourceManager.transferSourceMapEntries(sourceFile, source1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransferringSourceFileInfoTargetNotAdded() throws LockException {
+ SourceFile sourceFile = new SourceFile("/src/test/file123.cc");
+ int txId = program.startTransaction("transferring source map entries");
+ try {
+ sourceManager.transferSourceMapEntries(source1, sourceFile);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMappingSamePathDifferentIdentifiers()
+ throws AddressOverflowException, LockException {
+ HexFormat hexFormat = HexFormat.of();
+ assertEquals(3, sourceManager.getAllSourceFiles().size());
+ String path = "/src/test/file.c";
+ SourceFile test1 = new SourceFile(path);
+ SourceFile test2 = new SourceFile(path, SourceFileIdType.MD5,
+ hexFormat.parseHex("0123456789abcdef0123456789abcdef"));
+ SourceFile test3 =
+ new SourceFile(path, SourceFileIdType.TIMESTAMP_64, Longs.toByteArray(0));
+ SourceMapEntry entry1 = null;
+ SourceMapEntry entry2 = null;
+ SourceMapEntry entry3 = null;
+
+ List sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(0, sourceFiles.size());
+
+ List entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(0, entries.size());
+
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceFile(test1);
+ entry1 = sourceManager.addSourceMapEntry(test1, 1, ret2_1.getAddress(), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ assertEquals(test1, sourceFiles.get(0));
+
+ assertEquals(1, sourceManager.getSourceMapEntries(test1).size());
+ assertTrue(sourceManager.getSourceMapEntries(test2).isEmpty());
+ assertTrue(sourceManager.getSourceMapEntries(test3).isEmpty());
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(1, entries.size());
+ assertEquals(entry1, entries.get(0));
+
+ txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceFile(test2);
+ entry2 = sourceManager.addSourceMapEntry(test2, 2, ret2_1.getAddress(), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(2, sourceFiles.size());
+ assertTrue(sourceFiles.contains(test1));
+ assertTrue(sourceFiles.contains(test2));
+
+ assertEquals(1, sourceManager.getSourceMapEntries(test1).size());
+ assertEquals(1, sourceManager.getSourceMapEntries(test2).size());
+ assertTrue(sourceManager.getSourceMapEntries(test3).isEmpty());
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(2, entries.size());
+ assertTrue(entries.contains(entry1));
+ assertTrue(entries.contains(entry2));
+
+ txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceFile(test3);
+ entry3 = sourceManager.addSourceMapEntry(test3, 3, ret2_1.getAddress(), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(3, sourceFiles.size());
+ assertTrue(sourceFiles.contains(test1));
+ assertTrue(sourceFiles.contains(test2));
+ assertTrue(sourceFiles.contains(test3));
+
+ assertEquals(1, sourceManager.getSourceMapEntries(test1).size());
+ assertEquals(1, sourceManager.getSourceMapEntries(test2).size());
+ assertEquals(1, sourceManager.getSourceMapEntries(test3).size());
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(3, entries.size());
+ assertTrue(entries.contains(entry1));
+ assertTrue(entries.contains(entry2));
+ assertTrue(entries.contains(entry3));
+
+ txId = program.startTransaction("removing source map entry");
+ try {
+ assertTrue(sourceManager.removeSourceMapEntry(entry3));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(2, sourceFiles.size());
+ assertTrue(sourceFiles.contains(test1));
+ assertTrue(sourceFiles.contains(test2));
+
+ assertEquals(1, sourceManager.getSourceMapEntries(test1).size());
+ assertEquals(1, sourceManager.getSourceMapEntries(test2).size());
+ assertTrue(sourceManager.getSourceMapEntries(test3).isEmpty());
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(2, entries.size());
+ assertTrue(entries.contains(entry1));
+ assertTrue(entries.contains(entry2));
+
+ txId = program.startTransaction("removing source file");
+ try {
+ assertTrue(sourceManager.removeSourceFile(test2));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getMappedSourceFiles();
+ assertEquals(1, sourceFiles.size());
+ assertTrue(sourceFiles.contains(test1));
+
+ assertEquals(1, sourceManager.getSourceMapEntries(test1).size());
+ assertTrue(sourceManager.getSourceMapEntries(test2).isEmpty());
+ assertTrue(sourceManager.getSourceMapEntries(test3).isEmpty());
+
+ entries = sourceManager.getSourceMapEntries(ret2_1.getAddress());
+ assertEquals(1, entries.size());
+ assertTrue(entries.contains(entry1));
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingEntryAfterBlocks()
+ throws AddressOverflowException, LockException {
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(1, blocks.length);
+ int txId = program.startTransaction("adding entry after block");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blocks[0].getEnd().add(0x10), 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingLengthZeroEntryAfterBlocks()
+ throws AddressOverflowException, LockException {
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(1, blocks.length);
+ int txId = program.startTransaction("adding length zero entry after block");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blocks[0].getEnd().add(0x10), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingEntryOverlappingEndOfBlock()
+ throws AddressOverflowException, LockException {
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(1, blocks.length);
+ int txId = program.startTransaction("adding entry overlapping block end");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blocks[0].getEnd().subtract(5),10);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingEntryBeforeBlocks()
+ throws AddressOverflowException, LockException {
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(1, blocks.length);
+ int txId = program.startTransaction("adding entry after block");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blocks[0].getStart().subtract(0x10), 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingLengthZeroEntryBeforeBlocks()
+ throws AddressOverflowException, LockException {
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(1, blocks.length);
+ int txId = program.startTransaction("adding length zero entry after block");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blocks[0].getStart().subtract(0x10), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingEntryOverlappingStartOfBlock()
+ throws AddressOverflowException, LockException {
+ MemoryBlock[] blocks = program.getMemory().getBlocks();
+ assertEquals(1, blocks.length);
+ int txId = program.startTransaction("adding entry overlapping block end");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blocks[0].getStart().subtract(5), 10);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+
+
+}
diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/ModifyingMemoryMappingSourceFilesTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/ModifyingMemoryMappingSourceFilesTest.java
new file mode 100644
index 0000000000..4ff9ecf638
--- /dev/null
+++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/ModifyingMemoryMappingSourceFilesTest.java
@@ -0,0 +1,1207 @@
+/* ###
+ * 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.database.sourcemap;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import ghidra.framework.store.LockException;
+import ghidra.program.model.address.*;
+import ghidra.program.model.mem.*;
+import ghidra.program.model.sourcemap.SourceMapEntry;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.NotFoundException;
+import ghidra.util.task.TaskMonitor;
+
+public class ModifyingMemoryMappingSourceFilesTest extends AbstractSourceFileTest {
+
+ protected SourceFileManagerDB sourceDB;
+ private MemoryBlock textBlock;
+ private Address blockStart;
+ private Memory memory;
+
+ @Before
+ public void init() {
+ sourceDB = (SourceFileManagerDB) sourceManager;
+ textBlock = program.getMemory().getBlock(".text");
+ blockStart = textBlock.getStart();
+ memory = program.getMemory();
+ }
+
+ @Test(expected = AddressOverflowException.class)
+ public void testAddingEntryWrappingSpace() throws LockException, IllegalArgumentException,
+ MemoryConflictException, AddressOverflowException, CancelledException {
+ Address spaceMin = blockStart.getAddressSpace().getMinAddress();
+ Address spaceMax = blockStart.getAddressSpace().getMaxAddress();
+ int txId = program.startTransaction("initializing memory");
+ try {
+ memory.createInitializedBlock("min", spaceMin, 10L, (byte) 0, TaskMonitor.DUMMY, false);
+ memory.createInitializedBlock("max", spaceMax.subtract(10), 10, (byte) 0,
+ TaskMonitor.DUMMY, false);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding wrapping entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, spaceMax.subtract(5), 10);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingEntryCompletelyOutsideBlock()
+ throws AddressOverflowException, LockException {
+ Address start = textBlock.getStart().subtract(10);
+ assertFalse(memory.intersects(start.subtract(10), start.subtract(5)));
+
+ int txId = program.startTransaction("adding entry outside block");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, start.subtract(10), 5);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingLengthZeroEntryOutsideBlock()
+ throws AddressOverflowException, LockException {
+ Address start = textBlock.getStart().subtract(10);
+ assertFalse(memory.contains(start.subtract(10)));
+
+ int txId = program.startTransaction("adding length 0 entry outside block");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, start.subtract(10), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingEntryUndefinedStart() throws AddressOverflowException, LockException {
+ Address start = textBlock.getStart().subtract(5);
+ assertFalse(memory.contains(start));
+
+ int txId = program.startTransaction("adding entry with undefined start");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, start, 10);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingEntryUndefinedEnd() throws LockException {
+ Address end = textBlock.getEnd().add(5);
+ assertFalse(memory.contains(end));
+
+ int txId = program.startTransaction("adding entry with undefined end");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1,
+ new AddressRangeImpl(textBlock.getEnd().subtract(5), end));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = AddressOutOfBoundsException.class)
+ public void testAddingEntrySpanningUndefinedRange()
+ throws MemoryBlockException, LockException, NotFoundException,
+ AddressOverflowException {
+ Address removeStart = blockStart.add(10);
+ Address removeEnd = blockStart.add(15);
+ assertTrue(memory.contains(removeStart, removeEnd));
+
+ int txId = program.startTransaction("removing range");
+ try {
+ memory.split(textBlock, removeStart);
+ MemoryBlock blockToSplit = memory.getBlock(removeStart);
+ memory.split(blockToSplit, removeEnd.add(1));
+ MemoryBlock blockToRemove = memory.getBlock(removeStart);
+ memory.removeBlock(blockToRemove, TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ assertFalse(memory.intersects(removeStart, removeEnd));
+
+ txId = program.startTransaction("adding entry containing removed range");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, removeStart.subtract(5), 20);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testSetImageBase() throws AddressOverflowException, LockException {
+ blockStart = textBlock.getStart();
+
+ int txId = program.startTransaction("adding entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart, 0x10);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(0x20), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("Setting image base");
+ try {
+ program.setImageBase(program.getImageBase().add(0x200000), true);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(source1);
+ assertEquals(1, entries.size());
+ SourceMapEntry entry = entries.get(0);
+ assertEquals(source1, entry.getSourceFile());
+ assertEquals(1, entry.getLineNumber());
+ assertEquals(blockStart.add(0x200000), entry.getBaseAddress());
+ assertEquals(0x10, entry.getLength());
+
+ entries = sourceManager.getSourceMapEntries(source2);
+ assertEquals(1, entries.size());
+ entry = entries.get(0);
+ assertEquals(source2, entry.getSourceFile());
+ assertEquals(2, entry.getLineNumber());
+ assertEquals(blockStart.add(0x200000).add(0x20), entry.getBaseAddress());
+ assertEquals(0, entry.getLength());
+ }
+
+ @Test
+ public void testAddingEntrySpanningAdjacentBlocks() throws MemoryBlockException, LockException,
+ NotFoundException, AddressOutOfBoundsException, AddressOverflowException {
+ blockStart = textBlock.getStart();
+
+ int txId = program.startTransaction("splitting block");
+ try {
+ program.getMemory().split(textBlock, blockStart.add(20));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ MemoryBlock leftBlock = program.getMemory().getBlock(blockStart);
+ txId = program.startTransaction("splitting left block");
+ try {
+ program.getMemory().split(leftBlock, blockStart.add(10));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(5), 40);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(source1);
+ assertEquals(1, entries.size());
+ SourceMapEntry entry = entries.get(0);
+ assertEquals(source1, entry.getSourceFile());
+ assertEquals(1, entry.getLineNumber());
+ assertEquals(blockStart.add(5), entry.getBaseAddress());
+ assertEquals(40, entry.getLength());
+ }
+
+ @Test
+ public void testSplittingBlockContainingEntry()
+ throws AddressOverflowException, LockException, MemoryBlockException, NotFoundException,
+ AddressOutOfBoundsException {
+
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(5), 20);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ txId = program.startTransaction("splitting block");
+ try {
+ program.getMemory().split(textBlock, blockStart.add(10));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(source1);
+ assertEquals(1, entries.size());
+ SourceMapEntry entry = entries.get(0);
+ assertEquals(source1, entry.getSourceFile());
+ assertEquals(1, entry.getLineNumber());
+ assertEquals(blockStart.add(5), entry.getBaseAddress());
+ assertEquals(20, entry.getLength());
+ }
+
+ private void setUpNoEntriesInRegionTest()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+ int txId = program.startTransaction("adding source map entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(5), 3);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(5), 3);
+ sourceManager.addSourceMapEntry(source3, 3, blockStart.add(7), 0);
+ sourceManager.addSourceMapEntry(source1, 11, blockStart.add(25), 6);
+ sourceManager.addSourceMapEntry(source2, 22, blockStart.add(25), 6);
+ sourceManager.addSourceMapEntry(source3, 33, blockStart.add(27), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ private void checkNoEntriesInRegion() {
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source1).size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 3);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(25), 6);
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source2).size());
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 3);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(25), 6);
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source3).size());
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(7), 0);
+
+ entries = sourceManager.getSourceMapEntries(source3, 33);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(27), 0);
+ }
+
+ private void checkBaseAddressAndLength(SourceMapEntry entry, Address baseAddr, long length) {
+ assertEquals(baseAddr, entry.getBaseAddress());
+ assertEquals(length, entry.getLength());
+ }
+
+
+ @Test
+ public void testMovingRegionWithNoEntries()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpNoEntriesInRegionTest();
+ int txId = program.startTransaction("moving region");
+ try {
+ sourceDB.moveAddressRange(blockStart.add(10), blockStart.add(0x50000), 10,
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ checkNoEntriesInRegion();
+ }
+
+ @Test
+ public void testDeletingRegionWithNoEntries() throws AddressOverflowException, LockException,
+ AddressOutOfBoundsException, CancelledException {
+ setUpNoEntriesInRegionTest();
+ int txId = program.startTransaction("deleting region");
+ try {
+ sourceDB.deleteAddressRange(blockStart.add(10), blockStart.add(20), TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ checkNoEntriesInRegion();
+ }
+
+ private void setUpRegionContainingEntries()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+
+ int txId = program.startTransaction("adding entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(10 + 1), 2);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(10 + 1), 2);
+ sourceManager.addSourceMapEntry(source3, 3, blockStart.add(10 + 2), 0);
+ sourceManager.addSourceMapEntry(source1, 11, blockStart.add(10 + 6), 3);
+ sourceManager.addSourceMapEntry(source2, 22, blockStart.add(10 + 6), 3);
+ sourceManager.addSourceMapEntry(source3, 33, blockStart.add(10 + 7), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMovingRegionContainingEntries()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpRegionContainingEntries();
+
+ int txId = program.startTransaction("moving region");
+ try {
+ sourceDB.moveAddressRange(blockStart.add(10), blockStart.add(0x100000), 10,
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source1).size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000 + 1), 2);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000 + 6), 3);
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source2).size());
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000 + 1), 2);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000 + 6), 3);
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source3).size());
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000 + 2), 0);
+
+ entries = sourceManager.getSourceMapEntries(source3, 33);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000 + 7), 0);
+
+ }
+
+ @Test
+ public void testDeletingRegionContainingEntries()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpRegionContainingEntries();
+
+ int txId = program.startTransaction("deleting region");
+ try {
+ sourceDB.deleteAddressRange(blockStart.add(10), blockStart.add(20), TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(0, sourceManager.getMappedSourceFiles().size());
+ assertEquals(0, sourceManager.getSourceMapEntries(source1).size());
+ assertEquals(0, sourceManager.getSourceMapEntries(source2).size());
+ assertEquals(0, sourceManager.getSourceMapEntries(source3).size());
+ }
+
+ private void setUpOverlappingStart()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+ int txId = program.startTransaction("adding entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(5), 10);
+ sourceManager.addSourceMapEntry(source1, 11, blockStart.add(5), 10);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(5), 10);
+ sourceManager.addSourceMapEntry(source2, 22, blockStart.add(5), 10);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMovingRegionWithEntryOverlappingStart()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpOverlappingStart();
+ int txId = program.startTransaction("moving region");
+ try {
+ sourceDB.moveAddressRange(blockStart.add(10), blockStart.add(0x100000), 10,
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 5);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 5);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 5);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 5);
+
+ }
+
+ @Test
+ public void testDeletingRegionWithEntryOverlappingStart()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpOverlappingStart();
+ int txId = program.startTransaction("deleting region");
+ try {
+ sourceDB.deleteAddressRange(blockStart.add(10), blockStart.add(100), TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+
+ }
+
+ private void setUpOverlappingEnd()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+ int txId = program.startTransaction("adding entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(15), 10);
+ sourceManager.addSourceMapEntry(source1, 11, blockStart.add(15), 10);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(15), 10);
+ sourceManager.addSourceMapEntry(source2, 22, blockStart.add(15), 10);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMovingRegionWithEntryOverlappingEnd()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpOverlappingEnd();
+
+ int txId = program.startTransaction("moving region");
+ try {
+ sourceDB.moveAddressRange(blockStart.add(10), blockStart.add(0x100000), 10,
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(20), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100005), 5);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(20), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100005), 5);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(20), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100005), 5);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(20), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100005), 5);
+ }
+
+ @Test
+ public void testDeletingRegionWithEntryOverlappingEnd()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpOverlappingEnd();
+
+ int txId = program.startTransaction("deleting range");
+ try {
+ sourceDB.deleteAddressRange(blockStart.add(10), blockStart.add(20), TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(21), 4);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(21), 4);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(21), 4);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(21), 4);
+ }
+
+ private void setUpContainingRegion()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+ int txId = program.startTransaction("adding entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(5), 20);
+ sourceManager.addSourceMapEntry(source1, 11, blockStart.add(5), 20);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(5), 20);
+ sourceManager.addSourceMapEntry(source2, 22, blockStart.add(5), 20);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMovingRegionContainedWithinEntry()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpContainingRegion();
+
+ int txId = program.startTransaction("moving region");
+ try {
+ sourceDB.moveAddressRange(blockStart.add(10), blockStart.add(0x100000), 10,
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(3, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(20), 5);
+ checkBaseAddressAndLength(entries.get(2), blockStart.add(0x100000), 10);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(3, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(20), 5);
+ checkBaseAddressAndLength(entries.get(2), blockStart.add(0x100000), 10);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(3, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(20), 5);
+ checkBaseAddressAndLength(entries.get(2), blockStart.add(0x100000), 10);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(3, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(20), 5);
+ checkBaseAddressAndLength(entries.get(2), blockStart.add(0x100000), 10);
+ }
+
+ @Test
+ public void testDeletingRegionContainedWithinEntry()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpContainingRegion();
+
+ int txId = program.startTransaction("deleting region");
+ try {
+ sourceDB.deleteAddressRange(blockStart.add(10), blockStart.add(20), TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(21), 4);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(21), 4);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(21), 4);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(5), 5);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(21), 4);
+ }
+
+ private void setUpLengthZeroEntries()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+ int txId = program.startTransaction("adding entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(9), 0);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(9), 0);
+ sourceManager.addSourceMapEntry(source3, 3, blockStart.add(9), 0);
+ sourceManager.addSourceMapEntry(source1, 11, blockStart.add(10), 0);
+ sourceManager.addSourceMapEntry(source2, 22, blockStart.add(10), 0);
+ sourceManager.addSourceMapEntry(source3, 33, blockStart.add(10), 0);
+ sourceManager.addSourceMapEntry(source1, 111, blockStart.add(11), 0);
+ sourceManager.addSourceMapEntry(source2, 222, blockStart.add(11), 0);
+ sourceManager.addSourceMapEntry(source3, 333, blockStart.add(11), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMovingOneAddressLengthZeroEntries()
+ throws AddressOverflowException, CancelledException, AddressOutOfBoundsException,
+ LockException {
+ setUpLengthZeroEntries();
+ int txId = program.startTransaction("moving address");
+ try {
+ sourceDB.moveAddressRange(blockStart.add(10), blockStart.add(0x100000), 1,
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ assertEquals(3, sourceManager.getSourceMapEntries(source1).size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 0);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000), 0);
+
+ entries = sourceManager.getSourceMapEntries(source1, 111);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 0);
+
+ assertEquals(3, sourceManager.getSourceMapEntries(source2).size());
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 0);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000), 0);
+
+ entries = sourceManager.getSourceMapEntries(source2, 222);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 0);
+
+ assertEquals(3, sourceManager.getSourceMapEntries(source3).size());
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 0);
+
+ entries = sourceManager.getSourceMapEntries(source3, 33);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000), 0);
+
+ entries = sourceManager.getSourceMapEntries(source3, 333);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 0);
+ }
+
+ @Test
+ public void testDeletingOneAddressLengthZeroEntries()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpLengthZeroEntries();
+ int txId = program.startTransaction("deleting range");
+ try {
+ sourceDB.deleteAddressRange(blockStart.add(10), blockStart.add(10), TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source1).size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 0);
+
+ entries = sourceManager.getSourceMapEntries(source1, 111);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 0);
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source2).size());
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 0);
+
+ entries = sourceManager.getSourceMapEntries(source2, 222);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 0);
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source3).size());
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 0);
+
+ entries = sourceManager.getSourceMapEntries(source3, 333);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 0);
+ }
+
+ private void setUpLengthOneEntries()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+ int txId = program.startTransaction("adding entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(9), 1);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(9), 1);
+ sourceManager.addSourceMapEntry(source3, 3, blockStart.add(9), 1);
+ sourceManager.addSourceMapEntry(source1, 11, blockStart.add(10), 1);
+ sourceManager.addSourceMapEntry(source2, 22, blockStart.add(10), 1);
+ sourceManager.addSourceMapEntry(source3, 33, blockStart.add(10), 1);
+ sourceManager.addSourceMapEntry(source1, 111, blockStart.add(11), 1);
+ sourceManager.addSourceMapEntry(source2, 222, blockStart.add(11), 1);
+ sourceManager.addSourceMapEntry(source3, 333, blockStart.add(11), 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMoveOneAddressLengthOneEntries()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpLengthOneEntries();
+
+ int txId = program.startTransaction("moving address");
+ try {
+ sourceDB.moveAddressRange(blockStart.add(10), blockStart.add(0x100000), 1,
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ assertEquals(3, sourceManager.getSourceMapEntries(source1).size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000), 1);
+
+ entries = sourceManager.getSourceMapEntries(source1, 111);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+
+ assertEquals(3, sourceManager.getSourceMapEntries(source2).size());
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000), 1);
+
+ entries = sourceManager.getSourceMapEntries(source2, 222);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+
+ assertEquals(3, sourceManager.getSourceMapEntries(source3).size());
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+
+ entries = sourceManager.getSourceMapEntries(source3, 33);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(0x100000), 1);
+
+ entries = sourceManager.getSourceMapEntries(source3, 333);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+ }
+
+ @Test
+ public void testDeleteOneAddressLengthOneEntries() throws AddressOverflowException,
+ LockException, AddressOutOfBoundsException, CancelledException {
+ setUpLengthOneEntries();
+ int txId = program.startTransaction("deleting range");
+ try {
+ sourceDB.deleteAddressRange(blockStart.add(10), blockStart.add(10), TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source1).size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+
+ entries = sourceManager.getSourceMapEntries(source1, 111);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source2).size());
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+
+ entries = sourceManager.getSourceMapEntries(source2, 222);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+
+ assertEquals(2, sourceManager.getSourceMapEntries(source3).size());
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+
+ entries = sourceManager.getSourceMapEntries(source3, 333);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+ }
+
+ private void setUpLengthTwoEntries1()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+ int txId = program.startTransaction("adding entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(9), 2);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(9), 2);
+ sourceManager.addSourceMapEntry(source3, 3, blockStart.add(9), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMoveOneAddressLengthTwoEntries1()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpLengthTwoEntries1();
+ int txId = program.startTransaction("moving address");
+ try {
+ sourceDB.moveAddressRange(blockStart.add(10), blockStart.add(0x100000), 1,
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 1);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 1);
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 1);
+ }
+
+ @Test
+ public void testDeletingOneAddressLength2Entries1()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpLengthTwoEntries1();
+ int txId = program.startTransaction("deleting address");
+ try {
+ sourceDB.deleteAddressRange(blockStart.add(10), blockStart.add(10), TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9), 1);
+ }
+
+ private void setUpLengthTwoEntries2()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+ int txId = program.startTransaction("adding entries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(10), 2);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(10), 2);
+ sourceManager.addSourceMapEntry(source3, 3, blockStart.add(10), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMoveOneAddressLengthTwoEntries2()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpLengthTwoEntries2();
+ int txId = program.startTransaction("moving address");
+ try {
+ sourceDB.moveAddressRange(blockStart.add(10), blockStart.add(0x100000), 1,
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 1);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 1);
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(2, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+ checkBaseAddressAndLength(entries.get(1), blockStart.add(0x100000), 1);
+ }
+
+ @Test
+ public void testDeletingOneAddressLength2Entries2()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpLengthTwoEntries2();
+ int txId = program.startTransaction("deleting address");
+ try {
+ sourceDB.deleteAddressRange(blockStart.add(10), blockStart.add(10), TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11), 1);
+ }
+
+ private void setUpMovingRangeIntersection()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException {
+ int txId = program.startTransaction("adding new blentries");
+ try {
+ sourceManager.addSourceMapEntry(source1, 1, blockStart.add(8), 2);
+ sourceManager.addSourceMapEntry(source2, 2, blockStart.add(8), 2);
+ sourceManager.addSourceMapEntry(source3, 3, blockStart.add(9), 0);
+ sourceManager.addSourceMapEntry(source1, 11, blockStart.add(10), 2);
+ sourceManager.addSourceMapEntry(source2, 22, blockStart.add(10), 2);
+ sourceManager.addSourceMapEntry(source3, 33, blockStart.add(11), 0);
+ sourceManager.addSourceMapEntry(source1, 111, blockStart.add(12), 2);
+ sourceManager.addSourceMapEntry(source2, 222, blockStart.add(12), 2);
+ sourceManager.addSourceMapEntry(source3, 333, blockStart.add(13), 0);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test
+ public void testMovingIntersectionRangesForward()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpMovingRangeIntersection();
+ int txId = program.startTransaction("moving block");
+ try {
+ sourceDB.moveAddressRange(blockStart, blockStart.add(3), textBlock.getSize(),
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(8 + 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(10 + 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source1, 111);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(12 + 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(8 + 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(10 + 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source2, 222);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(12 + 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9 + 3), 0);
+
+ entries = sourceManager.getSourceMapEntries(source3, 33);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11 + 3), 0);
+
+ entries = sourceManager.getSourceMapEntries(source3, 333);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(13 + 3), 0);
+ }
+
+ @Test
+ public void testMovingIntersectionRangesBackward()
+ throws AddressOverflowException, LockException, AddressOutOfBoundsException,
+ CancelledException {
+ setUpMovingRangeIntersection();
+ int txId = program.startTransaction("moving block");
+ try {
+ sourceDB.moveAddressRange(blockStart, blockStart.subtract(3), textBlock.getSize(),
+ TaskMonitor.DUMMY);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ assertEquals(3, sourceManager.getMappedSourceFiles().size());
+
+ List entries = sourceManager.getSourceMapEntries(source1, 1);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(8 - 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source1, 11);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(10 - 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source1, 111);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(12 - 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source2, 2);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(8 - 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source2, 22);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(10 - 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source2, 222);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(12 - 3), 2);
+
+ entries = sourceManager.getSourceMapEntries(source3, 3);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(9 - 3), 0);
+
+ entries = sourceManager.getSourceMapEntries(source3, 33);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(11 - 3), 0);
+
+ entries = sourceManager.getSourceMapEntries(source3, 333);
+ assertEquals(1, entries.size());
+ checkBaseAddressAndLength(entries.get(0), blockStart.add(13 - 3), 0);
+
+ }
+
+
+}
diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/SourceFileTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/SourceFileTest.java
new file mode 100644
index 0000000000..23d3d011fb
--- /dev/null
+++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/SourceFileTest.java
@@ -0,0 +1,508 @@
+/* ###
+ * 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.database.sourcemap;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Test;
+import org.python.google.common.primitives.Longs;
+
+import ghidra.framework.store.LockException;
+import ghidra.util.SourceFileUtils;
+
+public class SourceFileTest extends AbstractSourceFileTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNonFilePathFailure() {
+ new SourceFile("/test/dir/");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRelativePathCreateFailure() {
+ new SourceFile("test1/test2.c");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullPathCreateFailure() {
+ new SourceFile((String) null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptyPathCreateFailure() {
+ new SourceFile(StringUtils.EMPTY);
+ }
+
+ @Test
+ public void testPathNormalization() {
+ assertEquals("/src/dir1/dir2/file.c",
+ new SourceFile("/src/test/../dir1/test/../dir2/file.c").getPath());
+ }
+
+ @Test
+ public void testGetFilename() {
+ assertEquals("file.c", new SourceFile("/src/test/file.c").getFilename());
+ }
+
+ @Test
+ public void testUtilityMethod() {
+ SourceFile linux = SourceFileUtils.getSourceFileFromPathString("/src/test/../file1.c");
+ assertEquals("/src/file1.c", linux.getPath());
+ assertEquals("file1.c", linux.getFilename());
+
+ SourceFile windows =
+ SourceFileUtils.getSourceFileFromPathString("c:\\src\\test\\..\\file1.c");
+ assertEquals("/c:/src/file1.c", windows.getPath());
+ assertEquals("file1.c", windows.getFilename());
+
+ windows =
+ SourceFileUtils.getSourceFileFromPathString("/C:/Users//guest/./temp/../file.exe");
+ assertEquals("/C:/Users/guest/file.exe", windows.getPath());
+ assertEquals("file.exe", windows.getFilename());
+ }
+
+ @Test
+ public void basicAddRemoveTest() throws LockException {
+ assertEquals(3, sourceManager.getAllSourceFiles().size());
+ SourceFile testSource = new SourceFile("/home/user/src/file.c");
+
+ int txId = program.startTransaction("adding source file");
+ try {
+ assertTrue(sourceManager.addSourceFile(testSource));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(4, sourceManager.getAllSourceFiles().size());
+
+ txId = program.startTransaction("adding same source file");
+ try {
+ assertFalse(sourceManager.addSourceFile(testSource));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(4, sourceManager.getAllSourceFiles().size());
+
+ txId = program.startTransaction("removing source file");
+ try {
+ assertTrue(sourceManager.removeSourceFile(testSource));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(3, sourceManager.getAllSourceFiles().size());
+
+ txId = program.startTransaction("removing source file again");
+ try {
+ assertFalse(sourceManager.removeSourceFile(testSource));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals(3, sourceManager.getAllSourceFiles().size());
+ }
+
+ @Test
+ public void testContainsNull() {
+ assertFalse(sourceManager.containsSourceFile(null));
+ }
+
+ @Test
+ public void basicContainsTest() throws LockException, IOException {
+ assertTrue(sourceManager.containsSourceFile(source1));
+ int txId = program.startTransaction("removing source1");
+ try {
+ assertTrue(sourceManager.removeSourceFile(source1));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ assertFalse(sourceManager.containsSourceFile(source1));
+ assertTrue(sourceManager.containsSourceFile(source2));
+ assertTrue(sourceManager.containsSourceFile(source3));
+
+ program.undo();
+
+ assertTrue(sourceManager.containsSourceFile(source1));
+
+ program.redo();
+
+ assertFalse(sourceManager.containsSourceFile(source1));
+ }
+
+ @Test
+ public void testUndoAddingSourceFile() throws LockException, IOException {
+ SourceFile test = new SourceFile("/a/b/c.h");
+ int txId = program.startTransaction("adding source file");
+ try {
+ sourceManager.addSourceFile(test);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertTrue(sourceManager.containsSourceFile(test));
+
+ program.undo();
+
+ assertFalse(sourceManager.containsSourceFile(test));
+
+ program.redo();
+
+ assertTrue(sourceManager.containsSourceFile(test));
+ }
+
+ @Test
+ public void testGetUri() throws URISyntaxException {
+ String path = "/src/test.c";
+ URI uri = new URI("file", null, path, null);
+ SourceFile testFile = new SourceFile(path);
+ assertEquals(uri, testFile.getUri());
+ }
+
+ @Test
+ public void testIdentifierDisplayString() {
+ String path = "/src/test/file.c";
+ HexFormat hexFormat = HexFormat.of();
+
+ SourceFile sourceFile = new SourceFile(path, SourceFileIdType.MD5,
+ hexFormat.parseHex("0123456789abcdef0123456789abcdef"));
+ assertEquals(SourceFileIdType.MD5, sourceFile.getIdType());
+ assertEquals(sourceFile.getIdAsString(), "0123456789abcdef0123456789abcdef");
+
+ sourceFile = new SourceFile(path);
+ assertEquals(SourceFileIdType.NONE, sourceFile.getIdType());
+ assertEquals(StringUtils.EMPTY, sourceFile.getIdAsString());
+
+ sourceFile = new SourceFile(path, SourceFileIdType.SHA1,
+ hexFormat.parseHex("0123456789abcdef0123456789abcdef01234567"));
+ assertEquals(SourceFileIdType.SHA1, sourceFile.getIdType());
+ assertEquals("0123456789abcdef0123456789abcdef01234567", sourceFile.getIdAsString());
+
+ sourceFile = new SourceFile(path, SourceFileIdType.SHA256,
+ hexFormat.parseHex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"));
+ assertEquals(SourceFileIdType.SHA256, sourceFile.getIdType());
+ assertEquals("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
+ sourceFile.getIdAsString());
+
+ sourceFile = new SourceFile(path, SourceFileIdType.SHA512,
+ hexFormat.parseHex(
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" +
+ "abcdef0123456789abcdef0123456789abcdef0123456789abcdef"));
+ assertEquals(
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" +
+ "abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
+ sourceFile.getIdAsString());
+
+ sourceFile = new SourceFile(path, SourceFileIdType.TIMESTAMP_64, Longs.toByteArray(0));
+ assertEquals(SourceFileIdType.TIMESTAMP_64, sourceFile.getIdType());
+ assertEquals("1970-01-01T00:00:00Z", sourceFile.getIdAsString());
+
+ sourceFile = new SourceFile(path, SourceFileIdType.UNKNOWN, new byte[] { 0x12, 0x13 });
+ assertEquals(SourceFileIdType.UNKNOWN, sourceFile.getIdType());
+ assertEquals("1213", sourceFile.getIdAsString());
+ }
+
+ @Test
+ public void testSamePathDifferentIdentifiers() throws LockException {
+ HexFormat hexFormat = HexFormat.of();
+
+ assertEquals(3, sourceManager.getAllSourceFiles().size());
+ String path = "/src/test/file.c";
+ SourceFile test1 = new SourceFile(path);
+ SourceFile test2 = new SourceFile(path, SourceFileIdType.MD5,
+ hexFormat.parseHex("0123456789abcdef0123456789abcdef"));
+ SourceFile test3 =
+ new SourceFile(path, SourceFileIdType.TIMESTAMP_64, Longs.toByteArray(0));
+
+ assertNotEquals(test1, test2);
+ assertNotEquals(test1, test3);
+ assertNotEquals(test2, test3);
+
+ assertFalse(sourceManager.containsSourceFile(test1));
+ assertFalse(sourceManager.containsSourceFile(test2));
+ assertFalse(sourceManager.containsSourceFile(test3));
+
+ int txId = program.startTransaction("adding source file");
+ try {
+ sourceManager.addSourceFile(test1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(4, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertFalse(sourceManager.containsSourceFile(test2));
+ assertFalse(sourceManager.containsSourceFile(test3));
+
+ txId = program.startTransaction("adding source file");
+ try {
+ sourceManager.addSourceFile(test2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(5, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertTrue(sourceManager.containsSourceFile(test2));
+ assertFalse(sourceManager.containsSourceFile(test3));
+
+ txId = program.startTransaction("adding source file");
+ try {
+ sourceManager.addSourceFile(test3);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(6, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertTrue(sourceManager.containsSourceFile(test2));
+ assertTrue(sourceManager.containsSourceFile(test3));
+
+ //repeat adding test3
+ txId = program.startTransaction("adding source file");
+ try {
+ sourceManager.addSourceFile(test3);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(6, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertTrue(sourceManager.containsSourceFile(test2));
+ assertTrue(sourceManager.containsSourceFile(test3));
+
+ txId = program.startTransaction("removing source file");
+ try {
+ sourceManager.removeSourceFile(test2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(5, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertFalse(sourceManager.containsSourceFile(test2));
+ assertTrue(sourceManager.containsSourceFile(test3));
+
+ }
+
+ @Test
+ public void testSameIdentifierDifferentPaths() throws LockException {
+ HexFormat hexFormat = HexFormat.of();
+ assertEquals(3, sourceManager.getAllSourceFiles().size());
+ byte[] md5 = hexFormat.parseHex("0123456789abcdef0123456789abcdef");
+
+ SourceFile test1 = new SourceFile("/src/file1.c", SourceFileIdType.MD5, md5);
+ SourceFile test2 = new SourceFile("/src/file2.c", SourceFileIdType.MD5, md5);
+ SourceFile test3 = new SourceFile("/src/file3.c", SourceFileIdType.MD5, md5);
+
+ assertNotEquals(test1, test2);
+ assertNotEquals(test1, test3);
+ assertNotEquals(test2, test3);
+
+ assertFalse(sourceManager.containsSourceFile(test1));
+ assertFalse(sourceManager.containsSourceFile(test2));
+ assertFalse(sourceManager.containsSourceFile(test3));
+
+ int txId = program.startTransaction("adding source file");
+ try {
+ sourceManager.addSourceFile(test1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ List sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(4, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertFalse(sourceManager.containsSourceFile(test2));
+ assertFalse(sourceManager.containsSourceFile(test3));
+
+ txId = program.startTransaction("adding source file");
+ try {
+ sourceManager.addSourceFile(test2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(5, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertTrue(sourceManager.containsSourceFile(test2));
+ assertFalse(sourceManager.containsSourceFile(test3));
+
+ txId = program.startTransaction("adding source file");
+ try {
+ sourceManager.addSourceFile(test3);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(6, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertTrue(sourceManager.containsSourceFile(test2));
+ assertTrue(sourceManager.containsSourceFile(test3));
+
+ //repeat adding test3
+ txId = program.startTransaction("adding source file");
+ try {
+ sourceManager.addSourceFile(test3);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(6, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertTrue(sourceManager.containsSourceFile(test2));
+ assertTrue(sourceManager.containsSourceFile(test3));
+
+ txId = program.startTransaction("removing source file");
+ try {
+ sourceManager.removeSourceFile(test2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ sourceFiles = sourceManager.getAllSourceFiles();
+ assertEquals(5, sourceFiles.size());
+
+ assertTrue(sourceManager.containsSourceFile(test1));
+ assertFalse(sourceManager.containsSourceFile(test2));
+ assertTrue(sourceManager.containsSourceFile(test3));
+
+ }
+
+ @Test
+ public void testNoIdentifierNonNullArray() {
+ SourceFile sourceFile =
+ new SourceFile("/src/file.c", SourceFileIdType.NONE, new byte[] { 0x11, 0x22 });
+ assertEquals("/src/file.c", sourceFile.getPath());
+ assertEquals(SourceFileIdType.NONE, sourceFile.getIdType());
+
+ // array passed to SourceFile constructor should be ignored
+ assertTrue(Arrays.equals(new byte[0], sourceFile.getIdentifier()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBadMd5Length() {
+ new SourceFile("/file.c", SourceFileIdType.MD5, new byte[] { 0x11, 0x22 });
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMd5NullArray() {
+ new SourceFile("/file.c", SourceFileIdType.MD5, null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testAddingNullSourceFile() throws LockException {
+ int txId = program.startTransaction("adding null SourceFile");
+ try {
+ sourceManager.addSourceFile(null);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testRemovingNullSourceFile() throws LockException {
+ int txId = program.startTransaction("removing null source file");
+ try {
+ assertFalse(sourceManager.removeSourceFile(null));
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testConvertingNullArrayToLong() {
+ SourceFileUtils.byteArrayToLong(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConvertingEmptyArrayToLong() {
+ SourceFileUtils.byteArrayToLong(new byte[0]);
+ }
+
+ @Test
+ public void testLongConversion() {
+ long testLong = 0x0102030405060708L;
+ byte[] testArray = new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8 };
+ assertEquals(testLong, SourceFileUtils.byteArrayToLong(testArray));
+ assertTrue(Arrays.equals(testArray, SourceFileUtils.longToByteArray(testLong)));
+ }
+
+ @Test
+ public void testHexStringToByteArrayConversion() {
+ assertTrue(Arrays.equals(new byte[0], SourceFileUtils.hexStringToByteArray(null)));
+ assertTrue(
+ Arrays.equals(new byte[0], SourceFileUtils.hexStringToByteArray(StringUtils.EMPTY)));
+ assertTrue(Arrays.equals(new byte[0], SourceFileUtils.hexStringToByteArray(" ")));
+ byte[] testArray = new byte[] { 0x00, 0x01, (byte) 0xaa, (byte) 0xff };
+ assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0001aaff")));
+ assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0001AAFF")));
+ assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0X0001AAFF")));
+ assertTrue(Arrays.equals(testArray, SourceFileUtils.hexStringToByteArray("0x0001aaff")));
+ }
+
+ @Test
+ public void testByteArrayToHexStringConversion() {
+ assertEquals(StringUtils.EMPTY, SourceFileUtils.byteArrayToHexString(null));
+ assertEquals(StringUtils.EMPTY, SourceFileUtils.byteArrayToHexString(new byte[0]));
+ assertEquals("00112233445566778899aabbccddeeff",
+ SourceFileUtils.byteArrayToHexString(new byte[] { 0x0, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66,
+ 0x77, (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff }));
+ }
+
+}
diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/SourceMapEntryIteratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/SourceMapEntryIteratorTest.java
new file mode 100644
index 0000000000..5dcdd61adb
--- /dev/null
+++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/SourceMapEntryIteratorTest.java
@@ -0,0 +1,222 @@
+/* ###
+ * 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.database.sourcemap;
+
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.junit.Test;
+
+import ghidra.framework.store.LockException;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressOverflowException;
+import ghidra.program.model.sourcemap.*;
+
+public class SourceMapEntryIteratorTest extends AbstractSourceFileTest {
+
+ @Test
+ public void testInappropriateAddress() {
+ assertFalse(sourceManager.getSourceMapEntryIterator(null, true).hasNext());
+ assertFalse(sourceManager.getSourceMapEntryIterator(null, false).hasNext());
+ assertFalse(sourceManager.getSourceMapEntryIterator(Address.NO_ADDRESS, true).hasNext());
+ assertFalse(sourceManager.getSourceMapEntryIterator(Address.NO_ADDRESS, false).hasNext());
+ }
+
+ @Test
+ public void testDummySourceManagerIterators() {
+ Address address = ret2_1.getAddress();
+ assertFalse(
+ SourceFileManager.DUMMY.getSourceMapEntryIterator(address, true).hasNext());
+ assertFalse(
+ SourceFileManager.DUMMY.getSourceMapEntryIterator(address, false).hasNext());
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testNoSuchElementExceptionForward() throws AddressOverflowException, LockException {
+ SourceMapEntry entry1 = null;
+
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ SourceMapEntryIterator iter =
+ sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), true);
+ assertTrue(iter.hasNext());
+ assertEquals(entry1, iter.next());
+ assertFalse(iter.hasNext());
+ iter.next();
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testNoSuchElementExceptionBackward()
+ throws AddressOverflowException, LockException {
+ SourceMapEntry entry1 = null;
+
+ int txId = program.startTransaction("adding source map entry");
+ try {
+ entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ SourceMapEntryIterator iter =
+ sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), false);
+ assertTrue(iter.hasNext());
+ assertEquals(entry1, iter.next());
+ assertFalse(iter.hasNext());
+ iter.next();
+ }
+
+ @Test
+ public void testForwardIterator() throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("adding first source map entry");
+ SourceMapEntry entry1 = null;
+ SourceMapEntry entry2 = null;
+ SourceMapEntry entry3 = null;
+ SourceMapEntry entry4 = null;
+ SourceMapEntry entry5 = null;
+
+ SourceMapEntryIterator iter =
+ sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), true);
+ for (int i = 0; i < 10; i++) {
+ assertFalse(iter.hasNext());
+ }
+
+ try {
+ entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
+ entry2 = sourceManager.addSourceMapEntry(source1, 2, ret2_1.getAddress(), 2);
+ entry3 = sourceManager.addSourceMapEntry(source2, 3, ret2_1.getAddress().add(1), 0);
+ entry4 = sourceManager.addSourceMapEntry(source3, 4, nop1_1.getAddress(), 1);
+ entry5 = sourceManager.addSourceMapEntry(source3, 5, nop1_1.getAddress(), 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ Set entries = new HashSet<>();
+ iter = sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), true);
+
+ for (int i = 0; i < 10; i++) {
+ assertTrue(iter.hasNext());
+ }
+ entries.add(iter.next());
+
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(entries.contains(entry1));
+ assertTrue(entries.contains(entry2));
+
+ entries.clear();
+
+ assertTrue(iter.hasNext());
+ assertEquals(entry3, iter.next());
+
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(entries.contains(entry4));
+ assertTrue(entries.contains(entry5));
+ assertFalse(iter.hasNext());
+ entries.clear();
+
+ iter = sourceManager.getSourceMapEntryIterator(ret2_1.getAddress().add(1), true);
+ assertTrue(iter.hasNext());
+ assertEquals(entry3, iter.next());
+
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(entries.contains(entry4));
+ assertTrue(entries.contains(entry5));
+ assertFalse(iter.hasNext());
+
+ iter = sourceManager.getSourceMapEntryIterator(ret2_2.getAddress(), true);
+ assertFalse(iter.hasNext());
+ }
+
+ @Test
+ public void testBackwardIterator() throws AddressOverflowException, LockException {
+ int txId = program.startTransaction("adding first source map entry");
+ SourceMapEntry entry1 = null;
+ SourceMapEntry entry2 = null;
+ SourceMapEntry entry3 = null;
+ SourceMapEntry entry4 = null;
+ SourceMapEntry entry5 = null;
+
+ SourceMapEntryIterator iter =
+ sourceManager.getSourceMapEntryIterator(ret2_1.getAddress(), false);
+ for (int i = 0; i < 10; i++) {
+ assertFalse(iter.hasNext());
+ }
+
+ try {
+ entry1 = sourceManager.addSourceMapEntry(source1, 1, ret2_1.getAddress(), 2);
+ entry2 = sourceManager.addSourceMapEntry(source1, 2, ret2_1.getAddress(), 2);
+ entry3 = sourceManager.addSourceMapEntry(source2, 3, ret2_1.getAddress().add(1), 0);
+ entry4 = sourceManager.addSourceMapEntry(source3, 4, nop1_1.getAddress(), 1);
+ entry5 = sourceManager.addSourceMapEntry(source3, 5, nop1_1.getAddress(), 1);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+ Set entries = new HashSet<>();
+ iter = sourceManager.getSourceMapEntryIterator(nop1_1.getAddress(), false);
+
+ for (int i = 0; i < 10; i++) {
+ assertTrue(iter.hasNext());
+ }
+ entries.add(iter.next());
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(entries.contains(entry4));
+ assertTrue(entries.contains(entry5));
+
+ entries.clear();
+
+ assertTrue(iter.hasNext());
+ assertEquals(entry3, iter.next());
+
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(entries.contains(entry1));
+ assertTrue(entries.contains(entry1));
+ assertFalse(iter.hasNext());
+ entries.clear();
+
+ iter = sourceManager.getSourceMapEntryIterator(ret2_1.getAddress().add(1), false);
+ assertTrue(iter.hasNext());
+ assertEquals(entry3, iter.next());
+
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(iter.hasNext());
+ entries.add(iter.next());
+ assertTrue(entries.contains(entry1));
+ assertTrue(entries.contains(entry2));
+ assertFalse(iter.hasNext());
+
+ }
+
+}