GT-3542 - Symbol Tree - fixed flashing of the symbol tree while

analyzing; fixed bad progress of Decompiler Parameter ID analyzer; fixed
odd progress updating of ConcurrentQ
This commit is contained in:
dragonmacher 2020-03-20 12:56:56 -04:00
parent e9ac056f90
commit 30db29d1ce
12 changed files with 98 additions and 58 deletions

View file

@ -255,7 +255,7 @@ public class MyProgramChangesDisplayPlugin extends ProgramPlugin implements Doma
*/ */
private void updateChangeMarkers() { private void updateChangeMarkers() {
Swing.assertThisIsTheSwingThread( Swing.assertSwingThread(
"Change markers must be manipulated on the Swing thread"); "Change markers must be manipulated on the Swing thread");
if (currentProgram == null) { if (currentProgram == null) {

View file

@ -76,6 +76,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
private List<GTreeTask> bufferedTasks = new ArrayList<>(); private List<GTreeTask> bufferedTasks = new ArrayList<>();
private SwingUpdateManager domainChangeUpdateManager = new SwingUpdateManager(1000, private SwingUpdateManager domainChangeUpdateManager = new SwingUpdateManager(1000,
SwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider", () -> { SwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider", () -> {
if (bufferedTasks.isEmpty()) { if (bufferedTasks.isEmpty()) {
return; return;
} }
@ -404,7 +405,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
private void addTask(GTreeTask task) { private void addTask(GTreeTask task) {
// Note: if we want to call this method from off the Swing thread, then we have to // Note: if we want to call this method from off the Swing thread, then we have to
// synchronize on the list that we are adding to here. // synchronize on the list that we are adding to here.
Swing.assertThisIsTheSwingThread( Swing.assertSwingThread(
"Adding tasks must be done on the Swing thread," + "Adding tasks must be done on the Swing thread," +
"since they are put into a list that is processed on the Swing thread. "); "since they are put into a list that is processed on the Swing thread. ");
@ -535,7 +536,10 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
public void run(TaskMonitor monitor) throws CancelledException { public void run(TaskMonitor monitor) throws CancelledException {
TreePath[] selectionPaths = tree.getSelectionPaths(); TreePath[] selectionPaths = tree.getSelectionPaths();
doRun(monitor); doRun(monitor);
tree.setSelectionPaths(selectionPaths);
if (selectionPaths.length != 0) {
tree.setSelectionPaths(selectionPaths);
}
} }
@Override @Override
@ -612,8 +616,9 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
@Override @Override
public void runBulk(TaskMonitor monitor) throws CancelledException { public void runBulk(TaskMonitor monitor) throws CancelledException {
if (tasks.size() > MAX_TASK_COUNT) { if (tasks.size() > MAX_TASK_COUNT) {
SystemUtilities.runSwingLater(() -> rebuildTree()); Swing.runLater(() -> rebuildTree());
return; return;
} }
@ -628,7 +633,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
@Override @Override
public void domainObjectChanged(DomainObjectChangedEvent event) { public void domainObjectChanged(DomainObjectChangedEvent event) {
if (!tool.isVisible(SymbolTreeProvider.this)) { if (ignoreEvents()) {
return; return;
} }
@ -686,5 +691,27 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
} }
} }
private boolean ignoreEvents() {
if (!isVisible()) {
return true;
}
return treeIsCollapsed();
}
private boolean treeIsCollapsed() {
// note: the root's children are visible by default
GTreeNode root = tree.getViewRoot();
if (!root.isExpanded()) {
return true;
}
List<GTreeNode> children = root.getChildren();
for (GTreeNode node : children) {
if (node.isExpanded()) {
return false;
}
}
return true;
}
} }
} }

View file

@ -50,9 +50,10 @@ public class DecompilerParameterIdCmd extends BackgroundCommand {
private boolean commitVoidReturn; private boolean commitVoidReturn;
private int decompilerTimeoutSecs; private int decompilerTimeoutSecs;
public DecompilerParameterIdCmd(AddressSetView entries, SourceType sourceTypeClearLevel, public DecompilerParameterIdCmd(String name, AddressSetView entries,
SourceType sourceTypeClearLevel,
boolean commitDataTypes, boolean commitVoidReturn, int decompilerTimeoutSecs) { boolean commitDataTypes, boolean commitVoidReturn, int decompilerTimeoutSecs) {
super("Create Function Stack Variables", true, true, false); super(name, true, true, false);
entryPoints.add(entries); entryPoints.add(entries);
this.sourceTypeClearLevel = sourceTypeClearLevel; this.sourceTypeClearLevel = sourceTypeClearLevel;
this.commitDataTypes = commitDataTypes; this.commitDataTypes = commitDataTypes;
@ -65,13 +66,13 @@ public class DecompilerParameterIdCmd extends BackgroundCommand {
program = (Program) obj; program = (Program) obj;
CachingPool<DecompInterface> decompilerPool = CachingPool<DecompInterface> decompilerPool =
new CachingPool<DecompInterface>(new DecompilerFactory()); new CachingPool<>(new DecompilerFactory());
QRunnable<Address> runnable = new ParallelDecompileRunnable(decompilerPool); QRunnable<Address> runnable = new ParallelDecompileRunnable(decompilerPool);
ConcurrentGraphQ<Address> queue = null; ConcurrentGraphQ<Address> queue = null;
try { try {
monitor.setMessage("Analyzing Call Hierarchy..."); monitor.setMessage(getName() + " - creating dependency graph...");
AcyclicCallGraphBuilder builder = AcyclicCallGraphBuilder builder =
new AcyclicCallGraphBuilder(program, entryPoints, true); new AcyclicCallGraphBuilder(program, entryPoints, true);
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(monitor); AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(monitor);
@ -80,10 +81,11 @@ public class DecompilerParameterIdCmd extends BackgroundCommand {
} }
GThreadPool pool = AutoAnalysisManager.getSharedAnalsysThreadPool(); GThreadPool pool = AutoAnalysisManager.getSharedAnalsysThreadPool();
queue = new ConcurrentGraphQ<Address>(runnable, graph, pool, monitor); queue = new ConcurrentGraphQ<>(runnable, graph, pool, monitor);
resetFunctionSourceTypes(graph.getValues()); resetFunctionSourceTypes(graph.getValues());
monitor.setMessage("Analyzing..."); monitor.setMessage(getName() + " - analyzing...");
monitor.initialize(graph.size());
queue.execute(); queue.execute();
} }
@ -114,7 +116,8 @@ public class DecompilerParameterIdCmd extends BackgroundCommand {
*/ */
private boolean funcIsExternalGlue(Function func) { private boolean funcIsExternalGlue(Function func) {
String blockName = program.getMemory().getBlock(func.getEntryPoint()).getName(); String blockName = program.getMemory().getBlock(func.getEntryPoint()).getName();
return (blockName.equals("EXTERNAL") || blockName.equals(".plt") || blockName.equals("__stub_helper")); return (blockName.equals("EXTERNAL") || blockName.equals(".plt") ||
blockName.equals("__stub_helper"));
} }
private void resetFunctionSourceTypes(Set<Address> set) { private void resetFunctionSourceTypes(Set<Address> set) {
@ -147,10 +150,8 @@ public class DecompilerParameterIdCmd extends BackgroundCommand {
} }
} }
catch (InvalidInputException e) { catch (InvalidInputException e) {
Msg.warn( Msg.warn(this,
this, "Error changing signature SourceType on--" + func.getName(), e);
"Error changing signature SourceType (should not since Input is the same) on--" +
func.getName(), e);
} }
} }
} }
@ -248,10 +249,10 @@ public class DecompilerParameterIdCmd extends BackgroundCommand {
} }
/** /**
* Check for consistency of returned results. Trying to propogate, don't want to propagate garbage. * Check for consistency of returned results. Trying to propagate, don't want to propagate garbage.
* *
* @param decompRes * @param decompRes the decompile result
* @return * @return true if inconsistent results
*/ */
private boolean hasInconsistentResults(DecompileResults decompRes) { private boolean hasInconsistentResults(DecompileResults decompRes) {
HighFunction hfunc = decompRes.getHighFunction(); HighFunction hfunc = decompRes.getHighFunction();
@ -382,7 +383,7 @@ public class DecompilerParameterIdCmd extends BackgroundCommand {
private void doWork(Function function, DecompInterface decompiler, TaskMonitor monitor) private void doWork(Function function, DecompInterface decompiler, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
monitor.checkCanceled(); monitor.checkCanceled();
monitor.setMessage("Decompile " + function.getName()); monitor.setMessage(getName() + " - decompile " + function.getName());
analyzeFunction(decompiler, function, monitor); analyzeFunction(decompiler, function, monitor);
} }

View file

@ -37,11 +37,10 @@ import ghidra.util.graph.AbstractDependencyGraph;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer { public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
private static final String NAME = "Call Convention Identification"; private static final String NAME = "Call Convention ID";
private static final String DESCRIPTION = private static final String DESCRIPTION =
"Uses decompiler to figure out unknown calling conventions."; "Uses decompiler to figure out unknown calling conventions.";
private static final String STD_NAMESPACE = "std";
private static final String COULD_NOT_RECOVER_CALLING_CONVENTION = private static final String COULD_NOT_RECOVER_CALLING_CONVENTION =
"Could not recover calling convention"; "Could not recover calling convention";
private static final String OPTION_NAME_DECOMPILER_TIMEOUT_SECS = private static final String OPTION_NAME_DECOMPILER_TIMEOUT_SECS =
@ -53,8 +52,6 @@ public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
private boolean ignoreBookmarks = false; private boolean ignoreBookmarks = false;
private Program program;
//================================================================================================== //==================================================================================================
// Interface Methods // Interface Methods
//================================================================================================== //==================================================================================================
@ -68,11 +65,11 @@ public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
@Override @Override
public boolean canAnalyze(Program program) { public boolean canAnalyze(Program program) {
boolean cando = program.getLanguage().supportsPcode(); boolean can = program.getLanguage().supportsPcode();
// for a single entry compiler convention, the convention is always used, so no need to identify it // for a single entry compiler convention, the convention is always used, so no need to identify it
cando &= program.getCompilerSpec().getCallingConventions().length > 1; can &= program.getCompilerSpec().getCallingConventions().length > 1;
return cando; return can;
} }
@Override @Override
@ -92,8 +89,6 @@ public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws CancelledException { throws CancelledException {
this.program = program; // set program for use by ParallelDecompilerCallback
ignoreBookmarks = set.hasSameAddresses(program.getMemory()); ignoreBookmarks = set.hasSameAddresses(program.getMemory());
try { try {
@ -128,14 +123,14 @@ public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
CachingPool<DecompInterface> decompilerPool = CachingPool<DecompInterface> decompilerPool =
new CachingPool<>(new DecompilerFactory(program)); new CachingPool<>(new DecompilerFactory(program));
QRunnable<Address> callback = new ParallelDecompilerCallback(decompilerPool); QRunnable<Address> callback = new ParallelDecompilerCallback(decompilerPool, program);
ConcurrentGraphQ<Address> queue = null; ConcurrentGraphQ<Address> queue = null;
monitor.initialize(functionEntries.getNumAddresses()); monitor.initialize(functionEntries.getNumAddresses());
try { try {
monitor.setMessage("Analyzing Call Hierarchy..."); monitor.setMessage(NAME + " - creating dependency graph...");
AcyclicCallGraphBuilder builder = AcyclicCallGraphBuilder builder =
new AcyclicCallGraphBuilder(program, functionEntries, true); new AcyclicCallGraphBuilder(program, functionEntries, true);
AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(monitor); AbstractDependencyGraph<Address> graph = builder.getDependencyGraph(monitor);
@ -146,7 +141,8 @@ public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
GThreadPool pool = AutoAnalysisManager.getSharedAnalsysThreadPool(); GThreadPool pool = AutoAnalysisManager.getSharedAnalsysThreadPool();
queue = new ConcurrentGraphQ<>(callback, graph, pool, monitor); queue = new ConcurrentGraphQ<>(callback, graph, pool, monitor);
monitor.setMessage("Analyzing Call Conventions..."); monitor.setMessage(NAME + " - analyzing call conventions...");
monitor.initialize(graph.size());
queue.execute(); queue.execute();
} }
@ -272,9 +268,11 @@ public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
private class ParallelDecompilerCallback implements QRunnable<Address> { private class ParallelDecompilerCallback implements QRunnable<Address> {
private CachingPool<DecompInterface> pool; private CachingPool<DecompInterface> pool;
private Program program;
ParallelDecompilerCallback(CachingPool<DecompInterface> decompilerPool) { ParallelDecompilerCallback(CachingPool<DecompInterface> decompilerPool, Program program) {
this.pool = decompilerPool; this.pool = decompilerPool;
this.program = program;
} }
@Override @Override
@ -287,10 +285,12 @@ public class DecompilerCallConventionAnalyzer extends AbstractAnalyzer {
try { try {
Function function = program.getFunctionManager().getFunctionAt(address); Function function = program.getFunctionManager().getFunctionAt(address);
monitor.setMessage(getName() + " - decompile " + function.getName());
performConventionAnalysis(function, decompiler, monitor); performConventionAnalysis(function, decompiler, monitor);
} }
finally { finally {
pool.release(decompiler); pool.release(decompiler);
monitor.incrementProgress(1);
} }
return; return;
} }

View file

@ -71,7 +71,7 @@ public class DecompilerFunctionAnalyzer extends AbstractAnalyzer {
@Override @Override
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) { public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) {
DecompilerParameterIdCmd cmd = new DecompilerParameterIdCmd(set, DecompilerParameterIdCmd cmd = new DecompilerParameterIdCmd(NAME, set,
sourceTypeClearLevelOption, commitDataTypesOption, commitVoidReturnOption, sourceTypeClearLevelOption, commitDataTypesOption, commitVoidReturnOption,
decompilerTimeoutSecondsOption); decompilerTimeoutSecondsOption);
cmd.applyTo(program, monitor); cmd.applyTo(program, monitor);

View file

@ -19,7 +19,6 @@ import javax.swing.JTree;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import ghidra.util.worker.PriorityJob; import ghidra.util.worker.PriorityJob;
@ -53,6 +52,9 @@ public abstract class GTreeTask extends PriorityJob {
* values on later calls to getSelectedPaths(). So, to handle that 'feature' of the JTree, we * values on later calls to getSelectedPaths(). So, to handle that 'feature' of the JTree, we
* need to translate the given path to the equivalent path in the current tree (this code may * need to translate the given path to the equivalent path in the current tree (this code may
* not be needed in all uses of this task, but it protects us from the aforementioned case). * not be needed in all uses of this task, but it protects us from the aforementioned case).
* @param path the path to translate
* @param monitor the monitor
* @return the translated path
*/ */
protected TreePath translatePath(TreePath path, TaskMonitor monitor) { protected TreePath translatePath(TreePath path, TaskMonitor monitor) {

View file

@ -124,7 +124,7 @@ class TaskRunner {
private void showTaskDialog(WrappingTaskMonitor monitor) { private void showTaskDialog(WrappingTaskMonitor monitor) {
Swing.assertThisIsTheSwingThread("Must be on the Swing thread build the Task Dialog"); Swing.assertSwingThread("Must be on the Swing thread build the Task Dialog");
taskDialog = buildTaskDialog(parent, monitor); taskDialog = buildTaskDialog(parent, monitor);
monitor.setDelegate(taskDialog); // initialize the dialog to the current state of the monitor monitor.setDelegate(taskDialog); // initialize the dialog to the current state of the monitor

View file

@ -15,25 +15,26 @@
*/ */
package generic.concurrent; package generic.concurrent;
import java.util.Set;
import ghidra.util.graph.AbstractDependencyGraph; import ghidra.util.graph.AbstractDependencyGraph;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import java.util.Set;
public class ConcurrentGraphQ<I> { public class ConcurrentGraphQ<I> {
private ConcurrentQ<I, Object> queue; private ConcurrentQ<I, Object> queue;
private AbstractDependencyGraph<I> graph; private AbstractDependencyGraph<I> graph;
public ConcurrentGraphQ(QRunnable<I> runnable, AbstractDependencyGraph<I> graph, GThreadPool pool, public ConcurrentGraphQ(QRunnable<I> runnable, AbstractDependencyGraph<I> graph,
GThreadPool pool,
TaskMonitor monitor) { TaskMonitor monitor) {
this.graph = graph; this.graph = graph;
// @formatter:off // @formatter:off
queue = new ConcurrentQBuilder<I, Object>() queue = new ConcurrentQBuilder<I, Object>()
.setCollectResults(false) .setCollectResults(false)
.setThreadPool(pool) .setThreadPool(pool)
.setMonitor(monitor) .setMonitor(monitor)
.setListener(new MyItemListener()) .setListener(new MyItemListener())
.build(new QRunnableAdapter<I>(runnable)); .build(new QRunnableAdapter<>(runnable));
// @formatter:on // @formatter:on
} }

View file

@ -791,7 +791,17 @@ public class ConcurrentQ<I, R> {
@Override @Override
public void taskEnded(long id, I Item, long total, long progress) { public void taskEnded(long id, I Item, long total, long progress) {
if (!jobsReportProgress) { if (!jobsReportProgress) {
if (total != monitor.getMaximum()) { //
// This code works in 2 ways. The default case is that clients place items on
// the queue. As the amount of work grows, so too does the max progress value.
// This obviates the need for clients to manager progress. (The downside to this
// is that the progress may keep getting pushed back as it approaches the
// current maximum value.) The second case is where the client has specified a
// true maximum value. In that case, this code will not change the maxmimum
// (assuming that the client does not put more items into the queue than they
// specified).
//
if (total > monitor.getMaximum()) {
monitor.setMaximum(total); monitor.setMaximum(total);
} }
monitor.setProgress(progress); monitor.setProgress(progress);

View file

@ -248,7 +248,7 @@ public class EventManager {
private void sendEvents() { private void sendEvents() {
Swing.assertThisIsTheSwingThread("Events must be sent on the Swing thread"); Swing.assertSwingThread("Events must be sent on the Swing thread");
synchronized (eventQ) { synchronized (eventQ) {
currentEvent = eventQ.poll(); currentEvent = eventQ.poll();

View file

@ -91,7 +91,7 @@ public class AcyclicCallGraphBuilder {
AbstractDependencyGraph<Address> graph = new DependencyGraph<>(); AbstractDependencyGraph<Address> graph = new DependencyGraph<>();
Deque<Address> startPoints = findStartPoints(); Deque<Address> startPoints = findStartPoints();
Set<Address> unprocessed = new TreeSet<>(functionSet); // reliable processing order Set<Address> unprocessed = new TreeSet<>(functionSet); // reliable processing order
monitor.initialize(unprocessed.size());
while (!unprocessed.isEmpty()) { while (!unprocessed.isEmpty()) {
monitor.checkCanceled(); monitor.checkCanceled();
Address functionEntry = getNextStartFunction(startPoints, unprocessed); Address functionEntry = getNextStartFunction(startPoints, unprocessed);
@ -123,7 +123,7 @@ public class AcyclicCallGraphBuilder {
return startPoints; return startPoints;
} }
private void initializeNode(StackNode node, Set<Address> unprocessed) { private void initializeNode(StackNode node) {
FunctionManager fmanage = program.getFunctionManager(); FunctionManager fmanage = program.getFunctionManager();
Function function = fmanage.getFunctionAt(node.address); Function function = fmanage.getFunctionAt(node.address);
if (function.isThunk()) { if (function.isThunk()) {
@ -163,14 +163,16 @@ public class AcyclicCallGraphBuilder {
Address startFunction, TaskMonitor monitor) throws CancelledException { Address startFunction, TaskMonitor monitor) throws CancelledException {
VisitStack stack = new VisitStack(startFunction); VisitStack stack = new VisitStack(startFunction);
StackNode curnode = stack.peek(); StackNode curnode = stack.peek();
initializeNode(curnode, unprocessed); initializeNode(curnode);
graph.addValue(curnode.address); graph.addValue(curnode.address);
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
monitor.checkCanceled();
curnode = stack.peek(); curnode = stack.peek();
if (curnode.nextchild >= curnode.children.length) { // Node more to children to traverse for this node if (curnode.nextchild >= curnode.children.length) { // Node more to children to traverse for this node
unprocessed.remove(curnode.address); unprocessed.remove(curnode.address);
monitor.incrementProgress(1);
stack.pop(); stack.pop();
monitor.checkCanceled();
} }
else { else {
Address childAddr = curnode.children[curnode.nextchild++]; Address childAddr = curnode.children[curnode.nextchild++];
@ -178,7 +180,7 @@ public class AcyclicCallGraphBuilder {
if (unprocessed.contains(childAddr)) { if (unprocessed.contains(childAddr)) {
stack.push(childAddr); stack.push(childAddr);
StackNode nextnode = stack.peek(); StackNode nextnode = stack.peek();
initializeNode(nextnode, unprocessed); initializeNode(nextnode);
childAddr = nextnode.address; childAddr = nextnode.address;
graph.addValue(nextnode.address); graph.addValue(nextnode.address);
} }

View file

@ -84,21 +84,18 @@ public class Swing {
} }
/** /**
* A development/testing time method to make sure the current thread is the swing thread. * Logs a stack trace if the current calling thread is not the Swing thread
* @param errorMessage The message to display when the assert fails * @param errorMessage The message to display when not on the Swing thread
* @return true if the calling thread is the Swing thread
*/ */
public static void assertThisIsTheSwingThread(String errorMessage) { public static boolean assertSwingThread(String errorMessage) {
boolean isProductionMode =
!SystemUtilities.isInTestingMode() && !SystemUtilities.isInDevelopmentMode();
if (isProductionMode) {
return; // squash during production mode
}
if (!isSwingThread()) { if (!isSwingThread()) {
Throwable t = Throwable t =
ReflectionUtilities.filterJavaThrowable(new AssertException(errorMessage)); ReflectionUtilities.filterJavaThrowable(new AssertException(errorMessage));
Msg.error(SystemUtilities.class, errorMessage, t); Msg.error(Swing.class, errorMessage, t);
return false;
} }
return true;
} }
/** /**