mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-71: Prepping for source release.
This commit is contained in:
parent
ddbfbfe198
commit
8201baef2b
2705 changed files with 305722 additions and 53 deletions
|
@ -0,0 +1,43 @@
|
|||
/* ###
|
||||
* 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.annotproc;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
public class AbstractDBAnnotationValidator {
|
||||
protected final ValidationContext ctx;
|
||||
|
||||
public AbstractDBAnnotationValidator(ValidationContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
protected void checkEnclosingType(Class<? extends Annotation> annotType, VariableElement field,
|
||||
TypeElement type) {
|
||||
if (type.getKind() != ElementKind.CLASS) {
|
||||
ctx.messager.printMessage(Kind.ERROR, String.format(
|
||||
"@%s can only be applied to fields in a class", annotType.getSimpleName()), field);
|
||||
}
|
||||
else if (!ctx.isSubclass(type, ctx.DB_ANNOTATED_OBJECT_ELEM)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s can only be applied within a subclass of %s",
|
||||
annotType.getSimpleName(), ctx.DB_ANNOTATED_OBJECT_ELEM),
|
||||
field);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/* ###
|
||||
* 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.annotproc;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
public enum AccessSpec {
|
||||
PRIVATE(0), PACKAGE(1), PROTECTED(2), PUBLIC(3);
|
||||
|
||||
private final int level;
|
||||
|
||||
private AccessSpec(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the second permits the same or more access than the first
|
||||
*
|
||||
* @param first the first
|
||||
* @param second the second
|
||||
* @return true if the second is the same or more permissive
|
||||
*/
|
||||
public static boolean isSameOrMorePermissive(AccessSpec first, AccessSpec second) {
|
||||
// TODO: I'm not sure protected actually includes package...
|
||||
// It might be more diamond shaped
|
||||
return first.level <= second.level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the access specifier derived from the given modifiers
|
||||
*
|
||||
* @param modifiers the element's modifiers
|
||||
* @return the elements access specification
|
||||
*/
|
||||
public static AccessSpec get(Set<Modifier> modifiers) {
|
||||
if (modifiers.contains(Modifier.PRIVATE)) {
|
||||
return PRIVATE;
|
||||
}
|
||||
if (modifiers.contains(Modifier.PROTECTED)) {
|
||||
return PROTECTED;
|
||||
}
|
||||
if (modifiers.contains(Modifier.PUBLIC)) {
|
||||
return PUBLIC;
|
||||
}
|
||||
return PACKAGE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/* ###
|
||||
* 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.annotproc;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
import ghidra.util.database.annot.DBAnnotatedColumn;
|
||||
|
||||
public class DBAnnotatedColumnValidator extends AbstractDBAnnotationValidator {
|
||||
final VariableElement column;
|
||||
|
||||
public DBAnnotatedColumnValidator(ValidationContext ctx, VariableElement column) {
|
||||
super(ctx);
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
if (!ctx.hasType(column, ctx.DB_OBJECT_COLUMN_ELEM)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s can only be applied to fields of type %s",
|
||||
DBAnnotatedColumn.class.getSimpleName(), ctx.DB_OBJECT_COLUMN_ELEM),
|
||||
column);
|
||||
}
|
||||
Set<Modifier> mods = column.getModifiers();
|
||||
if (mods.contains(Modifier.FINAL)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to a final field",
|
||||
DBAnnotatedColumn.class.getSimpleName()),
|
||||
column);
|
||||
}
|
||||
if (!mods.contains(Modifier.STATIC)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s must be applied to a static field",
|
||||
DBAnnotatedColumn.class.getSimpleName()),
|
||||
column);
|
||||
}
|
||||
TypeElement type = (TypeElement) column.getEnclosingElement();
|
||||
checkEnclosingType(DBAnnotatedColumn.class, column, type);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/* ###
|
||||
* 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.annotproc;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
import db.DBHandle;
|
||||
import ghidra.util.database.DBCachedDomainObjectAdapter;
|
||||
import ghidra.util.database.DBOpenMode;
|
||||
import ghidra.util.database.annot.DBAnnotatedField;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DBAnnotatedFieldValidator extends AbstractDBAnnotationValidator {
|
||||
final VariableElement field;
|
||||
final Map<TypeMirror, TypeElement> javaToDBTypeMap;
|
||||
final static String FACTORY_NAME = "ghidra.util.database.DBCachedObjectStoreFactory";
|
||||
final static String BOOLEAN_CODEC_NAME = FACTORY_NAME + ".BooleanDBFieldCodec";
|
||||
final static String BYTE_CODEC_NAME = FACTORY_NAME + ".ByteDBFieldCodec";
|
||||
final static String SHORT_CODEC_NAME = FACTORY_NAME + ".ShortDBFieldCodec";
|
||||
final static String INT_CODEC_NAME = FACTORY_NAME + ".IntDBFieldCodec";
|
||||
final static String LONG_CODEC_NAME = FACTORY_NAME + ".LongDBFieldCodec";
|
||||
final static String STRING_CODEC_NAME = FACTORY_NAME + ".StringDBFieldCodec";
|
||||
final static String BYTE_ARRAY_CODEC_NAME = FACTORY_NAME + ".ByteArrayDBFieldCodec";
|
||||
final static String LONG_ARRAY_CODEC_NAME = FACTORY_NAME + ".LongArrayDBFieldCodec";
|
||||
final static String ENUM_CODEC_NAME = FACTORY_NAME + ".EnumDBByteFieldCodec";
|
||||
|
||||
final TypeElement ENUM_CODEC_ELEM;
|
||||
|
||||
public DBAnnotatedFieldValidator(ValidationContext ctx, VariableElement field) {
|
||||
super(ctx);
|
||||
this.field = field;
|
||||
|
||||
Map<TypeMirror, TypeElement> typeMap = new LinkedHashMap<>();
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.BOOLEAN, BOOLEAN_CODEC_NAME);
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.BYTE, BYTE_CODEC_NAME);
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.SHORT, SHORT_CODEC_NAME);
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.INT, INT_CODEC_NAME);
|
||||
putPrimitiveTypeCodec(typeMap, TypeKind.LONG, LONG_CODEC_NAME);
|
||||
putTypeCodec(typeMap, String.class, STRING_CODEC_NAME);
|
||||
putPrimitiveArrayTypeCodec(typeMap, TypeKind.BYTE, BYTE_ARRAY_CODEC_NAME);
|
||||
putPrimitiveArrayTypeCodec(typeMap, TypeKind.LONG, LONG_ARRAY_CODEC_NAME);
|
||||
// NOTE: Enum requires subtype check
|
||||
|
||||
javaToDBTypeMap = Map.copyOf(typeMap);
|
||||
|
||||
ENUM_CODEC_ELEM = ctx.elementUtils.getTypeElement(ENUM_CODEC_NAME);
|
||||
}
|
||||
|
||||
protected void putPrimitiveTypeCodec(Map<TypeMirror, TypeElement> map, TypeKind kind,
|
||||
String codecName) {
|
||||
PrimitiveType primitive = ctx.typeUtils.getPrimitiveType(kind);
|
||||
TypeMirror boxed = ctx.typeUtils.boxedClass(primitive).asType();
|
||||
TypeElement codec = ctx.elementUtils.getTypeElement(codecName);
|
||||
map.put(primitive, codec);
|
||||
map.put(boxed, codec);
|
||||
}
|
||||
|
||||
protected void putTypeCodec(Map<TypeMirror, TypeElement> map, Class<?> cls, String codecName) {
|
||||
TypeMirror type = ctx.elementUtils.getTypeElement(cls.getCanonicalName()).asType();
|
||||
TypeElement codec = ctx.elementUtils.getTypeElement(codecName);
|
||||
map.put(type, codec);
|
||||
}
|
||||
|
||||
protected void putPrimitiveArrayTypeCodec(Map<TypeMirror, TypeElement> map, TypeKind kind,
|
||||
String codecName) {
|
||||
PrimitiveType primitive = ctx.typeUtils.getPrimitiveType(kind);
|
||||
ArrayType array = ctx.typeUtils.getArrayType(primitive);
|
||||
TypeElement codec = ctx.elementUtils.getTypeElement(codecName);
|
||||
map.put(array, codec);
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
Set<Modifier> mods = field.getModifiers();
|
||||
if (mods.contains(Modifier.FINAL)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to a final field",
|
||||
DBAnnotatedField.class.getSimpleName()),
|
||||
field);
|
||||
}
|
||||
if (mods.contains(Modifier.STATIC)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to a static field",
|
||||
DBAnnotatedField.class.getSimpleName()),
|
||||
field);
|
||||
}
|
||||
TypeElement type = (TypeElement) field.getEnclosingElement();
|
||||
checkEnclosingType(DBAnnotatedField.class, field, type);
|
||||
checkCodecTypes(type);
|
||||
}
|
||||
|
||||
protected TypeElement getDefaultCodecType(TypeMirror javaType) {
|
||||
if (ctx.isEnumType(javaType)) {
|
||||
return ENUM_CODEC_ELEM;
|
||||
}
|
||||
return javaToDBTypeMap.get(javaType);
|
||||
}
|
||||
|
||||
protected TypeElement getCodecTypeElement() {
|
||||
DBAnnotatedField annotation = field.getAnnotation(DBAnnotatedField.class);
|
||||
TypeElement codecElem;
|
||||
try {
|
||||
codecElem = ctx.elementUtils.getTypeElement(annotation.codec().getCanonicalName());
|
||||
}
|
||||
catch (MirroredTypeException e) {
|
||||
codecElem = (TypeElement) ((DeclaredType) e.getTypeMirror()).asElement();
|
||||
}
|
||||
if (codecElem == ctx.DEFAULT_CODEC_ELEM) {
|
||||
return getDefaultCodecType(field.asType());
|
||||
}
|
||||
return codecElem;
|
||||
}
|
||||
|
||||
class A extends DBCachedDomainObjectAdapter {
|
||||
|
||||
protected A(DBHandle dbh, DBOpenMode openMode, TaskMonitor monitor, String name,
|
||||
int timeInterval, int bufSize, Object consumer) {
|
||||
super(dbh, openMode, monitor, name, timeInterval, bufSize, consumer);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChangeable() {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkCodecTypes(TypeElement objectType) {
|
||||
|
||||
//experiment(new Blargh(null, null));
|
||||
|
||||
TypeElement codecType = getCodecTypeElement();
|
||||
if (codecType == null) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Could not select default codec for %s. @%s.codec must be specified.",
|
||||
field.asType(), DBAnnotatedField.class.getSimpleName()),
|
||||
field);
|
||||
return;
|
||||
}
|
||||
|
||||
// REQUIREMENTS:
|
||||
// 1) ValueType matches the field's type exactly
|
||||
// Cannot be super or extends because it's read/write
|
||||
// 2) ObjectType is super of the containing object
|
||||
// Need to ensure extra interfaces (intersection) are considered
|
||||
// 3) FieldType is non-abstract
|
||||
// 4) The codec has an appropriate constructor
|
||||
|
||||
for (Element enc : codecType.getEnclosedElements()) {
|
||||
if (enc.getKind() == ElementKind.CONSTRUCTOR) {
|
||||
ExecutableElement exe = (ExecutableElement) enc;
|
||||
ExecutableType exeType = (ExecutableType) exe.asType();
|
||||
//throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, TypeMirror> args = ctx.getArguments(codecType, ctx.DB_FIELD_CODEC_ELEM);
|
||||
|
||||
// 1)
|
||||
TypeMirror argVT = args.get("VT");
|
||||
if (!ctx.hasType(field, argVT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Codec %s can only be used with fields of type %s", codecType, argVT),
|
||||
field);
|
||||
}
|
||||
|
||||
// 2) (INCOMPLETE)
|
||||
TypeMirror argOT = args.get("OT");
|
||||
if (!ctx.isCapturable(objectType.asType(), argOT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Codec %s requires the containing object to conform to %s", codecType,
|
||||
ctx.format(argOT)),
|
||||
field);
|
||||
}
|
||||
|
||||
// 3)
|
||||
TypeMirror argFT = args.get("FT");
|
||||
if (argFT.getKind() != TypeKind.DECLARED) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Codec %s must have a non-abstract class for its field type, not %s",
|
||||
codecType, argFT),
|
||||
codecType);
|
||||
}
|
||||
else if (((DeclaredType) argFT).asElement().getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Codec %s must have a non-abstract class for its field type, not %s",
|
||||
codecType, argFT),
|
||||
codecType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/* ###
|
||||
* 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.annotproc;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
|
||||
import ghidra.util.database.DBAnnotatedObject;
|
||||
import ghidra.util.database.annot.*;
|
||||
|
||||
/**
|
||||
* A compile-time annotation processor for {@link DBAnnotatedObject}-related annotations.
|
||||
*
|
||||
* Currently just performs compile-time checks. It does not generate any code, but perhaps one day,
|
||||
* it will.
|
||||
*/
|
||||
//@AutoService(Processor.class) // TODO: Evaluate Google's auto-service as a dependency
|
||||
public class DBAnnotatedObjectProcessor extends AbstractProcessor {
|
||||
static final Set<Class<? extends Annotation>> SUPPORTED_ANNOTATIONS =
|
||||
Set.of(DBAnnotatedColumn.class, DBAnnotatedField.class, DBAnnotatedObjectInfo.class);
|
||||
|
||||
private ValidationContext ctx;
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment env) {
|
||||
//System.err.println("HERE4");
|
||||
super.init(env);
|
||||
ctx = new ValidationContext(env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
Map<TypeElement, DBAnnotatedObjectValidator> types = new LinkedHashMap<>();
|
||||
for (Element element : roundEnv.getElementsAnnotatedWith(DBAnnotatedObjectInfo.class)) {
|
||||
TypeElement type = (TypeElement) element; // Required by annotation Target
|
||||
types.put(type, new DBAnnotatedObjectValidator(ctx, type));
|
||||
}
|
||||
for (Element field : roundEnv.getElementsAnnotatedWith(DBAnnotatedField.class)) {
|
||||
VariableElement varField = (VariableElement) field; // Required by annotation Target
|
||||
// Fields can only be members of types, right?
|
||||
TypeElement type = (TypeElement) field.getEnclosingElement();
|
||||
DBAnnotatedObjectValidator validator =
|
||||
types.computeIfAbsent(type, t -> new DBAnnotatedObjectValidator(ctx, type));
|
||||
validator.addAnnotatedField(varField);
|
||||
}
|
||||
for (Element column : roundEnv.getElementsAnnotatedWith(DBAnnotatedColumn.class)) {
|
||||
VariableElement varColumn = (VariableElement) column; // Required by annotation Target
|
||||
// Fields can only be members of types, right?
|
||||
TypeElement type = (TypeElement) column.getEnclosingElement();
|
||||
DBAnnotatedObjectValidator validator =
|
||||
types.computeIfAbsent(type, t -> new DBAnnotatedObjectValidator(ctx, type));
|
||||
validator.addAnnotatedColumn(varColumn);
|
||||
}
|
||||
|
||||
for (DBAnnotatedObjectValidator ov : types.values()) {
|
||||
ov.validate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends Completion> getCompletions(Element element,
|
||||
AnnotationMirror annotation, ExecutableElement member, String userText) {
|
||||
// TODO Auto-generated method stub
|
||||
return super.getCompletions(element, annotation, member, userText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latestSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedAnnotationTypes() {
|
||||
return SUPPORTED_ANNOTATIONS.stream().map(Class::getCanonicalName).collect(
|
||||
Collectors.toSet());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/* ###
|
||||
* 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.annotproc;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
|
||||
import ghidra.util.database.annot.*;
|
||||
|
||||
public class DBAnnotatedObjectValidator {
|
||||
private final ValidationContext ctx;
|
||||
private final TypeElement type;
|
||||
private final Map<String, DBAnnotatedFieldValidator> fieldsByName = new LinkedHashMap<>();
|
||||
private final Map<String, DBAnnotatedColumnValidator> columnsByName = new LinkedHashMap<>();
|
||||
|
||||
public DBAnnotatedObjectValidator(ValidationContext ctx, TypeElement type) {
|
||||
this.ctx = ctx;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void addAnnotatedField(VariableElement field) {
|
||||
DBAnnotatedField annotation = field.getAnnotation(DBAnnotatedField.class);
|
||||
assert annotation != null;
|
||||
fieldsByName.put(annotation.column(), new DBAnnotatedFieldValidator(ctx, field));
|
||||
}
|
||||
|
||||
public void addAnnotatedColumn(VariableElement column) {
|
||||
DBAnnotatedColumn annotation = column.getAnnotation(DBAnnotatedColumn.class);
|
||||
assert annotation != null;
|
||||
columnsByName.put(annotation.value(), new DBAnnotatedColumnValidator(ctx, column));
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
DBAnnotatedObjectInfo annotation = type.getAnnotation(DBAnnotatedObjectInfo.class);
|
||||
if (annotation != null && type.getKind() != ElementKind.CLASS) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to an interface",
|
||||
DBAnnotatedObjectInfo.class.getSimpleName()),
|
||||
type);
|
||||
}
|
||||
else if (annotation != null && type.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s cannot be applied to an abstract class",
|
||||
DBAnnotatedObjectInfo.class.getSimpleName()),
|
||||
type);
|
||||
}
|
||||
if (annotation != null && !ctx.isSubclass(type, ctx.DB_ANNOTATED_OBJECT_ELEM)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s can only be applied to subclasses of %s", "DBAnnotatedObject",
|
||||
DBAnnotatedObjectInfo.class.getSimpleName()));
|
||||
}
|
||||
if (annotation == null && !type.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("Non-abstract subclasses of %s must have @%s annotation",
|
||||
"DBAnnotatedObject", DBAnnotatedObjectInfo.class.getSimpleName()),
|
||||
type);
|
||||
}
|
||||
if (annotation != null && annotation.version() < 0) {
|
||||
ctx.messager.printMessage(Kind.ERROR, String.format("@%s.version cannot be negative",
|
||||
DBAnnotatedObjectInfo.class.getSimpleName()), type);
|
||||
}
|
||||
|
||||
validateFields();
|
||||
validateColumns();
|
||||
|
||||
checkMissing();
|
||||
}
|
||||
|
||||
protected void validateFields() {
|
||||
for (DBAnnotatedFieldValidator fv : fieldsByName.values()) {
|
||||
fv.validate();
|
||||
}
|
||||
}
|
||||
|
||||
protected void validateColumns() {
|
||||
for (DBAnnotatedColumnValidator cv : columnsByName.values()) {
|
||||
cv.validate();
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkMissing() {
|
||||
Set<String> names = new LinkedHashSet<>();
|
||||
names.addAll(fieldsByName.keySet());
|
||||
names.addAll(columnsByName.keySet());
|
||||
for (String n : names) {
|
||||
DBAnnotatedFieldValidator fv = fieldsByName.get(n);
|
||||
DBAnnotatedColumnValidator cv = columnsByName.get(n);
|
||||
if (fv == null && cv != null && !type.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.ERROR,
|
||||
String.format("@%s is missing corresponding @%s of the same column name: %s",
|
||||
DBAnnotatedColumn.class.getSimpleName(),
|
||||
DBAnnotatedField.class.getSimpleName(), n),
|
||||
cv.column);
|
||||
}
|
||||
if (fv != null && cv == null && !type.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
ctx.messager.printMessage(Kind.WARNING,
|
||||
String.format("@%s is missing corresponding @%s of the same column name: %s",
|
||||
DBAnnotatedField.class.getSimpleName(),
|
||||
DBAnnotatedColumn.class.getSimpleName(), n),
|
||||
fv.field);
|
||||
}
|
||||
if (fv != null && cv != null) {
|
||||
checkAccess(fv.field, cv.column, n);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void checkAccess(VariableElement field, VariableElement column, String name) {
|
||||
AccessSpec fieldSpec = AccessSpec.get(field.getModifiers());
|
||||
AccessSpec columnSpec = AccessSpec.get(column.getModifiers());
|
||||
if (!AccessSpec.isSameOrMorePermissive(fieldSpec, columnSpec)) {
|
||||
ctx.messager.printMessage(Kind.WARNING,
|
||||
String.format(
|
||||
"field with @%s should have same or greater access than field with" +
|
||||
" corresponding @%s for column name: %s",
|
||||
DBAnnotatedColumn.class.getSimpleName(), DBAnnotatedField.class.getSimpleName(),
|
||||
name),
|
||||
column);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,326 @@
|
|||
/* ###
|
||||
* 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.annotproc;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
|
||||
import ghidra.util.database.annot.DBAnnotatedField;
|
||||
|
||||
public class ValidationContext {
|
||||
final Types typeUtils;
|
||||
final Elements elementUtils;
|
||||
final Messager messager;
|
||||
|
||||
final TypeElement LIST_ELEM;
|
||||
final TypeElement DB_ANNOTATED_OBJECT_ELEM;
|
||||
final TypeElement DB_OBJECT_COLUMN_ELEM;
|
||||
final TypeElement DB_FIELD_CODEC_ELEM;
|
||||
final TypeElement DEFAULT_CODEC_ELEM;
|
||||
final TypeElement ENUM_ELEM;
|
||||
|
||||
public ValidationContext(ProcessingEnvironment env) {
|
||||
typeUtils = env.getTypeUtils();
|
||||
elementUtils = env.getElementUtils();
|
||||
messager = env.getMessager();
|
||||
|
||||
LIST_ELEM = elementUtils.getTypeElement(List.class.getCanonicalName());
|
||||
DB_ANNOTATED_OBJECT_ELEM =
|
||||
elementUtils.getTypeElement("ghidra.util.database.DBAnnotatedObject");
|
||||
DB_OBJECT_COLUMN_ELEM = elementUtils.getTypeElement("ghidra.util.database.DBObjectColumn");
|
||||
DB_FIELD_CODEC_ELEM = elementUtils.getTypeElement(
|
||||
"ghidra.util.database.DBCachedObjectStoreFactory.DBFieldCodec");
|
||||
DEFAULT_CODEC_ELEM = elementUtils.getTypeElement(
|
||||
DBAnnotatedField.class.getCanonicalName() + ".DefaultCodec");
|
||||
ENUM_ELEM = elementUtils.getTypeElement(Enum.class.getCanonicalName());
|
||||
}
|
||||
|
||||
public boolean isSubclass(TypeElement t1, TypeElement t2) {
|
||||
return typeUtils.isSubtype(typeUtils.erasure(t1.asType()), typeUtils.erasure(t2.asType()));
|
||||
}
|
||||
|
||||
public boolean hasType(VariableElement field, TypeElement type) {
|
||||
return hasType(field, type.asType());
|
||||
}
|
||||
|
||||
public boolean hasType(VariableElement field, TypeMirror type) {
|
||||
TypeMirror fieldType = field.asType();
|
||||
try {
|
||||
PrimitiveType unboxed = typeUtils.unboxedType(type);
|
||||
if (typeUtils.isSameType(fieldType, unboxed)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// Eh, I guess it's not unboxable
|
||||
}
|
||||
|
||||
if (fieldType.getKind() == TypeKind.DECLARED) {
|
||||
DeclaredType declType = (DeclaredType) fieldType;
|
||||
if (isSubclass((TypeElement) declType.asElement(), ENUM_ELEM)) {
|
||||
Map<String, TypeMirror> enumArgs = getArguments(declType, ENUM_ELEM);
|
||||
TypeMirror argE = enumArgs.get("E");
|
||||
if (typeUtils.isSameType(declType, argE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return typeUtils.isAssignable(fieldType, type);
|
||||
// return typeUtils.isSameType(fieldType, type);
|
||||
}
|
||||
|
||||
public boolean isCapturable(TypeMirror t1, TypeMirror t2) {
|
||||
// TODO: This only works for typevar at top level...
|
||||
// TODO: Need to figure out how to check for capture and check
|
||||
if (t2.getKind() == TypeKind.TYPEVAR) {
|
||||
TypeVariable v2 = (TypeVariable) t2;
|
||||
if (!typeUtils.isSubtype(t1, v2.getUpperBound())) {
|
||||
return false;
|
||||
}
|
||||
if (!typeUtils.isSubtype(v2.getLowerBound(), t1)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return typeUtils.isSubtype(t1, t2);
|
||||
}
|
||||
|
||||
public boolean isEnumType(TypeMirror t) {
|
||||
if (t.getKind() != TypeKind.DECLARED) {
|
||||
return false;
|
||||
}
|
||||
DeclaredType enumType = typeUtils.getDeclaredType(ENUM_ELEM, t);
|
||||
return typeUtils.isSubtype(t, enumType);
|
||||
}
|
||||
|
||||
protected DeclaredType findSupertype(Set<DeclaredType> types, TypeElement superType) {
|
||||
Set<DeclaredType> next;
|
||||
while (!types.isEmpty()) {
|
||||
next = new HashSet<>();
|
||||
for (DeclaredType t : types) {
|
||||
List<? extends TypeMirror> supers = typeUtils.directSupertypes(t);
|
||||
for (TypeMirror s : supers) {
|
||||
DeclaredType ds = (DeclaredType) s;
|
||||
if (superType == ds.asElement()) {
|
||||
return ds;
|
||||
}
|
||||
next.add(ds);
|
||||
}
|
||||
}
|
||||
types = next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public DeclaredType findSupertype(DeclaredType type, TypeElement superElem) {
|
||||
return findSupertype(Set.of(type), superElem);
|
||||
}
|
||||
|
||||
public DeclaredType findSupertype(TypeElement elem, TypeElement superElem) {
|
||||
return findSupertype((DeclaredType) elem.asType(), superElem);
|
||||
}
|
||||
|
||||
protected Map<String, TypeMirror> toArgsMap(TypeElement superElem, DeclaredType superType) {
|
||||
List<? extends TypeParameterElement> typeParameters = superElem.getTypeParameters();
|
||||
List<? extends TypeMirror> typeArguments = superType.getTypeArguments();
|
||||
assert typeParameters.size() == typeArguments.size();
|
||||
Map<String, TypeMirror> result = new HashMap<>();
|
||||
for (int i = 0; i < typeParameters.size(); i++) {
|
||||
result.put(typeParameters.get(i).getSimpleName().toString(), typeArguments.get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, TypeMirror> getArguments(DeclaredType type, TypeElement superElem) {
|
||||
return toArgsMap(superElem, findSupertype(type, superElem));
|
||||
}
|
||||
|
||||
public Map<String, TypeMirror> getArguments(TypeElement elem, TypeElement superElem) {
|
||||
return toArgsMap(superElem, findSupertype(elem, superElem));
|
||||
}
|
||||
|
||||
public String format(TypeMirror type) {
|
||||
FormatVisitor vis = new FormatVisitor();
|
||||
type.accept(vis, null);
|
||||
return vis.buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class FormatVisitor implements TypeVisitor<Void, Void> {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
@Override
|
||||
public Void visit(TypeMirror t, Void p) {
|
||||
switch (t.getKind()) {
|
||||
case ARRAY:
|
||||
return visitArray((ArrayType) t, p);
|
||||
case BOOLEAN:
|
||||
case BYTE:
|
||||
case CHAR:
|
||||
case DOUBLE:
|
||||
case FLOAT:
|
||||
case INT:
|
||||
case LONG:
|
||||
case SHORT:
|
||||
case VOID:
|
||||
return visitPrimitive((PrimitiveType) t, p);
|
||||
case DECLARED:
|
||||
return visitDeclared((DeclaredType) t, p);
|
||||
case ERROR:
|
||||
return visitError((ErrorType) t, p);
|
||||
case EXECUTABLE:
|
||||
return visitExecutable((ExecutableType) t, p);
|
||||
case INTERSECTION:
|
||||
return visitIntersection((IntersectionType) t, p);
|
||||
case NONE:
|
||||
return visitNoType((NoType) t, p);
|
||||
case NULL:
|
||||
return visitNull((NullType) t, p);
|
||||
case TYPEVAR:
|
||||
return visitTypeVariable((TypeVariable) t, p);
|
||||
case UNION:
|
||||
return visitUnion((UnionType) t, p);
|
||||
case WILDCARD:
|
||||
return visitWildcard((WildcardType) t, p);
|
||||
default:
|
||||
return visitUnknown(t, p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitPrimitive(PrimitiveType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitNull(NullType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitArray(ArrayType t, Void p) {
|
||||
visit(t.getComponentType());
|
||||
buf.append("[]");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitDeclared(DeclaredType t, Void p) {
|
||||
buf.append(t.asElement().toString());
|
||||
Iterator<? extends TypeMirror> it = t.getTypeArguments().iterator();
|
||||
if (it.hasNext()) {
|
||||
buf.append("<");
|
||||
visit(it.next());
|
||||
while (it.hasNext()) {
|
||||
buf.append(", ");
|
||||
visit(it.next());
|
||||
}
|
||||
buf.append(">");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitError(ErrorType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitTypeVariable(TypeVariable t, Void p) {
|
||||
buf.append(t.toString());
|
||||
TypeMirror lower = t.getLowerBound();
|
||||
if (lower.getKind() != TypeKind.NULL) {
|
||||
buf.append(" super ");
|
||||
visit(lower);
|
||||
}
|
||||
TypeMirror upper = t.getUpperBound();
|
||||
if (!upper.toString().equals("java.lang.Object")) {
|
||||
buf.append(" extends ");
|
||||
visit(upper);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitWildcard(WildcardType t, Void p) {
|
||||
buf.append("?");
|
||||
TypeMirror sup = t.getSuperBound();
|
||||
if (sup != null) {
|
||||
buf.append(" super ");
|
||||
visit(sup);
|
||||
}
|
||||
TypeMirror ext = t.getExtendsBound();
|
||||
if (ext != null) {
|
||||
buf.append(" extends ");
|
||||
visit(ext);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitExecutable(ExecutableType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitNoType(NoType t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitUnknown(TypeMirror t, Void p) {
|
||||
buf.append(t.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitUnion(UnionType t, Void p) {
|
||||
Iterator<? extends TypeMirror> it = t.getAlternatives().iterator();
|
||||
if (it.hasNext()) {
|
||||
visit(it.next());
|
||||
while (it.hasNext()) {
|
||||
buf.append(" | ");
|
||||
visit(it.next());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitIntersection(IntersectionType t, Void p) {
|
||||
Iterator<? extends TypeMirror> it = t.getBounds().iterator();
|
||||
if (it.hasNext()) {
|
||||
visit(it.next());
|
||||
while (it.hasNext()) {
|
||||
buf.append(" & ");
|
||||
visit(it.next());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
ghidra.util.database.annotproc.DBAnnotatedObjectProcessor
|
Loading…
Add table
Add a link
Reference in a new issue