GP-2437: Change DBAddressFieldCodec to use FixedField10

This commit is contained in:
Dan
2022-08-15 14:02:22 -04:00
parent b289708a00
commit c0c25e3805
114 changed files with 2354 additions and 652 deletions
@@ -83,7 +83,7 @@ class DefaultVar implements LValInternal, Var {
check.check(ctx.parser, name);
this.ctx = ctx;
this.name = name;
try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type")) {
this.type = ctx.dtm.resolve(type, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}
@@ -26,7 +26,7 @@ public abstract class Expr implements RValInternal {
protected Expr(StructuredSleigh ctx, DataType type) {
this.ctx = ctx;
try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type")) {
this.type = ctx.dtm.resolve(type, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}
@@ -19,9 +19,21 @@ import java.io.IOException;
import db.DBLongIterator;
/**
* An abstract implementation of {@link DirectedLongKeyIterator}
*
* <p>
* Essentially, this just wraps a {@link DBLongIterator}, but imposes and encapsulates its
* direction.
*/
public abstract class AbstractDirectedLongKeyIterator implements DirectedLongKeyIterator {
protected final DBLongIterator it;
/**
* Wrap the given iterator
*
* @param it the iterator
*/
public AbstractDirectedLongKeyIterator(DBLongIterator it) {
this.it = it;
}
@@ -19,9 +19,21 @@ import java.io.IOException;
import db.RecordIterator;
/**
* An abstract implementation of {@link DirectedRecordIterator}
*
* <p>
* Essentially, this just wraps a {@link RecordIterator}, but imposes and encapsulates its
* direction.
*/
public abstract class AbstractDirectedRecordIterator implements DirectedRecordIterator {
protected final RecordIterator it;
/**
* Wrap the given iterator
*
* @param it the iterator
*/
public AbstractDirectedRecordIterator(RecordIterator it) {
this.it = it;
}
@@ -19,6 +19,10 @@ import java.io.IOException;
import db.DBLongIterator;
/**
* A wrapper of {@link DBLongIterator} that runs it backward and implements
* {@link DirectedLongKeyIterator}
*/
public class BackwardLongKeyIterator extends AbstractDirectedLongKeyIterator {
public BackwardLongKeyIterator(DBLongIterator it) {
super(it);
@@ -20,6 +20,10 @@ import java.io.IOException;
import db.DBRecord;
import db.RecordIterator;
/**
* A wrapper of {@link RecordIterator} that runs it backward and implements
* {@link DirectedRecordIterator}
*/
public class BackwardRecordIterator extends AbstractDirectedRecordIterator {
public BackwardRecordIterator(RecordIterator it) {
super(it);
@@ -22,16 +22,115 @@ import db.DBRecord;
import ghidra.program.database.DatabaseObject;
import ghidra.util.LockHold;
import ghidra.util.database.DBCachedObjectStoreFactory.DBFieldCodec;
import ghidra.util.database.annot.DBAnnotatedObjectInfo;
/**
* An object backed by a {@link DBRecord}
*
* <p>
* Essentially, this is a data access object (DAO) for Ghidra's custom database engine. Not all
* object fields necessarily have a corresponding database field. Instead, those fields are
* annotated, and various methods are provided for updating the record, and conversely, re-loading
* fields from the record. These objects are managed using a {@link DBCachedObjectStore}. An example
* object definition:
*
* <pre>
* interface Person {
* // ...
* }
*
* &#64;DBAnnotatedObjectInfo(version = 1)
* public class DBPerson extends DBAnnotatedObject implements Person {
* public static final String TABLE_NAME = "Person"; // Conventionally defined here
*
* // Best practice is to define column names, then use in annotations
* static final String NAME_COLUMN_NAME = "Name";
* static final String ADDRESS_COLUMN_NAME = "Address";
*
* // Column handles
* &#64;DBAnnotatedColumn(NAME_COLUMN_NAME)
* static DBObjectColumn NAME_COLUMN;
* &#64;DBAnnotatedColumn(ADDRESS_COLUMN_NAME)
* static DBObjectColumn ADDRESS_COLUMN;
*
* // Column-backed fields
* &#64;DBAnnotatedField(column = NAME_COLUMN_NAME, indexed = true)
* private String name;
* &#64;DBAnnotatedField(column = ADDRESS_COLUMN_NAME)
* private String address;
*
* DBPerson(DBCachedObjectStore<DBPerson> store, DBRecord record) {
* super(store, record);
* }
*
* // Not required, but best practice
* private void set(String name, String address) {
* this.name = name;
* this.address = address;
* update(NAME_COLUMN, ADDRESS_COLUMN);
* }
*
* // ... other methods, getters, setters
* }
* </pre>
*
* <p>
* See {@link DBCachedObjectStoreFactory} for example code that uses the example {@code DBPerson}
* class.
*
* <p>
* All realizations of {@link DBAnnotatedObject} must be annotated with
* {@link DBAnnotatedObjectInfo}. This, along with the field annotations, are used to derive the
* table schema. Note the inclusion of a {@code TABLE_NAME} field. It is not required, nor is it
* used implicitly. It's included in this example as a manner of demonstrating best practice. When
* instantiating the object store, the field is used to provide the table name.
* <p>
* Next, we define the column names. These are not required nor used implicitly, but using literal
* strings in the column annotations is discouraged. Next, we declare variables to receive column
* handles. These are essentially the column numbers, but we have a named handle for each. They are
* initialized automatically the first time a store is created for this class.
* <p>
* Next we declare the variables representing the actual column values. Their initialization varies
* depending on how the object is instantiated. When creating a new object, the fields remain
* uninitialized. In some cases, it may be appropriate to provide an initial (default) value in the
* usual fashion, e.g., {@code private String address = "123 Pine St.";} In this case, the
* corresponding database field of the backing record is implicitly initialized upon creation. If
* the object is being loaded from a table, its fields are initialized with values from its backing
* record.
*
* <p>
* Next we define the constructor. There are no requirements on its signature, but it must call
* {@link #DBAnnotatedObject(DBCachedObjectStore, DBRecord) super}, so it likely takes its
* containing store and its backing record. Having the same signature as its super constructor
* allows the store to be created using a simple method reference, e.g., {@code DBPerson::new}.
* Additional user-defined parameters may be accepted. To pass such parameters, a lambda is
* recommended when creating the object store.
* <p>
* Finally, we demonstrate how to update the record. The record is <em>not</em> implicitly updated
* by direct modification of an annotated field. All setters must call
* {@link #update(DBObjectColumn...)} after updating a field. A common practice, especially when the
* object will have all its fields set at once, is to include a {@code set} method that initializes
* the fields and updates the record in one {@link #update(DBObjectColumn...)}.
*
* <p>
* Note that there is no way to specify the primary key. For object stores, the primary key is
* always the object id, and its type is always {@code long}.
*/
public class DBAnnotatedObject extends DatabaseObject {
private final DBCachedObjectStore<?> store;
private final DBCachedDomainObjectAdapter adapter;
private final List<DBFieldCodec<?, ?, ?>> codecs;
private final List<DBFieldCodec<?, ?, ?>> codecs; // The codecs, ordered by field
DBRecord record;
DBRecord record; // The backing record
/**
* The object constructor
*
* @param store the store containing this object
* @param record the record backing this object
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public DBAnnotatedObject(DBCachedObjectStore<?> store, DBRecord record) {
protected DBAnnotatedObject(DBCachedObjectStore<?> store, DBRecord record) {
super(store == null ? null : store.cache, record == null ? -1 : record.getKey());
this.store = store;
this.record = record;
@@ -51,54 +150,77 @@ public class DBAnnotatedObject extends DatabaseObject {
* @return the opaque object id
*/
public ObjectKey getObjectKey() {
return new ObjectKey(store.adapter, store.table.getName(), key);
return new ObjectKey(store.table, key);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void write(DBObjectColumn column) {
protected void doWrite(DBObjectColumn column) {
DBFieldCodec codec = codecs.get(column.columnNumber);
codec.store(this, record);
}
/**
* 1-arity version of {@link #update(DBObjectColumn...)}
*
* @param column the column
*/
protected void update(DBObjectColumn column) {
write(column);
doWrite(column);
try (LockHold hold = LockHold.lock(store.writeLock())) {
updated();
doUpdated();
}
catch (IOException e) {
store.dbError(e);
}
}
/**
* 2-arity version of {@link #update(DBObjectColumn...)}
*
* @param col1 a column
* @param col2 another column
*/
protected void update(DBObjectColumn col1, DBObjectColumn col2) {
write(col1);
write(col2);
doWrite(col1);
doWrite(col2);
try (LockHold hold = LockHold.lock(store.writeLock())) {
updated();
doUpdated();
}
catch (IOException e) {
store.dbError(e);
}
}
/**
* 2-arity version of {@link #update(DBObjectColumn...)}
*
* @param col1 a column
* @param col2 another column
* @param col3 another column
*/
protected void update(DBObjectColumn col1, DBObjectColumn col2, DBObjectColumn col3) {
write(col1);
write(col2);
write(col3);
doWrite(col1);
doWrite(col2);
doWrite(col3);
try (LockHold hold = LockHold.lock(store.writeLock())) {
updated();
doUpdated();
}
catch (IOException e) {
store.dbError(e);
}
}
/**
* Write the given columns into the record and update the table
*
* @param columns the columns to update
*/
protected void update(DBObjectColumn... columns) {
for (DBObjectColumn c : columns) {
write(c);
doWrite(c);
}
try (LockHold hold = LockHold.lock(store.writeLock())) {
updated();
doUpdated();
}
catch (IOException e) {
store.dbError(e);
@@ -110,28 +232,36 @@ public class DBAnnotatedObject extends DatabaseObject {
for (DBFieldCodec codec : codecs) {
codec.store(this, record);
}
updated();
doUpdated();
}
protected void updated() throws IOException {
protected void doUpdated() throws IOException {
store.table.putRecord(record);
}
/**
* Called when the object's fields are populated.
* Extension point: Called when the object's fields are populated.
*
* This provides an opportunity for the object to initialize any remaining (usually
* non-database-backed) fields.
* <p>
* This provides an opportunity for the object to initialize any non-database-backed fields that
* depend on the database-backed fields. Note that its use may indicate a situation better
* solved by a custom {@link DBFieldCodec}. If both the database-backed and non-database-backed
* fields are used frequently, then a codec may not be indicated. If the database-backed fields
* are only used in this method or to encode another frequently-used field, then a codec is
* likely better.
*
* <p>
* For a new object, the database-backed fields remain at their initial values. They will be
* saved after this method returns, so they may be further initialized with custom logic.
*
* For an object loaded from the database, the database-backed fields were already populated
* from the record. They are <em>not</em> automatically saved after this method returns. This
* method should not further initialize database-backed fields in this case.
* <p>
* For an object loaded from the database, the database-backed fields are already populated from
* the record when this method is called. They are <em>not</em> automatically saved after this
* method returns. This method should not further initialize database-backed fields in this
* case.
*
* @param created {@code true} to indicate the object is being created, or {@code false} to
* indicate it is being restored.
* @param created {@code true} when object is being created, or {@code false} when it is being
* loaded.
* @throws IOException if further initialization fails.
*/
protected void fresh(boolean created) throws IOException {
@@ -187,6 +317,12 @@ public class DBAnnotatedObject extends DatabaseObject {
return true;
}
/**
* Check if this object has been deleted
*
* @see #isDeleted(ghidra.util.Lock)
* @return true if deleted
*/
public boolean isDeleted() {
return super.isDeleted(adapter.getLock());
}
@@ -17,6 +17,11 @@ package ghidra.util.database;
import db.DBRecord;
/**
* Needed by a {@link DBCachedObjectStore} to describe how to construct the objects it manages
*
* @param <T> the type of objects in the store
*/
public interface DBAnnotatedObjectFactory<T extends DBAnnotatedObject> {
T create(DBCachedObjectStore<T> store, DBRecord record);
}
@@ -24,6 +24,15 @@ import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.task.TaskMonitor;
/**
* A domain object that can use {@link DBCachedObjectStoreFactory}.
*
* <p>
* Technically, this only introduces a read-write lock to the domain object. The
* {@link DBCachedObjectStoreFactory} and related require this read-write lock. Sadly, this idea
* didn't pan out, and that read-write lock is just a degenerate wrapper of the Ghidra
* {@link ghidra.util.Lock}, which is not a read-write lock. This class may disappear.
*/
public abstract class DBCachedDomainObjectAdapter extends DBDomainObjectSupport {
static class SwingAwareReadWriteLock extends ReentrantReadWriteLock {
@@ -151,12 +160,20 @@ public abstract class DBCachedDomainObjectAdapter extends DBDomainObjectSupport
protected ReadWriteLock rwLock;
/**
* @see {@link DBDomainObjectSupport}
*/
protected DBCachedDomainObjectAdapter(DBHandle dbh, DBOpenMode openMode, TaskMonitor monitor,
String name, int timeInterval, int bufSize, Object consumer) {
super(dbh, openMode, monitor, name, timeInterval, bufSize, consumer);
this.rwLock = new GhidraLockWrappingRWLock(lock);
}
/**
* Get the "read-write" lock
*
* @return the lock
*/
public ReadWriteLock getReadWriteLock() {
return rwLock;
}
@@ -26,6 +26,15 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This provides the implementation of {@link Map#entrySet()} for
* {@link DBCachedObjectStore#asMap()}
*
* <p>
* The store acts as a map from object id to object, thus an entry has a long key and object value.
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreEntrySet<T extends DBAnnotatedObject>
implements NavigableSet<Entry<Long, T>> {
protected final DBCachedObjectStore<T> store;
@@ -25,6 +25,14 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This is the sub-ranged form of {@link DBCachedObjectStoreEntrySet}
*
* <p>
* For example, this can be obtained via {@code store.asMap().subMap(...).entrySet()}.
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreEntrySubSet<T extends DBAnnotatedObject>
extends DBCachedObjectStoreEntrySet<T> {
@@ -26,19 +26,24 @@ import db.Field;
import db.util.ErrorHandler;
import ghidra.util.LockHold;
/**
* This provides the implementation of {@link DBCachedObjectIndex#get(Object)}
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreFoundKeysValueCollection<T extends DBAnnotatedObject>
implements Collection<T> {
protected final DBCachedObjectStore<T> store;
protected final ErrorHandler errHandler;
protected final ReadWriteLock lock;
protected final Set<Long> keys;
protected final List<Long> keys;
public DBCachedObjectStoreFoundKeysValueCollection(DBCachedObjectStore<T> store,
ErrorHandler errHandler, ReadWriteLock lock, Field[] keys) {
this.store = store;
this.errHandler = errHandler;
this.lock = lock;
this.keys = Stream.of(keys).map(Field::getLongValue).collect(Collectors.toSet());
this.keys = Stream.of(keys).map(Field::getLongValue).collect(Collectors.toList());
}
@Override
@@ -25,6 +25,9 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This provides the implementation of {@link Map#keySet()} for {@link DBCachedObjectStore#asMap()}
*/
public class DBCachedObjectStoreKeySet implements NavigableSet<Long> {
protected final DBCachedObjectStore<?> store;
protected final ErrorHandler errHandler;
@@ -24,6 +24,13 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This is the sub-ranged form of {@link DBCachedObjectStoreKeySubSet}
*
* <p>
* For example, this can be obtained via {@code store.asMap().subMap(...).keySet()} or
* {@code map.keySet().subSet(...)}.
*/
public class DBCachedObjectStoreKeySubSet extends DBCachedObjectStoreKeySet {
protected final Range<Long> keyRange;
@@ -25,6 +25,16 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This provides the implementation of {@link DBCachedObjectStore#asMap()}
*
* <p>
* This implements a map from object id (long) to object. Objects cannot be added directly to this
* map, e.g., {@link #put(Long, DBAnnotatedObject)} is not supported. Instead use
* {@link DBCachedObjectStore#create(long)}.
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreMap<T extends DBAnnotatedObject> implements NavigableMap<Long, T> {
protected final DBCachedObjectStore<T> store;
protected final ErrorHandler errHandler;
@@ -22,6 +22,14 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This is the sub-ranged form of {@link DBCachedObjectStoreMap}
*
* <p>
* For example, this can be obtained via {@code store.asMap().subMap(...)}.
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreSubMap<T extends DBAnnotatedObject>
extends DBCachedObjectStoreMap<T> {
protected final Range<Long> keyRange;
@@ -15,13 +15,17 @@
*/
package ghidra.util.database;
import java.util.Collection;
import java.util.Iterator;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This provides the implementation of {@link Map#values()} for {@link DBCachedObjectStore#asMap()}
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreValueCollection<T extends DBAnnotatedObject>
implements Collection<T> {
protected final DBCachedObjectStore<T> store;
@@ -24,6 +24,12 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This is the sub-ranged form of {@link DBCachedObjectStoreValueCollection}
*
* <p>
* For example, this can be obtained via {@code store.asMap().subMap(...).values()}.
*/
public class DBCachedObjectStoreValueSubCollection<T extends DBAnnotatedObject>
extends DBCachedObjectStoreValueCollection<T> {
protected final Range<Long> keyRange;
@@ -18,6 +18,18 @@ package ghidra.util.database;
import java.util.ArrayList;
import java.util.List;
import ghidra.util.database.annot.DBAnnotatedColumn;
/**
* An opaque handle to the column backing an object field
*
* <p>
* Each should be declared as a static field of the same class whose field it describes, probably
* with package-only access. Each must also be annotated with {@link DBAnnotatedColumn}. For an
* example, see the documentation of {@link DBAnnotatedObject}. The annotated field receives its
* value the first time a store is created for the containing class. Until then, it is
* uninitialized.
*/
public class DBObjectColumn {
static List<DBObjectColumn> instances = new ArrayList<>(20);
@@ -17,6 +17,9 @@ package ghidra.util.database;
import db.DBConstants;
/**
* An enum, providing a type-safe version of {@link DBConstants}.
*/
public enum DBOpenMode {
CREATE(DBConstants.CREATE),
UPDATE(DBConstants.UPDATE),
@@ -1,56 +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 db.DBHandle;
public class DBTransaction implements AutoCloseable {
public static DBTransaction start(DBHandle handle, boolean commitByDefault) {
long tid = handle.startTransaction();
return new DBTransaction(handle, tid, commitByDefault);
}
private final DBHandle handle;
private final long tid;
private boolean commit;
private boolean open = true;
private DBTransaction(DBHandle handle, long tid, boolean commitByDefault) {
this.handle = handle;
this.tid = tid;
this.commit = commitByDefault;
}
public void abort() throws IOException {
open = false;
handle.endTransaction(tid, false);
}
public void commit() throws IOException {
open = false;
handle.endTransaction(tid, true);
}
@Override
public void close() throws IOException {
if (open) {
handle.endTransaction(tid, commit);
}
}
}
@@ -20,18 +20,55 @@ import java.io.IOException;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
public interface DirectedIterator<T> {
public enum Direction {
FORWARD, BACKWARD;
import db.Table;
static Direction reverse(Direction direction) {
if (direction == FORWARD) {
/**
* An iterator over some component of a {@link Table}
*
* @param <T> the type of the component, i.e., a key or record
*/
public interface DirectedIterator<T> {
/**
* The direction of iteration
*/
public enum Direction {
FORWARD {
@Override
Direction reverse() {
return BACKWARD;
}
return FORWARD;
},
BACKWARD {
@Override
Direction reverse() {
return FORWARD;
}
};
/**
* Get the reverse of this direction
*
* @return the reverse
*/
abstract Direction reverse();
/**
* Get the reverse of the given direction
*
* @param direction the direction
* @return the reverse
*/
static Direction reverse(Direction direction) {
return direction.reverse();
}
}
/**
* Get the discrete lower bound of the given range
*
* @param range the range
* @return the lower bound
*/
static long toIteratorMin(Range<Long> range) {
if (range == null) {
return Long.MIN_VALUE;
@@ -47,6 +84,12 @@ public interface DirectedIterator<T> {
}
}
/**
* Get the discrete upper bound of the given range
*
* @param range the range
* @return the upper bound
*/
static long toIteratorMax(Range<Long> range) {
if (range == null) {
return Long.MAX_VALUE;
@@ -62,17 +105,51 @@ public interface DirectedIterator<T> {
}
}
/**
* Compute the effective starting point for a forward iterator starting at the given bound
*
* @param range the range describing a limited view of keys
* @param bound the starting key
* @param inclusive whether the starting key is included
* @return the starting point, inclusive
*/
static long clampLowerBound(Range<Long> range, long bound, boolean inclusive) {
return Math.max(toIteratorMin(range), inclusive ? bound : bound + 1);
}
/**
* Compute the effective starting point for a backward iterator starting at the given bound
*
* @param range the range describing a limited view of keys
* @param bound the starting key
* @param inclusive whether the starting key is included
* @return the starting point, inclusive
*/
static long clampUpperBound(Range<Long> range, long bound, boolean inclusive) {
return Math.min(toIteratorMax(range), inclusive ? bound : bound - 1);
}
/**
* Check if the table has another record
*
* @return true if so
* @throws IOException if the table cannot be read
*/
boolean hasNext() throws IOException;
/**
* Get the component of the next record
*
* @return the component
* @throws IOException if the table cannot be read
*/
T next() throws IOException;
/**
* Delete the current record
*
* @return true if successful
* @throws IOException if the table cannot be accessed
*/
boolean delete() throws IOException;
}
@@ -21,7 +21,19 @@ import com.google.common.collect.Range;
import db.Table;
/**
* An iterator over keys of a table
*/
public interface DirectedLongKeyIterator extends DirectedIterator<Long> {
/**
* Get an iterator over the table, restricted to the given range, in the given direction
*
* @param table the table
* @param keyRange the limited range
* @param direction the direction
* @return the iterator
* @throws IOException if the table cannot be read
*/
public static AbstractDirectedLongKeyIterator getIterator(Table table, Range<Long> keyRange,
Direction direction) throws IOException {
long min = DirectedIterator.toIteratorMin(keyRange);
@@ -22,8 +22,20 @@ import com.google.common.collect.Range;
import db.*;
/**
* An iterator over records of a table
*/
public interface DirectedRecordIterator extends DirectedIterator<DBRecord> {
/**
* Get an iterator over the table, restricted to the given range of keys, in the given direction
*
* @param table the table
* @param keyRange the limited range
* @param direction the direction
* @return the iterator
* @throws IOException if the table cannot be read
*/
public static AbstractDirectedRecordIterator getIterator(Table table, Range<Long> keyRange,
Direction direction) throws IOException {
long min = DirectedIterator.toIteratorMin(keyRange);
@@ -34,6 +46,13 @@ public interface DirectedRecordIterator extends DirectedIterator<DBRecord> {
return new BackwardRecordIterator(table.iterator(min, max, max));
}
/**
* Given an iterator over a closed range. Change its behavior to exclude the lower bound
*
* @param it the iterator over the closed range
* @param columnIndex the column number whose index being iterated
* @param exclude the lower bound to be excluded
*/
private static DirectedRecordIterator applyBegFilter(DirectedRecordIterator it, int columnIndex,
Field exclude) throws IOException {
return new DirectedRecordIterator() {
@@ -71,6 +90,13 @@ public interface DirectedRecordIterator extends DirectedIterator<DBRecord> {
};
}
/**
* Given an iterator over a closed range. Change its behavior to exclude the upper bound
*
* @param it the iterator over the closed range
* @param columnIndex the column number whose index being iterated
* @param exclude the upper bound to be excluded
*/
private static DirectedRecordIterator applyEndFilter(DirectedRecordIterator it, int columnIndex,
Field exclude) throws IOException {
return new DirectedRecordIterator() {
@@ -108,6 +134,17 @@ public interface DirectedRecordIterator extends DirectedIterator<DBRecord> {
return it;
}
/**
* Get an iterator over the table using a given index, restricted to the given range of values,
* in the given direction
*
* @param table the table
* @param columnIndex the column number of the index
* @param fieldRange the limited range
* @param direction the direction
* @return the iterator
* @throws IOException if the table cannot be read
*/
public static DirectedRecordIterator getIndexIterator(Table table, int columnIndex,
Range<Field> fieldRange, Direction direction) throws IOException {
Field lower = fieldRange.hasLowerBound() ? fieldRange.lowerEndpoint() : null;
@@ -19,6 +19,10 @@ import java.io.IOException;
import db.DBLongIterator;
/**
* A wrapper of {@link DBLongIterator} that runs it forward and implements
* {@link DirectedLongKeyIterator}
*/
public class ForwardLongKeyIterator extends AbstractDirectedLongKeyIterator {
public ForwardLongKeyIterator(DBLongIterator it) {
super(it);
@@ -20,6 +20,10 @@ import java.io.IOException;
import db.DBRecord;
import db.RecordIterator;
/**
* A wrapper of {@link RecordIterator} that runs it forward and implements
* {@link DirectedRecordIterator}
*/
public class ForwardRecordIterator extends AbstractDirectedRecordIterator {
public ForwardRecordIterator(RecordIterator it) {
super(it);
@@ -17,24 +17,22 @@ package ghidra.util.database;
import java.util.Objects;
import ghidra.framework.data.DomainObjectAdapterDB;
import db.Table;
/**
* Enough information to uniquely identify a trace object
* An opaque handle uniquely identifying a database-backed object
*/
public class ObjectKey implements Comparable<ObjectKey> {
private final DomainObjectAdapterDB adapter;
private final String tableName;
private final Table table;
private final long key;
private final int hash;
public ObjectKey(DomainObjectAdapterDB adapter, String tableName, long key) {
this.adapter = adapter;
this.tableName = tableName;
public ObjectKey(Table table, long key) {
this.table = table;
this.key = key;
this.hash = Objects.hash(System.identityHashCode(adapter), tableName, key);
this.hash = Objects.hash(System.identityHashCode(table), key);
}
@Override
@@ -43,10 +41,7 @@ public class ObjectKey implements Comparable<ObjectKey> {
return false;
}
ObjectKey that = (ObjectKey) obj;
if (this.adapter != that.adapter) {
return false;
}
if (!(Objects.equals(this.tableName, that.tableName))) {
if (this.table != that.table) {
return false;
}
if (this.key != that.key) {
@@ -63,16 +58,8 @@ public class ObjectKey implements Comparable<ObjectKey> {
@Override
public int compareTo(ObjectKey that) {
int result;
if (this.adapter != that.adapter) {
result = this.adapter.getName().compareTo(that.adapter.getName());
if (result != 0) {
return result;
}
return System.identityHashCode(this.adapter) - System.identityHashCode(that.adapter);
}
result = this.tableName.compareTo(that.tableName);
if (result != 0) {
return result;
if (this.table != that.table) {
return System.identityHashCode(this.table) - System.identityHashCode(that.table);
}
result = Long.compareUnsigned(this.key, that.key);
if (result != 0) {
@@ -22,7 +22,38 @@ import org.apache.commons.lang3.ArrayUtils;
import db.*;
/**
* A builder for {@link Schema}
*
* <p>
* Provides a more fluent syntax for creating table schemas. For example:
*
* <pre>
* new Schema(1, StringField.class, "UUID",
* new Class[] { StringField.class, IntField.class }, new String[] { "Name", "Flags" },
* new int[] { 1 });
* </pre>
*
* <p>
* Can be expressed using the builder:
*
* <pre>
* new SchemaBuilder().keyField("UUID", StringField.class)
* .field("Name", StringField.class)
* .field("Flags", IntField.class, true)
* .build();
* </pre>
*/
public class SchemaBuilder {
public static int[] toIntArray(List<Integer> list) {
int[] arr = new int[list.size()];
for (int i = 0; i < arr.length; i++) {
arr[i] = list.get(i);
}
return arr;
}
public static int getColumnIndex(Schema schema, String name) {
return ArrayUtils.indexOf(schema.getFieldNames(), name);
}
@@ -30,10 +61,11 @@ public class SchemaBuilder {
private int version = 0;
private String keyFieldName = "Key";
private Class<? extends Field> keyFieldClass = LongField.class;
private List<String> fieldNames = new ArrayList<>();
private List<Class<? extends Field>> fieldClasses = new ArrayList<>();
private final List<String> fieldNames = new ArrayList<>();
private final List<Class<? extends Field>> fieldClasses = new ArrayList<>();
private final List<Integer> sparseColumns = new ArrayList<>();
public SchemaBuilder version(@SuppressWarnings("hiding") int version) {
public SchemaBuilder version(int version) {
this.version = version;
return this;
}
@@ -44,12 +76,20 @@ public class SchemaBuilder {
return this;
}
public SchemaBuilder field(String name, Class<? extends Field> cls) {
public SchemaBuilder field(String name, Class<? extends Field> cls, boolean sparse) {
int index = fieldCount();
this.fieldNames.add(name);
this.fieldClasses.add(cls);
if (sparse) {
this.sparseColumns.add(index);
}
return this;
}
public SchemaBuilder field(String name, Class<? extends Field> cls) {
return field(name, cls, false);
}
public int fieldCount() {
return fieldNames.size();
}
@@ -57,6 +97,6 @@ public class SchemaBuilder {
public Schema build() {
return new Schema(version, keyFieldClass, keyFieldName,
fieldClasses.toArray(new Class[fieldClasses.size()]),
fieldNames.toArray(new String[fieldNames.size()]));
fieldNames.toArray(new String[fieldNames.size()]), toIntArray(sparseColumns));
}
}
@@ -15,51 +15,132 @@
*/
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", true)) {
* // ... Do that thing
* }
* </pre>
*/
public interface UndoableTransaction extends AutoCloseable {
public static UndoableTransaction start(UndoableDomainObject domainObject, String description,
boolean commitByDefault) {
/**
* 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, commitByDefault);
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
* @param commitByDefault true to commit at the end of the block
* @return the transaction handle
*/
public static UndoableTransaction start(UndoableDomainObject domainObject, String description,
AbortedTransactionListener listener, boolean commitByDefault) {
AbortedTransactionListener listener) {
int tid = domainObject.startTransaction(description, listener);
return new DomainObjectUndoableTransaction(domainObject, tid, commitByDefault);
return new DomainObjectUndoableTransaction(domainObject, tid);
}
public static UndoableTransaction start(DataTypeManager dataTypeManager, String description,
boolean commitByDefault) {
/**
* Open a transaction on a data type manager
*
* @param dataTypeManager the data type manager
* @param description a description of the change
* @param commitByDefault true to commit at the end of the block
* @return the transaction handle
*/
public static UndoableTransaction start(DataTypeManager dataTypeManager, String description) {
int tid = dataTypeManager.startTransaction(description);
return new DataTypeManagerUndoableTransaction(dataTypeManager, tid, commitByDefault);
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 {
protected final int transactionID;
private boolean commit;
private boolean commit = true;
private boolean open = true;
private AbstractUndoableTransaction(int transactionID, boolean commitByDefault) {
this.transactionID = transactionID;
this.commit = commitByDefault;
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) {
@@ -85,12 +166,54 @@ public interface UndoableTransaction extends AutoCloseable {
}
}
class DomainObjectUndoableTransaction extends AbstractUndoableTransaction {
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,
boolean commitByDefault) {
super(tid, commitByDefault);
private DomainObjectUndoableTransaction(UndoableDomainObject domainObject, int tid) {
super(tid);
this.domainObject = domainObject;
}
@@ -103,12 +226,11 @@ public interface UndoableTransaction extends AutoCloseable {
}
}
class DataTypeManagerUndoableTransaction extends AbstractUndoableTransaction {
class DataTypeManagerUndoableTransaction extends AbstractIntUndoableTransaction {
private final DataTypeManager dataTypeManager;
private DataTypeManagerUndoableTransaction(DataTypeManager dataTypeManager, int tid,
boolean commitByDefault) {
super(tid, commitByDefault);
private DataTypeManagerUndoableTransaction(DataTypeManager dataTypeManager, int tid) {
super(tid);
this.dataTypeManager = dataTypeManager;
}
@@ -118,14 +240,19 @@ public interface UndoableTransaction extends AutoCloseable {
}
}
class ProgramUserDataUndoableTransaction extends AbstractUndoableTransaction {
class ProgramUserDataUndoableTransaction extends AbstractIntUndoableTransaction {
private final ProgramUserData userData;
private ProgramUserDataUndoableTransaction(ProgramUserData userData, int tid) {
super(tid, true);
super(tid);
this.userData = userData;
}
@Override
public void abortOnClose() {
throw new UnsupportedOperationException();
}
@Override
public void abort() {
throw new UnsupportedOperationException();
@@ -137,8 +264,48 @@ public interface UndoableTransaction extends AutoCloseable {
}
}
/**
* 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
@@ -17,8 +17,22 @@ package ghidra.util.database.annot;
import java.lang.annotation.*;
import ghidra.util.database.DBAnnotatedObject;
import ghidra.util.database.DBObjectColumn;
/**
* Mark a {@link DBObjectColumn} to receive a column handle
*
* @see DBAnnotatedObject
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DBAnnotatedColumn {
/**
* The name of the column
*
* <p>
* There should be a {@link DBAnnotatedField} annotation with the same column name
*/
String value();
}
@@ -17,19 +17,58 @@ package ghidra.util.database.annot;
import java.lang.annotation.*;
import db.Field;
import ghidra.util.database.DBAnnotatedObject;
import ghidra.util.database.DBCachedObjectStoreFactory.DBFieldCodec;
/**
* Mark a field to be stored in a table column
*
* @see DBAnnotatedObject
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBAnnotatedField {
/**
* The name of the column
*
* <p>
* There should be a {@link DBAnnotatedColumn} annotation with the same column name
*/
String column();
/**
* True to index the column
*/
boolean indexed() default false;
/**
* True to use sparse storage
*
* <p>
* If the {@link Field} used by the codec does not support null values, this can be set to true
* to allow null values.
*/
boolean sparse() default false;
/**
* Specify a custom codec
*
* <p>
* This is not required for types supported directly by a {@link Field}.
*
* @see DBFieldCodec
*/
@SuppressWarnings("rawtypes")
Class<? extends DBFieldCodec> codec() default DefaultCodec.class;
/**
* A placeholder class
*
* <p>
* A reference to this class type indicates that {@link DBAnnotatedField#codec()} was not set.
* The framework will instead check for a built-in codec.
*/
static abstract class DefaultCodec<OT extends DBAnnotatedObject, FT extends db.Field>
implements DBFieldCodec<Void, OT, FT> {
private DefaultCodec() {
@@ -1,25 +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.annot;
import java.lang.annotation.*;
// TODO: Process these and specify or build indices accordingly.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBAnnotatedIndex {
// None
}
@@ -17,8 +17,30 @@ package ghidra.util.database.annot;
import java.lang.annotation.*;
import ghidra.util.database.DBAnnotatedObject;
/**
* Required annotation for {@link DBAnnotatedObject}
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBAnnotatedObjectInfo {
/**
* The schema version
*
* <p>
* This should be incremented in many situtations, including but not limited to:
* <ul>
* <li>A field is added or removed</li>
* <li>A field's type changes</li>
* <li>A field's column name changes. See {@link DBAnnotatedField#column()}</li>
* <li>A field's codec changes. See {@link DBAnnotatedField#codec()}</li>
* <li>A field's sparse-storage flag changes. See {@link DBAnnotatedField#sparse()}</li>
* <li>A field's index flag changes. See {@link DBAnnotatedField#indexed()}</li>
* <li>The order of field declarations changes.</li>
* <li>The codec used by a field changes how it encodes values</li>
* <li>The fields of a superclass change in any of the above ways</li>
* </ul>
*/
int version();
}
@@ -15,6 +15,9 @@
*/
package ghidra.util.database.err;
/**
* Exception when a custom codec is required
*/
@SuppressWarnings("serial")
public class NoDefaultCodecException extends RuntimeException {
public NoDefaultCodecException() {
@@ -114,7 +114,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
start = space.getAddress(0x00400000);
size = 0x1000;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
block = program.getMemory()
.createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY,
false);
@@ -194,7 +194,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
@Test
public void testWriteStdout() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV RAX," + SYSCALLNO_WRITE,
"MOV RDI," + EmuUnixFileDescriptor.FD_STDOUT,
@@ -226,7 +226,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
@Test
public void testReadStdin() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV RAX," + SYSCALLNO_READ,
"MOV RDI," + EmuUnixFileDescriptor.FD_STDIN,
@@ -258,7 +258,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
@Test
public void testWritevStdout() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
Address data = space.getAddress(0x00400800);
ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
@@ -313,7 +313,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
public void testReadvStdin() throws Exception {
Address strHello;
Address strWorld;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
Address data = space.getAddress(0x00400800);
ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
@@ -369,7 +369,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
@Test
public void testOpenWriteClose() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV RAX," + SYSCALLNO_OPEN,
"LEA RDI,[0x00400880]",
@@ -407,7 +407,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
@Test
public void testOpenReadClose() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV RAX," + SYSCALLNO_OPEN,
"LEA RDI,[0x00400880]",
@@ -114,7 +114,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
start = space.getAddress(0x00400000);
size = 0x1000;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
block = program.getMemory()
.createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY,
false);
@@ -194,7 +194,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
@Test
public void testWriteStdout() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV EAX," + SYSCALLNO_WRITE,
"MOV EBX," + EmuUnixFileDescriptor.FD_STDOUT,
@@ -226,7 +226,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
@Test
public void testReadStdin() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV EAX," + SYSCALLNO_READ,
"MOV EBX," + EmuUnixFileDescriptor.FD_STDIN,
@@ -258,7 +258,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
@Test
public void testWritevStdout() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
Address data = space.getAddress(0x00400800);
ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
@@ -313,7 +313,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
public void testReadvStdin() throws Exception {
Address strHello;
Address strWorld;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
Address data = space.getAddress(0x00400800);
ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
@@ -369,7 +369,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
@Test
public void testOpenWriteClose() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV EAX," + SYSCALLNO_OPEN,
"LEA EBX,[0x00400880]",
@@ -407,7 +407,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
@Test
public void testOpenReadClose() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV EAX," + SYSCALLNO_OPEN,
"LEA EBX,[0x00400880]",
@@ -128,7 +128,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
start = space.getAddress(0x00400000);
size = 0x1000;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
block = program.getMemory()
.createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY,
false);
@@ -177,7 +177,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
@Test
public void testSyscallWithStdcallConvention() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV RAX,0",
"MOV RCX,0xbeef",
@@ -196,7 +196,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
@Test
public void testSyscallWithSyscallConvention() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
asm.assemble(start,
"MOV RAX,1",
"MOV RCX,0xdead",
@@ -671,7 +671,7 @@ public class DBCachedObjectStoreFactoryTest {
DBHandle handle = new DBHandle();
MyDomainObject myDO = new MyDomainObject(handle, "Testing", this);
DBCachedObjectStoreFactory factory = new DBCachedObjectStoreFactory(myDO);
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables")) {
factory.getOrCreateCachedStore(MyObject.TABLE_NAME, MyObject.class, MyObject::new,
false);
factory.getOrCreateCachedStore(MyExtObject.TABLE_NAME, MyExtObject.class,
@@ -715,7 +715,7 @@ public class DBCachedObjectStoreFactoryTest {
DBHandle handle = new DBHandle();
MyDomainObject myDO = new MyDomainObject(handle, "Testing", this);
DBCachedObjectStoreFactory factory = new DBCachedObjectStoreFactory(myDO);
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables")) {
factory.getOrCreateCachedStore("MyTable", MyFromAbstract.class, MyFromAbstract::new,
false);
}
@@ -734,7 +734,7 @@ public class DBCachedObjectStoreFactoryTest {
MyDomainObject myDO = new MyDomainObject(handle, "Testing", this);
DBCachedObjectStoreFactory factory = new DBCachedObjectStoreFactory(myDO);
DBCachedObjectStore<MyKitchenSink> store;
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables")) {
store = factory.getOrCreateCachedStore(MyKitchenSink.TABLE_NAME, MyKitchenSink.class,
MyKitchenSink::new, false);
@@ -779,7 +779,7 @@ public class DBCachedObjectStoreFactoryTest {
DBHandle handle = new DBHandle();
MyDomainObject myDO = new MyDomainObject(handle, "Testing", this);
DBCachedObjectStoreFactory factory = new DBCachedObjectStoreFactory(myDO);
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables")) {
factory.getOrCreateCachedStore(MyUsesMyEnumTooBig.TABLE_NAME, MyUsesMyEnumTooBig.class,
MyUsesMyEnumTooBig::new, false);
fail();
@@ -72,7 +72,7 @@ public class DBCachedObjectStoreTest {
super(new DBHandle(), DBOpenMode.CREATE, new ConsoleTaskMonitor(), name, timeInterval,
bufSize, consumer);
this.storeFactory = new DBCachedObjectStoreFactory(this);
try (DBTransaction tid = DBTransaction.start(dbh, true)) {
try (UndoableTransaction tid = UndoableTransaction.start(dbh, this)) {
this.store = storeFactory.getOrCreateCachedStore(OBJECTS_TABLE_NAME, MyObject.class,
MyObject::new, false);
}
@@ -83,7 +83,7 @@ public class DBCachedObjectStoreTest {
throws VersionException, IOException {
super(handle, openMode, monitor, null, timeInterval, bufSize, consumer);
this.storeFactory = new DBCachedObjectStoreFactory(this);
try (DBTransaction tid = DBTransaction.start(handle, true)) {
try (UndoableTransaction tid = UndoableTransaction.start(handle, this)) {
this.store = storeFactory.getOrCreateCachedStore(OBJECTS_TABLE_NAME, MyObject.class,
MyObject::new, false);
}
@@ -119,14 +119,14 @@ public class DBCachedObjectStoreTest {
public void setF1(long f1) {
if (this.f1 != f1) {
this.f1 = f1;
write(COL1);
doWrite(COL1);
}
}
public void setF2(int f2) {
if (this.f2 != f2) {
this.f2 = f2;
write(COL2);
doWrite(COL2);
}
}
}
@@ -145,7 +145,7 @@ public class DBCachedObjectStoreTest {
DBCachedObjectStoreEntrySet<MyObject> rEntrySet;
protected UndoableTransaction trans() {
return UndoableTransaction.start(myDomainObject, "Test", true);
return UndoableTransaction.start(myDomainObject, "Test");
}
protected void populateStore(long... keys) {
@@ -270,7 +270,7 @@ public class DBCachedObjectStoreTest {
MyObject obj = store.create();
obj.setF1(0x801);
obj.setF2(0x802);
obj.updated();
obj.doUpdated();
Table table = handle.getTable(OBJECTS_TABLE_NAME);
DBRecord record = table.getRecord(obj.getKey());
assertEquals(0x801, record.getLongValue(0));
@@ -2622,15 +2622,15 @@ public class DBCachedObjectStoreTest {
try (UndoableTransaction tid = trans()) {
MyObject obj0 = store.create(0);
obj0.setF2(10);
obj0.updated();
obj0.doUpdated();
MyObject obj1 = store.create(1);
obj1.setF2(5);
obj1.updated();
obj1.doUpdated();
MyObject obj2 = store.create(2);
obj2.setF2(5);
obj2.updated();
obj2.doUpdated();
}
DBCachedObjectIndex<Integer, MyObject> index = store.getIndex(int.class, COL2_NAME);
return index;
@@ -2668,7 +2668,7 @@ public class DBCachedObjectStoreTest {
@SuppressWarnings("unlikely-arg-type")
public void testFoundContains() throws IOException {
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
Collection<MyObject> found5 = index.get(5);
assertFalse(found5.contains(null));
assertFalse(found5.contains("Wrong type"));
@@ -2679,7 +2679,7 @@ public class DBCachedObjectStoreTest {
@Test
public void testFoundIterator() throws IOException {
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
Collection<MyObject> found5 = index.get(5);
assertEquals(Set.of(store.getObjectAt(1), store.getObjectAt(2)),
new HashSet<>(IteratorUtils.toList(found5.iterator())));
@@ -2688,7 +2688,7 @@ public class DBCachedObjectStoreTest {
@Test
public void testFoundToArray() throws IOException {
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
Collection<MyObject> found5 = index.get(5);
assertEquals(Set.of(store.getObjectAt(1), store.getObjectAt(2)),
new HashSet<>(Arrays.asList(found5.toArray())));
@@ -2697,7 +2697,7 @@ public class DBCachedObjectStoreTest {
@Test
public void testFoundToTypedArray() throws IOException {
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
Collection<MyObject> found5 = index.get(5);
assertEquals(Set.of(store.getObjectAt(1), store.getObjectAt(2)),
new HashSet<>(Arrays.asList(found5.toArray(new MyObject[0]))));
@@ -2712,7 +2712,7 @@ public class DBCachedObjectStoreTest {
@Test
public void testFoundContainsAll() throws VersionException, IOException {
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
Collection<MyObject> found5 = index.get(5);
assertTrue(found5.containsAll(List.of()));
assertFalse(found5.containsAll(List.of(store.getObjectAt(1), "Wrong Type")));
@@ -2722,7 +2722,7 @@ public class DBCachedObjectStoreTest {
final MyObject altObj1;
MyDomainObject altDomainObject = new MyDomainObject("Alternative Dummy", 500, 1000, this);
try (UndoableTransaction tid =
UndoableTransaction.start(altDomainObject, "Create Obj2", true)) {
UndoableTransaction.start(altDomainObject, "Create Obj2")) {
altObj1 = altDomainObject.store.create(1);
}
@@ -636,7 +636,7 @@ public class RStarTreeMapTest {
super(new DBHandle(), DBOpenMode.CREATE, new ConsoleTaskMonitor(), "Testing", 500, 1000,
consumer);
storeFactory = new DBCachedObjectStoreFactory(this);
try (UndoableTransaction tid = UndoableTransaction.start(this, "CreateMaps", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(this, "CreateMaps")) {
tree = new IntRStarTree(storeFactory, DBIntRectStringDataRecord.TABLE_NAME,
true, MAX_CHILDREN);
map = tree.asSpatialMap();
@@ -858,7 +858,7 @@ public class RStarTreeMapTest {
List<Pair<IntRect, String>> entries = generateRandom(rect(0, 100, 0, 100), 10, 10, 125);
obj.tree.checkIntegrity();
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRandom", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRandom")) {
for (Entry<IntRect, String> ent : entries) {
obj.map.put(ent.getKey(), ent.getValue());
obj.tree.checkIntegrity();
@@ -874,7 +874,7 @@ public class RStarTreeMapTest {
// NOTE: This "thrashing" test covers nearly all the R*-Tree insertion logic.
List<Pair<IntRect, String>> entries = generateRandom(rect(0, 100, 0, 100), 10, 10, 1000);
Consumer<List<Pair<IntRect, String>>> inserter = list -> {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRandom", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRandom")) {
int i = 0;
for (Entry<IntRect, String> ent : list) {
obj.map.put(ent.getKey(), ent.getValue());
@@ -903,7 +903,7 @@ public class RStarTreeMapTest {
@Test
public void testIntegrityWith2000VerticallyStackedRects() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddVertical", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddVertical")) {
for (int i = 0; i < 2000; i++) {
obj.map.put(rect(0, 10, i, i + 1), "Ent" + i);
// Note, underlying tree is not synchronized, but map is
@@ -916,7 +916,7 @@ public class RStarTreeMapTest {
@Test
public void testSaveAndLoad() throws IOException, CancelledException, VersionException {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRecord", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRecord")) {
obj.map.put(rect(1, 5, 6, 10), "Some value");
}
@@ -958,7 +958,7 @@ public class RStarTreeMapTest {
// NOTE: This test is made also to cover the visitation logic.
assertTrue(obj.map.isEmpty());
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
for (Entry<IntRect, String> ent : generatePoints(rect(1, 12, 1, 12))) {
obj.map.put(ent.getKey(), ent.getValue());
}
@@ -977,7 +977,7 @@ public class RStarTreeMapTest {
@Test
public void testFirst() {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
for (Entry<IntRect, String> ent : generatePoints(rect(1, 12, 1, 12))) {
obj.map.put(ent.getKey(), ent.getValue());
}
@@ -1015,7 +1015,7 @@ public class RStarTreeMapTest {
@Test
public void testIterator() {
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
for (Entry<IntRect, String> ent : points) {
obj.map.put(ent.getKey(), ent.getValue());
}
@@ -1045,7 +1045,7 @@ public class RStarTreeMapTest {
@Test
public void testOrderedIterator() {
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
for (Entry<IntRect, String> ent : points) {
obj.map.put(ent.getKey(), ent.getValue());
}
@@ -1073,13 +1073,13 @@ public class RStarTreeMapTest {
public void testRemove() {
// TODO: Add a "minimal query including" abstract method to reduce search for removed item
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
for (Entry<IntRect, String> ent : points) {
obj.map.put(ent.getKey(), ent.getValue());
}
}
try (UndoableTransaction tid = UndoableTransaction.start(obj, "RemovePoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "RemovePoints")) {
assertFalse(obj.map.reduce(IntRectQuery.enclosed(rect(6, 6, 6, 6))).isEmpty());
obj.map.remove(rect(6, 6, 6, 6), "NotHere");
assertFalse(obj.map.reduce(IntRectQuery.enclosed(rect(6, 6, 6, 6))).isEmpty());
@@ -1099,13 +1099,13 @@ public class RStarTreeMapTest {
@Test
public void testClear() {
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
for (Entry<IntRect, String> ent : points) {
obj.map.put(ent.getKey(), ent.getValue());
}
}
try (UndoableTransaction tid = UndoableTransaction.start(obj, "RemovePoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "RemovePoints")) {
obj.map.reduce(IntRectQuery.enclosed(rect(6, 6, 6, 6))).clear();
obj.tree.checkIntegrity();
assertEquals(143, obj.map.size());
@@ -1135,7 +1135,7 @@ public class RStarTreeMapTest {
@Test
public void testValuesToArray() {
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
for (Entry<IntRect, String> ent : points) {
obj.map.put(ent.getKey(), ent.getValue());
}