From 37e33bbb4ada0745f7a53fffdeacb6be6467953e Mon Sep 17 00:00:00 2001 From: Alex Cook Date: Thu, 16 Apr 2026 12:41:03 -0400 Subject: [PATCH 1/4] refactor: remove old pipeline --- .../nullness/NullnessCheckGenerator.java | 58 --- .../runtimeframework/agent/RuntimeAgent.java | 4 +- .../runtimeframework/core/CheckGenerator.java | 39 -- .../runtimeframework/core/RuntimeChecker.java | 25 +- .../core/TypeSystemConfiguration.java | 71 --- .../runtimeframework/core/ValidationKind.java | 16 - .../EnforcementInstrumenter.java | 45 -- .../instrumentation/EnforcementTransform.java | 91 +--- ...r.java => ContractEnforcementPlanner.java} | 6 +- .../planning/InstrumentationAction.java | 23 - .../StrategyBackedEnforcementPlanner.java | 315 ------------- ...licy.java => ScopeAwareRuntimePolicy.java} | 8 +- .../strategy/BoundaryStrategy.java | 427 ------------------ .../strategy/InstrumentationStrategy.java | 67 --- .../strategy/StrictBoundaryStrategy.java | 16 - .../policy/DefaultRuntimePolicyTest.java | 156 ------- 16 files changed, 16 insertions(+), 1351 deletions(-) delete mode 100644 checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessCheckGenerator.java delete mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/core/CheckGenerator.java delete mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/core/TypeSystemConfiguration.java delete mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/core/ValidationKind.java rename framework/src/main/java/io/github/eisop/runtimeframework/planning/{SemanticsBackedEnforcementPlanner.java => ContractEnforcementPlanner.java} (98%) delete mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/planning/StrategyBackedEnforcementPlanner.java rename framework/src/main/java/io/github/eisop/runtimeframework/policy/{DefaultRuntimePolicy.java => ScopeAwareRuntimePolicy.java} (96%) delete mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/strategy/BoundaryStrategy.java delete mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/strategy/InstrumentationStrategy.java delete mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/strategy/StrictBoundaryStrategy.java delete mode 100644 framework/src/test/java/io/github/eisop/runtimeframework/policy/DefaultRuntimePolicyTest.java diff --git a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessCheckGenerator.java b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessCheckGenerator.java deleted file mode 100644 index 95de358..0000000 --- a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessCheckGenerator.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.eisop.runtimeframework.checker.nullness; - -import io.github.eisop.runtimeframework.core.CheckGenerator; -import io.github.eisop.runtimeframework.runtime.AttributionKind; -import java.lang.classfile.CodeBuilder; -import java.lang.classfile.TypeKind; -import java.lang.constant.ClassDesc; -import java.lang.constant.MethodTypeDesc; - -public class NullnessCheckGenerator implements CheckGenerator { - - private static final ClassDesc VERIFIER = ClassDesc.of(NullnessRuntimeVerifier.class.getName()); - private static final ClassDesc ATTRIBUTION_KIND = ClassDesc.of(AttributionKind.class.getName()); - - private static final String METHOD_DEFAULT = "checkNotNull"; - private static final MethodTypeDesc DESC_DEFAULT = - MethodTypeDesc.ofDescriptor("(Ljava/lang/Object;Ljava/lang/String;)V"); - - private static final String METHOD_ATTRIBUTED = "checkNotNull"; - private static final MethodTypeDesc DESC_ATTRIBUTED = - MethodTypeDesc.ofDescriptor( - "(Ljava/lang/Object;Ljava/lang/String;Lio/github/eisop/runtimeframework/runtime/AttributionKind;)V"); - - private final AttributionKind attribution; - - public NullnessCheckGenerator() { - this(AttributionKind.LOCAL); - } - - public NullnessCheckGenerator(AttributionKind attribution) { - this.attribution = attribution; - } - - @Override - public CheckGenerator withAttribution(AttributionKind kind) { - return new NullnessCheckGenerator(kind); - } - - @Override - public void generateCheck(CodeBuilder b, TypeKind type, String diagnosticName) { - if (type == TypeKind.REFERENCE) { - b.ldc(diagnosticName + " must be NonNull"); - - if (attribution == AttributionKind.LOCAL) { - b.invokestatic(VERIFIER, METHOD_DEFAULT, DESC_DEFAULT); - } else { - b.getstatic( - ATTRIBUTION_KIND, - attribution.name(), - ClassDesc.ofDescriptor("Lio/github/eisop/runtimeframework/runtime/AttributionKind;")); - b.invokestatic(VERIFIER, METHOD_ATTRIBUTED, DESC_ATTRIBUTED); - } - } else { - if (type.slotSize() == 1) b.pop(); - else b.pop2(); - } - } -} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/agent/RuntimeAgent.java b/framework/src/main/java/io/github/eisop/runtimeframework/agent/RuntimeAgent.java index d934975..fc74e97 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/agent/RuntimeAgent.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/agent/RuntimeAgent.java @@ -5,7 +5,7 @@ import io.github.eisop.runtimeframework.filter.ClassListFilter; import io.github.eisop.runtimeframework.filter.Filter; import io.github.eisop.runtimeframework.filter.FrameworkSafetyFilter; -import io.github.eisop.runtimeframework.policy.DefaultRuntimePolicy; +import io.github.eisop.runtimeframework.policy.ScopeAwareRuntimePolicy; import io.github.eisop.runtimeframework.policy.RuntimePolicy; import io.github.eisop.runtimeframework.resolution.ResolutionEnvironment; import io.github.eisop.runtimeframework.runtime.RuntimeVerifier; @@ -59,7 +59,7 @@ public static void premain(String args, Instrumentation inst) { } RuntimePolicy policy = - new DefaultRuntimePolicy( + new ScopeAwareRuntimePolicy( safeFilter, checkedScopeFilter, isGlobalMode, diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/core/CheckGenerator.java b/framework/src/main/java/io/github/eisop/runtimeframework/core/CheckGenerator.java deleted file mode 100644 index 59a8bbb..0000000 --- a/framework/src/main/java/io/github/eisop/runtimeframework/core/CheckGenerator.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.eisop.runtimeframework.core; - -import io.github.eisop.runtimeframework.runtime.AttributionKind; -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 CheckGenerator { - - /** - * 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); - - /** - * Returns a verifier that attributes the violation according to the given strategy. - * - * @param kind The attribution strategy. - * @return A verifier with the specified attribution. - */ - default CheckGenerator withAttribution(AttributionKind kind) { - return this; - } -} 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 d32a4b2..b9334f7 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 @@ -2,14 +2,12 @@ import io.github.eisop.runtimeframework.instrumentation.EnforcementInstrumenter; import io.github.eisop.runtimeframework.instrumentation.RuntimeInstrumenter; -import io.github.eisop.runtimeframework.planning.SemanticsBackedEnforcementPlanner; +import io.github.eisop.runtimeframework.planning.ContractEnforcementPlanner; import io.github.eisop.runtimeframework.policy.RuntimePolicy; import io.github.eisop.runtimeframework.resolution.BytecodeHierarchyResolver; import io.github.eisop.runtimeframework.resolution.HierarchyResolver; import io.github.eisop.runtimeframework.resolution.ResolutionEnvironment; import io.github.eisop.runtimeframework.semantics.CheckerSemantics; -import io.github.eisop.runtimeframework.strategy.BoundaryStrategy; -import io.github.eisop.runtimeframework.strategy.InstrumentationStrategy; /** * Represents a specific type system or check to be enforced (e.g., Nullness, Immutability). This @@ -33,7 +31,7 @@ public RuntimeInstrumenter createInstrumenter( HierarchyResolver resolver = new BytecodeHierarchyResolver(info -> policy.isChecked(info), resolutionEnvironment); return new EnforcementInstrumenter( - new SemanticsBackedEnforcementPlanner(policy, semantics, resolutionEnvironment), + new ContractEnforcementPlanner(policy, semantics, resolutionEnvironment), resolver, semantics.emitter()); } @@ -46,23 +44,4 @@ public RuntimeInstrumenter createInstrumenter( public RuntimeInstrumenter getInstrumenter(RuntimePolicy policy) { return createInstrumenter(policy); } - - /** - * Helper method to create the instrumentation strategy based on the active policy. - * - * @param config The TypeSystemConfiguration for this checker. - * @param policy The active runtime policy. - * @return A configured InstrumentationStrategy. - */ - protected InstrumentationStrategy createStrategy( - TypeSystemConfiguration config, RuntimePolicy policy) { - return createStrategy(config, policy, ResolutionEnvironment.system()); - } - - protected InstrumentationStrategy createStrategy( - TypeSystemConfiguration config, - RuntimePolicy policy, - ResolutionEnvironment resolutionEnvironment) { - return new BoundaryStrategy(config, policy, resolutionEnvironment); - } } 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 deleted file mode 100644 index 27ed05c..0000000 --- a/framework/src/main/java/io/github/eisop/runtimeframework/core/TypeSystemConfiguration.java +++ /dev/null @@ -1,71 +0,0 @@ -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, CheckGenerator 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, CheckGenerator 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, CheckGenerator 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 deleted file mode 100644 index 6a228b5..0000000 --- a/framework/src/main/java/io/github/eisop/runtimeframework/core/ValidationKind.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.eisop.runtimeframework.core; - -/** Defines the type of validation logic to apply for a specific annotation. */ -public enum ValidationKind { - /** - * The qualifier requires runtime verification. The associated {@link CheckGenerator} will be - * invoked to generate the check logic. - */ - 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/instrumentation/EnforcementInstrumenter.java b/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementInstrumenter.java index d30aa6b..a28db52 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementInstrumenter.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementInstrumenter.java @@ -5,13 +5,10 @@ import io.github.eisop.runtimeframework.planning.ClassContext; import io.github.eisop.runtimeframework.planning.EnforcementPlanner; import io.github.eisop.runtimeframework.planning.InstrumentationAction; -import io.github.eisop.runtimeframework.planning.StrategyBackedEnforcementPlanner; -import io.github.eisop.runtimeframework.planning.ValueAccess; import io.github.eisop.runtimeframework.policy.ClassClassification; import io.github.eisop.runtimeframework.resolution.HierarchyResolver; import io.github.eisop.runtimeframework.resolution.ParentMethod; import io.github.eisop.runtimeframework.semantics.PropertyEmitter; -import io.github.eisop.runtimeframework.strategy.InstrumentationStrategy; import java.lang.classfile.ClassBuilder; import java.lang.classfile.ClassModel; import java.lang.classfile.CodeBuilder; @@ -29,11 +26,6 @@ public class EnforcementInstrumenter extends RuntimeInstrumenter { private final HierarchyResolver hierarchyResolver; private final PropertyEmitter propertyEmitter; - public EnforcementInstrumenter( - InstrumentationStrategy strategy, HierarchyResolver hierarchyResolver) { - this(new StrategyBackedEnforcementPlanner(strategy), hierarchyResolver, null); - } - public EnforcementInstrumenter(EnforcementPlanner planner, HierarchyResolver hierarchyResolver) { this(planner, hierarchyResolver, null); } @@ -116,8 +108,6 @@ private void emitBridgeActions(CodeBuilder builder, BridgePlan plan, BridgeActio private void emitBridgeAction(CodeBuilder builder, InstrumentationAction action) { switch (action) { - case InstrumentationAction.LegacyCheckAction legacyCheckAction -> - emitLegacyBridgeCheck(builder, legacyCheckAction); case InstrumentationAction.ValueCheckAction valueCheckAction -> emitValueCheckAction(builder, valueCheckAction); case InstrumentationAction.LifecycleHookAction ignored -> @@ -136,41 +126,6 @@ private void emitValueCheckAction( } } - private void emitLegacyBridgeCheck( - CodeBuilder builder, InstrumentationAction.LegacyCheckAction action) { - String diagnosticName = action.diagnostic().displayName(); - switch (action.valueAccess()) { - case ValueAccess.LocalSlot localSlot -> { - loadLocal(builder, action.valueType(), localSlot.slot()); - action.generator().generateCheck(builder, action.valueType(), diagnosticName); - } - case ValueAccess.ThisReference ignored -> { - builder.aload(0); - action.generator().generateCheck(builder, action.valueType(), diagnosticName); - } - case ValueAccess.OperandStack operandStack -> { - if (operandStack.depthFromTop() != 0) { - throw new IllegalStateException("Only top-of-stack access is currently supported"); - } - emitTopOfStackCheck(builder, action.valueType(), action); - } - case ValueAccess.FieldWriteValue ignored -> - throw new IllegalStateException( - "Legacy bridge actions do not support planner-native field-write access"); - } - } - - private void emitTopOfStackCheck( - CodeBuilder builder, TypeKind type, InstrumentationAction.LegacyCheckAction action) { - switch (type.slotSize()) { - case 1 -> builder.dup(); - case 2 -> builder.dup2(); - default -> - throw new IllegalStateException("Unsupported stack size for check emission: " + type); - } - action.generator().generateCheck(builder, type, action.diagnostic().displayName()); - } - private void loadLocal(CodeBuilder b, TypeKind type, int slot) { switch (type) { case INT, BYTE, CHAR, SHORT, BOOLEAN -> b.iload(slot); diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementTransform.java b/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementTransform.java index b8082ad..eadae77 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementTransform.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementTransform.java @@ -255,8 +255,7 @@ public void emitParameterChecks(CodeBuilder builder) { } } if (!events.isEmpty()) { - emitActions( - builder, planner.planMethod(methodContext, events), ActionTiming.METHOD_ENTRY, null); + emitActions(builder, planner.planMethod(methodContext, events), ActionTiming.METHOD_ENTRY); } } @@ -269,22 +268,19 @@ private boolean isFieldRead(FieldInstruction f) { } private void emitPlannedActions(CodeBuilder builder, FlowEvent event, ActionTiming timing) { - emitActions(builder, planner.planMethod(methodContext, List.of(event)), timing, event); + emitActions(builder, planner.planMethod(methodContext, List.of(event)), timing); } - private void emitActions( - CodeBuilder builder, MethodPlan plan, ActionTiming timing, FlowEvent event) { + private void emitActions(CodeBuilder builder, MethodPlan plan, ActionTiming timing) { for (InstrumentationAction action : plan.actions()) { if (timing.matches(action)) { - emitAction(builder, action, event); + emitAction(builder, action); } } } - private void emitAction(CodeBuilder builder, InstrumentationAction action, FlowEvent event) { + private void emitAction(CodeBuilder builder, InstrumentationAction action) { switch (action) { - case InstrumentationAction.LegacyCheckAction legacyCheckAction -> - emitLegacyCheckAction(builder, legacyCheckAction, event); case InstrumentationAction.ValueCheckAction valueCheckAction -> emitValueCheckAction(builder, valueCheckAction); case InstrumentationAction.LifecycleHookAction ignored -> @@ -303,83 +299,6 @@ private void emitValueCheckAction( } } - private void emitLegacyCheckAction( - CodeBuilder builder, InstrumentationAction.LegacyCheckAction action, FlowEvent event) { - String diagnosticName = action.diagnostic().displayName(); - switch (action.valueAccess()) { - case ValueAccess.LocalSlot localSlot -> { - loadLocal(builder, action.valueType(), localSlot.slot()); - action.generator().generateCheck(builder, action.valueType(), diagnosticName); - } - case ValueAccess.ThisReference ignored -> { - builder.aload(0); - action.generator().generateCheck(builder, action.valueType(), diagnosticName); - } - case ValueAccess.OperandStack operandStack -> { - if (operandStack.depthFromTop() != 0) { - throw new IllegalStateException("Only top-of-stack access is currently supported"); - } - if (event instanceof FlowEvent.FieldWrite fieldWrite) { - emitFieldWriteStackCheck( - builder, - action.valueType(), - action.generator(), - diagnosticName, - fieldWrite.isStaticAccess()); - } else { - emitTopOfStackCheck(builder, action.valueType(), action.generator(), diagnosticName); - } - } - case ValueAccess.FieldWriteValue ignored -> - throw new IllegalStateException( - "Legacy check actions do not support planner-native field-write access"); - } - } - - private void emitTopOfStackCheck( - CodeBuilder builder, - TypeKind type, - io.github.eisop.runtimeframework.core.CheckGenerator generator, - String diagnosticName) { - switch (type.slotSize()) { - case 1 -> builder.dup(); - case 2 -> builder.dup2(); - default -> - throw new IllegalStateException("Unsupported stack size for check emission: " + type); - } - generator.generateCheck(builder, type, diagnosticName); - } - - private void emitFieldWriteStackCheck( - CodeBuilder builder, - TypeKind type, - io.github.eisop.runtimeframework.core.CheckGenerator generator, - String diagnosticName, - boolean isStaticAccess) { - if (isStaticAccess) { - emitTopOfStackCheck(builder, type, generator, diagnosticName); - return; - } - - if (type.slotSize() != 1) { - throw new IllegalStateException("PUTFIELD check currently expects a single-slot value"); - } - builder.dup_x1(); - generator.generateCheck(builder, type, diagnosticName); - builder.swap(); - } - - private void loadLocal(CodeBuilder builder, TypeKind type, int slot) { - switch (type) { - case INT, BYTE, CHAR, SHORT, BOOLEAN -> builder.iload(slot); - case LONG -> builder.lload(slot); - case FLOAT -> builder.fload(slot); - case DOUBLE -> builder.dload(slot); - case REFERENCE -> builder.aload(slot); - default -> throw new IllegalArgumentException("Unsupported local load type: " + type); - } - } - private BytecodeLocation currentLocation() { return BytecodeLocation.at(currentBytecodeOffset, currentSourceLine); } diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/planning/SemanticsBackedEnforcementPlanner.java b/framework/src/main/java/io/github/eisop/runtimeframework/planning/ContractEnforcementPlanner.java similarity index 98% rename from framework/src/main/java/io/github/eisop/runtimeframework/planning/SemanticsBackedEnforcementPlanner.java rename to framework/src/main/java/io/github/eisop/runtimeframework/planning/ContractEnforcementPlanner.java index 82f67f4..05bae11 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/planning/SemanticsBackedEnforcementPlanner.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/planning/ContractEnforcementPlanner.java @@ -17,14 +17,14 @@ import java.util.Objects; import java.util.Optional; -/** Planner implementation backed by checker-owned semantic contract resolution. */ -public final class SemanticsBackedEnforcementPlanner implements EnforcementPlanner { +/** Planner implementation that resolves checker contracts into enforcement actions. */ +public final class ContractEnforcementPlanner implements EnforcementPlanner { private final RuntimePolicy policy; private final ContractResolver contracts; private final ResolutionEnvironment resolutionEnvironment; - public SemanticsBackedEnforcementPlanner( + public ContractEnforcementPlanner( RuntimePolicy policy, CheckerSemantics semantics, ResolutionEnvironment resolutionEnvironment) { diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/planning/InstrumentationAction.java b/framework/src/main/java/io/github/eisop/runtimeframework/planning/InstrumentationAction.java index 5dc9f3c..c2addbb 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/planning/InstrumentationAction.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/planning/InstrumentationAction.java @@ -1,15 +1,12 @@ package io.github.eisop.runtimeframework.planning; import io.github.eisop.runtimeframework.contracts.ValueContract; -import io.github.eisop.runtimeframework.core.CheckGenerator; import io.github.eisop.runtimeframework.runtime.AttributionKind; -import java.lang.classfile.TypeKind; import java.util.Objects; /** A concrete action emitted by the planner for later bytecode instrumentation. */ public sealed interface InstrumentationAction permits InstrumentationAction.ValueCheckAction, - InstrumentationAction.LegacyCheckAction, InstrumentationAction.LifecycleHookAction { InjectionPoint injectionPoint(); @@ -30,26 +27,6 @@ record ValueCheckAction( } } - /** - * Transitional action used while the planner is still backed by the legacy strategy/check - * generator pipeline. - */ - record LegacyCheckAction( - InjectionPoint injectionPoint, - ValueAccess valueAccess, - TypeKind valueType, - CheckGenerator generator, - DiagnosticSpec diagnostic) - implements InstrumentationAction { - public LegacyCheckAction { - Objects.requireNonNull(injectionPoint, "injectionPoint"); - Objects.requireNonNull(valueAccess, "valueAccess"); - Objects.requireNonNull(valueType, "valueType"); - Objects.requireNonNull(generator, "generator"); - Objects.requireNonNull(diagnostic, "diagnostic"); - } - } - record LifecycleHookAction( InjectionPoint injectionPoint, ValueAccess valueAccess, LifecycleHook hook) implements InstrumentationAction { diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/planning/StrategyBackedEnforcementPlanner.java b/framework/src/main/java/io/github/eisop/runtimeframework/planning/StrategyBackedEnforcementPlanner.java deleted file mode 100644 index 8c78cc3..0000000 --- a/framework/src/main/java/io/github/eisop/runtimeframework/planning/StrategyBackedEnforcementPlanner.java +++ /dev/null @@ -1,315 +0,0 @@ -package io.github.eisop.runtimeframework.planning; - -import io.github.eisop.runtimeframework.core.CheckGenerator; -import io.github.eisop.runtimeframework.filter.ClassInfo; -import io.github.eisop.runtimeframework.resolution.ParentMethod; -import io.github.eisop.runtimeframework.resolution.ResolutionEnvironment; -import io.github.eisop.runtimeframework.strategy.InstrumentationStrategy; -import java.lang.classfile.MethodModel; -import java.lang.classfile.TypeKind; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Compatibility adapter that expresses the current {@link InstrumentationStrategy} behavior through - * the new planner model. - */ -public final class StrategyBackedEnforcementPlanner implements EnforcementPlanner { - - private final InstrumentationStrategy strategy; - private final ResolutionEnvironment resolutionEnvironment; - - public StrategyBackedEnforcementPlanner(InstrumentationStrategy strategy) { - this(strategy, ResolutionEnvironment.system()); - } - - public StrategyBackedEnforcementPlanner( - InstrumentationStrategy strategy, ResolutionEnvironment resolutionEnvironment) { - this.strategy = Objects.requireNonNull(strategy, "strategy"); - this.resolutionEnvironment = - Objects.requireNonNull(resolutionEnvironment, "resolutionEnvironment"); - } - - @Override - public MethodPlan planMethod(MethodContext methodContext, List events) { - List actions = new ArrayList<>(); - for (FlowEvent event : events) { - actions.addAll(planEvent(event)); - } - return new MethodPlan(actions); - } - - @Override - public boolean shouldGenerateBridge(ClassContext classContext, ParentMethod parentMethod) { - return strategy.shouldGenerateBridge(parentMethod); - } - - @Override - public BridgePlan planBridge(ClassContext classContext, ParentMethod parentMethod) { - MethodModel method = parentMethod.method(); - List actions = new ArrayList<>(); - int slotIndex = 1; - - for (int i = 0; i < method.methodTypeSymbol().parameterList().size(); i++) { - TypeKind type = TypeKind.from(method.methodTypeSymbol().parameterList().get(i)); - CheckGenerator generator = strategy.getBridgeParameterCheck(parentMethod, i); - if (generator != null) { - actions.add( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.bridgeEntry(), - new ValueAccess.LocalSlot(slotIndex), - type, - generator, - DiagnosticSpec.of( - "Parameter " - + i - + " in inherited method " - + method.methodName().stringValue()))); - } - slotIndex += type.slotSize(); - } - - CheckGenerator returnGenerator = strategy.getBridgeReturnCheck(parentMethod); - if (returnGenerator != null) { - actions.add( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.bridgeExit(), - new ValueAccess.OperandStack(0), - TypeKind.REFERENCE, - returnGenerator, - DiagnosticSpec.of( - "Return value of inherited method " + method.methodName().stringValue()))); - } - - return new BridgePlan(parentMethod, actions); - } - - private List planEvent(FlowEvent event) { - return switch (event) { - case FlowEvent.MethodParameter methodParameter -> planMethodParameter(methodParameter); - case FlowEvent.MethodReturn methodReturn -> planMethodReturn(methodReturn); - case FlowEvent.BoundaryCallReturn boundaryCallReturn -> - planBoundaryCallReturn(boundaryCallReturn); - case FlowEvent.FieldRead fieldRead -> planFieldRead(fieldRead); - case FlowEvent.FieldWrite fieldWrite -> planFieldWrite(fieldWrite); - case FlowEvent.ArrayLoad arrayLoad -> planArrayLoad(arrayLoad); - case FlowEvent.ArrayStore arrayStore -> planArrayStore(arrayStore); - case FlowEvent.LocalStore localStore -> planLocalStore(localStore); - case FlowEvent.OverrideReturn overrideReturn -> planOverrideReturn(overrideReturn); - case FlowEvent.BridgeParameter ignored -> List.of(); - case FlowEvent.BridgeReturn ignored -> List.of(); - case FlowEvent.OverrideParameter ignored -> List.of(); - case FlowEvent.ConstructorEnter ignored -> List.of(); - case FlowEvent.ConstructorCommit ignored -> List.of(); - case FlowEvent.BoundaryReceiverUse ignored -> List.of(); - }; - } - - private List planMethodParameter(FlowEvent.MethodParameter event) { - TargetRef.MethodParameter target = event.target(); - TypeKind type = - TypeKind.from( - target.method().methodTypeSymbol().parameterList().get(target.parameterIndex())); - CheckGenerator generator = - strategy.getParameterCheck(target.method(), target.parameterIndex(), type); - if (generator == null) { - return List.of(); - } - - return List.of( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.methodEntry(), - new ValueAccess.LocalSlot(parameterSlot(target.method(), target.parameterIndex())), - type, - generator, - DiagnosticSpec.of("Parameter " + target.parameterIndex()))); - } - - private List planMethodReturn(FlowEvent.MethodReturn event) { - TypeKind type = TypeKind.from(event.target().method().methodTypeSymbol().returnType()); - CheckGenerator generator = strategy.getReturnCheck(event.target().method()); - if (generator == null) { - return List.of(); - } - - return List.of( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.normalReturn(event.location().bytecodeIndex()), - new ValueAccess.OperandStack(0), - type, - generator, - DiagnosticSpec.of( - "Return value of " + event.target().method().methodName().stringValue()))); - } - - private List planBoundaryCallReturn(FlowEvent.BoundaryCallReturn event) { - ClassLoader loader = loader(event.methodContext()); - TargetRef.InvokedMethod target = event.target(); - CheckGenerator generator = - strategy.getBoundaryCallCheck(target.ownerInternalName(), target.descriptor(), loader); - if (generator == null) { - return List.of(); - } - - return List.of( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.afterInstruction(event.location().bytecodeIndex()), - new ValueAccess.OperandStack(0), - TypeKind.from(target.descriptor().returnType()), - generator, - DiagnosticSpec.of("Return value of " + target.methodName() + " (Boundary)"))); - } - - private List planFieldRead(FlowEvent.FieldRead event) { - CheckGenerator generator = resolveFieldReadGenerator(event.methodContext(), event.target()); - TypeKind type = TypeKind.fromDescriptor(event.target().descriptor()); - if (generator == null || type.slotSize() != 1) { - return List.of(); - } - - return List.of( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.afterInstruction(event.location().bytecodeIndex()), - new ValueAccess.OperandStack(0), - type, - generator, - DiagnosticSpec.of("Read Field '" + event.target().fieldName() + "'"))); - } - - private List planFieldWrite(FlowEvent.FieldWrite event) { - CheckGenerator generator = resolveFieldWriteGenerator(event.methodContext(), event.target()); - TypeKind type = TypeKind.fromDescriptor(event.target().descriptor()); - if (generator == null) { - return List.of(); - } - - String displayName = - event.isStaticAccess() - ? "Static Field '" + event.target().fieldName() + "'" - : "Field '" + event.target().fieldName() + "'"; - - return List.of( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.beforeInstruction(event.location().bytecodeIndex()), - new ValueAccess.OperandStack(0), - type, - generator, - DiagnosticSpec.of(displayName))); - } - - private List planArrayLoad(FlowEvent.ArrayLoad event) { - CheckGenerator generator = strategy.getArrayLoadCheck(TypeKind.REFERENCE); - if (generator == null) { - return List.of(); - } - - return List.of( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.afterInstruction(event.location().bytecodeIndex()), - new ValueAccess.OperandStack(0), - TypeKind.REFERENCE, - generator, - DiagnosticSpec.of("Array Element Read"))); - } - - private List planArrayStore(FlowEvent.ArrayStore event) { - CheckGenerator generator = strategy.getArrayStoreCheck(TypeKind.REFERENCE); - if (generator == null) { - return List.of(); - } - - return List.of( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.beforeInstruction(event.location().bytecodeIndex()), - new ValueAccess.OperandStack(0), - TypeKind.REFERENCE, - generator, - DiagnosticSpec.of("Array Element Write"))); - } - - private List planLocalStore(FlowEvent.LocalStore event) { - TargetRef.Local target = event.target(); - CheckGenerator generator = - strategy.getLocalVariableWriteCheck(target.method(), target.slot(), TypeKind.REFERENCE); - if (generator == null) { - return List.of(); - } - - return List.of( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.beforeInstruction(event.location().bytecodeIndex()), - new ValueAccess.OperandStack(0), - TypeKind.REFERENCE, - generator, - DiagnosticSpec.of("Local Variable Assignment (Slot " + target.slot() + ")"))); - } - - private List planOverrideReturn(FlowEvent.OverrideReturn event) { - MethodContext methodContext = event.methodContext(); - CheckGenerator generator = - strategy.getUncheckedOverrideReturnCheck( - methodContext.classContext().classModel(), - methodContext.methodModel(), - loader(methodContext)); - if (generator == null) { - return List.of(); - } - - return List.of( - new InstrumentationAction.LegacyCheckAction( - InjectionPoint.normalReturn(event.location().bytecodeIndex()), - new ValueAccess.OperandStack(0), - TypeKind.REFERENCE, - generator, - DiagnosticSpec.of( - "Return value of overridden method " - + methodContext.methodModel().methodName().stringValue()))); - } - - private CheckGenerator resolveFieldReadGenerator( - MethodContext methodContext, TargetRef.Field target) { - TypeKind type = TypeKind.fromDescriptor(target.descriptor()); - String ownerInternalName = ownerInternalName(methodContext); - if (target.ownerInternalName().equals(ownerInternalName)) { - return resolutionEnvironment - .findDeclaredField(target.ownerInternalName(), target.fieldName(), loader(methodContext)) - .map(field -> strategy.getFieldReadCheck(field, type)) - .orElse(null); - } - return strategy.getBoundaryFieldReadCheck( - target.ownerInternalName(), target.fieldName(), type, loader(methodContext)); - } - - private CheckGenerator resolveFieldWriteGenerator( - MethodContext methodContext, TargetRef.Field target) { - TypeKind type = TypeKind.fromDescriptor(target.descriptor()); - String ownerInternalName = ownerInternalName(methodContext); - if (target.ownerInternalName().equals(ownerInternalName)) { - return resolutionEnvironment - .findDeclaredField(target.ownerInternalName(), target.fieldName(), loader(methodContext)) - .map(field -> strategy.getFieldWriteCheck(field, type)) - .orElse(null); - } - return strategy.getBoundaryFieldWriteCheck( - target.ownerInternalName(), target.fieldName(), type, loader(methodContext)); - } - - private static int parameterSlot(MethodModel method, int parameterIndex) { - int slotIndex = Modifier.isStatic(method.flags().flagsMask()) ? 0 : 1; - for (int i = 0; i < parameterIndex; i++) { - slotIndex += TypeKind.from(method.methodTypeSymbol().parameterList().get(i)).slotSize(); - } - return slotIndex; - } - - private static String ownerInternalName(MethodContext methodContext) { - return methodContext.classContext().classInfo().internalName(); - } - - private static ClassLoader loader(MethodContext methodContext) { - ClassInfo classInfo = methodContext.classContext().classInfo(); - return classInfo.loader(); - } -} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/policy/DefaultRuntimePolicy.java b/framework/src/main/java/io/github/eisop/runtimeframework/policy/ScopeAwareRuntimePolicy.java similarity index 96% rename from framework/src/main/java/io/github/eisop/runtimeframework/policy/DefaultRuntimePolicy.java rename to framework/src/main/java/io/github/eisop/runtimeframework/policy/ScopeAwareRuntimePolicy.java index aa8acf2..7f03f2b 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/policy/DefaultRuntimePolicy.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/policy/ScopeAwareRuntimePolicy.java @@ -7,8 +7,8 @@ import io.github.eisop.runtimeframework.resolution.ResolutionEnvironment; import java.lang.classfile.ClassModel; -/** Default policy implementation for checked-scope and global-mode behavior. */ -public final class DefaultRuntimePolicy implements RuntimePolicy { +/** Runtime policy implementation for checked-scope and global-mode behavior. */ +public final class ScopeAwareRuntimePolicy implements RuntimePolicy { private final Filter instrumentationSafetyFilter; private final Filter checkedScopeFilter; @@ -16,7 +16,7 @@ public final class DefaultRuntimePolicy implements RuntimePolicy { private final boolean trustAnnotatedFor; private final AnnotatedForFilter annotatedForFilter; - public DefaultRuntimePolicy( + public ScopeAwareRuntimePolicy( Filter instrumentationSafetyFilter, Filter checkedScopeFilter, boolean isGlobalMode, @@ -31,7 +31,7 @@ public DefaultRuntimePolicy( ResolutionEnvironment.system()); } - public DefaultRuntimePolicy( + public ScopeAwareRuntimePolicy( Filter instrumentationSafetyFilter, Filter checkedScopeFilter, boolean isGlobalMode, diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/strategy/BoundaryStrategy.java b/framework/src/main/java/io/github/eisop/runtimeframework/strategy/BoundaryStrategy.java deleted file mode 100644 index b36a449..0000000 --- a/framework/src/main/java/io/github/eisop/runtimeframework/strategy/BoundaryStrategy.java +++ /dev/null @@ -1,427 +0,0 @@ -package io.github.eisop.runtimeframework.strategy; - -import io.github.eisop.runtimeframework.core.CheckGenerator; -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.policy.RuntimePolicy; -import io.github.eisop.runtimeframework.resolution.ParentMethod; -import io.github.eisop.runtimeframework.resolution.ResolutionEnvironment; -import io.github.eisop.runtimeframework.runtime.AttributionKind; -import java.lang.classfile.Annotation; -import java.lang.classfile.Attributes; -import java.lang.classfile.ClassModel; -import java.lang.classfile.FieldModel; -import java.lang.classfile.MethodModel; -import java.lang.classfile.TypeAnnotation; -import java.lang.classfile.TypeKind; -import java.lang.constant.MethodTypeDesc; -import java.util.ArrayList; -import java.util.List; - -public class BoundaryStrategy implements InstrumentationStrategy { - - protected final TypeSystemConfiguration configuration; - protected final RuntimePolicy policy; - protected final ResolutionEnvironment resolutionEnvironment; - - public BoundaryStrategy(TypeSystemConfiguration configuration, RuntimePolicy policy) { - this(configuration, policy, ResolutionEnvironment.system()); - } - - public BoundaryStrategy( - TypeSystemConfiguration configuration, - RuntimePolicy policy, - ResolutionEnvironment resolutionEnvironment) { - this.configuration = configuration; - this.policy = policy; - this.resolutionEnvironment = resolutionEnvironment; - } - - protected CheckGenerator resolveGenerator(List annotations) { - for (Annotation a : annotations) { - String desc = a.classSymbol().descriptorString(); - 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; - } - } - } - - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return defaultEntry.verifier(); - } - - return null; - } - - @Override - public CheckGenerator getParameterCheck(MethodModel method, int paramIndex, TypeKind type) { - if (type != TypeKind.REFERENCE) return null; - List annos = getMethodParamAnnotations(method, paramIndex); - CheckGenerator generator = resolveGenerator(annos); - return (generator != null) ? generator.withAttribution(AttributionKind.CALLER) : null; - } - - @Override - public CheckGenerator getFieldWriteCheck(FieldModel field, TypeKind type) { - return null; - } - - @Override - public CheckGenerator getFieldReadCheck(FieldModel field, TypeKind type) { - if (type != TypeKind.REFERENCE) return null; - List annos = getFieldAnnotations(field); - return resolveGenerator(annos); - } - - @Override - public CheckGenerator getReturnCheck(MethodModel method) { - return null; - } - - @Override - public CheckGenerator getLocalVariableWriteCheck(MethodModel method, int slot, TypeKind type) { - if (type != TypeKind.REFERENCE) return null; - List annos = getLocalVariableAnnotations(method, slot); - return resolveGenerator(annos); - } - - @Override - public CheckGenerator getArrayStoreCheck(TypeKind componentType) { - if (componentType == TypeKind.REFERENCE) { - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return defaultEntry.verifier(); - } - } - return null; - } - - @Override - public CheckGenerator getArrayLoadCheck(TypeKind componentType) { - if (componentType == TypeKind.REFERENCE) { - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return defaultEntry.verifier(); - } - } - return null; - } - - @Override - public CheckGenerator getBoundaryFieldWriteCheck( - String owner, String fieldName, TypeKind type, ClassLoader loader) { - if (!policy.isGlobalMode() || type != TypeKind.REFERENCE) { - return null; - } - - if (policy.isChecked(new ClassInfo(owner, loader, null))) { - if (isFieldOptOut(owner, fieldName, loader)) { - return null; - } - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return defaultEntry.verifier(); - } - } - return null; - } - - @Override - public CheckGenerator getBoundaryCallCheck( - String owner, MethodTypeDesc desc, ClassLoader loader) { - TypeKind returnType = TypeKind.from(desc.returnType()); - - if (isUncheckedBoundaryOwner(owner, loader) && returnType == TypeKind.REFERENCE) { - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return defaultEntry.verifier(); - } - } - return null; - } - - @Override - public CheckGenerator getBoundaryFieldReadCheck( - String owner, String fieldName, TypeKind type, ClassLoader loader) { - if (isUncheckedBoundaryOwner(owner, loader) && type == TypeKind.REFERENCE) { - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return defaultEntry.verifier(); - } - } - return null; - } - - @Override - public boolean shouldGenerateBridge(ParentMethod parentMethod) { - if (parentMethod.owner().thisClass().asInternalName().equals("java/lang/Object")) return false; - - MethodModel method = parentMethod.method(); - var paramTypes = method.methodTypeSymbol().parameterList(); - - // 1. Check Parameters - for (int i = 0; i < paramTypes.size(); i++) { - boolean explicitNoop = false; - boolean explicitEnforce = false; - - List annos = getMethodParamAnnotations(method, i); - - for (Annotation anno : annos) { - String desc = anno.classSymbol().descriptorString(); - 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; - - TypeKind pType = TypeKind.from(paramTypes.get(i)); - if (pType == TypeKind.REFERENCE && !explicitNoop) { - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return true; - } - } - } - - TypeKind returnType = TypeKind.from(method.methodTypeSymbol().returnType()); - if (returnType == TypeKind.REFERENCE) { - boolean explicitNoop = false; - boolean explicitEnforce = false; - - List annos = getMethodReturnAnnotations(method); - - for (Annotation anno : annos) { - String desc = anno.classSymbol().descriptorString(); - 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 (!explicitNoop) { - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return true; - } - } - } - - return false; - } - - @Override - public CheckGenerator getBridgeParameterCheck(ParentMethod parentMethod, int paramIndex) { - MethodModel method = parentMethod.method(); - List annos = getMethodParamAnnotations(method, paramIndex); - - CheckGenerator generator = resolveGenerator(annos); - if (generator != null) return generator.withAttribution(AttributionKind.CALLER); - - // Check default - var paramTypes = method.methodTypeSymbol().parameterList(); - TypeKind pType = TypeKind.from(paramTypes.get(paramIndex)); - - if (pType == TypeKind.REFERENCE) { - boolean isExplicitNoop = false; - for (Annotation a : annos) { - TypeSystemConfiguration.ConfigEntry entry = - configuration.find(a.classSymbol().descriptorString()); - if (entry != null && entry.kind() == ValidationKind.NOOP) isExplicitNoop = true; - } - - if (!isExplicitNoop) { - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return defaultEntry.verifier().withAttribution(AttributionKind.CALLER); - } - } - } - return null; - } - - @Override - public CheckGenerator getBridgeReturnCheck(ParentMethod parentMethod) { - MethodModel method = parentMethod.method(); - TypeKind returnType = TypeKind.from(method.methodTypeSymbol().returnType()); - if (returnType != TypeKind.REFERENCE) return null; - - List annos = getMethodReturnAnnotations(method); - - CheckGenerator generator = resolveGenerator(annos); - if (generator != null) return generator.withAttribution(AttributionKind.CALLER); - - boolean isExplicitNoop = false; - for (Annotation a : annos) { - TypeSystemConfiguration.ConfigEntry entry = - configuration.find(a.classSymbol().descriptorString()); - if (entry != null && entry.kind() == ValidationKind.NOOP) isExplicitNoop = true; - } - - if (!isExplicitNoop) { - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return defaultEntry.verifier().withAttribution(AttributionKind.CALLER); - } - } - return null; - } - - @Override - public CheckGenerator getUncheckedOverrideReturnCheck( - ClassModel classModel, MethodModel method, ClassLoader loader) { - if (!policy.isGlobalMode()) { - return null; - } - - try { - var parentModelOpt = resolutionEnvironment.loadSuperclass(classModel, loader); - while (parentModelOpt.isPresent()) { - ClassModel parentModel = parentModelOpt.get(); - if (parentModel.thisClass().asInternalName().equals("java/lang/Object")) { - return null; - } - - if (policy.isChecked( - new ClassInfo(parentModel.thisClass().asInternalName(), loader, null), parentModel)) { - for (MethodModel m : parentModel.methods()) { - if (m.methodName().stringValue().equals(method.methodName().stringValue()) - && m.methodTypeSymbol() - .descriptorString() - .equals(method.methodTypeSymbol().descriptorString())) { - - if (hasNoopAnnotation(getMethodAnnotations(m)) - || hasNoopAnnotation(getMethodReturnAnnotations(m))) { - return null; - } - - TypeKind returnType = TypeKind.from(m.methodTypeSymbol().returnType()); - if (returnType == TypeKind.REFERENCE) { - TypeSystemConfiguration.ConfigEntry defaultEntry = configuration.getDefault(); - if (defaultEntry != null && defaultEntry.kind() == ValidationKind.ENFORCE) { - return defaultEntry.verifier(); - } - } - } - } - } - - parentModelOpt = resolutionEnvironment.loadSuperclass(parentModel, loader); - } - } catch (Throwable e) { - System.out.println("bytecode parsing fail in method override: " + e.getMessage()); - } - - return null; - } - - protected List getMethodAnnotations(MethodModel method) { - List result = new ArrayList<>(); - method - .findAttribute(Attributes.runtimeVisibleAnnotations()) - .ifPresent(attr -> result.addAll(attr.annotations())); - return result; - } - - protected List getMethodParamAnnotations(MethodModel method, int paramIndex) { - List result = new ArrayList<>(); - method - .findAttribute(Attributes.runtimeVisibleParameterAnnotations()) - .ifPresent( - attr -> { - List> all = attr.parameterAnnotations(); - if (paramIndex < all.size()) result.addAll(all.get(paramIndex)); - }); - method - .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) - .ifPresent( - attr -> { - for (TypeAnnotation ta : attr.annotations()) { - if (ta.targetInfo() instanceof TypeAnnotation.FormalParameterTarget pt - && pt.formalParameterIndex() == paramIndex) result.add(ta.annotation()); - } - }); - return result; - } - - protected List getMethodReturnAnnotations(MethodModel method) { - List result = new ArrayList<>(); - method - .findAttribute(Attributes.runtimeVisibleAnnotations()) - .ifPresent(attr -> result.addAll(attr.annotations())); - method - .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) - .ifPresent( - attr -> { - for (TypeAnnotation ta : attr.annotations()) { - if (ta.targetInfo().targetType() == TypeAnnotation.TargetType.METHOD_RETURN) { - result.add(ta.annotation()); - } - } - }); - return result; - } - - protected List getFieldAnnotations(FieldModel field) { - List result = new ArrayList<>(); - field - .findAttribute(Attributes.runtimeVisibleAnnotations()) - .ifPresent(attr -> result.addAll(attr.annotations())); - field - .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) - .ifPresent( - attr -> { - for (TypeAnnotation ta : attr.annotations()) { - if (ta.targetInfo().targetType() == TypeAnnotation.TargetType.FIELD) { - result.add(ta.annotation()); - } - } - }); - return result; - } - - protected List getLocalVariableAnnotations(MethodModel method, int slot) { - List result = new ArrayList<>(); - for (ResolutionEnvironment.LocalVariableTypeAnnotation localAnnotation : - resolutionEnvironment.getLocalVariableTypeAnnotations(method, slot)) { - result.add(localAnnotation.annotation()); - } - return result; - } - - private boolean isUncheckedBoundaryOwner(String owner, ClassLoader loader) { - return !policy.isChecked(new ClassInfo(owner, loader, null)); - } - - private boolean isFieldOptOut(String owner, String fieldName, ClassLoader loader) { - try { - return resolutionEnvironment - .findDeclaredField(owner, fieldName, loader) - .map(field -> hasNoopAnnotation(getFieldAnnotations(field))) - .orElse(false); - } catch (Throwable t) { - System.out.println("bytecode fail in is field opt out"); - } - return false; - } - - private boolean hasNoopAnnotation(List annotations) { - for (Annotation anno : annotations) { - String desc = anno.classSymbol().descriptorString(); - TypeSystemConfiguration.ConfigEntry entry = configuration.find(desc); - if (entry != null && entry.kind() == ValidationKind.NOOP) return true; - } - return false; - } -} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/strategy/InstrumentationStrategy.java b/framework/src/main/java/io/github/eisop/runtimeframework/strategy/InstrumentationStrategy.java deleted file mode 100644 index 9654db8..0000000 --- a/framework/src/main/java/io/github/eisop/runtimeframework/strategy/InstrumentationStrategy.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.github.eisop.runtimeframework.strategy; - -import io.github.eisop.runtimeframework.core.CheckGenerator; -import io.github.eisop.runtimeframework.resolution.ParentMethod; -import java.lang.classfile.ClassModel; -import java.lang.classfile.FieldModel; -import java.lang.classfile.MethodModel; -import java.lang.classfile.TypeKind; -import java.lang.constant.MethodTypeDesc; - -/** Defines the rules for WHEN to inject a runtime check. */ -public interface InstrumentationStrategy { - - /** Should we check this specific parameter at method entry? */ - CheckGenerator getParameterCheck(MethodModel method, int paramIndex, TypeKind type); - - /** Should we check a write to this field? */ - CheckGenerator getFieldWriteCheck(FieldModel field, TypeKind type); - - /** Should we check a read from this field? */ - CheckGenerator getFieldReadCheck(FieldModel field, TypeKind type); - - /** Should we check this return value? */ - CheckGenerator getReturnCheck(MethodModel method); - - /** - * Should we check a write to a field in an EXTERNAL class? (Used when Unchecked code writes to - * Checked code). - */ - default CheckGenerator getBoundaryFieldWriteCheck( - String owner, String fieldName, TypeKind type, ClassLoader loader) { - return null; - } - - /** We are calling a method on 'owner'. Should we check the result? */ - CheckGenerator getBoundaryCallCheck(String owner, MethodTypeDesc desc, ClassLoader loader); - - /** We are reading field from an EXTERNAL class. Should we check the value? */ - CheckGenerator getBoundaryFieldReadCheck( - String owner, String fieldName, TypeKind type, ClassLoader loader); - - /** Should we generate a bridge for this inherited method? */ - boolean shouldGenerateBridge(ParentMethod parentMethod); - - /** For a bridge we are generating, what check applies to this parameter? */ - CheckGenerator getBridgeParameterCheck(ParentMethod parentMethod, int paramIndex); - - /** For a bridge we are generating, what check applies to the return value? */ - default CheckGenerator getBridgeReturnCheck(ParentMethod parentMethod) { - return null; - } - - /** Should we check an value being stored into an array? */ - CheckGenerator getArrayStoreCheck(TypeKind componentType); - - /** Should we check a value being read from an array? */ - CheckGenerator getArrayLoadCheck(TypeKind componentType); - - /** Should we check a value being stored in a variable? */ - CheckGenerator getLocalVariableWriteCheck(MethodModel method, int slot, TypeKind type); - - /** Should we check the return of an unchecked override? */ - default CheckGenerator getUncheckedOverrideReturnCheck( - ClassModel classModel, MethodModel method, ClassLoader loader) { - return null; - } -} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/strategy/StrictBoundaryStrategy.java b/framework/src/main/java/io/github/eisop/runtimeframework/strategy/StrictBoundaryStrategy.java deleted file mode 100644 index 424095f..0000000 --- a/framework/src/main/java/io/github/eisop/runtimeframework/strategy/StrictBoundaryStrategy.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.eisop.runtimeframework.strategy; - -import io.github.eisop.runtimeframework.core.TypeSystemConfiguration; -import io.github.eisop.runtimeframework.policy.RuntimePolicy; - -/** - * Backward-compatible alias for boundary strategy behavior. - * - *

Global behavior is now policy-driven and implemented by {@link BoundaryStrategy}. - */ -public class StrictBoundaryStrategy extends BoundaryStrategy { - - public StrictBoundaryStrategy(TypeSystemConfiguration configuration, RuntimePolicy policy) { - super(configuration, policy); - } -} diff --git a/framework/src/test/java/io/github/eisop/runtimeframework/policy/DefaultRuntimePolicyTest.java b/framework/src/test/java/io/github/eisop/runtimeframework/policy/DefaultRuntimePolicyTest.java deleted file mode 100644 index c49cbe3..0000000 --- a/framework/src/test/java/io/github/eisop/runtimeframework/policy/DefaultRuntimePolicyTest.java +++ /dev/null @@ -1,156 +0,0 @@ -package io.github.eisop.runtimeframework.policy; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import io.github.eisop.runtimeframework.filter.ClassInfo; -import io.github.eisop.runtimeframework.filter.ClassListFilter; -import io.github.eisop.runtimeframework.filter.Filter; -import io.github.eisop.runtimeframework.policy.fixtures.AnnotatedFixture; -import io.github.eisop.runtimeframework.policy.fixtures.UnannotatedFixture; -import java.io.IOException; -import java.io.InputStream; -import java.lang.classfile.ClassFile; -import java.lang.classfile.ClassModel; -import org.junit.jupiter.api.Test; - -public class DefaultRuntimePolicyTest { - - @Test - public void standardModeWithoutScopeIsSkip() { - RuntimePolicy policy = - new DefaultRuntimePolicy(Filter.acceptAll(), Filter.rejectAll(), false, false, "nullness"); - - ClassInfo info = classInfo("com/app/Foo", DefaultRuntimePolicyTest.class.getClassLoader()); - assertEquals(ClassClassification.SKIP, policy.classify(info)); - } - - @Test - public void globalModeWithoutScopeIsUnchecked() { - RuntimePolicy policy = - new DefaultRuntimePolicy(Filter.acceptAll(), Filter.rejectAll(), true, false, "nullness"); - - ClassInfo info = classInfo("com/app/Foo", DefaultRuntimePolicyTest.class.getClassLoader()); - assertEquals(ClassClassification.UNCHECKED, policy.classify(info)); - } - - @Test - public void explicitCheckedListMatchesAsChecked() { - RuntimePolicy policy = - new DefaultRuntimePolicy( - Filter.acceptAll(), - new ClassListFilter(java.util.List.of("com.app.Foo")), - false, - false, - "nullness"); - - ClassInfo hit = classInfo("com/app/Foo", DefaultRuntimePolicyTest.class.getClassLoader()); - assertEquals(ClassClassification.CHECKED, policy.classify(hit)); - } - - @Test - public void explicitCheckedListMissesAsSkipInStandard() { - RuntimePolicy policy = - new DefaultRuntimePolicy( - Filter.acceptAll(), - new ClassListFilter(java.util.List.of("com.app.Foo")), - false, - false, - "nullness"); - - ClassInfo miss = classInfo("com/app/Bar", DefaultRuntimePolicyTest.class.getClassLoader()); - assertEquals(ClassClassification.SKIP, policy.classify(miss)); - } - - @Test - public void explicitCheckedListMissesAsUncheckedInGlobal() { - RuntimePolicy policy = - new DefaultRuntimePolicy( - Filter.acceptAll(), - new ClassListFilter(java.util.List.of("com.app.Foo")), - true, - false, - "nullness"); - - ClassInfo miss = classInfo("com/app/Bar", DefaultRuntimePolicyTest.class.getClassLoader()); - assertEquals(ClassClassification.UNCHECKED, policy.classify(miss)); - } - - @Test - public void trustAnnotatedForMarksAnnotatedClassAsChecked() throws IOException { - RuntimePolicy policy = - new DefaultRuntimePolicy(Filter.acceptAll(), Filter.rejectAll(), false, true, "nullness"); - - ClassModel model = parseClassModel(AnnotatedFixture.class); - ClassInfo info = - classInfo(internalName(AnnotatedFixture.class), AnnotatedFixture.class.getClassLoader()); - assertEquals(ClassClassification.CHECKED, policy.classify(info, model)); - } - - @Test - public void trustAnnotatedForLeavesUnannotatedClassSkippedInStandard() throws IOException { - RuntimePolicy policy = - new DefaultRuntimePolicy(Filter.acceptAll(), Filter.rejectAll(), false, true, "nullness"); - - ClassModel model = parseClassModel(UnannotatedFixture.class); - ClassInfo info = - classInfo( - internalName(UnannotatedFixture.class), UnannotatedFixture.class.getClassLoader()); - assertEquals(ClassClassification.SKIP, policy.classify(info, model)); - } - - @Test - public void trustAnnotatedForLeavesUnannotatedClassUncheckedInGlobal() throws IOException { - RuntimePolicy policy = - new DefaultRuntimePolicy(Filter.acceptAll(), Filter.rejectAll(), true, true, "nullness"); - - ClassModel model = parseClassModel(UnannotatedFixture.class); - ClassInfo info = - classInfo( - internalName(UnannotatedFixture.class), UnannotatedFixture.class.getClassLoader()); - assertEquals(ClassClassification.UNCHECKED, policy.classify(info, model)); - } - - @Test - public void loaderSensitiveCheckedScopeDifferentiatesSameClassName() { - ClassLoader trustedLoader = new ClassLoader() {}; - ClassLoader untrustedLoader = new ClassLoader() {}; - - Filter loaderSensitiveCheckedScope = - info -> "com/app/Same".equals(info.internalName()) && info.loader() == trustedLoader; - - RuntimePolicy policy = - new DefaultRuntimePolicy( - Filter.acceptAll(), loaderSensitiveCheckedScope, false, false, "nullness"); - - assertEquals( - ClassClassification.CHECKED, policy.classify(classInfo("com/app/Same", trustedLoader))); - assertEquals( - ClassClassification.SKIP, policy.classify(classInfo("com/app/Same", untrustedLoader))); - } - - @Test - public void safetyFilterAlwaysWins() { - RuntimePolicy policy = - new DefaultRuntimePolicy(Filter.rejectAll(), Filter.acceptAll(), true, true, "nullness"); - - ClassInfo info = classInfo("com/app/Foo", DefaultRuntimePolicyTest.class.getClassLoader()); - assertEquals(ClassClassification.SKIP, policy.classify(info)); - } - - private static ClassInfo classInfo(String internalName, ClassLoader loader) { - return new ClassInfo(internalName, loader, null); - } - - private static String internalName(Class clazz) { - return clazz.getName().replace('.', '/'); - } - - private static ClassModel parseClassModel(Class clazz) throws IOException { - String resourcePath = internalName(clazz) + ".class"; - try (InputStream is = clazz.getClassLoader().getResourceAsStream(resourcePath)) { - assertNotNull(is, "Could not load class bytes for " + clazz.getName()); - return ClassFile.of().parse(is.readAllBytes()); - } - } -} From f2ba0abf96bdbaed841b871adee4ad3b24ce85f3 Mon Sep 17 00:00:00 2001 From: Alex Cook Date: Thu, 16 Apr 2026 13:17:21 -0400 Subject: [PATCH 2/4] feat: add type meta data API for each checker --- .../nullness/NullnessContractResolver.java | 314 +----------------- .../checker/nullness/NullnessSemantics.java | 9 +- .../NullnessTypeMetadataResolver.java | 239 +++++++++++++ .../semantics/CheckerSemantics.java | 4 + .../semantics/TypeMetadataResolver.java | 36 ++ .../semantics/TypeUseMetadata.java | 67 ++++ .../semantics/TypeUseQualifier.java | 21 ++ 7 files changed, 391 insertions(+), 299 deletions(-) create mode 100644 checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessTypeMetadataResolver.java create mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeMetadataResolver.java create mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseMetadata.java create mode 100644 framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseQualifier.java diff --git a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessContractResolver.java b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessContractResolver.java index bc76e36..d880db1 100644 --- a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessContractResolver.java +++ b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessContractResolver.java @@ -4,17 +4,11 @@ import io.github.eisop.runtimeframework.contracts.PropertyRequirement; import io.github.eisop.runtimeframework.contracts.ValueContract; import io.github.eisop.runtimeframework.planning.TargetRef; -import io.github.eisop.runtimeframework.resolution.ResolutionEnvironment; import io.github.eisop.runtimeframework.semantics.ContractResolver; import io.github.eisop.runtimeframework.semantics.ResolutionContext; -import java.lang.classfile.Annotation; -import java.lang.classfile.Attributes; -import java.lang.classfile.FieldModel; -import java.lang.classfile.MethodModel; -import java.lang.classfile.TypeAnnotation; -import java.lang.constant.MethodTypeDesc; -import java.util.ArrayList; -import java.util.List; +import io.github.eisop.runtimeframework.semantics.TypeMetadataResolver; +import io.github.eisop.runtimeframework.semantics.TypeUseMetadata; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -25,309 +19,33 @@ public final class NullnessContractResolver implements ContractResolver { private static final String NULLABLE_DESC = Nullable.class.descriptorString(); private static final ValueContract NON_NULL_CONTRACT = ValueContract.of(PropertyRequirement.of(PropertyId.NON_NULL)); + private final TypeMetadataResolver typeMetadata; - @Override - public ValueContract resolve(TargetRef target, ResolutionContext context) { - return switch (target) { - case TargetRef.MethodParameter methodParameter -> resolveMethodParameter(methodParameter); - case TargetRef.MethodReturn methodReturn -> resolveMethodReturn(methodReturn); - case TargetRef.InvokedMethod invokedMethod -> resolveInvokedMethod(invokedMethod, context); - case TargetRef.Field field -> resolveField(field, context); - case TargetRef.ArrayComponent arrayComponent -> - resolveArrayComponent(arrayComponent, context); - case TargetRef.Local local -> resolveLocal(local, context); - case TargetRef.Receiver receiver -> NON_NULL_CONTRACT; - }; - } - - private ValueContract resolveMethodParameter(TargetRef.MethodParameter target) { - MethodTypeDesc descriptor = target.method().methodTypeSymbol(); - String parameterDescriptor = - descriptor.parameterList().get(target.parameterIndex()).descriptorString(); - if (!isReferenceDescriptor(parameterDescriptor)) { - return ValueContract.none(); - } - AnnotatedTypeUse typeUse = methodParameterTypeUse(target.method(), target.parameterIndex()); - return resolveAnnotations(typeUse.rootAnnotations(), true); - } - - private ValueContract resolveMethodReturn(TargetRef.MethodReturn target) { - String descriptor = target.method().methodTypeSymbol().returnType().descriptorString(); - if (!isReferenceDescriptor(descriptor)) { - return ValueContract.none(); - } - return resolveAnnotations(methodReturnTypeUse(target.method()).rootAnnotations(), true); - } - - private ValueContract resolveInvokedMethod( - TargetRef.InvokedMethod target, ResolutionContext context) { - String returnDescriptor = target.descriptor().returnType().descriptorString(); - if (!isReferenceDescriptor(returnDescriptor)) { - return ValueContract.none(); - } - - return resolveAnnotations( - context - .resolutionEnvironment() - .findDeclaredMethod( - target.ownerInternalName(), - target.methodName(), - target.descriptor().descriptorString(), - context.loader()) - .map(method -> methodReturnTypeUse(method).rootAnnotations()) - .orElse(List.of()), - true); - } - - private ValueContract resolveField(TargetRef.Field target, ResolutionContext context) { - if (!isReferenceDescriptor(target.descriptor())) { - return ValueContract.none(); - } - - return resolveAnnotations( - context - .resolutionEnvironment() - .findDeclaredField(target.ownerInternalName(), target.fieldName(), context.loader()) - .map(field -> fieldTypeUse(field).rootAnnotations()) - .orElse(List.of()), - true); - } - - private ValueContract resolveArrayComponent( - TargetRef.ArrayComponent target, ResolutionContext context) { - if (!isReferenceArrayComponent(target.arrayDescriptor())) { - return ValueContract.none(); - } - - AnnotatedTypeUse componentType = arrayComponentTypeUse(target, context); - if (componentType == null) { - return NON_NULL_CONTRACT; - } - return resolveAnnotations(componentType.rootAnnotations(), true); + public NullnessContractResolver() { + this(new NullnessTypeMetadataResolver()); } - private ValueContract resolveLocal(TargetRef.Local target, ResolutionContext context) { - AnnotatedTypeUse typeUse = localTypeUse(target, null, context); - return resolveAnnotations(typeUse.rootAnnotations(), true); + public NullnessContractResolver(TypeMetadataResolver typeMetadata) { + this.typeMetadata = Objects.requireNonNull(typeMetadata, "typeMetadata"); } - private AnnotatedTypeUse arrayComponentTypeUse( - TargetRef.ArrayComponent target, ResolutionContext context) { - AnnotatedTypeUse parentType = - arraySourceTypeUse(target.arrayTarget(), target.arrayDescriptor(), context); - if (parentType == null || !target.arrayDescriptor().startsWith("[")) { - return null; - } - - String componentDescriptor = target.arrayDescriptor().substring(1); - List rootAnnotations = new ArrayList<>(); - List remainingTypeAnnotations = new ArrayList<>(); - for (TypeUseAnnotation typeAnnotation : parentType.typeAnnotations()) { - if (!startsWithArrayStep(typeAnnotation.targetPath())) { - continue; - } - List remainingPath = - typeAnnotation.targetPath().subList(1, typeAnnotation.targetPath().size()); - if (remainingPath.isEmpty()) { - rootAnnotations.add(typeAnnotation.annotation()); - } - remainingTypeAnnotations.add( - new TypeUseAnnotation(typeAnnotation.annotation(), List.copyOf(remainingPath))); - } - return new AnnotatedTypeUse( - componentDescriptor, List.copyOf(rootAnnotations), List.copyOf(remainingTypeAnnotations)); - } - - private AnnotatedTypeUse arraySourceTypeUse( - TargetRef sourceTarget, String descriptorHint, ResolutionContext context) { - if (sourceTarget == null) { - return new AnnotatedTypeUse(descriptorHint, List.of(), List.of()); - } - - return switch (sourceTarget) { - case TargetRef.MethodParameter methodParameter -> - methodParameterTypeUse(methodParameter.method(), methodParameter.parameterIndex()); - case TargetRef.MethodReturn methodReturn -> methodReturnTypeUse(methodReturn.method()); - case TargetRef.InvokedMethod invokedMethod -> invokedMethodTypeUse(invokedMethod, context); - case TargetRef.Field field -> fieldTypeUse(field, context); - case TargetRef.ArrayComponent arrayComponent -> - arrayComponentTypeUse(arrayComponent, context); - case TargetRef.Local local -> localTypeUse(local, descriptorHint, context); - case TargetRef.Receiver receiver -> - new AnnotatedTypeUse("L" + receiver.ownerInternalName() + ";", List.of(), List.of()); - }; - } - - private AnnotatedTypeUse methodParameterTypeUse(MethodModel method, int parameterIndex) { - String descriptor = - method.methodTypeSymbol().parameterList().get(parameterIndex).descriptorString(); - List rootAnnotations = new ArrayList<>(); - List typeAnnotations = new ArrayList<>(); - - method - .findAttribute(Attributes.runtimeVisibleParameterAnnotations()) - .ifPresent( - attr -> { - List> all = attr.parameterAnnotations(); - if (parameterIndex < all.size()) { - rootAnnotations.addAll(all.get(parameterIndex)); - } - }); - method - .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) - .ifPresent( - attr -> { - for (TypeAnnotation typeAnnotation : attr.annotations()) { - if (typeAnnotation.targetInfo() - instanceof TypeAnnotation.FormalParameterTarget target - && target.formalParameterIndex() == parameterIndex) { - addTypeAnnotation(rootAnnotations, typeAnnotations, typeAnnotation); - } - } - }); - return new AnnotatedTypeUse( - descriptor, List.copyOf(rootAnnotations), List.copyOf(typeAnnotations)); - } - - private AnnotatedTypeUse methodReturnTypeUse(MethodModel method) { - String descriptor = method.methodTypeSymbol().returnType().descriptorString(); - List rootAnnotations = new ArrayList<>(); - List typeAnnotations = new ArrayList<>(); - - method - .findAttribute(Attributes.runtimeVisibleAnnotations()) - .ifPresent(attr -> rootAnnotations.addAll(attr.annotations())); - method - .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) - .ifPresent( - attr -> { - for (TypeAnnotation typeAnnotation : attr.annotations()) { - if (typeAnnotation.targetInfo().targetType() - == TypeAnnotation.TargetType.METHOD_RETURN) { - addTypeAnnotation(rootAnnotations, typeAnnotations, typeAnnotation); - } - } - }); - return new AnnotatedTypeUse( - descriptor, List.copyOf(rootAnnotations), List.copyOf(typeAnnotations)); - } - - private AnnotatedTypeUse invokedMethodTypeUse( - TargetRef.InvokedMethod target, ResolutionContext context) { - String descriptor = target.descriptor().returnType().descriptorString(); - return context - .resolutionEnvironment() - .findDeclaredMethod( - target.ownerInternalName(), - target.methodName(), - target.descriptor().descriptorString(), - context.loader()) - .map(this::methodReturnTypeUse) - .orElse(new AnnotatedTypeUse(descriptor, List.of(), List.of())); - } - - private AnnotatedTypeUse fieldTypeUse(TargetRef.Field target, ResolutionContext context) { - return context - .resolutionEnvironment() - .findDeclaredField(target.ownerInternalName(), target.fieldName(), context.loader()) - .map(this::fieldTypeUse) - .orElse(new AnnotatedTypeUse(target.descriptor(), List.of(), List.of())); - } - - private AnnotatedTypeUse fieldTypeUse(FieldModel field) { - String descriptor = field.fieldType().stringValue(); - List rootAnnotations = new ArrayList<>(); - List typeAnnotations = new ArrayList<>(); - - field - .findAttribute(Attributes.runtimeVisibleAnnotations()) - .ifPresent(attr -> rootAnnotations.addAll(attr.annotations())); - field - .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) - .ifPresent( - attr -> { - for (TypeAnnotation typeAnnotation : attr.annotations()) { - if (typeAnnotation.targetInfo().targetType() == TypeAnnotation.TargetType.FIELD) { - addTypeAnnotation(rootAnnotations, typeAnnotations, typeAnnotation); - } - } - }); - return new AnnotatedTypeUse( - descriptor, List.copyOf(rootAnnotations), List.copyOf(typeAnnotations)); - } - - private AnnotatedTypeUse localTypeUse( - TargetRef.Local target, String descriptorHint, ResolutionContext context) { - List rootAnnotations = new ArrayList<>(); - List typeAnnotations = new ArrayList<>(); - for (ResolutionEnvironment.LocalVariableTypeAnnotation localAnnotation : - context - .resolutionEnvironment() - .localsAt(target.method(), target.bytecodeIndex(), target.slot())) { - addTypeAnnotation(rootAnnotations, typeAnnotations, localAnnotation); - } - return new AnnotatedTypeUse( - descriptorHint != null ? descriptorHint : "Ljava/lang/Object;", - List.copyOf(rootAnnotations), - List.copyOf(typeAnnotations)); - } - - private void addTypeAnnotation( - List rootAnnotations, - List typeAnnotations, - TypeAnnotation typeAnnotation) { - List targetPath = List.copyOf(typeAnnotation.targetPath()); - if (targetPath.isEmpty()) { - rootAnnotations.add(typeAnnotation.annotation()); - } - typeAnnotations.add(new TypeUseAnnotation(typeAnnotation.annotation(), targetPath)); - } - - private void addTypeAnnotation( - List rootAnnotations, - List typeAnnotations, - ResolutionEnvironment.LocalVariableTypeAnnotation localAnnotation) { - List targetPath = List.copyOf(localAnnotation.targetPath()); - if (targetPath.isEmpty()) { - rootAnnotations.add(localAnnotation.annotation()); - } - typeAnnotations.add(new TypeUseAnnotation(localAnnotation.annotation(), targetPath)); + @Override + public ValueContract resolve(TargetRef target, ResolutionContext context) { + return resolveMetadata(typeMetadata.resolve(target, context)); } - private ValueContract resolveAnnotations(List annotations, boolean isReference) { - if (!isReference) { + private ValueContract resolveMetadata(TypeUseMetadata metadata) { + if (!metadata.isReferenceType()) { return ValueContract.none(); } - for (Annotation annotation : annotations) { - String descriptor = annotation.classSymbol().descriptorString(); - if (NULLABLE_DESC.equals(descriptor)) { + for (var qualifier : metadata.rootQualifiers()) { + if (NULLABLE_DESC.equals(qualifier.descriptor())) { return ValueContract.none(); } - if (NON_NULL_DESC.equals(descriptor)) { + if (NON_NULL_DESC.equals(qualifier.descriptor())) { return NON_NULL_CONTRACT; } } return NON_NULL_CONTRACT; } - - private boolean isReferenceDescriptor(String descriptor) { - return descriptor.startsWith("L") || descriptor.startsWith("["); - } - - private boolean isReferenceArrayComponent(String arrayDescriptor) { - return arrayDescriptor.startsWith("[L") || arrayDescriptor.startsWith("[["); - } - - private boolean startsWithArrayStep(List targetPath) { - return !targetPath.isEmpty() - && targetPath.get(0).typePathKind() == TypeAnnotation.TypePathComponent.Kind.ARRAY; - } - - private record AnnotatedTypeUse( - String descriptor, - List rootAnnotations, - List typeAnnotations) {} - - private record TypeUseAnnotation( - Annotation annotation, List targetPath) {} } diff --git a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessSemantics.java b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessSemantics.java index adc6c0a..617bbb3 100644 --- a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessSemantics.java +++ b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessSemantics.java @@ -3,11 +3,13 @@ import io.github.eisop.runtimeframework.semantics.CheckerSemantics; import io.github.eisop.runtimeframework.semantics.ContractResolver; import io.github.eisop.runtimeframework.semantics.PropertyEmitter; +import io.github.eisop.runtimeframework.semantics.TypeMetadataResolver; /** Nullness runtime semantics expressed through contracts and property emission. */ public final class NullnessSemantics implements CheckerSemantics { - private final ContractResolver contracts = new NullnessContractResolver(); + private final TypeMetadataResolver typeMetadata = new NullnessTypeMetadataResolver(); + private final ContractResolver contracts = new NullnessContractResolver(typeMetadata); private final PropertyEmitter emitter = new NullnessPropertyEmitter(); @Override @@ -15,6 +17,11 @@ public ContractResolver contracts() { return contracts; } + @Override + public TypeMetadataResolver typeMetadata() { + return typeMetadata; + } + @Override public PropertyEmitter emitter() { return emitter; diff --git a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessTypeMetadataResolver.java b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessTypeMetadataResolver.java new file mode 100644 index 0000000..fb73b1c --- /dev/null +++ b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessTypeMetadataResolver.java @@ -0,0 +1,239 @@ +package io.github.eisop.runtimeframework.checker.nullness; + +import io.github.eisop.runtimeframework.planning.TargetRef; +import io.github.eisop.runtimeframework.resolution.ResolutionEnvironment; +import io.github.eisop.runtimeframework.semantics.ResolutionContext; +import io.github.eisop.runtimeframework.semantics.TypeMetadataResolver; +import io.github.eisop.runtimeframework.semantics.TypeUseMetadata; +import io.github.eisop.runtimeframework.semantics.TypeUseQualifier; +import java.lang.classfile.Attributes; +import java.lang.classfile.FieldModel; +import java.lang.classfile.MethodModel; +import java.lang.classfile.TypeAnnotation; +import java.util.ArrayList; +import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Resolves nullness-specific type-use metadata, including default qualifiers. */ +public final class NullnessTypeMetadataResolver implements TypeMetadataResolver { + + private static final String NON_NULL_DESC = NonNull.class.descriptorString(); + private static final String NULLABLE_DESC = Nullable.class.descriptorString(); + + @Override + public TypeUseMetadata resolve(TargetRef target, ResolutionContext context) { + return switch (target) { + case TargetRef.MethodParameter methodParameter -> + applyNullnessDefault( + methodParameterTypeUse(methodParameter.method(), methodParameter.parameterIndex())); + case TargetRef.MethodReturn methodReturn -> + applyNullnessDefault(methodReturnTypeUse(methodReturn.method())); + case TargetRef.InvokedMethod invokedMethod -> + applyNullnessDefault(invokedMethodTypeUse(invokedMethod, context)); + case TargetRef.Field field -> applyNullnessDefault(fieldTypeUse(field, context)); + case TargetRef.ArrayComponent arrayComponent -> + applyNullnessDefault(arrayComponentTypeUse(arrayComponent, context)); + case TargetRef.Local local -> + applyNullnessDefault(localTypeUse(local, "Ljava/lang/Object;", context)); + case TargetRef.Receiver receiver -> + TypeUseMetadata.empty("L" + receiver.ownerInternalName() + ";") + .withRootQualifier(NON_NULL_DESC, true); + }; + } + + private TypeUseMetadata applyNullnessDefault(TypeUseMetadata metadata) { + if (!metadata.isReferenceType()) { + return metadata; + } + if (metadata.hasRootQualifier(NON_NULL_DESC) || metadata.hasRootQualifier(NULLABLE_DESC)) { + return metadata; + } + return metadata.withRootQualifier(NON_NULL_DESC, true); + } + + private TypeUseMetadata arrayComponentTypeUse( + TargetRef.ArrayComponent target, ResolutionContext context) { + if (!isReferenceArrayComponent(target.arrayDescriptor())) { + return TypeUseMetadata.empty( + target.arrayDescriptor().startsWith("[") + ? target.arrayDescriptor().substring(1) + : target.arrayDescriptor()); + } + + return arraySourceTypeUse(target.arrayTarget(), target.arrayDescriptor(), context) + .arrayComponent(); + } + + private TypeUseMetadata arraySourceTypeUse( + TargetRef sourceTarget, String descriptorHint, ResolutionContext context) { + if (sourceTarget == null) { + return TypeUseMetadata.empty(descriptorHint); + } + + return switch (sourceTarget) { + case TargetRef.MethodParameter methodParameter -> + methodParameterTypeUse(methodParameter.method(), methodParameter.parameterIndex()); + case TargetRef.MethodReturn methodReturn -> methodReturnTypeUse(methodReturn.method()); + case TargetRef.InvokedMethod invokedMethod -> invokedMethodTypeUse(invokedMethod, context); + case TargetRef.Field field -> fieldTypeUse(field, context); + case TargetRef.ArrayComponent arrayComponent -> arrayComponentTypeUse(arrayComponent, context); + case TargetRef.Local local -> localTypeUse(local, descriptorHint, context); + case TargetRef.Receiver receiver -> + TypeUseMetadata.empty("L" + receiver.ownerInternalName() + ";"); + }; + } + + private TypeUseMetadata methodParameterTypeUse(MethodModel method, int parameterIndex) { + String descriptor = + method.methodTypeSymbol().parameterList().get(parameterIndex).descriptorString(); + if (!isReferenceDescriptor(descriptor)) { + return TypeUseMetadata.empty(descriptor); + } + + List qualifiers = new ArrayList<>(); + method + .findAttribute(Attributes.runtimeVisibleParameterAnnotations()) + .ifPresent( + attr -> { + var all = attr.parameterAnnotations(); + if (parameterIndex < all.size()) { + all.get(parameterIndex).forEach(annotation -> addRootQualifier(qualifiers, annotation)); + } + }); + method + .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) + .ifPresent( + attr -> { + for (TypeAnnotation typeAnnotation : attr.annotations()) { + if (typeAnnotation.targetInfo() + instanceof TypeAnnotation.FormalParameterTarget target + && target.formalParameterIndex() == parameterIndex) { + addQualifier(qualifiers, typeAnnotation); + } + } + }); + return new TypeUseMetadata(descriptor, qualifiers); + } + + private TypeUseMetadata methodReturnTypeUse(MethodModel method) { + String descriptor = method.methodTypeSymbol().returnType().descriptorString(); + if (!isReferenceDescriptor(descriptor)) { + return TypeUseMetadata.empty(descriptor); + } + + List qualifiers = new ArrayList<>(); + method + .findAttribute(Attributes.runtimeVisibleAnnotations()) + .ifPresent(attr -> attr.annotations().forEach(annotation -> addRootQualifier(qualifiers, annotation))); + method + .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) + .ifPresent( + attr -> { + for (TypeAnnotation typeAnnotation : attr.annotations()) { + if (typeAnnotation.targetInfo().targetType() + == TypeAnnotation.TargetType.METHOD_RETURN) { + addQualifier(qualifiers, typeAnnotation); + } + } + }); + return new TypeUseMetadata(descriptor, qualifiers); + } + + private TypeUseMetadata invokedMethodTypeUse( + TargetRef.InvokedMethod target, ResolutionContext context) { + String descriptor = target.descriptor().returnType().descriptorString(); + if (!isReferenceDescriptor(descriptor)) { + return TypeUseMetadata.empty(descriptor); + } + + return context + .resolutionEnvironment() + .findDeclaredMethod( + target.ownerInternalName(), + target.methodName(), + target.descriptor().descriptorString(), + context.loader()) + .map(this::methodReturnTypeUse) + .orElse(TypeUseMetadata.empty(descriptor)); + } + + private TypeUseMetadata fieldTypeUse(TargetRef.Field target, ResolutionContext context) { + if (!isReferenceDescriptor(target.descriptor())) { + return TypeUseMetadata.empty(target.descriptor()); + } + + return context + .resolutionEnvironment() + .findDeclaredField(target.ownerInternalName(), target.fieldName(), context.loader()) + .map(this::fieldTypeUse) + .orElse(TypeUseMetadata.empty(target.descriptor())); + } + + private TypeUseMetadata fieldTypeUse(FieldModel field) { + String descriptor = field.fieldType().stringValue(); + if (!isReferenceDescriptor(descriptor)) { + return TypeUseMetadata.empty(descriptor); + } + + List qualifiers = new ArrayList<>(); + field + .findAttribute(Attributes.runtimeVisibleAnnotations()) + .ifPresent(attr -> attr.annotations().forEach(annotation -> addRootQualifier(qualifiers, annotation))); + field + .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) + .ifPresent( + attr -> { + for (TypeAnnotation typeAnnotation : attr.annotations()) { + if (typeAnnotation.targetInfo().targetType() == TypeAnnotation.TargetType.FIELD) { + addQualifier(qualifiers, typeAnnotation); + } + } + }); + return new TypeUseMetadata(descriptor, qualifiers); + } + + private TypeUseMetadata localTypeUse( + TargetRef.Local target, String descriptorHint, ResolutionContext context) { + List qualifiers = new ArrayList<>(); + for (ResolutionEnvironment.LocalVariableTypeAnnotation localAnnotation : + context + .resolutionEnvironment() + .localsAt(target.method(), target.bytecodeIndex(), target.slot())) { + addQualifier(qualifiers, localAnnotation); + } + return new TypeUseMetadata(descriptorHint, qualifiers); + } + + private void addRootQualifier( + List qualifiers, java.lang.classfile.Annotation annotation) { + qualifiers.add( + new TypeUseQualifier(annotation.classSymbol().descriptorString(), List.of(), false)); + } + + private void addQualifier(List qualifiers, TypeAnnotation typeAnnotation) { + qualifiers.add( + new TypeUseQualifier( + typeAnnotation.annotation().classSymbol().descriptorString(), + List.copyOf(typeAnnotation.targetPath()), + false)); + } + + private void addQualifier( + List qualifiers, + ResolutionEnvironment.LocalVariableTypeAnnotation localAnnotation) { + qualifiers.add( + new TypeUseQualifier( + localAnnotation.annotation().classSymbol().descriptorString(), + List.copyOf(localAnnotation.targetPath()), + false)); + } + + private boolean isReferenceDescriptor(String descriptor) { + return descriptor.startsWith("L") || descriptor.startsWith("["); + } + + private boolean isReferenceArrayComponent(String arrayDescriptor) { + return arrayDescriptor.startsWith("[L") || arrayDescriptor.startsWith("[["); + } +} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/semantics/CheckerSemantics.java b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/CheckerSemantics.java index 3ac1e1e..f94c60e 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/semantics/CheckerSemantics.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/CheckerSemantics.java @@ -7,6 +7,10 @@ public interface CheckerSemantics { PropertyEmitter emitter(); + default TypeMetadataResolver typeMetadata() { + return TypeMetadataResolver.none(); + } + default LifecycleSemantics lifecycle() { return LifecycleSemantics.none(); } diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeMetadataResolver.java b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeMetadataResolver.java new file mode 100644 index 0000000..e1087ec --- /dev/null +++ b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeMetadataResolver.java @@ -0,0 +1,36 @@ +package io.github.eisop.runtimeframework.semantics; + +import io.github.eisop.runtimeframework.planning.TargetRef; + +/** Resolves checker-specific type-use metadata for runtime flow targets. */ +public interface TypeMetadataResolver { + + TypeUseMetadata resolve(TargetRef target, ResolutionContext context); + + static TypeMetadataResolver none() { + return (target, context) -> TypeUseMetadata.empty(descriptorOf(target)); + } + + private static String descriptorOf(TargetRef target) { + return switch (target) { + case TargetRef.MethodParameter methodParameter -> + methodParameter + .method() + .methodTypeSymbol() + .parameterList() + .get(methodParameter.parameterIndex()) + .descriptorString(); + case TargetRef.MethodReturn methodReturn -> + methodReturn.method().methodTypeSymbol().returnType().descriptorString(); + case TargetRef.InvokedMethod invokedMethod -> + invokedMethod.descriptor().returnType().descriptorString(); + case TargetRef.Field field -> field.descriptor(); + case TargetRef.ArrayComponent arrayComponent -> + arrayComponent.arrayDescriptor().startsWith("[") + ? arrayComponent.arrayDescriptor().substring(1) + : arrayComponent.arrayDescriptor(); + case TargetRef.Local ignored -> "Ljava/lang/Object;"; + case TargetRef.Receiver receiver -> "L" + receiver.ownerInternalName() + ";"; + }; + } +} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseMetadata.java b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseMetadata.java new file mode 100644 index 0000000..d83fc83 --- /dev/null +++ b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseMetadata.java @@ -0,0 +1,67 @@ +package io.github.eisop.runtimeframework.semantics; + +import java.lang.classfile.TypeAnnotation; +import java.util.List; +import java.util.Objects; + +/** Checker-owned metadata about qualifiers attached to a JVM type use. */ +public record TypeUseMetadata(String descriptor, List qualifiers) { + + public TypeUseMetadata { + Objects.requireNonNull(descriptor, "descriptor"); + qualifiers = List.copyOf(Objects.requireNonNull(qualifiers, "qualifiers")); + } + + public static TypeUseMetadata empty(String descriptor) { + return new TypeUseMetadata(descriptor, List.of()); + } + + public boolean isReferenceType() { + return descriptor.startsWith("L") || descriptor.startsWith("["); + } + + public List rootQualifiers() { + return qualifiers.stream().filter(TypeUseQualifier::isRoot).toList(); + } + + public boolean hasRootQualifier(String qualifierDescriptor) { + return rootQualifiers().stream() + .anyMatch(qualifier -> qualifier.descriptor().equals(qualifierDescriptor)); + } + + public TypeUseMetadata withRootQualifier(String qualifierDescriptor, boolean defaulted) { + return new TypeUseMetadata( + descriptor, + append( + qualifiers, new TypeUseQualifier(qualifierDescriptor, List.of(), defaulted))); + } + + public TypeUseMetadata arrayComponent() { + if (!descriptor.startsWith("[")) { + return empty(descriptor); + } + + String componentDescriptor = descriptor.substring(1); + List componentQualifiers = + qualifiers.stream() + .filter(qualifier -> startsWithArrayStep(qualifier.targetPath())) + .map( + qualifier -> + new TypeUseQualifier( + qualifier.descriptor(), + qualifier.targetPath().subList(1, qualifier.targetPath().size()), + qualifier.defaulted())) + .toList(); + return new TypeUseMetadata(componentDescriptor, componentQualifiers); + } + + private static boolean startsWithArrayStep(List targetPath) { + return !targetPath.isEmpty() + && targetPath.get(0).typePathKind() == TypeAnnotation.TypePathComponent.Kind.ARRAY; + } + + private static List append(List values, T value) { + return java.util.stream.Stream.concat(values.stream(), java.util.stream.Stream.of(value)) + .toList(); + } +} diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseQualifier.java b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseQualifier.java new file mode 100644 index 0000000..5e39d4f --- /dev/null +++ b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseQualifier.java @@ -0,0 +1,21 @@ +package io.github.eisop.runtimeframework.semantics; + +import java.lang.classfile.TypeAnnotation; +import java.util.List; +import java.util.Objects; + +/** A checker-relevant qualifier applied at a particular type-use path. */ +public record TypeUseQualifier( + String descriptor, + List targetPath, + boolean defaulted) { + + public TypeUseQualifier { + Objects.requireNonNull(descriptor, "descriptor"); + targetPath = List.copyOf(Objects.requireNonNull(targetPath, "targetPath")); + } + + public boolean isRoot() { + return targetPath.isEmpty(); + } +} From e02a381d29151f9f31344b8badc1425901baaf97 Mon Sep 17 00:00:00 2001 From: Alex Cook Date: Thu, 16 Apr 2026 13:21:04 -0400 Subject: [PATCH 3/4] chore: spotless --- .../nullness/NullnessTypeMetadataResolver.java | 14 ++++++++++---- .../eisop/runtimeframework/agent/RuntimeAgent.java | 2 +- .../instrumentation/EnforcementTransform.java | 2 -- .../planning/InstrumentationAction.java | 3 +-- .../semantics/TypeUseMetadata.java | 3 +-- .../semantics/TypeUseQualifier.java | 4 +--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessTypeMetadataResolver.java b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessTypeMetadataResolver.java index fb73b1c..98ea3b9 100644 --- a/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessTypeMetadataResolver.java +++ b/checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessTypeMetadataResolver.java @@ -77,7 +77,8 @@ private TypeUseMetadata arraySourceTypeUse( case TargetRef.MethodReturn methodReturn -> methodReturnTypeUse(methodReturn.method()); case TargetRef.InvokedMethod invokedMethod -> invokedMethodTypeUse(invokedMethod, context); case TargetRef.Field field -> fieldTypeUse(field, context); - case TargetRef.ArrayComponent arrayComponent -> arrayComponentTypeUse(arrayComponent, context); + case TargetRef.ArrayComponent arrayComponent -> + arrayComponentTypeUse(arrayComponent, context); case TargetRef.Local local -> localTypeUse(local, descriptorHint, context); case TargetRef.Receiver receiver -> TypeUseMetadata.empty("L" + receiver.ownerInternalName() + ";"); @@ -98,7 +99,8 @@ private TypeUseMetadata methodParameterTypeUse(MethodModel method, int parameter attr -> { var all = attr.parameterAnnotations(); if (parameterIndex < all.size()) { - all.get(parameterIndex).forEach(annotation -> addRootQualifier(qualifiers, annotation)); + all.get(parameterIndex) + .forEach(annotation -> addRootQualifier(qualifiers, annotation)); } }); method @@ -125,7 +127,9 @@ private TypeUseMetadata methodReturnTypeUse(MethodModel method) { List qualifiers = new ArrayList<>(); method .findAttribute(Attributes.runtimeVisibleAnnotations()) - .ifPresent(attr -> attr.annotations().forEach(annotation -> addRootQualifier(qualifiers, annotation))); + .ifPresent( + attr -> + attr.annotations().forEach(annotation -> addRootQualifier(qualifiers, annotation))); method .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) .ifPresent( @@ -179,7 +183,9 @@ private TypeUseMetadata fieldTypeUse(FieldModel field) { List qualifiers = new ArrayList<>(); field .findAttribute(Attributes.runtimeVisibleAnnotations()) - .ifPresent(attr -> attr.annotations().forEach(annotation -> addRootQualifier(qualifiers, annotation))); + .ifPresent( + attr -> + attr.annotations().forEach(annotation -> addRootQualifier(qualifiers, annotation))); field .findAttribute(Attributes.runtimeVisibleTypeAnnotations()) .ifPresent( diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/agent/RuntimeAgent.java b/framework/src/main/java/io/github/eisop/runtimeframework/agent/RuntimeAgent.java index fc74e97..4502efa 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/agent/RuntimeAgent.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/agent/RuntimeAgent.java @@ -5,8 +5,8 @@ import io.github.eisop.runtimeframework.filter.ClassListFilter; import io.github.eisop.runtimeframework.filter.Filter; import io.github.eisop.runtimeframework.filter.FrameworkSafetyFilter; -import io.github.eisop.runtimeframework.policy.ScopeAwareRuntimePolicy; import io.github.eisop.runtimeframework.policy.RuntimePolicy; +import io.github.eisop.runtimeframework.policy.ScopeAwareRuntimePolicy; import io.github.eisop.runtimeframework.resolution.ResolutionEnvironment; import io.github.eisop.runtimeframework.runtime.RuntimeVerifier; import io.github.eisop.runtimeframework.runtime.ViolationHandler; diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementTransform.java b/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementTransform.java index eadae77..9729dab 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementTransform.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/instrumentation/EnforcementTransform.java @@ -9,7 +9,6 @@ import io.github.eisop.runtimeframework.planning.MethodContext; import io.github.eisop.runtimeframework.planning.MethodPlan; import io.github.eisop.runtimeframework.planning.TargetRef; -import io.github.eisop.runtimeframework.planning.ValueAccess; import io.github.eisop.runtimeframework.policy.ClassClassification; import io.github.eisop.runtimeframework.semantics.PropertyEmitter; import java.lang.classfile.ClassModel; @@ -19,7 +18,6 @@ import java.lang.classfile.Instruction; import java.lang.classfile.MethodModel; import java.lang.classfile.Opcode; -import java.lang.classfile.TypeKind; import java.lang.classfile.instruction.ArrayLoadInstruction; import java.lang.classfile.instruction.ArrayStoreInstruction; import java.lang.classfile.instruction.FieldInstruction; diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/planning/InstrumentationAction.java b/framework/src/main/java/io/github/eisop/runtimeframework/planning/InstrumentationAction.java index c2addbb..7536fac 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/planning/InstrumentationAction.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/planning/InstrumentationAction.java @@ -6,8 +6,7 @@ /** A concrete action emitted by the planner for later bytecode instrumentation. */ public sealed interface InstrumentationAction - permits InstrumentationAction.ValueCheckAction, - InstrumentationAction.LifecycleHookAction { + permits InstrumentationAction.ValueCheckAction, InstrumentationAction.LifecycleHookAction { InjectionPoint injectionPoint(); diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseMetadata.java b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseMetadata.java index d83fc83..167b43a 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseMetadata.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseMetadata.java @@ -32,8 +32,7 @@ public boolean hasRootQualifier(String qualifierDescriptor) { public TypeUseMetadata withRootQualifier(String qualifierDescriptor, boolean defaulted) { return new TypeUseMetadata( descriptor, - append( - qualifiers, new TypeUseQualifier(qualifierDescriptor, List.of(), defaulted))); + append(qualifiers, new TypeUseQualifier(qualifierDescriptor, List.of(), defaulted))); } public TypeUseMetadata arrayComponent() { diff --git a/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseQualifier.java b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseQualifier.java index 5e39d4f..ee5e063 100644 --- a/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseQualifier.java +++ b/framework/src/main/java/io/github/eisop/runtimeframework/semantics/TypeUseQualifier.java @@ -6,9 +6,7 @@ /** A checker-relevant qualifier applied at a particular type-use path. */ public record TypeUseQualifier( - String descriptor, - List targetPath, - boolean defaulted) { + String descriptor, List targetPath, boolean defaulted) { public TypeUseQualifier { Objects.requireNonNull(descriptor, "descriptor"); From ed8bcb4eb7bebef4e2c4f90f199ee41c66fbfbf7 Mon Sep 17 00:00:00 2001 From: Alex Cook Date: Thu, 16 Apr 2026 13:24:34 -0400 Subject: [PATCH 4/4] test: remove old tests --- .../runtimeframework/policy/fixtures/AnnotatedFixture.java | 6 ------ .../policy/fixtures/UnannotatedFixture.java | 3 --- 2 files changed, 9 deletions(-) delete mode 100644 framework/src/test/java/io/github/eisop/runtimeframework/policy/fixtures/AnnotatedFixture.java delete mode 100644 framework/src/test/java/io/github/eisop/runtimeframework/policy/fixtures/UnannotatedFixture.java diff --git a/framework/src/test/java/io/github/eisop/runtimeframework/policy/fixtures/AnnotatedFixture.java b/framework/src/test/java/io/github/eisop/runtimeframework/policy/fixtures/AnnotatedFixture.java deleted file mode 100644 index 5187515..0000000 --- a/framework/src/test/java/io/github/eisop/runtimeframework/policy/fixtures/AnnotatedFixture.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.eisop.runtimeframework.policy.fixtures; - -import io.github.eisop.runtimeframework.qual.AnnotatedFor; - -@AnnotatedFor("nullness") -public class AnnotatedFixture {} diff --git a/framework/src/test/java/io/github/eisop/runtimeframework/policy/fixtures/UnannotatedFixture.java b/framework/src/test/java/io/github/eisop/runtimeframework/policy/fixtures/UnannotatedFixture.java deleted file mode 100644 index 13aff8d..0000000 --- a/framework/src/test/java/io/github/eisop/runtimeframework/policy/fixtures/UnannotatedFixture.java +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.eisop.runtimeframework.policy.fixtures; - -public class UnannotatedFixture {}