GT-3165 - Tables - Fixed Ctrl-A conflict between tables and the Listing

This commit is contained in:
dragonmacher 2019-09-19 14:52:00 -04:00
parent ab5e715607
commit a87b65b758
5 changed files with 443 additions and 336 deletions

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +15,8 @@
*/
package ghidra.program.model.block;
import java.util.*;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.listing.Program;
@ -24,8 +25,6 @@ import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import java.util.*;
/**
* <CODE>MultEntSubModel</CODE> (M-model) defines subroutines which do not share code with
* any other subroutine and may have one or more entry points. Each entry-
@ -41,160 +40,169 @@ import java.util.*;
public class MultEntSubModel implements SubroutineBlockModel {
public static final String NAME = "Multiple Entry";
protected Program program;
protected Listing listing;
private AddressObjectMap foundMSubs; // used for caching model-M subroutines
private CodeBlockModel bbModel; // basic block model
protected final boolean includeExternals;
/**
* Construct a <CODE>MultEntSubModel</CODE> for a program.
*
* @param program program to create blocks from.
*/
public MultEntSubModel(Program program) {
this(program, false);
}
/**
* Construct a <CODE>MultEntSubModel</CODE> for a program.
* @param program program to create blocks from.
* @param includeExternals external blocks will be included if true
*/
public MultEntSubModel(Program program, boolean includeExternals) {
this.program = program;
this.includeExternals = includeExternals;
listing = program.getListing();
foundMSubs = new AddressObjectMap();
}
/**
* Get the code block that has an entry point at addr.
*
* @param addr one of the entry points for a Model-M subroutine
* @param monitor task monitor which allows user to cancel operation.
* @return null if there is no subroutine with an entry at addr.
* @throws CancelledException if the monitor cancels the operation.
*/
public CodeBlock getCodeBlockAt(Address addr, TaskMonitor monitor) throws CancelledException {
if (addr == null)
return null;
protected Program program;
protected Listing listing;
CodeBlock block = getSubFromCache(addr);
if (block == null) {
block = getAddressSetContaining(addr, monitor);
}
if (block != null){
Address[] entPts = block.getStartAddresses();
for (int i = 0; i < entPts.length; i++) {
if (entPts[i].equals(addr))
return block;
}
}
return null;
}
private AddressObjectMap foundMSubs; // used for caching model-M subroutines
private CodeBlockModel bbModel; // basic block model
protected final boolean includeExternals;
/**
* Get the M-Model subroutine address set which contains the specified address.
* This method also identifies the entry points and caches the resulting CodeBlock.
*
* @param addr Address inside the subroutine that we are seeking
* @param monitor task monitor which allows user to cancel operation.
* @return The <CODE>CodeBlock</CODE> for a
* <CODE>MultEntSubModel</CODE> Subroutine.
* Null is returned if there is no instruction at addr.
* @throws CancelledException if the monitor cancels the operation.
**/
protected CodeBlock getAddressSetContaining(Address addr, TaskMonitor monitor) throws CancelledException {
/**
* Construct a <CODE>MultEntSubModel</CODE> for a program.
*
* @param program program to create blocks from.
*/
public MultEntSubModel(Program program) {
this(program, false);
}
if (addr.isExternalAddress()) {
if (includeExternals) {
CodeBlock block = new ExtCodeBlockImpl(this, addr);
foundMSubs.addObject(block, addr, addr);
return block;
}
return null;
}
// create a holder for the blockSet
AddressSet addrSet = new AddressSet();
// Use a new block model each time, so that only the current
// function basic blocks will get saved.
/**
* Construct a <CODE>MultEntSubModel</CODE> for a program.
* @param program program to create blocks from.
* @param includeExternals external blocks will be included if true
*/
public MultEntSubModel(Program program, boolean includeExternals) {
this.program = program;
this.includeExternals = includeExternals;
listing = program.getListing();
foundMSubs = new AddressObjectMap();
}
/**
* Get the code block that has an entry point at addr.
*
* @param addr one of the entry points for a Model-M subroutine
* @param monitor task monitor which allows user to cancel operation.
* @return null if there is no subroutine with an entry at addr.
* @throws CancelledException if the monitor cancels the operation.
*/
@Override
public CodeBlock getCodeBlockAt(Address addr, TaskMonitor monitor) throws CancelledException {
if (addr == null) {
return null;
}
CodeBlock block = getSubFromCache(addr);
if (block == null) {
block = getAddressSetContaining(addr, monitor);
}
if (block != null) {
Address[] entPts = block.getStartAddresses();
for (Address entPt : entPts) {
if (entPt.equals(addr)) {
return block;
}
}
}
return null;
}
/**
* Get the M-Model subroutine address set which contains the specified address.
* This method also identifies the entry points and caches the resulting CodeBlock.
*
* @param addr Address inside the subroutine that we are seeking
* @param monitor task monitor which allows user to cancel operation.
* @return The <CODE>CodeBlock</CODE> for a
* <CODE>MultEntSubModel</CODE> Subroutine.
* Null is returned if there is no instruction at addr.
* @throws CancelledException if the monitor cancels the operation.
**/
protected CodeBlock getAddressSetContaining(Address addr, TaskMonitor monitor)
throws CancelledException {
if (addr.isExternalAddress()) {
if (includeExternals) {
CodeBlock block = new ExtCodeBlockImpl(this, addr);
foundMSubs.addObject(block, addr, addr);
return block;
}
return null;
}
// create a holder for the blockSet
AddressSet addrSet = new AddressSet();
// Use a new block model each time, so that only the current
// function basic blocks will get saved.
bbModel = new SimpleBlockModel(program, includeExternals);
// Create the todoStack and initialize it with instr; also initialize the list for entryPts.
ArrayList<Address> entryPtList = new ArrayList<Address>();
LinkedList<Address> todoList = new LinkedList<Address>(); // list of address destinations to follow
LinkedList<CodeBlock> srcList = new LinkedList<CodeBlock>(); // list of blocks to process for possible sources
todoList.addFirst(addr);
// Build model-M subroutine from basic blocks
while (!todoList.isEmpty() || !srcList.isEmpty()) {
if (monitor != null && monitor.isCancelled())
throw new CancelledException();
// Create the todoStack and initialize it with instr; also initialize the list for entryPts.
ArrayList<Address> entryPtList = new ArrayList<Address>();
LinkedList<Address> todoList = new LinkedList<Address>(); // list of address destinations to follow
LinkedList<CodeBlock> srcList = new LinkedList<CodeBlock>(); // list of blocks to process for possible sources
todoList.addFirst(addr);
// Build model-M subroutine from basic blocks
while (!todoList.isEmpty() || !srcList.isEmpty()) {
if (monitor != null && monitor.isCancelled()) {
throw new CancelledException();
}
// if todoList is empty
// process any blocks for sources that we put off to analyze later
// It is easier/efficient to follow flow in the forward direction.
if (todoList.isEmpty()) {
while (todoList.isEmpty() && !srcList.isEmpty()) {
CodeBlock bblock = srcList.removeFirst();
while (todoList.isEmpty() && !srcList.isEmpty()) {
CodeBlock bblock = srcList.removeFirst();
// Process all block source references
addSources(monitor, entryPtList, todoList, bblock);
}
continue;
addSources(monitor, entryPtList, todoList, bblock);
}
continue;
}
// get the next address to process
Address a = null;
a = todoList.removeFirst();
// Get basic block at the specified address
if (addrSet.contains(a))
continue; // already processed this block
CodeBlock bblock = bbModel.getFirstCodeBlockContaining(a, monitor);
if (bblock == null)
continue;
// Verify that the block contains instructions
if (listing.getInstructionAt(bblock.getMinAddress()) == null)
continue;
// Add basic block to subroutine address set
addrSet.add(bblock);
// Process all destination references
addDestinations(monitor, todoList, bblock);
// add block to list of block to process later
srcList.addLast(bblock);
}
if (addrSet.isEmpty())
// Get basic block at the specified address
if (addrSet.contains(a)) {
continue; // already processed this block
}
CodeBlock bblock = bbModel.getFirstCodeBlockContaining(a, monitor);
if (bblock == null) {
continue;
}
// Verify that the block contains instructions
if (listing.getInstructionAt(bblock.getMinAddress()) == null) {
continue;
}
// Add basic block to subroutine address set
addrSet.add(bblock);
// Process all destination references
addDestinations(monitor, todoList, bblock);
// add block to list of block to process later
srcList.addLast(bblock);
}
if (addrSet.isEmpty()) {
return null;
}
// Check for failure to find entry point
if (entryPtList.size() == 0) {
Msg.warn(this, "WARNING: failed to find entry point for subroutine containing " + addr);
Msg.warn(this, "Failed to find entry point for subroutine containing " + addr);
entryPtList.add(addrSet.getMinAddress());
}
Address[] entryPts = new Address[entryPtList.size()];
entryPtList.toArray(entryPts);
}
Address[] entryPts = new Address[entryPtList.size()];
entryPtList.toArray(entryPts);
CodeBlock block = new CodeBlockImpl(this, entryPts, addrSet);
foundMSubs.addObject(block, addrSet);
CodeBlock block = new CodeBlockImpl(this, entryPts, addrSet);
foundMSubs.addObject(block, addrSet);
return block;
}
return block;
}
private void addDestinations(TaskMonitor monitor, LinkedList<Address> todoList, CodeBlock bblock)
throws CancelledException {
private void addDestinations(TaskMonitor monitor, LinkedList<Address> todoList,
CodeBlock bblock) throws CancelledException {
CodeBlockReferenceIterator destIter = bblock.getDestinations(monitor);
while (destIter.hasNext()) {
CodeBlockReference destRef = destIter.next();
@ -203,18 +211,17 @@ public class MultEntSubModel implements SubroutineBlockModel {
if (destAddr.isMemoryAddress()) {
FlowType refFlowType = destRef.getFlowType();
if (refFlowType.isJump()) {
todoList.addLast(destAddr);
}
todoList.addLast(destAddr);
}
else if (refFlowType.isFallthrough()) {
todoList.addFirst(destAddr);
todoList.addFirst(destAddr);
}
}
}
}
private void addSources(TaskMonitor monitor,
List<Address> entryPtList, LinkedList<Address> todoList, CodeBlock bblock)
throws CancelledException {
private void addSources(TaskMonitor monitor, List<Address> entryPtList,
LinkedList<Address> todoList, CodeBlock bblock) throws CancelledException {
CodeBlockReferenceIterator srcIter = bblock.getSources(monitor);
boolean isSource = true;
boolean isEntry = false;
@ -224,8 +231,9 @@ public class MultEntSubModel implements SubroutineBlockModel {
FlowType refFlowType = srcRef.getFlowType();
if (refFlowType.isJump() || refFlowType.isFallthrough()) {
// Add Jump and Fall-through sources to the todoList
todoList.addLast(srcRef.getSourceAddress());
} else if (refFlowType.isCall()) {
todoList.addLast(srcRef.getSourceAddress());
}
else if (refFlowType.isCall()) {
// Basic block is a subroutine entry point
isEntry = true;
}
@ -234,214 +242,234 @@ public class MultEntSubModel implements SubroutineBlockModel {
entryPtList.add(bblock.getMinAddress());
}
}
/**
* Get the MultEntSubModel Code Block that contains the address.
*
* @param addr Address to find a containing block.
* @param monitor task monitor which allows user to cancel operation.
* @return A CodeBlock if any block contains the address.
* null otherwise.
* @throws CancelledException if the monitor cancels the operation.
*/
public CodeBlock getFirstCodeBlockContaining(Address addr, TaskMonitor monitor) throws CancelledException {
CodeBlock block = getSubFromCache(addr);
if (block == null) {
block = getAddressSetContaining(addr, monitor);
}
/**
* Get the MultEntSubModel Code Block that contains the address.
*
* @param addr Address to find a containing block.
* @param monitor task monitor which allows user to cancel operation.
* @return A CodeBlock if any block contains the address.
* null otherwise.
* @throws CancelledException if the monitor cancels the operation.
*/
@Override
public CodeBlock getFirstCodeBlockContaining(Address addr, TaskMonitor monitor)
throws CancelledException {
CodeBlock block = getSubFromCache(addr);
if (block == null) {
block = getAddressSetContaining(addr, monitor);
}
return block;
}
}
/**
* Returns the one code block contained by addr (only for
* a model that has shared subroutines would this method
* return more than one code block)
*
* @param addr Address to find a containing block.
* @param monitor task monitor which allows user to cancel operation.
* @return A CodeBlock if any block contains the address.
* empty array otherwise.
* @throws CancelledException if the monitor cancels the operation.
*/
@Override
public CodeBlock[] getCodeBlocksContaining(Address addr, TaskMonitor monitor)
throws CancelledException {
CodeBlock sub = getFirstCodeBlockContaining(addr, monitor);
if (sub == null) {
return emptyBlockArray;
}
CodeBlock[] blocks = new CodeBlock[1];
blocks[0] = sub;
return blocks;
}
/**
* Returns the one code block contained by addr (only for
* a model that has shared subroutines would this method
* return more than one code block)
*
* @param addr Address to find a containing block.
* @param monitor task monitor which allows user to cancel operation.
* @return A CodeBlock if any block contains the address.
* empty array otherwise.
* @throws CancelledException if the monitor cancels the operation.
*/
public CodeBlock[] getCodeBlocksContaining(Address addr, TaskMonitor monitor) throws CancelledException {
CodeBlock sub = getFirstCodeBlockContaining(addr, monitor);
if (sub == null) {
return emptyBlockArray;
}
CodeBlock[] blocks = new CodeBlock[1];
blocks[0] = sub;
return blocks;
}
/**
* Get an iterator over the code blocks in the entire program.
* @param monitor task monitor which allows user to cancel operation.
* @throws CancelledException if the monitor cancels the operation.
*/
@Override
public CodeBlockIterator getCodeBlocks(TaskMonitor monitor) throws CancelledException {
return new MultEntSubIterator(this, monitor);
}
/**
* Get an iterator over CodeBlocks which overlap the specified address set.
*
* @param addrSet an address set within program
* @param monitor task monitor which allows user to cancel operation.
* @throws CancelledException if the monitor cancels the operation.
*/
@Override
public CodeBlockIterator getCodeBlocksContaining(AddressSetView addrSet, TaskMonitor monitor)
throws CancelledException {
return new MultEntSubIterator(this, addrSet, monitor);
}
/**
* Get an iterator over the code blocks in the entire program.
* @param monitor task monitor which allows user to cancel operation.
* @throws CancelledException if the monitor cancels the operation.
*/
public CodeBlockIterator getCodeBlocks(TaskMonitor monitor) throws CancelledException {
return new MultEntSubIterator(this, monitor);
}
/**
* @see ghidra.program.model.block.CodeBlockModel#getProgram()
*/
@Override
public Program getProgram() {
return program;
}
/**
* Get an iterator over CodeBlocks which overlap the specified address set.
*
* @param addrSet an address set within program
* @param monitor task monitor which allows user to cancel operation.
* @throws CancelledException if the monitor cancels the operation.
*/
public CodeBlockIterator getCodeBlocksContaining(AddressSetView addrSet, TaskMonitor monitor) throws CancelledException {
return new MultEntSubIterator(this, addrSet, monitor);
}
/**
* Returns the listing associated with this block model.
* @return the listing associated with this block model.
*/
public Listing getListing() {
return listing;
}
/**
* @see ghidra.program.model.block.CodeBlockModel#getProgram()
*/
public Program getProgram() {
return program;
}
/**
* @see ghidra.program.model.block.CodeBlockModel#getName(ghidra.program.model.block.CodeBlock)
*/
@Override
public String getName(CodeBlock block) {
/**
* Returns the listing associated with this block model.
* @return the listing associated with this block model.
*/
public Listing getListing() {
return listing;
}
if (!(block.getModel() instanceof MultEntSubModel)) {
throw new IllegalArgumentException();
}
/**
* @see ghidra.program.model.block.CodeBlockModel#getName(ghidra.program.model.block.CodeBlock)
*/
public String getName(CodeBlock block) {
// get the start address for the block
// look up the symbol in the symbol table.
// it should have one if anyone calls it.
// if not, make up a label
if (!(block.getModel() instanceof MultEntSubModel))
throw new IllegalArgumentException();
Address start = block.getFirstStartAddress();
// get the start address for the block
// look up the symbol in the symbol table.
// it should have one if anyone calls it.
// if not, make up a label
Address start = block.getFirstStartAddress();
Symbol symbol = program.getSymbolTable().getPrimarySymbol(start);
if (symbol != null) {
return symbol.getName();
}
Symbol symbol = program.getSymbolTable().getPrimarySymbol(start);
if (symbol != null) {
return symbol.getName();
}
return "SOURCE_SUB" + start.toString();
}
return "SOURCE_SUB" + start.toString();
}
/**
* Return in general how things flow out of this node.
* This method exists for the SIMPLEBLOCK model.
*
* <p>
* Since it doesn't make a great deal of sense to ask for this method
* in the case of subroutines, we return FlowType.UNKNOWN
* as long as the block exists.
*
* <p>
* If this block has no valid instructions, it can't flow,
* so FlowType.INVALID is returned.
*
* @return flow type of this node
*/
@Override
public FlowType getFlowType(CodeBlock block) {
if (!(block.getModel() instanceof MultEntSubModel)) {
throw new IllegalArgumentException();
}
/**
* Return in general how things flow out of this node.
* This method exists for the SIMPLEBLOCK model.
*
* <p>
* Since it doesn't make a great deal of sense to ask for this method
* in the case of subroutines, we return FlowType.UNKNOWN
* as long as the block exists.
*
* <p>
* If this block has no valid instructions, it can't flow,
* so FlowType.INVALID is returned.
*
* @return flow type of this node
*/
public FlowType getFlowType(CodeBlock block) {
/* If there are multiple unique ways out of the node, then we
should return FlowType.UNKNOWN (or FlowType.MULTIFLOW ?).
Possible considerations for the future which are particularly
applicable to model-P subroutines: add FlowType.MULTICALL if
only calls out and FlowType.MULTIJUMP if multiple jumps OUT
(as opposed to jumping within the subroutine).
Might want to consider FlowType.MULTITERMINAL for multiple returns? */
if (!(block.getModel() instanceof MultEntSubModel))
throw new IllegalArgumentException();
return RefType.FLOW;
}
/* If there are multiple unique ways out of the node, then we
should return FlowType.UNKNOWN (or FlowType.MULTIFLOW ?).
Possible considerations for the future which are particularly
applicable to model-P subroutines: add FlowType.MULTICALL if
only calls out and FlowType.MULTIJUMP if multiple jumps OUT
(as opposed to jumping within the subroutine).
Might want to consider FlowType.MULTITERMINAL for multiple returns? */
/**
* @see ghidra.program.model.block.CodeBlockModel#getSources(ghidra.program.model.block.CodeBlock, ghidra.util.task.TaskMonitor)
*/
@Override
public CodeBlockReferenceIterator getSources(CodeBlock block, TaskMonitor monitor)
throws CancelledException {
if (!(block.getModel() instanceof MultEntSubModel)) {
throw new IllegalArgumentException();
}
return RefType.FLOW;
}
return new SubroutineSourceReferenceIterator(block, monitor);
}
/**
* @see ghidra.program.model.block.CodeBlockModel#getSources(ghidra.program.model.block.CodeBlock, ghidra.util.task.TaskMonitor)
*/
public CodeBlockReferenceIterator getSources(CodeBlock block, TaskMonitor monitor) throws CancelledException {
if (!(block.getModel() instanceof MultEntSubModel))
throw new IllegalArgumentException();
/**
* @see ghidra.program.model.block.CodeBlockModel#getNumSources(ghidra.program.model.block.CodeBlock, ghidra.util.task.TaskMonitor)
*/
@Override
public int getNumSources(CodeBlock block, TaskMonitor monitor) throws CancelledException {
return new SubroutineSourceReferenceIterator(block, monitor);
}
if (!(block.getModel() instanceof MultEntSubModel)) {
throw new IllegalArgumentException();
}
/**
* @see ghidra.program.model.block.CodeBlockModel#getNumSources(ghidra.program.model.block.CodeBlock, ghidra.util.task.TaskMonitor)
*/
public int getNumSources(CodeBlock block, TaskMonitor monitor) throws CancelledException {
if (!(block.getModel() instanceof MultEntSubModel))
throw new IllegalArgumentException();
return SubroutineSourceReferenceIterator.getNumSources(block, monitor);
}
}
/**
* @see ghidra.program.model.block.CodeBlockModel#getDestinations(ghidra.program.model.block.CodeBlock, ghidra.util.task.TaskMonitor)
*/
public CodeBlockReferenceIterator getDestinations(CodeBlock block, TaskMonitor monitor) throws CancelledException {
/**
* @see ghidra.program.model.block.CodeBlockModel#getDestinations(ghidra.program.model.block.CodeBlock, ghidra.util.task.TaskMonitor)
*/
@Override
public CodeBlockReferenceIterator getDestinations(CodeBlock block, TaskMonitor monitor)
throws CancelledException {
if (!(block.getModel() instanceof MultEntSubModel))
throw new IllegalArgumentException();
if (!(block.getModel() instanceof MultEntSubModel)) {
throw new IllegalArgumentException();
}
return new SubroutineDestReferenceIterator(block, monitor);
}
return new SubroutineDestReferenceIterator(block, monitor);
}
/**
* Get number of destination references flowing out of this subroutine (block).
* All Calls from this block, and all external FlowType block references
* from this block are counted.
*
* @param block code block to get the number of destination references from.
* @param monitor task monitor which allows user to cancel operation.
* @throws CancelledException if the monitor cancels the operation.
*/
public int getNumDestinations(CodeBlock block, TaskMonitor monitor) throws CancelledException {
/**
* Get number of destination references flowing out of this subroutine (block).
* All Calls from this block, and all external FlowType block references
* from this block are counted.
*
* @param block code block to get the number of destination references from.
* @param monitor task monitor which allows user to cancel operation.
* @throws CancelledException if the monitor cancels the operation.
*/
@Override
public int getNumDestinations(CodeBlock block, TaskMonitor monitor) throws CancelledException {
if (!(block.getModel() instanceof MultEntSubModel))
throw new IllegalArgumentException();
if (!(block.getModel() instanceof MultEntSubModel)) {
throw new IllegalArgumentException();
}
return SubroutineDestReferenceIterator.getNumDestinations(block, monitor);
}
/**
* Compute an address set that represents all the addresses contained
* in all instructions that are part of this block
*
* @param block code block to compute address set for.
*/
public AddressSetView getAddressSet(CodeBlock block) {
}
if (!(block.getModel() instanceof MultEntSubModel))
throw new IllegalArgumentException();
/**
* Compute an address set that represents all the addresses contained
* in all instructions that are part of this block
*
* @param block code block to compute address set for.
*/
public AddressSetView getAddressSet(CodeBlock block) {
return new AddressSet(block);
}
if (!(block.getModel() instanceof MultEntSubModel)) {
throw new IllegalArgumentException();
}
/**
* Gets a subroutine from the cache containing addr. If none there, returns null.
* It is assumed that an address will only occur within a single MSub
*/
private CodeBlock getSubFromCache(Address addr){
Object[] mapObjs = this.foundMSubs.getObjects(addr);
return mapObjs.length == 0 ? null : (CodeBlock) mapObjs[0];
}
return new AddressSet(block);
}
/**
* Gets a subroutine from the cache containing addr. If none there, returns null.
* It is assumed that an address will only occur within a single MSub
*/
private CodeBlock getSubFromCache(Address addr) {
Object[] mapObjs = this.foundMSubs.getObjects(addr);
return mapObjs.length == 0 ? null : (CodeBlock) mapObjs[0];
}
/**
* @see ghidra.program.model.block.CodeBlockModel#getBasicBlockModel()
*/
@Override
public CodeBlockModel getBasicBlockModel() {
if (bbModel == null) {
bbModel = new SimpleBlockModel(program);
@ -452,13 +480,15 @@ public class MultEntSubModel implements SubroutineBlockModel {
/**
* @see ghidra.program.model.block.CodeBlockModel#getName()
*/
@Override
public String getName() {
return NAME;
}
/**
* @see ghidra.program.model.block.SubroutineBlockModel#getBaseSubroutineModel()
*/
@Override
public SubroutineBlockModel getBaseSubroutineModel() {
return this;
}
@ -466,6 +496,7 @@ public class MultEntSubModel implements SubroutineBlockModel {
/**
* @see ghidra.program.model.block.CodeBlockModel#allowsBlockOverlap()
*/
@Override
public boolean allowsBlockOverlap() {
return false;
}
@ -473,6 +504,7 @@ public class MultEntSubModel implements SubroutineBlockModel {
/**
* @see ghidra.program.model.block.CodeBlockModel#externalsIncluded()
*/
@Override
public boolean externalsIncluded() {
return includeExternals;
}