Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -21,9 +22,15 @@ public String getName() {

@Override
public RuntimeInstrumenter getInstrumenter(Filter<ClassInfo> 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(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
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";
private static final MethodTypeDesc DESC =
MethodTypeDesc.ofDescriptor("(Ljava/lang/Object;Ljava/lang/String;)V");

@Override
public Class<? extends Annotation> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,39 +39,39 @@ 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");
}
}
}

@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");
}
}
}

@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())) {
Expand All @@ -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();
}
}
Expand All @@ -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())) {
Expand All @@ -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());
}
}

Expand All @@ -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());
Expand Down Expand Up @@ -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 + ")");
}
}

Expand All @@ -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();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,20 +29,18 @@ public abstract class RuntimeChecker {
*
* <p>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<TargetAnnotation> targets,
Collection<OptOutAnnotation> optOuts,
Filter<ClassInfo> filter) {
TypeSystemConfiguration config, Filter<ClassInfo> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.
*
* <p><b>Contract:</b> 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);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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.
*
* <p>Maps annotations to their validation semantics and verification logic.
*/
public class TypeSystemConfiguration {

private final Map<String, ConfigEntry> 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<? extends Annotation> 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<? extends Annotation> 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) {}
}
Original file line number Diff line number Diff line change
@@ -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
}
Loading