NationalSecurityAgency/ghidra · error · IllegalArgumentException
DBAnnotatedObject {objectType} must have @DBAnnotatedObjectI
Error message
DBAnnotatedObject {objectType} must have @DBAnnotatedObjectInfo annotation What it means
buildInfo requires every concrete DBAnnotatedObject subclass to carry @DBAnnotatedObjectInfo(version=N) so the factory knows the schema version and column layout. The annotation is read via objectType.getAnnotation(...) (not inherited), so a missing annotation on the concrete class leaves no safe default and the table cannot be opened or migrated - IllegalArgumentException is thrown.
Source
Thrown at Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java:1341
/**
* Get the codecs for the given class
*
* @param <OT> the type of objects to store in the table
* @param objectType the class describing {@link OT}
* @return the codecs, in column order
*/
static <OT extends DBAnnotatedObject> List<DBFieldCodec<?, OT, ?>> getCodecs(
Class<OT> objectType) {
return getInfo(objectType).codecs;
}
/**
* The non-cached implementation of {@link #getInfo(Class)}
*/
private static <OT extends DBAnnotatedObject> TableInfo<OT> buildInfo(Class<OT> objectType) {
DBAnnotatedObjectInfo info = objectType.getAnnotation(DBAnnotatedObjectInfo.class);
if (info == null) {
throw new IllegalArgumentException(
DBAnnotatedObject.class.getSimpleName() + " " + objectType.getName() +
" must have @" + DBAnnotatedObjectInfo.class.getSimpleName() + " annotation");
}
Map<String, Field> fields = new LinkedHashMap<>();
List<Field> indexFields = new ArrayList<>();
List<Field> sparseFields = new ArrayList<>();
collectFields(objectType, fields, indexFields, sparseFields);
TableInfo<OT> tableInfo =
new TableInfo<>(objectType, info.version(), fields, indexFields, sparseFields);
tableInfo.writeColumnNumbers(objectType);
return tableInfo;
}
/**
* Collect the fields of the given class, recursively, starting with its super classView on GitHub (pinned to d5f144c24d)
Solutions
- Add @DBAnnotatedObjectInfo(version=N) directly onto the concrete subclass you open as a store.
- Confirm the version number matches the existing database schema (or intentionally bump it with migration logic).
- If you share config across subclasses, still annotate each concrete class since the lookup is non-inherited.
Example fix
// before
class MyObj extends DBAnnotatedObject {
MyObj(DBCachedObjectStore<?> store, DBRecord record) { super(store, record); }
}
// after
@DBAnnotatedObjectInfo(version = 1)
class MyObj extends DBAnnotatedObject {
MyObj(DBCachedObjectStore<?> store, DBRecord record) { super(store, record); }
} Defensive patterns
Strategy: validation
Validate before calling
if (objectType.getAnnotation(DBAnnotatedObjectInfo.class) == None) {
throw new IllegalStateException(objectType.getName() + " missing @DBAnnotatedObjectInfo");
} Prevention
- Always annotate concrete DBAnnotatedObject subclasses with @DBAnnotatedObjectInfo.
- Remember the lookup is non-inherited - an annotation on an abstract base is not enough.
When it happens
Trigger: Opening/creating a store for a class extending DBAnnotatedObject whose concrete class lacks @DBAnnotatedObjectInfo. Putting the annotation only on an abstract superclass does NOT satisfy getAnnotation, so subclasses still throw.
Common situations: Adding a new DBAnnotatedObject subclass and forgetting the class annotation; annotating an abstract base only; introducing a subclass layer during refactor without re-annotating.
Related errors
- Cannot find column '{}' for @DBAnnotatedColumn on {}
- @DBAnnotatedColumn fields must be static. Got {}
- The parameter '%s' of method '%s' refers to a non-existent s
- {}
- Column {} is not indexed
AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14).
Data as JSON: /api/errors/28a35514877eb7fe.
Report an issue: GitHub.