Merge branch 'GP-3122_ghidra1_AutoTransaction'

This commit is contained in:
ghidra1 2023-02-28 14:53:27 -05:00
commit 725c752320
209 changed files with 1892 additions and 2054 deletions

View file

@ -647,6 +647,11 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
return true;
}
@Override
public Transaction openTransaction() {
return openTransaction("Property Change");
}
@Override
public int startTransaction() {
return startTransaction("Property Change");

View file

@ -19,8 +19,7 @@ import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import db.DBConstants;
import db.DBHandle;
import db.*;
import db.util.ErrorHandler;
import ghidra.framework.model.DomainFile;
import ghidra.framework.options.Options;
@ -243,6 +242,11 @@ public class ProgramDataTypeManager extends ProgramBasedDataTypeManagerDB
return program.isChangeable();
}
@Override
public Transaction openTransaction(String description) throws IllegalStateException {
return program.openTransaction(description);
}
@Override
public int startTransaction(String description) {
return program.startTransaction(description);

View file

@ -19,8 +19,7 @@ import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import db.DBConstants;
import db.DBHandle;
import db.*;
import db.util.ErrorHandler;
import ghidra.framework.model.DomainFile;
import ghidra.program.database.DataTypeArchiveDB;
@ -187,6 +186,11 @@ public class ProjectDataTypeManager extends DataTypeManagerDB
// TODO
}
@Override
public Transaction openTransaction(String description) throws IllegalStateException {
return dataTypeArchive.openTransaction(description);
}
@Override
public int startTransaction(String description) {
return dataTypeArchive.startTransaction(description);

View file

@ -1,87 +0,0 @@
/* ###
* 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.util;
import ghidra.program.model.listing.Program;
/**
* A convenience context for transaction IDs on a Ghidra program database
*
* <p>
* This is meant to be used as an idiom in a try-with-resources block:
*
* <pre>
* try (ProgramTransaction t = ProgramTransaction.open(program, "Demo")) {
* program.getMemory().....
* t.commit();
* }
* </pre>
*
* <p>
* This idiom is very useful if there is complex logic in your transaction, it's very easy to forget
* to close the transaction, especially if an error occurs, leaving the database in an open
* transaction indefinitely. The try-with-resources block will ensure that the transaction is closed
* in all circumstances. Note, however, that in order for the transaction to be committed, you must
* call {@link #commit()}.
*
* <p>
* Any exceptions within the block will cause {@code t.commit()} to be skipped, thus aborting the
* transaction.
*/
public class ProgramTransaction implements AutoCloseable {
protected Program program;
protected int tid;
protected boolean commit = false;
/**
* Start a transaction on the given program with the given description
*
* @param program the program to modify
* @param description a description of the transaction
*/
public static ProgramTransaction open(Program program, String description) {
int tid = program.startTransaction(description);
return new ProgramTransaction(program, tid);
}
private ProgramTransaction(Program program, int tid) {
this.program = program;
this.tid = tid;
}
/**
* Finish the transaction
*
* <p>
* If this is called before {@link #commit()}, then the transaction is aborted. This is called
* automatically at the close of a try-with-resources block.
*/
@Override
public void close() {
program.endTransaction(tid, commit);
}
/**
* Finish the transaction, and commit
*
* <p>
* This MUST be called in order to commit the transaction. The transaction is not committed
* until the close of the try-with-resources block.
*/
public void commit() {
commit = true;
}
}

View file

@ -17,6 +17,7 @@ package ghidra.program.model.data;
import java.util.*;
import db.Transaction;
import ghidra.util.InvalidNameException;
import ghidra.util.UniversalID;
import ghidra.util.exception.CancelledException;
@ -317,6 +318,26 @@ public interface DataTypeManager {
*/
public void setName(String name) throws InvalidNameException;
/**
* Returns true if this DataTypeManager can be modified.
* @return true if this DataTypeMangaer can be modified.
*/
public boolean isUpdatable();
/**
* Open new transaction. This should generally be done with a try-with-resources block:
* <pre>
* try (Transaction tx = dtm.openTransaction(description)) {
* // ... Do something
* }
* </pre>
*
* @param description a short description of the changes to be made.
* @return transaction object
* @throws IllegalStateException if this {@link DataTypeManager} has already been closed.
*/
public Transaction openTransaction(String description) throws IllegalStateException;
/**
* Starts a transaction for making changes in this data type manager.
* @param description a short description of the changes to be made.
@ -324,12 +345,6 @@ public interface DataTypeManager {
*/
public int startTransaction(String description);
/**
* Returns true if this DataTypeManager can be modified.
* @return true if this DataTypeMangaer can be modified.
*/
public boolean isUpdatable();
/**
* Ends the current transaction
* @param transactionID id of the transaction to end

View file

@ -18,6 +18,7 @@ package ghidra.program.model.data;
import java.io.IOException;
import java.util.LinkedList;
import db.Transaction;
import db.DBConstants;
import generic.jar.ResourceFile;
import ghidra.program.database.data.DataTypeManagerDB;
@ -32,6 +33,7 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB {
protected String name;
private int transactionCount;
private Long transaction;
private boolean commitTransaction;
/**
* Constructor for new temporary data-type manager using the default DataOrganization.
@ -87,14 +89,33 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB {
return super.getDataOrganization();
}
@Override
public Transaction openTransaction(String description) throws IllegalStateException {
return new Transaction() {
private int txId = startTransaction(description);
@Override
protected boolean endTransaction(boolean commit) {
StandAloneDataTypeManager.this.endTransaction(txId, commit);
return commitTransaction;
}
@Override
public boolean isSubTransaction() {
return true;
}
};
}
@Override
public synchronized int startTransaction(String description) {
if (transaction == null) {
transaction = dbHandle.startTransaction();
commitTransaction = true;
}
transactionCount++;
return transaction.intValue();
}
@Override
@ -110,9 +131,12 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB {
if (transaction.intValue() != transactionID) {
throw new IllegalArgumentException("Transaction id does not match current transaction");
}
if (!commit) {
commitTransaction = false;
}
if (--transactionCount == 0) {
try {
dbHandle.endTransaction(transaction.longValue(), commit);
dbHandle.endTransaction(transaction.longValue(), commitTransaction);
transaction = null;
}
catch (IOException e) {

View file

@ -18,6 +18,7 @@ package ghidra.program.model.listing;
import java.util.List;
import java.util.Set;
import db.Transaction;
import ghidra.framework.model.UserData;
import ghidra.framework.options.Options;
import ghidra.program.model.util.*;
@ -26,6 +27,19 @@ import ghidra.util.exception.PropertyTypeMismatchException;
public interface ProgramUserData extends UserData {
/**
* Open new transaction. This should generally be done with a try-with-resources block:
* <pre>
* try (Transaction tx = pud.openTransaction(description)) {
* // ... Do something
* }
* </pre>
*
* @return transaction object
* @throws IllegalStateException if this {@link ProgramUserData} has already been closed.
*/
public Transaction openTransaction();
/**
* Start a transaction prior to changing any properties
* @return transaction ID needed for endTransaction

View file

@ -21,7 +21,7 @@ import java.nio.ByteOrder;
import org.apache.commons.lang3.ArrayUtils;
public interface MemBufferAdapter extends MemBuffer {
public interface MemBufferMixin extends MemBuffer {
int getBytes(ByteBuffer buffer, int addressOffset);
@Override

View file

@ -1,311 +0,0 @@
/* ###
* 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.database;
import java.io.IOException;
import javax.help.UnsupportedOperationException;
import db.DBHandle;
import db.NoTransactionException;
import db.util.ErrorHandler;
import ghidra.framework.model.AbortedTransactionListener;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.listing.ProgramUserData;
import ghidra.util.Msg;
/**
* Provides syntax for opening a database transaction using a try-with-resources block
*
* <p>
* For example, using {@link UndoableDomainObject#startTransaction(String)} directly:
*
* <pre>
* int txid = program.startTransaction("Do a thing");
* try {
* // ... Do that thing
* }
* finally {
* program.endTransaction(txid, true);
* }
* </pre>
*
* <p>
* Can be expressed using an undoable transaction instead:
*
* <pre>
* try (UndoableTransaction txid = UndoableTransaction.start(program, "Do a thing")) {
* // ... Do that thing
* }
* </pre>
*/
public interface UndoableTransaction extends AutoCloseable {
/**
* Open a transaction directly on a database handle
*
* @param handle the handle
* @param errHandler a handler for database errors, usually the domain object
* @return the transaction handle
*/
public static UndoableTransaction start(DBHandle handle, ErrorHandler errHandler) {
long tid = handle.startTransaction();
return new DBHandleUndoableTransaction(handle, tid, errHandler);
}
/**
* Open a transaction on a domain object
*
* @param domainObject the domain object
* @param description a description of the change
* @return the transaction handle
*/
public static UndoableTransaction start(UndoableDomainObject domainObject, String description) {
int tid = domainObject.startTransaction(description);
return new DomainObjectUndoableTransaction(domainObject, tid);
}
/**
* Open a transaction on a domain object
*
* <p>
* Even if this transaction is committed, if a sub-transaction is aborted, this transaction
* could become aborted, too. The listener can be used to detect this situation.
*
* @param domainObject the domain object
* @param description a description of the change
* @param listener a listener for aborted transactions
* @return the transaction handle
*/
public static UndoableTransaction start(UndoableDomainObject domainObject, String description,
AbortedTransactionListener listener) {
int tid = domainObject.startTransaction(description, listener);
return new DomainObjectUndoableTransaction(domainObject, tid);
}
/**
* Open a transaction on a data type manager
*
* @param dataTypeManager the data type manager
* @param description a description of the change
* @return the transaction handle
*/
public static UndoableTransaction start(DataTypeManager dataTypeManager, String description) {
int tid = dataTypeManager.startTransaction(description);
return new DataTypeManagerUndoableTransaction(dataTypeManager, tid);
}
/**
* Open a transaction on program user data
*
* @param userData the user data
* @return the transaction handle
*/
public static UndoableTransaction start(ProgramUserData userData) {
int tid = userData.startTransaction();
return new ProgramUserDataUndoableTransaction(userData, tid);
}
abstract class AbstractUndoableTransaction implements UndoableTransaction {
private boolean commit = true;
private boolean open = true;
protected AbstractUndoableTransaction() {
}
abstract void endTransaction(@SuppressWarnings("hiding") boolean commit);
@Override
public void abortOnClose() {
commit = false;
}
@Override
public void commitOnClose() {
commit = true;
}
@Override
public void abort() {
if (open) {
open = false;
endTransaction(false);
}
}
@Override
public void commit() {
if (open) {
open = false;
endTransaction(true);
}
}
@Override
public void close() {
if (open) {
open = false;
endTransaction(commit);
}
}
}
abstract class AbstractLongUndoableTransaction extends AbstractUndoableTransaction {
final long transactionID;
public AbstractLongUndoableTransaction(long transactionID) {
super();
this.transactionID = transactionID;
}
}
abstract class AbstractIntUndoableTransaction extends AbstractUndoableTransaction {
final int transactionID;
public AbstractIntUndoableTransaction(int transactionID) {
super();
this.transactionID = transactionID;
}
}
class DBHandleUndoableTransaction extends AbstractLongUndoableTransaction {
private final DBHandle handle;
private final ErrorHandler errHandler;
public DBHandleUndoableTransaction(DBHandle handle, long transactionID,
ErrorHandler errHandler) {
super(transactionID);
this.handle = handle;
this.errHandler = errHandler;
}
@Override
void endTransaction(boolean commit) {
if (!commit) {
Msg.debug(this, "Aborting transaction");
}
try {
handle.endTransaction(transactionID, commit);
}
catch (IOException e) {
errHandler.dbError(e);
}
}
}
class DomainObjectUndoableTransaction extends AbstractIntUndoableTransaction {
private final UndoableDomainObject domainObject;
private DomainObjectUndoableTransaction(UndoableDomainObject domainObject, int tid) {
super(tid);
this.domainObject = domainObject;
}
@Override
void endTransaction(boolean commit) {
if (!commit) {
Msg.debug(this, "Aborting transaction");
}
domainObject.endTransaction(transactionID, commit);
}
}
class DataTypeManagerUndoableTransaction extends AbstractIntUndoableTransaction {
private final DataTypeManager dataTypeManager;
private DataTypeManagerUndoableTransaction(DataTypeManager dataTypeManager, int tid) {
super(tid);
this.dataTypeManager = dataTypeManager;
}
@Override
void endTransaction(boolean commit) {
dataTypeManager.endTransaction(transactionID, commit);
}
}
class ProgramUserDataUndoableTransaction extends AbstractIntUndoableTransaction {
private final ProgramUserData userData;
private ProgramUserDataUndoableTransaction(ProgramUserData userData, int tid) {
super(tid);
this.userData = userData;
}
@Override
public void abortOnClose() {
throw new UnsupportedOperationException();
}
@Override
public void abort() {
throw new UnsupportedOperationException();
}
@Override
void endTransaction(boolean commit) {
userData.endTransaction(transactionID);
}
}
/**
* Set this transaction to commit when closed
*
* <p>
* This is the default behavior. If an error occurs, or when the end of the try block is
* reached, the transaction will be committed. The user is expected to undo unwanted
* transactions, including those committed with an error. It could be the results are still
* mostly correct. Additionally, aborting a transaction can roll back other concurrent
* transactions.
*/
void commitOnClose();
/**
* Set this transaction to abort by when closed
*
* <p>
* Ordinarily, if an error occurs, the transaction is committed as is. The user is expected to
* undo unwanted transactions. Calling this method will cause the transaction to be aborted
* instead. <b>WARNING:</b> Aborting this transaction may abort other concurrent transactions.
* Use with extreme care. <b>NOTE:</b> Use of this method requires that the transaction be
* explicitly committed using {@link #commit()}. When this transaction is closed, if it hasn't
* been committed, it will be aborted.
*/
void abortOnClose();
/**
* Commit the transaction and close it immediately
*
* <p>
* Note that attempting to make changes after this call will likely result in a
* {@link NoTransactionException}.
*/
void commit();
/**
* Abort the transaction and close it immediately
*
* <p>
* Note that attempting to make changes after this call will likely result in a
* {@link NoTransactionException}. <b>WARNING:</b> Aborting this transaction may abort other
* concurrent transactions. Use with extreme care.
*/
void abort();
@Override
void close();
}

View file

@ -22,10 +22,10 @@ import java.util.List;
import org.junit.*;
import db.Transaction;
import generic.test.AbstractGenericTest;
import ghidra.app.plugin.assembler.*;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.util.ProgramTransaction;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.lang.*;
@ -77,7 +77,7 @@ public class PublicAPITest extends AbstractGenericTest {
program = new ProgramDB("test", toy, toy.getDefaultCompilerSpec(), this);
InstructionIterator it;
try (ProgramTransaction tid = ProgramTransaction.open(program, "Test")) {
try (Transaction tx = program.openTransaction("Test")) {
program.getMemory()
.createInitializedBlock(".text", addr(0x00400000), 0x1000, (byte) 0,
TaskMonitor.DUMMY, false);
@ -86,8 +86,6 @@ public class PublicAPITest extends AbstractGenericTest {
it = asm.assemble(addr(0x00400000),
"brds 0x00400004",
"add r0, #6");
tid.commit();
}
List<Instruction> result = new ArrayList<>();

View file

@ -19,6 +19,7 @@ import java.io.File;
import java.io.IOException;
import java.util.*;
import db.Transaction;
import ghidra.framework.model.*;
import ghidra.framework.options.Options;
import ghidra.framework.store.LockException;
@ -46,6 +47,11 @@ public class StubProgram implements Program {
throw new UnsupportedOperationException();
}
@Override
public Transaction openTransaction(String description) throws IllegalStateException {
throw new UnsupportedOperationException();
}
@Override
public int startTransaction(String description, AbortedTransactionListener listener) {
throw new UnsupportedOperationException();
@ -57,7 +63,7 @@ public class StubProgram implements Program {
}
@Override
public Transaction getCurrentTransaction() {
public TransactionInfo getCurrentTransactionInfo() {
throw new UnsupportedOperationException();
}

View file

@ -17,6 +17,7 @@ package ghidra.program.model.data;
import java.util.*;
import db.Transaction;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -191,6 +192,11 @@ public class TestDoubleDataTypeManager implements DataTypeManager {
throw new UnsupportedOperationException();
}
@Override
public Transaction openTransaction(String description) throws IllegalStateException {
throw new UnsupportedOperationException();
}
@Override
public int startTransaction(String description) {
throw new UnsupportedOperationException();

View file

@ -17,6 +17,7 @@ package ghidra.program.model.data;
import java.util.*;
import db.Transaction;
import ghidra.util.InvalidNameException;
import ghidra.util.UniversalID;
import ghidra.util.exception.CancelledException;
@ -215,6 +216,11 @@ public class TestDummyDataTypeManager implements DataTypeManager {
}
@Override
public Transaction openTransaction(String description) throws IllegalStateException {
throw new UnsupportedOperationException();
}
@Override
public int startTransaction(String description) {
// stub