From c188924ab638cf6869815f135d6e59d356ce05fe Mon Sep 17 00:00:00 2001 From: Alex Cook Date: Tue, 27 Jan 2026 15:42:07 -0500 Subject: [PATCH] refactor: update how a type system is configured --- .../nullness/NullnessRuntimeChecker.java | 17 +- ...nNullTarget.java => NullnessVerifier.java} | 13 +- .../core/AnnotationInstrumenter.java | 39 +++-- .../core/OptOutAnnotation.java | 13 -- .../runtimeframework/core/RuntimeChecker.java | 11 +- .../core/RuntimeVerifier.java | 28 +++ .../core/TargetAnnotation.java | 25 --- .../core/TypeSystemConfiguration.java | 71 ++++++++ .../runtimeframework/core/ValidationKind.java | 16 ++ .../policy/EnforcementPolicy.java | 26 +-- .../policy/GlobalEnforcementPolicy.java | 50 +++--- .../policy/StandardEnforcementPolicy.java | 165 +++++++++--------- 12 files changed, 276 insertions(+), 198 deletions(-) rename checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/{NonNullTarget.java => NullnessVerifier.java} (64%) delete mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/core/OptOutAnnotation.java create mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/core/RuntimeVerifier.java delete mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/core/TargetAnnotation.java create mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/core/TypeSystemConfiguration.java create mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/core/ValidationKind.java diff --git a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessRuntimeChecker.java b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessRuntimeChecker.java index c363cf8..87fd2e2 100644 --- a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessRuntimeChecker.java +++ b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessRuntimeChecker.java @@ -1,15 +1,16 @@ package io.github.eisop.runtimeframework.checker.nullness; import io.github.eisop.runtimeframework.core.AnnotationInstrumenter; -import io.github.eisop.runtimeframework.core.OptOutAnnotation; import io.github.eisop.runtimeframework.core.RuntimeChecker; import io.github.eisop.runtimeframework.core.RuntimeInstrumenter; +import io.github.eisop.runtimeframework.core.TypeSystemConfiguration; +import io.github.eisop.runtimeframework.core.ValidationKind; import io.github.eisop.runtimeframework.filter.ClassInfo; import io.github.eisop.runtimeframework.filter.Filter; import io.github.eisop.runtimeframework.policy.EnforcementPolicy; import io.github.eisop.runtimeframework.resolution.HierarchyResolver; import io.github.eisop.runtimeframework.resolution.ReflectionHierarchyResolver; -import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; public class NullnessRuntimeChecker extends RuntimeChecker { @@ -21,9 +22,15 @@ public String getName() { @Override public RuntimeInstrumenter getInstrumenter(Filter filter) { - EnforcementPolicy policy = - createPolicy( - List.of(new NonNullTarget()), List.of(new OptOutAnnotation(Nullable.class)), filter); + NullnessVerifier verifier = new NullnessVerifier(); + + TypeSystemConfiguration config = + new TypeSystemConfiguration() + .onEnforce(NonNull.class, verifier) + .onNoop(Nullable.class) + .withDefault(ValidationKind.ENFORCE, verifier); + + EnforcementPolicy policy = createPolicy(config, filter); HierarchyResolver resolver = new ReflectionHierarchyResolver( diff --git a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NonNullTarget.java b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessVerifier.java similarity index 64% rename from checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NonNullTarget.java rename to checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessVerifier.java index 479e4c8..67a0f01 100644 --- a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NonNullTarget.java +++ b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessVerifier.java @@ -1,14 +1,12 @@ package io.github.eisop.runtimeframework.checker.nullness; -import io.github.eisop.runtimeframework.core.TargetAnnotation; -import java.lang.annotation.Annotation; +import io.github.eisop.runtimeframework.core.RuntimeVerifier; import java.lang.classfile.CodeBuilder; import java.lang.classfile.TypeKind; import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; -import org.checkerframework.checker.nullness.qual.NonNull; -public class NonNullTarget implements TargetAnnotation { +public class NullnessVerifier implements RuntimeVerifier { private static final ClassDesc VERIFIER = ClassDesc.of(NullnessRuntimeVerifier.class.getName()); private static final String METHOD = "checkNotNull"; @@ -16,12 +14,7 @@ public class NonNullTarget implements TargetAnnotation { MethodTypeDesc.ofDescriptor("(Ljava/lang/Object;Ljava/lang/String;)V"); @Override - public Class annotationType() { - return NonNull.class; - } - - @Override - public void check(CodeBuilder b, TypeKind type, String diagnosticName) { + public void generateCheck(CodeBuilder b, TypeKind type, String diagnosticName) { if (type == TypeKind.REFERENCE) { b.ldc(diagnosticName + " must be NonNull"); b.invokestatic(VERIFIER, METHOD, DESC); diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/core/AnnotationInstrumenter.java b/framework/src/main/java/io/github/eisop/runtimeframework/core/AnnotationInstrumenter.java index 8e37c67..af620f7 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/core/AnnotationInstrumenter.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/core/AnnotationInstrumenter.java @@ -39,10 +39,10 @@ public AnnotationInstrumenter( @Override protected void generateArrayStoreCheck(CodeBuilder b, ArrayStoreInstruction instruction) { if (instruction.opcode() == Opcode.AASTORE) { - TargetAnnotation target = policy.getArrayStoreCheck(TypeKind.REFERENCE); + RuntimeVerifier target = policy.getArrayStoreCheck(TypeKind.REFERENCE); if (target != null) { b.dup(); - target.check(b, TypeKind.REFERENCE, "Array Element Write"); + target.generateCheck(b, TypeKind.REFERENCE, "Array Element Write"); } } } @@ -50,10 +50,10 @@ protected void generateArrayStoreCheck(CodeBuilder b, ArrayStoreInstruction inst @Override protected void generateArrayLoadCheck(CodeBuilder b, ArrayLoadInstruction instruction) { if (instruction.opcode() == Opcode.AALOAD) { - TargetAnnotation target = policy.getArrayLoadCheck(TypeKind.REFERENCE); + RuntimeVerifier target = policy.getArrayLoadCheck(TypeKind.REFERENCE); if (target != null) { b.dup(); - target.check(b, TypeKind.REFERENCE, "Array Element Read"); + target.generateCheck(b, TypeKind.REFERENCE, "Array Element Read"); } } } @@ -61,17 +61,17 @@ protected void generateArrayLoadCheck(CodeBuilder b, ArrayLoadInstruction instru @Override protected void generateParameterCheck( CodeBuilder b, int slotIndex, TypeKind type, MethodModel method, int paramIndex) { - TargetAnnotation target = policy.getParameterCheck(method, paramIndex, type); + RuntimeVerifier target = policy.getParameterCheck(method, paramIndex, type); if (target != null) { b.aload(slotIndex); - target.check(b, type, "Parameter " + paramIndex); + target.generateCheck(b, type, "Parameter " + paramIndex); } } @Override protected void generateFieldWriteCheck( CodeBuilder b, FieldInstruction field, ClassModel classModel) { - TargetAnnotation target = null; + RuntimeVerifier target = null; TypeKind type = TypeKind.fromDescriptor(field.typeSymbol().descriptorString()); if (field.owner().asInternalName().equals(classModel.thisClass().asInternalName())) { @@ -88,10 +88,10 @@ protected void generateFieldWriteCheck( if (target != null) { if (field.opcode() == Opcode.PUTSTATIC) { b.dup(); - target.check(b, type, "Static Field '" + field.name().stringValue() + "'"); + target.generateCheck(b, type, "Static Field '" + field.name().stringValue() + "'"); } else if (field.opcode() == Opcode.PUTFIELD) { b.dup_x1(); - target.check(b, type, "Field '" + field.name().stringValue() + "'"); + target.generateCheck(b, type, "Field '" + field.name().stringValue() + "'"); b.swap(); } } @@ -100,7 +100,7 @@ protected void generateFieldWriteCheck( @Override protected void generateFieldReadCheck( CodeBuilder b, FieldInstruction field, ClassModel classModel) { - TargetAnnotation target = null; + RuntimeVerifier target = null; TypeKind type = TypeKind.fromDescriptor(field.typeSymbol().descriptorString()); if (field.owner().asInternalName().equals(classModel.thisClass().asInternalName())) { @@ -117,17 +117,18 @@ protected void generateFieldReadCheck( if (target != null) { if (type.slotSize() == 1) { b.dup(); - target.check(b, type, "Read Field '" + field.name().stringValue() + "'"); + target.generateCheck(b, type, "Read Field '" + field.name().stringValue() + "'"); } } } @Override protected void generateReturnCheck(CodeBuilder b, ReturnInstruction ret, MethodModel method) { - TargetAnnotation target = policy.getReturnCheck(method); + RuntimeVerifier target = policy.getReturnCheck(method); if (target != null) { b.dup(); - target.check(b, TypeKind.REFERENCE, "Return value of " + method.methodName().stringValue()); + target.generateCheck( + b, TypeKind.REFERENCE, "Return value of " + method.methodName().stringValue()); } } @@ -139,11 +140,11 @@ protected void generateUncheckedReturnCheck( ClassModel classModel, ClassLoader loader) { if (ret.opcode() != Opcode.ARETURN) return; - TargetAnnotation target = policy.getUncheckedOverrideReturnCheck(classModel, method, loader); + RuntimeVerifier target = policy.getUncheckedOverrideReturnCheck(classModel, method, loader); if (target != null) { b.dup(); - target.check( + target.generateCheck( b, TypeKind.REFERENCE, "Return value of overridden method " + method.methodName().stringValue()); @@ -177,11 +178,11 @@ protected void generateStoreCheck( if (!isRefStore) return; int slot = instruction.slot(); - TargetAnnotation target = policy.getLocalVariableWriteCheck(method, slot, TypeKind.REFERENCE); + RuntimeVerifier target = policy.getLocalVariableWriteCheck(method, slot, TypeKind.REFERENCE); if (target != null) { b.dup(); - target.check(b, TypeKind.REFERENCE, "Local Variable Assignment (Slot " + slot + ")"); + target.generateCheck(b, TypeKind.REFERENCE, "Local Variable Assignment (Slot " + slot + ")"); } } @@ -207,10 +208,10 @@ private void emitBridge(ClassBuilder builder, Method parentMethod) { for (int i = 0; i < paramTypes.length; i++) { TypeKind type = TypeKind.from(ClassDesc.ofDescriptor(paramTypes[i].descriptorString())); - TargetAnnotation target = policy.getBridgeParameterCheck(parentMethod, i); + RuntimeVerifier target = policy.getBridgeParameterCheck(parentMethod, i); if (target != null) { codeBuilder.aload(slotIndex); - target.check( + target.generateCheck( codeBuilder, type, "Parameter " + i + " in inherited method " + methodName); } slotIndex += type.slotSize(); diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/core/OptOutAnnotation.java b/framework/src/main/java/io/github/eisop/runtimeframework/core/OptOutAnnotation.java deleted file mode 100644 index 53deda5..0000000 --- a/framework/src/main/java/io/github/eisop/runtimeframework/core/OptOutAnnotation.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.eisop.runtimeframework.core; - -import java.lang.annotation.Annotation; - -/** - * Represents an annotation that explicitly disables strict default checks. - * - *

For example, in a Nullness system, {@code @Nullable} is an OptOutAnnotation. When present, the - * policy will skip generating checks. - */ -public record OptOutAnnotation(Class annotationType) { - // No helper methods needed; the consumer calls annotationType().descriptorString() -} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/core/RuntimeChecker.java b/framework/src/main/java/io/github/eisop/runtimeframework/core/RuntimeChecker.java index a024090..4d09bf7 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/core/RuntimeChecker.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/core/RuntimeChecker.java @@ -5,7 +5,6 @@ import io.github.eisop.runtimeframework.policy.EnforcementPolicy; import io.github.eisop.runtimeframework.policy.GlobalEnforcementPolicy; import io.github.eisop.runtimeframework.policy.StandardEnforcementPolicy; -import java.util.Collection; /** * Represents a specific type system or check to be enforced (e.g., Nullness, Immutability). This @@ -30,20 +29,18 @@ public abstract class RuntimeChecker { * *

Subclasses should use this instead of manually checking system properties. * - * @param targets The collection of target annotations relevant to this checker. + * @param config The TypeSystemConfiguration for this checker. * @param filter The filter defining the boundary between Checked and Unchecked code. * @return A configured EnforcementPolicy (Standard or Global). */ protected EnforcementPolicy createPolicy( - Collection targets, - Collection optOuts, - Filter filter) { + TypeSystemConfiguration config, Filter filter) { boolean isGlobalMode = Boolean.getBoolean("runtime.global"); if (isGlobalMode) { - return new GlobalEnforcementPolicy(targets, optOuts, filter); + return new GlobalEnforcementPolicy(config, filter); } else { - return new StandardEnforcementPolicy(targets, optOuts, filter); + return new StandardEnforcementPolicy(config, filter); } } } diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/core/RuntimeVerifier.java b/framework/src/main/java/io/github/eisop/runtimeframework/core/RuntimeVerifier.java new file mode 100644 index 0000000..5b1320d --- /dev/null +++ b/framework/src/main/java/io/github/eisop/runtimeframework/core/RuntimeVerifier.java @@ -0,0 +1,28 @@ +package io.github.eisop.runtimeframework.core; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.TypeKind; + +/** + * A functional interface for generating runtime verification bytecode. + * + *

Implementations are responsible for emitting instructions to verify that a value on the + * operand stack satisfies a specific property. + */ +@FunctionalInterface +public interface RuntimeVerifier { + + /** + * Generates bytecode to verify a property. + * + *

Contract: The value to be checked is already at the top of the operand stack. This + * method must consume that value (e.g., by checking it) or restore the stack state (e.g., by + * checking a duplicated value). + * + * @param b The CodeBuilder to emit instructions into. + * @param type The type of the value on the stack. + * @param diagnosticName A human-readable name for the value (e.g., "Parameter 0") to be used in + * error messages. + */ + void generateCheck(CodeBuilder b, TypeKind type, String diagnosticName); +} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/core/TargetAnnotation.java b/framework/src/main/java/io/github/eisop/runtimeframework/core/TargetAnnotation.java deleted file mode 100644 index 4759e47..0000000 --- a/framework/src/main/java/io/github/eisop/runtimeframework/core/TargetAnnotation.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.eisop.runtimeframework.core; - -import java.lang.annotation.Annotation; -import java.lang.classfile.CodeBuilder; -import java.lang.classfile.TypeKind; - -/** Represents a strategy for enforcing a property triggered by a specific annotation. */ -public interface TargetAnnotation { - - /** The annotation class that triggers this check. */ - Class annotationType(); - - /** - * Generates bytecode to verify the property. - * - *

Contract: The value to be checked is already at the top of the operand stack. This - * method must consume that value (e.g., by checking it) or restore the stack state. - * - * @param b The CodeBuilder to emit instructions into. - * @param type The type of the value on the stack. - * @param diagnosticName A human-readable name for the value (e.g., "Parameter 0", "Field 'x'") to - * be used in error messages. - */ - void check(CodeBuilder b, TypeKind type, String diagnosticName); -} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/core/TypeSystemConfiguration.java b/framework/src/main/java/io/github/eisop/runtimeframework/core/TypeSystemConfiguration.java new file mode 100644 index 0000000..515fed6 --- /dev/null +++ b/framework/src/main/java/io/github/eisop/runtimeframework/core/TypeSystemConfiguration.java @@ -0,0 +1,71 @@ +package io.github.eisop.runtimeframework.core; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; + +/** + * Configuration for a runtime type system. + * + *

Maps annotations to their validation semantics and verification logic. + */ +public class TypeSystemConfiguration { + + private final Map registry = new HashMap<>(); + private ConfigEntry defaultEntry; + + public TypeSystemConfiguration() { + // A specific checker sets its own default. + this.defaultEntry = new ConfigEntry(ValidationKind.NOOP, null); + } + + /** + * Registers a qualifier that requires enforcement. + * + * @param annotation The annotation class. + * @param verifier The logic to verify the property. + * @return this configuration (fluent). + */ + public TypeSystemConfiguration onEnforce( + Class annotation, RuntimeVerifier verifier) { + registry.put(annotation.descriptorString(), new ConfigEntry(ValidationKind.ENFORCE, verifier)); + return this; + } + + /** + * Registers a qualifier that requires NO runtime check (a no-op). + * + * @param annotation The annotation class. + * @return this configuration (fluent). + */ + public TypeSystemConfiguration onNoop(Class annotation) { + registry.put(annotation.descriptorString(), new ConfigEntry(ValidationKind.NOOP, null)); + return this; + } + + /** + * Sets the default behavior when no registered annotation is found on an element. + * + * @param kind The validation kind. + * @param verifier The verifier (required if kind is ENFORCE). + * @return this configuration (fluent). + */ + public TypeSystemConfiguration withDefault(ValidationKind kind, RuntimeVerifier verifier) { + this.defaultEntry = new ConfigEntry(kind, verifier); + return this; + } + + /** + * Looks up the configuration for a specific annotation descriptor. Returns null if the annotation + * is not registered (i.e., it is irrelevant). + */ + public ConfigEntry find(String annotationDescriptor) { + return registry.get(annotationDescriptor); + } + + public ConfigEntry getDefault() { + return defaultEntry; + } + + public record ConfigEntry(ValidationKind kind, RuntimeVerifier verifier) {} +} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/core/ValidationKind.java b/framework/src/main/java/io/github/eisop/runtimeframework/core/ValidationKind.java new file mode 100644 index 0000000..0bf6705 --- /dev/null +++ b/framework/src/main/java/io/github/eisop/runtimeframework/core/ValidationKind.java @@ -0,0 +1,16 @@ +package io.github.eisop.runtimeframework.core; + +/** Defines the semantic behavior of a qualifier in the runtime system. */ +public enum ValidationKind { + /** + * The qualifier requires runtime verification. The associated {@link RuntimeVerifier} will be + * invoked. + */ + ENFORCE, + + /** + * The qualifier explicitly indicates that no check is required. (e.g., a Top type like @Nullable, + * or an explicit suppression). + */ + NOOP +} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/policy/EnforcementPolicy.java b/framework/src/main/java/io/github/eisop/runtimeframework/policy/EnforcementPolicy.java index c1b557f..0b8a3a4 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/policy/EnforcementPolicy.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/policy/EnforcementPolicy.java @@ -1,6 +1,6 @@ package io.github.eisop.runtimeframework.policy; -import io.github.eisop.runtimeframework.core.TargetAnnotation; +import io.github.eisop.runtimeframework.core.RuntimeVerifier; import java.lang.classfile.ClassModel; import java.lang.classfile.FieldModel; import java.lang.classfile.MethodModel; @@ -12,49 +12,49 @@ public interface EnforcementPolicy { /** Should we check this specific parameter at method entry? */ - TargetAnnotation getParameterCheck(MethodModel method, int paramIndex, TypeKind type); + RuntimeVerifier getParameterCheck(MethodModel method, int paramIndex, TypeKind type); /** Should we check a write to this field? */ - TargetAnnotation getFieldWriteCheck(FieldModel field, TypeKind type); + RuntimeVerifier getFieldWriteCheck(FieldModel field, TypeKind type); /** Should we check a read from this field? */ - TargetAnnotation getFieldReadCheck(FieldModel field, TypeKind type); + RuntimeVerifier getFieldReadCheck(FieldModel field, TypeKind type); /** Should we check this return value? */ - TargetAnnotation getReturnCheck(MethodModel method); + RuntimeVerifier getReturnCheck(MethodModel method); /** * Should we check a write to a field in an EXTERNAL class? (Used when Unchecked code writes to * Checked code). */ - default TargetAnnotation getBoundaryFieldWriteCheck( + default RuntimeVerifier getBoundaryFieldWriteCheck( String owner, String fieldName, TypeKind type) { return null; } /** We are calling a method on 'owner'. Should we check the result? */ - TargetAnnotation getBoundaryCallCheck(String owner, MethodTypeDesc desc); + RuntimeVerifier getBoundaryCallCheck(String owner, MethodTypeDesc desc); /** We are reading field from an EXTERNAL class. Should we check the value? */ - TargetAnnotation getBoundaryFieldReadCheck(String owner, String fieldName, TypeKind type); + RuntimeVerifier getBoundaryFieldReadCheck(String owner, String fieldName, TypeKind type); /** Should we generate a bridge for this inherited method? */ boolean shouldGenerateBridge(Method parentMethod); /** For a bridge we are generating, what check applies to this parameter? */ - TargetAnnotation getBridgeParameterCheck(Method parentMethod, int paramIndex); + RuntimeVerifier getBridgeParameterCheck(Method parentMethod, int paramIndex); /** Should we check an value being stored into an array? */ - TargetAnnotation getArrayStoreCheck(TypeKind componentType); + RuntimeVerifier getArrayStoreCheck(TypeKind componentType); /** Should we check a value being read from an array? */ - TargetAnnotation getArrayLoadCheck(TypeKind componentType); + RuntimeVerifier getArrayLoadCheck(TypeKind componentType); /** Should we check a value being stored in a variable? */ - TargetAnnotation getLocalVariableWriteCheck(MethodModel method, int slot, TypeKind type); + RuntimeVerifier getLocalVariableWriteCheck(MethodModel method, int slot, TypeKind type); /** Should we check the return of an unchecked override? */ - default TargetAnnotation getUncheckedOverrideReturnCheck( + default RuntimeVerifier getUncheckedOverrideReturnCheck( ClassModel classModel, MethodModel method, ClassLoader loader) { return null; } diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/policy/GlobalEnforcementPolicy.java b/framework/src/main/java/io/github/eisop/runtimeframework/policy/GlobalEnforcementPolicy.java index 00533ac..771eeed 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/policy/GlobalEnforcementPolicy.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/policy/GlobalEnforcementPolicy.java @@ -1,7 +1,8 @@ package io.github.eisop.runtimeframework.policy; -import io.github.eisop.runtimeframework.core.OptOutAnnotation; -import io.github.eisop.runtimeframework.core.TargetAnnotation; +import io.github.eisop.runtimeframework.core.RuntimeVerifier; +import io.github.eisop.runtimeframework.core.TypeSystemConfiguration; +import io.github.eisop.runtimeframework.core.ValidationKind; import io.github.eisop.runtimeframework.filter.ClassInfo; import io.github.eisop.runtimeframework.filter.Filter; import java.lang.classfile.ClassModel; @@ -9,33 +10,32 @@ import java.lang.classfile.TypeKind; import java.lang.constant.ClassDesc; import java.lang.reflect.Method; -import java.util.Collection; public class GlobalEnforcementPolicy extends StandardEnforcementPolicy { public GlobalEnforcementPolicy( - Collection targetAnnotations, - Collection optOutAnnotations, - Filter safetyFilter) { - super(targetAnnotations, optOutAnnotations, safetyFilter); + TypeSystemConfiguration configuration, Filter safetyFilter) { + super(configuration, safetyFilter); } @Override - public TargetAnnotation getBoundaryFieldWriteCheck( - String owner, String fieldName, TypeKind type) { + public RuntimeVerifier getBoundaryFieldWriteCheck(String owner, String fieldName, TypeKind type) { if (isClassChecked(owner)) { if (type == TypeKind.REFERENCE) { if (isFieldOptOut(owner, fieldName)) { return null; } - return super.defaultTarget; + TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); + if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { + return defaultEntry.verifier(); + } } } return null; } @Override - public TargetAnnotation getUncheckedOverrideReturnCheck( + public RuntimeVerifier getUncheckedOverrideReturnCheck( ClassModel classModel, MethodModel method, ClassLoader loader) { String superName = classModel.superclass().map(sc -> sc.asInternalName().replace('/', '.')).orElse(null); @@ -52,25 +52,27 @@ public TargetAnnotation getUncheckedOverrideReturnCheck( String methodDesc = method.methodTypeSymbol().descriptorString(); String parentDesc = getMethodDescriptor(m); if (methodDesc.equals(parentDesc)) { + // Check parent method annotations for (java.lang.annotation.Annotation anno : m.getAnnotations()) { String desc = "L" + anno.annotationType().getName().replace('.', '/') + ";"; - if (optOutDescriptors.contains(desc)) { - return null; - } + TypeSystemConfiguration.ConfigEntry entry = configuration.find(desc); + if (entry != null && entry.kind() == ValidationKind.NOOP) return null; } - + // Check return type annotations for (java.lang.annotation.Annotation anno : m.getAnnotatedReturnType().getAnnotations()) { String desc = "L" + anno.annotationType().getName().replace('.', '/') + ";"; - if (optOutDescriptors.contains(desc)) { - return null; - } + TypeSystemConfiguration.ConfigEntry entry = configuration.find(desc); + if (entry != null && entry.kind() == ValidationKind.NOOP) return null; } TypeKind returnType = TypeKind.from(ClassDesc.ofDescriptor(m.getReturnType().descriptorString())); if (returnType == TypeKind.REFERENCE) { - return super.defaultTarget; + TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); + if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { + return defaultEntry.verifier(); + } } } } @@ -113,12 +115,14 @@ private boolean isFieldOptOut(String owner, String fieldName) { java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); for (java.lang.annotation.Annotation anno : field.getAnnotations()) { - if (optOutDescriptors.contains( - "L" + anno.annotationType().getName().replace('.', '/') + ";")) return true; + String desc = "L" + anno.annotationType().getName().replace('.', '/') + ";"; + TypeSystemConfiguration.ConfigEntry entry = configuration.find(desc); + if (entry != null && entry.kind() == ValidationKind.NOOP) return true; } for (java.lang.annotation.Annotation anno : field.getAnnotatedType().getAnnotations()) { - if (optOutDescriptors.contains( - "L" + anno.annotationType().getName().replace('.', '/') + ";")) return true; + String desc = "L" + anno.annotationType().getName().replace('.', '/') + ";"; + TypeSystemConfiguration.ConfigEntry entry = configuration.find(desc); + if (entry != null && entry.kind() == ValidationKind.NOOP) return true; } } catch (Throwable t) { System.out.println("reflection fail in is field opt out"); diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/policy/StandardEnforcementPolicy.java b/framework/src/main/java/io/github/eisop/runtimeframework/policy/StandardEnforcementPolicy.java index f513ca6..897fa83 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/policy/StandardEnforcementPolicy.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/policy/StandardEnforcementPolicy.java @@ -1,7 +1,8 @@ package io.github.eisop.runtimeframework.policy; -import io.github.eisop.runtimeframework.core.OptOutAnnotation; -import io.github.eisop.runtimeframework.core.TargetAnnotation; +import io.github.eisop.runtimeframework.core.RuntimeVerifier; +import io.github.eisop.runtimeframework.core.TypeSystemConfiguration; +import io.github.eisop.runtimeframework.core.ValidationKind; import io.github.eisop.runtimeframework.filter.ClassInfo; import io.github.eisop.runtimeframework.filter.Filter; import java.lang.classfile.Annotation; @@ -14,132 +15,121 @@ import java.lang.constant.MethodTypeDesc; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; public class StandardEnforcementPolicy implements EnforcementPolicy { - protected final Map targets; - protected final Set optOutDescriptors; - protected final TargetAnnotation defaultTarget; + protected final TypeSystemConfiguration configuration; protected final Filter safetyFilter; public StandardEnforcementPolicy( - Collection targetAnnotations, - Collection optOutAnnotations, - Filter safetyFilter) { - - this.targets = - targetAnnotations.stream() - .collect(Collectors.toMap(t -> t.annotationType().descriptorString(), t -> t)); - - this.optOutDescriptors = - optOutAnnotations.stream() - .map(o -> o.annotationType().descriptorString()) - .collect(Collectors.toSet()); - - this.defaultTarget = targetAnnotations.stream().findFirst().orElse(null); + TypeSystemConfiguration configuration, Filter safetyFilter) { + this.configuration = configuration; this.safetyFilter = safetyFilter; } - private TargetAnnotation findTarget(List annotations) { + /** + * Resolves the verifier for a given list of annotations. Logic: 1. Check for any annotation that + * explicitly ENFORCES. 2. Check for any annotation that explicitly NOOPs. 3. Fallback to default. + */ + protected RuntimeVerifier resolveVerifier(List annotations) { + // 1. Look for explicit configuration for (Annotation a : annotations) { String desc = a.classSymbol().descriptorString(); - TargetAnnotation t = targets.get(desc); - if (t != null) return t; + TypeSystemConfiguration.ConfigEntry entry = configuration.find(desc); + if (entry != null) { + if (entry.kind() == ValidationKind.ENFORCE) { + return entry.verifier(); + } else if (entry.kind() == ValidationKind.NOOP) { + return null; // Explicitly skipped + } + } } - return null; - } - private boolean hasOptOutAnnotation(List annotations) { - for (Annotation a : annotations) { - if (optOutDescriptors.contains(a.classSymbol().descriptorString())) { - return true; - } + // 2. Fallback to default + TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); + if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { + return defaultEntry.verifier(); } - return false; + + return null; } @Override - public TargetAnnotation getParameterCheck(MethodModel method, int paramIndex, TypeKind type) { + public RuntimeVerifier getParameterCheck(MethodModel method, int paramIndex, TypeKind type) { if (type != TypeKind.REFERENCE) return null; List annos = getMethodParamAnnotations(method, paramIndex); - - TargetAnnotation explicit = findTarget(annos); - if (explicit != null) return explicit; - if (hasOptOutAnnotation(annos)) return null; - - return defaultTarget; + return resolveVerifier(annos); } @Override - public TargetAnnotation getFieldWriteCheck(FieldModel field, TypeKind type) { + public RuntimeVerifier getFieldWriteCheck(FieldModel field, TypeKind type) { return null; } @Override - public TargetAnnotation getFieldReadCheck(FieldModel field, TypeKind type) { + public RuntimeVerifier getFieldReadCheck(FieldModel field, TypeKind type) { if (type != TypeKind.REFERENCE) return null; List annos = getFieldAnnotations(field); - TargetAnnotation explicit = findTarget(annos); - if (explicit != null) return explicit; - if (hasOptOutAnnotation(annos)) return null; - return defaultTarget; + return resolveVerifier(annos); } @Override - public TargetAnnotation getReturnCheck(MethodModel method) { + public RuntimeVerifier getReturnCheck(MethodModel method) { return null; } @Override - public TargetAnnotation getLocalVariableWriteCheck(MethodModel method, int slot, TypeKind type) { + public RuntimeVerifier getLocalVariableWriteCheck(MethodModel method, int slot, TypeKind type) { if (type != TypeKind.REFERENCE) return null; - List annos = getLocalVariableAnnotations(method, slot); - - TargetAnnotation explicit = findTarget(annos); - if (explicit != null) return explicit; - if (hasOptOutAnnotation(annos)) return null; - - return defaultTarget; + return resolveVerifier(annos); } @Override - public TargetAnnotation getArrayStoreCheck(TypeKind componentType) { + public RuntimeVerifier getArrayStoreCheck(TypeKind componentType) { if (componentType == TypeKind.REFERENCE) { - return defaultTarget; + TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); + if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { + return defaultEntry.verifier(); + } } return null; } @Override - public TargetAnnotation getArrayLoadCheck(TypeKind componentType) { + public RuntimeVerifier getArrayLoadCheck(TypeKind componentType) { if (componentType == TypeKind.REFERENCE) { - return defaultTarget; + TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); + if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { + return defaultEntry.verifier(); + } } return null; } @Override - public TargetAnnotation getBoundaryCallCheck(String owner, MethodTypeDesc desc) { + public RuntimeVerifier getBoundaryCallCheck(String owner, MethodTypeDesc desc) { boolean isUnchecked = !safetyFilter.test(new ClassInfo(owner, null, null)); TypeKind returnType = TypeKind.from(desc.returnType()); if (isUnchecked && returnType == TypeKind.REFERENCE) { - return defaultTarget; + TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); + if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { + return defaultEntry.verifier(); + } } return null; } @Override - public TargetAnnotation getBoundaryFieldReadCheck(String owner, String fieldName, TypeKind type) { + public RuntimeVerifier getBoundaryFieldReadCheck(String owner, String fieldName, TypeKind type) { boolean isUnchecked = !safetyFilter.test(new ClassInfo(owner, null, null)); if (isUnchecked && type == TypeKind.REFERENCE) { - return defaultTarget; + TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); + if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { + return defaultEntry.verifier(); + } } return null; } @@ -153,46 +143,55 @@ public boolean shouldGenerateBridge(Method parentMethod) { java.lang.annotation.Annotation[][] paramAnnos = parentMethod.getParameterAnnotations(); for (int i = 0; i < paramTypes.length; i++) { + // Check specific parameter annotations + boolean explicitNoop = false; + boolean explicitEnforce = false; + for (java.lang.annotation.Annotation anno : paramAnnos[i]) { String desc = "L" + anno.annotationType().getName().replace('.', '/') + ";"; - if (targets.containsKey(desc)) return true; + TypeSystemConfiguration.ConfigEntry entry = configuration.find(desc); + if (entry != null) { + if (entry.kind() == ValidationKind.ENFORCE) explicitEnforce = true; + if (entry.kind() == ValidationKind.NOOP) explicitNoop = true; + } } + if (explicitEnforce) return true; + + // If no explicit decision, check default if it's a reference type ClassDesc pTypeDesc = ClassDesc.ofDescriptor(paramTypes[i].descriptorString()); - if (TypeKind.from(pTypeDesc) == TypeKind.REFERENCE && defaultTarget != null) { - boolean isOptedOut = false; - for (java.lang.annotation.Annotation anno : paramAnnos[i]) { - String desc = "L" + anno.annotationType().getName().replace('.', '/') + ";"; - if (optOutDescriptors.contains(desc)) { - isOptedOut = true; - break; - } + if (TypeKind.from(pTypeDesc) == TypeKind.REFERENCE && !explicitNoop) { + TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); + if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { + return true; } - if (!isOptedOut) return true; } } return false; } @Override - public TargetAnnotation getBridgeParameterCheck(Method parentMethod, int paramIndex) { + public RuntimeVerifier getBridgeParameterCheck(Method parentMethod, int paramIndex) { java.lang.annotation.Annotation[] annos = parentMethod.getParameterAnnotations()[paramIndex]; Class paramType = parentMethod.getParameterTypes()[paramIndex]; + // 1. Explicit Configuration for (java.lang.annotation.Annotation anno : annos) { String desc = "L" + anno.annotationType().getName().replace('.', '/') + ";"; - TargetAnnotation t = targets.get(desc); - if (t != null) return t; - } - - for (java.lang.annotation.Annotation anno : annos) { - String desc = "L" + anno.annotationType().getName().replace('.', '/') + ";"; - if (optOutDescriptors.contains(desc)) return null; + TypeSystemConfiguration.ConfigEntry entry = configuration.find(desc); + if (entry != null) { + if (entry.kind() == ValidationKind.ENFORCE) return entry.verifier(); + if (entry.kind() == ValidationKind.NOOP) return null; + } } + // 2. Default ClassDesc pTypeDesc = ClassDesc.ofDescriptor(paramType.descriptorString()); if (TypeKind.from(pTypeDesc) == TypeKind.REFERENCE) { - return defaultTarget; + TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); + if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { + return defaultEntry.verifier(); + } } return null; }