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 class

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Add @DBAnnotatedObjectInfo(version=N) directly onto the concrete subclass you open as a store.
  2. Confirm the version number matches the existing database schema (or intentionally bump it with migration logic).
  3. 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

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


AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14). Data as JSON: /api/errors/28a35514877eb7fe. Report an issue: GitHub.