From ad287e691f1a97c31beb38906bf2098d20caeadb Mon Sep 17 00:00:00 2001 From: Su5eD Date: Sun, 11 Oct 2026 20:37:09 +0200 Subject: [PATCH 01/44] Move things around --- .../analysis/{tree => compare}/CodePaths.java | 8 +- .../{ => compare}/InsnComparator.java | 2 +- .../{ => compare}/InstructionMatcher.java | 2 +- .../{ => compare}/MethodLabelComparator.java | 6 +- .../AdvancedSourceInterpreter.java | 2 +- .../{selector => flow}/FrameUtil.java | 6 +- .../flow}/WrapOpAnalyzer.java | 3 +- .../analysis/locals/LocalVarAnalyzer.java | 19 ++-- .../locals}/LocalVariable.java | 2 +- .../analysis/locals/LocalVariableLookup.java | 2 +- .../{ => method}/InheritanceHandler.java | 6 +- .../analysis/method/MethodAnalyzer.java | 10 +- .../ctx => analysis/method}/MethodFinder.java | 8 +- .../ctx => analysis/method}/MethodHelper.java | 34 +++--- .../analysis/method/MethodInsnMatcher.java | 2 +- .../method}/WrapOpOriginalCall.java | 10 +- .../params/LayeredParamsDiffSnapshot.java | 10 +- .../params}/ParamDiffResolver.java | 12 ++- .../adapter/{env/ctx => api}/AuditTrail.java | 8 +- .../{types => api}/BytecodeFixerUpper.java | 7 +- .../{util/provider => api}/ClassLookup.java | 2 +- .../sinytra/adapter/api/ClassTransformer.java | 9 ++ .../{patch => api}/DynamicPatches.java | 17 ++- .../{transform/patch => api}/MethodPatch.java | 9 +- .../patch => api}/MethodPatchBuilder.java | 5 +- .../adapter/api/MethodTransformer.java | 8 ++ .../{env/ctx => api}/MixinClassGenerator.java | 2 +- .../provider => api}/MixinClassLookup.java | 2 +- .../{env/ctx => api}/PatchEnvironment.java | 7 +- .../adapter/{env/ctx => api}/PatchResult.java | 2 +- .../java/org/sinytra/adapter/api/Patcher.java | 57 ++++++++++ .../{env/ctx => api}/RefmapHolder.java | 2 +- .../{types => api}/SimpleTypeAdapter.java | 2 +- .../adapter/{patch => api}/TxPhase.java | 2 +- .../adapter/{types => api}/TypeAdapter.java | 2 +- .../provider => api}/ZipClassLookup.java | 2 +- .../locals => edit}/LVTSnapshot.java | 2 +- .../ctx => edit}/MixinClassGeneratorImpl.java | 9 +- .../param/InjectParameterTransform.java | 20 ++-- .../param/InlineParameterTransformer.java | 8 +- .../param/ModifyArgsOffsetUpgrader.java | 8 +- .../param/MoveParametersTransformer.java | 6 +- .../param/ParamTransformationUtil.java} | 101 ++++++++++-------- .../param/ParameterTransformer.java | 6 +- .../param/RemoveParameterTransformer.java | 8 +- .../param/ReplaceParametersTransformer.java | 16 +-- .../param/SubstituteParameterTransformer.java | 8 +- .../param/SwapParametersTransformer.java | 6 +- .../param/TransformParameters.java | 8 +- .../{env/util => mixin}/MixinUtil.java | 2 +- .../{env => mixin}/MockMixinRuntime.java | 0 .../{util => model}/MethodQualifier.java | 2 +- .../{env/ctx => model}/TargetPair.java | 2 +- .../adapter/{env/ctx => model}/Targets.java | 2 +- .../{env => model}/ann/AnnotationData.java | 6 +- .../ann}/AnnotationHandle.java | 2 +- .../ann}/AnnotationValueHandle.java | 2 +- .../adapter/{env => model}/ann/AtData.java | 18 ++-- .../{env => model}/ann/ClassTarget.java | 9 +- .../{env => model}/ann/ConstantData.java | 13 ++- .../{env/ctx => model/ann}/RefMapper.java | 2 +- .../adapter/{env => model}/ann/SliceData.java | 13 +-- .../config/BasePropertyContainer.java | 8 +- .../config/Configuration.java | 12 +-- .../config/ConfigurationImpl.java | 4 +- .../config/ConfigurationTemplates.java | 6 +- .../config/Configurations.java | 4 +- .../config/MutableConfiguration.java | 12 +-- .../config/MutablePropertyContainer.java | 6 +- .../config/PropertyContainer.java | 4 +- .../config/PropertyContainerTemplate.java | 2 +- .../{patch => model}/config/PropertyKey.java | 4 +- .../config/key/ControlKeys.java | 6 +- .../config/key/MixinKeys.java | 14 +-- .../config/key/SpecialKeys.java | 4 +- .../constant}/MixinAnnotationConstants.java | 2 +- .../constant}/MixinAnnotations.java | 2 +- .../constant}/TypeConstants.java | 2 +- .../{env => model}/param/Annotation.java | 2 +- .../{env => model}/param/Copiable.java | 2 +- .../param/MethodParameters.java | 4 +- .../{env => model}/param/Parameter.java | 4 +- .../adapter/model/param/Parameters.java | 62 +++++++++++ .../adapter/patch/processor/Processor.java | 10 -- .../adapter/patch/resolver/SubResolver.java | 11 -- .../{patch => pipeline}/MixinParser.java | 34 +++--- .../PatcherImpl.java} | 75 ++++--------- .../PipelineMethodTransformer.java | 41 ++++--- .../adapter/{patch => pipeline}/Recipe.java | 22 ++-- .../adapter/{patch => pipeline}/TxResult.java | 2 +- .../audit}/AuditTrailImpl.java | 11 +- .../{env/ctx => pipeline/audit}/Auditor.java | 2 +- .../audit}/MixinGroups.java | 2 +- .../cls/DynamicAnonClassIndexPatch.java | 16 +-- .../DynamicAnonymousShadowFieldTypePatch.java | 12 +-- .../{env => pipeline}/ctx/MixinContext.java | 24 +++-- .../{env => pipeline}/ctx/PatchContext.java | 4 +- .../ctx/PatchContextImpl.java | 3 +- .../ctx/PatchEnvironmentImpl.java | 16 ++- .../injector}/AbstractMixinType.java | 4 +- .../injector}/InjectMixin.java | 44 ++++---- .../injector}/MixinFlag.java | 2 +- .../injector}/MixinType.java | 18 ++-- .../injector}/MixinTypes.java | 4 +- .../injector}/ModifyArgMixin.java | 42 ++++---- .../injector}/ModifyExpressionValueMixin.java | 40 +++---- .../injector}/ModifyReturnValueMixin.java | 38 +++---- .../injector}/ModifyVariableMixin.java | 46 ++++---- .../injector}/RedirectMixin.java | 47 ++++---- .../injector}/WrapOperationMixin.java | 43 ++++---- .../manual}/ConfigurationMatcher.java | 6 +- .../manual}/MethodPatchBuilderImpl.java | 26 ++--- .../manual}/MethodPatchImpl.java | 9 +- .../manual}/MethodPatchProcessor.java | 14 +-- .../manual}/MethodPatchResolver.java | 17 +-- .../LocalCaptureUpgradeTransformer.java | 24 ++--- .../processor/DisableMixinProcessor.java | 10 +- .../processor/MixinTypeProcessor.java | 12 +-- .../processor/ParametersPostProcessor.java | 18 ++-- .../processor/ParametersProcessor.java | 24 ++--- .../adapter/pipeline/processor/Processor.java | 10 ++ .../processor/Processors.java | 6 +- .../processor/PropertyProcessor.java | 25 ++--- .../processor/ReturnTypeProcessor.java | 18 ++-- .../processor/SplitCancellationProcessor.java | 16 +-- .../processor/StaticAccessProcessor.java | 14 +-- .../processor/TargetMethodProcessor.java | 12 +-- .../extract/ExtractMixinProcessor.java | 32 +++--- .../extract/MirrorableExtractMixin.java | 22 ++-- .../extract/ProxyExtractMixinSub.java | 24 ++--- .../redirect/DivertRedirectProcessor.java | 18 ++-- .../redirect/ParameterUsageProcessor.java | 22 ++-- .../wrapop/WrapOpParamsProcessor.java | 17 +-- .../processor/wrapop/WrapOpSurgeon.java | 13 +-- .../resolver/CompoundResolver.java | 8 +- .../resolver/Resolver.java | 8 +- .../resolver/Resolvers.java | 10 +- .../pipeline/resolver/SubResolver.java | 11 ++ .../ArbitraryInjectionPointSubResolver.java | 28 ++--- .../AtVariableAssignStoreSubResolver.java | 22 ++-- .../ComparingInjectionPointResolver.java | 43 ++++---- .../InheritedInjectionPointSubResolver.java | 34 +++--- .../injection/InjectionPointResolver.java | 14 +-- .../injection/InjectionPointSubResolvers.java | 32 +++--- .../ModifyVarInjectionPointSubResolver.java | 19 ++-- .../OverloadedInjectionPointSubResolver.java | 24 ++--- .../special/InjectorOrdinalResolver.java | 34 +++--- .../special/ModifyVarAtReturnResolver.java | 30 +++--- .../special/ModifyVarUpgradeResolver.java | 18 ++-- .../special/ResolverSyntheticInstanceof.java | 28 ++--- .../special/SliceBoundaryResolver.java | 24 ++--- .../target/SplitMethodCancellationHelper.java | 16 +-- .../target/SplitTargetMethodSubResolver.java | 24 ++--- .../resolver/target/TargetMethodResolver.java | 12 +-- .../target/TargetMethodSubResolvers.java | 22 ++-- .../adapter/transform/ClassTransformer.java | 10 -- .../adapter/transform/MethodTransformer.java | 9 -- .../param/ParamTransformationUtil.java | 71 ------------ .../types/FieldAccessorTypeTransformer.java | 34 ++++-- .../types/FieldTypeUsageTransformer.java | 12 ++- .../adapter/types/ObjectTypeAdapter.java | 3 +- .../adapter/types/SupplierTypeAdapter.java | 1 + .../adapter/types/TypeAdapterProvider.java | 1 + .../org/sinytra/adapter/util/AdapterUtil.java | 14 +-- .../{env => }/util/OrderedRegistry.java | 2 +- .../{env => }/util/WeighedDisambiguation.java | 3 +- .../mixin/BytecodeFixerUpperTestFrontend.java | 8 +- .../mixin/DynamicMixinPatchTest.java | 10 +- .../mixin/BytecodeFixerUpperTestFrontend.java | 8 +- .../mixin/DynamicMixinPatchTest.java | 10 +- .../mixin/MinecraftMixinPatchTest.java | 16 +-- .../patch/test/EnhancedParamsDiffTest.java | 6 +- .../patch/test/ParamDiffResolverTest.java | 6 +- 173 files changed, 1250 insertions(+), 1191 deletions(-) rename core/src/main/java/org/sinytra/adapter/analysis/{tree => compare}/CodePaths.java (96%) rename core/src/main/java/org/sinytra/adapter/analysis/{ => compare}/InsnComparator.java (99%) rename core/src/main/java/org/sinytra/adapter/analysis/{ => compare}/InstructionMatcher.java (97%) rename core/src/main/java/org/sinytra/adapter/analysis/{ => compare}/MethodLabelComparator.java (97%) rename core/src/main/java/org/sinytra/adapter/analysis/{method => flow}/AdvancedSourceInterpreter.java (98%) rename core/src/main/java/org/sinytra/adapter/analysis/{selector => flow}/FrameUtil.java (97%) rename core/src/main/java/org/sinytra/adapter/{patch/processor/wrapop => analysis/flow}/WrapOpAnalyzer.java (97%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => analysis/locals}/LocalVariable.java (65%) rename core/src/main/java/org/sinytra/adapter/analysis/{ => method}/InheritanceHandler.java (93%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => analysis/method}/MethodFinder.java (94%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => analysis/method}/MethodHelper.java (92%) rename core/src/main/java/org/sinytra/adapter/{patch/processor/wrapop => analysis/method}/WrapOpOriginalCall.java (94%) rename core/src/main/java/org/sinytra/adapter/{env/param => analysis/params}/ParamDiffResolver.java (84%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => api}/AuditTrail.java (89%) rename core/src/main/java/org/sinytra/adapter/{types => api}/BytecodeFixerUpper.java (91%) rename core/src/main/java/org/sinytra/adapter/{util/provider => api}/ClassLookup.java (94%) create mode 100644 core/src/main/java/org/sinytra/adapter/api/ClassTransformer.java rename core/src/main/java/org/sinytra/adapter/{patch => api}/DynamicPatches.java (60%) rename core/src/main/java/org/sinytra/adapter/{transform/patch => api}/MethodPatch.java (57%) rename core/src/main/java/org/sinytra/adapter/{transform/patch => api}/MethodPatchBuilder.java (90%) create mode 100644 core/src/main/java/org/sinytra/adapter/api/MethodTransformer.java rename core/src/main/java/org/sinytra/adapter/{env/ctx => api}/MixinClassGenerator.java (91%) rename core/src/main/java/org/sinytra/adapter/{util/provider => api}/MixinClassLookup.java (95%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => api}/PatchEnvironment.java (86%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => api}/PatchResult.java (89%) create mode 100644 core/src/main/java/org/sinytra/adapter/api/Patcher.java rename core/src/main/java/org/sinytra/adapter/{env/ctx => api}/RefmapHolder.java (77%) rename core/src/main/java/org/sinytra/adapter/{types => api}/SimpleTypeAdapter.java (92%) rename core/src/main/java/org/sinytra/adapter/{patch => api}/TxPhase.java (87%) rename core/src/main/java/org/sinytra/adapter/{types => api}/TypeAdapter.java (93%) rename core/src/main/java/org/sinytra/adapter/{util/provider => api}/ZipClassLookup.java (97%) rename core/src/main/java/org/sinytra/adapter/{analysis/locals => edit}/LVTSnapshot.java (98%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => edit}/MixinClassGeneratorImpl.java (90%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/InjectParameterTransform.java (87%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/InlineParameterTransformer.java (91%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/ModifyArgsOffsetUpgrader.java (90%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/MoveParametersTransformer.java (92%) rename core/src/main/java/org/sinytra/adapter/{env/param/Parameters.java => edit/param/ParamTransformationUtil.java} (54%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/ParameterTransformer.java (71%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/RemoveParameterTransformer.java (89%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/ReplaceParametersTransformer.java (92%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/SubstituteParameterTransformer.java (90%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/SwapParametersTransformer.java (90%) rename core/src/main/java/org/sinytra/adapter/{transform => edit}/param/TransformParameters.java (94%) rename core/src/main/java/org/sinytra/adapter/{env/util => mixin}/MixinUtil.java (96%) rename core/src/main/java/org/sinytra/adapter/{env => mixin}/MockMixinRuntime.java (100%) rename core/src/main/java/org/sinytra/adapter/{util => model}/MethodQualifier.java (98%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => model}/TargetPair.java (81%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => model}/Targets.java (63%) rename core/src/main/java/org/sinytra/adapter/{env => model}/ann/AnnotationData.java (88%) rename core/src/main/java/org/sinytra/adapter/{analysis/selector => model/ann}/AnnotationHandle.java (98%) rename core/src/main/java/org/sinytra/adapter/{analysis/selector => model/ann}/AnnotationValueHandle.java (98%) rename core/src/main/java/org/sinytra/adapter/{env => model}/ann/AtData.java (89%) rename core/src/main/java/org/sinytra/adapter/{env => model}/ann/ClassTarget.java (80%) rename core/src/main/java/org/sinytra/adapter/{env => model}/ann/ConstantData.java (75%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => model/ann}/RefMapper.java (64%) rename core/src/main/java/org/sinytra/adapter/{env => model}/ann/SliceData.java (78%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/BasePropertyContainer.java (95%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/Configuration.java (80%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/ConfigurationImpl.java (96%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/ConfigurationTemplates.java (81%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/Configurations.java (84%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/MutableConfiguration.java (90%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/MutablePropertyContainer.java (87%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/PropertyContainer.java (82%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/PropertyContainerTemplate.java (98%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/PropertyKey.java (97%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/key/ControlKeys.java (86%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/key/MixinKeys.java (90%) rename core/src/main/java/org/sinytra/adapter/{patch => model}/config/key/SpecialKeys.java (89%) rename core/src/main/java/org/sinytra/adapter/{env/util => model/constant}/MixinAnnotationConstants.java (96%) rename core/src/main/java/org/sinytra/adapter/{env/util => model/constant}/MixinAnnotations.java (98%) rename core/src/main/java/org/sinytra/adapter/{env/util => model/constant}/TypeConstants.java (95%) rename core/src/main/java/org/sinytra/adapter/{env => model}/param/Annotation.java (98%) rename core/src/main/java/org/sinytra/adapter/{env => model}/param/Copiable.java (53%) rename core/src/main/java/org/sinytra/adapter/{env => model}/param/MethodParameters.java (98%) rename core/src/main/java/org/sinytra/adapter/{env => model}/param/Parameter.java (95%) create mode 100644 core/src/main/java/org/sinytra/adapter/model/param/Parameters.java delete mode 100644 core/src/main/java/org/sinytra/adapter/patch/processor/Processor.java delete mode 100644 core/src/main/java/org/sinytra/adapter/patch/resolver/SubResolver.java rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/MixinParser.java (83%) rename core/src/main/java/org/sinytra/adapter/{patch/Patcher.java => pipeline/PatcherImpl.java} (70%) rename core/src/main/java/org/sinytra/adapter/{transform => pipeline}/PipelineMethodTransformer.java (87%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/Recipe.java (91%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/TxResult.java (65%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => pipeline/audit}/AuditTrailImpl.java (96%) rename core/src/main/java/org/sinytra/adapter/{env/ctx => pipeline/audit}/Auditor.java (69%) rename core/src/main/java/org/sinytra/adapter/{analysis => pipeline/audit}/MixinGroups.java (97%) rename core/src/main/java/org/sinytra/adapter/{transform => pipeline}/cls/DynamicAnonClassIndexPatch.java (88%) rename core/src/main/java/org/sinytra/adapter/{transform => pipeline}/cls/DynamicAnonymousShadowFieldTypePatch.java (89%) rename core/src/main/java/org/sinytra/adapter/{env => pipeline}/ctx/MixinContext.java (86%) rename core/src/main/java/org/sinytra/adapter/{env => pipeline}/ctx/PatchContext.java (77%) rename core/src/main/java/org/sinytra/adapter/{env => pipeline}/ctx/PatchContextImpl.java (93%) rename core/src/main/java/org/sinytra/adapter/{env => pipeline}/ctx/PatchEnvironmentImpl.java (86%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/AbstractMixinType.java (87%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/InjectMixin.java (70%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/MixinFlag.java (89%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/MixinType.java (57%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/MixinTypes.java (95%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/ModifyArgMixin.java (73%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/ModifyExpressionValueMixin.java (62%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/ModifyReturnValueMixin.java (67%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/ModifyVariableMixin.java (73%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/RedirectMixin.java (73%) rename core/src/main/java/org/sinytra/adapter/{patch/mixin => pipeline/injector}/WrapOperationMixin.java (64%) rename core/src/main/java/org/sinytra/adapter/{transform/patch => pipeline/manual}/ConfigurationMatcher.java (95%) rename core/src/main/java/org/sinytra/adapter/{transform/patch => pipeline/manual}/MethodPatchBuilderImpl.java (87%) rename core/src/main/java/org/sinytra/adapter/{transform/patch => pipeline/manual}/MethodPatchImpl.java (81%) rename core/src/main/java/org/sinytra/adapter/{transform/patch => pipeline/manual}/MethodPatchProcessor.java (52%) rename core/src/main/java/org/sinytra/adapter/{transform/patch => pipeline/manual}/MethodPatchResolver.java (80%) rename core/src/main/java/org/sinytra/adapter/{transform => pipeline}/preprocess/LocalCaptureUpgradeTransformer.java (86%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/DisableMixinProcessor.java (64%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/MixinTypeProcessor.java (75%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/ParametersPostProcessor.java (89%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/ParametersProcessor.java (68%) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/Processor.java rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/Processors.java (86%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/PropertyProcessor.java (70%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/ReturnTypeProcessor.java (83%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/SplitCancellationProcessor.java (56%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/StaticAccessProcessor.java (90%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/TargetMethodProcessor.java (80%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/extract/ExtractMixinProcessor.java (96%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/extract/MirrorableExtractMixin.java (90%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/extract/ProxyExtractMixinSub.java (80%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/redirect/DivertRedirectProcessor.java (78%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/redirect/ParameterUsageProcessor.java (85%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/wrapop/WrapOpParamsProcessor.java (90%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/processor/wrapop/WrapOpSurgeon.java (95%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/CompoundResolver.java (89%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/Resolver.java (84%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/Resolvers.java (56%) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/SubResolver.java rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/injection/ArbitraryInjectionPointSubResolver.java (85%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/injection/AtVariableAssignStoreSubResolver.java (87%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/injection/ComparingInjectionPointResolver.java (90%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/injection/InheritedInjectionPointSubResolver.java (74%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/injection/InjectionPointResolver.java (80%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/injection/InjectionPointSubResolvers.java (89%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/injection/ModifyVarInjectionPointSubResolver.java (82%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/injection/OverloadedInjectionPointSubResolver.java (79%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/special/InjectorOrdinalResolver.java (95%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/special/ModifyVarAtReturnResolver.java (86%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/special/ModifyVarUpgradeResolver.java (79%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/special/ResolverSyntheticInstanceof.java (89%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/special/SliceBoundaryResolver.java (84%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/target/SplitMethodCancellationHelper.java (93%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/target/SplitTargetMethodSubResolver.java (89%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/target/TargetMethodResolver.java (78%) rename core/src/main/java/org/sinytra/adapter/{patch => pipeline}/resolver/target/TargetMethodSubResolvers.java (91%) delete mode 100644 core/src/main/java/org/sinytra/adapter/transform/ClassTransformer.java delete mode 100644 core/src/main/java/org/sinytra/adapter/transform/MethodTransformer.java delete mode 100644 core/src/main/java/org/sinytra/adapter/transform/param/ParamTransformationUtil.java rename core/src/main/java/org/sinytra/adapter/{env => }/util/OrderedRegistry.java (98%) rename core/src/main/java/org/sinytra/adapter/{env => }/util/WeighedDisambiguation.java (95%) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/tree/CodePaths.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java similarity index 96% rename from core/src/main/java/org/sinytra/adapter/analysis/tree/CodePaths.java rename to core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java index 2a65ead4..855dfa74 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/tree/CodePaths.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.analysis.tree; +package org.sinytra.adapter.analysis.compare; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.jetbrains.annotations.Nullable; @@ -8,10 +8,10 @@ import org.objectweb.asm.tree.VarInsnNode; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.MethodQualifier; import java.util.*; import java.util.function.Predicate; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/InsnComparator.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/InsnComparator.java similarity index 99% rename from core/src/main/java/org/sinytra/adapter/analysis/InsnComparator.java rename to core/src/main/java/org/sinytra/adapter/analysis/compare/InsnComparator.java index 9d0ad3ff..2e0040a7 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/InsnComparator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/InsnComparator.java @@ -23,7 +23,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package org.sinytra.adapter.analysis; +package org.sinytra.adapter.analysis.compare; import org.objectweb.asm.tree.*; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/InstructionMatcher.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/InstructionMatcher.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/analysis/InstructionMatcher.java rename to core/src/main/java/org/sinytra/adapter/analysis/compare/InstructionMatcher.java index 6837237c..7f244ef0 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/InstructionMatcher.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/InstructionMatcher.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.analysis; +package org.sinytra.adapter.analysis.compare; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/MethodLabelComparator.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodLabelComparator.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/analysis/MethodLabelComparator.java rename to core/src/main/java/org/sinytra/adapter/analysis/compare/MethodLabelComparator.java index 946f21ec..0f6f657f 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/MethodLabelComparator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodLabelComparator.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.analysis; +package org.sinytra.adapter.analysis.compare; import com.mojang.datafixers.util.Pair; import org.jetbrains.annotations.Nullable; @@ -6,8 +6,8 @@ import org.objectweb.asm.tree.FrameNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.env.ctx.Targets; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.pipeline.Recipe; import java.util.*; import java.util.stream.Stream; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/AdvancedSourceInterpreter.java b/core/src/main/java/org/sinytra/adapter/analysis/flow/AdvancedSourceInterpreter.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/analysis/method/AdvancedSourceInterpreter.java rename to core/src/main/java/org/sinytra/adapter/analysis/flow/AdvancedSourceInterpreter.java index 39ec07c4..7fa35cd9 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/AdvancedSourceInterpreter.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/flow/AdvancedSourceInterpreter.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.analysis.method; +package org.sinytra.adapter.analysis.flow; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/selector/FrameUtil.java b/core/src/main/java/org/sinytra/adapter/analysis/flow/FrameUtil.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/analysis/selector/FrameUtil.java rename to core/src/main/java/org/sinytra/adapter/analysis/flow/FrameUtil.java index d95f4206..aa466854 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/selector/FrameUtil.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/flow/FrameUtil.java @@ -1,8 +1,8 @@ -package org.sinytra.adapter.analysis.selector; +package org.sinytra.adapter.analysis.flow; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InvokeDynamicInsnNode; import org.objectweb.asm.tree.MethodInsnNode; @@ -13,7 +13,7 @@ import org.objectweb.asm.tree.analysis.SourceInterpreter; import org.objectweb.asm.tree.analysis.SourceValue; import org.objectweb.asm.tree.analysis.Value; -import org.sinytra.adapter.analysis.method.AdvancedSourceInterpreter; + import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/flow/WrapOpAnalyzer.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpAnalyzer.java rename to core/src/main/java/org/sinytra/adapter/analysis/flow/WrapOpAnalyzer.java index f898d25a..8659e87d 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/flow/WrapOpAnalyzer.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.patch.processor.wrapop; +package org.sinytra.adapter.analysis.flow; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; @@ -6,7 +6,6 @@ import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.analysis.Frame; import org.objectweb.asm.tree.analysis.SourceValue; -import org.sinytra.adapter.analysis.selector.FrameUtil; import org.sinytra.adapter.util.AdapterUtil; import java.util.ArrayList; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVarAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVarAnalyzer.java index 7dfe7f82..0e9ee854 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVarAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVarAnalyzer.java @@ -9,18 +9,17 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.InstructionMatcher; +import org.sinytra.adapter.analysis.compare.InstructionMatcher; +import org.sinytra.adapter.analysis.method.MethodHelper; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; -import org.sinytra.adapter.env.ctx.LocalVariable; -import org.sinytra.adapter.env.ctx.MethodHelper; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.Parameter; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.transform.param.TransformParameters; +import org.sinytra.adapter.edit.param.TransformParameters; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.OpcodeUtil; import org.slf4j.Logger; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/LocalVariable.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariable.java similarity index 65% rename from core/src/main/java/org/sinytra/adapter/env/ctx/LocalVariable.java rename to core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariable.java index 1e79c497..85ae4cee 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/LocalVariable.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariable.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.analysis.locals; import org.objectweb.asm.Type; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java index 7ccaedcf..76a48c72 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java @@ -6,7 +6,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodHelper; import java.util.*; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/InheritanceHandler.java b/core/src/main/java/org/sinytra/adapter/analysis/method/InheritanceHandler.java similarity index 93% rename from core/src/main/java/org/sinytra/adapter/analysis/InheritanceHandler.java rename to core/src/main/java/org/sinytra/adapter/analysis/method/InheritanceHandler.java index 77b62b76..39a4bd27 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/InheritanceHandler.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/InheritanceHandler.java @@ -1,8 +1,8 @@ -package org.sinytra.adapter.analysis; +package org.sinytra.adapter.analysis.method; import org.objectweb.asm.tree.ClassNode; -import org.sinytra.adapter.util.provider.ClassLookup; -import org.sinytra.adapter.env.util.TypeConstants; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.model.constant.TypeConstants; import java.util.*; import java.util.concurrent.ConcurrentHashMap; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java index 727e00be..1b9e21ae 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java @@ -2,18 +2,18 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.analysis.*; -import org.sinytra.adapter.analysis.selector.FrameUtil; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.env.util.TypeConstants; +import org.sinytra.adapter.analysis.flow.FrameUtil; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.MethodQualifier; import org.sinytra.adapter.util.OpcodeUtil; -import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.BiConsumer; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/MethodFinder.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodFinder.java similarity index 94% rename from core/src/main/java/org/sinytra/adapter/env/ctx/MethodFinder.java rename to core/src/main/java/org/sinytra/adapter/analysis/method/MethodFinder.java index ee1bdcd7..d99c2a8d 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/MethodFinder.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodFinder.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.analysis.method; import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; @@ -7,8 +7,10 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.util.provider.ClassLookup; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; import org.slf4j.Logger; import java.util.Collection; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/MethodHelper.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java similarity index 92% rename from core/src/main/java/org/sinytra/adapter/env/ctx/MethodHelper.java rename to core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java index 16261ba7..8595312a 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/MethodHelper.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.analysis.method; import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; @@ -6,37 +6,39 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.locals.LocalVariable; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.MockMixinRuntime; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.param.ParamDiffResolver; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.patch.config.Configuration; +import org.sinytra.adapter.analysis.params.ParamDiffResolver; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.mixin.MockMixinRuntime; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.util.provider.ClassLookup; import org.slf4j.Logger; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.code.ISliceContext; import org.spongepowered.asm.mixin.injection.code.MethodSlice; import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.refmap.IMixinContext; -import org.spongepowered.asm.util.Locals; import java.util.*; import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.stream.Stream; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.*; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.*; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; public class MethodHelper { private static final Logger LOGGER = LogUtils.getLogger(); - private static final Object LOCALS_LOCK = new Object(); private final MixinContext context; private final MethodFinder methodFinder; @@ -276,11 +278,7 @@ public List getTargetMethodLocals(TargetPair target, int startPos return null; } // Get available local variables at the injection point in the target method - LocalVariableNode[] localVariables; - // Synchronize to avoid issues in mixin. This is necessary. - synchronized (LOCALS_LOCK) { - localVariables = Locals.getLocalsAt(target.classNode(), target.methodNode(), targetInsns.getFirst(), lvtCompatLevel); - } + LocalVariableNode[] localVariables = MockMixinRuntime.getLocalsAt(target, targetInsns.getFirst(), lvtCompatLevel); LocalVariable[] locals = Stream.of(localVariables) .filter(Objects::nonNull) .map(lv -> new LocalVariable(lv.index, Type.getType(lv.desc))) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodInsnMatcher.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodInsnMatcher.java index 6dc1df60..dc96595a 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodInsnMatcher.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodInsnMatcher.java @@ -3,8 +3,8 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LabelNode; -import org.sinytra.adapter.analysis.InstructionMatcher; import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.analysis.compare.InstructionMatcher; import java.util.List; import java.util.function.UnaryOperator; diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpOriginalCall.java b/core/src/main/java/org/sinytra/adapter/analysis/method/WrapOpOriginalCall.java similarity index 94% rename from core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpOriginalCall.java rename to core/src/main/java/org/sinytra/adapter/analysis/method/WrapOpOriginalCall.java index 29f92c47..2dd4b688 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpOriginalCall.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/WrapOpOriginalCall.java @@ -1,13 +1,13 @@ -package org.sinytra.adapter.patch.processor.wrapop; +package org.sinytra.adapter.analysis.method; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.util.TypeConstants; -import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.MethodQualifier; import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.flow.WrapOpAnalyzer; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.util.AdapterUtil; import java.util.ArrayList; import java.util.Collection; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/LayeredParamsDiffSnapshot.java b/core/src/main/java/org/sinytra/adapter/analysis/params/LayeredParamsDiffSnapshot.java index 20adfa61..7e03b25b 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/LayeredParamsDiffSnapshot.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/LayeredParamsDiffSnapshot.java @@ -3,7 +3,15 @@ import com.google.errorprone.annotations.CheckReturnValue; import org.objectweb.asm.Type; import org.objectweb.asm.commons.InstructionAdapter; -import org.sinytra.adapter.transform.param.*; +import org.sinytra.adapter.edit.param.InjectParameterTransform; +import org.sinytra.adapter.edit.param.InlineParameterTransformer; +import org.sinytra.adapter.edit.param.MoveParametersTransformer; +import org.sinytra.adapter.edit.param.ParameterTransformer; +import org.sinytra.adapter.edit.param.RemoveParameterTransformer; +import org.sinytra.adapter.edit.param.ReplaceParametersTransformer; +import org.sinytra.adapter.edit.param.SubstituteParameterTransformer; +import org.sinytra.adapter.edit.param.SwapParametersTransformer; +import org.sinytra.adapter.edit.param.TransformParameters; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/env/param/ParamDiffResolver.java b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java similarity index 84% rename from core/src/main/java/org/sinytra/adapter/env/param/ParamDiffResolver.java rename to core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java index 9af81e35..37cd8c1a 100644 --- a/core/src/main/java/org/sinytra/adapter/env/param/ParamDiffResolver.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java @@ -1,10 +1,16 @@ -package org.sinytra.adapter.env.param; +package org.sinytra.adapter.analysis.params; import it.unimi.dsi.fastutil.objects.ReferenceArrayList; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; -import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; -import org.sinytra.adapter.transform.param.*; +import org.sinytra.adapter.edit.param.InjectParameterTransform; +import org.sinytra.adapter.edit.param.InlineParameterTransformer; +import org.sinytra.adapter.edit.param.MoveParametersTransformer; +import org.sinytra.adapter.edit.param.ParameterTransformer; +import org.sinytra.adapter.edit.param.RemoveParameterTransformer; +import org.sinytra.adapter.edit.param.ReplaceParametersTransformer; +import org.sinytra.adapter.edit.param.SubstituteParameterTransformer; +import org.sinytra.adapter.edit.param.SwapParametersTransformer; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/AuditTrail.java b/core/src/main/java/org/sinytra/adapter/api/AuditTrail.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/env/ctx/AuditTrail.java rename to core/src/main/java/org/sinytra/adapter/api/AuditTrail.java index 28fc7490..75f7dd4b 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/AuditTrail.java +++ b/core/src/main/java/org/sinytra/adapter/api/AuditTrail.java @@ -1,11 +1,13 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.api; import it.unimi.dsi.fastutil.Pair; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.MixinGroups; -import org.sinytra.adapter.patch.config.Configuration; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.audit.AuditTrailImpl; +import org.sinytra.adapter.pipeline.audit.MixinGroups; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/types/BytecodeFixerUpper.java b/core/src/main/java/org/sinytra/adapter/api/BytecodeFixerUpper.java similarity index 91% rename from core/src/main/java/org/sinytra/adapter/types/BytecodeFixerUpper.java rename to core/src/main/java/org/sinytra/adapter/api/BytecodeFixerUpper.java index 8874ef54..3524871a 100644 --- a/core/src/main/java/org/sinytra/adapter/types/BytecodeFixerUpper.java +++ b/core/src/main/java/org/sinytra/adapter/api/BytecodeFixerUpper.java @@ -1,10 +1,13 @@ -package org.sinytra.adapter.types; +package org.sinytra.adapter.api; import com.mojang.datafixers.util.Pair; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.FieldNode; -import org.sinytra.adapter.util.provider.ClassLookup; +import org.sinytra.adapter.types.BytecodeFixerJarGenerator; +import org.sinytra.adapter.types.ObjectTypeAdapter; +import org.sinytra.adapter.types.SupplierTypeAdapter; +import org.sinytra.adapter.types.TypeAdapterProvider; import java.util.List; import java.util.Map; diff --git a/core/src/main/java/org/sinytra/adapter/util/provider/ClassLookup.java b/core/src/main/java/org/sinytra/adapter/api/ClassLookup.java similarity index 94% rename from core/src/main/java/org/sinytra/adapter/util/provider/ClassLookup.java rename to core/src/main/java/org/sinytra/adapter/api/ClassLookup.java index 1b6ba4ca..0e07a315 100644 --- a/core/src/main/java/org/sinytra/adapter/util/provider/ClassLookup.java +++ b/core/src/main/java/org/sinytra/adapter/api/ClassLookup.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.util.provider; +package org.sinytra.adapter.api; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; diff --git a/core/src/main/java/org/sinytra/adapter/api/ClassTransformer.java b/core/src/main/java/org/sinytra/adapter/api/ClassTransformer.java new file mode 100644 index 00000000..2deb8e99 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/api/ClassTransformer.java @@ -0,0 +1,9 @@ +package org.sinytra.adapter.api; + +import org.objectweb.asm.tree.ClassNode; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.pipeline.ctx.PatchContext; + +public interface ClassTransformer { + PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchContext context); +} diff --git a/core/src/main/java/org/sinytra/adapter/patch/DynamicPatches.java b/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java similarity index 60% rename from core/src/main/java/org/sinytra/adapter/patch/DynamicPatches.java rename to core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java index 65efa2e9..0b7a0cdc 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/DynamicPatches.java +++ b/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java @@ -1,20 +1,17 @@ -package org.sinytra.adapter.patch; +package org.sinytra.adapter.api; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; -import org.sinytra.adapter.transform.ClassTransformer; -import org.sinytra.adapter.transform.MethodTransformer; -import org.sinytra.adapter.transform.PipelineMethodTransformer; -import org.sinytra.adapter.transform.cls.DynamicAnonClassIndexPatch; -import org.sinytra.adapter.transform.cls.DynamicAnonymousShadowFieldTypePatch; -import org.sinytra.adapter.transform.patch.MethodPatch; -import org.sinytra.adapter.transform.preprocess.LocalCaptureUpgradeTransformer; +import org.sinytra.adapter.pipeline.PipelineMethodTransformer; +import org.sinytra.adapter.pipeline.cls.DynamicAnonClassIndexPatch; +import org.sinytra.adapter.pipeline.cls.DynamicAnonymousShadowFieldTypePatch; +import org.sinytra.adapter.pipeline.preprocess.LocalCaptureUpgradeTransformer; import org.sinytra.adapter.types.FieldAccessorTypeTransformer; import org.sinytra.adapter.types.FieldTypeUsageTransformer; import java.util.List; -public class DynamicPatches { +public final class DynamicPatches { public static final List CLASS_PATCHES = List.of( new DynamicAnonClassIndexPatch(), new DynamicAnonymousShadowFieldTypePatch(), @@ -28,4 +25,6 @@ TxPhase.EARLY, new LocalCaptureUpgradeTransformer(), TxPhase.VALIDATED, new PipelineMethodTransformer(patches, false) ); } + + private DynamicPatches() {} } diff --git a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatch.java b/core/src/main/java/org/sinytra/adapter/api/MethodPatch.java similarity index 57% rename from core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatch.java rename to core/src/main/java/org/sinytra/adapter/api/MethodPatch.java index 7f4aea90..f2ae7d87 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatch.java +++ b/core/src/main/java/org/sinytra/adapter/api/MethodPatch.java @@ -1,8 +1,9 @@ -package org.sinytra.adapter.transform.patch; +package org.sinytra.adapter.api; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.transform.MethodTransformer; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.pipeline.manual.ConfigurationMatcher; +import org.sinytra.adapter.pipeline.manual.MethodPatchBuilderImpl; import java.util.List; import java.util.function.BiConsumer; diff --git a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilder.java b/core/src/main/java/org/sinytra/adapter/api/MethodPatchBuilder.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilder.java rename to core/src/main/java/org/sinytra/adapter/api/MethodPatchBuilder.java index 8be5d2e2..daafaada 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilder.java +++ b/core/src/main/java/org/sinytra/adapter/api/MethodPatchBuilder.java @@ -1,9 +1,8 @@ -package org.sinytra.adapter.transform.patch; +package org.sinytra.adapter.api; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.commons.InstructionAdapter; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.transform.MethodTransformer; +import org.sinytra.adapter.model.param.MethodParameters; import java.util.function.Consumer; import java.util.function.UnaryOperator; diff --git a/core/src/main/java/org/sinytra/adapter/api/MethodTransformer.java b/core/src/main/java/org/sinytra/adapter/api/MethodTransformer.java new file mode 100644 index 00000000..5daf1af5 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/api/MethodTransformer.java @@ -0,0 +1,8 @@ +package org.sinytra.adapter.api; + +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +public interface MethodTransformer { + PatchResult apply(MixinContext context, Configuration config); +} diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/MixinClassGenerator.java b/core/src/main/java/org/sinytra/adapter/api/MixinClassGenerator.java similarity index 91% rename from core/src/main/java/org/sinytra/adapter/env/ctx/MixinClassGenerator.java rename to core/src/main/java/org/sinytra/adapter/api/MixinClassGenerator.java index 57bb1cf7..08e3efd4 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/MixinClassGenerator.java +++ b/core/src/main/java/org/sinytra/adapter/api/MixinClassGenerator.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.api; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.ClassNode; diff --git a/core/src/main/java/org/sinytra/adapter/util/provider/MixinClassLookup.java b/core/src/main/java/org/sinytra/adapter/api/MixinClassLookup.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/util/provider/MixinClassLookup.java rename to core/src/main/java/org/sinytra/adapter/api/MixinClassLookup.java index 805cb9cb..65151af7 100644 --- a/core/src/main/java/org/sinytra/adapter/util/provider/MixinClassLookup.java +++ b/core/src/main/java/org/sinytra/adapter/api/MixinClassLookup.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.util.provider; +package org.sinytra.adapter.api; import com.mojang.logging.LogUtils; import org.objectweb.asm.tree.ClassNode; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchEnvironment.java b/core/src/main/java/org/sinytra/adapter/api/PatchEnvironment.java similarity index 86% rename from core/src/main/java/org/sinytra/adapter/env/ctx/PatchEnvironment.java rename to core/src/main/java/org/sinytra/adapter/api/PatchEnvironment.java index e82698d0..7aa5d46f 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchEnvironment.java +++ b/core/src/main/java/org/sinytra/adapter/api/PatchEnvironment.java @@ -1,9 +1,8 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.api; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.analysis.InheritanceHandler; -import org.sinytra.adapter.types.BytecodeFixerUpper; -import org.sinytra.adapter.util.provider.ClassLookup; +import org.sinytra.adapter.analysis.method.InheritanceHandler; +import org.sinytra.adapter.pipeline.ctx.PatchEnvironmentImpl; import java.util.Collection; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchResult.java b/core/src/main/java/org/sinytra/adapter/api/PatchResult.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/env/ctx/PatchResult.java rename to core/src/main/java/org/sinytra/adapter/api/PatchResult.java index a083c60c..158b1c88 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchResult.java +++ b/core/src/main/java/org/sinytra/adapter/api/PatchResult.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.api; public enum PatchResult { PASS, diff --git a/core/src/main/java/org/sinytra/adapter/api/Patcher.java b/core/src/main/java/org/sinytra/adapter/api/Patcher.java new file mode 100644 index 00000000..c486012d --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/api/Patcher.java @@ -0,0 +1,57 @@ +package org.sinytra.adapter.api; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import org.objectweb.asm.tree.ClassNode; +import org.sinytra.adapter.pipeline.PatcherImpl; + +import java.util.ArrayList; +import java.util.List; + +/** + * Patches mixin classes so that they apply to the dirty environment. + */ +public interface Patcher { + /** + * Patch a mixin class in place. Classes that are not mixins are left unchanged. + */ + PatchResult process(ClassNode classNode); + + static Builder builder(PatchEnvironment environment) { + return new Builder(environment); + } + + class Builder { + private final PatchEnvironment environment; + private final List classTransformers = new ArrayList<>(); + private final Multimap methodTransformers = HashMultimap.create(); + + public Builder(PatchEnvironment environment) { + this.environment = environment; + } + + public Builder classTransformer(ClassTransformer transformer) { + this.classTransformers.add(transformer); + return this; + } + + public Builder classTransformers(List transformers) { + this.classTransformers.addAll(transformers); + return this; + } + + public Builder methodTransformer(TxPhase phase, MethodTransformer transformer) { + this.methodTransformers.put(phase, transformer); + return this; + } + + public Builder methodTransformers(Multimap transformers) { + this.methodTransformers.putAll(transformers); + return this; + } + + public Patcher build() { + return new PatcherImpl(this.environment, this.classTransformers, this.methodTransformers); + } + } +} diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/RefmapHolder.java b/core/src/main/java/org/sinytra/adapter/api/RefmapHolder.java similarity index 77% rename from core/src/main/java/org/sinytra/adapter/env/ctx/RefmapHolder.java rename to core/src/main/java/org/sinytra/adapter/api/RefmapHolder.java index 28befb0e..d63874ea 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/RefmapHolder.java +++ b/core/src/main/java/org/sinytra/adapter/api/RefmapHolder.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.api; public interface RefmapHolder { String remap(String cls, String reference); diff --git a/core/src/main/java/org/sinytra/adapter/types/SimpleTypeAdapter.java b/core/src/main/java/org/sinytra/adapter/api/SimpleTypeAdapter.java similarity index 92% rename from core/src/main/java/org/sinytra/adapter/types/SimpleTypeAdapter.java rename to core/src/main/java/org/sinytra/adapter/api/SimpleTypeAdapter.java index aded5018..94da6c47 100644 --- a/core/src/main/java/org/sinytra/adapter/types/SimpleTypeAdapter.java +++ b/core/src/main/java/org/sinytra/adapter/api/SimpleTypeAdapter.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.types; +package org.sinytra.adapter.api; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; diff --git a/core/src/main/java/org/sinytra/adapter/patch/TxPhase.java b/core/src/main/java/org/sinytra/adapter/api/TxPhase.java similarity index 87% rename from core/src/main/java/org/sinytra/adapter/patch/TxPhase.java rename to core/src/main/java/org/sinytra/adapter/api/TxPhase.java index cfb70222..066ffad5 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/TxPhase.java +++ b/core/src/main/java/org/sinytra/adapter/api/TxPhase.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.patch; +package org.sinytra.adapter.api; public enum TxPhase { /** diff --git a/core/src/main/java/org/sinytra/adapter/types/TypeAdapter.java b/core/src/main/java/org/sinytra/adapter/api/TypeAdapter.java similarity index 93% rename from core/src/main/java/org/sinytra/adapter/types/TypeAdapter.java rename to core/src/main/java/org/sinytra/adapter/api/TypeAdapter.java index a98a80ed..600652bf 100644 --- a/core/src/main/java/org/sinytra/adapter/types/TypeAdapter.java +++ b/core/src/main/java/org/sinytra/adapter/api/TypeAdapter.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.types; +package org.sinytra.adapter.api; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; diff --git a/core/src/main/java/org/sinytra/adapter/util/provider/ZipClassLookup.java b/core/src/main/java/org/sinytra/adapter/api/ZipClassLookup.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/util/provider/ZipClassLookup.java rename to core/src/main/java/org/sinytra/adapter/api/ZipClassLookup.java index 1e9f82b3..2ff14761 100644 --- a/core/src/main/java/org/sinytra/adapter/util/provider/ZipClassLookup.java +++ b/core/src/main/java/org/sinytra/adapter/api/ZipClassLookup.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.util.provider; +package org.sinytra.adapter.api; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LVTSnapshot.java b/core/src/main/java/org/sinytra/adapter/edit/LVTSnapshot.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/analysis/locals/LVTSnapshot.java rename to core/src/main/java/org/sinytra/adapter/edit/LVTSnapshot.java index 171af333..d960fdb2 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LVTSnapshot.java +++ b/core/src/main/java/org/sinytra/adapter/edit/LVTSnapshot.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.analysis.locals; +package org.sinytra.adapter.edit; import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; import it.unimi.dsi.fastutil.ints.Int2IntMap; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/MixinClassGeneratorImpl.java b/core/src/main/java/org/sinytra/adapter/edit/MixinClassGeneratorImpl.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/env/ctx/MixinClassGeneratorImpl.java rename to core/src/main/java/org/sinytra/adapter/edit/MixinClassGeneratorImpl.java index 808ce577..1e46f039 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/MixinClassGeneratorImpl.java +++ b/core/src/main/java/org/sinytra/adapter/edit/MixinClassGeneratorImpl.java @@ -1,13 +1,14 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.edit; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.util.provider.ClassLookup; -import org.sinytra.adapter.env.util.TypeConstants; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.MixinClassGenerator; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.constant.TypeConstants; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/InjectParameterTransform.java b/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java similarity index 87% rename from core/src/main/java/org/sinytra/adapter/transform/param/InjectParameterTransform.java rename to core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java index 9a334446..897d2ebc 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/InjectParameterTransform.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java @@ -1,23 +1,23 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.TypeReference; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.locals.LVTSnapshot; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.ctx.MethodHelper; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.mixin.MixinFlag; +import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.edit.LVTSnapshot; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.injector.MixinFlag; import org.sinytra.adapter.util.AdapterUtil; import java.util.Comparator; import java.util.List; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.PROPERTY_INDEX; -import static org.sinytra.adapter.transform.param.ParamTransformationUtil.getLVTIndex; +import static org.sinytra.adapter.edit.param.ParamTransformationUtil.getLVTIndex; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_INDEX; // TODO Cleanup param transformers public record InjectParameterTransform(int index, Type type) implements ParameterTransformer { diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/InlineParameterTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/InlineParameterTransformer.java similarity index 91% rename from core/src/main/java/org/sinytra/adapter/transform/param/InlineParameterTransformer.java rename to core/src/main/java/org/sinytra/adapter/edit/param/InlineParameterTransformer.java index cf68d073..a38948c8 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/InlineParameterTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/InlineParameterTransformer.java @@ -1,12 +1,12 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import com.mojang.logging.LogUtils; import org.objectweb.asm.Type; import org.objectweb.asm.commons.InstructionAdapter; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.analysis.locals.LVTSnapshot; -import org.sinytra.adapter.env.ctx.PatchResult; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.edit.LVTSnapshot; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/ModifyArgsOffsetUpgrader.java b/core/src/main/java/org/sinytra/adapter/edit/param/ModifyArgsOffsetUpgrader.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/transform/param/ModifyArgsOffsetUpgrader.java rename to core/src/main/java/org/sinytra/adapter/edit/param/ModifyArgsOffsetUpgrader.java index 75c6f7dd..235ce3db 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/ModifyArgsOffsetUpgrader.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ModifyArgsOffsetUpgrader.java @@ -1,14 +1,14 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.env.util.TypeConstants; -import java.util.Set; import java.util.LinkedHashSet; +import java.util.Set; public class ModifyArgsOffsetUpgrader { private static final MethodQualifier ARGS_GET = new MethodQualifier(TypeConstants.ARGS_TYPE.getDescriptor(), "get", "(I)Ljava/lang/Object;"); diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/MoveParametersTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/MoveParametersTransformer.java similarity index 92% rename from core/src/main/java/org/sinytra/adapter/transform/param/MoveParametersTransformer.java rename to core/src/main/java/org/sinytra/adapter/edit/param/MoveParametersTransformer.java index af0a59d9..2bff68fe 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/MoveParametersTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/MoveParametersTransformer.java @@ -1,11 +1,11 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import com.mojang.logging.LogUtils; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.PatchResult; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.slf4j.Logger; import java.util.ArrayList; diff --git a/core/src/main/java/org/sinytra/adapter/env/param/Parameters.java b/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java similarity index 54% rename from core/src/main/java/org/sinytra/adapter/env/param/Parameters.java rename to core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java index 246a08f6..ea760140 100644 --- a/core/src/main/java/org/sinytra/adapter/env/param/Parameters.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java @@ -1,72 +1,82 @@ -package org.sinytra.adapter.env.param; +package org.sinytra.adapter.edit.param; import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.transform.param.ParamTransformationUtil; import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.model.param.Annotation; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.util.AdapterUtil; import java.util.*; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; -public final class Parameters { - public static List parse(MethodNode method) { - return parse(method, -1, -1); +public final class ParamTransformationUtil { + public static void updateParameterAnnotationArrays(MethodNode methodNode, int paramCount, Consumer>> action) { + methodNode.visibleParameterAnnotations = updateParameterAnnotations(methodNode.visibleParameterAnnotations, paramCount, action); + methodNode.invisibleParameterAnnotations = updateParameterAnnotations(methodNode.invisibleParameterAnnotations, paramCount, action); + // Derive the count from the final descriptor later + methodNode.visibleAnnotableParameterCount = 0; + methodNode.invisibleAnnotableParameterCount = 0; } - public static List parse(MethodNode method, int start, int end) { - List types = getParameterTypes(method.desc); - List parameters = new ArrayList<>(); - - int first = start == -1 ? 0 : start; - int last = end == -1 ? types.size() : end; - for (int i = first; i < last; i++) { - Type type = types.get(i); - Parameter.Builder builder = Parameter.builder(type); - parseAnnotations(builder, method.invisibleParameterAnnotations, false, i); - parseAnnotations(builder, method.visibleParameterAnnotations, true, i); - Parameter parameter = builder.build(); - - parameters.add(parameter); + @Nullable + @SuppressWarnings("unchecked") + private static List[] updateParameterAnnotations(@Nullable List[] annotations, int paramCount, Consumer>> action) { + if (annotations == null) { + return null; } - - return parameters; + List> list = new ArrayList<>(Arrays.asList(annotations)); + while (list.size() < paramCount) { + list.add(null); + } + action.accept(list); + return list.toArray(List[]::new); } - private static void parseAnnotations(Parameter.Builder builder, @Nullable List[] source, boolean visible, int index) { - if (source != null && source.length > index) { - List annotations = source[index]; - if (annotations != null) { - for (AnnotationNode node : annotations) { - Annotation parsed = Annotation.parse(node, visible); - builder.annotate(parsed); - } + public static void reorderParameters(MethodNode methodNode, List parameters, boolean nonStatic, int[] order) { + List permuted = Arrays.stream(order).mapToObj(parameters::get).toList(); + + Int2IntMap slots = new Int2IntOpenHashMap(); + for (int pos = 0; pos < order.length; pos++) { + slots.put(getLVTIndex(parameters, nonStatic, order[pos]), getLVTIndex(permuted, nonStatic, pos)); + } + AdapterUtil.replaceLVT(methodNode, idx -> slots.getOrDefault(idx, idx)); + if (methodNode.localVariables != null) { + for (LocalVariableNode lvn : methodNode.localVariables) { + lvn.index = slots.getOrDefault(lvn.index, lvn.index); } } - } - /** - * {@return the stack operands of a call: its receiver (if any), followed by its parameters} - */ - public static List getCallOperandTypes(@Nullable Type receiver, String desc) { - List types = getParameterTypes(desc); - if (receiver != null) { - types.addFirst(receiver); + updateParameterAnnotationArrays(methodNode, parameters.size(), annotations -> { + List> original = new ArrayList<>(annotations); + for (int pos = 0; pos < order.length; pos++) { + annotations.set(pos, original.get(order[pos])); + } + }); + + for (int pos = 0; pos < order.length; pos++) { + parameters.set(pos, permuted.get(pos)); } - return types; } - public static List getParameterTypes(String desc) { - return new ArrayList<>(Arrays.asList(Type.getArgumentTypes(desc))); + public static int getLVTIndex(List parameters, boolean nonStatic, int index) { + int lvt = nonStatic ? 1 : 0; + for (int i = 0; i < index; i++) { + lvt += parameters.get(i).getSize(); + } + return lvt; } - public static Map> gatherVarMappings(MethodNode method, List cleanParameters, List dirtyParameters, Map replacements, MixinContext context) { - LocalVariableLookup lookup = context.methods().getLVT(method); + public static Map> gatherVarMappings(MethodNode method, List cleanParameters, List dirtyParameters, Map replacements) { + LocalVariableLookup lookup = method.localVariables == null ? null : new LocalVariableLookup(method); Map> map = replacements.entrySet().stream() .map(entry -> { int oldIndex = cleanParameters.indexOf(entry.getKey()); @@ -100,7 +110,7 @@ public static void applyVarMappings(MethodNode methodNode, Map parameters) { - ParamTransformationUtil.updateParameterAnnotationArrays(method, Type.getArgumentCount(method.desc), annotations -> {}); + updateParameterAnnotationArrays(method, Type.getArgumentCount(method.desc), annotations -> {}); for (int i = 0; i < parameters.size(); i++) { Parameter param = parameters.get(i); @@ -130,7 +140,4 @@ public static void retypeVariableAndReceivers(MethodNode methodNode, VarInsnNode } }); } - - private Parameters() { - } } diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/ParameterTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/ParameterTransformer.java similarity index 71% rename from core/src/main/java/org/sinytra/adapter/transform/param/ParameterTransformer.java rename to core/src/main/java/org/sinytra/adapter/edit/param/ParameterTransformer.java index 1ac1a148..76ab4181 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/ParameterTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ParameterTransformer.java @@ -1,10 +1,10 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.PatchResult; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/RemoveParameterTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/RemoveParameterTransformer.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/transform/param/RemoveParameterTransformer.java rename to core/src/main/java/org/sinytra/adapter/edit/param/RemoveParameterTransformer.java index 8c833d6c..a45de1d9 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/RemoveParameterTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/RemoveParameterTransformer.java @@ -1,12 +1,12 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.analysis.locals.LVTSnapshot; -import org.sinytra.adapter.env.ctx.PatchResult; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.edit.LVTSnapshot; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/ReplaceParametersTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java similarity index 92% rename from core/src/main/java/org/sinytra/adapter/transform/param/ReplaceParametersTransformer.java rename to core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java index 63a60d11..15329ea9 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/ReplaceParametersTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java @@ -1,18 +1,18 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import com.mojang.logging.LogUtils; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.types.TypeAdapter; -import org.slf4j.Logger; -import org.sinytra.adapter.patch.processor.wrapop.WrapOpOriginalCall; import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.method.WrapOpOriginalCall; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.slf4j.Logger; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/SubstituteParameterTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/SubstituteParameterTransformer.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/transform/param/SubstituteParameterTransformer.java rename to core/src/main/java/org/sinytra/adapter/edit/param/SubstituteParameterTransformer.java index a7eae0c5..5164cbd6 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/SubstituteParameterTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/SubstituteParameterTransformer.java @@ -1,12 +1,12 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import com.mojang.logging.LogUtils; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.locals.LVTSnapshot; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.PatchResult; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.edit.LVTSnapshot; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/SwapParametersTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/SwapParametersTransformer.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/transform/param/SwapParametersTransformer.java rename to core/src/main/java/org/sinytra/adapter/edit/param/SwapParametersTransformer.java index 4c20fb69..a8c9a446 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/SwapParametersTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/SwapParametersTransformer.java @@ -1,11 +1,11 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import com.mojang.logging.LogUtils; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.PatchResult; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.slf4j.Logger; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/TransformParameters.java b/core/src/main/java/org/sinytra/adapter/edit/param/TransformParameters.java similarity index 94% rename from core/src/main/java/org/sinytra/adapter/transform/param/TransformParameters.java rename to core/src/main/java/org/sinytra/adapter/edit/param/TransformParameters.java index 1647a298..cb700ed5 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/param/TransformParameters.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/TransformParameters.java @@ -1,13 +1,13 @@ -package org.sinytra.adapter.transform.param; +package org.sinytra.adapter.edit.param; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.patch.mixin.MixinFlag; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.injector.MixinFlag; import java.util.ArrayList; import java.util.Arrays; diff --git a/core/src/main/java/org/sinytra/adapter/env/util/MixinUtil.java b/core/src/main/java/org/sinytra/adapter/mixin/MixinUtil.java similarity index 96% rename from core/src/main/java/org/sinytra/adapter/env/util/MixinUtil.java rename to core/src/main/java/org/sinytra/adapter/mixin/MixinUtil.java index 6659e234..b8f89fc4 100644 --- a/core/src/main/java/org/sinytra/adapter/env/util/MixinUtil.java +++ b/core/src/main/java/org/sinytra/adapter/mixin/MixinUtil.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.util; +package org.sinytra.adapter.mixin; import org.spongepowered.asm.mixin.injection.InjectionPoint; diff --git a/core/src/main/java/org/sinytra/adapter/env/MockMixinRuntime.java b/core/src/main/java/org/sinytra/adapter/mixin/MockMixinRuntime.java similarity index 100% rename from core/src/main/java/org/sinytra/adapter/env/MockMixinRuntime.java rename to core/src/main/java/org/sinytra/adapter/mixin/MockMixinRuntime.java diff --git a/core/src/main/java/org/sinytra/adapter/util/MethodQualifier.java b/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/util/MethodQualifier.java rename to core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java index 39166f29..08d98e94 100644 --- a/core/src/main/java/org/sinytra/adapter/util/MethodQualifier.java +++ b/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.util; +package org.sinytra.adapter.model; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/TargetPair.java b/core/src/main/java/org/sinytra/adapter/model/TargetPair.java similarity index 81% rename from core/src/main/java/org/sinytra/adapter/env/ctx/TargetPair.java rename to core/src/main/java/org/sinytra/adapter/model/TargetPair.java index 6b1572c3..e9de9b91 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/TargetPair.java +++ b/core/src/main/java/org/sinytra/adapter/model/TargetPair.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.model; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/Targets.java b/core/src/main/java/org/sinytra/adapter/model/Targets.java similarity index 63% rename from core/src/main/java/org/sinytra/adapter/env/ctx/Targets.java rename to core/src/main/java/org/sinytra/adapter/model/Targets.java index 9a9206ed..023c4bd7 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/Targets.java +++ b/core/src/main/java/org/sinytra/adapter/model/Targets.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.model; public record Targets(TargetPair clean, TargetPair dirty) { } diff --git a/core/src/main/java/org/sinytra/adapter/env/ann/AnnotationData.java b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationData.java similarity index 88% rename from core/src/main/java/org/sinytra/adapter/env/ann/AnnotationData.java rename to core/src/main/java/org/sinytra/adapter/model/ann/AnnotationData.java index 82e5f510..d9f2aef1 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ann/AnnotationData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationData.java @@ -1,9 +1,9 @@ -package org.sinytra.adapter.env.ann; +package org.sinytra.adapter.model.ann; import com.google.common.base.MoreObjects; import org.objectweb.asm.tree.AnnotationNode; -import org.sinytra.adapter.patch.config.PropertyContainer; -import org.sinytra.adapter.patch.config.PropertyKey; +import org.sinytra.adapter.model.config.PropertyContainer; +import org.sinytra.adapter.model.config.PropertyKey; import java.util.Objects; import java.util.Optional; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/selector/AnnotationHandle.java b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationHandle.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/analysis/selector/AnnotationHandle.java rename to core/src/main/java/org/sinytra/adapter/model/ann/AnnotationHandle.java index 65b9d9be..19e1aa7c 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/selector/AnnotationHandle.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationHandle.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.analysis.selector; +package org.sinytra.adapter.model.ann; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AnnotationNode; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/selector/AnnotationValueHandle.java b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationValueHandle.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/analysis/selector/AnnotationValueHandle.java rename to core/src/main/java/org/sinytra/adapter/model/ann/AnnotationValueHandle.java index ef1b0710..c9403abf 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/selector/AnnotationValueHandle.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationValueHandle.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.analysis.selector; +package org.sinytra.adapter.model.ann; import org.objectweb.asm.tree.AnnotationNode; diff --git a/core/src/main/java/org/sinytra/adapter/env/ann/AtData.java b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/env/ann/AtData.java rename to core/src/main/java/org/sinytra/adapter/model/ann/AtData.java index ddbf6f6c..1b7ec0cf 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ann/AtData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java @@ -1,16 +1,14 @@ -package org.sinytra.adapter.env.ann; +package org.sinytra.adapter.model.ann; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.ctx.RefMapper; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.env.util.MixinUtil; -import org.sinytra.adapter.patch.config.MutablePropertyContainer; -import org.sinytra.adapter.patch.config.PropertyContainer; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; -import org.sinytra.adapter.patch.config.PropertyKey; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.mixin.MixinUtil; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.config.MutablePropertyContainer; +import org.sinytra.adapter.model.config.PropertyContainer; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.PropertyKey; +import org.sinytra.adapter.model.constant.MixinAnnotations; import org.spongepowered.asm.mixin.injection.At; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/env/ann/ClassTarget.java b/core/src/main/java/org/sinytra/adapter/model/ann/ClassTarget.java similarity index 80% rename from core/src/main/java/org/sinytra/adapter/env/ann/ClassTarget.java rename to core/src/main/java/org/sinytra/adapter/model/ann/ClassTarget.java index edf6ba7f..a7df0fe2 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ann/ClassTarget.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/ClassTarget.java @@ -1,17 +1,14 @@ -package org.sinytra.adapter.env.ann; +package org.sinytra.adapter.model.ann; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Either; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.analysis.selector.AnnotationValueHandle; -import org.sinytra.adapter.env.ctx.RefMapper; import java.util.List; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.MIXIN_TARGETS; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.MIXIN_VALUE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.MIXIN_TARGETS; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.MIXIN_VALUE; public class ClassTarget { private List types; diff --git a/core/src/main/java/org/sinytra/adapter/env/ann/ConstantData.java b/core/src/main/java/org/sinytra/adapter/model/ann/ConstantData.java similarity index 75% rename from core/src/main/java/org/sinytra/adapter/env/ann/ConstantData.java rename to core/src/main/java/org/sinytra/adapter/model/ann/ConstantData.java index 9a7b9a65..fd04a94a 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ann/ConstantData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/ConstantData.java @@ -1,12 +1,11 @@ -package org.sinytra.adapter.env.ann; +package org.sinytra.adapter.model.ann; import org.objectweb.asm.Type; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.config.MutablePropertyContainer; -import org.sinytra.adapter.patch.config.PropertyContainer; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; -import org.sinytra.adapter.patch.config.PropertyKey; +import org.sinytra.adapter.model.config.MutablePropertyContainer; +import org.sinytra.adapter.model.config.PropertyContainer; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.PropertyKey; +import org.sinytra.adapter.model.constant.MixinAnnotations; import java.util.Optional; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/RefMapper.java b/core/src/main/java/org/sinytra/adapter/model/ann/RefMapper.java similarity index 64% rename from core/src/main/java/org/sinytra/adapter/env/ctx/RefMapper.java rename to core/src/main/java/org/sinytra/adapter/model/ann/RefMapper.java index 0c6f443f..2ea071db 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/RefMapper.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/RefMapper.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.model.ann; public interface RefMapper { String remap(String refmapEntry); diff --git a/core/src/main/java/org/sinytra/adapter/env/ann/SliceData.java b/core/src/main/java/org/sinytra/adapter/model/ann/SliceData.java similarity index 78% rename from core/src/main/java/org/sinytra/adapter/env/ann/SliceData.java rename to core/src/main/java/org/sinytra/adapter/model/ann/SliceData.java index 98f6dc28..6eabc97e 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ann/SliceData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/SliceData.java @@ -1,16 +1,13 @@ -package org.sinytra.adapter.env.ann; +package org.sinytra.adapter.model.ann; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.tree.AnnotationNode; -import org.sinytra.adapter.env.ctx.RefMapper; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.analysis.selector.AnnotationValueHandle; +import org.sinytra.adapter.model.constant.MixinAnnotations; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.SLICE_FROM; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.SLICE_ID; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.SLICE_TO; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.SLICE_FROM; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.SLICE_ID; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.SLICE_TO; public class SliceData { @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/BasePropertyContainer.java b/core/src/main/java/org/sinytra/adapter/model/config/BasePropertyContainer.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/patch/config/BasePropertyContainer.java rename to core/src/main/java/org/sinytra/adapter/model/config/BasePropertyContainer.java index 51f1af58..3e8fb42b 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/BasePropertyContainer.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/BasePropertyContainer.java @@ -1,11 +1,11 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; import com.google.common.collect.ImmutableMap; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.analysis.selector.AnnotationValueHandle; -import org.sinytra.adapter.env.ctx.RefMapper; import org.objectweb.asm.tree.AnnotationNode; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.AnnotationValueHandle; +import org.sinytra.adapter.model.ann.RefMapper; import java.util.*; diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/Configuration.java b/core/src/main/java/org/sinytra/adapter/model/config/Configuration.java similarity index 80% rename from core/src/main/java/org/sinytra/adapter/patch/config/Configuration.java rename to core/src/main/java/org/sinytra/adapter/model/config/Configuration.java index ffe2f240..a47f7e1c 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/Configuration.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/Configuration.java @@ -1,12 +1,12 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.patch.config.key.ControlKeys; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.key.ControlKeys; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.MethodParameters; public interface Configuration extends PropertyContainer { @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/ConfigurationImpl.java b/core/src/main/java/org/sinytra/adapter/model/config/ConfigurationImpl.java similarity index 96% rename from core/src/main/java/org/sinytra/adapter/patch/config/ConfigurationImpl.java rename to core/src/main/java/org/sinytra/adapter/model/config/ConfigurationImpl.java index aa92799f..cb631024 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/ConfigurationImpl.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/ConfigurationImpl.java @@ -1,7 +1,7 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.env.param.Copiable; +import org.sinytra.adapter.model.param.Copiable; /** * Contains a single recipe state with mixin parameters and custom variables diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/ConfigurationTemplates.java b/core/src/main/java/org/sinytra/adapter/model/config/ConfigurationTemplates.java similarity index 81% rename from core/src/main/java/org/sinytra/adapter/patch/config/ConfigurationTemplates.java rename to core/src/main/java/org/sinytra/adapter/model/config/ConfigurationTemplates.java index 370d7b8e..e752530a 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/ConfigurationTemplates.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/ConfigurationTemplates.java @@ -1,7 +1,7 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; -import org.sinytra.adapter.patch.config.key.ControlKeys; -import org.sinytra.adapter.patch.config.key.MixinKeys; +import org.sinytra.adapter.model.config.key.ControlKeys; +import org.sinytra.adapter.model.config.key.MixinKeys; public final class ConfigurationTemplates { public static final PropertyContainerTemplate MIXIN_BASE = PropertyContainerTemplate.builder() diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/Configurations.java b/core/src/main/java/org/sinytra/adapter/model/config/Configurations.java similarity index 84% rename from core/src/main/java/org/sinytra/adapter/patch/config/Configurations.java rename to core/src/main/java/org/sinytra/adapter/model/config/Configurations.java index 7dd28cc6..3f9fa202 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/Configurations.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/Configurations.java @@ -1,6 +1,6 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; -import org.sinytra.adapter.env.ann.AtData; +import org.sinytra.adapter.model.ann.AtData; public class Configurations { public static final Configuration DELETE = MutableConfiguration.create(ConfigurationTemplates.DELETE) diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/MutableConfiguration.java b/core/src/main/java/org/sinytra/adapter/model/config/MutableConfiguration.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/patch/config/MutableConfiguration.java rename to core/src/main/java/org/sinytra/adapter/model/config/MutableConfiguration.java index 831279e5..fa9fb3c6 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/MutableConfiguration.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/MutableConfiguration.java @@ -1,15 +1,15 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.patch.config.key.ControlKeys; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.key.ControlKeys; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.MethodParameters; public interface MutableConfiguration extends Configuration, MutablePropertyContainer { static MutableConfiguration create() { diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/MutablePropertyContainer.java b/core/src/main/java/org/sinytra/adapter/model/config/MutablePropertyContainer.java similarity index 87% rename from core/src/main/java/org/sinytra/adapter/patch/config/MutablePropertyContainer.java rename to core/src/main/java/org/sinytra/adapter/model/config/MutablePropertyContainer.java index 511ebbca..579efd51 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/MutablePropertyContainer.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/MutablePropertyContainer.java @@ -1,8 +1,8 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.env.ctx.RefMapper; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.RefMapper; public interface MutablePropertyContainer extends PropertyContainer { static MutablePropertyContainer create() { diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/PropertyContainer.java b/core/src/main/java/org/sinytra/adapter/model/config/PropertyContainer.java similarity index 82% rename from core/src/main/java/org/sinytra/adapter/patch/config/PropertyContainer.java rename to core/src/main/java/org/sinytra/adapter/model/config/PropertyContainer.java index 95e4021b..dbba07ea 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/PropertyContainer.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/PropertyContainer.java @@ -1,7 +1,7 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; import org.objectweb.asm.tree.AnnotationNode; +import org.sinytra.adapter.model.ann.AnnotationHandle; import java.util.Map; import java.util.Optional; diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/PropertyContainerTemplate.java b/core/src/main/java/org/sinytra/adapter/model/config/PropertyContainerTemplate.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/patch/config/PropertyContainerTemplate.java rename to core/src/main/java/org/sinytra/adapter/model/config/PropertyContainerTemplate.java index dbc73dc3..4989c6f7 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/PropertyContainerTemplate.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/PropertyContainerTemplate.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/PropertyKey.java b/core/src/main/java/org/sinytra/adapter/model/config/PropertyKey.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/patch/config/PropertyKey.java rename to core/src/main/java/org/sinytra/adapter/model/config/PropertyKey.java index 88078c56..0c53a2f7 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/PropertyKey.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/PropertyKey.java @@ -1,8 +1,8 @@ -package org.sinytra.adapter.patch.config; +package org.sinytra.adapter.model.config; import com.google.common.base.MoreObjects; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.env.ctx.RefMapper; +import org.sinytra.adapter.model.ann.RefMapper; import java.util.Objects; diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/key/ControlKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/ControlKeys.java similarity index 86% rename from core/src/main/java/org/sinytra/adapter/patch/config/key/ControlKeys.java rename to core/src/main/java/org/sinytra/adapter/model/config/key/ControlKeys.java index d0969116..9877a051 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/key/ControlKeys.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/key/ControlKeys.java @@ -1,8 +1,8 @@ -package org.sinytra.adapter.patch.config.key; +package org.sinytra.adapter.model.config.key; import org.objectweb.asm.Type; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.patch.config.PropertyKey; +import org.sinytra.adapter.model.config.PropertyKey; +import org.sinytra.adapter.model.param.MethodParameters; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/key/MixinKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/MixinKeys.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/patch/config/key/MixinKeys.java rename to core/src/main/java/org/sinytra/adapter/model/config/key/MixinKeys.java index 1bd47447..8e4bc32c 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/key/MixinKeys.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/key/MixinKeys.java @@ -1,12 +1,12 @@ -package org.sinytra.adapter.patch.config.key; +package org.sinytra.adapter.model.config.key; import org.objectweb.asm.tree.AnnotationNode; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ann.ConstantData; -import org.sinytra.adapter.env.ann.SliceData; -import org.sinytra.adapter.patch.config.PropertyKey; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.ann.ConstantData; +import org.sinytra.adapter.model.ann.SliceData; +import org.sinytra.adapter.model.config.PropertyKey; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/patch/config/key/SpecialKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/SpecialKeys.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/patch/config/key/SpecialKeys.java rename to core/src/main/java/org/sinytra/adapter/model/config/key/SpecialKeys.java index 650c6cbf..dbbe9f0c 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/config/key/SpecialKeys.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/key/SpecialKeys.java @@ -1,8 +1,8 @@ -package org.sinytra.adapter.patch.config.key; +package org.sinytra.adapter.model.config.key; import org.objectweb.asm.commons.InstructionAdapter; import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.patch.config.PropertyKey; +import org.sinytra.adapter.model.config.PropertyKey; import java.util.function.Consumer; diff --git a/core/src/main/java/org/sinytra/adapter/env/util/MixinAnnotationConstants.java b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java similarity index 96% rename from core/src/main/java/org/sinytra/adapter/env/util/MixinAnnotationConstants.java rename to core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java index ae6fc8b1..7c28b3d3 100644 --- a/core/src/main/java/org/sinytra/adapter/env/util/MixinAnnotationConstants.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.util; +package org.sinytra.adapter.model.constant; public class MixinAnnotationConstants { public static final String MIXIN_VALUE = "value"; diff --git a/core/src/main/java/org/sinytra/adapter/env/util/MixinAnnotations.java b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/env/util/MixinAnnotations.java rename to core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java index 0f101219..06465f0e 100644 --- a/core/src/main/java/org/sinytra/adapter/env/util/MixinAnnotations.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.util; +package org.sinytra.adapter.model.constant; public class MixinAnnotations { // Standard mixins diff --git a/core/src/main/java/org/sinytra/adapter/env/util/TypeConstants.java b/core/src/main/java/org/sinytra/adapter/model/constant/TypeConstants.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/env/util/TypeConstants.java rename to core/src/main/java/org/sinytra/adapter/model/constant/TypeConstants.java index 67931554..f443cb71 100644 --- a/core/src/main/java/org/sinytra/adapter/env/util/TypeConstants.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/TypeConstants.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.util; +package org.sinytra.adapter.model.constant; import org.objectweb.asm.Type; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; diff --git a/core/src/main/java/org/sinytra/adapter/env/param/Annotation.java b/core/src/main/java/org/sinytra/adapter/model/param/Annotation.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/env/param/Annotation.java rename to core/src/main/java/org/sinytra/adapter/model/param/Annotation.java index bd376cfb..862aaf49 100644 --- a/core/src/main/java/org/sinytra/adapter/env/param/Annotation.java +++ b/core/src/main/java/org/sinytra/adapter/model/param/Annotation.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.param; +package org.sinytra.adapter.model.param; import com.google.common.collect.ImmutableMap; import org.objectweb.asm.AnnotationVisitor; diff --git a/core/src/main/java/org/sinytra/adapter/env/param/Copiable.java b/core/src/main/java/org/sinytra/adapter/model/param/Copiable.java similarity index 53% rename from core/src/main/java/org/sinytra/adapter/env/param/Copiable.java rename to core/src/main/java/org/sinytra/adapter/model/param/Copiable.java index a549ef4e..b96a9090 100644 --- a/core/src/main/java/org/sinytra/adapter/env/param/Copiable.java +++ b/core/src/main/java/org/sinytra/adapter/model/param/Copiable.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.param; +package org.sinytra.adapter.model.param; public interface Copiable { T copy(); diff --git a/core/src/main/java/org/sinytra/adapter/env/param/MethodParameters.java b/core/src/main/java/org/sinytra/adapter/model/param/MethodParameters.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/env/param/MethodParameters.java rename to core/src/main/java/org/sinytra/adapter/model/param/MethodParameters.java index fe1bd1b3..52fc63f6 100644 --- a/core/src/main/java/org/sinytra/adapter/env/param/MethodParameters.java +++ b/core/src/main/java/org/sinytra/adapter/model/param/MethodParameters.java @@ -1,9 +1,9 @@ -package org.sinytra.adapter.env.param; +package org.sinytra.adapter.model.param; import com.google.common.collect.ImmutableMap; import org.objectweb.asm.Type; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.util.TypeConstants; +import org.sinytra.adapter.model.constant.TypeConstants; import java.util.*; import java.util.function.Predicate; diff --git a/core/src/main/java/org/sinytra/adapter/env/param/Parameter.java b/core/src/main/java/org/sinytra/adapter/model/param/Parameter.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/env/param/Parameter.java rename to core/src/main/java/org/sinytra/adapter/model/param/Parameter.java index 647c1df6..ae9206d0 100644 --- a/core/src/main/java/org/sinytra/adapter/env/param/Parameter.java +++ b/core/src/main/java/org/sinytra/adapter/model/param/Parameter.java @@ -1,8 +1,8 @@ -package org.sinytra.adapter.env.param; +package org.sinytra.adapter.model.param; import com.google.common.collect.ImmutableList; import org.objectweb.asm.Type; -import org.sinytra.adapter.env.util.MixinAnnotations; +import org.sinytra.adapter.model.constant.MixinAnnotations; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/model/param/Parameters.java b/core/src/main/java/org/sinytra/adapter/model/param/Parameters.java new file mode 100644 index 00000000..e625c537 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/model/param/Parameters.java @@ -0,0 +1,62 @@ +package org.sinytra.adapter.model.param; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; + +import java.util.*; + +public final class Parameters { + public static List parse(MethodNode method) { + return parse(method, -1, -1); + } + + public static List parse(MethodNode method, int start, int end) { + List types = getParameterTypes(method.desc); + List parameters = new ArrayList<>(); + + int first = start == -1 ? 0 : start; + int last = end == -1 ? types.size() : end; + for (int i = first; i < last; i++) { + Type type = types.get(i); + Parameter.Builder builder = Parameter.builder(type); + parseAnnotations(builder, method.invisibleParameterAnnotations, false, i); + parseAnnotations(builder, method.visibleParameterAnnotations, true, i); + Parameter parameter = builder.build(); + + parameters.add(parameter); + } + + return parameters; + } + + private static void parseAnnotations(Parameter.Builder builder, @Nullable List[] source, boolean visible, int index) { + if (source != null && source.length > index) { + List annotations = source[index]; + if (annotations != null) { + for (AnnotationNode node : annotations) { + Annotation parsed = Annotation.parse(node, visible); + builder.annotate(parsed); + } + } + } + } + + /** + * {@return the stack operands of a call: its receiver (if any), followed by its parameters} + */ + public static List getCallOperandTypes(@Nullable Type receiver, String desc) { + List types = getParameterTypes(desc); + if (receiver != null) { + types.addFirst(receiver); + } + return types; + } + + public static List getParameterTypes(String desc) { + return new ArrayList<>(Arrays.asList(Type.getArgumentTypes(desc))); + } + + private Parameters() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/Processor.java b/core/src/main/java/org/sinytra/adapter/patch/processor/Processor.java deleted file mode 100644 index 093a4dc6..00000000 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/Processor.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sinytra.adapter.patch.processor; - -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; - -public interface Processor { - TxResult process(MixinContext context, Configuration dirty, Recipe recipe); -} diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/SubResolver.java b/core/src/main/java/org/sinytra/adapter/patch/resolver/SubResolver.java deleted file mode 100644 index 0b70d05a..00000000 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/SubResolver.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sinytra.adapter.patch.resolver; - -import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; - -public interface SubResolver { - @Nullable - Configuration resolve(MixinContext context, Recipe recipe); -} diff --git a/core/src/main/java/org/sinytra/adapter/patch/MixinParser.java b/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java similarity index 83% rename from core/src/main/java/org/sinytra/adapter/patch/MixinParser.java rename to core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java index 2db8a10c..9fe69dea 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/MixinParser.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java @@ -1,26 +1,26 @@ -package org.sinytra.adapter.patch; +package org.sinytra.adapter.pipeline; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.ann.ClassTarget; -import org.sinytra.adapter.env.ctx.MethodHelper; -import org.sinytra.adapter.env.ctx.PatchEnvironment; -import org.sinytra.adapter.env.ctx.RefMapper; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.config.MutablePropertyContainer; -import org.sinytra.adapter.patch.config.PropertyContainer; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; -import org.sinytra.adapter.patch.config.key.ControlKeys; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.patch.mixin.MixinType; -import org.sinytra.adapter.patch.mixin.MixinTypes; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.util.provider.ClassLookup; +import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.model.ann.RefMapper; +import org.sinytra.adapter.model.config.MutablePropertyContainer; +import org.sinytra.adapter.model.config.PropertyContainer; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.ControlKeys; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.injector.MixinType; +import org.sinytra.adapter.pipeline.injector.MixinTypes; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/patch/Patcher.java b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java similarity index 70% rename from core/src/main/java/org/sinytra/adapter/patch/Patcher.java rename to core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java index a73d3bb1..4de90b73 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/Patcher.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java @@ -1,33 +1,31 @@ -package org.sinytra.adapter.patch; +package org.sinytra.adapter.pipeline; -import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.mojang.logging.LogUtils; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.MixinGroups; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.ann.ClassTarget; -import org.sinytra.adapter.env.ctx.*; -import org.sinytra.adapter.env.util.MixinAnnotationConstants; -import org.sinytra.adapter.patch.MixinParser.MixinMethodHandle; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; -import org.sinytra.adapter.patch.mixin.MixinFlag; -import org.sinytra.adapter.patch.mixin.MixinType; -import org.sinytra.adapter.transform.ClassTransformer; -import org.sinytra.adapter.transform.MethodTransformer; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.api.*; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.constant.MixinAnnotationConstants; +import org.sinytra.adapter.pipeline.MixinParser.MixinMethodHandle; +import org.sinytra.adapter.pipeline.audit.MixinGroups; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.ctx.PatchContextImpl; +import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.pipeline.injector.MixinType; import org.slf4j.Logger; -import java.util.ArrayList; import java.util.List; import java.util.Set; import static org.sinytra.adapter.util.AdapterUtil.MIXINPATCH; -public class Patcher { +public class PatcherImpl implements Patcher { private static final Logger LOGGER = LogUtils.getLogger(); // TODO Support all known mixin types from PatchInstance (incl. interface mixins) @@ -35,12 +33,13 @@ public class Patcher { private final List classPatches; private final Multimap methodTransformers; - private Patcher(PatchEnvironment environment, List classPatches, Multimap methodTransformers) { + public PatcherImpl(PatchEnvironment environment, List classPatches, Multimap methodTransformers) { this.environment = environment; this.classPatches = classPatches; this.methodTransformers = methodTransformers; } + @Override public PatchResult process(ClassNode classNode) { // Parse class data ClassTarget classTarget = MixinParser.prepareMixinClass(classNode, this.environment); @@ -145,42 +144,4 @@ private PatchResult runPhase(TxPhase phase, MixinContext mixinContext, MutableCo } return result; } - - public static Builder builder(PatchEnvironment environment) { - return new Builder(environment); - } - - public static class Builder { - private final PatchEnvironment environment; - private final List classTransformers = new ArrayList<>(); - private final Multimap methodTransformers = HashMultimap.create(); - - public Builder(PatchEnvironment environment) { - this.environment = environment; - } - - public Builder classTransformer(ClassTransformer transformer) { - this.classTransformers.add(transformer); - return this; - } - - public Builder classTransformers(List transformers) { - this.classTransformers.addAll(transformers); - return this; - } - - public Builder methodTransformer(TxPhase phase, MethodTransformer transformer) { - this.methodTransformers.put(phase, transformer); - return this; - } - - public Builder methodTransformers(Multimap transformers) { - this.methodTransformers.putAll(transformers); - return this; - } - - public Patcher build() { - return new Patcher(this.environment, this.classTransformers, this.methodTransformers); - } - } } diff --git a/core/src/main/java/org/sinytra/adapter/transform/PipelineMethodTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java similarity index 87% rename from core/src/main/java/org/sinytra/adapter/transform/PipelineMethodTransformer.java rename to core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java index e9e98271..2a48d04c 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/PipelineMethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java @@ -1,30 +1,29 @@ -package org.sinytra.adapter.transform; +package org.sinytra.adapter.pipeline; import com.mojang.logging.LogUtils; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ann.SliceData; -import org.sinytra.adapter.env.ctx.AuditTrail; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.key.ControlKeys; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.mixin.MixinType; -import org.sinytra.adapter.patch.mixin.MixinTypes; -import org.sinytra.adapter.patch.processor.Processor; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.resolver.Resolver; -import org.sinytra.adapter.patch.resolver.Resolvers; -import org.sinytra.adapter.transform.patch.MethodPatch; -import org.sinytra.adapter.transform.patch.MethodPatchResolver; +import org.sinytra.adapter.api.AuditTrail; +import org.sinytra.adapter.api.MethodPatch; +import org.sinytra.adapter.api.MethodTransformer; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.ann.SliceData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.ControlKeys; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.injector.MixinType; +import org.sinytra.adapter.pipeline.injector.MixinTypes; +import org.sinytra.adapter.pipeline.manual.MethodPatchResolver; +import org.sinytra.adapter.pipeline.processor.Processor; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolver; +import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.slf4j.Logger; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.points.BeforeConstant; diff --git a/core/src/main/java/org/sinytra/adapter/patch/Recipe.java b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java similarity index 91% rename from core/src/main/java/org/sinytra/adapter/patch/Recipe.java rename to core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java index f7752164..0254ac51 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/Recipe.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java @@ -1,18 +1,18 @@ -package org.sinytra.adapter.patch; +package org.sinytra.adapter.pipeline; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.resolver.Resolvers; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.util.Cached; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.util.provider.ClassLookup; -import org.sinytra.adapter.env.ctx.Targets; -import org.objectweb.asm.tree.AbstractInsnNode; import java.util.Objects; diff --git a/core/src/main/java/org/sinytra/adapter/patch/TxResult.java b/core/src/main/java/org/sinytra/adapter/pipeline/TxResult.java similarity index 65% rename from core/src/main/java/org/sinytra/adapter/patch/TxResult.java rename to core/src/main/java/org/sinytra/adapter/pipeline/TxResult.java index 3429d7b0..9997eaac 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/TxResult.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/TxResult.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.patch; +package org.sinytra.adapter.pipeline; public enum TxResult { SUCCESS, diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/AuditTrailImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/audit/AuditTrailImpl.java similarity index 96% rename from core/src/main/java/org/sinytra/adapter/env/ctx/AuditTrailImpl.java rename to core/src/main/java/org/sinytra/adapter/pipeline/audit/AuditTrailImpl.java index 09e1287b..6190961c 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/AuditTrailImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/audit/AuditTrailImpl.java @@ -1,14 +1,15 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.pipeline.audit; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.Pair; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.MixinGroups; -import org.sinytra.adapter.analysis.MixinGroups.GroupInfo; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.key.MixinKeys; +import org.sinytra.adapter.api.AuditTrail; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.pipeline.audit.MixinGroups.GroupInfo; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.slf4j.Logger; import java.text.DecimalFormat; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/Auditor.java b/core/src/main/java/org/sinytra/adapter/pipeline/audit/Auditor.java similarity index 69% rename from core/src/main/java/org/sinytra/adapter/env/ctx/Auditor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/audit/Auditor.java index 318d2d7c..fe8639fa 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/Auditor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/audit/Auditor.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.pipeline.audit; public interface Auditor { void recordAudit(Object actor, String message, Object... args); diff --git a/core/src/main/java/org/sinytra/adapter/analysis/MixinGroups.java b/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/analysis/MixinGroups.java rename to core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java index 7c51d748..4b56c847 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/MixinGroups.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.analysis; +package org.sinytra.adapter.pipeline.audit; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; diff --git a/core/src/main/java/org/sinytra/adapter/transform/cls/DynamicAnonClassIndexPatch.java b/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java similarity index 88% rename from core/src/main/java/org/sinytra/adapter/transform/cls/DynamicAnonClassIndexPatch.java rename to core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java index 5ae53db0..611723e3 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/cls/DynamicAnonClassIndexPatch.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java @@ -1,17 +1,17 @@ -package org.sinytra.adapter.transform.cls; +package org.sinytra.adapter.pipeline.cls; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InnerClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ann.ClassTarget; -import org.sinytra.adapter.env.ctx.PatchContext; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.patch.MixinParser; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.transform.ClassTransformer; +import org.sinytra.adapter.api.ClassTransformer; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.pipeline.MixinParser; +import org.sinytra.adapter.pipeline.ctx.PatchContext; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.MethodQualifier; /** diff --git a/core/src/main/java/org/sinytra/adapter/transform/cls/DynamicAnonymousShadowFieldTypePatch.java b/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/transform/cls/DynamicAnonymousShadowFieldTypePatch.java rename to core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java index 10a57712..8e9c913d 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/cls/DynamicAnonymousShadowFieldTypePatch.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java @@ -1,14 +1,14 @@ -package org.sinytra.adapter.transform.cls; +package org.sinytra.adapter.pipeline.cls; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.ann.ClassTarget; -import org.sinytra.adapter.env.ctx.PatchContext; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.transform.ClassTransformer; +import org.sinytra.adapter.api.ClassTransformer; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.ctx.PatchContext; import org.sinytra.adapter.util.AdapterUtil; import java.util.Collection; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/MixinContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java similarity index 86% rename from core/src/main/java/org/sinytra/adapter/env/ctx/MixinContext.java rename to core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java index 25c5373a..9300c52f 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/MixinContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java @@ -1,19 +1,23 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.pipeline.ctx; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.ann.ClassTarget; -import org.sinytra.adapter.patch.mixin.MixinFlag; -import org.sinytra.adapter.patch.mixin.MixinType; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.resolver.Resolvers; -import org.sinytra.adapter.types.BytecodeFixerUpper; -import org.sinytra.adapter.types.TypeAdapter; +import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.api.BytecodeFixerUpper; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.model.ann.RefMapper; +import org.sinytra.adapter.pipeline.audit.Auditor; +import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.pipeline.injector.MixinType; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.provider.ClassLookup; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java similarity index 77% rename from core/src/main/java/org/sinytra/adapter/env/ctx/PatchContext.java rename to core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java index 293eb053..feba3453 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java @@ -1,7 +1,9 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.pipeline.ctx; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.model.ann.RefMapper; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchContextImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java similarity index 93% rename from core/src/main/java/org/sinytra/adapter/env/ctx/PatchContextImpl.java rename to core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java index a644ef7b..2e3a13db 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchContextImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java @@ -1,7 +1,8 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.pipeline.ctx; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; +import org.sinytra.adapter.api.PatchEnvironment; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchEnvironmentImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchEnvironmentImpl.java similarity index 86% rename from core/src/main/java/org/sinytra/adapter/env/ctx/PatchEnvironmentImpl.java rename to core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchEnvironmentImpl.java index e355f22e..a96d1eb9 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ctx/PatchEnvironmentImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchEnvironmentImpl.java @@ -1,11 +1,17 @@ -package org.sinytra.adapter.env.ctx; +package org.sinytra.adapter.pipeline.ctx; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.analysis.InheritanceHandler; -import org.sinytra.adapter.types.BytecodeFixerUpper; +import org.sinytra.adapter.analysis.method.InheritanceHandler; +import org.sinytra.adapter.api.AuditTrail; +import org.sinytra.adapter.api.BytecodeFixerUpper; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.MixinClassGenerator; +import org.sinytra.adapter.api.MixinClassLookup; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.api.RefmapHolder; +import org.sinytra.adapter.edit.MixinClassGeneratorImpl; +import org.sinytra.adapter.pipeline.audit.AuditTrailImpl; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.provider.ClassLookup; -import org.sinytra.adapter.util.provider.MixinClassLookup; import java.util.Collection; import java.util.Map; diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/AbstractMixinType.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/AbstractMixinType.java similarity index 87% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/AbstractMixinType.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/AbstractMixinType.java index 9a06246f..3de70ce4 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/AbstractMixinType.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/AbstractMixinType.java @@ -1,6 +1,6 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; import java.util.Arrays; import java.util.Collections; diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java similarity index 70% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/InjectMixin.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index 221f4f16..115c75e2 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -1,30 +1,32 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; import org.objectweb.asm.Type; -import org.sinytra.adapter.patch.config.ConfigurationTemplates; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.Parameter; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.patch.config.*; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.resolver.Resolvers; -import org.sinytra.adapter.patch.resolver.injection.ArbitraryInjectionPointSubResolver; -import org.sinytra.adapter.patch.resolver.injection.AtVariableAssignStoreSubResolver; -import org.sinytra.adapter.patch.resolver.injection.ComparingInjectionPointResolver; -import org.sinytra.adapter.patch.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.patch.resolver.special.InjectorOrdinalResolver; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.env.ctx.Targets; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolvers; +import org.sinytra.adapter.pipeline.resolver.injection.ArbitraryInjectionPointSubResolver; +import org.sinytra.adapter.pipeline.resolver.injection.AtVariableAssignStoreSubResolver; +import org.sinytra.adapter.pipeline.resolver.injection.ComparingInjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.special.InjectorOrdinalResolver; import java.util.List; import java.util.Objects; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.*; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.*; public class InjectMixin extends AbstractMixinType { private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_AT.extend() diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinFlag.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinFlag.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/MixinFlag.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinFlag.java index 64a798b5..7decede0 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinFlag.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinFlag.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; public enum MixinFlag { /** diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinType.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java similarity index 57% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/MixinType.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java index cd71d1b6..d9e516b0 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinType.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java @@ -1,13 +1,13 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.resolver.Resolvers; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolvers; import java.util.EnumSet; import java.util.Set; diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinTypes.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/MixinTypes.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java index 840f9fa6..8943794e 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinTypes.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java @@ -1,7 +1,7 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.env.util.MixinAnnotations; +import org.sinytra.adapter.model.constant.MixinAnnotations; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyVariable; diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyArgMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java similarity index 73% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyArgMixin.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java index 850f4cd2..105d7ee0 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyArgMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java @@ -1,30 +1,30 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.Parameter; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.ConfigurationTemplates; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.resolver.Resolvers; -import org.sinytra.adapter.patch.resolver.injection.ArbitraryInjectionPointSubResolver; -import org.sinytra.adapter.patch.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.types.TypeAdapter; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolvers; +import org.sinytra.adapter.pipeline.resolver.injection.ArbitraryInjectionPointSubResolver; +import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import java.util.List; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.SINGLE_ANY; -import static org.sinytra.adapter.patch.config.key.MixinKeys.INDEX; +import static org.sinytra.adapter.model.config.key.MixinKeys.INDEX; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.SINGLE_ANY; public class ModifyArgMixin extends AbstractMixinType { private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_AT.extend() diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyExpressionValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java similarity index 62% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyExpressionValueMixin.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java index f68454fc..db473616 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyExpressionValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java @@ -1,28 +1,28 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; import org.objectweb.asm.Type; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.ConfigurationTemplates; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.resolver.Resolvers; -import org.sinytra.adapter.patch.resolver.injection.ArbitraryInjectionPointSubResolver; -import org.sinytra.adapter.patch.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.patch.resolver.special.ResolverSyntheticInstanceof; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolvers; +import org.sinytra.adapter.pipeline.resolver.injection.ArbitraryInjectionPointSubResolver; +import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.special.ResolverSyntheticInstanceof; import java.util.List; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.LOCALS; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.SINGLE_ANY; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.LOCALS; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.SINGLE_ANY; public class ModifyExpressionValueMixin extends AbstractMixinType { private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_AT.extend() diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyReturnValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java similarity index 67% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyReturnValueMixin.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java index 0ec54783..b3b63f53 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyReturnValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java @@ -1,28 +1,28 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; import org.objectweb.asm.Type; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.ConfigurationTemplates; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.resolver.Resolvers; -import org.sinytra.adapter.patch.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.patch.resolver.special.InjectorOrdinalResolver; -import org.sinytra.adapter.util.MethodQualifier; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.env.ctx.Targets; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolvers; +import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.special.InjectorOrdinalResolver; import java.util.List; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.*; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_RETURN; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_RETURN; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.*; public class ModifyReturnValueMixin extends AbstractMixinType { private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_AT.extend() diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java similarity index 73% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyVariableMixin.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index 33a09589..377c5cc7 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -1,34 +1,34 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; import org.objectweb.asm.Type; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.Parameter; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.ConfigurationTemplates; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.PropertyContainerTemplate; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.resolver.Resolvers; -import org.sinytra.adapter.patch.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.patch.resolver.injection.ModifyVarInjectionPointSubResolver; -import org.sinytra.adapter.patch.resolver.special.InjectorOrdinalResolver; -import org.sinytra.adapter.patch.resolver.special.ModifyVarAtReturnResolver; -import org.sinytra.adapter.patch.resolver.special.ModifyVarUpgradeResolver; -import org.sinytra.adapter.types.TypeAdapter; -import org.sinytra.adapter.env.ctx.TargetPair; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolvers; +import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.injection.ModifyVarInjectionPointSubResolver; +import org.sinytra.adapter.pipeline.resolver.special.InjectorOrdinalResolver; +import org.sinytra.adapter.pipeline.resolver.special.ModifyVarAtReturnResolver; +import org.sinytra.adapter.pipeline.resolver.special.ModifyVarUpgradeResolver; import java.util.List; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.LOCALS; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.SINGLE_ANY; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.LOCALS; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.SINGLE_ANY; public class ModifyVariableMixin extends AbstractMixinType { private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_AT.extend() diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java similarity index 73% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/RedirectMixin.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index b3b5d2bf..2a98534e 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -1,30 +1,33 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; import org.objectweb.asm.Type; -import org.sinytra.adapter.env.ctx.MethodHelper; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.MethodParameters.ParamGroup; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.env.util.MixinAnnotationConstants; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.*; -import org.sinytra.adapter.patch.config.key.ControlKeys; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.processor.ParametersProcessor; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.processor.redirect.DivertRedirectProcessor; -import org.sinytra.adapter.patch.processor.redirect.ParameterUsageProcessor; -import org.sinytra.adapter.patch.resolver.Resolvers; -import org.sinytra.adapter.patch.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.patch.resolver.special.ResolverSyntheticInstanceof; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.ControlKeys; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.constant.MixinAnnotationConstants; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.ParametersProcessor; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.processor.redirect.DivertRedirectProcessor; +import org.sinytra.adapter.pipeline.processor.redirect.ParameterUsageProcessor; +import org.sinytra.adapter.pipeline.resolver.Resolvers; +import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.special.ResolverSyntheticInstanceof; import java.util.List; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.*; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.*; public class RedirectMixin extends AbstractMixinType { private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_AT.extend() diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java similarity index 64% rename from core/src/main/java/org/sinytra/adapter/patch/mixin/WrapOperationMixin.java rename to core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index 65667b52..f368899b 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -1,28 +1,31 @@ -package org.sinytra.adapter.patch.mixin; +package org.sinytra.adapter.pipeline.injector; import org.objectweb.asm.Type; -import org.sinytra.adapter.patch.config.*; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.MethodHelper; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.processor.ParametersProcessor; -import org.sinytra.adapter.patch.processor.Processors; -import org.sinytra.adapter.patch.processor.wrapop.WrapOpParamsProcessor; -import org.sinytra.adapter.patch.resolver.Resolvers; -import org.sinytra.adapter.patch.resolver.injection.AtVariableAssignStoreSubResolver; -import org.sinytra.adapter.patch.resolver.injection.ComparingInjectionPointResolver; -import org.sinytra.adapter.patch.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.env.util.TypeConstants; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.env.ctx.TargetPair; +import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.ParametersProcessor; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.processor.wrapop.WrapOpParamsProcessor; +import org.sinytra.adapter.pipeline.resolver.Resolvers; +import org.sinytra.adapter.pipeline.resolver.injection.AtVariableAssignStoreSubResolver; +import org.sinytra.adapter.pipeline.resolver.injection.ComparingInjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import java.util.List; -import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.*; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.*; public class WrapOperationMixin extends AbstractMixinType { private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_BASE.extend() diff --git a/core/src/main/java/org/sinytra/adapter/transform/patch/ConfigurationMatcher.java b/core/src/main/java/org/sinytra/adapter/pipeline/manual/ConfigurationMatcher.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/transform/patch/ConfigurationMatcher.java rename to core/src/main/java/org/sinytra/adapter/pipeline/manual/ConfigurationMatcher.java index 33544f81..e8183169 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/patch/ConfigurationMatcher.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/manual/ConfigurationMatcher.java @@ -1,8 +1,8 @@ -package org.sinytra.adapter.transform.patch; +package org.sinytra.adapter.pipeline.manual; import com.google.common.collect.ImmutableList; -import org.sinytra.adapter.patch.config.PropertyContainer; -import org.sinytra.adapter.patch.config.PropertyKey; +import org.sinytra.adapter.model.config.PropertyContainer; +import org.sinytra.adapter.model.config.PropertyKey; import java.util.*; import java.util.function.Consumer; diff --git a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilderImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java similarity index 87% rename from core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilderImpl.java rename to core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java index 1192e5e7..5026462f 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilderImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java @@ -1,16 +1,18 @@ -package org.sinytra.adapter.transform.patch; +package org.sinytra.adapter.pipeline.manual; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.commons.InstructionAdapter; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.util.MixinAnnotationConstants; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.transform.MethodTransformer; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.api.MethodPatch; +import org.sinytra.adapter.api.MethodPatchBuilder; +import org.sinytra.adapter.api.MethodTransformer; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.constant.MixinAnnotationConstants; +import org.sinytra.adapter.model.param.MethodParameters; import java.util.ArrayList; import java.util.List; @@ -22,8 +24,8 @@ import java.util.stream.Stream; import static java.util.function.Predicate.isEqual; -import static org.sinytra.adapter.patch.config.key.ControlKeys.MIXIN_TYPE; -import static org.sinytra.adapter.patch.config.key.ControlKeys.TARGET_CLASS; +import static org.sinytra.adapter.model.config.key.ControlKeys.MIXIN_TYPE; +import static org.sinytra.adapter.model.config.key.ControlKeys.TARGET_CLASS; public class MethodPatchBuilderImpl implements MethodPatchBuilder { private final ConfigurationMatcher.Builder matcher = ConfigurationMatcher.builder(); diff --git a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchImpl.java similarity index 81% rename from core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchImpl.java rename to core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchImpl.java index 7a2e553f..01ea1372 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchImpl.java @@ -1,8 +1,9 @@ -package org.sinytra.adapter.transform.patch; +package org.sinytra.adapter.pipeline.manual; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.transform.MethodTransformer; +import org.sinytra.adapter.api.MethodPatch; +import org.sinytra.adapter.api.MethodTransformer; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; import java.util.List; import java.util.function.BiConsumer; diff --git a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchProcessor.java similarity index 52% rename from core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchProcessor.java index 20dbf53f..76761e7a 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchProcessor.java @@ -1,11 +1,11 @@ -package org.sinytra.adapter.transform.patch; +package org.sinytra.adapter.pipeline.manual; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.processor.Processor; -import org.sinytra.adapter.transform.MethodTransformer; +import org.sinytra.adapter.api.MethodTransformer; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processor; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchResolver.java similarity index 80% rename from core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchResolver.java index a68bef58..82ec68a6 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchResolver.java @@ -1,12 +1,13 @@ -package org.sinytra.adapter.transform.patch; +package org.sinytra.adapter.pipeline.manual; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.Configurations; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.resolver.Resolver; -import org.sinytra.adapter.transform.MethodTransformer; +import org.sinytra.adapter.api.MethodPatch; +import org.sinytra.adapter.api.MethodTransformer; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.Configurations; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.Resolver; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/transform/preprocess/LocalCaptureUpgradeTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java similarity index 86% rename from core/src/main/java/org/sinytra/adapter/transform/preprocess/LocalCaptureUpgradeTransformer.java rename to core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java index 7c1e2531..fe9197ca 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/preprocess/LocalCaptureUpgradeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java @@ -1,24 +1,24 @@ -package org.sinytra.adapter.transform.preprocess; +package org.sinytra.adapter.pipeline.preprocess; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.AuditTrail; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.transform.MethodTransformer; import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.env.ctx.Targets; -import org.sinytra.adapter.env.param.Parameters; +import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.api.AuditTrail; +import org.sinytra.adapter.api.MethodTransformer; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.*; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.PROPERTY_ORDINAL; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ORDINAL; public class LocalCaptureUpgradeTransformer implements MethodTransformer { @Override diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/DisableMixinProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/DisableMixinProcessor.java similarity index 64% rename from core/src/main/java/org/sinytra/adapter/patch/processor/DisableMixinProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/DisableMixinProcessor.java index 0bf7f018..1a8d8b56 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/DisableMixinProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/DisableMixinProcessor.java @@ -1,9 +1,9 @@ -package org.sinytra.adapter.patch.processor; +package org.sinytra.adapter.pipeline.processor; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; public class DisableMixinProcessor implements Processor { @Override diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/MixinTypeProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/MixinTypeProcessor.java similarity index 75% rename from core/src/main/java/org/sinytra/adapter/patch/processor/MixinTypeProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/MixinTypeProcessor.java index e499ac39..3c1a4e1a 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/MixinTypeProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/MixinTypeProcessor.java @@ -1,12 +1,12 @@ -package org.sinytra.adapter.patch.processor; +package org.sinytra.adapter.pipeline.processor; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; public class MixinTypeProcessor implements Processor { diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/ParametersPostProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/patch/processor/ParametersPostProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java index c7f06370..05ad0363 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/ParametersPostProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java @@ -1,19 +1,19 @@ -package org.sinytra.adapter.patch.processor; +package org.sinytra.adapter.pipeline.processor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.transform.param.ReplaceParametersTransformer; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.edit.param.ReplaceParametersTransformer; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/ParametersProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersProcessor.java similarity index 68% rename from core/src/main/java/org/sinytra/adapter/patch/processor/ParametersProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersProcessor.java index 28604107..b519fd8e 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/ParametersProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersProcessor.java @@ -1,17 +1,17 @@ -package org.sinytra.adapter.patch.processor; +package org.sinytra.adapter.pipeline.processor; import com.mojang.datafixers.util.Pair; import org.objectweb.asm.Type; import org.objectweb.asm.tree.VarInsnNode; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.edit.param.ParamTransformationUtil; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.List; import java.util.Map; @@ -26,7 +26,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe return TxResult.PASS; // Apply mappings of params that will be removed in dirty - Map> oldVarMap = Parameters.gatherVarMappings(context.methodNode(), cleanParams.merge(), dirtyParams.merge(), dirtyParams.getMapping(), context); + Map> oldVarMap = ParamTransformationUtil.gatherVarMappings(context.methodNode(), cleanParams.merge(), dirtyParams.merge(), dirtyParams.getMapping()); if (cleanParams.getOrder().equals(dirtyParams.getOrder())) { List order = dirtyParams.getOrder(); @@ -47,9 +47,9 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe } // Apply mappings of params that only exist in dirty - Parameters.applyVarMappings(context.methodNode(), Parameters.gatherVarMappings(context.methodNode(), dirtyParams.merge(), dirtyParams.merge(), dirtyParams.getMapping(), context)); - Parameters.applyVarMappings(context.methodNode(), oldVarMap); - Parameters.applyAnnotations(context.methodNode(), dirtyParams.merge()); + ParamTransformationUtil.applyVarMappings(context.methodNode(), ParamTransformationUtil.gatherVarMappings(context.methodNode(), dirtyParams.merge(), dirtyParams.merge(), dirtyParams.getMapping())); + ParamTransformationUtil.applyVarMappings(context.methodNode(), oldVarMap); + ParamTransformationUtil.applyAnnotations(context.methodNode(), dirtyParams.merge()); return TxResult.SUCCESS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processor.java new file mode 100644 index 00000000..6d634ac6 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processor.java @@ -0,0 +1,10 @@ +package org.sinytra.adapter.pipeline.processor; + +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +public interface Processor { + TxResult process(MixinContext context, Configuration dirty, Recipe recipe); +} diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/Processors.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java similarity index 86% rename from core/src/main/java/org/sinytra/adapter/patch/processor/Processors.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java index f574aee3..d0450988 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/Processors.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java @@ -1,7 +1,7 @@ -package org.sinytra.adapter.patch.processor; +package org.sinytra.adapter.pipeline.processor; -import org.sinytra.adapter.env.util.OrderedRegistry; -import org.sinytra.adapter.patch.processor.extract.ExtractMixinProcessor; +import org.sinytra.adapter.pipeline.processor.extract.ExtractMixinProcessor; +import org.sinytra.adapter.util.OrderedRegistry; public class Processors extends OrderedRegistry { diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/PropertyProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/PropertyProcessor.java similarity index 70% rename from core/src/main/java/org/sinytra/adapter/patch/processor/PropertyProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/PropertyProcessor.java index b477dd2d..41aafa52 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/PropertyProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/PropertyProcessor.java @@ -1,20 +1,21 @@ -package org.sinytra.adapter.patch.processor; - -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.PropertyKey; -import org.sinytra.adapter.patch.config.key.MixinKeys; - +package org.sinytra.adapter.pipeline.processor; + +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyKey; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.List; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; public class PropertyProcessor implements Processor { - private static final Set> ACCEPTED_KEYS = Set.of( + private static final List> ACCEPTED_KEYS = List.of( MixinKeys.TARGET_METHOD, MixinKeys.TARGET_AT, MixinKeys.TARGET_CONSTANT, MixinKeys.ORDINAL, MixinKeys.INDEX, MixinKeys.SLICE, MixinKeys.SLICES, MixinKeys.ARGS_ONLY ); diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/ReturnTypeProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java similarity index 83% rename from core/src/main/java/org/sinytra/adapter/patch/processor/ReturnTypeProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java index cf5b1a4c..728e430e 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/ReturnTypeProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java @@ -1,21 +1,21 @@ -package org.sinytra.adapter.patch.processor; +package org.sinytra.adapter.pipeline.processor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.Frame; import org.objectweb.asm.tree.analysis.SourceValue; +import org.sinytra.adapter.analysis.flow.FrameUtil; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.types.TypeAdapter; -import org.objectweb.asm.tree.analysis.Frame; -import org.sinytra.adapter.analysis.selector.FrameUtil; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; -import java.util.Set; import java.util.LinkedHashSet; +import java.util.Set; public class ReturnTypeProcessor implements Processor { @Override diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/SplitCancellationProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java similarity index 56% rename from core/src/main/java/org/sinytra/adapter/patch/processor/SplitCancellationProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java index ce3b156c..97d7b181 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/SplitCancellationProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java @@ -1,12 +1,12 @@ -package org.sinytra.adapter.patch.processor; +package org.sinytra.adapter.pipeline.processor; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.patch.resolver.target.SplitMethodCancellationHelper; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.target.SplitMethodCancellationHelper; public class SplitCancellationProcessor implements Processor { @Override diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/StaticAccessProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/patch/processor/StaticAccessProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java index bb1ce34f..1ba0fd2d 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/StaticAccessProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java @@ -1,16 +1,16 @@ -package org.sinytra.adapter.patch.processor; +package org.sinytra.adapter.pipeline.processor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.MethodHelper; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.key.SpecialKeys; +import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.SingleValueHandle; diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/TargetMethodProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java similarity index 80% rename from core/src/main/java/org/sinytra/adapter/patch/processor/TargetMethodProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java index 3aa9918a..5439cd5e 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/TargetMethodProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java @@ -1,12 +1,12 @@ -package org.sinytra.adapter.patch.processor; +package org.sinytra.adapter.pipeline.processor; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; -import org.sinytra.adapter.env.ctx.LocalVariable; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; +import org.sinytra.adapter.analysis.locals.LocalVariable; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/extract/ExtractMixinProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java similarity index 96% rename from core/src/main/java/org/sinytra/adapter/patch/processor/extract/ExtractMixinProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java index 29326a49..796f7007 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/extract/ExtractMixinProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java @@ -1,31 +1,31 @@ -package org.sinytra.adapter.patch.processor.extract; +package org.sinytra.adapter.pipeline.processor.extract; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; -import org.sinytra.adapter.env.ctx.LocalVariable; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.patch.processor.Processor; import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; +import org.sinytra.adapter.analysis.locals.LocalVariable; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.transform.param.TransformParameters; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.edit.param.TransformParameters; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processor; import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.OpcodeUtil; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.util.SingleValueHandle; -import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/extract/MirrorableExtractMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/patch/processor/extract/MirrorableExtractMixin.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java index b27eecd9..44cdcad2 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/extract/MirrorableExtractMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java @@ -1,26 +1,30 @@ -package org.sinytra.adapter.patch.processor.extract; +package org.sinytra.adapter.pipeline.processor.extract; import com.google.common.collect.ImmutableList; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.ctx.*; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.env.util.TypeConstants; -import org.sinytra.adapter.patch.Recipe; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.MethodQualifier; import org.sinytra.adapter.util.OpcodeUtil; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_METHOD; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_METHOD; // TODO Cleanup public class MirrorableExtractMixin { diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/extract/ProxyExtractMixinSub.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java similarity index 80% rename from core/src/main/java/org/sinytra/adapter/patch/processor/extract/ProxyExtractMixinSub.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java index 24112769..0085f786 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/extract/ProxyExtractMixinSub.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java @@ -1,25 +1,25 @@ -package org.sinytra.adapter.patch.processor.extract; +package org.sinytra.adapter.pipeline.processor.extract; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.patch.processor.Processor; import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processor; import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.PROPERTY_ARGS_ONLY; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.PROPERTY_INDEX; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ARGS_ONLY; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_INDEX; // Create an identical mixin in the destination class and call the old one public class ProxyExtractMixinSub implements Processor { diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/redirect/DivertRedirectProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java similarity index 78% rename from core/src/main/java/org/sinytra/adapter/patch/processor/redirect/DivertRedirectProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java index ecdd9f2c..b4c3f7e9 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/redirect/DivertRedirectProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java @@ -1,21 +1,21 @@ -package org.sinytra.adapter.patch.processor.redirect; +package org.sinytra.adapter.pipeline.processor.redirect; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.InstructionAdapter; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.patch.processor.Processor; -import org.sinytra.adapter.util.MethodQualifier; import org.sinytra.adapter.analysis.InsnTraversal; import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processor; import java.util.function.Consumer; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; public class DivertRedirectProcessor implements Processor { @Override diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/redirect/ParameterUsageProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java similarity index 85% rename from core/src/main/java/org/sinytra/adapter/patch/processor/redirect/ParameterUsageProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java index c164f339..bc570c8e 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/redirect/ParameterUsageProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java @@ -1,23 +1,23 @@ -package org.sinytra.adapter.patch.processor.redirect; +package org.sinytra.adapter.pipeline.processor.redirect; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodInsnNode; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.MethodParameters.ParamGroup; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.processor.Processor; +import org.sinytra.adapter.edit.param.InjectParameterTransform; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processor; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.transform.param.InjectParameterTransform; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpParamsProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpParamsProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java index 8f2bca3d..e0fe9a91 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpParamsProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java @@ -1,16 +1,17 @@ -package org.sinytra.adapter.patch.processor.wrapop; +package org.sinytra.adapter.pipeline.processor.wrapop; import com.mojang.logging.LogUtils; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.Parameter; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.TxResult; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.processor.Processor; +import org.sinytra.adapter.analysis.method.WrapOpOriginalCall; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processor; import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; diff --git a/core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpSurgeon.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpSurgeon.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java index daba6e89..c9d567c8 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/processor/wrapop/WrapOpSurgeon.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.patch.processor.wrapop; +package org.sinytra.adapter.pipeline.processor.wrapop; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; @@ -7,14 +7,15 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.patch.Recipe; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.types.TypeAdapter; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.method.WrapOpOriginalCall; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.OpcodeUtil; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import java.util.ArrayList; import java.util.HashMap; diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/CompoundResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/CompoundResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java index d470ac0f..db07a380 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/CompoundResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java @@ -1,9 +1,9 @@ -package org.sinytra.adapter.patch.resolver; +package org.sinytra.adapter.pipeline.resolver; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/Resolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolver.java similarity index 84% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/Resolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolver.java index 36b4a174..2a80ebeb 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/Resolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolver.java @@ -1,10 +1,10 @@ -package org.sinytra.adapter.patch.resolver; +package org.sinytra.adapter.pipeline.resolver; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.Optional; diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/Resolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java similarity index 56% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/Resolvers.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java index 05a95668..6263c5cc 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/Resolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java @@ -1,9 +1,9 @@ -package org.sinytra.adapter.patch.resolver; +package org.sinytra.adapter.pipeline.resolver; -import org.sinytra.adapter.env.util.OrderedRegistry; -import org.sinytra.adapter.patch.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.patch.resolver.special.SliceBoundaryResolver; -import org.sinytra.adapter.patch.resolver.target.TargetMethodResolver; +import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.special.SliceBoundaryResolver; +import org.sinytra.adapter.pipeline.resolver.target.TargetMethodResolver; +import org.sinytra.adapter.util.OrderedRegistry; public class Resolvers extends OrderedRegistry { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SubResolver.java new file mode 100644 index 00000000..bf1a8235 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SubResolver.java @@ -0,0 +1,11 @@ +package org.sinytra.adapter.pipeline.resolver; + +import org.jetbrains.annotations.Nullable; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +public interface SubResolver { + @Nullable + Configuration resolve(MixinContext context, Recipe recipe); +} diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/ArbitraryInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java similarity index 85% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/injection/ArbitraryInjectionPointSubResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java index cfcb8269..6d17f7e1 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/ArbitraryInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java @@ -1,28 +1,28 @@ -package org.sinytra.adapter.patch.resolver.injection; +package org.sinytra.adapter.pipeline.resolver.injection; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.InsnComparator; -import org.sinytra.adapter.analysis.InstructionMatcher; -import org.sinytra.adapter.analysis.method.MethodInsnMatcher; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.mixin.MixinFlag; -import org.sinytra.adapter.patch.resolver.SubResolver; -import org.sinytra.adapter.util.MethodQualifier; import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.analysis.compare.InsnComparator; +import org.sinytra.adapter.analysis.compare.InstructionMatcher; +import org.sinytra.adapter.analysis.method.MethodInsnMatcher; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.pipeline.resolver.SubResolver; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.function.UnaryOperator; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; public class ArbitraryInjectionPointSubResolver implements SubResolver { @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/AtVariableAssignStoreSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java similarity index 87% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/injection/AtVariableAssignStoreSubResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java index 65abd8f7..97006af1 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/AtVariableAssignStoreSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java @@ -1,22 +1,22 @@ -package org.sinytra.adapter.patch.resolver.injection; +package org.sinytra.adapter.pipeline.resolver.injection; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.mixin.MixinFlag; -import org.sinytra.adapter.patch.resolver.SubResolver; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.pipeline.resolver.SubResolver; import org.sinytra.adapter.util.OpcodeUtil; -import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; -import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.env.ctx.Targets; import java.util.List; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; /** * Find our new injection point with relation to variable assignments diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java similarity index 90% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/injection/ComparingInjectionPointResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java index e87e91ad..066671d4 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/ComparingInjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java @@ -1,37 +1,38 @@ -package org.sinytra.adapter.patch.resolver.injection; +package org.sinytra.adapter.pipeline.resolver.injection; import com.google.common.collect.Multimap; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ann.ConstantData; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.MethodParameters.ParamGroup; -import org.sinytra.adapter.env.param.Parameter; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.env.util.TypeConstants; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.*; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.patch.processor.wrapop.WrapOpSurgeon; -import org.sinytra.adapter.patch.resolver.SubResolver; -import org.sinytra.adapter.analysis.MethodLabelComparator; +import org.sinytra.adapter.analysis.compare.MethodLabelComparator; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.env.ctx.Targets; import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.ann.ConstantData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.wrapop.WrapOpSurgeon; +import org.sinytra.adapter.pipeline.resolver.SubResolver; import java.util.Collection; import java.util.List; import java.util.Optional; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.PROPERTY_ORDINAL; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ORDINAL; public abstract class ComparingInjectionPointResolver implements SubResolver { diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InheritedInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java similarity index 74% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InheritedInjectionPointSubResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java index 15d1e8e6..0e66f2e5 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InheritedInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java @@ -1,30 +1,30 @@ -package org.sinytra.adapter.patch.resolver.injection; +package org.sinytra.adapter.pipeline.resolver.injection; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.param.MethodParameters; -import org.sinytra.adapter.env.param.MethodParameters.ParamGroup; -import org.sinytra.adapter.env.param.Parameter; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.mixin.MixinFlag; -import org.sinytra.adapter.patch.resolver.SubResolver; -import org.sinytra.adapter.env.ctx.PatchContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.types.BytecodeFixerUpper; -import org.sinytra.adapter.util.MethodQualifier; import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.api.BytecodeFixerUpper; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.ctx.PatchContext; +import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.pipeline.resolver.SubResolver; import java.util.List; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; public class InheritedInjectionPointSubResolver implements SubResolver { @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java similarity index 80% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java index d20851f4..60acefac 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java @@ -1,14 +1,14 @@ -package org.sinytra.adapter.patch.resolver.injection; +package org.sinytra.adapter.pipeline.resolver.injection; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.resolver.CompoundResolver; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.CompoundResolver; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointSubResolvers.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java index 80248398..f4a5ae04 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java @@ -1,27 +1,27 @@ -package org.sinytra.adapter.patch.resolver.injection; +package org.sinytra.adapter.pipeline.resolver.injection; import com.google.common.collect.Multimap; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.InstructionMatcher; +import org.sinytra.adapter.analysis.compare.CodePaths; +import org.sinytra.adapter.analysis.compare.CodePaths.CodePath; +import org.sinytra.adapter.analysis.compare.CodePaths.CodePathStep; +import org.sinytra.adapter.analysis.compare.InstructionMatcher; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodInsnMatcher; -import org.sinytra.adapter.analysis.tree.CodePaths; -import org.sinytra.adapter.analysis.tree.CodePaths.CodePath; -import org.sinytra.adapter.analysis.tree.CodePaths.CodePathStep; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.env.util.WeighedDisambiguation; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.patch.resolver.SubResolver; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.SubResolver; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.env.ctx.Targets; +import org.sinytra.adapter.util.WeighedDisambiguation; import java.util.List; import java.util.Objects; diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/ModifyVarInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java similarity index 82% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/injection/ModifyVarInjectionPointSubResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java index a4b0a97d..739dc857 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/ModifyVarInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java @@ -1,21 +1,20 @@ -package org.sinytra.adapter.patch.resolver.injection; +package org.sinytra.adapter.pipeline.resolver.injection; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.resolver.SubResolver; import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.env.ctx.Targets; - - -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_STORE; -import static org.sinytra.adapter.patch.config.key.MixinKeys.ORDINAL; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.SubResolver; + +import static org.sinytra.adapter.model.config.key.MixinKeys.ORDINAL; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_STORE; public class ModifyVarInjectionPointSubResolver implements SubResolver { diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/OverloadedInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java similarity index 79% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/injection/OverloadedInjectionPointSubResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java index 26ad42a4..50b52f5d 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/OverloadedInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java @@ -1,23 +1,23 @@ -package org.sinytra.adapter.patch.resolver.injection; +package org.sinytra.adapter.pipeline.resolver.injection; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.resolver.SubResolver; -import org.sinytra.adapter.util.MethodQualifier; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.env.ctx.Targets; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.SubResolver; import java.util.List; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; public class OverloadedInjectionPointSubResolver implements SubResolver { @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/InjectorOrdinalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/special/InjectorOrdinalResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java index 0155fe5a..06509c4a 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/InjectorOrdinalResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java @@ -1,37 +1,37 @@ -package org.sinytra.adapter.patch.resolver.special; +package org.sinytra.adapter.pipeline.resolver.special; import com.google.common.collect.Multimap; import com.mojang.datafixers.util.Pair; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.InsnComparator; -import org.sinytra.adapter.analysis.InstructionMatcher; +import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.analysis.compare.InsnComparator; +import org.sinytra.adapter.analysis.compare.InstructionMatcher; import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodInsnMatcher; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.mixin.MixinFlag; -import org.sinytra.adapter.patch.resolver.Resolver; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.pipeline.resolver.Resolver; import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.GeneratedVariables; -import org.sinytra.adapter.util.MethodQualifier; -import org.sinytra.adapter.util.SingleValueHandle; import org.sinytra.adapter.util.OpcodeUtil; -import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.util.SingleValueHandle; import java.util.*; import java.util.function.Function; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_RETURN; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_RETURN; public class InjectorOrdinalResolver implements Resolver { private static final Map OFFSET_HANDLERS = Map.of( diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/ModifyVarAtReturnResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java similarity index 86% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/special/ModifyVarAtReturnResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java index 81c131c4..406929bf 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/ModifyVarAtReturnResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java @@ -1,30 +1,30 @@ -package org.sinytra.adapter.patch.resolver.special; +package org.sinytra.adapter.pipeline.resolver.special; import com.mojang.datafixers.util.Pair; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.key.MixinKeys; -import org.sinytra.adapter.patch.resolver.Resolver; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.env.MockMixinRuntime; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.mixin.MockMixinRuntime; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.Resolver; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.Target; -import org.sinytra.adapter.env.ctx.Targets; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import java.util.List; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_RETURN; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_RETURN; /** * Original mixin: diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/ModifyVarUpgradeResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarUpgradeResolver.java similarity index 79% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/special/ModifyVarUpgradeResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarUpgradeResolver.java index 88eb6bad..b9357413 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/ModifyVarUpgradeResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarUpgradeResolver.java @@ -1,15 +1,15 @@ -package org.sinytra.adapter.patch.resolver.special; +package org.sinytra.adapter.pipeline.resolver.special; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.resolver.Resolver; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configurations; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.Resolver; import org.spongepowered.asm.mixin.injection.At; -import org.sinytra.adapter.patch.config.Configurations; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; /** * Upgrade a ModifyVar to a ModifyExpressionVal diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/ResolverSyntheticInstanceof.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ResolverSyntheticInstanceof.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/special/ResolverSyntheticInstanceof.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ResolverSyntheticInstanceof.java index d08a7bd7..8f416fb9 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/ResolverSyntheticInstanceof.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ResolverSyntheticInstanceof.java @@ -1,27 +1,27 @@ -package org.sinytra.adapter.patch.resolver.special; +package org.sinytra.adapter.pipeline.resolver.special; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.patch.config.Configurations; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.resolver.Resolver; -import org.sinytra.adapter.analysis.InsnComparator; -import org.sinytra.adapter.analysis.InstructionMatcher; -import org.sinytra.adapter.analysis.method.MethodInsnMatcher; -import org.sinytra.adapter.env.ctx.TargetPair; import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.analysis.compare.InsnComparator; +import org.sinytra.adapter.analysis.compare.InstructionMatcher; +import org.sinytra.adapter.analysis.method.MethodInsnMatcher; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.Configurations; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.Resolver; import java.util.ArrayList; import java.util.List; import java.util.Map; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_SINYTRA_INSTANCEOF; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_SINYTRA_INSTANCEOF; /** *

diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/SliceBoundaryResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java similarity index 84% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/special/SliceBoundaryResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java index a34fbdc4..9499e7ff 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/special/SliceBoundaryResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java @@ -1,26 +1,26 @@ -package org.sinytra.adapter.patch.resolver.special; +package org.sinytra.adapter.pipeline.resolver.special; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ann.SliceData; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.resolver.Resolver; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.ann.SliceData; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.Resolver; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import static org.sinytra.adapter.env.util.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.patch.config.key.MixinKeys.SLICE; -import static org.sinytra.adapter.patch.config.key.MixinKeys.SLICES; +import static org.sinytra.adapter.model.config.key.MixinKeys.SLICE; +import static org.sinytra.adapter.model.config.key.MixinKeys.SLICES; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; public class SliceBoundaryResolver implements Resolver { @Override diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/target/SplitMethodCancellationHelper.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java similarity index 93% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/target/SplitMethodCancellationHelper.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java index 84333fc2..821b7e0c 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/target/SplitMethodCancellationHelper.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.patch.resolver.target; +package org.sinytra.adapter.pipeline.resolver.target; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Label; @@ -7,15 +7,15 @@ import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.patch.Recipe; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.env.ctx.MixinClassGenerator; -import org.sinytra.adapter.env.util.TypeConstants; -import org.sinytra.adapter.env.ctx.TargetPair; +import org.sinytra.adapter.api.MixinClassGenerator; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.MethodQualifier; import java.util.List; import java.util.stream.IntStream; diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/target/SplitTargetMethodSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java similarity index 89% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/target/SplitTargetMethodSubResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java index b7e8e396..e0345c4a 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/target/SplitTargetMethodSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java @@ -1,21 +1,21 @@ -package org.sinytra.adapter.patch.resolver.target; +package org.sinytra.adapter.pipeline.resolver.target; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.config.key.SpecialKeys; -import org.sinytra.adapter.patch.resolver.SubResolver; -import org.sinytra.adapter.analysis.InsnComparator; -import org.sinytra.adapter.analysis.InstructionMatcher; +import org.sinytra.adapter.analysis.compare.InsnComparator; +import org.sinytra.adapter.analysis.compare.InstructionMatcher; import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; import org.sinytra.adapter.analysis.method.MethodInsnMatcher; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.SubResolver; import java.util.List; import java.util.Objects; diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/target/TargetMethodResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java similarity index 78% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/target/TargetMethodResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java index 0d3efb43..ff2faae2 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/target/TargetMethodResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java @@ -1,10 +1,10 @@ -package org.sinytra.adapter.patch.resolver.target; +package org.sinytra.adapter.pipeline.resolver.target; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.resolver.CompoundResolver; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.CompoundResolver; public class TargetMethodResolver extends CompoundResolver { public TargetMethodResolver() { diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/target/TargetMethodSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java similarity index 91% rename from core/src/main/java/org/sinytra/adapter/patch/resolver/target/TargetMethodSubResolvers.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java index 2891c298..28f530e2 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/target/TargetMethodSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.patch.resolver.target; +package org.sinytra.adapter.pipeline.resolver.target; import com.mojang.datafixers.util.Pair; import org.jetbrains.annotations.Nullable; @@ -6,16 +6,16 @@ import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.env.ctx.TargetPair; -import org.sinytra.adapter.env.param.Parameters; -import org.sinytra.adapter.patch.Recipe; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.patch.config.MutableConfiguration; -import org.sinytra.adapter.patch.resolver.Resolver; -import org.sinytra.adapter.patch.resolver.SubResolver; -import org.sinytra.adapter.patch.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.util.MethodQualifier; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.Resolver; +import org.sinytra.adapter.pipeline.resolver.SubResolver; +import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import java.util.Comparator; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/transform/ClassTransformer.java b/core/src/main/java/org/sinytra/adapter/transform/ClassTransformer.java deleted file mode 100644 index 18ebf08c..00000000 --- a/core/src/main/java/org/sinytra/adapter/transform/ClassTransformer.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sinytra.adapter.transform; - -import org.objectweb.asm.tree.ClassNode; -import org.sinytra.adapter.env.ann.ClassTarget; -import org.sinytra.adapter.env.ctx.PatchContext; -import org.sinytra.adapter.env.ctx.PatchResult; - -public interface ClassTransformer { - PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchContext context); -} diff --git a/core/src/main/java/org/sinytra/adapter/transform/MethodTransformer.java b/core/src/main/java/org/sinytra/adapter/transform/MethodTransformer.java deleted file mode 100644 index 0c97b1d9..00000000 --- a/core/src/main/java/org/sinytra/adapter/transform/MethodTransformer.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.sinytra.adapter.transform; - -import org.sinytra.adapter.env.ctx.MixinContext; -import org.sinytra.adapter.patch.config.Configuration; -import org.sinytra.adapter.env.ctx.PatchResult; - -public interface MethodTransformer { - PatchResult apply(MixinContext context, Configuration config); -} diff --git a/core/src/main/java/org/sinytra/adapter/transform/param/ParamTransformationUtil.java b/core/src/main/java/org/sinytra/adapter/transform/param/ParamTransformationUtil.java deleted file mode 100644 index 521f05f0..00000000 --- a/core/src/main/java/org/sinytra/adapter/transform/param/ParamTransformationUtil.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.sinytra.adapter.transform.param; - -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.util.AdapterUtil; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Consumer; - -public final class ParamTransformationUtil { - public static void updateParameterAnnotationArrays(MethodNode methodNode, int paramCount, Consumer>> action) { - methodNode.visibleParameterAnnotations = updateParameterAnnotations(methodNode.visibleParameterAnnotations, paramCount, action); - methodNode.invisibleParameterAnnotations = updateParameterAnnotations(methodNode.invisibleParameterAnnotations, paramCount, action); - // Derive the count from the final descriptor later - methodNode.visibleAnnotableParameterCount = 0; - methodNode.invisibleAnnotableParameterCount = 0; - } - - @Nullable - @SuppressWarnings("unchecked") - private static List[] updateParameterAnnotations(@Nullable List[] annotations, int paramCount, Consumer>> action) { - if (annotations == null) { - return null; - } - List> list = new ArrayList<>(Arrays.asList(annotations)); - while (list.size() < paramCount) { - list.add(null); - } - action.accept(list); - return list.toArray(List[]::new); - } - - public static void reorderParameters(MethodNode methodNode, List parameters, boolean nonStatic, int[] order) { - List permuted = Arrays.stream(order).mapToObj(parameters::get).toList(); - - Int2IntMap slots = new Int2IntOpenHashMap(); - for (int pos = 0; pos < order.length; pos++) { - slots.put(getLVTIndex(parameters, nonStatic, order[pos]), getLVTIndex(permuted, nonStatic, pos)); - } - AdapterUtil.replaceLVT(methodNode, idx -> slots.getOrDefault(idx, idx)); - if (methodNode.localVariables != null) { - for (LocalVariableNode lvn : methodNode.localVariables) { - lvn.index = slots.getOrDefault(lvn.index, lvn.index); - } - } - - updateParameterAnnotationArrays(methodNode, parameters.size(), annotations -> { - List> original = new ArrayList<>(annotations); - for (int pos = 0; pos < order.length; pos++) { - annotations.set(pos, original.get(order[pos])); - } - }); - - for (int pos = 0; pos < order.length; pos++) { - parameters.set(pos, permuted.get(pos)); - } - } - - public static int getLVTIndex(List parameters, boolean nonStatic, int index) { - int lvt = nonStatic ? 1 : 0; - for (int i = 0; i < index; i++) { - lvt += parameters.get(i).getSize(); - } - return lvt; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java index cf301ad0..bcad2d96 100644 --- a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java @@ -7,16 +7,22 @@ import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.env.ann.ClassTarget; -import org.sinytra.adapter.env.ctx.MethodHelper; -import org.sinytra.adapter.env.ctx.PatchContext; -import org.sinytra.adapter.env.ctx.PatchResult; -import org.sinytra.adapter.env.util.MixinAnnotations; -import org.sinytra.adapter.transform.ClassTransformer; -import org.sinytra.adapter.util.AdapterUtil; +import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.api.BytecodeFixerUpper; +import org.sinytra.adapter.api.ClassTransformer; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.AnnotationValueHandle; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.pipeline.ctx.PatchContext; import org.sinytra.adapter.util.OpcodeUtil; -import org.sinytra.adapter.env.util.TypeConstants; +import org.spongepowered.asm.mixin.gen.AccessorInfo; + +import java.util.Optional; public class FieldAccessorTypeTransformer implements ClassTransformer { private static final String PREFIX = "adapter$"; @@ -43,7 +49,7 @@ public PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchCont } private PatchResult redirectAccessor(ClassNode classNode, MethodNode methodNode, AnnotationNode accessor, Type owner, BytecodeFixerUpper bfu, PatchContext context) { - String fieldFqn = AdapterUtil.getAccessorTargetFieldName(classNode.name, methodNode, new AnnotationHandle(accessor), context.environment()) + String fieldFqn = getAccessorTargetFieldName(classNode.name, methodNode, new AnnotationHandle(accessor), context.environment()) .orElse(null); if (fieldFqn == null) return PatchResult.PASS; @@ -127,4 +133,12 @@ private static String getFieldName(String desc) { int descIndex = desc.indexOf(':'); return descIndex == -1 ? desc : desc.substring(0, descIndex); } + + private static Optional getAccessorTargetFieldName(String owner, MethodNode method, AnnotationHandle annotationHandle, PatchEnvironment environment) { + return annotationHandle.getValue("value") + .map(AnnotationValueHandle::get) + .filter(str -> !str.isEmpty()) + .or(() -> Optional.ofNullable(AccessorInfo.AccessorName.of(method.name)) + .map(name -> environment.refmapHolder().remap(owner, name.name))); + } } diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java index a094b9ec..7efcf520 100644 --- a/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java @@ -5,13 +5,15 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.ann.ClassTarget; -import org.sinytra.adapter.transform.ClassTransformer; -import org.sinytra.adapter.env.ctx.PatchContext; -import org.sinytra.adapter.env.ctx.PatchResult; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.api.BytecodeFixerUpper; +import org.sinytra.adapter.api.ClassTransformer; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.pipeline.ctx.PatchContext; import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import java.util.HashMap; import java.util.Map; diff --git a/core/src/main/java/org/sinytra/adapter/types/ObjectTypeAdapter.java b/core/src/main/java/org/sinytra/adapter/types/ObjectTypeAdapter.java index 2d65210b..460d9fd9 100644 --- a/core/src/main/java/org/sinytra/adapter/types/ObjectTypeAdapter.java +++ b/core/src/main/java/org/sinytra/adapter/types/ObjectTypeAdapter.java @@ -5,7 +5,8 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.TypeInsnNode; -import org.sinytra.adapter.env.util.TypeConstants; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.constant.TypeConstants; public class ObjectTypeAdapter implements TypeAdapterProvider { public static final TypeAdapterProvider INSTANCE = new ObjectTypeAdapter(); diff --git a/core/src/main/java/org/sinytra/adapter/types/SupplierTypeAdapter.java b/core/src/main/java/org/sinytra/adapter/types/SupplierTypeAdapter.java index 7336bf2a..1064e74b 100644 --- a/core/src/main/java/org/sinytra/adapter/types/SupplierTypeAdapter.java +++ b/core/src/main/java/org/sinytra/adapter/types/SupplierTypeAdapter.java @@ -6,6 +6,7 @@ import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; +import org.sinytra.adapter.api.TypeAdapter; import java.util.function.Supplier; diff --git a/core/src/main/java/org/sinytra/adapter/types/TypeAdapterProvider.java b/core/src/main/java/org/sinytra/adapter/types/TypeAdapterProvider.java index 2d8b4ceb..9b714a5d 100644 --- a/core/src/main/java/org/sinytra/adapter/types/TypeAdapterProvider.java +++ b/core/src/main/java/org/sinytra/adapter/types/TypeAdapterProvider.java @@ -1,6 +1,7 @@ package org.sinytra.adapter.types; import org.objectweb.asm.Type; +import org.sinytra.adapter.api.TypeAdapter; public interface TypeAdapterProvider { TypeAdapter provide(Type from, Type to); diff --git a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java index 01b4ec85..e31f171d 100644 --- a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java +++ b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java @@ -10,13 +10,9 @@ import org.objectweb.asm.tree.analysis.SourceValue; import org.objectweb.asm.util.Textifier; import org.objectweb.asm.util.TraceMethodVisitor; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.analysis.selector.AnnotationValueHandle; -import org.sinytra.adapter.env.ctx.PatchEnvironment; -import org.sinytra.adapter.env.util.MixinAnnotations; +import org.sinytra.adapter.model.constant.MixinAnnotations; import org.slf4j.Marker; import org.slf4j.MarkerFactory; -import org.spongepowered.asm.mixin.gen.AccessorInfo; import java.io.PrintWriter; import java.io.StringWriter; @@ -61,14 +57,6 @@ public static boolean isAnonymousClass(String name) { return name.matches("^.+\\$\\d+$"); } - public static Optional getAccessorTargetFieldName(String owner, MethodNode method, AnnotationHandle annotationHandle, PatchEnvironment environment) { - return annotationHandle.getValue("value") - .map(AnnotationValueHandle::get) - .filter(str -> !str.isEmpty()) - .or(() -> Optional.ofNullable(AccessorInfo.AccessorName.of(method.name)) - .map(name -> environment.refmapHolder().remap(owner, name.name))); - } - @Nullable public static SingleValueHandle handleLocalVarInsnValue(AbstractInsnNode insn) { if (insn instanceof VarInsnNode varInsn) { diff --git a/core/src/main/java/org/sinytra/adapter/env/util/OrderedRegistry.java b/core/src/main/java/org/sinytra/adapter/util/OrderedRegistry.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/env/util/OrderedRegistry.java rename to core/src/main/java/org/sinytra/adapter/util/OrderedRegistry.java index 4a433131..9c7bebbf 100644 --- a/core/src/main/java/org/sinytra/adapter/env/util/OrderedRegistry.java +++ b/core/src/main/java/org/sinytra/adapter/util/OrderedRegistry.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.env.util; +package org.sinytra.adapter.util; import org.jetbrains.annotations.Nullable; diff --git a/core/src/main/java/org/sinytra/adapter/env/util/WeighedDisambiguation.java b/core/src/main/java/org/sinytra/adapter/util/WeighedDisambiguation.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/env/util/WeighedDisambiguation.java rename to core/src/main/java/org/sinytra/adapter/util/WeighedDisambiguation.java index 91693f3e..aa668ae9 100644 --- a/core/src/main/java/org/sinytra/adapter/env/util/WeighedDisambiguation.java +++ b/core/src/main/java/org/sinytra/adapter/util/WeighedDisambiguation.java @@ -1,7 +1,6 @@ -package org.sinytra.adapter.env.util; +package org.sinytra.adapter.util; import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.util.AdapterUtil; import java.util.ArrayList; import java.util.List; diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/BytecodeFixerUpperTestFrontend.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/BytecodeFixerUpperTestFrontend.java index 22447150..fd04addd 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/BytecodeFixerUpperTestFrontend.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/BytecodeFixerUpperTestFrontend.java @@ -6,10 +6,10 @@ import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; -import org.sinytra.adapter.types.BytecodeFixerUpper; -import org.sinytra.adapter.types.SimpleTypeAdapter; -import org.sinytra.adapter.types.TypeAdapter; -import org.sinytra.adapter.util.provider.ClassLookup; +import org.sinytra.adapter.api.BytecodeFixerUpper; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.SimpleTypeAdapter; +import org.sinytra.adapter.api.TypeAdapter; import java.util.List; diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java index 29c60233..432a7368 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java @@ -6,13 +6,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; import org.objectweb.asm.tree.ClassNode; -import org.sinytra.adapter.env.ctx.PatchEnvironment; -import org.sinytra.adapter.env.ctx.RefmapHolder; -import org.sinytra.adapter.patch.DynamicPatches; -import org.sinytra.adapter.patch.Patcher; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.DynamicPatches; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.api.Patcher; +import org.sinytra.adapter.api.RefmapHolder; import org.sinytra.adapter.patch.test_main.mixin.MinecraftMixinPatchTest; import org.sinytra.adapter.types.FieldTypeUsageTransformer; -import org.sinytra.adapter.util.provider.ClassLookup; import org.slf4j.Logger; import org.spongepowered.asm.mixin.FabricUtil; diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/BytecodeFixerUpperTestFrontend.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/BytecodeFixerUpperTestFrontend.java index b6976a5a..bf65a003 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/BytecodeFixerUpperTestFrontend.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/BytecodeFixerUpperTestFrontend.java @@ -6,10 +6,10 @@ import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; -import org.sinytra.adapter.types.BytecodeFixerUpper; -import org.sinytra.adapter.types.SimpleTypeAdapter; -import org.sinytra.adapter.types.TypeAdapter; -import org.sinytra.adapter.util.provider.ClassLookup; +import org.sinytra.adapter.api.BytecodeFixerUpper; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.SimpleTypeAdapter; +import org.sinytra.adapter.api.TypeAdapter; import java.util.List; diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 6cc36bb4..6e42890f 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -5,13 +5,13 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.objectweb.asm.tree.ClassNode; -import org.sinytra.adapter.env.ctx.PatchEnvironment; -import org.sinytra.adapter.env.ctx.RefmapHolder; -import org.sinytra.adapter.patch.DynamicPatches; -import org.sinytra.adapter.patch.Patcher; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.DynamicPatches; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.api.Patcher; +import org.sinytra.adapter.api.RefmapHolder; import org.sinytra.adapter.patch.test_main.mixin.MinecraftMixinPatchTest; import org.sinytra.adapter.types.FieldTypeUsageTransformer; -import org.sinytra.adapter.util.provider.ClassLookup; import org.slf4j.Logger; import org.spongepowered.asm.mixin.FabricUtil; diff --git a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java index 24256c8e..25111c87 100644 --- a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java +++ b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java @@ -5,15 +5,15 @@ import org.assertj.core.api.Assertions; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.selector.AnnotationHandle; -import org.sinytra.adapter.analysis.selector.AnnotationValueHandle; -import org.sinytra.adapter.env.ann.AtData; -import org.sinytra.adapter.env.ctx.MixinClassGenerator; -import org.sinytra.adapter.env.ctx.PatchEnvironment; -import org.sinytra.adapter.env.util.MixinAnnotations; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.MixinClassGenerator; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.api.ZipClassLookup; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.AnnotationValueHandle; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.provider.ClassLookup; -import org.sinytra.adapter.util.provider.ZipClassLookup; import org.slf4j.Logger; import java.io.IOException; diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/EnhancedParamsDiffTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/EnhancedParamsDiffTest.java index d4287ee6..9876964e 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/EnhancedParamsDiffTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/EnhancedParamsDiffTest.java @@ -4,9 +4,9 @@ import org.objectweb.asm.Type; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; -import org.sinytra.adapter.transform.param.InjectParameterTransform; -import org.sinytra.adapter.transform.param.ReplaceParametersTransformer; -import org.sinytra.adapter.transform.param.RemoveParameterTransformer; +import org.sinytra.adapter.edit.param.InjectParameterTransform; +import org.sinytra.adapter.edit.param.RemoveParameterTransformer; +import org.sinytra.adapter.edit.param.ReplaceParametersTransformer; import java.util.*; diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java index f97b058b..773dc760 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java @@ -2,11 +2,11 @@ import org.junit.jupiter.api.Test; import org.objectweb.asm.Type; -import org.sinytra.adapter.env.param.ParamDiffResolver; -import org.sinytra.adapter.env.param.ParamDiffResolver.ParamEvalResult; -import org.sinytra.adapter.env.param.ParamDiffResolver.ParamState; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; +import org.sinytra.adapter.analysis.params.ParamDiffResolver; +import org.sinytra.adapter.analysis.params.ParamDiffResolver.ParamEvalResult; +import org.sinytra.adapter.analysis.params.ParamDiffResolver.ParamState; import java.io.InputStream; import java.io.OutputStream; From de9e094ccc278a942afcac19a689dbce2908e213 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Sun, 11 Oct 2026 21:23:08 +0200 Subject: [PATCH 02/44] Unified MethodLocator for finding methods --- .../adapter/analysis/compare/CodePaths.java | 2 +- .../adapter/analysis/method/MethodHelper.java | 41 +---------- .../{MethodFinder.java => MethodLocator.java} | 69 +++++++++++-------- .../pipeline/PipelineMethodTransformer.java | 4 +- .../org/sinytra/adapter/pipeline/Recipe.java | 12 ++-- .../adapter/pipeline/ctx/MixinContext.java | 26 ++++++- .../injector/ModifyReturnValueMixin.java | 2 +- .../injector/ModifyVariableMixin.java | 2 +- .../pipeline/injector/RedirectMixin.java | 4 +- .../pipeline/injector/WrapOperationMixin.java | 2 +- .../LocalCaptureUpgradeTransformer.java | 2 +- .../ArbitraryInjectionPointSubResolver.java | 2 +- .../ComparingInjectionPointResolver.java | 4 +- .../injection/InjectionPointResolver.java | 2 +- .../injection/InjectionPointSubResolvers.java | 4 +- .../OverloadedInjectionPointSubResolver.java | 4 +- .../target/SplitTargetMethodSubResolver.java | 2 +- .../target/TargetMethodSubResolvers.java | 8 +-- 18 files changed, 98 insertions(+), 94 deletions(-) rename core/src/main/java/org/sinytra/adapter/analysis/method/{MethodFinder.java => MethodLocator.java} (53%) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java index 855dfa74..146f6367 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java @@ -78,7 +78,7 @@ private static CodePath findCodePathRecursive(TargetPair from, TargetPair to, Se continue; } - TargetPair nextFrom = context.methods().findInheritedMethodPair(context.dirtyLookup(), MethodQualifier.create(minsn)); + TargetPair nextFrom = context.dirtyMethods().resolveInherited(MethodQualifier.create(minsn)); if (nextFrom == null) continue; LocalVariableLookup lvt = context.methods().getLVT(nextFrom.methodNode()); diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java index 8595312a..c3e23991 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java @@ -1,6 +1,5 @@ package org.sinytra.adapter.analysis.method; -import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; @@ -11,11 +10,8 @@ import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.analysis.params.ParamDiffResolver; -import org.sinytra.adapter.api.ClassLookup; import org.sinytra.adapter.mixin.MockMixinRuntime; -import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; @@ -41,45 +37,14 @@ public class MethodHelper { private static final Logger LOGGER = LogUtils.getLogger(); private final MixinContext context; - private final MethodFinder methodFinder; private final Map> targetInstructionsCache = new HashMap<>(); private record TargetInsnsKey(@Nullable TargetPair target, boolean ignoreOrdinal, @Nullable Object injectionPoint, @Nullable Object methodAnnotation) { } - public MethodHelper(MixinContext context, List targetTypes) { + public MethodHelper(MixinContext context) { this.context = context; - - String singleTargetClass = targetTypes.getFirst().getInternalName(); - this.methodFinder = new MethodFinder(context.environment(), singleTargetClass); - } - - @Nullable - public TargetPair findInheritedMethodPair(ClassLookup lookup, MethodQualifier qualifier) { - return this.methodFinder.findInheritedMethod(lookup, qualifier); - } - - @Nullable - public TargetPair findMethodPair(ClassLookup lookup, MethodQualifier qualifier) { - return this.methodFinder.findMethod(lookup, qualifier, 0); - } - - @Nullable - public TargetPair findOwnMethodPair(ClassLookup lookup, MethodQualifier qualifier) { - return this.methodFinder.findMethod(lookup, qualifier, MethodFinder.Flags.FALLBACK_OWNER); - } - - @Nullable - public Targets findTargets(MethodQualifier qualifier) { - TargetPair clean = findOwnMethodPair(this.context.cleanLookup(), qualifier); - TargetPair dirty = findOwnMethodPair(this.context.dirtyLookup(), qualifier); - return clean != null && dirty != null ? new Targets(clean, dirty) : null; - } - - @Nullable - public Pair> findOwnMethodsByName(ClassLookup lookup, MethodQualifier qualifier) { - return this.methodFinder.findMethods(lookup, qualifier, MethodFinder.Flags.FALLBACK_OWNER | MethodFinder.Flags.IGNORE_DESC); } public boolean hasInjectionTargetInsns(@Nullable TargetPair target) { @@ -165,8 +130,8 @@ public List resolveCapturedMethodParams(Configuration clean, Configuration // Evaluate parameter difference, capture additional params when necessary if (!cleanCaptured.isEmpty()) { - TargetPair cleanPair = findOwnMethodPair(this.context.cleanLookup(), clean.getTargetMethod()); - TargetPair dirtyPair = findOwnMethodPair(this.context.dirtyLookup(), dirty.getTargetMethod()); + TargetPair cleanPair = this.context.cleanMethods().resolve(clean.getTargetMethod()); + TargetPair dirtyPair = this.context.dirtyMethods().resolve(dirty.getTargetMethod()); if (cleanPair == null || dirtyPair == null) return dirtyCaptured; MethodNode cleanTarget = cleanPair.methodNode(); MethodNode dirtyTarget = dirtyPair.methodNode(); diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodFinder.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java similarity index 53% rename from core/src/main/java/org/sinytra/adapter/analysis/method/MethodFinder.java rename to core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java index d99c2a8d..348a90a7 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodFinder.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java @@ -17,43 +17,63 @@ import java.util.List; import java.util.stream.Stream; -public class MethodFinder { +public final class MethodLocator { private static final Logger LOGGER = LogUtils.getLogger(); - public static class Flags { - public static final int IGNORE_DESC = 0b01; - public static final int FALLBACK_OWNER = 0b10; - } - private final PatchEnvironment environment; + private final ClassLookup lookup; @Nullable - private final String fallbackOwner; + private final String defaultOwner; - public MethodFinder(PatchEnvironment environment, String fallbackOwner) { + public MethodLocator(PatchEnvironment environment, ClassLookup lookup, @Nullable String defaultOwner) { this.environment = environment; - this.fallbackOwner = fallbackOwner; + this.lookup = lookup; + this.defaultOwner = defaultOwner; + } + + public ClassLookup lookup() { + return this.lookup; + } + + /// {@return a single method matching the qualifier, searched in the specified class or default owner} + @Nullable + public TargetPair resolve(MethodQualifier qualifier) { + return resolveSingle(qualifier, true); } + /// {@return the single method matching the qualifier in the class it specifies} @Nullable - public TargetPair findInheritedMethod(ClassLookup lookup, MethodQualifier qualifier) { + public TargetPair resolveDeclared(MethodQualifier qualifier) { + return resolveSingle(qualifier, false); + } + + /// {@return the first method matching the qualifier in the class it specifies or its parents} + @Nullable + public TargetPair resolveInherited(MethodQualifier qualifier) { String owner = qualifier.internalOwnerName(); if (owner == null || qualifier.name() == null) return null; - ClassNode node = lookup.getClass(owner).orElse(null); + ClassNode node = this.lookup.getClass(owner).orElse(null); if (node == null) return null; - Collection parents = this.environment.inheritanceHandler(lookup).getClassParents(node.name); + Collection parents = this.environment.inheritanceHandler(this.lookup).getClassParents(node.name); return Stream.concat(Stream.of(node.name), parents.stream()) - .flatMap(cls -> lookup.findMethod(cls, qualifier.name(), qualifier.desc()).stream() - .map(m -> new TargetPair(lookup.getClass(cls).orElseThrow(), m))) + .flatMap(cls -> this.lookup.findMethod(cls, qualifier.name(), qualifier.desc()).stream() + .map(m -> new TargetPair(this.lookup.getClass(cls).orElseThrow(), m))) .findFirst() .orElse(null); } + /// {@return all methods with the qualifier's name, searched in the specified class or default owner} + public List findByName(MethodQualifier qualifier) { + Pair> pair = findCandidates(qualifier, true, true); + return pair != null ? pair.getSecond() : List.of(); + } + @Nullable - public TargetPair findMethod(ClassLookup lookup, MethodQualifier qualifier, int flags) { - Pair> pair = findMethods(lookup, qualifier, flags); + private TargetPair resolveSingle(MethodQualifier qualifier, boolean useDefaultOwner) { + Pair> pair = findCandidates(qualifier, useDefaultOwner, false); if (pair == null) { return null; } @@ -70,7 +90,7 @@ public TargetPair findMethod(ClassLookup lookup, MethodQualifier qualifier, int } @Nullable - public Pair> findMethods(ClassLookup lookup, MethodQualifier qualifier, int flags) { + private Pair> findCandidates(@Nullable MethodQualifier qualifier, boolean useDefaultOwner, boolean ignoreDesc) { if (qualifier == null || qualifier.name() == null) { return null; } @@ -79,14 +99,14 @@ public Pair> findMethods(ClassLookup lookup, MethodQ String owner; if (qualifier.internalOwnerName() != null) { owner = qualifier.internalOwnerName(); - } else if (hasFlag(flags, Flags.FALLBACK_OWNER) && this.fallbackOwner != null) { - owner = this.fallbackOwner; + } else if (useDefaultOwner && this.defaultOwner != null) { + owner = this.defaultOwner; } else { return null; } // Find target class - ClassNode targetClass = lookup.getClass(owner).orElse(null); + ClassNode targetClass = this.lookup.getClass(owner).orElse(null); if (targetClass == null) { return null; } @@ -94,8 +114,7 @@ public Pair> findMethods(ClassLookup lookup, MethodQ // Find target method in class String desc = qualifier.desc(); List candidates = targetClass.methods.stream() - .filter(mtd -> mtd.name.equals(qualifier.name()) - && (hasFlag(flags, Flags.IGNORE_DESC) || desc == null || mtd.desc.equals(desc))) + .filter(mtd -> mtd.name.equals(qualifier.name()) && (ignoreDesc || desc == null || mtd.desc.equals(desc))) .toList(); // If there's multiple candidates, try removing bouncer methods @@ -104,13 +123,9 @@ public Pair> findMethods(ClassLookup lookup, MethodQ } if (candidates.isEmpty() && targetClass.superName != null) { - return findMethods(lookup, new MethodQualifier(Type.getObjectType(targetClass.superName).getDescriptor(), qualifier.name(), qualifier.desc()), flags); + return findCandidates(new MethodQualifier(Type.getObjectType(targetClass.superName).getDescriptor(), qualifier.name(), qualifier.desc()), useDefaultOwner, ignoreDesc); } return Pair.of(targetClass, candidates); } - - private static boolean hasFlag(int flags, int flag) { - return (flags & flag) != 0; - } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java index 2a48d04c..94a90613 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java @@ -47,10 +47,10 @@ public PipelineMethodTransformer(List methodPatches, boolean patche @Override public PatchResult apply(MixinContext context, Configuration config) { - TargetPair cleanTarget = context.methods().findOwnMethodPair(context.cleanLookup(), config.getTargetMethod()); + TargetPair cleanTarget = context.cleanMethods().resolve(config.getTargetMethod()); if (cleanTarget == null) return PatchResult.PASS; - TargetPair dirtyTarget = context.methods().findOwnMethodPair(context.dirtyLookup(), config.getTargetMethod()); + TargetPair dirtyTarget = context.dirtyMethods().resolve(config.getTargetMethod()); if (!this.patchResolver.matches(config) && !failsDirtyInjectionCheck(context, config, dirtyTarget) && hasValidSlice(context, config, dirtyTarget)) return PatchResult.PASS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java index 0254ac51..84726028 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java @@ -3,7 +3,7 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.analysis.method.MethodLocator; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; @@ -53,18 +53,18 @@ private Recipe(Configuration clean, Configuration dirty, Resolvers resolvers, Pr this.newCleanTarget = original.newCleanTarget; this.cleanLocalsTable = original.cleanLocalsTable; } else { - this.cleanTarget = Cached.of(() -> findTarget(context, context.cleanLookup(), clean.getTargetMethod())); - this.newCleanTarget = Cached.of(() -> findTarget(context, context.dirtyLookup(), clean.getTargetMethod())); + this.cleanTarget = Cached.of(() -> findTarget(context.cleanMethods(), clean.getTargetMethod())); + this.newCleanTarget = Cached.of(() -> findTarget(context.dirtyMethods(), clean.getTargetMethod())); this.cleanLocalsTable = Cached.of(() -> getLocalsTable(context, getCleanTarget())); } - this.dirtyTarget = Cached.keyed(dirty::getTargetMethod, target -> findTarget(context, context.dirtyLookup(), target)); + this.dirtyTarget = Cached.keyed(dirty::getTargetMethod, target -> findTarget(context.dirtyMethods(), target)); this.dirtyLocalsTable = Cached.keyed(this::getDirtyTarget, target -> getLocalsTable(context, target)); } @Nullable - private static TargetPair findTarget(MixinContext context, ClassLookup lookup, @Nullable MethodQualifier target) { - return target != null ? context.methods().findOwnMethodPair(lookup, target) : null; + private static TargetPair findTarget(MethodLocator locator, @Nullable MethodQualifier target) { + return target != null ? locator.resolve(target) : null; } @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java index 9300c52f..57639f85 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java @@ -5,10 +5,14 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodLocator; import org.sinytra.adapter.api.BytecodeFixerUpper; import org.sinytra.adapter.api.ClassLookup; import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.ClassTarget; import org.sinytra.adapter.model.ann.RefMapper; @@ -39,6 +43,8 @@ public class MixinContext implements RefMapper, Auditor { private final String mixinId; private final MethodHelper methodHelper; + private final MethodLocator cleanMethods; + private final MethodLocator dirtyMethods; private final Resolvers resolvers = new Resolvers(); private final Processors processors = new Processors(); @@ -52,7 +58,10 @@ public MixinContext(MixinType mixinType, PatchContext patchContext, ClassTarget this.injectionPointAnnotation = injectionPointAnnotation; this.flags = flags; - this.methodHelper = new MethodHelper(this, classTarget.getTypes()); + this.methodHelper = new MethodHelper(this); + String defaultOwner = classTarget.getTypes().getFirst().getInternalName(); + this.cleanMethods = new MethodLocator(patchContext.environment(), patchContext.environment().cleanClassLookup(), defaultOwner); + this.dirtyMethods = new MethodLocator(patchContext.environment(), patchContext.environment().dirtyClassLookup(), defaultOwner); this.originalMethodNode = AdapterUtil.copyMethod(this.methodNode); this.mixinId = classNode.name + "#" + methodNode.name + methodNode.desc; @@ -99,6 +108,21 @@ public MethodHelper methods() { return this.methodHelper; } + public MethodLocator cleanMethods() { + return this.cleanMethods; + } + + public MethodLocator dirtyMethods() { + return this.dirtyMethods; + } + + @Nullable + public Targets targets(MethodQualifier qualifier) { + TargetPair clean = this.cleanMethods.resolve(qualifier); + TargetPair dirty = this.dirtyMethods.resolve(qualifier); + return clean != null && dirty != null ? new Targets(clean, dirty) : null; + } + public ClassLookup cleanLookup() { return this.patchContext.environment().cleanClassLookup(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java index b3b63f53..4fb32c0c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java @@ -41,7 +41,7 @@ public boolean canInject(MixinContext context, Configuration config) { return true; } - Targets targets = context.methods().findTargets(target); + Targets targets = context.targets(target); return targets == null || MethodAnalyzer.findReturnInsns(targets.clean().methodNode()).size() == MethodAnalyzer.findReturnInsns(targets.dirty().methodNode()).size(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index 377c5cc7..1efdc0b8 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -96,7 +96,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo public boolean canInject(MixinContext context, Configuration config) { // Ensure variable at index has the correct type if (config.hasProperty(MixinKeys.INDEX)) { - TargetPair targetPair = context.methods().findOwnMethodPair(context.dirtyLookup(), config.getTargetMethod()); + TargetPair targetPair = context.dirtyMethods().resolve(config.getTargetMethod()); if (targetPair == null) return false; MethodNode target = targetPair.methodNode(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index 2a98534e..8565f51f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -85,7 +85,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo if (targetDesc == null || targetDesc.desc() == null) return TxResult.FAIL; - TargetPair dirtyTarget = context.methods().findOwnMethodPair(context.dirtyLookup(), targetDesc); + TargetPair dirtyTarget = context.dirtyMethods().resolve(targetDesc); if (dirtyTarget == null) return TxResult.FAIL; @@ -106,7 +106,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo } private boolean isStaticRedirect(MixinContext context, MethodQualifier targetDesc, List methodParams, List callTypes) { - TargetPair cleanTarget = context.methods().findOwnMethodPair(context.cleanLookup(), targetDesc); + TargetPair cleanTarget = context.cleanMethods().resolve(targetDesc); if (cleanTarget != null) { return MethodHelper.isStatic(cleanTarget.methodNode()); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index f368899b..762f5231 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -60,7 +60,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo return TxResult.FAIL; List dirtyCaptured = context.methods().resolveCapturedMethodParams(recipe.clean(), recipe.dirty()); - TargetPair targetMethodCall = dirtyAtTarget.isFull() ? context.methods().findInheritedMethodPair(context.dirtyLookup(), dirtyAtTarget) : null; + TargetPair targetMethodCall = dirtyAtTarget.isFull() ? context.dirtyMethods().resolveInherited(dirtyAtTarget) : null; Type receiver = targetMethodCall != null && !MethodHelper.isStatic(targetMethodCall.methodNode()) ? Type.getObjectType(dirtyAtTarget.internalOwnerName()) : null; List callTypes = Parameters.getCallOperandTypes(receiver, dirtyAtTarget.desc()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java index fe9197ca..2e0fccc9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java @@ -24,7 +24,7 @@ public class LocalCaptureUpgradeTransformer implements MethodTransformer { @Override public PatchResult apply(MixinContext context, Configuration config) { // Check requirements - Targets targets = context.methods().findTargets(config.getTargetMethod()); + Targets targets = context.targets(config.getTargetMethod()); if (targets == null) return PatchResult.PASS; if (!config.hasProperty(MixinKeys.LOCALS)) return PatchResult.PASS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java index 6d17f7e1..b5a63aa9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java @@ -37,7 +37,7 @@ public Configuration resolve(MixinContext context, Recipe recipe) { if (targetMethodCall == null) return null; if (targetMethodCall.owner.equals(dirtyTarget.classNode().name)) { - TargetPair target = context.methods().findOwnMethodPair(context.dirtyLookup(), MethodQualifier.create(targetMethodCall)); + TargetPair target = context.dirtyMethods().resolve(MethodQualifier.create(targetMethodCall)); if (target == null) return null; // New method is in the same class? It's possible our target injection point was moved there diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java index 066671d4..60ee841b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java @@ -76,7 +76,7 @@ public Configuration resolve(MixinContext context, Recipe recipe) { private static Configuration attemptExtractMixin(MethodInsnNode minsn, MixinContext context, Configuration clean) { // Looks like some code was moved into a static method outside this class // Attempt extracting mixin - TargetPair target = context.methods().findMethodPair(context.dirtyLookup(), MethodQualifier.create(minsn)); + TargetPair target = context.dirtyMethods().resolveDeclared(MethodQualifier.create(minsn)); if (target == null) return null; // MethodUpgrader.adjustInjectorOrdinalForNewMethod(minsn, context.legacy()); FIXME @@ -243,7 +243,7 @@ private static Optional handleTargetModification(List insn instanceof MethodInsnNode minsn && minsn.owner.equals(dirtyClass.name)) .map(MethodInsnNode.class::cast) .filter(minsn -> { - TargetPair pair = context.methods().findMethodPair(context.dirtyLookup(), MethodQualifier.create(minsn)); + TargetPair pair = context.dirtyMethods().resolveDeclared(MethodQualifier.create(minsn)); return pair != null && context.methods().hasInjectionTargetInsns(pair); }) .map(minsn -> MutableConfiguration.create() diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java index 60acefac..a387865f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java @@ -38,7 +38,7 @@ protected Configuration tryReuse(MixinContext context, Recipe recipe) { MethodQualifier targetQualifier = dirty.getTargetMethod(); if (targetQualifier == null) return null; - TargetPair dirtyTarget = context.methods().findMethodPair(context.dirtyLookup(), targetQualifier.withOwner(Type.getObjectType(targetClass))); + TargetPair dirtyTarget = context.dirtyMethods().resolveDeclared(targetQualifier.withOwner(Type.getObjectType(targetClass))); if (dirtyTarget == null) return null; // Try reusing the original diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java index f4a5ae04..a9b25ae2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java @@ -81,7 +81,7 @@ public class InjectionPointSubResolvers { int selfParamIndex = argInsns.indexOf(self); MethodQualifier target = MethodQualifier.create(minsn); - TargetPair pair = context.methods().findMethodPair(context.dirtyLookup(), target); + TargetPair pair = context.dirtyMethods().resolveDeclared(target); if (pair != null && context.methods().hasInjectionTargetInsns(pair)) { return recipe.dirty().copyClean() .setTargetClass(minsn.owner) @@ -105,7 +105,7 @@ public class InjectionPointSubResolvers { if (newCalls.isEmpty()) return null; Set trackLocals = context.isStatic() ? Set.of() : Set.of(0); - TargetPair to = context.methods().findInheritedMethodPair(context.dirtyLookup(), MethodQualifier.create(minsn)); + TargetPair to = context.dirtyMethods().resolveInherited(MethodQualifier.create(minsn)); CodePath path = CodePaths.findCodePath(targets.dirty(), to, trackLocals, context, newCalls::contains); if (path == null) return null; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java index 50b52f5d..8f4762cd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java @@ -36,10 +36,10 @@ public Configuration resolve(MixinContext context, Recipe recipe) { if (!(cleanInsn instanceof MethodInsnNode cleanMinsn)) return null; MethodQualifier cleanQualifier = MethodQualifier.create(cleanMinsn); - TargetPair cleanTarget = context.methods().findMethodPair(context.cleanLookup(), cleanQualifier); + TargetPair cleanTarget = context.cleanMethods().resolveDeclared(cleanQualifier); if (cleanTarget == null) return null; - TargetPair newCleanTarget = context.methods().findMethodPair(context.dirtyLookup(), cleanQualifier); + TargetPair newCleanTarget = context.dirtyMethods().resolveDeclared(cleanQualifier); if (newCleanTarget == null) return null; // Check if called clean method was marked as deprecated diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java index e0345c4a..ce54e8a1 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java @@ -84,7 +84,7 @@ private static List locateCandidates(MixinContext context, Targ private static List tryFindPartialCandidates(MethodNode cleanTargetMethod, ClassNode dirtyTargetClass, MethodNode dirtyTargetMethod, MixinContext context) { List dirtyOnlyCalls = MethodAnalyzer.findDirtyOnlyCalls(cleanTargetMethod, dirtyTargetMethod, false).stream() .filter(i -> i.owner.equals(dirtyTargetClass.name)) - .map(i -> context.methods().findOwnMethodPair(context.dirtyLookup(), MethodQualifier.create(i))) + .map(i -> context.dirtyMethods().resolve(MethodQualifier.create(i))) .filter(Objects::nonNull) .toList(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java index 28f530e2..6aaa1392 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java @@ -57,11 +57,11 @@ public class TargetMethodSubResolvers { MethodQualifier cleanQualifier = recipe.clean().getTargetMethod(); - Pair> candidates = context.methods().findOwnMethodsByName(context.dirtyLookup(), cleanQualifier); - if (candidates == null) return null; + List candidates = context.dirtyMethods().findByName(cleanQualifier); + if (candidates.isEmpty()) return null; // Find single matching candidate - Configuration resolved = resolveReplacementCandidate(context, recipe, candidates.getSecond()); + Configuration resolved = resolveReplacementCandidate(context, recipe, candidates); if (resolved == null) return null; // Only apply single candidate change when the target desc has changed @@ -152,7 +152,7 @@ private static Configuration resolveReplacementCandidate(MixinContext context, R } public static boolean isDirtyDeprecatedMethod(MixinContext context, MethodNode dirty) { - TargetPair pair = context.methods().findOwnMethodPair(context.cleanLookup(), MethodQualifier.create(dirty)); + TargetPair pair = context.cleanMethods().resolve(MethodQualifier.create(dirty)); return pair != null ? MethodAnalyzer.isDirtyDeprecatedMethod(pair.methodNode(), dirty) : MethodAnalyzer.isDeprecated(dirty); } } From 91c5a5a75786e3c910ab8969d0a3d6a0c4f09856 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Sun, 11 Oct 2026 22:18:52 +0200 Subject: [PATCH 03/44] Extract InjectionLocator for finding injection insns --- .../analysis/injection/InjectionLocator.java | 183 ++++++++++++++++++ .../analysis/injection/InjectionQuery.java | 88 +++++++++ .../adapter/analysis/method/MethodHelper.java | 150 +------------- .../constant/MixinAnnotationConstants.java | 1 + .../pipeline/PipelineMethodTransformer.java | 33 +--- .../org/sinytra/adapter/pipeline/Recipe.java | 2 +- .../adapter/pipeline/ctx/MixinContext.java | 7 + .../wrapop/WrapOpParamsProcessor.java | 5 +- .../ArbitraryInjectionPointSubResolver.java | 2 +- .../ComparingInjectionPointResolver.java | 5 +- .../InheritedInjectionPointSubResolver.java | 2 +- .../injection/InjectionPointResolver.java | 2 +- .../injection/InjectionPointSubResolvers.java | 4 +- .../OverloadedInjectionPointSubResolver.java | 3 +- .../special/InjectorOrdinalResolver.java | 2 +- .../special/ModifyVarAtReturnResolver.java | 2 +- .../special/SliceBoundaryResolver.java | 2 +- .../target/SplitTargetMethodSubResolver.java | 4 +- .../resolver/target/TargetMethodResolver.java | 2 +- .../target/TargetMethodSubResolvers.java | 4 +- 20 files changed, 307 insertions(+), 196 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java create mode 100644 core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java new file mode 100644 index 00000000..2451ae2e --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java @@ -0,0 +1,183 @@ +package org.sinytra.adapter.analysis.injection; + +import com.mojang.logging.LogUtils; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.InsnList; +import org.sinytra.adapter.mixin.MockMixinRuntime; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.SliceData; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.injection.InjectionPoint; +import org.spongepowered.asm.mixin.injection.code.ISliceContext; +import org.spongepowered.asm.mixin.injection.code.MethodSlice; +import org.spongepowered.asm.mixin.injection.points.BeforeConstant; +import org.spongepowered.asm.mixin.injection.struct.Target; +import org.spongepowered.asm.mixin.refmap.IMixinContext; + +import java.util.*; + +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.*; + +public final class InjectionLocator { + private static final Logger LOGGER = LogUtils.getLogger(); + + private final MixinContext context; + private final Map> cache; + + private record CacheKey(TargetPair target, boolean ignoreOrdinal, boolean keepShift, boolean ignoreSlice, + @Nullable Object injectionPoint, @Nullable Object methodAnnotation + ) { + } + + public InjectionLocator(MixinContext context) { + this.context = context; + this.cache = new HashMap<>(); + } + + /// {@return instructions matching mixin's @At selector in the target method} + public List find(@Nullable TargetPair target) { + return find(target, InjectionQuery.current()); + } + + /// {@return instructions matching the query in the target method} + public List find(@Nullable TargetPair target, InjectionQuery query) { + if (target == null) { + return List.of(); + } + if (query.source() != InjectionQuery.Source.CURRENT) { + return compute(target, query); + } + CacheKey key = new CacheKey( + target, query.ignoreOrdinal(), query.keepShift(), query.ignoreSlice(), + annotationKey(this.context.injectionPointAnnotation()), annotationKey(this.context.methodAnnotation()) + ); + List insns = this.cache.get(key); + if (insns == null) { + insns = compute(target, query); + this.cache.put(key, insns); + } + return insns; + } + + /// {@return a single instruction the mixin's @At matches in the target method or null if there isn't exactly one} + @Nullable + public AbstractInsnNode resolve(@Nullable TargetPair target) { + return resolve(target, InjectionQuery.current()); + } + + /// {@return a single instruction the query matches in the target method or null if there isn't exactly one} + @Nullable + public AbstractInsnNode resolve(@Nullable TargetPair target, InjectionQuery query) { + List insns = find(target, query); + return insns.size() == 1 ? insns.getFirst() : null; + } + + /// {@return whether the mixin's @At matches any instructions in the target method} + public boolean matches(@Nullable TargetPair target) { + return !find(target).isEmpty(); + } + + /// {@return whether the query matches any instructions in the target method} + public boolean matches(@Nullable TargetPair target, InjectionQuery query) { + return !find(target, query).isEmpty(); + } + + /// {@return the instructions of the target method within the slice} + public InsnList slice(TargetPair target, SliceData slice) { + return computeSlice(target, slice.toAnnotationNode(), createMixinTarget(target)); + } + + private List compute(TargetPair target, InjectionQuery query) { + AnnotationHandle injectionPoint = this.context.injectionPointAnnotation(); + AnnotationHandle source = switch (query.source()) { + case CURRENT -> injectionPoint; + case AT, SLICE_BOUNDARY -> injectionPoint != null ? new AnnotationHandle(Objects.requireNonNull(query.at()).toAnnotationNode()) : null; + case CONSTANT -> this.context.methodAnnotation().getNested(PROPERTY_CONSTANT).orElse(null); + }; + if (source == null) { + return List.of(); + } + AnnotationHandle node = source.copy(); + if (!query.keepShift()) { + node.removeValues(AT_SHIFT); + } + if (query.ignoreOrdinal()) { + node.removeValues(PROPERTY_ORDINAL); + } + + IMixinContext mixinContext = MockMixinRuntime.forClass(this.context.classNode().name, target.classNode().name, this.context.environment()); + List targetInsns = new ArrayList<>(); + try { + InjectionPoint point = parse(mixinContext, query, node); + Target mixinTarget = createMixinTarget(target); + InsnList instructions = query.ignoreSlice() ? target.methodNode().instructions : getSlicedInsns(target, mixinTarget); + if (MockMixinRuntime.injectionPointNeedsSpecialCare(point)) { + MockMixinRuntime.findModifyVariableInjectionInsns(point, mixinContext, target.methodNode().instructions, targetInsns, mixinTarget); + } else { + point.find(target.methodNode().desc, instructions, targetInsns); + } + } catch (Throwable e) { + LOGGER.error("Error finding injection insns", e); + return List.of(); + } + return targetInsns; + } + + private InjectionPoint parse(IMixinContext mixinContext, InjectionQuery query, AnnotationHandle node) { + AnnotationHandle methodAnnotation = this.context.methodAnnotation(); + return switch (query.source()) { + case CURRENT, SLICE_BOUNDARY -> InjectionPoint.parse(mixinContext, this.context.methodNode(), methodAnnotation.unwrap(), node.unwrap()); + case AT -> { + AnnotationHandle parent = methodAnnotation.copy(); + parent.setOrAppendNonNull(PROPERTY_AT, node.unwrap()); + yield InjectionPoint.parse(mixinContext, this.context.methodNode(), parent.unwrap(), node.unwrap()); + } + case CONSTANT -> new BeforeConstant(mixinContext, node.unwrap(), Type.getReturnType(this.context.methodNode().desc).getDescriptor()); + }; + } + + private static Target createMixinTarget(TargetPair target) { + return MockMixinRuntime.createMixinTarget(target); + } + + private InsnList getSlicedInsns(TargetPair target, Target mixinTarget) { + return this.context.methodAnnotation().getValue(PROPERTY_SLICE) + .map(handle -> handle.get() instanceof List list ? (AnnotationNode) list.getFirst() : (AnnotationNode) handle.get()) + .map(slice -> computeSlice(target, slice, mixinTarget)) + .orElse(target.methodNode().instructions); + } + + private InsnList computeSlice(TargetPair target, AnnotationNode slice, Target mixinTarget) { + IMixinContext mixinContext = MockMixinRuntime.forClass(this.context.classNode().name, target.classNode().name, this.context.environment()); + ISliceContext sliceContext = MockMixinRuntime.forSlice(mixinContext, this.context.methodNode()); + return MethodSlice.parse(sliceContext, slice).getSlice(mixinTarget); + } + + @Nullable + private static Object annotationKey(@Nullable AnnotationHandle handle) { + return handle != null ? annotationValueKey(handle.unwrap()) : null; + } + + private static Object annotationValueKey(Object value) { + if (value instanceof AnnotationNode node) { + List key = new ArrayList<>(); + key.add(node.desc); + if (node.values != null) { + node.values.forEach(v -> key.add(annotationValueKey(v))); + } + return key; + } + if (value instanceof List list) { + return list.stream().map(InjectionLocator::annotationValueKey).toList(); + } + if (value instanceof Object[] array) { + return Arrays.stream(array).map(InjectionLocator::annotationValueKey).toList(); + } + return value; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java new file mode 100644 index 00000000..79743213 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java @@ -0,0 +1,88 @@ +package org.sinytra.adapter.analysis.injection; + +import org.jetbrains.annotations.Nullable; +import org.sinytra.adapter.model.ann.AtData; + +public final class InjectionQuery { + public enum Source { + /// The mixin's own @At + CURRENT, + /// The supplied @At + AT, + /// The supplied @At within the mixin's slice + SLICE_BOUNDARY, + /// The mixin's @Constant + CONSTANT + } + + private final Source source; + @Nullable + private final AtData at; + private final boolean ignoreOrdinal; + private final boolean keepShift; + private final boolean ignoreSlice; + + private InjectionQuery(Source source, @Nullable AtData at, boolean ignoreOrdinal, boolean keepShift, boolean ignoreSlice) { + this.source = source; + this.at = at; + this.ignoreOrdinal = ignoreOrdinal; + this.keepShift = keepShift; + this.ignoreSlice = ignoreSlice; + } + + /// {@return a query for the mixin's own @At} + public static InjectionQuery current() { + return new InjectionQuery(Source.CURRENT, null, false, false, false); + } + + /// {@return a query for the given @At} + public static InjectionQuery of(AtData at) { + return new InjectionQuery(Source.AT, at, false, false, false); + } + + /// {@return a query for an @At within the mixin's slice, including shift} + public static InjectionQuery sliceBoundary(AtData at) { + return new InjectionQuery(Source.SLICE_BOUNDARY, at, false, true, true); + } + + /// {@return a query for the mixin's @Constant} + public static InjectionQuery constant() { + return new InjectionQuery(Source.CONSTANT, null, false, false, false); + } + + /// {@return this query ignoring the ordinal} + public InjectionQuery ignoringOrdinal() { + return new InjectionQuery(this.source, this.at, true, this.keepShift, this.ignoreSlice); + } + + /// {@return this query with the injection point's shift applied} + public InjectionQuery keepingShift() { + return new InjectionQuery(this.source, this.at, this.ignoreOrdinal, true, this.ignoreSlice); + } + + /// {@return this query ignoring the mixin's slice} + public InjectionQuery ignoringSlice() { + return new InjectionQuery(this.source, this.at, this.ignoreOrdinal, this.keepShift, true); + } + + public Source source() { + return this.source; + } + + @Nullable + public AtData at() { + return this.at; + } + + public boolean ignoreOrdinal() { + return this.ignoreOrdinal; + } + + public boolean keepShift() { + return this.keepShift; + } + + public boolean ignoreSlice() { + return this.ignoreSlice; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java index c3e23991..9e02ed45 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java @@ -12,25 +12,15 @@ import org.sinytra.adapter.analysis.params.ParamDiffResolver; import org.sinytra.adapter.mixin.MockMixinRuntime; import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.ann.AnnotationHandle; -import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; -import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.code.ISliceContext; -import org.spongepowered.asm.mixin.injection.code.MethodSlice; -import org.spongepowered.asm.mixin.injection.struct.Target; -import org.spongepowered.asm.mixin.refmap.IMixinContext; import java.util.*; -import java.util.function.BiFunction; -import java.util.function.Supplier; import java.util.stream.Stream; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.*; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; public class MethodHelper { @@ -38,92 +28,10 @@ public class MethodHelper { private final MixinContext context; - private final Map> targetInstructionsCache = new HashMap<>(); - - private record TargetInsnsKey(@Nullable TargetPair target, boolean ignoreOrdinal, @Nullable Object injectionPoint, @Nullable Object methodAnnotation) { - } - public MethodHelper(MixinContext context) { this.context = context; } - public boolean hasInjectionTargetInsns(@Nullable TargetPair target) { - return !findInjectionTargetInsns(target).isEmpty(); - } - - @Nullable - public AbstractInsnNode findInjectionTargetInsn(@Nullable TargetPair target) { - List cleanInsns = findInjectionTargetInsns(target); - return cleanInsns.size() != 1 ? null : cleanInsns.getFirst(); - } - - public List findInjectionTargetInsns(@Nullable TargetPair target) { - return findInjectionTargetInsns(target, false); - } - - public List findInjectionTargetInsns(@Nullable TargetPair target, boolean ignoreOrdinal) { - TargetInsnsKey key = new TargetInsnsKey(target, ignoreOrdinal, annotationKey(this.context.injectionPointAnnotation()), annotationKey(this.context.methodAnnotation())); - List insns = this.targetInstructionsCache.get(key); - if (insns == null) { - insns = computeInjectionTargetInsns( - target, - this.context::injectionPointAnnotation, - (ctx, h) -> { - AnnotationHandle atAnn = h; - if (ignoreOrdinal) { - atAnn = atAnn.copy(); - atAnn.removeValues(PROPERTY_ORDINAL); - } - - return InjectionPoint.parse(ctx, this.context.methodNode(), this.context.methodAnnotation().unwrap(), atAnn.unwrap()); - }, - true - ); - this.targetInstructionsCache.put(key, insns); - } - return insns; - } - - @Nullable - private static Object annotationKey(@Nullable AnnotationHandle handle) { - return handle != null ? annotationValueKey(handle.unwrap()) : null; - } - - private static Object annotationValueKey(Object value) { - if (value instanceof AnnotationNode node) { - List key = new ArrayList<>(); - key.add(node.desc); - if (node.values != null) { - node.values.forEach(v -> key.add(annotationValueKey(v))); - } - return key; - } - if (value instanceof List list) { - return list.stream().map(MethodHelper::annotationValueKey).toList(); - } - if (value instanceof Object[] array) { - return Arrays.stream(array).map(MethodHelper::annotationValueKey).toList(); - } - return value; - } - - @Nullable - public AbstractInsnNode findInjectionTargetInsn(@Nullable TargetPair target, AtData atData) { - List cleanInsns = computeInjectionTargetInsns( - target, - this.context::injectionPointAnnotation, - (ctx, h) -> { - AnnotationNode atCopy = atData.toAnnotationNode(); - AnnotationHandle annCopy = this.context.methodAnnotation().copy(); - annCopy.setOrAppendNonNull(PROPERTY_AT, atCopy); - - return InjectionPoint.parse(ctx, this.context.methodNode(), annCopy.unwrap(), atCopy); - }, - true - ); - return cleanInsns.size() != 1 ? null : cleanInsns.getFirst(); - } - public List resolveCapturedMethodParams(Configuration clean, Configuration dirty) { List cleanCaptured = clean.getParameters().getTypes(CAPTURED_PARAMS); List dirtyCaptured = new ArrayList<>(); @@ -164,62 +72,6 @@ public List resolveCapturedMethodParams(Configuration clean, Configuration return dirtyCaptured; } - @Nullable - public List computeInjectionTargetInsns(@Nullable TargetPair target, Supplier atNodeSupplier, BiFunction injectionPointParser, boolean ignoreShift) { - return computeInjectionTargetInsns(target, atNodeSupplier, injectionPointParser, ignoreShift, false); - } - - @Nullable - public List computeInjectionTargetInsns(@Nullable TargetPair target, Supplier atNodeSupplier, BiFunction injectionPointParser, boolean ignoreShift, boolean ignoreSlice) { - if (target == null) { - return List.of(); - } - AnnotationHandle atNode = atNodeSupplier.get(); - if (atNode == null) { - return List.of(); - } - AnnotationHandle atNodeCopy = atNode.copy(); - if (ignoreShift) { - atNodeCopy.removeValues(AT_SHIFT); - } - // Provide a minimum implementation of IMixinContext - IMixinContext mixinContext = MockMixinRuntime.forClass(this.context.classNode().name, target.classNode().name, this.context.environment()); - List targetInsns = new ArrayList<>(); - try { - // Parse injection point - InjectionPoint injectionPoint = injectionPointParser.apply(mixinContext, atNodeCopy); - Target mixinTarget = MockMixinRuntime.createMixinTarget(target); - // Find target instructions - InsnList instructions = ignoreSlice ? target.methodNode().instructions : getSlicedInsns(target, mixinTarget); - if (MockMixinRuntime.injectionPointNeedsSpecialCare(injectionPoint)) { - MockMixinRuntime.findModifyVariableInjectionInsns(injectionPoint, mixinContext, target.methodNode().instructions, targetInsns, mixinTarget); - } else { - injectionPoint.find(target.methodNode().desc, instructions, targetInsns); - } - } catch (Throwable e) { - LOGGER.error("Error finding injection insns", e); - return List.of(); - } - return targetInsns; - } - - private InsnList getSlicedInsns(TargetPair target, Target mixinTarget) { - return this.context.methodAnnotation().getValue(PROPERTY_SLICE) - .map(handle -> handle.get() instanceof List list ? (AnnotationNode) list.getFirst() : (AnnotationNode) handle.get()) - .map(slice -> computeSlice(target, slice, mixinTarget)) - .orElse(target.methodNode().instructions); - } - - public InsnList computeSlice(TargetPair target, AnnotationNode slice) { - return computeSlice(target, slice, MockMixinRuntime.createMixinTarget(target)); - } - - private InsnList computeSlice(TargetPair target, AnnotationNode slice, Target mixinTarget) { - IMixinContext mixinContext = MockMixinRuntime.forClass(this.context.classNode().name, target.classNode().name, this.context.environment()); - ISliceContext sliceContext = MockMixinRuntime.forSlice(mixinContext, this.context.methodNode()); - return MethodSlice.parse(sliceContext, slice).getSlice(mixinTarget); - } - @Nullable public List getTargetMethodLocals(TargetPair target) { Type[] targetParams = Type.getArgumentTypes(target.methodNode().desc); @@ -237,7 +89,7 @@ public List getTargetMethodLocals(TargetPair target, int startPos @Nullable public List getTargetMethodLocals(TargetPair target, int startPos, int lvtCompatLevel) { - List targetInsns = findInjectionTargetInsns(target); + List targetInsns = this.context.injections().find(target); if (targetInsns.isEmpty()) { LOGGER.debug("Skipping LVT patch, no target instructions found"); return null; diff --git a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java index 7c28b3d3..2cb14a6f 100644 --- a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java @@ -18,6 +18,7 @@ public class MixinAnnotationConstants { public static final String PROPERTY_ORDINAL = "ordinal"; public static final String PROPERTY_SLICE = "slice"; public static final String PROPERTY_INDEX = "index"; + public static final String PROPERTY_CONSTANT = "constant"; public static final String SLICE_FROM = "from"; public static final String SLICE_TO = "to"; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java index 94a90613..fcb7d3be 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java @@ -3,8 +3,8 @@ import com.mojang.logging.LogUtils; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.api.AuditTrail; import org.sinytra.adapter.api.MethodPatch; import org.sinytra.adapter.api.MethodTransformer; @@ -25,8 +25,6 @@ import org.sinytra.adapter.pipeline.resolver.Resolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.slf4j.Logger; -import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.points.BeforeConstant; import java.util.Arrays; import java.util.List; @@ -153,18 +151,8 @@ private PatchResult execute(MixinContext context, Configuration config) { public boolean failsDirtyInjectionCheck(MixinContext context, Configuration config, TargetPair dirtyTarget) { return !context.getMixinType().canInject(context, config) || dirtyTarget == null - || !context.methods().hasInjectionTargetInsns(dirtyTarget) - && computeConstantTargetInsns(context, dirtyTarget).isEmpty(); - } - - // TODO Clean up - public List computeConstantTargetInsns(MixinContext context, @Nullable TargetPair target) { - return context.methods().computeInjectionTargetInsns( - target, - () -> context.methodAnnotation().getNested("constant").orElse(null), - (ctx, h) -> new BeforeConstant(ctx, h.unwrap(), Type.getReturnType(context.methodNode().desc).getDescriptor()), - false - ); + || !context.injections().matches(dirtyTarget) + && !context.injections().matches(dirtyTarget, InjectionQuery.constant()); } public boolean hasValidSlice(MixinContext context, Configuration config, @Nullable TargetPair target) { @@ -177,24 +165,13 @@ public boolean hasValidSlice(MixinContext context, Configuration config, @Nullab .orElse(List.of()); for (SliceData slice : slices) { AtData from = slice.from(); - if (from != null && !validateAtNode(context, from, target)) + if (from != null && !context.injections().matches(target, InjectionQuery.sliceBoundary(from))) return false; AtData to = slice.to(); - if (to != null && !validateAtNode(context, to, target)) + if (to != null && !context.injections().matches(target, InjectionQuery.sliceBoundary(to))) return false; } return true; } - - private boolean validateAtNode(MixinContext context, AtData at, TargetPair target) { - List insns = context.methods().computeInjectionTargetInsns( - target, - context::injectionPointAnnotation, - (ctx, h) -> InjectionPoint.parse(ctx, context.methodNode(), context.methodAnnotation().unwrap(), at.toAnnotationNode()), - false, - true - ); - return !insns.isEmpty(); - } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java index 84726028..61e111be 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java @@ -107,7 +107,7 @@ public Targets targets() { @Nullable public AbstractInsnNode getCleanInjectionInsn() { - return this.context.methods().findInjectionTargetInsn(getCleanTarget()); + return this.context.injections().resolve(getCleanTarget()); } public boolean hasInjectionPointValue(String value) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java index 57639f85..c8a296a5 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java @@ -4,6 +4,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.analysis.injection.InjectionLocator; import org.sinytra.adapter.analysis.method.MethodHelper; import org.sinytra.adapter.analysis.method.MethodLocator; import org.sinytra.adapter.api.BytecodeFixerUpper; @@ -45,6 +46,7 @@ public class MixinContext implements RefMapper, Auditor { private final MethodHelper methodHelper; private final MethodLocator cleanMethods; private final MethodLocator dirtyMethods; + private final InjectionLocator injections; private final Resolvers resolvers = new Resolvers(); private final Processors processors = new Processors(); @@ -59,6 +61,7 @@ public MixinContext(MixinType mixinType, PatchContext patchContext, ClassTarget this.flags = flags; this.methodHelper = new MethodHelper(this); + this.injections = new InjectionLocator(this); String defaultOwner = classTarget.getTypes().getFirst().getInternalName(); this.cleanMethods = new MethodLocator(patchContext.environment(), patchContext.environment().cleanClassLookup(), defaultOwner); this.dirtyMethods = new MethodLocator(patchContext.environment(), patchContext.environment().dirtyClassLookup(), defaultOwner); @@ -116,6 +119,10 @@ public MethodLocator dirtyMethods() { return this.dirtyMethods; } + public InjectionLocator injections() { + return this.injections; + } + @Nullable public Targets targets(MethodQualifier qualifier) { TargetPair clean = this.cleanMethods.resolve(qualifier); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java index e0fe9a91..0db3a8b4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java @@ -3,6 +3,7 @@ import com.mojang.logging.LogUtils; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.method.WrapOpOriginalCall; import org.sinytra.adapter.model.config.Configuration; @@ -45,8 +46,8 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe if (dirty.getAtData() != null) { // Upgrade calls on "instance" variable - AbstractInsnNode cleanInsn = context.methods().findInjectionTargetInsn(recipe.getCleanTarget()); - AbstractInsnNode dirtyInsn = context.methods().findInjectionTargetInsn(recipe.getDirtyTarget(), dirty.getAtData()); + AbstractInsnNode cleanInsn = context.injections().resolve(recipe.getCleanTarget()); + AbstractInsnNode dirtyInsn = context.injections().resolve(recipe.getDirtyTarget(), InjectionQuery.of(dirty.getAtData()).keepingShift()); if (cleanInsn instanceof MethodInsnNode cleanMinsn && dirtyInsn instanceof MethodInsnNode dirtyMinsn) { List methodParams = dirty.getParameters().getTypes(MethodParameters.ParamGroup.METHOD_PARAMS); if (!WrapOpSurgeon.tryUpgrade(context, recipe, methodParams, cleanMinsn, dirtyMinsn)) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java index b5a63aa9..4edeb341 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java @@ -41,7 +41,7 @@ public Configuration resolve(MixinContext context, Recipe recipe) { if (target == null) return null; // New method is in the same class? It's possible our target injection point was moved there - if (context.methods().hasInjectionTargetInsns(target)) { + if (context.injections().matches(target)) { return MutableConfiguration.create().setTargetMethod(targetMethodCall); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java index 60ee841b..918d3683 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java @@ -6,6 +6,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.compare.MethodLabelComparator; +import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; @@ -80,7 +81,7 @@ private static Configuration attemptExtractMixin(MethodInsnNode minsn, MixinCont if (target == null) return null; // MethodUpgrader.adjustInjectorOrdinalForNewMethod(minsn, context.legacy()); FIXME - List newTargetInsns = context.methods().findInjectionTargetInsns(target, true); + List newTargetInsns = context.injections().find(target, InjectionQuery.current().ignoringOrdinal()); if (newTargetInsns.isEmpty()) return null; return MutableConfiguration.create() @@ -244,7 +245,7 @@ private static Optional handleTargetModification(List { TargetPair pair = context.dirtyMethods().resolveDeclared(MethodQualifier.create(minsn)); - return pair != null && context.methods().hasInjectionTargetInsns(pair); + return pair != null && context.injections().matches(pair); }) .map(minsn -> MutableConfiguration.create() .setAtData(AtData.create(AT_VAL_INVOKE, minsn))) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java index 0e66f2e5..6f4043fd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java @@ -38,7 +38,7 @@ public Configuration resolve(MixinContext context, Recipe recipe) { TargetPair targetPair = recipe.getDirtyTarget(); if (targetPair == null) return null; - if (context.methods().hasInjectionTargetInsns(targetPair)) return null; + if (context.injections().matches(targetPair)) return null; String owner = atTarget.internalOwnerName(); for (AbstractInsnNode insn : targetPair.methodNode().instructions) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java index a387865f..8202aec5 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java @@ -42,7 +42,7 @@ protected Configuration tryReuse(MixinContext context, Recipe recipe) { if (dirtyTarget == null) return null; // Try reusing the original - List insns = context.methods().findInjectionTargetInsns(dirtyTarget); + List insns = context.injections().find(dirtyTarget); if (!insns.isEmpty()) { return dirty.copyClean() .inheritAtData(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java index a9b25ae2..515a4afe 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java @@ -33,7 +33,7 @@ public class InjectionPointSubResolvers { if (targets == null) return null; // Find single clean target minsn - List insns = context.methods().findInjectionTargetInsns(targets.clean()); + List insns = context.injections().find(targets.clean()); if (insns.isEmpty() || !(insns.getFirst() instanceof MethodInsnNode cleanInsn)) return null; InstructionMatcher cleanMatcher = MethodInsnMatcher.findSurroundingInstructions(cleanInsn); @@ -82,7 +82,7 @@ public class InjectionPointSubResolvers { MethodQualifier target = MethodQualifier.create(minsn); TargetPair pair = context.dirtyMethods().resolveDeclared(target); - if (pair != null && context.methods().hasInjectionTargetInsns(pair)) { + if (pair != null && context.injections().matches(pair)) { return recipe.dirty().copyClean() .setTargetClass(minsn.owner) .setTargetMethod(minsn) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java index 8f4762cd..1d5ab83b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java @@ -3,6 +3,7 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; @@ -32,7 +33,7 @@ public Configuration resolve(MixinContext context, Recipe recipe) { // Temporarily modify @At data to target INVOKE if originally using INVOKE_ASSIGN AtData tempAt = getInvokeAtData(at); - AbstractInsnNode cleanInsn = context.methods().findInjectionTargetInsn(targets.clean(), tempAt); + AbstractInsnNode cleanInsn = context.injections().resolve(targets.clean(), InjectionQuery.of(tempAt).keepingShift()); if (!(cleanInsn instanceof MethodInsnNode cleanMinsn)) return null; MethodQualifier cleanQualifier = MethodQualifier.create(cleanMinsn); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java index 06509c4a..037ca02c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java @@ -337,7 +337,7 @@ private static class ModifyVariableOffsetHandler implements UpdateHandler ordinal) { int variableIndex = available.get(ordinal).index; AbstractInsnNode cleanInsn = recipe.getCleanInjectionInsn(); - AbstractInsnNode dirtyInsn = context.methods().findInjectionTargetInsn(recipe.getDirtyTarget()); + AbstractInsnNode dirtyInsn = context.injections().resolve(recipe.getDirtyTarget()); if (cleanInsn != null && dirtyInsn != null) { for (AbstractInsnNode insn : InsnTraversal.untilLabel(cleanInsn, InsnTraversal.FORWARD, true)) { SingleValueHandle handle = AdapterUtil.handleLocalVarInsnValue(insn); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java index 406929bf..bdefbd7e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java @@ -124,7 +124,7 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { private static Pair getTargetPair(Configuration clean, MixinContext context, TargetPair injectionTarget, int ordinal) { // Find injection point insn - List targetInsns = context.methods().findInjectionTargetInsns(injectionTarget); + List targetInsns = context.injections().find(injectionTarget); if (targetInsns.isEmpty()) return null; int index = ordinal == -1 ? targetInsns.size() - 1 : ordinal; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java index 9499e7ff..16b75431 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java @@ -103,7 +103,7 @@ private static Optional fixSlideInjectionPoint(AtData boundary, MethodNo } private static boolean passesSliceCheck(SliceData slice, MixinContext context, TargetPair dirtyTarget) { - InsnList insns = context.methods().computeSlice(dirtyTarget, slice.toAnnotationNode()); + InsnList insns = context.injections().slice(dirtyTarget, slice); return insns.size() != dirtyTarget.methodNode().instructions.size(); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java index ce54e8a1..0e935ddf 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java @@ -97,7 +97,7 @@ private static List disambiguate(List candidat return candidates; } - List cleanInsns = context.methods().findInjectionTargetInsns(cleanTarget); + List cleanInsns = context.injections().find(cleanTarget); if (cleanInsns.size() != 1) { return candidates; } @@ -122,7 +122,7 @@ private static List disambiguate(List candidat private static List findInsnsCalls(List methods, MixinContext context) { return methods.stream() .map(pair -> { - List insns = context.methods().findInjectionTargetInsns(pair); + List insns = context.injections().find(pair); return !insns.isEmpty() ? new CandidateMethod(pair.methodNode(), insns) : null; }) .filter(Objects::nonNull) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java index ff2faae2..241fa1b7 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java @@ -22,7 +22,7 @@ protected boolean canApply(Recipe recipe) { @Override protected Configuration tryReuse(MixinContext context, Recipe recipe) { TargetPair target = recipe.getNewCleanTarget(); - if (target != null && context.methods().hasInjectionTargetInsns(target)) { + if (target != null && context.injections().matches(target)) { return recipe.dirty().copyClean() .inheritTargetMethod(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java index 6aaa1392..0e877e7e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java @@ -82,7 +82,7 @@ public class TargetMethodSubResolvers { // Find lambda invocations and search for target insns inside the lambda for (IndyHandle handle : MethodAnalyzer.findOwnIndyHandles(target.classNode(), target.methodNode())) { TargetPair lambda = new TargetPair(target.classNode(), handle.method()); - if (!context.methods().findInjectionTargetInsns(lambda).isEmpty()) { + if (context.injections().matches(lambda)) { return MutableConfiguration.create() .setTargetMethod(lambda.methodNode()); } @@ -100,7 +100,7 @@ private static Configuration resolveOverloadedReplacement(MixinContext context, List invocations = MethodAnalyzer.getSelfTopTierMethodCalls(dirtyTarget); for (MethodNode invocation : invocations) { - if (context.methods().hasInjectionTargetInsns(new TargetPair(dirtyTarget.classNode(), invocation))) { + if (context.injections().matches(new TargetPair(dirtyTarget.classNode(), invocation))) { return MutableConfiguration.create() .setTargetMethod(invocation); } From 136ba2d714fcbae3f5d506ea3ce048038b1b3170 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 19:02:46 +0200 Subject: [PATCH 04/44] Add DataFlow insn analysis utility --- .../adapter/analysis/compare/CodePaths.java | 6 +- .../adapter/analysis/flow/DataFlow.java | 418 ++++++++++++++++++ .../adapter/analysis/flow/FrameUtil.java | 116 ----- .../adapter/analysis/flow/WrapOpAnalyzer.java | 94 ---- .../analysis/method/MethodAnalyzer.java | 227 ---------- .../analysis/method/WrapOpOriginalCall.java | 9 +- .../edit/param/ModifyArgsOffsetUpgrader.java | 4 +- .../edit/param/ParamTransformationUtil.java | 4 +- .../param/ReplaceParametersTransformer.java | 4 +- .../adapter/pipeline/ctx/MixinContext.java | 11 + .../processor/ParametersPostProcessor.java | 4 +- .../processor/ReturnTypeProcessor.java | 11 +- .../extract/MirrorableExtractMixin.java | 3 +- .../redirect/ParameterUsageProcessor.java | 4 +- .../processor/wrapop/WrapOpSurgeon.java | 6 +- .../ComparingInjectionPointResolver.java | 3 +- .../injection/InjectionPointSubResolvers.java | 2 +- .../special/ModifyVarAtReturnResolver.java | 3 +- .../target/SplitMethodCancellationHelper.java | 2 +- .../target/SplitTargetMethodSubResolver.java | 2 +- .../target/TargetMethodSubResolvers.java | 2 +- .../types/FieldTypeUsageTransformer.java | 4 +- 22 files changed, 462 insertions(+), 477 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java delete mode 100644 core/src/main/java/org/sinytra/adapter/analysis/flow/FrameUtil.java delete mode 100644 core/src/main/java/org/sinytra/adapter/analysis/flow/WrapOpAnalyzer.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java index 146f6367..a3800a23 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java @@ -6,8 +6,8 @@ import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; +import org.sinytra.adapter.analysis.flow.DataFlow.ImplicitCalls; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -68,7 +68,7 @@ private static CodePath findCodePathRecursive(TargetPair from, TargetPair to, Se } visited.put(key, depth); - List topTierCalls = MethodAnalyzer.getTopTierMethodCalls(from, true); + List topTierCalls = context.dataFlow(from).findTopLevelCalls(ImplicitCalls.UNWRAP); for (MethodInsnNode minsn : topTierCalls) { if (!filter.test(minsn) @@ -88,7 +88,7 @@ private static CodePath findCodePathRecursive(TargetPair from, TargetPair to, Se Map locals = new Int2IntOpenHashMap(); if (!trackLocals.isEmpty()) { - List> receiverInsns = MethodAnalyzer.getMethodCallArgInsns(from.methodNode(), minsn); + List> receiverInsns = context.dataFlow(from).findOperandExpressions(minsn); for (int i = 0; i < receiverInsns.size(); i++) { List argInsns = receiverInsns.get(i); if (argInsns.size() != 1 || !(argInsns.getFirst() instanceof VarInsnNode varInsn)) continue; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java b/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java new file mode 100644 index 00000000..855802db --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java @@ -0,0 +1,418 @@ +package org.sinytra.adapter.analysis.flow; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.tree.analysis.*; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.util.AdapterUtil; + +import java.util.*; +import java.util.function.BiConsumer; + +public final class DataFlow { + public enum Producers { + /// Instructions that loaded the value, including DUP and SWAP + LOADER, + /// Instructions that actually computed the value + COMPUTER + } + + public enum ImplicitCalls { + /// No changes + KEEP, + /// Unwrap the inner arguments of boxing and unboxing calls + UNWRAP + } + + private static final Set BOXED_TYPES = Set.of( + "java/lang/Boolean", "java/lang/Byte", "java/lang/Character", "java/lang/Short", + "java/lang/Integer", "java/lang/Float", "java/lang/Long", "java/lang/Double" + ); + private static final Set UNBOXING_METHODS = Set.of( + "booleanValue", "byteValue", "charValue", "shortValue", "intValue", "floatValue", "longValue", "doubleValue" + ); + + private final MethodNode method; + private final Producers producers; + @Nullable + private Frame[] frames; + + private DataFlow(MethodNode method, Producers producers) { + this.method = method; + this.producers = producers; + } + + public static DataFlow of(MethodNode method) { + return of(method, Producers.LOADER); + } + + public static DataFlow of(MethodNode method, Producers producers) { + return new DataFlow(method, producers); + } + + public MethodNode method() { + return this.method; + } + + public Producers producers() { + return this.producers; + } + + /// {@return the frame before the instruction} + @Nullable + public Frame frame(AbstractInsnNode insn) { + Frame[] frames = frames(); + int index = this.method.instructions.indexOf(insn); + return index >= 0 && index < frames.length ? frames[index] : null; + } + + /// {@return the stack values the insn consumes or an empty list if unreachable} + public List operands(AbstractInsnNode insn) { + Frame frame = frame(insn); + return frame != null ? operands(frame, insn) : List.of(); + } + + /// {@return a single producer of each argument of the call or null if an arg has more producers} + @Nullable + public List resolveCallOperandProducers(MethodInsnNode call) { + if (!this.method.instructions.contains(call)) { + List calls = MethodAnalyzer.findCalls(this.method, MethodQualifier.create(call)); + return calls.size() == 1 ? resolveCallOperandProducers(calls.getFirst()) : null; + } + + Frame frame = frame(call); + if (frame == null) { + return null; + } + + List insns = new ArrayList<>(); + for (SourceValue src : operands(frame, call)) { + AbstractInsnNode srcInsn = AdapterUtil.getSingleInsn(src); + if (srcInsn == null) { + return null; + } + insns.add(srcInsn); + } + + return insns; + } + + /// {@return instructions of operands excluding the call insn} + @Nullable + public List resolveOperandProducers(MethodInsnNode call) { + List insns = resolveCallOperandProducers(call); + return insns != null && !insns.isEmpty() ? AdapterUtil.subListInsnsExc(insns.getFirst(), call) : null; + } + + /// {@return operands for each call matching the qualifier including the call insn} + public List> findCallsWithProducers(MethodQualifier qualifier) { + List> list = new ArrayList<>(); + for (MethodInsnNode call : MethodAnalyzer.findCalls(this.method, qualifier)) { + List insns = resolveCallOperandProducers(call); + if (insns != null) { + insns.add(call); + list.add(insns); + } + } + return list; + } + + /// {@return for each operand of the call, all instructions computing it in order, or an empty list} + public List> findOperandExpressions(MethodInsnNode call) { + List> args = new ArrayList<>(); + if (!this.method.instructions.contains(call)) { + return args; + } + + Frame frame = frame(call); + if (frame == null) { + return args; + } + + for (SourceValue value : operands(frame, call)) { + List insns = new ArrayList<>(); + for (AbstractInsnNode insn : value.insns) { + expandExpression(insns, insn); + insns.add(insn); + } + args.add(insns); + } + return args; + } + + /// Call the action for each reachable call with its operands + public void forEachCall(BiConsumer> action) { + for (AbstractInsnNode insn : this.method.instructions) { + if (insn instanceof MethodInsnNode minsn) { + Frame frame = frame(minsn); + if (frame != null) { // Dead code otherwise + action.accept(minsn, operands(frame, minsn)); + } + } + } + } + + /// Call the action for each reachable instance call with the producers of its receiver + public void forEachReceiver(BiConsumer> action) { + forEachCall((call, operands) -> { + if (call.getOpcode() != Opcodes.INVOKESTATIC && operands.size() == Type.getArgumentCount(call.desc) + 1) { + action.accept(call, operands.getFirst().insns); + } + }); + } + + /// {@return calls whose result is not used as an operand of another call in order} + public List findTopLevelCalls(ImplicitCalls implicitCalls) { + InsnList instructions = this.method.instructions; + + Set consumedCalls = new HashSet<>(); + for (AbstractInsnNode insn : instructions) { + if (insn instanceof MethodInsnNode call) { + traceInputs(call, consumedCalls, new HashSet<>()); + } + } + + List topLevelCalls = new ArrayList<>(); + for (AbstractInsnNode insn : instructions) { + if (insn instanceof MethodInsnNode call && !consumedCalls.contains(call)) { + if (implicitCalls == ImplicitCalls.UNWRAP && isImplicitCall(call)) { + unwrapImplicitCall(call, topLevelCalls, new HashSet<>()); + } else if (!topLevelCalls.contains(call)) { + topLevelCalls.add(call); + } + } + } + topLevelCalls.sort(Comparator.comparingInt(instructions::indexOf)); + + return topLevelCalls; + } + + /// {@return methods of the owner class that are called at the top level} + public List findTopLevelOwnCalls(ClassNode owner) { + List ownCalls = new ArrayList<>(); + for (MethodInsnNode insn : findTopLevelCalls(ImplicitCalls.KEEP)) { + MethodNode method = AdapterUtil.findMethod(owner, insn.name, insn.desc); + if (method != null) { + ownCalls.add(method); + } + } + return ownCalls; + } + + /// {@return instructions initializing each element of the array, including the array store insn} + public List> findArrayElements(AbstractInsnNode newArrayInsn) { + Frame[] frames = frames(); + + Map indexToStoreNode = new HashMap<>(); + InsnList instructions = this.method.instructions; + for (int i = 0; i < instructions.size(); i++) { + AbstractInsnNode insn = instructions.get(i); + + if (insn.getOpcode() == Opcodes.AASTORE) { + Frame frame = frames[i]; + if (frame == null) continue; + + if (frame.getStackSize() < 3) continue; + SourceValue arrayRefSource = frame.getStack(frame.getStackSize() - 3); + + if (arrayRefSource.insns.contains(newArrayInsn)) { + SourceValue indexSource = frame.getStack(frame.getStackSize() - 2); + Integer constantIndex = resolveConstantIndex(indexSource); + + if (constantIndex != null) { + indexToStoreNode.put(constantIndex, insn); + } + } + } + } + + List> groups = new ArrayList<>(); + AbstractInsnNode currentStart = newArrayInsn.getNext(); + int maxIndex = indexToStoreNode.keySet().stream().max(Integer::compare).orElse(-1); + + for (int i = 0; i <= maxIndex; i++) { + AbstractInsnNode endNode = indexToStoreNode.get(i); + List currentGroup = new ArrayList<>(); + + if (endNode != null) { + AbstractInsnNode ptr = currentStart; + while (ptr != null) { + currentGroup.add(ptr); + if (ptr == endNode) break; + ptr = ptr.getNext(); + } + currentStart = endNode.getNext(); + } + + groups.add(currentGroup); + } + + return groups; + } + + private Frame[] frames() { + if (this.frames == null) { + Interpreter interpreter = this.producers == Producers.COMPUTER ? new AdvancedSourceInterpreter(true) : new SourceInterpreter(); + try { + this.frames = new Analyzer<>(interpreter).analyze(Object.class.getName(), this.method); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return this.frames; + } + + private void expandExpression(List out, AbstractInsnNode insn) { + Frame frame = frame(insn); + if (frame == null) return; + + for (SourceValue value : operands(frame, insn)) { + for (AbstractInsnNode sourceInsn : value.insns) { + if (!out.contains(sourceInsn)) { + expandExpression(out, sourceInsn); + out.add(sourceInsn); + } + } + } + } + + private void unwrapImplicitCall(MethodInsnNode call, List out, Set seen) { + if (!seen.add(call)) return; + + Set wrapped = new HashSet<>(); + traceInputs(call, wrapped, new HashSet<>()); + if (wrapped.isEmpty()) { + if (!out.contains(call)) { + out.add(call); + } + return; + } + for (MethodInsnNode inner : wrapped) { + if (isImplicitCall(inner)) { + unwrapImplicitCall(inner, out, seen); + } else if (!out.contains(inner)) { + out.add(inner); + } + } + } + + private void collectProducerCalls(SourceValue value, Set consumedCalls, Set visited) { + for (AbstractInsnNode producer : value.insns) { + if (!visited.add(producer)) continue; + + if (producer instanceof MethodInsnNode producerCall) { + consumedCalls.add(producerCall); + continue; + } + + traceInputs(producer, consumedCalls, visited); + } + } + + private void traceInputs(AbstractInsnNode insn, Set consumedCalls, Set visited) { + Frame frame = frame(insn); + if (frame == null) return; + + for (SourceValue input : operands(frame, insn)) { + collectProducerCalls(input, consumedCalls, visited); + } + } + + private static boolean isImplicitCall(MethodInsnNode minsn) { + if (!BOXED_TYPES.contains(minsn.owner)) { + return false; + } + if (minsn.getOpcode() == Opcodes.INVOKESTATIC && minsn.name.equals("valueOf")) { + Type[] args = Type.getArgumentTypes(minsn.desc); + return args.length == 1 && args[0].getSort() <= Type.DOUBLE; + } + return minsn.getOpcode() == Opcodes.INVOKEVIRTUAL && UNBOXING_METHODS.contains(minsn.name) && Type.getArgumentCount(minsn.desc) == 0; + } + + private static Integer resolveConstantIndex(SourceValue source) { + for (AbstractInsnNode insn : source.insns) { + int index = AdapterUtil.getIntConstValue(insn).orElse(-1); + if (index != -1) { + return index; + } + } + return null; + } + + private static List operands(Frame frame, AbstractInsnNode insn) { + int stackSize = frame.getStackSize(); + int start = Math.max(0, stackSize - getPopCount(insn, frame)); + List operands = new ArrayList<>(stackSize - start); + for (int i = start; i < stackSize; i++) { + operands.add(frame.getStack(i)); + } + return operands; + } + + private static int getPopCount(AbstractInsnNode insn, @Nullable Frame frame) { + int op = insn.getOpcode(); + + if (insn instanceof MethodInsnNode minsn) { + int args = Type.getArgumentCount(minsn.desc); + return op == Opcodes.INVOKESTATIC ? args : args + 1; + } + + if (insn instanceof InvokeDynamicInsnNode indy) { + return Type.getArgumentCount(indy.desc); + } + + if (insn instanceof MultiANewArrayInsnNode multiANewArray) { + return multiANewArray.dims; + } + + return switch (op) { + case Opcodes.POP2, Opcodes.DUP2 -> isWide(frame, 0) ? 1 : 2; + case Opcodes.DUP_X2 -> isWide(frame, 1) ? 2 : 3; + case Opcodes.DUP2_X1 -> isWide(frame, 0) ? 2 : 3; + case Opcodes.DUP2_X2 -> { + if (isWide(frame, 0)) { + yield isWide(frame, 1) ? 2 : 3; + } + yield isWide(frame, 2) ? 3 : 4; + } + + case Opcodes.IASTORE, Opcodes.LASTORE, Opcodes.FASTORE, Opcodes.DASTORE, Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE, + Opcodes.SASTORE -> 3; + + case Opcodes.IADD, Opcodes.LADD, Opcodes.FADD, Opcodes.DADD, Opcodes.ISUB, Opcodes.LSUB, Opcodes.FSUB, + Opcodes.DSUB, Opcodes.IMUL, Opcodes.LMUL, Opcodes.FMUL, Opcodes.DMUL, Opcodes.IDIV, Opcodes.LDIV, + Opcodes.FDIV, Opcodes.DDIV, Opcodes.IREM, Opcodes.LREM, Opcodes.FREM, Opcodes.DREM, Opcodes.ISHL, + Opcodes.LSHL, Opcodes.ISHR, Opcodes.LSHR, Opcodes.IUSHR, Opcodes.LUSHR, Opcodes.IAND, Opcodes.LAND, + Opcodes.IOR, Opcodes.LOR, Opcodes.IXOR, Opcodes.LXOR, Opcodes.LCMP, Opcodes.FCMPL, Opcodes.FCMPG, + Opcodes.DCMPL, Opcodes.DCMPG, + Opcodes.IALOAD, Opcodes.LALOAD, Opcodes.FALOAD, Opcodes.DALOAD, Opcodes.AALOAD, Opcodes.BALOAD, Opcodes.CALOAD, + Opcodes.SALOAD, + Opcodes.IF_ICMPEQ, Opcodes.IF_ICMPNE, Opcodes.IF_ICMPLT, Opcodes.IF_ICMPGE, Opcodes.IF_ICMPGT, Opcodes.IF_ICMPLE, + Opcodes.IF_ACMPEQ, Opcodes.IF_ACMPNE, + Opcodes.PUTFIELD, Opcodes.SWAP, Opcodes.DUP_X1 -> 2; + + case Opcodes.I2L, Opcodes.I2F, Opcodes.I2D, Opcodes.L2I, Opcodes.L2F, Opcodes.L2D, Opcodes.F2I, Opcodes.F2L, + Opcodes.F2D, Opcodes.D2I, Opcodes.D2L, Opcodes.D2F, Opcodes.I2B, Opcodes.I2C, Opcodes.I2S, Opcodes.INEG, + Opcodes.LNEG, Opcodes.FNEG, Opcodes.DNEG, Opcodes.CHECKCAST, Opcodes.INSTANCEOF, + Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, Opcodes.ASTORE, Opcodes.POP, Opcodes.DUP, + Opcodes.IFEQ, Opcodes.IFNE, Opcodes.IFLT, Opcodes.IFGE, Opcodes.IFGT, Opcodes.IFLE, Opcodes.IFNULL, Opcodes.IFNONNULL, + Opcodes.TABLESWITCH, Opcodes.LOOKUPSWITCH, + Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN, Opcodes.ATHROW, + Opcodes.MONITORENTER, Opcodes.MONITOREXIT, Opcodes.ARRAYLENGTH, Opcodes.NEWARRAY, Opcodes.ANEWARRAY, + Opcodes.GETFIELD, Opcodes.PUTSTATIC -> 1; + + default -> 0; + }; + } + + private static boolean isWide(@Nullable Frame frame, int depth) { + if (frame == null) { + return false; + } + int index = frame.getStackSize() - 1 - depth; + return index >= 0 && frame.getStack(index).getSize() == 2; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/flow/FrameUtil.java b/core/src/main/java/org/sinytra/adapter/analysis/flow/FrameUtil.java deleted file mode 100644 index aa466854..00000000 --- a/core/src/main/java/org/sinytra/adapter/analysis/flow/FrameUtil.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.sinytra.adapter.analysis.flow; - -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.InvokeDynamicInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.MultiANewArrayInsnNode; -import org.objectweb.asm.tree.analysis.Analyzer; -import org.objectweb.asm.tree.analysis.Frame; -import org.objectweb.asm.tree.analysis.SourceInterpreter; -import org.objectweb.asm.tree.analysis.SourceValue; -import org.objectweb.asm.tree.analysis.Value; - -import java.util.ArrayList; -import java.util.List; - -public class FrameUtil { - public static Frame[] getFrames(MethodNode methodNode) { - try { - Analyzer analyzer = new Analyzer<>(new SourceInterpreter()); - return analyzer.analyze(Object.class.getName(), methodNode); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static Frame[] getStableFrames(MethodNode methodNode) { - try { - Analyzer analyzer = new Analyzer<>(new AdvancedSourceInterpreter(true)); - return analyzer.analyze(Object.class.getName(), methodNode); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static List getOperands(Frame frame, AbstractInsnNode insn) { - int stackSize = frame.getStackSize(); - int start = Math.max(0, stackSize - getPopCount(insn, frame)); - List operands = new ArrayList<>(stackSize - start); - for (int i = start; i < stackSize; i++) { - operands.add(frame.getStack(i)); - } - return operands; - } - - public static int getPopCount(AbstractInsnNode insn) { - return getPopCount(insn, null); - } - - public static int getPopCount(AbstractInsnNode insn, @Nullable Frame frame) { - int op = insn.getOpcode(); - - if (insn instanceof MethodInsnNode minsn) { - int args = Type.getArgumentCount(minsn.desc); - return op == Opcodes.INVOKESTATIC ? args : args + 1; - } - - if (insn instanceof InvokeDynamicInsnNode indy) { - return Type.getArgumentCount(indy.desc); - } - - if (insn instanceof MultiANewArrayInsnNode multiANewArray) { - return multiANewArray.dims; - } - - return switch (op) { - case Opcodes.POP2, Opcodes.DUP2 -> isWide(frame, 0) ? 1 : 2; - case Opcodes.DUP_X2 -> isWide(frame, 1) ? 2 : 3; - case Opcodes.DUP2_X1 -> isWide(frame, 0) ? 2 : 3; - case Opcodes.DUP2_X2 -> { - if (isWide(frame, 0)) { - yield isWide(frame, 1) ? 2 : 3; - } - yield isWide(frame, 2) ? 3 : 4; - } - - case Opcodes.IASTORE, Opcodes.LASTORE, Opcodes.FASTORE, Opcodes.DASTORE, Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE, - Opcodes.SASTORE -> 3; - - case Opcodes.IADD, Opcodes.LADD, Opcodes.FADD, Opcodes.DADD, Opcodes.ISUB, Opcodes.LSUB, Opcodes.FSUB, - Opcodes.DSUB, Opcodes.IMUL, Opcodes.LMUL, Opcodes.FMUL, Opcodes.DMUL, Opcodes.IDIV, Opcodes.LDIV, - Opcodes.FDIV, Opcodes.DDIV, Opcodes.IREM, Opcodes.LREM, Opcodes.FREM, Opcodes.DREM, Opcodes.ISHL, - Opcodes.LSHL, Opcodes.ISHR, Opcodes.LSHR, Opcodes.IUSHR, Opcodes.LUSHR, Opcodes.IAND, Opcodes.LAND, - Opcodes.IOR, Opcodes.LOR, Opcodes.IXOR, Opcodes.LXOR, Opcodes.LCMP, Opcodes.FCMPL, Opcodes.FCMPG, - Opcodes.DCMPL, Opcodes.DCMPG, - Opcodes.IALOAD, Opcodes.LALOAD, Opcodes.FALOAD, Opcodes.DALOAD, Opcodes.AALOAD, Opcodes.BALOAD, Opcodes.CALOAD, - Opcodes.SALOAD, - Opcodes.IF_ICMPEQ, Opcodes.IF_ICMPNE, Opcodes.IF_ICMPLT, Opcodes.IF_ICMPGE, Opcodes.IF_ICMPGT, Opcodes.IF_ICMPLE, - Opcodes.IF_ACMPEQ, Opcodes.IF_ACMPNE, - Opcodes.PUTFIELD, Opcodes.SWAP, Opcodes.DUP_X1 -> 2; - - case Opcodes.I2L, Opcodes.I2F, Opcodes.I2D, Opcodes.L2I, Opcodes.L2F, Opcodes.L2D, Opcodes.F2I, Opcodes.F2L, - Opcodes.F2D, Opcodes.D2I, Opcodes.D2L, Opcodes.D2F, Opcodes.I2B, Opcodes.I2C, Opcodes.I2S, Opcodes.INEG, - Opcodes.LNEG, Opcodes.FNEG, Opcodes.DNEG, Opcodes.CHECKCAST, Opcodes.INSTANCEOF, - Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, Opcodes.ASTORE, Opcodes.POP, Opcodes.DUP, - Opcodes.IFEQ, Opcodes.IFNE, Opcodes.IFLT, Opcodes.IFGE, Opcodes.IFGT, Opcodes.IFLE, Opcodes.IFNULL, Opcodes.IFNONNULL, - Opcodes.TABLESWITCH, Opcodes.LOOKUPSWITCH, - Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN, Opcodes.ATHROW, - Opcodes.MONITORENTER, Opcodes.MONITOREXIT, Opcodes.ARRAYLENGTH, Opcodes.NEWARRAY, Opcodes.ANEWARRAY, - Opcodes.GETFIELD, Opcodes.PUTSTATIC -> 1; - - default -> 0; - }; - } - - private static boolean isWide(@Nullable Frame frame, int depth) { - if (frame == null) { - return false; - } - int index = frame.getStackSize() - 1 - depth; - return index >= 0 && frame.getStack(index).getSize() == 2; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/flow/WrapOpAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/flow/WrapOpAnalyzer.java deleted file mode 100644 index 8659e87d..00000000 --- a/core/src/main/java/org/sinytra/adapter/analysis/flow/WrapOpAnalyzer.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.sinytra.adapter.analysis.flow; - -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.analysis.Frame; -import org.objectweb.asm.tree.analysis.SourceValue; -import org.sinytra.adapter.util.AdapterUtil; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class WrapOpAnalyzer { - public static List> groupArrayInitializers(MethodNode methodNode, AbstractInsnNode newArrayInsn) { - // 1. Analyze the method frames to track data flow - Frame[] frames = FrameUtil.getStableFrames(methodNode); - - // Map to hold the AASTORE instruction for each array index - Map indexToStoreNode = new HashMap<>(); - - // 2. Scan for AASTORE instructions targeting our array - InsnList instructions = methodNode.instructions; - for (int i = 0; i < instructions.size(); i++) { - AbstractInsnNode insn = instructions.get(i); - - if (insn.getOpcode() == Opcodes.AASTORE) { - Frame frame = frames[i]; - if (frame == null) continue; - - // Stack for AASTORE: [..., arrayRef, index, value] - // arrayRef is at stack size - 3 - if (frame.getStackSize() < 3) continue; - SourceValue arrayRefSource = frame.getStack(frame.getStackSize() - 3); - - // Check if this AASTORE is operating on the array created by newArrayInsn - if (arrayRefSource.insns.contains(newArrayInsn)) { - // Get the index (stack size - 2) - SourceValue indexSource = frame.getStack(frame.getStackSize() - 2); - Integer constantIndex = resolveConstantIndex(indexSource); - - if (constantIndex != null) { - indexToStoreNode.put(constantIndex, insn); - } - } - } - } - - // 3. Group the instructions linearly - // We assume the compiler emits instructions in contiguous blocks for each element - List> groups = new ArrayList<>(); - - // Start looking after the creation of the array - AbstractInsnNode currentStart = newArrayInsn.getNext(); - - // Find the maximum index we encountered to size our list - int maxIndex = indexToStoreNode.keySet().stream().max(Integer::compare).orElse(-1); - - for (int i = 0; i <= maxIndex; i++) { - AbstractInsnNode endNode = indexToStoreNode.get(i); - List currentGroup = new ArrayList<>(); - - if (endNode != null) { - // Collect everything from currentStart up to (and including) the AASTORE - AbstractInsnNode ptr = currentStart; - while (ptr != null) { - currentGroup.add(ptr); - if (ptr == endNode) break; - ptr = ptr.getNext(); - } - // Advance start pointer for the next group - currentStart = endNode.getNext(); - } - - groups.add(currentGroup); - } - - return groups; - } - - // Helper to extract the integer value from a SourceValue - private static Integer resolveConstantIndex(SourceValue source) { - for (AbstractInsnNode insn : source.insns) { - int index = AdapterUtil.getIntConstValue(insn).orElse(-1); - if (index != -1) { - return index; - } - } - // Could not resolve to a static constant - return null; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java index 1b9e21ae..3bab7c63 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java @@ -2,33 +2,18 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; -import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Handle; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.objectweb.asm.tree.analysis.*; -import org.sinytra.adapter.analysis.flow.FrameUtil; import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.OpcodeUtil; import java.util.*; -import java.util.function.BiConsumer; public class MethodAnalyzer { public static final String LAMBDA_PREFIX = "lambda$"; - private static final Set BOXED_TYPES = Set.of( - "java/lang/Boolean", "java/lang/Byte", "java/lang/Character", "java/lang/Short", - "java/lang/Integer", "java/lang/Float", "java/lang/Long", "java/lang/Double" - ); - private static final Set UNBOXING_METHODS = Set.of( - "booleanValue", "byteValue", "charValue", "shortValue", "intValue", "floatValue", "longValue", "doubleValue" - ); - public static boolean isLambda(MethodNode methodNode) { return methodNode.name.startsWith(LAMBDA_PREFIX); } @@ -45,55 +30,6 @@ public static boolean isDirtyDeprecatedMethod(MethodNode clean, MethodNode dirty return !isDeprecated(clean) && isDeprecated(dirty); } - // Return outer method calls where receiver = this - public static List getSelfTopTierMethodCalls(TargetPair targetPair) { - ClassNode classNode = targetPair.classNode(); - - List topTierCalls = getTopTierMethodCalls(targetPair); - List ownCalls = new ArrayList<>(); - - for (MethodInsnNode insn : topTierCalls) { - MethodNode method = AdapterUtil.findMethod(classNode, insn.name, insn.desc); - if (method != null) { - ownCalls.add(method); - } - } - - return ownCalls; - } - - // Return outer method calls - public static List getTopTierMethodCalls(TargetPair targetPair) { - return getTopTierMethodCalls(targetPair, false); - } - - public static List getTopTierMethodCalls(TargetPair targetPair, boolean skipImplicitCalls) { - MethodNode methodNode = targetPair.methodNode(); - InsnList instructions = methodNode.instructions; - Frame[] frames = FrameUtil.getFrames(methodNode); - - Set consumedCalls = new HashSet<>(); - for (AbstractInsnNode insn : instructions) { - if (insn instanceof MethodInsnNode call) { - traceInputs(call, frames, instructions, consumedCalls, new HashSet<>()); - } - } - - List topTierCalls = new ArrayList<>(); - for (AbstractInsnNode insn : instructions) { - if (insn instanceof MethodInsnNode call && !consumedCalls.contains(call)) { - if (skipImplicitCalls && isImplicitCall(call)) { - unwrapImplicitCall(call, frames, instructions, topTierCalls, new HashSet<>()); - } else if (!topTierCalls.contains(call)) { - topTierCalls.add(call); - } - } - } - topTierCalls.sort(Comparator.comparingInt(instructions::indexOf)); - - return topTierCalls; - } - public record IndyHandle(InvokeDynamicInsnNode indy, int argIndex, Handle handle, MethodNode method) { } @@ -146,59 +82,6 @@ public static List findDirtyOnlyCalls(MethodNode clean, MethodNo return dirtyOnly; } - private static boolean isImplicitCall(MethodInsnNode minsn) { - if (!BOXED_TYPES.contains(minsn.owner)) { - return false; - } - if (minsn.getOpcode() == Opcodes.INVOKESTATIC && minsn.name.equals("valueOf")) { - Type[] args = Type.getArgumentTypes(minsn.desc); - return args.length == 1 && args[0].getSort() <= Type.DOUBLE; - } - return minsn.getOpcode() == Opcodes.INVOKEVIRTUAL && UNBOXING_METHODS.contains(minsn.name) && Type.getArgumentCount(minsn.desc) == 0; - } - - private static void unwrapImplicitCall(MethodInsnNode call, Frame[] frames, InsnList instructions, List out, Set seen) { - if (!seen.add(call)) return; - - Set wrapped = new LinkedHashSet<>(); - traceInputs(call, frames, instructions, wrapped, new HashSet<>()); - if (wrapped.isEmpty()) { - if (!out.contains(call)) { - out.add(call); - } - return; - } - for (MethodInsnNode inner : wrapped) { - if (isImplicitCall(inner)) { - unwrapImplicitCall(inner, frames, instructions, out, seen); - } else if (!out.contains(inner)) { - out.add(inner); - } - } - } - - private static void collectProducerCalls(SourceValue value, Frame[] frames, InsnList indices, Set consumedCalls, Set visited) { - for (AbstractInsnNode producer : value.insns) { - if (!visited.add(producer)) continue; - - if (producer instanceof MethodInsnNode producerCall) { - consumedCalls.add(producerCall); - continue; - } - - traceInputs(producer, frames, indices, consumedCalls, visited); - } - } - - private static void traceInputs(AbstractInsnNode insn, Frame[] frames, InsnList indices, Set consumedCalls, Set visited) { - Frame frame = frames[indices.indexOf(insn)]; - if (frame == null) return; - - for (SourceValue input : FrameUtil.getOperands(frame, insn)) { - collectProducerCalls(input, frames, indices, consumedCalls, visited); - } - } - public static List findCalls(MethodNode methodNode, MethodQualifier qualifier) { List calls = new ArrayList<>(); for (AbstractInsnNode insn : methodNode.instructions) { @@ -208,114 +91,4 @@ public static List findCalls(MethodNode methodNode, MethodQualif } return calls; } - - public static void forEachCall(MethodNode methodNode, BiConsumer> action) { - Frame[] frames = FrameUtil.getFrames(methodNode); - for (AbstractInsnNode insn : methodNode.instructions) { - if (insn instanceof MethodInsnNode minsn) { - Frame frame = frames[methodNode.instructions.indexOf(insn)]; - if (frame != null) { // Dead code otherwise - action.accept(minsn, FrameUtil.getOperands(frame, minsn)); - } - } - } - } - - public static void forEachReceiver(MethodNode methodNode, BiConsumer> action) { - forEachCall(methodNode, (call, operands) -> { - if (call.getOpcode() != Opcodes.INVOKESTATIC && operands.size() == Type.getArgumentCount(call.desc) + 1) { - action.accept(call, operands.getFirst().insns); - } - }); - } - - @Nullable - public static List getMethodCallInsns(MethodNode methodNode, MethodInsnNode minsn) { - List insns = getMethodCallSrcInsns(methodNode, minsn); - return insns != null && !insns.isEmpty() ? AdapterUtil.subListInsnsExc(insns.getFirst(), minsn) : null; - } - - public static List> getAllMethodCallSrcInsnsInclusive(MethodNode methodNode, MethodQualifier qualifier) { - Frame[] frames = FrameUtil.getStableFrames(methodNode); - List> list = new ArrayList<>(); - for (MethodInsnNode minsn : findCalls(methodNode, qualifier)) { - List insns = getMethodCallSrcInsns(frames, methodNode, minsn); - if (insns != null) { - insns.add(minsn); - list.add(insns); - } - } - return list; - } - - @Nullable - public static List getMethodCallSrcInsns(MethodNode methodNode, MethodInsnNode minsn) { - return getMethodCallSrcInsns(methodNode, minsn, false); - } - - @Nullable - public static List getMethodCallSrcInsns(MethodNode methodNode, MethodInsnNode minsn, boolean stable) { - // Handle cases where the minsn comes from another method - if (!methodNode.instructions.contains(minsn)) { - List minsns = findCalls(methodNode, MethodQualifier.create(minsn)); - return minsns.size() == 1 ? getMethodCallSrcInsns(methodNode, minsns.getFirst(), stable) : null; - } - Frame[] frames = stable ? FrameUtil.getStableFrames(methodNode) : FrameUtil.getFrames(methodNode); - return getMethodCallSrcInsns(frames, methodNode, minsn); - } - - @Nullable - private static List getMethodCallSrcInsns(Frame[] frames, MethodNode methodNode, MethodInsnNode minsn) { - Frame frame = frames[methodNode.instructions.indexOf(minsn)]; - if (frame == null) { - return null; - } - - List insns = new ArrayList<>(); - for (SourceValue src : FrameUtil.getOperands(frame, minsn)) { - AbstractInsnNode srcInsn = AdapterUtil.getSingleInsn(src); - if (srcInsn == null) { - return null; - } - insns.add(srcInsn); - } - return insns; - } - - public static List> getMethodCallArgInsns(MethodNode methodNode, MethodInsnNode minsn) { - List> args = new ArrayList<>(); - if (!methodNode.instructions.contains(minsn)) { - return args; - } - - Frame[] frames = FrameUtil.getFrames(methodNode); - Frame frame = frames[methodNode.instructions.indexOf(minsn)]; - if (frame == null) { - return args; - } - - for (SourceValue value : FrameUtil.getOperands(frame, minsn)) { - List insns = new ArrayList<>(); - for (AbstractInsnNode insn : value.insns) { - expandArgInitInsnsRecursive(insns, methodNode.instructions, frames, insn); - insns.add(insn); - } - args.add(insns); - } - return args; - } - - private static void expandArgInitInsnsRecursive(List out, InsnList insns, Frame[] frames, AbstractInsnNode insn) { - int index = insns.indexOf(insn); - if (index < 0 || index >= frames.length || frames[index] == null) return; - - for (SourceValue value : FrameUtil.getOperands(frames[index], insn)) { - for (AbstractInsnNode sourceInsn : value.insns) { - if (!out.contains(sourceInsn)) { - expandArgInitInsnsRecursive(out, insns, frames, sourceInsn); - out.add(sourceInsn); - } - } - } - } } diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/WrapOpOriginalCall.java b/core/src/main/java/org/sinytra/adapter/analysis/method/WrapOpOriginalCall.java index 2dd4b688..2c743df5 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/WrapOpOriginalCall.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/WrapOpOriginalCall.java @@ -4,7 +4,8 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.flow.WrapOpAnalyzer; +import org.sinytra.adapter.analysis.flow.DataFlow; +import org.sinytra.adapter.analysis.flow.DataFlow.Producers; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.util.AdapterUtil; @@ -19,12 +20,12 @@ public record WrapOpOriginalCall(List args) { public static final MethodQualifier QUALIFIER = new MethodQualifier(TypeConstants.OPERATION_TYPE.getDescriptor(), "call", "([Ljava/lang/Object;)Ljava/lang/Object;"); public static List> findAll(MethodNode methodNode) { - return MethodAnalyzer.getAllMethodCallSrcInsnsInclusive(methodNode, QUALIFIER); + return DataFlow.of(methodNode, Producers.COMPUTER).findCallsWithProducers(QUALIFIER); } public static List findFirstCallInsns(MethodNode methodNode) { List calls = MethodAnalyzer.findCalls(methodNode, QUALIFIER); - return calls.isEmpty() ? List.of() : Objects.requireNonNullElseGet(MethodAnalyzer.getMethodCallInsns(methodNode, calls.getFirst()), List::of); + return calls.isEmpty() ? List.of() : Objects.requireNonNullElseGet(DataFlow.of(methodNode).resolveOperandProducers(calls.getFirst()), List::of); } public static List findCallVarInsns(MethodNode methodNode) { @@ -43,7 +44,7 @@ public static WrapOpOriginalCall parse(MethodNode methodNode, List pre, List load, List post) { diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/ModifyArgsOffsetUpgrader.java b/core/src/main/java/org/sinytra/adapter/edit/param/ModifyArgsOffsetUpgrader.java index 235ce3db..89523966 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/param/ModifyArgsOffsetUpgrader.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ModifyArgsOffsetUpgrader.java @@ -2,7 +2,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.util.AdapterUtil; @@ -16,7 +16,7 @@ public class ModifyArgsOffsetUpgrader { public static boolean upgradeAfterParamInsert(MethodNode methodNode, int index) { Set insns = new LinkedHashSet<>(); - MethodAnalyzer.forEachCall(methodNode, (call, operands) -> { + DataFlow.of(methodNode).forEachCall((call, operands) -> { if ((ARGS_GET.matches(call) || ARGS_SET.matches(call)) && operands.size() > 1) { insns.add(AdapterUtil.getSingleInsn(operands.get(1))); } diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java b/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java index ea760140..ca95da69 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java @@ -7,8 +7,8 @@ import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.param.Annotation; import org.sinytra.adapter.model.param.Parameter; import org.sinytra.adapter.util.AdapterUtil; @@ -134,7 +134,7 @@ public static void applyAnnotations(MethodNode method, List parameter public static void retypeVariableAndReceivers(MethodNode methodNode, VarInsnNode targetVarInsn, int newIndex, String newOwner) { targetVarInsn.var = newIndex; - MethodAnalyzer.forEachReceiver(methodNode, (call, receiver) -> { + DataFlow.of(methodNode).forEachReceiver((call, receiver) -> { if (receiver.contains(targetVarInsn)) { call.owner = newOwner; } diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java index 15329ea9..b8a23cda 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java @@ -4,8 +4,8 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.WrapOpOriginalCall; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.api.TypeAdapter; @@ -85,7 +85,7 @@ public PatchResult apply(ClassNode classNode, MethodNode methodNode, MixinContex } // Calls made on the variable must target the new type unless its value was adapted back to the original above - MethodAnalyzer.forEachReceiver(methodNode, (call, receiver) -> { + DataFlow.of(methodNode).forEachReceiver((call, receiver) -> { if (call.owner.equals(originalType.getInternalName()) && receiver.stream().anyMatch(p -> p instanceof VarInsnNode varInsn && varInsn.var == localVar.index) ) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java index c8a296a5..e82d39e1 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java @@ -4,6 +4,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.injection.InjectionLocator; import org.sinytra.adapter.analysis.method.MethodHelper; import org.sinytra.adapter.analysis.method.MethodLocator; @@ -25,7 +26,9 @@ import org.sinytra.adapter.util.AdapterUtil; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Supplier; @@ -47,6 +50,8 @@ public class MixinContext implements RefMapper, Auditor { private final MethodLocator cleanMethods; private final MethodLocator dirtyMethods; private final InjectionLocator injections; + + private final Map targetFlows; private final Resolvers resolvers = new Resolvers(); private final Processors processors = new Processors(); @@ -68,6 +73,8 @@ public MixinContext(MixinType mixinType, PatchContext patchContext, ClassTarget this.originalMethodNode = AdapterUtil.copyMethod(this.methodNode); this.mixinId = classNode.name + "#" + methodNode.name + methodNode.desc; + + this.targetFlows = new HashMap<>(); } public MixinType getMixinType() { @@ -123,6 +130,10 @@ public InjectionLocator injections() { return this.injections; } + public DataFlow dataFlow(TargetPair target) { + return this.targetFlows.computeIfAbsent(target.methodNode(), DataFlow::of); + } + @Nullable public Targets targets(MethodQualifier qualifier) { TargetPair clean = this.cleanMethods.resolve(qualifier); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java index 05ad0363..5f755418 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java @@ -3,8 +3,8 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.edit.param.ReplaceParametersTransformer; @@ -68,7 +68,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe // Add casts to usage in method calls List methodArgs = Parameters.getParameterTypes(minsn.desc); - List callArgs = MethodAnalyzer.getMethodCallSrcInsns(methodNode, minsn); + List callArgs = DataFlow.of(methodNode).resolveCallOperandProducers(minsn); if (callArgs == null) continue; int instanceOffset = minsn.getOpcode() == Opcodes.INVOKESTATIC ? 0 : 1; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java index 728e430e..90644957 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java @@ -4,9 +4,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.analysis.Frame; -import org.objectweb.asm.tree.analysis.SourceValue; -import org.sinytra.adapter.analysis.flow.FrameUtil; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.TypeAdapter; import org.sinytra.adapter.model.config.Configuration; @@ -33,13 +31,10 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe } MethodNode methodNode = context.methodNode(); - Frame[] frames = FrameUtil.getFrames(methodNode); + DataFlow flow = DataFlow.of(methodNode); Set sources = new LinkedHashSet<>(); for (AbstractInsnNode ret : MethodAnalyzer.findReturnInsns(methodNode)) { - Frame frame = frames[methodNode.instructions.indexOf(ret)]; - if (frame != null) { - FrameUtil.getOperands(frame, ret).forEach(value -> sources.addAll(value.insns)); - } + flow.operands(ret).forEach(value -> sources.addAll(value.insns)); } for (AbstractInsnNode insn : sources) { if (insn.getOpcode() != Opcodes.ACONST_NULL) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java index 44cdcad2..58de0b11 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java @@ -5,7 +5,6 @@ import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodHelper; import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.PatchResult; @@ -48,7 +47,7 @@ public static PatchResult apply(MixinContext context, Recipe recipe, String dest return PatchResult.PASS; } - List callInsns = MethodAnalyzer.getMethodCallSrcInsns(dirtyTarget.methodNode(), destinationMethodInvocation); + List callInsns = context.dataFlow(dirtyTarget).resolveCallOperandProducers(destinationMethodInvocation); if (callInsns == null || callInsns.size() <= selfIndex) { return PatchResult.PASS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java index bc570c8e..fcb17cd9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java @@ -4,8 +4,8 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.edit.param.InjectParameterTransform; @@ -41,7 +41,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(cleanParams, dirtyParams); if (diff == null) return TxResult.PASS; - List> callInsns = MethodAnalyzer.getAllMethodCallSrcInsnsInclusive(context.methodNode(), cleanTargetQual); + List> callInsns = DataFlow.of(context.methodNode(), DataFlow.Producers.COMPUTER).findCallsWithProducers(cleanTargetQual); if (callInsns.isEmpty()) { return TxResult.SUCCESS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java index c9d567c8..2597e653 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java @@ -7,8 +7,8 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.WrapOpOriginalCall; import org.sinytra.adapter.api.TypeAdapter; import org.sinytra.adapter.model.TargetPair; @@ -69,7 +69,7 @@ public static boolean tryUpgrade(MixinContext context, Recipe recipe, List TargetPair cleanTarget = recipe.getCleanTarget(); if (cleanTarget == null) return null; - List> callArgInsns = MethodAnalyzer.getMethodCallArgInsns(cleanTarget.methodNode(), cleanInsn); + List> callArgInsns = context.dataFlow(cleanTarget).findOperandExpressions(cleanInsn); if (callArgInsns.isEmpty()) return null; List receiverInsns = callArgInsns.getFirst(); @@ -89,7 +89,7 @@ public static boolean tryUpgrade(MixinContext context, Recipe recipe, List TypeAdapter adapter = typeAdapter.andThen((list, insn) -> { // Fix receiver type originally changed by ReplaceParametersTransformer - MethodAnalyzer.forEachReceiver(context.methodNode(), (call, receiver) -> { + DataFlow.of(context.methodNode()).forEachReceiver((call, receiver) -> { if (call.owner.equals(dirtyInsn.owner) && receiver.contains(insn)) { call.owner = cleanInsn.owner; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java index 918d3683..c7173ffa 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java @@ -8,7 +8,6 @@ import org.sinytra.adapter.analysis.compare.MethodLabelComparator; import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; @@ -153,7 +152,7 @@ private static Optional handleWrapOperationToInstanceOf(Recipe re LocalVariableLookup dirtyLocals = recipe.dirtyLocalsTable(); if (cleanTarget == null || cleanLocals == null || dirtyLocals == null) return Optional.empty(); - List originalCallArgs = MethodAnalyzer.getMethodCallSrcInsns(cleanTarget.methodNode(), cleanInjectionInsn); + List originalCallArgs = context.dataFlow(cleanTarget).resolveCallOperandProducers(cleanInjectionInsn); if (originalCallArgs == null || originalCallArgs.isEmpty() || !(originalCallArgs.getFirst() instanceof VarInsnNode originalInstanceInsn)) return Optional.empty(); LocalVariableNode originalInstanceLocal = cleanLocals.getByIndexOrNull(originalInstanceInsn.var); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java index 515a4afe..627218a0 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java @@ -69,7 +69,7 @@ public class InjectionPointSubResolvers { if (minsn.getOpcode() != Opcodes.INVOKESTATIC || minsn.owner.equals(targets.dirty().classNode().name)) continue; - List argInsns = MethodAnalyzer.getMethodCallSrcInsns(targets.dirty().methodNode(), minsn); + List argInsns = context.dataFlow(targets.dirty()).resolveCallOperandProducers(minsn); if (argInsns == null) continue; AbstractInsnNode self = argInsns.stream() diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java index bdefbd7e..99634d67 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java @@ -5,7 +5,6 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.mixin.MockMixinRuntime; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; @@ -97,7 +96,7 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { return ResolutionResult.pass(); } // Get method call argument instructions - List args = MethodAnalyzer.getMethodCallSrcInsns(targets.dirty().methodNode(), dirtyMinsn); + List args = context.dataFlow(targets.dirty()).resolveCallOperandProducers(dirtyMinsn); if (args == null) { return ResolutionResult.pass(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java index 821b7e0c..4386c780 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java @@ -35,7 +35,7 @@ public static void handle(MixinContext context, Recipe recipe, MethodNode newTar return; } - List invocations = MethodAnalyzer.getSelfTopTierMethodCalls(newCleanTarget); + List invocations = context.dataFlow(newCleanTarget).findTopLevelOwnCalls(newCleanTarget.classNode()); if (invocations == null) { return; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java index 0e935ddf..528694e2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java @@ -58,7 +58,7 @@ private static List locateCandidates(MixinContext context, Targ return tryFindPartialCandidates(cleanTargetMethod, dirtyTargetClass, dirtyTargetMethod, context); } - List invocations = MethodAnalyzer.getSelfTopTierMethodCalls(dirtyTarget); + List invocations = context.dataFlow(dirtyTarget).findTopLevelOwnCalls(dirtyTarget.classNode()); if (invocations == null) { return List.of(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java index 0e877e7e..1efe5b88 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java @@ -98,7 +98,7 @@ private static Configuration resolveOverloadedReplacement(MixinContext context, TargetPair dirtyTarget = recipe.getNewCleanTarget(); if (dirtyTarget == null || !MethodAnalyzer.isDeprecated(dirtyTarget.methodNode())) return null; - List invocations = MethodAnalyzer.getSelfTopTierMethodCalls(dirtyTarget); + List invocations = context.dataFlow(dirtyTarget).findTopLevelOwnCalls(dirtyTarget.classNode()); for (MethodNode invocation : invocations) { if (context.injections().matches(new TargetPair(dirtyTarget.classNode(), invocation))) { return MutableConfiguration.create() diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java index 7efcf520..c67a5128 100644 --- a/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java @@ -5,7 +5,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.api.BytecodeFixerUpper; import org.sinytra.adapter.api.ClassTransformer; import org.sinytra.adapter.api.PatchResult; @@ -68,7 +68,7 @@ public PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchCont // Search for method calls made on modified class fields and update their owners to match the new field types if (!classUpdatedTypes.isEmpty()) { AtomicBoolean retargeted = new AtomicBoolean(); - MethodAnalyzer.forEachReceiver(method, (call, receiver) -> { + DataFlow.of(method).forEachReceiver((call, receiver) -> { if (call.getOpcode() == Opcodes.INVOKEVIRTUAL && AdapterUtil.getSingleOrNull(receiver) instanceof FieldInsnNode finsn && finsn.owner.equals(classNode.name) From efda11bea2d0b427620a3f2dd5e387fe15151fd4 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 19:20:06 +0200 Subject: [PATCH 05/44] Clean up LocalVarAnalyzer --- .../adapter/analysis/compare/CodePaths.java | 2 +- .../analysis/locals/LocalVariableLookup.java | 9 +- ...alVarAnalyzer.java => LocalsAnalyzer.java} | 144 +++++++++++++----- .../analysis/method/MethodAnalyzer.java | 5 + .../adapter/analysis/method/MethodHelper.java | 117 -------------- .../edit/param/InjectParameterTransform.java | 4 +- .../param/ReplaceParametersTransformer.java | 2 +- .../sinytra/adapter/pipeline/MixinParser.java | 4 +- .../org/sinytra/adapter/pipeline/Recipe.java | 8 +- .../adapter/pipeline/ctx/MixinContext.java | 17 ++- .../injector/ModifyExpressionValueMixin.java | 2 +- .../injector/ModifyReturnValueMixin.java | 2 +- .../injector/ModifyVariableMixin.java | 2 +- .../pipeline/injector/RedirectMixin.java | 8 +- .../pipeline/injector/WrapOperationMixin.java | 6 +- .../LocalCaptureUpgradeTransformer.java | 10 +- .../processor/ParametersPostProcessor.java | 2 +- .../processor/StaticAccessProcessor.java | 4 +- .../processor/TargetMethodProcessor.java | 8 +- .../extract/ExtractMixinProcessor.java | 8 +- .../extract/MirrorableExtractMixin.java | 6 +- .../extract/ProxyExtractMixinSub.java | 3 +- .../redirect/ParameterUsageProcessor.java | 2 +- .../wrapop/WrapOpParamsProcessor.java | 2 +- .../processor/wrapop/WrapOpSurgeon.java | 2 +- .../AtVariableAssignStoreSubResolver.java | 6 +- .../ComparingInjectionPointResolver.java | 2 +- .../ModifyVarInjectionPointSubResolver.java | 8 +- .../special/InjectorOrdinalResolver.java | 6 +- .../types/FieldAccessorTypeTransformer.java | 4 +- 30 files changed, 181 insertions(+), 224 deletions(-) rename core/src/main/java/org/sinytra/adapter/analysis/locals/{LocalVarAnalyzer.java => LocalsAnalyzer.java} (59%) delete mode 100644 core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java index a3800a23..71f66836 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java @@ -81,7 +81,7 @@ private static CodePath findCodePathRecursive(TargetPair from, TargetPair to, Se TargetPair nextFrom = context.dirtyMethods().resolveInherited(MethodQualifier.create(minsn)); if (nextFrom == null) continue; - LocalVariableLookup lvt = context.methods().getLVT(nextFrom.methodNode()); + LocalVariableLookup lvt = LocalVariableLookup.of(nextFrom.methodNode()); if (lvt == null) { continue; } diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java index 76a48c72..84b84cde 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java @@ -6,7 +6,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import java.util.*; @@ -18,8 +18,13 @@ public class LocalVariableLookup { private final Int2ObjectMap byIndex = new Int2ObjectOpenHashMap<>(); private final Map> byType = new HashMap<>(); + @Nullable + public static LocalVariableLookup of(MethodNode method) { + return method.localVariables != null ? new LocalVariableLookup(method) : null; + } + public LocalVariableLookup(MethodNode methodNode) { - this.isNonStatic = !MethodHelper.isStatic(methodNode); + this.isNonStatic = !MethodAnalyzer.isStatic(methodNode); List localVariables = methodNode.localVariables; this.sortedLocals = localVariables == null ? List.of() : localVariables.stream() diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVarAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java similarity index 59% rename from core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVarAnalyzer.java rename to core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index 0e9ee854..5544ddde 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVarAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -10,15 +10,17 @@ import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.InsnTraversal; import org.sinytra.adapter.analysis.compare.InstructionMatcher; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; +import org.sinytra.adapter.analysis.params.ParamDiffResolver; import org.sinytra.adapter.edit.param.TransformParameters; +import org.sinytra.adapter.mixin.MockMixinRuntime; import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.param.MethodParameters; import org.sinytra.adapter.model.param.Parameter; import org.sinytra.adapter.model.param.Parameters; -import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.OpcodeUtil; @@ -27,10 +29,19 @@ import java.util.*; import java.util.function.Consumer; import java.util.stream.IntStream; +import java.util.stream.Stream; -public final class LocalVarAnalyzer { +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; + +public final class LocalsAnalyzer { private static final Logger LOGGER = LogUtils.getLogger(); + private final MixinContext context; + + public LocalsAnalyzer(MixinContext context) { + this.context = context; + } + public record CapturedLocals(TargetPair target, boolean isStatic, int paramLocalStart, int paramLocalEnd, int lvtOffset, List expected, LocalVariableLookup lvt) { } @@ -57,34 +68,31 @@ private static CapturedParameters findCapturedParameters(MethodNode methodNode) return null; } - public static boolean capturesLocals(MixinContext context) { - CapturedParameters captured = findCapturedParameters(context.methodNode()); + public boolean capturesLocals() { + CapturedParameters captured = findCapturedParameters(this.context.methodNode()); return captured != null && !captured.types().isEmpty(); } @Nullable - public static CapturedLocals findCapturedLocals(MixinContext context, Recipe recipe) { - TargetPair target = recipe.getDirtyTarget(); - return target != null ? findCapturedLocals(context, target) : null; - } - - @Nullable - public static CapturedLocals findCapturedLocals(MixinContext context, TargetPair dirtyTarget) { - MethodNode methodNode = context.methodNode(); + public CapturedLocals resolveCapturedLocals(@Nullable TargetPair dirtyTarget) { + if (dirtyTarget == null) { + return null; + } + MethodNode methodNode = this.context.methodNode(); CapturedParameters captured = findCapturedParameters(methodNode); if (captured == null) { - LOGGER.debug("Missing CI or CIR argument in injector of type {}", context.methodAnnotation().getDesc()); + LOGGER.debug("Missing CI or CIR argument in injector of type {}", this.context.methodAnnotation().getDesc()); return null; } if (captured.types().isEmpty()) { return null; } - LocalVariableLookup lvt = context.methods().getLVT(methodNode); + LocalVariableLookup lvt = LocalVariableLookup.of(methodNode); if (lvt == null) { LOGGER.debug("Missing local variable table in injector {}", methodNode.name); return null; } - boolean isStatic = MethodHelper.isStatic(methodNode); + boolean isStatic = MethodAnalyzer.isStatic(methodNode); int end = captured.start() + captured.types().size() - 1; return new CapturedLocals(dirtyTarget, isStatic, captured.start(), end, isStatic ? 0 : 1, captured.types(), lvt); } @@ -93,12 +101,12 @@ public record CapturedLocalsInfo(CapturedLocals capturedLocals, LayeredParamsDif } @Nullable - public static CapturedLocalsInfo getCapturedLocals(MixinContext context, TargetPair dirtyTarget) { - CapturedLocals capturedLocals = findCapturedLocals(context, dirtyTarget); + public CapturedLocalsInfo resolveCapturedLocalsInfo(TargetPair dirtyTarget) { + CapturedLocals capturedLocals = resolveCapturedLocals(dirtyTarget); if (capturedLocals == null) return null; // Get available local variables at the injection point in the target method - List available = context.methods().getTargetMethodLocals(capturedLocals.target()); + List available = resolveAvailableLocals(capturedLocals.target()); if (available == null) return null; List availableTypes = available.stream().map(LocalVariable::type).toList(); @@ -155,26 +163,85 @@ public record CapturedLocalsUsage( } public record CapturedLocalsTransform(Collection used, TransformParameters remover, Collection usedLocalNodes) { - public CapturedLocalsUsage getUsage(CapturedLocals captured, MixinContext context) { - LocalVariableLookup targetTable = context.methods().getLVT(captured.target().methodNode()); - Int2ObjectMap usedLocals = new Int2ObjectOpenHashMap<>(); - Int2ObjectMap varInsnLists = new Int2ObjectOpenHashMap<>(); - Int2IntMap usageCount = new Int2IntOpenHashMap(); - - // Captured locals are the locals available at the injection point, in order - List available = context.methods().getTargetMethodLocals(captured.target()); - if (targetTable != null && available != null) { - this.used.forEach(ordinal -> { - int localIndex = ordinal - captured.paramLocalStart(); - if (localIndex >= 0 && localIndex < available.size()) { - LocalVariable local = available.get(localIndex); - usedLocals.put((int) ordinal, local); - findVariableInitializerInsns(captured.target().methodNode(), captured.isStatic(), local.index(), varInsnLists, usageCount); - } - }); + } + + public CapturedLocalsUsage resolveUsage(CapturedLocals captured, CapturedLocalsTransform transform) { + LocalVariableLookup targetTable = LocalVariableLookup.of(captured.target().methodNode()); + Int2ObjectMap usedLocals = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap varInsnLists = new Int2ObjectOpenHashMap<>(); + Int2IntMap usageCount = new Int2IntOpenHashMap(); + + // Captured locals are the locals available at the injection point, in order + List available = resolveAvailableLocals(captured.target()); + if (targetTable != null && available != null) { + transform.used().forEach(ordinal -> { + int localIndex = ordinal - captured.paramLocalStart(); + if (localIndex >= 0 && localIndex < available.size()) { + LocalVariable local = available.get(localIndex); + usedLocals.put((int) ordinal, local); + findVariableInitializerInsns(captured.target().methodNode(), captured.isStatic(), local.index(), varInsnLists, usageCount); + } + }); + } + return new CapturedLocalsUsage(targetTable, usedLocals, usageCount, varInsnLists); + } + + @Nullable + public List resolveAvailableLocals(TargetPair target) { + Type[] targetParams = Type.getArgumentTypes(target.methodNode().desc); + int lvtOffset = MethodAnalyzer.isStatic(target.methodNode()) ? 0 : 1; + int targetLocalPos = targetParams.length + lvtOffset; + + List targetInsns = this.context.injections().find(target); + if (targetInsns.isEmpty()) { + LOGGER.debug("Skipping LVT patch, no target instructions found"); + return null; + } + + LocalVariableNode[] localVariables = MockMixinRuntime.getLocalsAt(target, targetInsns.getFirst(), this.context.environment().fabricLVTCompatibility()); + LocalVariable[] locals = Stream.of(localVariables) + .filter(Objects::nonNull) + .map(lv -> new LocalVariable(lv.index, Type.getType(lv.desc))) + .toArray(LocalVariable[]::new); + return AdapterUtil.summariseLocals(locals, targetLocalPos); + } + + public List findDirtyCapturedParams(Configuration clean, Configuration dirty) { + List cleanCaptured = clean.getParameters().getTypes(CAPTURED_PARAMS); + List dirtyCaptured = new ArrayList<>(); + + if (!cleanCaptured.isEmpty()) { + TargetPair cleanPair = this.context.cleanMethods().resolve(clean.getTargetMethod()); + TargetPair dirtyPair = this.context.dirtyMethods().resolve(dirty.getTargetMethod()); + if (cleanPair == null || dirtyPair == null) return dirtyCaptured; + MethodNode cleanTarget = cleanPair.methodNode(); + MethodNode dirtyTarget = dirtyPair.methodNode(); + + LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.compareMethodParameters(cleanTarget, dirtyTarget); + if (diff == null) return dirtyCaptured; + + List cleanTargetParams = Parameters.getParameterTypes(cleanTarget.desc); + if (cleanCaptured.size() > cleanTargetParams.size()) return dirtyCaptured; + + List capturedSublist = cleanTargetParams.subList(0, cleanCaptured.size()); + if (!cleanCaptured.equals(capturedSublist)) return dirtyCaptured; + + ParamDiffResolver.ParamEvalResult evalResult = ParamDiffResolver.resolve(cleanTargetParams, diff); + + int maxIndex = capturedSublist.stream() + .map(evalResult::getUpdated) + .filter(Objects::nonNull) + .mapToInt(ParamDiffResolver.ParamState::dirtyIndex) + .max() + .orElse(-1); + + if (maxIndex != -1) { + List dirtyTargetParams = Parameters.getParameterTypes(dirtyTarget.desc); + dirtyCaptured = List.copyOf(dirtyTargetParams.subList(0, maxIndex + 1)); } - return new CapturedLocalsUsage(targetTable, usedLocals, usageCount, varInsnLists); } + + return dirtyCaptured; } public static CapturedLocalsTransform analyzeCapturedLocals(CapturedLocals capturedLocals, MethodNode methodNode) { @@ -219,7 +286,4 @@ public static void findVariableInitializerInsns(MethodNode methodNode, boolean i }); varInsnLists.put(index, insns); } - - private LocalVarAnalyzer() { - } } diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java index 3bab7c63..73645646 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.constant.TypeConstants; @@ -14,6 +15,10 @@ public class MethodAnalyzer { public static final String LAMBDA_PREFIX = "lambda$"; + public static boolean isStatic(MethodNode methodNode) { + return (methodNode.access & Opcodes.ACC_STATIC) != 0; + } + public static boolean isLambda(MethodNode methodNode) { return methodNode.name.startsWith(LAMBDA_PREFIX); } diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java deleted file mode 100644 index 9e02ed45..00000000 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodHelper.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.sinytra.adapter.analysis.method; - -import com.mojang.logging.LogUtils; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.locals.LocalVariable; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; -import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; -import org.sinytra.adapter.analysis.params.ParamDiffResolver; -import org.sinytra.adapter.mixin.MockMixinRuntime; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.param.Parameters; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.util.AdapterUtil; -import org.slf4j.Logger; - -import java.util.*; -import java.util.stream.Stream; - -import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; - -public class MethodHelper { - private static final Logger LOGGER = LogUtils.getLogger(); - - private final MixinContext context; - - public MethodHelper(MixinContext context) { - this.context = context; - } - - public List resolveCapturedMethodParams(Configuration clean, Configuration dirty) { - List cleanCaptured = clean.getParameters().getTypes(CAPTURED_PARAMS); - List dirtyCaptured = new ArrayList<>(); - - // Evaluate parameter difference, capture additional params when necessary - if (!cleanCaptured.isEmpty()) { - TargetPair cleanPair = this.context.cleanMethods().resolve(clean.getTargetMethod()); - TargetPair dirtyPair = this.context.dirtyMethods().resolve(dirty.getTargetMethod()); - if (cleanPair == null || dirtyPair == null) return dirtyCaptured; - MethodNode cleanTarget = cleanPair.methodNode(); - MethodNode dirtyTarget = dirtyPair.methodNode(); - - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.compareMethodParameters(cleanTarget, dirtyTarget); - if (diff == null) return dirtyCaptured; - - List cleanTargetParams = Parameters.getParameterTypes(cleanTarget.desc); - // Expect cleanTargetParams to begin with or be equal to cleanCaptured - if (cleanCaptured.size() > cleanTargetParams.size()) return dirtyCaptured; - // Use a sublist instead of cleanCaptured to be able to compare Type instances directly - List capturedSublist = cleanTargetParams.subList(0, cleanCaptured.size()); - if (!cleanCaptured.equals(capturedSublist)) return dirtyCaptured; - - ParamDiffResolver.ParamEvalResult evalResult = ParamDiffResolver.resolve(cleanTargetParams, diff); - - int maxIndex = capturedSublist.stream() - .map(evalResult::getUpdated) - .filter(Objects::nonNull) - .mapToInt(ParamDiffResolver.ParamState::dirtyIndex) - .max() - .orElse(-1); - - if (maxIndex != -1) { - List dirtyTargetParams = Parameters.getParameterTypes(dirtyTarget.desc); - dirtyCaptured = List.copyOf(dirtyTargetParams.subList(0, maxIndex + 1)); - } - } - - return dirtyCaptured; - } - - @Nullable - public List getTargetMethodLocals(TargetPair target) { - Type[] targetParams = Type.getArgumentTypes(target.methodNode().desc); - boolean isStatic = isStatic(target.methodNode()); - int lvtOffset = isStatic ? 0 : 1; - // The starting LVT index is of the first var after all method parameters. Offset by 1 for instance methods to skip 'this' - int targetLocalPos = targetParams.length + lvtOffset; - return getTargetMethodLocals(target, targetLocalPos); - } - - @Nullable - public List getTargetMethodLocals(TargetPair target, int startPos) { - return getTargetMethodLocals(target, startPos, this.context.environment().fabricLVTCompatibility()); - } - - @Nullable - public List getTargetMethodLocals(TargetPair target, int startPos, int lvtCompatLevel) { - List targetInsns = this.context.injections().find(target); - if (targetInsns.isEmpty()) { - LOGGER.debug("Skipping LVT patch, no target instructions found"); - return null; - } - // Get available local variables at the injection point in the target method - LocalVariableNode[] localVariables = MockMixinRuntime.getLocalsAt(target, targetInsns.getFirst(), lvtCompatLevel); - LocalVariable[] locals = Stream.of(localVariables) - .filter(Objects::nonNull) - .map(lv -> new LocalVariable(lv.index, Type.getType(lv.desc))) - .toArray(LocalVariable[]::new); - return AdapterUtil.summariseLocals(locals, startPos); - } - - @Nullable - public LocalVariableLookup getLVT(MethodNode method) { - if (method.localVariables == null) { - return null; - } - return new LocalVariableLookup(method); - } - - public static boolean isStatic(MethodNode node) { - return (node.access & Opcodes.ACC_STATIC) != 0; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java b/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java index 897d2ebc..acc24e60 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java @@ -4,7 +4,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.TypeReference; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.edit.LVTSnapshot; import org.sinytra.adapter.model.ann.AnnotationHandle; @@ -28,7 +28,7 @@ public InjectParameterTransform offset(int offset) { @Override public PatchResult apply(ClassNode classNode, MethodNode methodNode, MixinContext context, List parameters, int offset) { - boolean isNonStatic = !MethodHelper.isStatic(methodNode); + boolean isNonStatic = !MethodAnalyzer.isStatic(methodNode); int index = this.index + offset; if (index >= parameters.size() + 1) { return PatchResult.PASS; diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java index b8a23cda..24f9ef10 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java @@ -39,7 +39,7 @@ public PatchResult apply(ClassNode classNode, MethodNode methodNode, MixinContex return PatchResult.PASS; } - LocalVariableLookup lvtLookup = context.methods().getLVT(methodNode); + LocalVariableLookup lvtLookup = LocalVariableLookup.of(methodNode); LocalVariableNode localVar = lvtLookup == null ? null : lvtLookup.getByParameterOrdinalOrNull(paramIndex); if (localVar == null) { LOGGER.info(MIXINPATCH, "Cannot replace parameter {}, missing local variable info in {}.{}", paramIndex, classNode.name, methodNode.name); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java b/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java index 9fe69dea..275b1c8c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java @@ -5,7 +5,7 @@ import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.ClassLookup; import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.model.MethodQualifier; @@ -67,7 +67,7 @@ public static MixinMethodHandle parseMixin(ClassTarget cls, MethodNode method, R String targetClass = Objects.requireNonNullElseGet(cls.getSingle(), () -> cls.getTypes().getFirst()).getInternalName(); properties.setProperty(ControlKeys.TARGET_CLASS, targetClass); properties.setProperty(ControlKeys.RETURN_TYPE, Type.getReturnType(method.desc)); - properties.setProperty(SpecialKeys.STATIC, MethodHelper.isStatic(method)); + properties.setProperty(SpecialKeys.STATIC, MethodAnalyzer.isStatic(method)); resolveTargetDescriptor(properties, environment); return new MixinMethodHandle(mixinType, method, handle, properties); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java index 61e111be..8fae02ad 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java @@ -55,11 +55,11 @@ private Recipe(Configuration clean, Configuration dirty, Resolvers resolvers, Pr } else { this.cleanTarget = Cached.of(() -> findTarget(context.cleanMethods(), clean.getTargetMethod())); this.newCleanTarget = Cached.of(() -> findTarget(context.dirtyMethods(), clean.getTargetMethod())); - this.cleanLocalsTable = Cached.of(() -> getLocalsTable(context, getCleanTarget())); + this.cleanLocalsTable = Cached.of(() -> getLocalsTable(getCleanTarget())); } this.dirtyTarget = Cached.keyed(dirty::getTargetMethod, target -> findTarget(context.dirtyMethods(), target)); - this.dirtyLocalsTable = Cached.keyed(this::getDirtyTarget, target -> getLocalsTable(context, target)); + this.dirtyLocalsTable = Cached.keyed(this::getDirtyTarget, target -> getLocalsTable(target)); } @Nullable @@ -68,8 +68,8 @@ private static TargetPair findTarget(MethodLocator locator, @Nullable MethodQual } @Nullable - private static LocalVariableLookup getLocalsTable(MixinContext context, @Nullable TargetPair target) { - return target != null ? context.methods().getLVT(target.methodNode()) : null; + private static LocalVariableLookup getLocalsTable(@Nullable TargetPair target) { + return target != null ? LocalVariableLookup.of(target.methodNode()) : null; } @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java index e82d39e1..a2206627 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java @@ -6,7 +6,8 @@ import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.injection.InjectionLocator; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodLocator; import org.sinytra.adapter.api.BytecodeFixerUpper; import org.sinytra.adapter.api.ClassLookup; @@ -46,10 +47,10 @@ public class MixinContext implements RefMapper, Auditor { private final List auditContext = new ArrayList<>(); private final String mixinId; - private final MethodHelper methodHelper; private final MethodLocator cleanMethods; private final MethodLocator dirtyMethods; private final InjectionLocator injections; + private final LocalsAnalyzer locals; private final Map targetFlows; private final Resolvers resolvers = new Resolvers(); @@ -65,8 +66,8 @@ public MixinContext(MixinType mixinType, PatchContext patchContext, ClassTarget this.injectionPointAnnotation = injectionPointAnnotation; this.flags = flags; - this.methodHelper = new MethodHelper(this); this.injections = new InjectionLocator(this); + this.locals = new LocalsAnalyzer(this); String defaultOwner = classTarget.getTypes().getFirst().getInternalName(); this.cleanMethods = new MethodLocator(patchContext.environment(), patchContext.environment().cleanClassLookup(), defaultOwner); this.dirtyMethods = new MethodLocator(patchContext.environment(), patchContext.environment().dirtyClassLookup(), defaultOwner); @@ -114,10 +115,6 @@ public MethodNode unmodifiedMethodNode() { return this.originalMethodNode; } - public MethodHelper methods() { - return this.methodHelper; - } - public MethodLocator cleanMethods() { return this.cleanMethods; } @@ -130,6 +127,10 @@ public InjectionLocator injections() { return this.injections; } + public LocalsAnalyzer locals() { + return this.locals; + } + public DataFlow dataFlow(TargetPair target) { return this.targetFlows.computeIfAbsent(target.methodNode(), DataFlow::of); } @@ -174,7 +175,7 @@ public PatchContext patchContext() { } public boolean isStatic() { - return MethodHelper.isStatic(this.methodNode); + return MethodAnalyzer.isStatic(this.methodNode); } public List targetTypes() { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java index db473616..3366bb2f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java @@ -63,7 +63,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo return TxResult.SUCCESS; } - List dirtyCaptured = context.methods().resolveCapturedMethodParams(recipe.clean(), recipe.dirty()); + List dirtyCaptured = context.locals().findDirtyCapturedParams(recipe.clean(), recipe.dirty()); Type modifyingType = Type.getReturnType(targetDesc.desc()); MethodParameters params = clean.getParameters().mutableCopy() diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java index 4fb32c0c..44a7d49d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java @@ -65,7 +65,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo return TxResult.FAIL; } - List dirtyCaptured = context.methods().resolveCapturedMethodParams(recipe.clean(), recipe.dirty()); + List dirtyCaptured = context.locals().findDirtyCapturedParams(recipe.clean(), recipe.dirty()); Type modifyingType = Type.getReturnType(target.desc()); MethodParameters params = clean.getParameters().mutableCopy() diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index 1efdc0b8..f43bcb8d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -101,7 +101,7 @@ public boolean canInject(MixinContext context, Configuration config) { MethodNode target = targetPair.methodNode(); Integer index = config.getProperty(MixinKeys.INDEX).orElseThrow(); - LocalVariableLookup lookup = context.methods().getLVT(target); + LocalVariableLookup lookup = LocalVariableLookup.of(target); if (lookup == null) return false; LocalVariableNode node = lookup.getByIndexOrNull(index); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index 8565f51f..40a8a5e6 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -1,7 +1,7 @@ package org.sinytra.adapter.pipeline.injector; import org.objectweb.asm.Type; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; @@ -89,8 +89,8 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo if (dirtyTarget == null) return TxResult.FAIL; - List dirtyCaptured = context.methods().resolveCapturedMethodParams(recipe.clean(), recipe.dirty()); - Type receiver = MethodHelper.isStatic(dirtyTarget.methodNode()) ? null : Type.getObjectType(dirtyTarget.classNode().name); + List dirtyCaptured = context.locals().findDirtyCapturedParams(recipe.clean(), recipe.dirty()); + Type receiver = MethodAnalyzer.isStatic(dirtyTarget.methodNode()) ? null : Type.getObjectType(dirtyTarget.classNode().name); List callTypes = Parameters.getCallOperandTypes(receiver, targetDesc.desc()); dirty.inheritProperyIfAbsent(ControlKeys.PARAMETERS); @@ -108,7 +108,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo private boolean isStaticRedirect(MixinContext context, MethodQualifier targetDesc, List methodParams, List callTypes) { TargetPair cleanTarget = context.cleanMethods().resolve(targetDesc); if (cleanTarget != null) { - return MethodHelper.isStatic(cleanTarget.methodNode()); + return MethodAnalyzer.isStatic(cleanTarget.methodNode()); } return methodParams.size() >= callTypes.size() && methodParams.subList(0, callTypes.size()).equals(callTypes); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index 762f5231..7a35cbf4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -1,7 +1,7 @@ package org.sinytra.adapter.pipeline.injector; import org.objectweb.asm.Type; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; @@ -59,9 +59,9 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo if (dirtyAtTarget == null || dirtyAtTarget.desc() == null) return TxResult.FAIL; - List dirtyCaptured = context.methods().resolveCapturedMethodParams(recipe.clean(), recipe.dirty()); + List dirtyCaptured = context.locals().findDirtyCapturedParams(recipe.clean(), recipe.dirty()); TargetPair targetMethodCall = dirtyAtTarget.isFull() ? context.dirtyMethods().resolveInherited(dirtyAtTarget) : null; - Type receiver = targetMethodCall != null && !MethodHelper.isStatic(targetMethodCall.methodNode()) ? Type.getObjectType(dirtyAtTarget.internalOwnerName()) : null; + Type receiver = targetMethodCall != null && !MethodAnalyzer.isStatic(targetMethodCall.methodNode()) ? Type.getObjectType(dirtyAtTarget.internalOwnerName()) : null; List callTypes = Parameters.getCallOperandTypes(receiver, dirtyAtTarget.desc()); MethodParameters params = clean.getParameters().mutableCopy() diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java index 2e0fccc9..0c69e5b4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java @@ -4,8 +4,8 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.api.AuditTrail; import org.sinytra.adapter.api.MethodTransformer; import org.sinytra.adapter.api.PatchResult; @@ -34,13 +34,13 @@ public PatchResult apply(MixinContext context, Configuration config) { // Already upgraded if (Parameters.parse(methodNode).stream().anyMatch(p -> p.hasAnnotation(MixinAnnotations.LOCAL))) return PatchResult.PASS; - LocalVarAnalyzer.CapturedLocalsInfo info = LocalVarAnalyzer.getCapturedLocals(context, targets.dirty()); + LocalsAnalyzer.CapturedLocalsInfo info = context.locals().resolveCapturedLocalsInfo(targets.dirty()); if (info == null || info.diff().isEmpty()) return PatchResult.PASS; - LocalVarAnalyzer.CapturedLocalsTransform transform = LocalVarAnalyzer.analyzeCapturedLocals(info.capturedLocals(), methodNode); + LocalsAnalyzer.CapturedLocalsTransform transform = LocalsAnalyzer.analyzeCapturedLocals(info.capturedLocals(), methodNode); - LocalVariableLookup cleanLookup = context.methods().getLVT(targets.clean().methodNode()); - LocalVariableLookup dirtyLookup = context.methods().getLVT(targets.dirty().methodNode()); + LocalVariableLookup cleanLookup = LocalVariableLookup.of(targets.clean().methodNode()); + LocalVariableLookup dirtyLookup = LocalVariableLookup.of(targets.dirty().methodNode()); LocalVariableLookup lookup = info.capturedLocals().lvt(); if (cleanLookup == null || dirtyLookup == null) return PatchResult.PASS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java index 5f755418..73deae9d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java @@ -52,7 +52,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe if (diff == null) return TxResult.PASS; MethodNode methodNode = context.methodNode(); - LocalVariableLookup lookup = context.methods().getLVT(context.methodNode()); + LocalVariableLookup lookup = LocalVariableLookup.of(context.methodNode()); if (lookup == null) return TxResult.PASS; boolean matched = false; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java index 1ba0fd2d..33b8226d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java @@ -5,7 +5,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.key.SpecialKeys; import org.sinytra.adapter.pipeline.Recipe; @@ -28,7 +28,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe if (cleanStatic == dirtyStatic) return TxResult.PASS; MethodNode method = context.methodNode(); - boolean actuallyStatic = MethodHelper.isStatic(context.methodNode()); + boolean actuallyStatic = MethodAnalyzer.isStatic(context.methodNode()); // Add static if (!actuallyStatic && !cleanStatic && dirtyStatic) { // Mixin types add the target instance as the first parameter, which replaces usages of "this" diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java index 5439cd5e..c2d182a2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java @@ -1,8 +1,8 @@ package org.sinytra.adapter.pipeline.processor; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.analysis.locals.LocalVariable; +import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; @@ -22,14 +22,14 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe // TODO Is there a better approach? private static void upgradeCapturedLocals(MethodNode methodNode, MixinContext context, Recipe recipe) { - LocalVarAnalyzer.CapturedLocals capturedLocals = LocalVarAnalyzer.findCapturedLocals(context, recipe); + LocalsAnalyzer.CapturedLocals capturedLocals = context.locals().resolveCapturedLocals(recipe.getDirtyTarget()); if (capturedLocals == null) return; - List availableLocals = context.methods().getTargetMethodLocals(capturedLocals.target()); + List availableLocals = context.locals().resolveAvailableLocals(capturedLocals.target()); // For now, only handle cases where all locals are part of the method's params, convenient when switching the target to a lambda if (availableLocals == null || !availableLocals.isEmpty()) return; - LocalVarAnalyzer.CapturedLocalsTransform transform = LocalVarAnalyzer.analyzeCapturedLocals(capturedLocals, methodNode); + LocalsAnalyzer.CapturedLocalsTransform transform = LocalsAnalyzer.analyzeCapturedLocals(capturedLocals, methodNode); transform.remover().apply(context); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java index 796f7007..31938a05 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java @@ -7,9 +7,9 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.analysis.locals.LocalVariable; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; import org.sinytra.adapter.api.PatchResult; @@ -242,15 +242,15 @@ private static int fixAccess(int access) { * } */ private static PatchResult recreateLocalVariables(MethodNode methodNode, MixinContext context, Recipe recipe, ClassNode extractClass) { - LocalVarAnalyzer.CapturedLocals capturedLocals = LocalVarAnalyzer.findCapturedLocals(context, recipe); + LocalsAnalyzer.CapturedLocals capturedLocals = context.locals().resolveCapturedLocals(recipe.getDirtyTarget()); if (capturedLocals == null) { return PatchResult.PASS; } int paramLocalStart = capturedLocals.paramLocalStart(); // Mixin requires capturing locals in their original order, so we must filter out unused ones - LocalVarAnalyzer.CapturedLocalsTransform transform = LocalVarAnalyzer.analyzeCapturedLocals(capturedLocals, methodNode); - LocalVarAnalyzer.CapturedLocalsUsage usage = transform.getUsage(capturedLocals, context); + LocalsAnalyzer.CapturedLocalsTransform transform = LocalsAnalyzer.analyzeCapturedLocals(capturedLocals, methodNode); + LocalsAnalyzer.CapturedLocalsUsage usage = context.locals().resolveUsage(capturedLocals, transform); // Used captured locals in parameter order List used = transform.used().stream().sorted().toList(); LocalVariableLookup targetTable = usage.targetTable(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java index 58de0b11..1961ca31 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java @@ -5,7 +5,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.model.MethodQualifier; @@ -117,7 +117,7 @@ public static MethodNode generateMirror(MixinContext context, String destination // Now call the original mixin GeneratorAdapter gen = new GeneratorAdapter(mirror, mirror.access, mirror.name, mirror.desc); gen.newLabel(); - if (!MethodHelper.isStatic(methodNode)) { + if (!MethodAnalyzer.isStatic(methodNode)) { loadInstance.accept(gen); } for (int argIndex : argIndices) { @@ -133,7 +133,7 @@ public static MethodNode generateMirror(MixinContext context, String destination static void invokeHandler(GeneratorAdapter gen, MethodNode handler, ClassNode handlerOwner) { boolean isInterface = (handlerOwner.access & Opcodes.ACC_INTERFACE) != 0; - int opcode = MethodHelper.isStatic(handler) ? Opcodes.INVOKESTATIC : isInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL; + int opcode = MethodAnalyzer.isStatic(handler) ? Opcodes.INVOKESTATIC : isInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL; gen.visitMethodInsn(opcode, handlerOwner.name, handler.name, handler.desc, isInterface); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java index 0085f786..43d431fc 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java @@ -3,7 +3,6 @@ import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.key.SpecialKeys; @@ -30,7 +29,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe if (recipe.clean().getTargetClass().equals(dirty.getTargetClass())) return TxResult.PASS; - if (!dirty.hasProperty(SpecialKeys.EXTRACT_ORIGIN_PARAM) || LocalVarAnalyzer.capturesLocals(context)) + if (!dirty.hasProperty(SpecialKeys.EXTRACT_ORIGIN_PARAM) || context.locals().capturesLocals()) return TxResult.FAIL; TargetPair dirtyTarget = recipe.getNewCleanTarget(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java index fcb17cd9..6b56c9cf 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java @@ -46,7 +46,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe return TxResult.SUCCESS; } - LocalVariableLookup lookup = context.methods().getLVT(context.methodNode()); + LocalVariableLookup lookup = LocalVariableLookup.of(context.methodNode()); if (lookup == null || diff.insertions().stream().anyMatch(i -> lookup.getByParameterOrdinalOrNull(i.index()) == null)) { return TxResult.PASS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java index 0db3a8b4..83e1296f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java @@ -69,7 +69,7 @@ private static boolean upgradeOriginalCall(MixinContext context, Configuration d AbstractInsnNode arrayInit = dirtyCallInsns.get(1); List params = dirty.getParameters().get(MethodParameters.ParamGroup.METHOD_PARAMS); - LocalVariableLookup lookup = context.methods().getLVT(context.methodNode()); + LocalVariableLookup lookup = LocalVariableLookup.of(context.methodNode()); if (lookup == null || IntStream.range(0, params.size()).anyMatch(i -> lookup.getByParameterOrdinalOrNull(i) == null)) { return false; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java index 2597e653..207a564a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java @@ -29,7 +29,7 @@ public class WrapOpSurgeon { public static boolean tryUpgrade(MixinContext context, Recipe recipe, List methodParams, MethodInsnNode cleanInsn, MethodInsnNode dirtyInsn) { MethodNode methodNode = context.methodNode(); - LocalVariableLookup mixinLocals = context.methods().getLVT(methodNode); + LocalVariableLookup mixinLocals = LocalVariableLookup.of(methodNode); if (mixinLocals == null) return false; LocalVariableNode instanceLocal = mixinLocals.getByParameterOrdinalOrNull(0); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java index 97006af1..0faed871 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java @@ -3,8 +3,8 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; @@ -54,12 +54,12 @@ public Configuration resolve(MixinContext context, Recipe recipe) { } LocalVariableNode dirtyLocal = dirtyLocals.get(cleanLocals.indexOf(cleanLocal)); // Find store insns - List cleanStoreInsns = LocalVarAnalyzer.findStoreInsns(targets.clean().methodNode(), cleanLocal.index); + List cleanStoreInsns = LocalsAnalyzer.findStoreInsns(targets.clean().methodNode(), cleanLocal.index); int cleanStoreInsnIndex = cleanStoreInsns.indexOf(varInsn); if (cleanStoreInsnIndex == -1) { return null; } - List dirtyStoreInsns = LocalVarAnalyzer.findStoreInsns(targets.dirty().methodNode(), dirtyLocal.index); + List dirtyStoreInsns = LocalsAnalyzer.findStoreInsns(targets.dirty().methodNode(), dirtyLocal.index); if (cleanStoreInsns.size() != dirtyStoreInsns.size()) { return null; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java index c7173ffa..7366bb8b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java @@ -128,7 +128,7 @@ private static Optional handleWrapOperationToInstanceOf(Recipe re TypeInsnNode instanceOfCall = instanceOfCalls.getFirst(); MethodNode methodNode = context.methodNode(); - LocalVariableLookup lvt = context.methods().getLVT(methodNode); + LocalVariableLookup lvt = LocalVariableLookup.of(methodNode); if (lvt == null) return Optional.empty(); LocalVariableNode instanceLocal = lvt.getByParameterOrdinalOrNull(0); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java index 739dc857..bade2e39 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java @@ -5,8 +5,8 @@ import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; @@ -38,14 +38,14 @@ public Configuration resolve(MixinContext context, Recipe recipe) { if (desired == null) return null; // Find variable initializer insns - InsnList desiredInitializerInsns = LocalVarAnalyzer.findInitializerInsns(targets.clean().methodNode(), desired.index); + InsnList desiredInitializerInsns = LocalsAnalyzer.findInitializerInsns(targets.clean().methodNode(), desired.index); // Get all matching variables for (MethodNode method : targets.dirty().classNode().methods) { - LocalVariableLookup dirtyLookup = context.methods().getLVT(method); + LocalVariableLookup dirtyLookup = LocalVariableLookup.of(method); if (dirtyLookup == null) continue; - LocalVariableNode match = LocalVarAnalyzer.findLocalWithInitializer(method, desired.desc, desiredInitializerInsns).orElse(null); + LocalVariableNode match = LocalsAnalyzer.findLocalWithInitializer(method, desired.desc, desiredInitializerInsns).orElse(null); if (match != null) { Integer dirtyOrdinal = dirtyLookup.getTypedOrdinal(match).orElse(null); if (dirtyOrdinal == null) continue; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java index 037ca02c..b2882f33 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java @@ -8,8 +8,8 @@ import org.sinytra.adapter.analysis.InsnTraversal; import org.sinytra.adapter.analysis.compare.InsnComparator; import org.sinytra.adapter.analysis.compare.InstructionMatcher; -import org.sinytra.adapter.analysis.locals.LocalVarAnalyzer; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodInsnMatcher; import org.sinytra.adapter.model.MethodQualifier; @@ -422,8 +422,8 @@ private static Optional tryFindUpdatedIndex(Type targetType, Recipe re private static Optional findReplacementLocal(MethodNode cleanMethod, MethodNode dirtyMethod, LocalVariableNode desired) { // Find variable initializer insns - InsnList desiredInitializerInsns = LocalVarAnalyzer.findInitializerInsns(cleanMethod, desired.index); - return LocalVarAnalyzer.findLocalWithInitializer(dirtyMethod, desired.desc, desiredInitializerInsns); + InsnList desiredInitializerInsns = LocalsAnalyzer.findInitializerInsns(cleanMethod, desired.index); + return LocalsAnalyzer.findLocalWithInitializer(dirtyMethod, desired.desc, desiredInitializerInsns); } } } diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java index bcad2d96..65729109 100644 --- a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java @@ -7,7 +7,7 @@ import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.method.MethodHelper; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.BytecodeFixerUpper; import org.sinytra.adapter.api.ClassTransformer; import org.sinytra.adapter.api.PatchEnvironment; @@ -86,7 +86,7 @@ private String addRedirectAcceptorField(Type owner, MethodNode methodNode, Strin private static void addRedirectAcceptorMethod(ClassNode node, Type owner, MethodNode methodNode, String field, TypeAdapter adapter, String methodName, Type to, String methodDesc) { if (node.methods.stream().noneMatch(m -> m.name.equals(methodName))) { - boolean isStatic = MethodHelper.isStatic(methodNode); + boolean isStatic = MethodAnalyzer.isStatic(methodNode); MethodNode method = (MethodNode) node.visitMethod(Opcodes.ACC_PUBLIC | (isStatic ? Opcodes.ACC_STATIC : 0), methodName, methodDesc, null, null); { AnnotationVisitor annotationVisitor = method.visitAnnotation(MixinAnnotations.UNIQUE, true); From 3f66323a8ed4369b478719176354a54dccada5fa Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 19:28:45 +0200 Subject: [PATCH 06/44] Add default owner to DataFlow --- .../adapter/analysis/flow/DataFlow.java | 23 +++++++++++++++---- .../adapter/pipeline/ctx/MixinContext.java | 2 +- .../target/SplitMethodCancellationHelper.java | 2 +- .../target/SplitTargetMethodSubResolver.java | 2 +- .../target/TargetMethodSubResolvers.java | 2 +- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java b/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java index 855802db..25d42010 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java @@ -7,6 +7,7 @@ import org.objectweb.asm.tree.analysis.*; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.util.AdapterUtil; import java.util.*; @@ -36,12 +37,15 @@ public enum ImplicitCalls { ); private final MethodNode method; + @Nullable + private final ClassNode owner; private final Producers producers; @Nullable private Frame[] frames; - private DataFlow(MethodNode method, Producers producers) { + private DataFlow(MethodNode method, @Nullable ClassNode owner, Producers producers) { this.method = method; + this.owner = owner; this.producers = producers; } @@ -50,7 +54,15 @@ public static DataFlow of(MethodNode method) { } public static DataFlow of(MethodNode method, Producers producers) { - return new DataFlow(method, producers); + return new DataFlow(method, null, producers); + } + + public static DataFlow of(TargetPair target) { + return of(target, Producers.LOADER); + } + + public static DataFlow of(TargetPair target, Producers producers) { + return new DataFlow(target.methodNode(), target.classNode(), producers); } public MethodNode method() { @@ -191,10 +203,13 @@ public List findTopLevelCalls(ImplicitCalls implicitCalls) { } /// {@return methods of the owner class that are called at the top level} - public List findTopLevelOwnCalls(ClassNode owner) { + public List findTopLevelOwnCalls() { + if (this.owner == null) { + throw new IllegalStateException("Missing owner for data flow of " + this.method.name + this.method.desc); + } List ownCalls = new ArrayList<>(); for (MethodInsnNode insn : findTopLevelCalls(ImplicitCalls.KEEP)) { - MethodNode method = AdapterUtil.findMethod(owner, insn.name, insn.desc); + MethodNode method = AdapterUtil.findMethod(this.owner, insn.name, insn.desc); if (method != null) { ownCalls.add(method); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java index a2206627..ff07270d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java @@ -132,7 +132,7 @@ public LocalsAnalyzer locals() { } public DataFlow dataFlow(TargetPair target) { - return this.targetFlows.computeIfAbsent(target.methodNode(), DataFlow::of); + return this.targetFlows.computeIfAbsent(target.methodNode(), m -> DataFlow.of(target)); } @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java index 4386c780..319e63ea 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java @@ -35,7 +35,7 @@ public static void handle(MixinContext context, Recipe recipe, MethodNode newTar return; } - List invocations = context.dataFlow(newCleanTarget).findTopLevelOwnCalls(newCleanTarget.classNode()); + List invocations = context.dataFlow(newCleanTarget).findTopLevelOwnCalls(); if (invocations == null) { return; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java index 528694e2..3ba3f751 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java @@ -58,7 +58,7 @@ private static List locateCandidates(MixinContext context, Targ return tryFindPartialCandidates(cleanTargetMethod, dirtyTargetClass, dirtyTargetMethod, context); } - List invocations = context.dataFlow(dirtyTarget).findTopLevelOwnCalls(dirtyTarget.classNode()); + List invocations = context.dataFlow(dirtyTarget).findTopLevelOwnCalls(); if (invocations == null) { return List.of(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java index 1efe5b88..6e5d4233 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java @@ -98,7 +98,7 @@ private static Configuration resolveOverloadedReplacement(MixinContext context, TargetPair dirtyTarget = recipe.getNewCleanTarget(); if (dirtyTarget == null || !MethodAnalyzer.isDeprecated(dirtyTarget.methodNode())) return null; - List invocations = context.dataFlow(dirtyTarget).findTopLevelOwnCalls(dirtyTarget.classNode()); + List invocations = context.dataFlow(dirtyTarget).findTopLevelOwnCalls(); for (MethodNode invocation : invocations) { if (context.injections().matches(new TargetPair(dirtyTarget.classNode(), invocation))) { return MutableConfiguration.create() From 156cb0a59f6f3a3f8cac914fd3d3e3e04ce88505 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 19:40:31 +0200 Subject: [PATCH 07/44] Abstract mixin internals behind MixinBridge --- .../analysis/injection/InjectionLocator.java | 57 +---- .../analysis/locals/LocalsAnalyzer.java | 4 +- ...MockMixinRuntime.java => MixinBridge.java} | 230 ++++++++++-------- .../org/sinytra/adapter/mixin/MixinUtil.java | 28 --- .../org/sinytra/adapter/model/ann/AtData.java | 4 +- .../adapter/pipeline/audit/MixinGroups.java | 41 +--- .../special/ModifyVarAtReturnResolver.java | 20 +- .../types/FieldAccessorTypeTransformer.java | 6 +- 8 files changed, 167 insertions(+), 223 deletions(-) rename core/src/main/java/org/sinytra/adapter/mixin/{MockMixinRuntime.java => MixinBridge.java} (53%) delete mode 100644 core/src/main/java/org/sinytra/adapter/mixin/MixinUtil.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java index 2451ae2e..7c54ddd8 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java @@ -1,31 +1,21 @@ package org.sinytra.adapter.analysis.injection; -import com.mojang.logging.LogUtils; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.InsnList; -import org.sinytra.adapter.mixin.MockMixinRuntime; +import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.SliceData; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.slf4j.Logger; -import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.code.ISliceContext; -import org.spongepowered.asm.mixin.injection.code.MethodSlice; -import org.spongepowered.asm.mixin.injection.points.BeforeConstant; -import org.spongepowered.asm.mixin.injection.struct.Target; -import org.spongepowered.asm.mixin.refmap.IMixinContext; import java.util.*; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.*; public final class InjectionLocator { - private static final Logger LOGGER = LogUtils.getLogger(); - private final MixinContext context; private final Map> cache; @@ -89,7 +79,7 @@ public boolean matches(@Nullable TargetPair target, InjectionQuery query) { /// {@return the instructions of the target method within the slice} public InsnList slice(TargetPair target, SliceData slice) { - return computeSlice(target, slice.toAnnotationNode(), createMixinTarget(target)); + return session(target).slice(slice.toAnnotationNode()); } private List compute(TargetPair target, InjectionQuery query) { @@ -110,52 +100,29 @@ private List compute(TargetPair target, InjectionQuery query) node.removeValues(PROPERTY_ORDINAL); } - IMixinContext mixinContext = MockMixinRuntime.forClass(this.context.classNode().name, target.classNode().name, this.context.environment()); - List targetInsns = new ArrayList<>(); - try { - InjectionPoint point = parse(mixinContext, query, node); - Target mixinTarget = createMixinTarget(target); - InsnList instructions = query.ignoreSlice() ? target.methodNode().instructions : getSlicedInsns(target, mixinTarget); - if (MockMixinRuntime.injectionPointNeedsSpecialCare(point)) { - MockMixinRuntime.findModifyVariableInjectionInsns(point, mixinContext, target.methodNode().instructions, targetInsns, mixinTarget); - } else { - point.find(target.methodNode().desc, instructions, targetInsns); - } - } catch (Throwable e) { - LOGGER.error("Error finding injection insns", e); - return List.of(); - } - return targetInsns; - } - - private InjectionPoint parse(IMixinContext mixinContext, InjectionQuery query, AnnotationHandle node) { + MixinBridge.Session session = session(target); + AnnotationNode slice = query.ignoreSlice() ? null : getSlice(); AnnotationHandle methodAnnotation = this.context.methodAnnotation(); return switch (query.source()) { - case CURRENT, SLICE_BOUNDARY -> InjectionPoint.parse(mixinContext, this.context.methodNode(), methodAnnotation.unwrap(), node.unwrap()); + case CURRENT, SLICE_BOUNDARY -> session.findInjectionPoint(methodAnnotation.unwrap(), node.unwrap(), slice); case AT -> { AnnotationHandle parent = methodAnnotation.copy(); parent.setOrAppendNonNull(PROPERTY_AT, node.unwrap()); - yield InjectionPoint.parse(mixinContext, this.context.methodNode(), parent.unwrap(), node.unwrap()); + yield session.findInjectionPoint(parent.unwrap(), node.unwrap(), slice); } - case CONSTANT -> new BeforeConstant(mixinContext, node.unwrap(), Type.getReturnType(this.context.methodNode().desc).getDescriptor()); + case CONSTANT -> session.findConstant(node.unwrap(), Type.getReturnType(this.context.methodNode().desc).getDescriptor(), slice); }; } - private static Target createMixinTarget(TargetPair target) { - return MockMixinRuntime.createMixinTarget(target); + private MixinBridge.Session session(TargetPair target) { + return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment().refmapHolder()); } - private InsnList getSlicedInsns(TargetPair target, Target mixinTarget) { + @Nullable + private AnnotationNode getSlice() { return this.context.methodAnnotation().getValue(PROPERTY_SLICE) .map(handle -> handle.get() instanceof List list ? (AnnotationNode) list.getFirst() : (AnnotationNode) handle.get()) - .map(slice -> computeSlice(target, slice, mixinTarget)) - .orElse(target.methodNode().instructions); - } - - private InsnList computeSlice(TargetPair target, AnnotationNode slice, Target mixinTarget) { - IMixinContext mixinContext = MockMixinRuntime.forClass(this.context.classNode().name, target.classNode().name, this.context.environment()); - ISliceContext sliceContext = MockMixinRuntime.forSlice(mixinContext, this.context.methodNode()); - return MethodSlice.parse(sliceContext, slice).getSlice(mixinTarget); + .orElse(null); } @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index 5544ddde..0a547dae 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -15,7 +15,7 @@ import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.analysis.params.ParamDiffResolver; import org.sinytra.adapter.edit.param.TransformParameters; -import org.sinytra.adapter.mixin.MockMixinRuntime; +import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.param.MethodParameters; @@ -198,7 +198,7 @@ public List resolveAvailableLocals(TargetPair target) { return null; } - LocalVariableNode[] localVariables = MockMixinRuntime.getLocalsAt(target, targetInsns.getFirst(), this.context.environment().fabricLVTCompatibility()); + LocalVariableNode[] localVariables = MixinBridge.getLocalsAt(target.classNode(), target.methodNode(), targetInsns.getFirst(), this.context.environment().fabricLVTCompatibility()); LocalVariable[] locals = Stream.of(localVariables) .filter(Objects::nonNull) .map(lv -> new LocalVariable(lv.index, Type.getType(lv.desc))) diff --git a/core/src/main/java/org/sinytra/adapter/mixin/MockMixinRuntime.java b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java similarity index 53% rename from core/src/main/java/org/sinytra/adapter/mixin/MockMixinRuntime.java rename to core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java index 42f0a80d..fe6b346b 100644 --- a/core/src/main/java/org/sinytra/adapter/mixin/MockMixinRuntime.java +++ b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java @@ -1,31 +1,34 @@ -package org.sinytra.adapter.env; +package org.sinytra.adapter.mixin; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import com.mojang.logging.LogUtils; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; -import org.sinytra.adapter.env.ctx.PatchEnvironment; -import org.sinytra.adapter.env.ctx.TargetPair; +import org.sinytra.adapter.api.RefmapHolder; import org.slf4j.Logger; -import org.spongepowered.asm.mixin.FabricUtil; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.extensibility.IMixinConfig; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinConfigSource; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.mixin.gen.AccessorInfo; +import org.spongepowered.asm.mixin.injection.Group; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.code.ISliceContext; import org.spongepowered.asm.mixin.injection.code.MethodSlice; +import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; +import org.spongepowered.asm.mixin.injection.points.BeforeConstant; import org.spongepowered.asm.mixin.injection.selectors.ISelectorContext; -import org.spongepowered.asm.mixin.injection.struct.CallbackInjectionInfo; -import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; -import org.spongepowered.asm.mixin.injection.struct.ModifyVariableInjectionInfo; -import org.spongepowered.asm.mixin.injection.struct.Target; +import org.spongepowered.asm.mixin.injection.struct.*; import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.mixin.refmap.IReferenceMapper; import org.spongepowered.asm.mixin.struct.AnnotatedMethodInfo; -import org.spongepowered.asm.mixin.struct.SpecialMethodInfo; import org.spongepowered.asm.mixin.transformer.ClassInfo; import org.spongepowered.asm.mixin.transformer.ext.Extensions; +import org.spongepowered.asm.util.Annotations; +import org.spongepowered.asm.util.Locals; import org.spongepowered.asm.util.asm.IAnnotationHandle; import sun.misc.Unsafe; @@ -35,9 +38,8 @@ import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -public class MockMixinRuntime { +public final class MixinBridge { private static final Logger LOGGER = LogUtils.getLogger(); private static final MethodHandles.Lookup TRUSTED_LOOKUP; private static final Unsafe UNSAFE; @@ -45,6 +47,9 @@ public class MockMixinRuntime { private static final Class LOCAL_VARIABLE_INJECTION_POINT_CLASS; private static final VarHandle ANNOTATED_METHOD_INFO_CONTEXT; private static final MethodHandle LOCAL_VARIABLE_INJECTION_POINT_FIND; + private static final VarHandle INJECTION_POINT_TYPES; + private static final String CLS_REGEX = "^([A-Za-z_][A-Za-z0-9_]*[\\.\\$])+[A-Za-z_][A-Za-z0-9_]*$"; + private static final Object LOCALS_LOCK = new Object(); static { try { @@ -58,108 +63,144 @@ public class MockMixinRuntime { LOCAL_VARIABLE_INJECTION_POINT_CLASS = Class.forName("org.spongepowered.asm.mixin.injection.modify.ModifyVariableInjector$LocalVariableInjectionPoint"); ANNOTATED_METHOD_INFO_CONTEXT = TRUSTED_LOOKUP.findVarHandle(AnnotatedMethodInfo.class, "context", IMixinContext.class); + INJECTION_POINT_TYPES = MethodHandles.privateLookupIn(InjectionPoint.class, MethodHandles.lookup()).findStaticVarHandle(InjectionPoint.class, "types", Map.class); LOCAL_VARIABLE_INJECTION_POINT_FIND = MethodHandles.privateLookupIn(LOCAL_VARIABLE_INJECTION_POINT_CLASS, MethodHandles.lookup()).findVirtual(LOCAL_VARIABLE_INJECTION_POINT_CLASS, "find", MethodType.methodType(boolean.class, InjectionInfo.class, InsnList.class, Collection.class, Target.class)); } catch (Throwable t) { throw new RuntimeException(t); } } - public static Target createMixinTarget(TargetPair pair) { - ClassInfo info = ClassInfo.forName(pair.classNode().name); - try { - return (Target) TARGET_CTR.invoke(info, pair.classNode(), pair.methodNode()); - } catch (Throwable t) { - throw new RuntimeException(t); - } + public static Session session(String mixinClass, MethodNode mixinMethod, ClassNode targetClass, MethodNode targetMethod, RefmapHolder refmapHolder) { + return new Session(new ClassMixinContext(mixinClass, targetClass.name, refmapHolder), mixinMethod, targetClass, targetMethod); } - public static IMixinContext forClass(String className, String targetClass, PatchEnvironment environment) { - return new ClassMixinContext(className, targetClass, environment); + public static LocalVariableNode[] getLocalsAt(ClassNode targetClass, MethodNode targetMethod, AbstractInsnNode insn, int lvtCompatLevel) { + // Synchronize to avoid issues in mixin. This is necessary. + synchronized (LOCALS_LOCK) { + return Locals.getLocalsAt(targetClass, targetMethod, insn, lvtCompatLevel); + } } - public static ISliceContext forSlice(IMixinContext context, MethodNode methodNode) { - return new MethodSliceContext(context, methodNode); + @SuppressWarnings("unchecked") + public static boolean isKnownInjectionPointType(String value) { + Map> types = (Map>) INJECTION_POINT_TYPES.get(); + return types.containsKey(value.toUpperCase(Locale.ROOT)) || value.matches(CLS_REGEX); } - public static InjectionInfo forInjectionInfo(String className, String targetClass, PatchEnvironment environment) { - try { - InjectionInfo injectionInfo = (InjectionInfo) UNSAFE.allocateInstance(CallbackInjectionInfo.class); - IMixinContext context = forClass(className, targetClass, environment); - ANNOTATED_METHOD_INFO_CONTEXT.set(injectionInfo, context); - CompatibilityContexts.attach(injectionInfo, environment.fabricLVTCompatibility()); - return injectionInfo; - } catch (Throwable t) { - throw new RuntimeException(t); - } + @Nullable + public static String getAccessorFieldName(String methodName) { + AccessorInfo.AccessorName name = AccessorInfo.AccessorName.of(methodName); + return name != null ? name.name : null; } - public static boolean findModifyVariableInjectionInsns(InjectionPoint injectionPoint, IMixinContext mixinContext, InsnList insns, List list, Target target) throws Throwable { - InjectionInfo info = MockMixinRuntime.createModifyVariableInjectionInfo(mixinContext); - return (boolean) LOCAL_VARIABLE_INJECTION_POINT_FIND.invoke(injectionPoint, info, insns, list, target); - } + @Nullable + public static String getGroupName(MethodNode method) { + AnnotationNode node = Annotations.getInvisible(method, Group.class); + if (node == null) return null; - public static boolean injectionPointNeedsSpecialCare(InjectionPoint injectionPoint) { - return LOCAL_VARIABLE_INJECTION_POINT_CLASS.isInstance(injectionPoint); + String name = Annotations.getValue(node, "name"); + return name == null || name.isEmpty() ? null : name; } - public static InjectionInfo createModifyVariableInjectionInfo(IMixinContext mixinContext) throws Exception { - ModifyVariableInjectionInfo info = (ModifyVariableInjectionInfo) UNSAFE.allocateInstance(ModifyVariableInjectionInfo.class); - ANNOTATED_METHOD_INFO_CONTEXT.set(info, mixinContext); - CompatibilityContexts.attach(info, lvtCompatibility(mixinContext)); - return info; + public static List findInjectorGroups(List methods) { + InjectorGroupInfo.Map infos = new InjectorGroupInfo.Map(); + Multimap members = HashMultimap.create(); + + for (MethodNode method : methods) { + InjectorGroupInfo info = infos.parseGroup(method, "default"); + if (info.isDefault() || info.getName().equals("default")) continue; + + members.put(info.getName(), method); + } + + List groups = new ArrayList<>(); + members.asMap().forEach((name, mems) -> + groups.add(new InjectorGroup(name, List.copyOf(mems), infos.forName(name).getMinRequired()))); + return groups; } - private static int lvtCompatibility(IMixinContext mixinContext) { - IMixinConfig config = mixinContext.getMixin().getConfig(); - return config.hasDecoration(FabricUtil.KEY_COMPATIBILITY) ? config.getDecoration(FabricUtil.KEY_COMPATIBILITY) : FabricUtil.COMPATIBILITY_LATEST; + public record InjectorGroup(String name, List members, int minRequired) { } - private static final class CompatibilityContexts { - @Nullable - private static final VarHandle[] HANDLES; + public static final class Session { + private final IMixinContext mixinContext; + private final MethodNode mixinMethod; + private final ClassNode targetClass; + private final MethodNode targetMethod; @Nullable - private static final Class[] CLASSES; - private static final Map CONTEXTS = new ConcurrentHashMap<>(); + private Target target; - static { - VarHandle[] handles = null; - Class[] classes = null; + private Session(IMixinContext mixinContext, MethodNode mixinMethod, ClassNode targetClass, MethodNode targetMethod) { + this.mixinContext = mixinContext; + this.mixinMethod = mixinMethod; + this.targetClass = targetClass; + this.targetMethod = targetMethod; + } + + public List findInjectionPoint(AnnotationNode parent, AnnotationNode at, @Nullable AnnotationNode slice) { try { - Class targetContext = Class.forName("org.spongepowered.asm.mixin.transformer.MixinTargetContext"); - Class mixinInfo = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo"); - Class mixinConfig = Class.forName("org.spongepowered.asm.mixin.transformer.MixinConfig"); - handles = new VarHandle[]{ - TRUSTED_LOOKUP.findVarHandle(SpecialMethodInfo.class, "mixin", targetContext), - TRUSTED_LOOKUP.findVarHandle(targetContext, "mixin", mixinInfo), - TRUSTED_LOOKUP.findVarHandle(mixinInfo, "parent", mixinConfig), - TRUSTED_LOOKUP.findVarHandle(mixinConfig, "decorations", Map.class) - }; - classes = new Class[]{targetContext, mixinInfo, mixinConfig}; - } catch (Throwable t) { - LOGGER.debug("Mixin target contexts are unavailable, local variable compatibility uses the selector context", t); + InjectionPoint point = InjectionPoint.parse(this.mixinContext, this.mixinMethod, parent, at); + return find(point, slice); + } catch (Throwable e) { + LOGGER.error("Error finding injection insns", e); + return List.of(); } - HANDLES = handles; - CLASSES = classes; } - public static void attach(InjectionInfo info, int lvtCompatibility) { - if (HANDLES != null) { - HANDLES[0].set(info, CONTEXTS.computeIfAbsent(lvtCompatibility, CompatibilityContexts::create)); + public List findConstant(AnnotationNode constant, String returnDesc, @Nullable AnnotationNode slice) { + try { + return find(new BeforeConstant(this.mixinContext, constant, returnDesc), slice); + } catch (Throwable e) { + LOGGER.error("Error finding injection insns", e); + return List.of(); } } - private static Object create(int lvtCompatibility) { - try { - Object config = UNSAFE.allocateInstance(CLASSES[2]); - HANDLES[3].set(config, new HashMap<>(Map.of(FabricUtil.KEY_COMPATIBILITY, lvtCompatibility))); - Object mixinInfo = UNSAFE.allocateInstance(CLASSES[1]); - HANDLES[2].set(mixinInfo, config); - Object context = UNSAFE.allocateInstance(CLASSES[0]); - HANDLES[1].set(context, mixinInfo); - return context; - } catch (InstantiationException e) { - throw new RuntimeException(e); + public InsnList slice(AnnotationNode slice) { + ISliceContext sliceContext = new MethodSliceContext(this.mixinContext, this.mixinMethod); + return MethodSlice.parse(sliceContext, slice).getSlice(target()); + } + + public int findModifiedLocal(AnnotationNode modifyVariable, Type returnType, AbstractInsnNode insn) { + LocalVariableDiscriminator discriminator = LocalVariableDiscriminator.parse(modifyVariable); + InjectionInfo injectionInfo = createInjectionInfo(CallbackInjectionInfo.class, this.mixinContext); + LocalVariableDiscriminator.Context ctx = new LocalVariableDiscriminator.Context(injectionInfo, returnType, discriminator.isArgsOnly(), target(), insn); + return discriminator.findLocal(ctx); + } + + private List find(InjectionPoint point, @Nullable AnnotationNode slice) throws Throwable { + List targetInsns = new ArrayList<>(); + if (LOCAL_VARIABLE_INJECTION_POINT_CLASS.isInstance(point)) { + // Local variable injection points need a ModifyVariable injector, and always search the whole method + InjectionInfo info = createInjectionInfo(ModifyVariableInjectionInfo.class, this.mixinContext); + LOCAL_VARIABLE_INJECTION_POINT_FIND.invoke(point, info, this.targetMethod.instructions, targetInsns, target()); + } else { + InsnList instructions = slice != null ? slice(slice) : this.targetMethod.instructions; + point.find(this.targetMethod.desc, instructions, targetInsns); } + return targetInsns; + } + + private Target target() { + if (this.target == null) { + ClassInfo info = ClassInfo.forName(this.targetClass.name); + try { + this.target = (Target) TARGET_CTR.invoke(info, this.targetClass, this.targetMethod); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + return this.target; + } + } + + private static InjectionInfo createInjectionInfo(Class type, IMixinContext mixinContext) { + try { + InjectionInfo info = (InjectionInfo) UNSAFE.allocateInstance(type); + ANNOTATED_METHOD_INFO_CONTEXT.set(info, mixinContext); + return info; + } catch (InstantiationException e) { + throw new RuntimeException(e); } } @@ -187,10 +228,10 @@ public String remap(String reference) { //@formatter:on } - private record ReferenceRemapper(PatchEnvironment env) implements IReferenceMapper { + private record ReferenceRemapper(RefmapHolder refmapHolder) implements IReferenceMapper { @Override public String remapWithContext(String context, String className, String reference) { - return this.env.refmapHolder().remap(className, reference); + return this.refmapHolder.remap(className, reference); } //@formatter:off @@ -203,23 +244,12 @@ public String remapWithContext(String context, String className, String referenc //@formatter:on } - private record DummyMixinConfig(int lvtCompatibility) implements IMixinConfig { + private static class DummyMixinConfig implements IMixinConfig { @Override public MixinEnvironment getEnvironment() { return MixinEnvironment.getCurrentEnvironment(); } - @Override - public boolean hasDecoration(String key) { - return FabricUtil.KEY_COMPATIBILITY.equals(key); - } - - @Override - @SuppressWarnings("unchecked") - public V getDecoration(String key) { - return FabricUtil.KEY_COMPATIBILITY.equals(key) ? (V) (Integer) this.lvtCompatibility : null; - } - //@formatter:off @Override public IMixinConfigSource getSource() {throw new UnsupportedOperationException();} @Override public String getCleanSourceId() {return "dummy";} @@ -230,6 +260,8 @@ public V getDecoration(String key) { @Override public boolean isRequired() {throw new UnsupportedOperationException();} @Override public Set getTargets() {throw new UnsupportedOperationException();} @Override public void decorate(String key, V value) {} + @Override public boolean hasDecoration(String key) {return false;} + @Override public V getDecoration(String key) {return null;} //@formatter:on } @@ -258,11 +290,11 @@ private static final class ClassMixinContext implements IMixinContext { private final ReferenceRemapper referenceRemapper; private final IMixinInfo mixinInfo; - public ClassMixinContext(String className, String targetClass, PatchEnvironment env) { + public ClassMixinContext(String className, String targetClass, RefmapHolder refmapHolder) { this.className = className; this.targetClass = targetClass; - this.referenceRemapper = new ReferenceRemapper(env); - this.mixinInfo = new DummyMixinInfo(new DummyMixinConfig(env.fabricLVTCompatibility())); + this.referenceRemapper = new ReferenceRemapper(refmapHolder); + this.mixinInfo = new DummyMixinInfo(new DummyMixinConfig()); } @Override diff --git a/core/src/main/java/org/sinytra/adapter/mixin/MixinUtil.java b/core/src/main/java/org/sinytra/adapter/mixin/MixinUtil.java deleted file mode 100644 index b8f89fc4..00000000 --- a/core/src/main/java/org/sinytra/adapter/mixin/MixinUtil.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.sinytra.adapter.mixin; - -import org.spongepowered.asm.mixin.injection.InjectionPoint; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; -import java.util.Locale; -import java.util.Map; - -public class MixinUtil { - private static final VarHandle TYPES_HANDLE; - private static final String CLS_REGEX = "^([A-Za-z_][A-Za-z0-9_]*[\\.\\$])+[A-Za-z_][A-Za-z0-9_]*$"; - - static { - try { - TYPES_HANDLE = MethodHandles.privateLookupIn(InjectionPoint.class, MethodHandles.lookup()) - .findStaticVarHandle(InjectionPoint.class, "types", Map.class); - } catch (Exception e) { - throw new RuntimeException("Error unreflecting Injection Point types", e); - } - } - - @SuppressWarnings("unchecked") - public static boolean isKnownInjectionPointType(String value) { - Map> types = (Map>) TYPES_HANDLE.get(); - return types.containsKey(value.toUpperCase(Locale.ROOT)) || value.matches(CLS_REGEX); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java index 1b7ec0cf..0faf8d28 100644 --- a/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.mixin.MixinUtil; +import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.config.MutablePropertyContainer; import org.sinytra.adapter.model.config.PropertyContainer; @@ -98,7 +98,7 @@ public static Builder builder(String value) { private static boolean validateInjectionPointType(PropertyContainer container) { return container.getProperty(Keys.VALUE) - .map(MixinUtil::isKnownInjectionPointType) + .map(MixinBridge::isKnownInjectionPointType) .orElse(true); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java b/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java index 4b56c847..87c43108 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java @@ -1,13 +1,8 @@ package org.sinytra.adapter.pipeline.audit; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.MethodNode; -import org.spongepowered.asm.mixin.injection.Group; -import org.spongepowered.asm.mixin.injection.struct.InjectorGroupInfo; -import org.spongepowered.asm.util.Annotations; +import org.sinytra.adapter.mixin.MixinBridge; import java.util.Collection; import java.util.HashMap; @@ -20,37 +15,21 @@ public class MixinGroups { private MixinGroups(Map groups) { this.groups = Map.copyOf(groups); } - + @Nullable public GroupInfo getGroupInfo(MethodNode methodNode) { - AnnotationNode node = Annotations.getInvisible(methodNode, Group.class); - if (node == null) return null; - - String name = Annotations.getValue(node, "name"); - if (name == null || name.isEmpty()) return null; - - return this.groups.get(name); + String name = MixinBridge.getGroupName(methodNode); + return name != null ? this.groups.get(name) : null; } - - public static MixinGroups create(List methods) { - InjectorGroupInfo.Map infos = new InjectorGroupInfo.Map(); - Multimap members = HashMultimap.create(); - for (MethodNode method : methods) { - InjectorGroupInfo info = infos.parseGroup(method, "default"); - if (info.isDefault() || info.getName().equals("default")) continue; - - members.put(info.getName(), method); - } - + public static MixinGroups create(List methods) { Map groups = new HashMap<>(); - members.asMap().forEach((name, mems) -> { - InjectorGroupInfo info = infos.forName(name); - groups.put(name, new GroupInfo(mems, info.getMinRequired())); - }); - + for (MixinBridge.InjectorGroup group : MixinBridge.findInjectorGroups(methods)) { + groups.put(group.name(), new GroupInfo(group.members(), group.minRequired())); + } return new MixinGroups(groups); } - public record GroupInfo(Collection members, int minRequired) {} + public record GroupInfo(Collection members, int minRequired) { + } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java index 99634d67..ae9f4965 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java @@ -1,11 +1,10 @@ package org.sinytra.adapter.pipeline.resolver.special; import com.mojang.datafixers.util.Pair; -import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; -import org.sinytra.adapter.mixin.MockMixinRuntime; +import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; @@ -16,9 +15,6 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.resolver.Resolver; -import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; -import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; -import org.spongepowered.asm.mixin.injection.struct.Target; import java.util.List; @@ -131,14 +127,12 @@ private static Pair getTargetPair(Configuration clean AbstractInsnNode targetInsn = targetInsns.get(index); // Find modified variable - LocalVariableDiscriminator discriminator = LocalVariableDiscriminator.parse(context.methodAnnotation().unwrap()); - InjectionInfo injectionInfo = MockMixinRuntime.forInjectionInfo(context.classNode().name, injectionTarget.classNode().name, context.environment()); - - Type returnType = clean.getReturnType(); - Target target = MockMixinRuntime.createMixinTarget(injectionTarget); - LocalVariableDiscriminator.Context ctx = new LocalVariableDiscriminator.Context(injectionInfo, returnType, discriminator.isArgsOnly(), target, targetInsn); - - int local = discriminator.findLocal(ctx); + MixinBridge.Session session = MixinBridge.session( + context.classNode().name, + context.methodNode(), injectionTarget.classNode(), injectionTarget.methodNode(), + context.environment().refmapHolder() + ); + int local = session.findModifiedLocal(context.methodAnnotation().unwrap(), clean.getReturnType(), targetInsn); return Pair.of(targetInsn, local); } } diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java index 65729109..a3f30405 100644 --- a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java @@ -13,6 +13,7 @@ import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.AnnotationValueHandle; import org.sinytra.adapter.model.ann.ClassTarget; @@ -20,7 +21,6 @@ import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.pipeline.ctx.PatchContext; import org.sinytra.adapter.util.OpcodeUtil; -import org.spongepowered.asm.mixin.gen.AccessorInfo; import java.util.Optional; @@ -138,7 +138,7 @@ private static Optional getAccessorTargetFieldName(String owner, MethodN return annotationHandle.getValue("value") .map(AnnotationValueHandle::get) .filter(str -> !str.isEmpty()) - .or(() -> Optional.ofNullable(AccessorInfo.AccessorName.of(method.name)) - .map(name -> environment.refmapHolder().remap(owner, name.name))); + .or(() -> Optional.ofNullable(MixinBridge.getAccessorFieldName(method.name)) + .map(name -> environment.refmapHolder().remap(owner, name))); } } From 24ce132a13a81de9c6207155b084f187b333f653 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 20:05:53 +0200 Subject: [PATCH 08/44] Scoped local resolution --- .../analysis/locals/LocalVariableLookup.java | 25 +++-- .../analysis/locals/LocalsAnalyzer.java | 22 +++++ .../sinytra/adapter/mixin/MixinBridge.java | 30 +++++- .../LocalCaptureUpgradeTransformer.java | 36 ++++++-- .../ComparingInjectionPointResolver.java | 11 ++- .../ModifyVarInjectionPointSubResolver.java | 19 +++- .../special/InjectorOrdinalResolver.java | 92 +++++++++++++------ .../special/ModifyVarAtReturnResolver.java | 2 +- .../org/sinytra/adapter/util/AdapterUtil.java | 5 + 9 files changed, 182 insertions(+), 60 deletions(-) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java index 84b84cde..dcf6c37c 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalVariableLookup.java @@ -4,6 +4,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.method.MethodAnalyzer; @@ -11,6 +13,7 @@ import java.util.*; public class LocalVariableLookup { + private final InsnList instructions; private final List sortedLocals; private final boolean isNonStatic; private final int paramCount; @@ -24,6 +27,7 @@ public static LocalVariableLookup of(MethodNode method) { } public LocalVariableLookup(MethodNode methodNode) { + this.instructions = methodNode.instructions; this.isNonStatic = !MethodAnalyzer.isStatic(methodNode); List localVariables = methodNode.localVariables; @@ -72,6 +76,17 @@ public LocalVariableNode getByIndexOrNull(int index) { return this.byIndex.get(index); } + @Nullable + public LocalVariableNode getInScopeOrNull(int index, AbstractInsnNode insn) { + int position = this.instructions.indexOf(insn); + for (LocalVariableNode node : this.sortedLocals) { + if (node.index == index && this.instructions.indexOf(node.start) <= position && position < this.instructions.indexOf(node.end)) { + return node; + } + } + return null; + } + public int getOrdinal(LocalVariableNode node) { return this.sortedLocals.indexOf(node); } @@ -97,14 +112,4 @@ public List getForType(Type type) { .filter(l -> Type.getType(l.desc).equals(type)) .toList()); } - - public Optional getByTypedOrdinal(Type type, int ordinal) { - List available = getForType(type); - return ordinal >= 0 && available.size() > ordinal ? Optional.of(available.get(ordinal)) : Optional.empty(); - } - - public Optional getTypedOrdinal(LocalVariableNode node) { - int ordinal = getForType(node).indexOf(node); - return ordinal == -1 ? Optional.empty() : Optional.of(ordinal); - } } diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index 0a547dae..438e8806 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -16,6 +16,7 @@ import org.sinytra.adapter.analysis.params.ParamDiffResolver; import org.sinytra.adapter.edit.param.TransformParameters; import org.sinytra.adapter.mixin.MixinBridge; +import org.sinytra.adapter.mixin.MixinBridge.LocalCandidate; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.param.MethodParameters; @@ -186,6 +187,27 @@ public CapturedLocalsUsage resolveUsage(CapturedLocals captured, CapturedLocalsT return new CapturedLocalsUsage(targetTable, usedLocals, usageCount, varInsnLists); } + public List findSelectableLocals(TargetPair target, AbstractInsnNode insn, Type type) { + return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment().refmapHolder()) + .findLocalCandidates(type, insn, false); + } + + public int resolveLocalOrdinal(TargetPair target, AbstractInsnNode insn, Type type, int index) { + return findSelectableLocals(target, insn, type).stream() + .filter(c -> c.index() == index) + .mapToInt(LocalCandidate::ordinal) + .findFirst() + .orElse(-1); + } + + public int resolveLocalIndex(TargetPair target, AbstractInsnNode insn, Type type, int ordinal) { + return findSelectableLocals(target, insn, type).stream() + .filter(c -> c.ordinal() == ordinal) + .mapToInt(LocalCandidate::index) + .findFirst() + .orElse(-1); + } + @Nullable public List resolveAvailableLocals(TargetPair target) { Type[] targetParams = Type.getArgumentTypes(target.methodNode().desc); diff --git a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java index fe6b346b..b8d6a507 100644 --- a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java +++ b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java @@ -122,6 +122,9 @@ public static List findInjectorGroups(List methods) { public record InjectorGroup(String name, List members, int minRequired) { } + public record LocalCandidate(int ordinal, int index) { + } + public static final class Session { private final IMixinContext mixinContext; private final MethodNode mixinMethod; @@ -161,11 +164,30 @@ public InsnList slice(AnnotationNode slice) { return MethodSlice.parse(sliceContext, slice).getSlice(target()); } - public int findModifiedLocal(AnnotationNode modifyVariable, Type returnType, AbstractInsnNode insn) { - LocalVariableDiscriminator discriminator = LocalVariableDiscriminator.parse(modifyVariable); + public int findLocal(AnnotationNode selector, Type type, AbstractInsnNode insn) { + LocalVariableDiscriminator discriminator = LocalVariableDiscriminator.parse(selector); + return discriminator.findLocal(localsContext(type, discriminator.isArgsOnly(), insn)); + } + + public List findLocalCandidates(Type type, AbstractInsnNode insn, boolean argsOnly) { + LocalVariableDiscriminator.Context context = localsContext(type, argsOnly, insn); + List candidates = new ArrayList<>(); + // Ordinals start at slot 0, so the first ordinal of a type may belong to `this` and select nothing + for (int ordinal = 0; ordinal < this.targetMethod.maxLocals; ordinal++) { + int index = new LocalVariableDiscriminator(argsOnly, ordinal, -1, Set.of(), false).findLocal(context); + if (index != -1) { + candidates.add(new LocalCandidate(ordinal, index)); + } + } + return candidates; + } + + private LocalVariableDiscriminator.Context localsContext(Type type, boolean argsOnly, AbstractInsnNode insn) { InjectionInfo injectionInfo = createInjectionInfo(CallbackInjectionInfo.class, this.mixinContext); - LocalVariableDiscriminator.Context ctx = new LocalVariableDiscriminator.Context(injectionInfo, returnType, discriminator.isArgsOnly(), target(), insn); - return discriminator.findLocal(ctx); + // The context reads the locals through Locals.getLocalsAt, which isn't thread safe + synchronized (LOCALS_LOCK) { + return new LocalVariableDiscriminator.Context(injectionInfo, type, argsOnly, target(), insn); + } } private List find(InjectionPoint point, @Nullable AnnotationNode slice) throws Throwable { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java index 0c69e5b4..bebf7b3d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java @@ -2,13 +2,16 @@ import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.analysis.locals.LocalVariable; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.api.AuditTrail; import org.sinytra.adapter.api.MethodTransformer; import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.mixin.MixinBridge.LocalCandidate; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.key.MixinKeys; @@ -39,19 +42,32 @@ public PatchResult apply(MixinContext context, Configuration config) { LocalsAnalyzer.CapturedLocalsTransform transform = LocalsAnalyzer.analyzeCapturedLocals(info.capturedLocals(), methodNode); - LocalVariableLookup cleanLookup = LocalVariableLookup.of(targets.clean().methodNode()); - LocalVariableLookup dirtyLookup = LocalVariableLookup.of(targets.dirty().methodNode()); - LocalVariableLookup lookup = info.capturedLocals().lvt(); - if (cleanLookup == null || dirtyLookup == null) return PatchResult.PASS; + List cleanInsns = context.injections().find(targets.clean()); + List dirtyInsns = context.injections().find(targets.dirty()); + List cleanAvailable = context.locals().resolveAvailableLocals(targets.clean()); + if (cleanInsns.isEmpty() || dirtyInsns.isEmpty() || cleanAvailable == null) return PatchResult.PASS; + LocalVariableLookup lookup = info.capturedLocals().lvt(); + int capturedStart = info.capturedLocals().paramLocalStart(); + Type[] args = Type.getArgumentTypes(methodNode.desc); Map parameterToOrdinal = new HashMap<>(); Set usedOrdinals = new HashSet<>(); for (LocalVariableNode node : transform.usedLocalNodes()) { Type expected = Type.getType(node.desc); boolean addOrdinal = true; - List cleanLocals = cleanLookup.getForType(expected); - List dirtyLocals = dirtyLookup.getForType(expected); + int paramOrdinal = lookup.getParameterOrdinal(node); + if (paramOrdinal < capturedStart) return PatchResult.PASS; + long typedPosition = Arrays.stream(args, capturedStart, paramOrdinal).filter(expected::equals).count(); + LocalVariable cleanLocal = cleanAvailable.stream() + .filter(l -> l.type().equals(expected)) + .skip(typedPosition) + .findFirst() + .orElse(null); + if (cleanLocal == null) return PatchResult.PASS; + + List cleanLocals = context.locals().findSelectableLocals(targets.clean(), cleanInsns.getFirst(), expected); + List dirtyLocals = context.locals().findSelectableLocals(targets.dirty(), dirtyInsns.getFirst(), expected); if (cleanLocals.size() != dirtyLocals.size()) { long matching = info.availableTypes().stream() .filter(expected::equals) @@ -62,10 +78,13 @@ public PatchResult apply(MixinContext context, Configuration config) { addOrdinal = false; } - int localOrdinal = lookup.getTypedOrdinal(node).orElse(-1); + int localOrdinal = cleanLocals.stream() + .filter(c -> c.index() == cleanLocal.index()) + .mapToInt(LocalCandidate::ordinal) + .findFirst() + .orElse(-1); if (localOrdinal == -1) return PatchResult.PASS; - int paramOrdinal = lookup.getParameterOrdinal(node); usedOrdinals.add(paramOrdinal); if (addOrdinal && cleanLocals.size() > 1) { @@ -73,7 +92,6 @@ public PatchResult apply(MixinContext context, Configuration config) { } } - Type[] args = Type.getArgumentTypes(methodNode.desc); int start = info.capturedLocals().paramLocalStart(); for (int i = start; i < args.length; i++) { if (!usedOrdinals.contains(i)) continue; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java index 7366bb8b..50b9451e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java @@ -155,12 +155,15 @@ private static Optional handleWrapOperationToInstanceOf(Recipe re List originalCallArgs = context.dataFlow(cleanTarget).resolveCallOperandProducers(cleanInjectionInsn); if (originalCallArgs == null || originalCallArgs.isEmpty() || !(originalCallArgs.getFirst() instanceof VarInsnNode originalInstanceInsn)) return Optional.empty(); - LocalVariableNode originalInstanceLocal = cleanLocals.getByIndexOrNull(originalInstanceInsn.var); - if (originalInstanceLocal == null) return Optional.empty(); - int cleanOrdinal = cleanLocals.getTypedOrdinal(originalInstanceLocal).orElse(-1); + TargetPair dirtyTarget = recipe.getDirtyTarget(); + LocalVariableNode originalInstanceLocal = cleanLocals.getInScopeOrNull(originalInstanceInsn.var, cleanInjectionInsn); + if (dirtyTarget == null || originalInstanceLocal == null) return Optional.empty(); + + int cleanOrdinal = context.locals().resolveLocalOrdinal(cleanTarget, cleanInjectionInsn, Type.getType(originalInstanceLocal.desc), originalInstanceInsn.var); if (cleanOrdinal == -1) return Optional.empty(); - LocalVariableNode dirtyLocal = dirtyLocals.getByTypedOrdinal(Type.getType(instanceLocal.desc), cleanOrdinal).orElse(null); + int dirtyIndex = context.locals().resolveLocalIndex(dirtyTarget, instanceOfCall, Type.getType(instanceLocal.desc), cleanOrdinal); + LocalVariableNode dirtyLocal = dirtyLocals.getInScopeOrNull(dirtyIndex, instanceOfCall); if (dirtyLocal == null) return Optional.empty(); Parameter replacementInstanceParam = Parameter.builder(dirtyLocal.desc) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java index bade2e39..d6ed71d7 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java @@ -2,17 +2,22 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; +import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.resolver.SubResolver; +import java.util.List; + import static org.sinytra.adapter.model.config.key.MixinKeys.ORDINAL; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_STORE; @@ -32,9 +37,11 @@ public Configuration resolve(MixinContext context, Recipe recipe) { Type varType = Type.getReturnType(context.methodNode().desc); LocalVariableLookup lookup = recipe.cleanLocalsTable(); - if (lookup == null) return null; + AbstractInsnNode cleanInsn = recipe.getCleanInjectionInsn(); + if (lookup == null || cleanInsn == null) return null; - LocalVariableNode desired = lookup.getByTypedOrdinal(varType, ordinal).orElse(null); + int cleanIndex = context.locals().resolveLocalIndex(targets.clean(), cleanInsn, varType, ordinal); + LocalVariableNode desired = lookup.getInScopeOrNull(cleanIndex, cleanInsn); if (desired == null) return null; // Find variable initializer insns @@ -47,8 +54,12 @@ public Configuration resolve(MixinContext context, Recipe recipe) { LocalVariableNode match = LocalsAnalyzer.findLocalWithInitializer(method, desired.desc, desiredInitializerInsns).orElse(null); if (match != null) { - Integer dirtyOrdinal = dirtyLookup.getTypedOrdinal(match).orElse(null); - if (dirtyOrdinal == null) continue; + List stores = LocalsAnalyzer.findStoreInsns(method, match.index); + if (stores.isEmpty()) continue; + + TargetPair candidate = new TargetPair(targets.dirty().classNode(), method); + int dirtyOrdinal = context.locals().resolveLocalOrdinal(candidate, stores.getFirst().getNext(), varType, match.index); + if (dirtyOrdinal == -1) continue; return recipe.dirty().copyClean() .setProperty(ORDINAL, dirtyOrdinal) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java index b2882f33..022bb63c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java @@ -12,6 +12,7 @@ import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodInsnMatcher; +import org.sinytra.adapter.mixin.MixinBridge.LocalCandidate; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AtData; @@ -82,11 +83,13 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { if (context.hasFlag(MixinFlag.TARGETS_VARIABLE)) { LocalVariableLookup cleanTable = recipe.cleanLocalsTable(); - if (cleanTable != null) { + TargetPair cleanTarget = recipe.getCleanTarget(); + AbstractInsnNode cleanInsn = cleanTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)) : null; + if (cleanTable != null && cleanInsn != null) { // Handle modified ordinals cleanConfig.getProperty(MixinKeys.ORDINAL) - .flatMap(ordinal -> cleanTable.getByTypedOrdinal(returnType, ordinal) - .flatMap(lvn -> cleanTable.getTypedOrdinal(lvn).map(o -> new LocalVar(lvn, o, true))) + .flatMap(ordinal -> Optional.ofNullable(cleanTable.getInScopeOrNull(context.locals().resolveLocalIndex(cleanTarget, cleanInsn, returnType, ordinal), cleanInsn)) + .map(lvn -> new LocalVar(lvn, ordinal, true)) .map(local -> new HandlerInstance<>(ModifyVariableOffsetHandler.INSTANCE, local, pair -> MutableConfiguration.create() .setProperty(MixinKeys.ORDINAL, pair.getFirst().ordinal()) @@ -94,8 +97,11 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { ))) // Handle modified indexes .or(() -> cleanConfig.getProperty(MixinKeys.INDEX) - .flatMap(i -> Optional.ofNullable(cleanTable.getByIndexOrNull(i)) - .flatMap(lvn -> cleanTable.getTypedOrdinal(lvn).map(o -> new LocalVar(lvn, o, false))) + .flatMap(i -> Optional.ofNullable(cleanTable.getInScopeOrNull(i, cleanInsn)) + .flatMap(lvn -> { + int ordinal = context.locals().resolveLocalOrdinal(cleanTarget, cleanInsn, Type.getType(lvn.desc), i); + return ordinal == -1 ? Optional.empty() : Optional.of(new LocalVar(lvn, ordinal, false)); + }) .map(local -> new HandlerInstance<>(ModifyVariableOffsetHandler.INSTANCE, local, pair -> MutableConfiguration.create() .setProperty(MixinKeys.INDEX, pair.getFirst().lvn().index) @@ -291,9 +297,28 @@ private static class ModifyVariableOffsetHandler implements UpdateHandler>map(var -> Pair.of(var, null)) - .or(() -> tryFindSyntheticVariableIndex(mixinContext, methodNode, recipe, local)); + .or(() -> tryFindSyntheticVariableIndex(mixinContext, methodNode, recipe, local, points)); + } + + private record InjectionPoints(TargetPair cleanTarget, AbstractInsnNode cleanInsn, TargetPair dirtyTarget, AbstractInsnNode dirtyInsn) { + } + + private static int indexOfOrdinal(List candidates, int ordinal) { + return candidates.stream().filter(c -> c.ordinal() == ordinal).mapToInt(LocalCandidate::index).findFirst().orElse(-1); } /** @@ -324,7 +349,7 @@ private static class ModifyVariableOffsetHandler implements UpdateHandler */ - private static Optional> tryFindSyntheticVariableIndex(MixinContext context, MethodNode methodNode, Recipe recipe, LocalVar local) { + private static Optional> tryFindSyntheticVariableIndex(MixinContext context, MethodNode methodNode, Recipe recipe, LocalVar local, InjectionPoints points) { int ordinal = local.ordinal(); Type variableType = Type.getReturnType(methodNode.desc); LocalVariableLookup cleanTable = recipe.cleanLocalsTable(); @@ -332,10 +357,11 @@ private static class ModifyVariableOffsetHandler implements UpdateHandler available = dirtyTable.getForType(variableType); - if (available.size() > ordinal) { - int variableIndex = available.get(ordinal).index; + List cleanLocals = context.locals().findSelectableLocals(points.cleanTarget(), points.cleanInsn(), variableType); + List dirtyLocals = context.locals().findSelectableLocals(points.dirtyTarget(), points.dirtyInsn(), variableType); + if (cleanLocals.size() == dirtyLocals.size()) { + int variableIndex = indexOfOrdinal(dirtyLocals, ordinal); + if (variableIndex != -1) { AbstractInsnNode cleanInsn = recipe.getCleanInjectionInsn(); AbstractInsnNode dirtyInsn = context.injections().resolve(recipe.getDirtyTarget()); if (cleanInsn != null && dirtyInsn != null) { @@ -353,12 +379,15 @@ private static class ModifyVariableOffsetHandler implements UpdateHandler Pair.of( - new LocalVar(lvn, o), - MutableConfiguration.create() - .setProperty(MixinKeys.ARGS_ONLY, false) - )); + int dirtyOrdinal = context.locals().resolveLocalOrdinal(points.dirtyTarget(), points.dirtyInsn(), Type.getType(lvn.desc), dirtyIndex); + if (dirtyOrdinal == -1) { + return Optional.empty(); + } + return Optional.of(Pair.of( + new LocalVar(lvn, dirtyOrdinal), + MutableConfiguration.create() + .setProperty(MixinKeys.ARGS_ONLY, false) + )); } } break; @@ -383,28 +412,35 @@ private static List> getUsedVariablesInLabel(Abstract return list; } - private static Optional tryFindUpdatedIndex(Type targetType, Recipe recipe, LocalVar local) { + private static Optional tryFindUpdatedIndex(MixinContext context, Type targetType, Recipe recipe, LocalVar local, InjectionPoints points) { int ordinal = local.ordinal(); LocalVariableLookup cleanVarLookup = recipe.cleanLocalsTable(); LocalVariableLookup dirtyVarLookup = recipe.dirtyLocalsTable(); if (cleanVarLookup == null || dirtyVarLookup == null) { return Optional.empty(); } - List cleanLocals = cleanVarLookup.getForType(targetType); - if (cleanLocals.size() <= ordinal) { + List cleanLocals = context.locals().findSelectableLocals(points.cleanTarget(), points.cleanInsn(), targetType); + LocalVariableNode cleanLocal = cleanVarLookup.getInScopeOrNull(indexOfOrdinal(cleanLocals, ordinal), points.cleanInsn()); + if (cleanLocal == null) { return Optional.empty(); } - LocalVariableNode cleanLocal = cleanLocals.get(ordinal); if (!GeneratedVariables.isGeneratedVariableName(cleanLocal.name, Type.getType(cleanLocal.desc))) { return Optional.empty(); } - List dirtyLocals = dirtyVarLookup.getForType(targetType); - if (cleanLocals.size() != dirtyLocals.size() || dirtyLocals.size() <= ordinal) { - return findReplacementLocal(recipe.getCleanTarget().methodNode(), recipe.getDirtyTarget().methodNode(), cleanLocal) - .flatMap(var -> dirtyVarLookup.getTypedOrdinal(var).map(o -> new LocalVar(var, o))); + List dirtyLocals = context.locals().findSelectableLocals(points.dirtyTarget(), points.dirtyInsn(), targetType); + int dirtyIndex = indexOfOrdinal(dirtyLocals, ordinal); + if (cleanLocals.size() != dirtyLocals.size() || dirtyIndex == -1) { + return findReplacementLocal(points.cleanTarget().methodNode(), points.dirtyTarget().methodNode(), cleanLocal) + .flatMap(var -> { + int dirtyOrdinal = context.locals().resolveLocalOrdinal(points.dirtyTarget(), points.dirtyInsn(), Type.getType(var.desc), var.index); + return dirtyOrdinal == -1 ? Optional.empty() : Optional.of(new LocalVar(var, dirtyOrdinal)); + }); + } + LocalVariableNode dirtyLocal = dirtyVarLookup.getInScopeOrNull(dirtyIndex, points.dirtyInsn()); + if (dirtyLocal == null) { + return Optional.empty(); } - LocalVariableNode dirtyLocal = dirtyLocals.get(ordinal); if (!local.relative() && dirtyLocal.index == local.lvn().index) { return Optional.empty(); } @@ -414,7 +450,7 @@ private static Optional tryFindUpdatedIndex(Type targetType, Recipe re } if (cleanLocal.index != dirtyLocal.index && !local.relative()) { - return Optional.of(new LocalVar(dirtyLocal, dirtyLocals.indexOf(dirtyLocal))); + return Optional.of(new LocalVar(dirtyLocal, ordinal)); } return Optional.empty(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java index ae9f4965..eb64412e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java @@ -132,7 +132,7 @@ private static Pair getTargetPair(Configuration clean context.methodNode(), injectionTarget.classNode(), injectionTarget.methodNode(), context.environment().refmapHolder() ); - int local = session.findModifiedLocal(context.methodAnnotation().unwrap(), clean.getReturnType(), targetInsn); + int local = session.findLocal(context.methodAnnotation().unwrap(), clean.getReturnType(), targetInsn); return Pair.of(targetInsn, local); } } diff --git a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java index e31f171d..849a1b94 100644 --- a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java +++ b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java @@ -239,6 +239,11 @@ public static AbstractInsnNode getSingleInsn(SourceValue value) { return value.insns.size() == 1 ? value.insns.iterator().next() : null; } + @Nullable + public static T getFirstOrNull(List list) { + return list.isEmpty() ? null : list.getFirst(); + } + @Nullable public static T getSingleOrNull(Collection collection) { return collection.size() == 1 ? collection.iterator().next() : null; From 12236dea4363468a3e338dc1befa269b6c4af853 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 20:10:40 +0200 Subject: [PATCH 09/44] Use Mixin internals to parse qualifiers --- .../org/sinytra/adapter/mixin/MixinBridge.java | 18 ++++++++++++++++++ .../sinytra/adapter/model/MethodQualifier.java | 17 ++++++----------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java index b8d6a507..6bdc2f74 100644 --- a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java +++ b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java @@ -21,6 +21,7 @@ import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; import org.spongepowered.asm.mixin.injection.points.BeforeConstant; import org.spongepowered.asm.mixin.injection.selectors.ISelectorContext; +import org.spongepowered.asm.mixin.injection.selectors.InvalidSelectorException; import org.spongepowered.asm.mixin.injection.struct.*; import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.mixin.refmap.IReferenceMapper; @@ -122,6 +123,23 @@ public static List findInjectorGroups(List methods) { public record InjectorGroup(String name, List members, int minRequired) { } + public record MemberSelector(@Nullable String owner, @Nullable String name, @Nullable String desc, int maxMatches, boolean field) { + } + + @Nullable + public static MemberSelector parseMemberSelector(String input) { + String selector = input.strip(); + if (selector.startsWith("/") || selector.startsWith("@")) { + return null; + } + try { + MemberInfo info = MemberInfo.parse(selector, null).validate(); + return new MemberSelector(info.getOwner(), info.getName(), info.getDesc(), info.getMaxMatchCount(), info.isField()); + } catch (InvalidSelectorException e) { + return null; + } + } + public record LocalCandidate(int ordinal, int index) { } diff --git a/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java b/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java index 08d98e94..e6c09aaa 100644 --- a/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java +++ b/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java @@ -5,28 +5,23 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.mixin.MixinBridge; import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public record MethodQualifier(@Nullable String owner, @Nullable String name, @Nullable String desc) { - public static final Pattern METHOD_QUALIFIER_PATTERN = Pattern.compile("^(?L.+?;)?(?[^(:]+)?(?\\((?:\\[*[ZCBSIFJD]|\\[*L[a-zA-Z0-9/_$]+;)*\\)(?:\\[*[VZCBSIFJD]|\\[?L[a-zA-Z0-9/_;$]+))?$"); public MethodQualifier(@Nullable String name, @Nullable String desc) { this(null, name, desc); } public static Optional parse(String qualifier) { - Matcher matcher = METHOD_QUALIFIER_PATTERN.matcher(qualifier); - if (matcher.matches()) { - String name = matcher.group("name"); - String desc = matcher.group("desc"); - if (name != null || desc != null) { - return Optional.of(new MethodQualifier(matcher.group("owner"), name, desc)); - } + MixinBridge.MemberSelector selector = MixinBridge.parseMemberSelector(qualifier); + if (selector == null || selector.field() || selector.maxMatches() != 1 || selector.name() == null && selector.desc() == null) { + return Optional.empty(); } - return Optional.empty(); + String owner = selector.owner() != null ? Type.getObjectType(selector.owner()).getDescriptor() : null; + return Optional.of(new MethodQualifier(owner, selector.name(), selector.desc())); } public static MethodQualifier create(ClassNode classNode, MethodNode methodNode) { From 380a1ed25371a1fe8137a5f04f578d4c1ceb8b85 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 20:52:51 +0200 Subject: [PATCH 10/44] Automatic mixin signature configuration --- .../adapter/model/param/HandlerSignature.java | 56 ++++++++++ .../pipeline/injector/HandlerSignatures.java | 102 ++++++++++++++++++ .../pipeline/injector/InjectMixin.java | 33 +++--- .../adapter/pipeline/injector/MixinType.java | 3 + .../pipeline/injector/ModifyArgMixin.java | 22 ++-- .../injector/ModifyExpressionValueMixin.java | 35 +++--- .../injector/ModifyReturnValueMixin.java | 35 +++--- .../injector/ModifyVariableMixin.java | 25 +++-- .../pipeline/injector/RedirectMixin.java | 40 +++---- .../pipeline/injector/WrapOperationMixin.java | 47 ++++---- 10 files changed, 287 insertions(+), 111 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java diff --git a/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java b/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java new file mode 100644 index 00000000..16a655fc --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java @@ -0,0 +1,56 @@ +package org.sinytra.adapter.model.param; + +import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; + +import java.util.List; + +public record HandlerSignature(List slots, ValueSource returns) { + public enum ParamSource { + /// All arguments of the target method + TARGET_ARGS, + /// CI/CIR + CALLBACK_INFO, + /// The receiver and arguments of the call the @At selects + CALL_OPERANDS, + /// MixinExtras Operation + OPERATION, + /// A single value the injector modifies + VALUE, + /// A subset of the target method's arguments + CAPTURED_TARGET_ARGS, + /// Kept unchanged, such as captured locals and sugar + KEEP + } + + public enum ValueSource { + VOID, + /// The return type of the call the `@At` selects + CALL_RETURN, + /// The return type of the target method + TARGET_RETURN, + /// An argument of the call the `@At` selects, chosen by the injector + CALL_ARG, + /// A local variable of the target method, chosen by the injector + VARIABLE + } + + public record Slot(ParamGroup group, ParamSource source) { + } + + public static HandlerSignature of(ValueSource returns, Slot... slots) { + return new HandlerSignature(List.of(slots), returns); + } + + public static Slot slot(ParamGroup group, ParamSource source) { + return new Slot(group, source); + } + + public List groups() { + return this.slots.stream().map(Slot::group).toList(); + } + + public MethodParameters parse(MethodNode handler) { + return MethodParameters.create(handler, groups()); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java new file mode 100644 index 00000000..702580f6 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java @@ -0,0 +1,102 @@ +package org.sinytra.adapter.pipeline.injector; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.sinytra.adapter.analysis.injection.InjectionQuery; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.AdapterUtil; + +import java.util.List; + +public final class HandlerSignatures { + + public static TxResult apply(MixinContext context, HandlerSignature signature, Configuration clean, MutableConfiguration dirty, Recipe recipe, @Nullable Type value) { + Type valueType = switch (signature.returns()) { + case VOID -> Type.VOID_TYPE; + case CALL_RETURN -> { + MethodQualifier call = getCallQualifier(dirty); + yield call != null ? Type.getReturnType(call.desc()) : null; + } + case TARGET_RETURN -> dirty.getTargetMethod() != null && dirty.getTargetMethod().desc() != null + ? Type.getReturnType(dirty.getTargetMethod().desc()) : null; + case CALL_ARG, VARIABLE -> value; + }; + if (valueType == null) { + return TxResult.FAIL; + } + + MethodParameters.Builder params = clean.getParameters().mutableCopy(); + for (HandlerSignature.Slot slot : signature.slots()) { + switch (slot.source()) { + case TARGET_ARGS -> { + if (dirty.getTargetMethod() == null || dirty.getTargetMethod().desc() == null) return TxResult.FAIL; + + params.putTypes(slot.group(), Parameters.getParameterTypes(dirty.getTargetMethod().desc())); + } + case CALL_OPERANDS -> { + List operands = findCallOperands(context, dirty); + if (operands == null) return TxResult.FAIL; + + params.putTypes(slot.group(), operands); + } + case OPERATION -> params.putType(slot.group(), TypeConstants.OPERATION_TYPE); + case VALUE -> params.putType(slot.group(), valueType); + case CAPTURED_TARGET_ARGS -> params.putTypes(slot.group(), context.locals().findDirtyCapturedParams(recipe.clean(), recipe.dirty())); + default -> { + } + } + } + + dirty.setParameters(params.build()); + dirty.setReturnType(valueType); + return TxResult.SUCCESS; + } + + @Nullable + private static MethodQualifier getCallQualifier(Configuration dirty) { + AtData at = dirty.getAtData(); + MethodQualifier call = at != null ? at.getTargetQualifier().orElse(null) : null; + return call != null && call.desc() != null ? call : null; + } + + @Nullable + private static List findCallOperands(MixinContext context, Configuration dirty) { + MethodQualifier call = getCallQualifier(dirty); + if (call == null) return null; + + TargetPair dirtyTarget = dirty.getTargetMethod() != null ? context.dirtyMethods().resolve(dirty.getTargetMethod()) : null; + AbstractInsnNode insn = dirtyTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget, InjectionQuery.of(dirty.getAtData()))) : null; + + boolean isStatic; + String owner; + if (insn instanceof MethodInsnNode minsn) { + isStatic = minsn.getOpcode() == Opcodes.INVOKESTATIC; + owner = minsn.owner; + } else { + TargetPair method = call.isFull() ? context.dirtyMethods().resolveInherited(call) : context.dirtyMethods().resolve(call); + if (method == null) return null; + isStatic = MethodAnalyzer.isStatic(method.methodNode()); + owner = call.internalOwnerName() != null ? call.internalOwnerName() : method.classNode().name; + } + + return Parameters.getCallOperandTypes(isStatic ? null : Type.getObjectType(owner), call.desc()); + } + + private HandlerSignatures() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index 115c75e2..c4f4f89d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -9,9 +9,10 @@ import org.sinytra.adapter.model.config.PropertyContainerTemplate; import org.sinytra.adapter.model.config.key.MixinKeys; import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; import org.sinytra.adapter.model.param.MethodParameters; import org.sinytra.adapter.model.param.Parameter; -import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -26,6 +27,8 @@ import java.util.List; import java.util.Objects; +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.*; public class InjectMixin extends AbstractMixinType { @@ -34,10 +37,22 @@ public class InjectMixin extends AbstractMixinType { .pluralKeys(MixinKeys.TARGET_METHOD, MixinKeys.TARGET_AT) .build(); + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.VOID, + slot(METHOD_PARAMS, TARGET_ARGS), + slot(CI_CIR, CALLBACK_INFO), + slot(LOCALS, KEEP) + ); + public InjectMixin() { super(TEMPLATE); } + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers @@ -47,7 +62,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res .addSubResolver(new ComparingInjectionPointResolver.Inject()) .addSubResolver(new ArbitraryInjectionPointSubResolver()); - clean.setParameters(MethodParameters.create(context.methodNode(), List.of(METHOD_PARAMS, CI_CIR, LOCALS))); + clean.setParameters(SIGNATURE.parse(context.methodNode())); // TODO Handle slices // mixin.getProperty(Configuration.Keys.SLICES) @@ -63,18 +78,10 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo return TxResult.SUCCESS; } - if (sameTarget(recipe)) { + if (sameTarget(recipe) || clean.getParameters().getTypes(METHOD_PARAMS).isEmpty()) { dirty.inheritParameters(); - } else { - List cleanParams = clean.getParameters().getTypes(METHOD_PARAMS); - if (!cleanParams.isEmpty()) { - MethodParameters newParams = clean.getParameters().copy(); - List dirtyTargetMethodParams = Parameters.getParameterTypes(dirty.getTargetMethod().desc()); - newParams.setTypes(METHOD_PARAMS, dirtyTargetMethodParams); - dirty.setParameters(newParams); - } else { - dirty.inheritParameters(); - } + } else if (HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, null) == TxResult.FAIL) { + return TxResult.FAIL; } // Process static modifier diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java index d9e516b0..7bd6668b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java @@ -3,6 +3,7 @@ import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.param.HandlerSignature; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -18,6 +19,8 @@ public interface MixinType { PropertyContainerTemplate getConfigurationTemplate(); + HandlerSignature signature(); + default Set getFlags() { return EnumSet.noneOf(MixinFlag.class); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java index 105d7ee0..42c191f1 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java @@ -10,7 +10,8 @@ import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; import org.sinytra.adapter.model.param.Parameter; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; @@ -24,6 +25,8 @@ import java.util.List; import static org.sinytra.adapter.model.config.key.MixinKeys.INDEX; +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.SINGLE_ANY; public class ModifyArgMixin extends AbstractMixinType { @@ -32,16 +35,26 @@ public class ModifyArgMixin extends AbstractMixinType { .pluralKeys(MixinKeys.TARGET_METHOD) .build(); + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.CALL_ARG, + slot(SINGLE_ANY, VALUE) + ); + public ModifyArgMixin() { super(TEMPLATE); } + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers.getOrThrow(InjectionPointResolver.class) .addSubResolver(new ArbitraryInjectionPointSubResolver()); - clean.setParameters(MethodParameters.create(context.methodNode(), List.of(SINGLE_ANY))); + clean.setParameters(SIGNATURE.parse(context.methodNode())); return TxResult.SUCCESS; } @@ -61,10 +74,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo Type type = findArgType(context, recipe.clean().getAtData(), dirty.getAtData(), clean, dirty); if (type != null) { - MethodParameters parameters = MethodParameters.create(context.methodNode(), List.of(SINGLE_ANY)); - parameters.set(SINGLE_ANY, Parameter.simple(type)); - dirty.setParameters(parameters); - dirty.setReturnType(type); + return HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, type); } return TxResult.SUCCESS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java index 3366bb2f..ded583e9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java @@ -1,13 +1,13 @@ package org.sinytra.adapter.pipeline.injector; -import org.objectweb.asm.Type; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.ConfigurationTemplates; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -17,9 +17,9 @@ import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.special.ResolverSyntheticInstanceof; -import java.util.List; - import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.LOCALS; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.SINGLE_ANY; @@ -29,10 +29,22 @@ public class ModifyExpressionValueMixin extends AbstractMixinType { .pluralKeys(MixinKeys.TARGET_METHOD, MixinKeys.TARGET_AT) .build(); + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.CALL_RETURN, + slot(SINGLE_ANY, VALUE), + slot(CAPTURED_PARAMS, CAPTURED_TARGET_ARGS), + slot(LOCALS, KEEP) + ); + public ModifyExpressionValueMixin() { super(TEMPLATE, MixinFlag.RETURN_TYPE_SENSITIVE); } + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers.getOrThrow(InjectionPointResolver.class) @@ -40,7 +52,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res resolvers .addBefore(InjectionPointResolver.class, new ResolverSyntheticInstanceof(true)); - clean.setParameters(MethodParameters.create(context.methodNode(), List.of(SINGLE_ANY, CAPTURED_PARAMS, LOCALS))); + clean.setParameters(SIGNATURE.parse(context.methodNode())); return TxResult.SUCCESS; } @@ -63,17 +75,6 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo return TxResult.SUCCESS; } - List dirtyCaptured = context.locals().findDirtyCapturedParams(recipe.clean(), recipe.dirty()); - Type modifyingType = Type.getReturnType(targetDesc.desc()); - - MethodParameters params = clean.getParameters().mutableCopy() - .putType(SINGLE_ANY, modifyingType) - .putTypes(CAPTURED_PARAMS, dirtyCaptured) - .build(); - - dirty.setParameters(params); - dirty.setReturnType(modifyingType); - - return TxResult.SUCCESS; + return HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, null); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java index 44a7d49d..b5249fec 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java @@ -1,6 +1,5 @@ package org.sinytra.adapter.pipeline.injector; -import org.objectweb.asm.Type; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.Targets; @@ -10,7 +9,8 @@ import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -19,9 +19,9 @@ import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.special.InjectorOrdinalResolver; -import java.util.List; - import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_RETURN; +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.*; public class ModifyReturnValueMixin extends AbstractMixinType { @@ -29,10 +29,22 @@ public class ModifyReturnValueMixin extends AbstractMixinType { .pluralKeys(MixinKeys.TARGET_METHOD, MixinKeys.TARGET_AT) .build(); + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.TARGET_RETURN, + slot(SINGLE_ANY, VALUE), + slot(CAPTURED_PARAMS, CAPTURED_TARGET_ARGS), + slot(LOCALS, KEEP) + ); + public ModifyReturnValueMixin() { super(TEMPLATE); } + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + @Override public boolean canInject(MixinContext context, Configuration config) { AtData at = config.getAtData(); @@ -50,7 +62,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res resolvers .addBefore(InjectionPointResolver.class, new InjectorOrdinalResolver()); - clean.setParameters(MethodParameters.create(context.methodNode(), List.of(SINGLE_ANY, CAPTURED_PARAMS, LOCALS))); + clean.setParameters(SIGNATURE.parse(context.methodNode())); return TxResult.SUCCESS; } @@ -65,17 +77,6 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo return TxResult.FAIL; } - List dirtyCaptured = context.locals().findDirtyCapturedParams(recipe.clean(), recipe.dirty()); - Type modifyingType = Type.getReturnType(target.desc()); - - MethodParameters params = clean.getParameters().mutableCopy() - .putType(SINGLE_ANY, modifyingType) - .putTypes(CAPTURED_PARAMS, dirtyCaptured) - .build(); - - dirty.setParameters(params); - dirty.setReturnType(modifyingType); - - return TxResult.SUCCESS; + return HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, null); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index f43bcb8d..19480bfe 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -11,8 +11,8 @@ import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.param.MethodParameters; -import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; @@ -27,6 +27,8 @@ import java.util.List; +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.LOCALS; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.SINGLE_ANY; @@ -36,10 +38,21 @@ public class ModifyVariableMixin extends AbstractMixinType { .pluralKeys(MixinKeys.TARGET_METHOD) .build(); + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.VARIABLE, + slot(SINGLE_ANY, VALUE), + slot(LOCALS, KEEP) + ); + public ModifyVariableMixin() { super(TEMPLATE, MixinFlag.TARGETS_VARIABLE); } + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers @@ -49,7 +62,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res resolvers.getOrThrow(InjectionPointResolver.class) .addSubResolver(new ModifyVarInjectionPointSubResolver()); - clean.setParameters(MethodParameters.create(context.methodNode(), List.of(SINGLE_ANY, LOCALS))); + clean.setParameters(SIGNATURE.parse(context.methodNode())); return TxResult.SUCCESS; } @@ -78,11 +91,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo TypeAdapter adapter = context.getTypeAdapter(cleanVarType, dirtyVarType); if (adapter != null) { - MethodParameters newParams = MethodParameters.create(context.methodNode(), List.of(SINGLE_ANY, LOCALS)); - newParams.get(SINGLE_ANY).set(0, Parameter.simple(dirtyVarType)); - - dirty.setParameters(newParams); - dirty.setReturnType(dirtyVarType); + return HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, dirtyVarType); } return TxResult.SUCCESS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index 40a8a5e6..d93f306e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -8,9 +8,10 @@ import org.sinytra.adapter.model.config.ConfigurationTemplates; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; -import org.sinytra.adapter.model.config.key.ControlKeys; import org.sinytra.adapter.model.config.key.MixinKeys; import org.sinytra.adapter.model.constant.MixinAnnotationConstants; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; import org.sinytra.adapter.model.param.MethodParameters; import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; import org.sinytra.adapter.model.param.Parameters; @@ -27,6 +28,8 @@ import java.util.List; +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.*; public class RedirectMixin extends AbstractMixinType { @@ -34,10 +37,22 @@ public class RedirectMixin extends AbstractMixinType { .pluralKeys(MixinKeys.TARGET_METHOD) .build(); + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.CALL_RETURN, + slot(METHOD_PARAMS, CALL_OPERANDS), + slot(CAPTURED_PARAMS, CAPTURED_TARGET_ARGS), + slot(LOCALS, KEEP) + ); + public RedirectMixin() { super(TEMPLATE, MixinFlag.AT_TARGET_SENSITIVE, MixinFlag.ACCEPTS_INSTANCE); } + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers. @@ -81,28 +96,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo if (dirty.getTargetMethod() == null || dirty.getAtData() == null) return TxResult.FAIL; - MethodQualifier targetDesc = dirty.getAtData().getTargetQualifier().orElse(null); - if (targetDesc == null || targetDesc.desc() == null) - return TxResult.FAIL; - - TargetPair dirtyTarget = context.dirtyMethods().resolve(targetDesc); - if (dirtyTarget == null) - return TxResult.FAIL; - - List dirtyCaptured = context.locals().findDirtyCapturedParams(recipe.clean(), recipe.dirty()); - Type receiver = MethodAnalyzer.isStatic(dirtyTarget.methodNode()) ? null : Type.getObjectType(dirtyTarget.classNode().name); - List callTypes = Parameters.getCallOperandTypes(receiver, targetDesc.desc()); - - dirty.inheritProperyIfAbsent(ControlKeys.PARAMETERS); - MethodParameters params = (dirty.getParameters() != null ? dirty.getParameters().mutableCopy() : MethodParameters.builder()) - .putTypes(METHOD_PARAMS, callTypes) - .putTypes(CAPTURED_PARAMS, dirtyCaptured) - .build(); - - dirty.setParameters(params); - dirty.setReturnType(Type.getReturnType(targetDesc.desc())); - - return TxResult.SUCCESS; + return HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, null); } private boolean isStaticRedirect(MixinContext context, MethodQualifier targetDesc, List methodParams, List callTypes) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index 7a35cbf4..b20e6e2a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -1,17 +1,14 @@ package org.sinytra.adapter.pipeline.injector; -import org.objectweb.asm.Type; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.ConfigurationTemplates; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ParamSource; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; import org.sinytra.adapter.model.param.MethodParameters; -import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -23,8 +20,9 @@ import org.sinytra.adapter.pipeline.resolver.injection.ComparingInjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; -import java.util.List; +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.*; public class WrapOperationMixin extends AbstractMixinType { @@ -33,10 +31,23 @@ public class WrapOperationMixin extends AbstractMixinType { .pluralKeys(MixinKeys.TARGET_METHOD, MixinKeys.TARGET_AT, MixinKeys.TARGET_CONSTANT) .build(); + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.CALL_RETURN, + slot(METHOD_PARAMS, CALL_OPERANDS), + slot(MethodParameters.ParamGroup.OPERATION, ParamSource.OPERATION), + slot(CAPTURED_PARAMS, CAPTURED_TARGET_ARGS), + slot(LOCALS, KEEP) + ); + public WrapOperationMixin() { super(TEMPLATE, MixinFlag.AT_TARGET_SENSITIVE, MixinFlag.ACCEPTS_INSTANCE); } + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers.getOrThrow(InjectionPointResolver.class) @@ -45,7 +56,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res processors .addAfter(ParametersProcessor.class, new WrapOpParamsProcessor()); - clean.setParameters(MethodParameters.create(context.methodNode(), List.of(METHOD_PARAMS, OPERATION, CAPTURED_PARAMS, LOCALS))); + clean.setParameters(SIGNATURE.parse(context.methodNode())); return TxResult.SUCCESS; } @@ -55,24 +66,6 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo if (dirty.getTargetMethod() == null) return TxResult.FAIL; if (dirty.getAtData() == null) return TxResult.PASS; - MethodQualifier dirtyAtTarget = dirty.getAtData().getTargetQualifier().orElse(null); - if (dirtyAtTarget == null || dirtyAtTarget.desc() == null) - return TxResult.FAIL; - - List dirtyCaptured = context.locals().findDirtyCapturedParams(recipe.clean(), recipe.dirty()); - TargetPair targetMethodCall = dirtyAtTarget.isFull() ? context.dirtyMethods().resolveInherited(dirtyAtTarget) : null; - Type receiver = targetMethodCall != null && !MethodAnalyzer.isStatic(targetMethodCall.methodNode()) ? Type.getObjectType(dirtyAtTarget.internalOwnerName()) : null; - List callTypes = Parameters.getCallOperandTypes(receiver, dirtyAtTarget.desc()); - - MethodParameters params = clean.getParameters().mutableCopy() - .putTypes(METHOD_PARAMS, callTypes) - .putType(OPERATION, TypeConstants.OPERATION_TYPE) - .putTypes(CAPTURED_PARAMS, dirtyCaptured) - .build(); - - dirty.setParameters(params); - dirty.setReturnType(Type.getReturnType(dirtyAtTarget.desc())); - - return TxResult.SUCCESS; + return HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, null); } } From 809b5779af758f13ef332506dc5fdbd29ec33e0b Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 21:04:53 +0200 Subject: [PATCH 11/44] Unify signature processors --- .../pipeline/injector/RedirectMixin.java | 6 +-- .../pipeline/injector/WrapOperationMixin.java | 4 -- .../pipeline/processor/Processors.java | 11 ++-- .../OperationCallStep.java} | 8 +-- .../ParameterCastStep.java} | 6 +-- .../ParametersStep.java} | 6 +-- .../RedirectedCallStep.java} | 7 ++- .../ReturnTypeStep.java} | 6 +-- .../signature/SignatureProcessor.java | 52 +++++++++++++++++++ .../processor/signature/SignatureStep.java | 10 ++++ .../StaticAccessStep.java} | 6 +-- 11 files changed, 86 insertions(+), 36 deletions(-) rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{wrapop/WrapOpParamsProcessor.java => signature/OperationCallStep.java} (94%) rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{ParametersPostProcessor.java => signature/ParameterCastStep.java} (95%) rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{ParametersProcessor.java => signature/ParametersStep.java} (93%) rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{redirect/ParameterUsageProcessor.java => signature/RedirectedCallStep.java} (93%) rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{ReturnTypeProcessor.java => signature/ReturnTypeStep.java} (88%) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/SignatureProcessor.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/SignatureStep.java rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{StaticAccessProcessor.java => signature/StaticAccessStep.java} (94%) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index d93f306e..b4a27fd9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -18,10 +18,9 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.ParametersProcessor; import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.processor.redirect.DivertRedirectProcessor; -import org.sinytra.adapter.pipeline.processor.redirect.ParameterUsageProcessor; +import org.sinytra.adapter.pipeline.processor.signature.SignatureProcessor; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.special.ResolverSyntheticInstanceof; @@ -58,8 +57,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res resolvers. addBefore(InjectionPointResolver.class, new ResolverSyntheticInstanceof(false)); processors - .addAfter(ParametersProcessor.class, new DivertRedirectProcessor()) - .addAfter(ParametersProcessor.class, new ParameterUsageProcessor()); + .addAfter(SignatureProcessor.class, new DivertRedirectProcessor()); if (clean.getAtData() == null || !MixinAnnotationConstants.AT_VAL_INVOKE.equals(clean.getAtData().getValue())) return TxResult.FAIL; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index b20e6e2a..58e00209 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -12,9 +12,7 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.ParametersProcessor; import org.sinytra.adapter.pipeline.processor.Processors; -import org.sinytra.adapter.pipeline.processor.wrapop.WrapOpParamsProcessor; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.resolver.injection.AtVariableAssignStoreSubResolver; import org.sinytra.adapter.pipeline.resolver.injection.ComparingInjectionPointResolver; @@ -53,8 +51,6 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res resolvers.getOrThrow(InjectionPointResolver.class) .addSubResolverFirst(new ComparingInjectionPointResolver.WrapOperation()) .addSubResolver(new AtVariableAssignStoreSubResolver()); - processors - .addAfter(ParametersProcessor.class, new WrapOpParamsProcessor()); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java index d0450988..f51a8f51 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java @@ -1,6 +1,7 @@ package org.sinytra.adapter.pipeline.processor; import org.sinytra.adapter.pipeline.processor.extract.ExtractMixinProcessor; +import org.sinytra.adapter.pipeline.processor.signature.SignatureProcessor; import org.sinytra.adapter.util.OrderedRegistry; public class Processors extends OrderedRegistry { @@ -21,14 +22,8 @@ private void registerDefaultProcessors() { add(new PropertyProcessor()); // Target method post processor add(new TargetMethodProcessor()); - // Mixin method parameters - add(new ParametersProcessor()); - // Parameter casts - add(new ParametersPostProcessor()); - // Mixin method return type - add(new ReturnTypeProcessor()); - // Static access modifier - add(new StaticAccessProcessor()); + // Mixin method signature + add(new SignatureProcessor()); // Side effects that must only happen once the mixin is otherwise patched add(new SplitCancellationProcessor()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java similarity index 94% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java index 83e1296f..12f60915 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpParamsProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.processor.wrapop; +package org.sinytra.adapter.pipeline.processor.signature; import com.mojang.logging.LogUtils; import org.objectweb.asm.Type; @@ -12,7 +12,7 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processor; +import org.sinytra.adapter.pipeline.processor.wrapop.WrapOpSurgeon; import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; @@ -22,11 +22,11 @@ import static org.sinytra.adapter.util.AdapterUtil.MIXINPATCH; -public class WrapOpParamsProcessor implements Processor { +public class OperationCallStep implements SignatureStep { private static final Logger LOGGER = LogUtils.getLogger(); @Override - public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { + public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { if (dirty.getParameters() == null) return TxResult.FAIL; // Find original.call(...) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java index 73deae9d..8e50f3cc 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersPostProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.processor; +package org.sinytra.adapter.pipeline.processor.signature; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -37,9 +37,9 @@ * } */ // TODO Add type safety check -public class ParametersPostProcessor implements Processor { +public class ParameterCastStep implements SignatureStep { @Override - public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { + public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { MethodParameters cleanParams = recipe.clean().getParameters(); MethodParameters dirtyParams = dirty.getParameters(); if (dirtyParams == null || !cleanParams.has(MethodParameters.ParamGroup.METHOD_PARAMS) || !dirtyParams.has(MethodParameters.ParamGroup.METHOD_PARAMS)) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java similarity index 93% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java index b519fd8e..5764f0c3 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ParametersProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.processor; +package org.sinytra.adapter.pipeline.processor.signature; import com.mojang.datafixers.util.Pair; import org.objectweb.asm.Type; @@ -16,9 +16,9 @@ import java.util.List; import java.util.Map; -public class ParametersProcessor implements Processor { +public class ParametersStep implements SignatureStep { @Override - public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { + public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { MethodParameters cleanParams = recipe.clean().getParameters(); MethodParameters dirtyParams = dirty.getParameters(); if (dirtyParams == null) return TxResult.FAIL; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java similarity index 93% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java index 6b56c9cf..f0bee6e0 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/ParameterUsageProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.processor.redirect; +package org.sinytra.adapter.pipeline.processor.signature; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; @@ -16,14 +16,13 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processor; import org.sinytra.adapter.util.AdapterUtil; import java.util.List; -public class ParameterUsageProcessor implements Processor { +public class RedirectedCallStep implements SignatureStep { @Override - public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { + public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { String cleanTarget = recipe.clean().getAtData().getTarget().orElse(null); if (cleanTarget == null) return TxResult.PASS; String dirtyTarget = dirty.getAtData().getTarget().orElse(null); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ReturnTypeStep.java similarity index 88% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ReturnTypeStep.java index 90644957..fbeb531b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ReturnTypeProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ReturnTypeStep.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.processor; +package org.sinytra.adapter.pipeline.processor.signature; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -15,9 +15,9 @@ import java.util.LinkedHashSet; import java.util.Set; -public class ReturnTypeProcessor implements Processor { +public class ReturnTypeStep implements SignatureStep { @Override - public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { + public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { Type cleanType = recipe.clean().getReturnType(); Type dirtyType = dirty.getReturnType(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/SignatureProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/SignatureProcessor.java new file mode 100644 index 00000000..4c2c3475 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/SignatureProcessor.java @@ -0,0 +1,52 @@ +package org.sinytra.adapter.pipeline.processor.signature; + +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ParamSource; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processor; + +import java.util.ArrayList; +import java.util.List; + +public class SignatureProcessor implements Processor { + private static final SignatureStep PARAMETERS = new ParametersStep(); + private static final SignatureStep OPERATION_CALLS = new OperationCallStep(); + private static final SignatureStep REDIRECTED_CALLS = new RedirectedCallStep(); + private static final SignatureStep CASTS = new ParameterCastStep(); + private static final SignatureStep RETURN_TYPE = new ReturnTypeStep(); + private static final SignatureStep STATIC_ACCESS = new StaticAccessStep(); + + @Override + public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { + TxResult result = TxResult.PASS; + for (SignatureStep step : steps(context.getMixinType().signature())) { + TxResult stepResult = context.audited(step, () -> step.apply(context, dirty, recipe)); + if (stepResult == TxResult.FAIL || stepResult == TxResult.FINALIZE) { + return stepResult; + } + if (stepResult == TxResult.SUCCESS) { + result = TxResult.SUCCESS; + } + } + return result; + } + + private List steps(HandlerSignature signature) { + List steps = new ArrayList<>(); + steps.add(PARAMETERS); + if (hasSource(signature, ParamSource.CALL_OPERANDS)) { + steps.add(hasSource(signature, ParamSource.OPERATION) ? OPERATION_CALLS : REDIRECTED_CALLS); + } + steps.add(CASTS); + steps.add(RETURN_TYPE); + steps.add(STATIC_ACCESS); + return steps; + } + + private static boolean hasSource(HandlerSignature signature, ParamSource source) { + return signature.slots().stream().anyMatch(slot -> slot.source() == source); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/SignatureStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/SignatureStep.java new file mode 100644 index 00000000..187d0b98 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/SignatureStep.java @@ -0,0 +1,10 @@ +package org.sinytra.adapter.pipeline.processor.signature; + +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +public interface SignatureStep { + TxResult apply(MixinContext context, Configuration dirty, Recipe recipe); +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java similarity index 94% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java index 33b8226d..40042f57 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/StaticAccessProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.processor; +package org.sinytra.adapter.pipeline.processor.signature; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -17,9 +17,9 @@ import java.util.List; import java.util.Objects; -public class StaticAccessProcessor implements Processor { +public class StaticAccessStep implements SignatureStep { @Override - public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { + public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { if (!recipe.clean().hasProperty(SpecialKeys.STATIC) || !dirty.hasProperty(SpecialKeys.STATIC)) return TxResult.PASS; From dde68071156dba136d48d74863cb030642e86cb8 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 21:13:15 +0200 Subject: [PATCH 12/44] Replace special keys with intent --- .../org/sinytra/adapter/model/Relocation.java | 11 ++++++++++ .../adapter/model/config/key/ControlKeys.java | 1 + .../adapter/model/config/key/IntentKeys.java | 21 ++++++++++++++++++ .../adapter/model/config/key/SpecialKeys.java | 22 ------------------- .../sinytra/adapter/pipeline/MixinParser.java | 3 +-- .../pipeline/injector/InjectMixin.java | 4 ++-- .../manual/MethodPatchBuilderImpl.java | 7 +++--- .../processor/SplitCancellationProcessor.java | 5 ++--- .../SplitMethodCancellationHelper.java | 2 +- .../extract/ExtractMixinProcessor.java | 13 +++++------ .../extract/ProxyExtractMixinSub.java | 6 ++--- .../redirect/DivertRedirectProcessor.java | 4 ++-- .../processor/signature/StaticAccessStep.java | 8 +++---- .../ComparingInjectionPointResolver.java | 5 +++-- .../injection/InjectionPointSubResolvers.java | 5 +++-- .../target/SplitTargetMethodSubResolver.java | 4 ++-- 16 files changed, 66 insertions(+), 55 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/model/Relocation.java create mode 100644 core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java delete mode 100644 core/src/main/java/org/sinytra/adapter/model/config/key/SpecialKeys.java rename core/src/main/java/org/sinytra/adapter/pipeline/{resolver/target => processor}/SplitMethodCancellationHelper.java (99%) diff --git a/core/src/main/java/org/sinytra/adapter/model/Relocation.java b/core/src/main/java/org/sinytra/adapter/model/Relocation.java new file mode 100644 index 00000000..875c5f49 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/model/Relocation.java @@ -0,0 +1,11 @@ +package org.sinytra.adapter.model; + +import org.objectweb.asm.tree.MethodInsnNode; + +public sealed interface Relocation { + record CalledFrom(MethodInsnNode call) implements Relocation { + } + + record ReceivesOrigin(int argIndex) implements Relocation { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/model/config/key/ControlKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/ControlKeys.java index 9877a051..d6e74db5 100644 --- a/core/src/main/java/org/sinytra/adapter/model/config/key/ControlKeys.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/key/ControlKeys.java @@ -17,6 +17,7 @@ public final class ControlKeys { // Method public static final PropertyKey PARAMETERS = PropertyKey.create("parameters"); public static final PropertyKey RETURN_TYPE = PropertyKey.create("return_type"); + public static final PropertyKey STATIC = PropertyKey.create("static"); // Control public static final PropertyKey DELETE = PropertyKey.create("delete"); diff --git a/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java new file mode 100644 index 00000000..13c1b879 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java @@ -0,0 +1,21 @@ +package org.sinytra.adapter.model.config.key; + +import org.objectweb.asm.commons.InstructionAdapter; +import org.sinytra.adapter.model.Relocation; +import org.sinytra.adapter.model.config.PropertyKey; + +import java.util.function.Consumer; + +public final class IntentKeys { + /// The mixin moves to the dirty target class + public static final PropertyKey RELOCATION = PropertyKey.create("relocation"); + + /// The target method was split, so cancelling in one of them must also apply to the rest + public static final PropertyKey PROPAGATE_CANCELLATION = PropertyKey.create("propagate_cancellation"); + + /// After each redirected call in the handler, run this code instead of the rest of the original statement + public static final PropertyKey> DIVERT_REDIRECT = PropertyKey.create("divert_redirect"); + + private IntentKeys() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/model/config/key/SpecialKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/SpecialKeys.java deleted file mode 100644 index dbbe9f0c..00000000 --- a/core/src/main/java/org/sinytra/adapter/model/config/key/SpecialKeys.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.sinytra.adapter.model.config.key; - -import org.objectweb.asm.commons.InstructionAdapter; -import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.model.config.PropertyKey; - -import java.util.function.Consumer; - -/** - * Mixin-type-specific Configuration Keys. Never serialized. - */ -public final class SpecialKeys { - // Hidden - public static final PropertyKey EXTRACT_TARGET = PropertyKey.create("_extract_target_minsn"); - public static final PropertyKey EXTRACT_ORIGIN_PARAM = PropertyKey.create("_extract_origin_param"); - public static final PropertyKey> REDIRECT_ADAPTER = PropertyKey.create("_redirect_adapter"); - public static final PropertyKey STATIC = PropertyKey.create("_static"); - public static final PropertyKey SPLIT_CANCELLATION = PropertyKey.create("_split_cancellation"); - - private SpecialKeys() { - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java b/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java index 275b1c8c..41f98922 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java @@ -17,7 +17,6 @@ import org.sinytra.adapter.model.config.PropertyContainerTemplate; import org.sinytra.adapter.model.config.key.ControlKeys; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.config.key.SpecialKeys; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.pipeline.injector.MixinType; import org.sinytra.adapter.pipeline.injector.MixinTypes; @@ -67,7 +66,7 @@ public static MixinMethodHandle parseMixin(ClassTarget cls, MethodNode method, R String targetClass = Objects.requireNonNullElseGet(cls.getSingle(), () -> cls.getTypes().getFirst()).getInternalName(); properties.setProperty(ControlKeys.TARGET_CLASS, targetClass); properties.setProperty(ControlKeys.RETURN_TYPE, Type.getReturnType(method.desc)); - properties.setProperty(SpecialKeys.STATIC, MethodAnalyzer.isStatic(method)); + properties.setProperty(ControlKeys.STATIC, MethodAnalyzer.isStatic(method)); resolveTargetDescriptor(properties, environment); return new MixinMethodHandle(mixinType, method, handle, properties); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index c4f4f89d..a8d5d354 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -7,8 +7,8 @@ import org.sinytra.adapter.model.config.ConfigurationTemplates; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.ControlKeys; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.config.key.SpecialKeys; import org.sinytra.adapter.model.param.HandlerSignature; import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; import org.sinytra.adapter.model.param.MethodParameters; @@ -85,7 +85,7 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo } // Process static modifier - if (!clean.getProperty(SpecialKeys.STATIC).orElse(false) && dirty.getProperty(SpecialKeys.STATIC).orElse(false)) { + if (!clean.getProperty(ControlKeys.STATIC).orElse(false) && dirty.getProperty(ControlKeys.STATIC).orElse(false)) { List types = context.targetTypes(); if (types.size() != 1) { return TxResult.FAIL; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java index 5026462f..a35b48ef 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java @@ -9,8 +9,9 @@ import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.ControlKeys; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.config.key.SpecialKeys; import org.sinytra.adapter.model.constant.MixinAnnotationConstants; import org.sinytra.adapter.model.param.MethodParameters; @@ -148,7 +149,7 @@ public MethodPatchBuilder modifyParams(UnaryOperator op) { @Override public MethodPatchBuilder modifyStatic(boolean isStatic) { - this.config.setProperty(SpecialKeys.STATIC, isStatic); + this.config.setProperty(ControlKeys.STATIC, isStatic); return this; } @@ -166,7 +167,7 @@ public MethodPatchBuilder disable() { @Override public MethodPatchBuilder divertRedirector(Consumer patcher) { - this.config.setProperty(SpecialKeys.REDIRECT_ADAPTER, patcher); + this.config.setProperty(IntentKeys.DIVERT_REDIRECT, patcher); return this; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java index 97d7b181..1e676bb4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java @@ -2,16 +2,15 @@ import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.target.SplitMethodCancellationHelper; public class SplitCancellationProcessor implements Processor { @Override public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { - if (!dirty.getProperty(SpecialKeys.SPLIT_CANCELLATION).orElse(false)) { + if (!dirty.getProperty(IntentKeys.PROPAGATE_CANCELLATION).orElse(false)) { return TxResult.PASS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitMethodCancellationHelper.java similarity index 99% rename from core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitMethodCancellationHelper.java index 319e63ea..9b5e166b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitMethodCancellationHelper.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitMethodCancellationHelper.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.resolver.target; +package org.sinytra.adapter.pipeline.processor; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Label; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java index 31938a05..bf6c4eca 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java @@ -14,10 +14,11 @@ import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.edit.param.TransformParameters; +import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.config.key.SpecialKeys; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; @@ -39,13 +40,11 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe if (recipe.clean().getTargetClass().equals(dirty.getTargetClass())) return TxResult.PASS; PatchResult result = apply(dirty.getTargetClass(), context, recipe); - if (result == PatchResult.PASS && dirty.hasProperty(SpecialKeys.EXTRACT_TARGET)) { - MethodInsnNode minsn = dirty.getProperty(SpecialKeys.EXTRACT_TARGET).orElseThrow(); - result = MirrorableExtractMixin.apply(context, recipe, dirty.getTargetClass(), minsn); + Relocation relocation = dirty.getProperty(IntentKeys.RELOCATION).orElse(null); + if (result == PatchResult.PASS && relocation instanceof Relocation.CalledFrom(MethodInsnNode call)) { + result = MirrorableExtractMixin.apply(context, recipe, dirty.getTargetClass(), call); } - - // Defer to ProxyExtractMixinSub - if (result == PatchResult.PASS && dirty.hasProperty(SpecialKeys.EXTRACT_ORIGIN_PARAM)) { + if (result == PatchResult.PASS && relocation instanceof Relocation.ReceivesOrigin) { return ProxyExtractMixinSub.INSTANCE.process(context, dirty, recipe); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java index 43d431fc..b5654ec2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java @@ -3,9 +3,10 @@ import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; @@ -29,7 +30,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe if (recipe.clean().getTargetClass().equals(dirty.getTargetClass())) return TxResult.PASS; - if (!dirty.hasProperty(SpecialKeys.EXTRACT_ORIGIN_PARAM) || context.locals().capturesLocals()) + if (!(dirty.getProperty(IntentKeys.RELOCATION).orElse(null) instanceof Relocation.ReceivesOrigin(int originParamIndex)) || context.locals().capturesLocals()) return TxResult.FAIL; TargetPair dirtyTarget = recipe.getNewCleanTarget(); @@ -37,7 +38,6 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe MethodNode methodNode = context.methodNode(); - int originParamIndex = dirty.getProperty(SpecialKeys.EXTRACT_ORIGIN_PARAM).orElseThrow(); if (dirty.getTargetMethod() == null || dirty.getTargetMethod().desc() == null) return TxResult.FAIL; List destParams = Parameters.getParameterTypes(dirty.getTargetMethod().desc()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java index b4c3f7e9..0d0f6179 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java @@ -7,7 +7,7 @@ import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -20,7 +20,7 @@ public class DivertRedirectProcessor implements Processor { @Override public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { - Consumer patcher = dirty.getProperty(SpecialKeys.REDIRECT_ADAPTER).orElse(null); + Consumer patcher = dirty.getProperty(IntentKeys.DIVERT_REDIRECT).orElse(null); if (patcher == null) return TxResult.PASS; if (!recipe.hasInjectionPointValue(AT_VAL_INVOKE)) return TxResult.PASS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java index 40042f57..d6ff9095 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java @@ -7,7 +7,7 @@ import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.config.key.ControlKeys; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -20,11 +20,11 @@ public class StaticAccessStep implements SignatureStep { @Override public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { - if (!recipe.clean().hasProperty(SpecialKeys.STATIC) || !dirty.hasProperty(SpecialKeys.STATIC)) + if (!recipe.clean().hasProperty(ControlKeys.STATIC) || !dirty.hasProperty(ControlKeys.STATIC)) return TxResult.PASS; - boolean cleanStatic = recipe.clean().getProperty(SpecialKeys.STATIC).orElseThrow(); - boolean dirtyStatic = dirty.getProperty(SpecialKeys.STATIC).orElseThrow(); + boolean cleanStatic = recipe.clean().getProperty(ControlKeys.STATIC).orElseThrow(); + boolean dirtyStatic = dirty.getProperty(ControlKeys.STATIC).orElseThrow(); if (cleanStatic == dirtyStatic) return TxResult.PASS; MethodNode method = context.methodNode(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java index 50b9451e..551e11be 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java @@ -9,14 +9,15 @@ import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.ann.ConstantData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.config.key.SpecialKeys; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.model.param.MethodParameters; @@ -87,7 +88,7 @@ private static Configuration attemptExtractMixin(MethodInsnNode minsn, MixinCont .setTargetClass(target.classNode().name) .setTargetMethod(minsn) .setAtData(clean.getAtData().withOrdinal(null)) // TODO Find new ordinal - .setProperty(SpecialKeys.EXTRACT_TARGET, minsn); + .setProperty(IntentKeys.RELOCATION, new Relocation.CalledFrom(minsn)); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java index 627218a0..7676819d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java @@ -11,11 +11,12 @@ import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodInsnMatcher; import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.config.key.SpecialKeys; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -87,7 +88,7 @@ public class InjectionPointSubResolvers { .setTargetClass(minsn.owner) .setTargetMethod(minsn) .inheritAtData() - .setProperty(SpecialKeys.EXTRACT_ORIGIN_PARAM, selfParamIndex) + .setProperty(IntentKeys.RELOCATION, new Relocation.ReceivesOrigin(selfParamIndex)) .inheritProperyIfAbsent(MixinKeys.INDEX) .inheritProperyIfAbsent(MixinKeys.ORDINAL); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java index 3ba3f751..d0cc521d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java @@ -12,7 +12,7 @@ import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; -import org.sinytra.adapter.model.config.key.SpecialKeys; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.resolver.SubResolver; @@ -40,7 +40,7 @@ public Configuration resolve(MixinContext context, Recipe recipe) { MutableConfiguration config = MutableConfiguration.create() .setTargetMethod(method); if (recipe.clean().isCancellable()) { - config.setProperty(SpecialKeys.SPLIT_CANCELLATION, true); + config.setProperty(IntentKeys.PROPAGATE_CANCELLATION, true); } return config; } From 95dfdc2d50593911ad08a00d08b7bc53122426ae Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 22:13:14 +0200 Subject: [PATCH 13/44] Sanity check patched methods --- .../org/sinytra/adapter/api/AuditTrail.java | 4 + .../adapter/pipeline/HandlerValidator.java | 125 ++++++++++++++++++ .../sinytra/adapter/pipeline/PatcherImpl.java | 27 ++-- .../pipeline/PipelineMethodTransformer.java | 11 +- .../pipeline/audit/AuditTrailImpl.java | 6 + .../adapter/pipeline/ctx/MixinContext.java | 14 ++ 6 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java diff --git a/core/src/main/java/org/sinytra/adapter/api/AuditTrail.java b/core/src/main/java/org/sinytra/adapter/api/AuditTrail.java index 75f7dd4b..da49b724 100644 --- a/core/src/main/java/org/sinytra/adapter/api/AuditTrail.java +++ b/core/src/main/java/org/sinytra/adapter/api/AuditTrail.java @@ -27,6 +27,10 @@ static AuditTrail create() { void recordResult(MixinContext context, Configuration configuration, Match match); + default void recordRejection(MixinContext context, Configuration configuration) { + recordResult(context, configuration, Match.NONE); + } + void processGroups(ClassNode classNode, MixinGroups groups); String getCompleteReport(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java b/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java new file mode 100644 index 00000000..99d7a853 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java @@ -0,0 +1,125 @@ +package org.sinytra.adapter.pipeline; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.sinytra.adapter.analysis.injection.InjectionQuery; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.ControlKeys; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public final class HandlerValidator { + @Nullable + public static String validate(MixinContext context, Configuration dirty, Recipe recipe) { + if (dirty.getProperty(ControlKeys.DELETE).orElse(false) + || !Objects.equals(recipe.clean().getTargetClass(), dirty.getTargetClass()) + || !Objects.equals(recipe.clean().getProperty(ControlKeys.MIXIN_TYPE), dirty.getProperty(ControlKeys.MIXIN_TYPE)) + || dirty.getTargetMethod() == null) { + return null; + } + + TargetPair target = context.dirtyMethods().resolve(dirty.getTargetMethod()); + if (target == null) { + return "dirty target method %s not found".formatted(dirty.getTargetMethod().asDescriptor()); + } + + AtData at = dirty.getAtData(); + List insns; + if (at != null) { + insns = context.injections().find(target, InjectionQuery.of(at)); + } else if (dirty.hasProperty(MixinKeys.TARGET_CONSTANT)) { + insns = context.injections().find(target, InjectionQuery.constant()); + } else { + return null; + } + if (insns.isEmpty()) { + return "the injection point selects nothing in %s".formatted(target.methodNode().name + target.methodNode().desc); + } + + return validateSignature(context, context.getMixinType().signature(), dirty, target, insns.getFirst()); + } + + @Nullable + private static String validateSignature(MixinContext context, HandlerSignature signature, Configuration dirty, TargetPair target, AbstractInsnNode insn) { + MethodParameters params = dirty.getParameters(); + if (params == null) { + return null; + } + + Type[] handlerArgs = Type.getArgumentTypes(context.methodNode().desc); + if (!Arrays.asList(handlerArgs).equals(params.mergeTypes())) { + return "handler parameters %s don't match the expected %s".formatted(Arrays.toString(handlerArgs), params.mergeTypes()); + } + Type handlerReturn = Type.getReturnType(context.methodNode().desc); + if (!handlerReturn.equals(dirty.getReturnType())) { + return "handler returns %s instead of the expected %s".formatted(handlerReturn, dirty.getReturnType()); + } + + MethodInsnNode call = insn instanceof MethodInsnNode minsn ? minsn : null; + List targetArgs = Parameters.getParameterTypes(target.methodNode().desc); + Type targetReturn = Type.getReturnType(target.methodNode().desc); + + Type expectedReturn = switch (signature.returns()) { + case VOID -> Type.VOID_TYPE; + case CALL_RETURN -> call != null ? Type.getReturnType(call.desc) : null; + case TARGET_RETURN -> targetReturn; + case CALL_ARG, VARIABLE -> null; + }; + if (expectedReturn != null && !expectedReturn.equals(handlerReturn)) { + return "handler returns %s but the injector expects %s".formatted(handlerReturn, expectedReturn); + } + + for (HandlerSignature.Slot slot : signature.slots()) { + if (!params.has(slot.group())) continue; + List group = params.get(slot.group()); + String problem = switch (slot.source()) { + case CALL_OPERANDS -> call == null ? null : checkTypes("call operand", group, Parameters.getCallOperandTypes( + call.getOpcode() == Opcodes.INVOKESTATIC ? null : Type.getObjectType(call.owner), call.desc)); + case OPERATION -> checkTypes("operation", group, List.of(TypeConstants.OPERATION_TYPE)); + case CALLBACK_INFO -> group.isEmpty() ? null + : checkTypes("callback info", group, List.of(targetReturn.equals(Type.VOID_TYPE) ? TypeConstants.CI_TYPE : TypeConstants.CIR_TYPE)); + case TARGET_ARGS -> group.isEmpty() ? null : checkTypes("target argument", group, targetArgs); + case CAPTURED_TARGET_ARGS -> group.size() > targetArgs.size() ? "handler captures %d target arguments, but the target has %d".formatted(group.size(), targetArgs.size()) + : checkTypes("captured target argument", group, targetArgs.subList(0, group.size())); + case VALUE -> expectedReturn == null ? null : checkTypes("value", group, List.of(expectedReturn)); + case KEEP -> null; + }; + if (problem != null) { + return problem; + } + } + return null; + } + + @Nullable + private static String checkTypes(String kind, List group, List expected) { + if (group.size() != expected.size()) { + return "handler has %d %s parameters, but the injector expects %d (%s)".formatted(group.size(), kind, expected.size(), expected); + } + for (int i = 0; i < group.size(); i++) { + Parameter param = group.get(i); + if (!param.type().equals(expected.get(i)) && !param.hasAnnotation(MixinAnnotations.COERCE)) { + return "%s parameter %d is %s, but the injector expects %s".formatted(kind, i, param.type(), expected.get(i)); + } + } + return null; + } + + private HandlerValidator() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java index 4de90b73..e8312260 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java @@ -90,20 +90,31 @@ private PatchResult processMixin(ClassNode classNode, ClassTarget classTarget, P this.environment.auditTrail().prepareMethod(mixinContext); int checkpoint = patchContext.checkpoint(); + PatchResult result; try { - return applyPatches(mixinContext, configuration); + result = applyPatches(mixinContext, configuration); } catch (Exception e) { LOGGER.error(MIXINPATCH, "Error patching mixin {}, reverting changes", mixinId, e); - - int index = classNode.methods.indexOf(mixinContext.methodNode()); - if (index != -1) { - classNode.methods.set(index, mixinContext.unmodifiedMethodNode()); - } - patchContext.rollback(checkpoint); - + rollback(classNode, patchContext, checkpoint, mixinContext); this.environment.auditTrail().recordResult(mixinContext, configuration, AuditTrail.Match.NONE); return PatchResult.PASS; } + + if (mixinContext.getRejection() != null) { + LOGGER.debug(MIXINPATCH, "Rejected patch of mixin {}, reverting changes: {}", mixinId, mixinContext.getRejection()); + rollback(classNode, patchContext, checkpoint, mixinContext); + this.environment.auditTrail().recordRejection(mixinContext, configuration); + return PatchResult.PASS; + } + return result; + } + + private static void rollback(ClassNode classNode, PatchContextImpl patchContext, int checkpoint, MixinContext mixinContext) { + int index = classNode.methods.indexOf(mixinContext.methodNode()); + if (index != -1) { + classNode.methods.set(index, mixinContext.unmodifiedMethodNode()); + } + patchContext.rollback(checkpoint); } private PatchResult applyPatches(MixinContext mixinContext, MutableConfiguration configuration) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java index fcb7d3be..fc394b29 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java @@ -132,7 +132,16 @@ private PatchResult execute(MixinContext context, Configuration config) { break; } if (res == TxResult.FAIL) { - LOGGER.debug(MIXINPATCH, "Skipping mixin {} due to failed PROCESSOR {}", mixinId, processor.getClass().getSimpleName()); + context.reject(processor, "processor %s failed".formatted(processor.getClass().getSimpleName())); + return PatchResult.PASS; + } + } + + // 5. Check that the patched mixin will apply + if (!this.patchesOnly && !this.patchResolver.matches(config)) { + String problem = HandlerValidator.validate(context, recipe.dirty(), recipe); + if (problem != null) { + context.reject(this, "validation failed: " + problem); return PatchResult.PASS; } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/audit/AuditTrailImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/audit/AuditTrailImpl.java index 6190961c..3e2a33b4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/audit/AuditTrailImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/audit/AuditTrailImpl.java @@ -71,6 +71,12 @@ public void recordResult(MixinContext context, Configuration configuration, Matc }); } + @Override + public void recordRejection(MixinContext context, Configuration configuration) { + boolean isNotRequired = configuration.getProperty(MixinKeys.REQUIRE).map(i -> i == 0).orElse(false); + this.candidates.put(new Candidate(context.classNode(), context.methodNode()), isNotRequired ? Match.IGNORED : Match.NONE); + } + public String getCompleteReport() { StringBuilder builder = new StringBuilder(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java index ff07270d..e24374c8 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java @@ -41,6 +41,8 @@ public class MixinContext implements RefMapper, Auditor { private final MethodNode methodNode; private final MethodNode originalMethodNode; private final Set flags; + @Nullable + private String rejection; private final AnnotationHandle methodAnnotation; private final AnnotationHandle injectionPointAnnotation; @@ -195,6 +197,18 @@ public T audited(Object actor, Supplier action) { } } + public void reject(Object actor, String reason) { + if (this.rejection == null) { + this.rejection = reason; + } + recordAudit(actor, "Rejected: %s", reason); + } + + @Nullable + public String getRejection() { + return this.rejection; + } + public void recordCtxAudit(String message, Object... args) { if (this.auditContext.isEmpty()) { throw new RuntimeException("Missing audit context object"); From d0a85c1d509a931eaae8972f733a4308e67da779 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 21 Oct 2026 23:28:23 +0200 Subject: [PATCH 14/44] Method alignment diff --- .../analysis/compare/MethodAlignment.java | 350 ++++++++++++++++++ .../adapter/pipeline/locate/Candidate.java | 13 + .../adapter/pipeline/locate/Describer.java | 36 ++ .../pipeline/locate/LocatingSubResolver.java | 88 +++++ .../adapter/pipeline/locate/Locator.java | 12 + .../pipeline/locate/SameMethodLocator.java | 38 ++ .../injection/InjectionPointResolver.java | 5 +- .../injection/InjectionPointSubResolvers.java | 83 ----- .../OverloadedInjectionPointSubResolver.java | 76 ---- .../special/InjectorOrdinalResolver.java | 47 +-- .../adapter/util/WeighedDisambiguation.java | 59 --- 11 files changed, 542 insertions(+), 265 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/Candidate.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/Locator.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java delete mode 100644 core/src/main/java/org/sinytra/adapter/util/WeighedDisambiguation.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java new file mode 100644 index 00000000..40fabde7 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java @@ -0,0 +1,350 @@ +package org.sinytra.adapter.analysis.compare; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Handle; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.tree.analysis.SourceValue; +import org.sinytra.adapter.analysis.flow.DataFlow; + +import java.util.*; +import java.util.function.IntFunction; + +public final class MethodAlignment { + private static final int MAX_LCS_CELLS = 250000; + + public enum Confidence { + EXACT, + FINGERPRINT, + POSITIONAL + } + + public record Match(AbstractInsnNode dirty, Confidence confidence) { + } + + public record Hunk(List dirtyInsns, @Nullable AbstractInsnNode cleanBefore, @Nullable AbstractInsnNode cleanAfter) { + } + + private record Fingerprint(String insn, List producers) { + } + + private record Pairing(int dirty, Confidence confidence, boolean inOrder) { + } + + private final List clean; + private final List dirty; + private final Map cleanIndices = new HashMap<>(); + private final Fingerprint[] cleanPrints; + private final Fingerprint[] dirtyPrints; + private final Map pairings = new HashMap<>(); + private final Map reverse = new HashMap<>(); + + private MethodAlignment(MethodNode cleanMethod, MethodNode dirtyMethod) { + this.clean = realInsns(cleanMethod); + this.dirty = realInsns(dirtyMethod); + for (int i = 0; i < this.clean.size(); i++) { + this.cleanIndices.put(this.clean.get(i), i); + } + this.cleanPrints = fingerprints(cleanMethod, this.clean); + this.dirtyPrints = fingerprints(dirtyMethod, this.dirty); + } + + public static MethodAlignment align(MethodNode cleanMethod, MethodNode dirtyMethod) { + MethodAlignment alignment = new MethodAlignment(cleanMethod, dirtyMethod); + alignment.alignInOrder(); + alignment.alignMoved(); + return alignment; + } + + public List find(AbstractInsnNode cleanInsn) { + Integer index = indexOf(cleanInsn); + if (index == null) { + return List.of(); + } + Pairing pairing = this.pairings.get(index); + if (pairing != null) { + return List.of(new Match(this.dirty.get(pairing.dirty()), pairing.confidence())); + } + + int[] range = dirtyRangeAround(index); + List matches = new ArrayList<>(); + for (int i = range[0]; i < range[1]; i++) { + if (!this.reverse.containsKey(i) && this.dirtyPrints[i].insn().equals(this.cleanPrints[index].insn())) { + matches.add(new Match(this.dirty.get(i), Confidence.POSITIONAL)); + } + } + return matches; + } + + public List findDirtyAround(AbstractInsnNode cleanInsn) { + Integer index = indexOf(cleanInsn); + if (index == null || this.pairings.containsKey(index)) { + return List.of(); + } + int[] range = dirtyRangeAround(index); + List insns = new ArrayList<>(); + for (int i = range[0]; i < range[1]; i++) { + if (!this.reverse.containsKey(i)) { + insns.add(this.dirty.get(i)); + } + } + return insns; + } + + private int[] dirtyRangeAround(int index) { + int from = 0; + for (int i = index - 1; i >= 0; i--) { + Pairing before = this.pairings.get(i); + if (before != null && before.inOrder()) { + from = before.dirty() + 1; + break; + } + } + int to = this.dirty.size(); + for (int i = index + 1; i < this.clean.size(); i++) { + Pairing after = this.pairings.get(i); + if (after != null && after.inOrder()) { + to = after.dirty(); + break; + } + } + return new int[]{from, to}; + } + + @Nullable + public AbstractInsnNode resolve(AbstractInsnNode cleanInsn) { + List matches = find(cleanInsn); + return matches.size() == 1 && matches.getFirst().confidence() != Confidence.POSITIONAL ? matches.getFirst().dirty() : null; + } + + public List findHunks() { + List hunks = new ArrayList<>(); + int start = -1; + for (int i = 0; i <= this.dirty.size(); i++) { + boolean unmatched = i < this.dirty.size() && !this.reverse.containsKey(i); + if (unmatched && start == -1) { + start = i; + } else if (!unmatched && start != -1) { + hunks.add(new Hunk(List.copyOf(this.dirty.subList(start, i)), cleanPartner(start - 1), cleanPartner(i))); + start = -1; + } + } + return hunks; + } + + @Nullable + private AbstractInsnNode cleanPartner(int dirtyIndex) { + Integer cleanIndex = dirtyIndex >= 0 && dirtyIndex < this.dirty.size() ? this.reverse.get(dirtyIndex) : null; + return cleanIndex != null ? this.clean.get(cleanIndex) : null; + } + + @Nullable + private Integer indexOf(AbstractInsnNode insn) { + AbstractInsnNode current = insn; + while (current != null && isPseudo(current)) { + current = current.getNext(); + } + return current != null ? this.cleanIndices.get(current) : null; + } + + private void alignInOrder() { + diff(0, this.clean.size(), 0, this.dirty.size(), i -> this.cleanPrints[i], i -> this.dirtyPrints[i], Confidence.EXACT); + + List matched = new ArrayList<>(this.pairings.keySet()); + Collections.sort(matched); + int prevClean = -1; + int prevDirty = -1; + for (int cleanIndex : matched) { + int dirtyIndex = this.pairings.get(cleanIndex).dirty(); + diff(prevClean + 1, cleanIndex, prevDirty + 1, dirtyIndex, i -> this.cleanPrints[i].insn(), i -> this.dirtyPrints[i].insn(), Confidence.FINGERPRINT); + prevClean = cleanIndex; + prevDirty = dirtyIndex; + } + diff(prevClean + 1, this.clean.size(), prevDirty + 1, this.dirty.size(), i -> this.cleanPrints[i].insn(), i -> this.dirtyPrints[i].insn(), Confidence.FINGERPRINT); + } + + private void alignMoved() { + Map> cleanUnmatched = new HashMap<>(); + for (int i = 0; i < this.clean.size(); i++) { + if (!this.pairings.containsKey(i)) { + cleanUnmatched.computeIfAbsent(this.cleanPrints[i], k -> new ArrayList<>()).add(i); + } + } + Map> dirtyUnmatched = new HashMap<>(); + for (int i = 0; i < this.dirty.size(); i++) { + if (!this.reverse.containsKey(i)) { + dirtyUnmatched.computeIfAbsent(this.dirtyPrints[i], k -> new ArrayList<>()).add(i); + } + } + cleanUnmatched.forEach((print, cleanIndices) -> { + List dirtyIndices = dirtyUnmatched.get(print); + if (cleanIndices.size() == 1 && dirtyIndices != null && dirtyIndices.size() == 1) { + pair(cleanIndices.getFirst(), dirtyIndices.getFirst(), Confidence.FINGERPRINT, false); + } + }); + } + + private void diff(int cleanFrom, int cleanTo, int dirtyFrom, int dirtyTo, IntFunction cleanKey, IntFunction dirtyKey, Confidence confidence) { + while (cleanFrom < cleanTo && dirtyFrom < dirtyTo && cleanKey.apply(cleanFrom).equals(dirtyKey.apply(dirtyFrom))) { + pair(cleanFrom++, dirtyFrom++, confidence, true); + } + while (cleanFrom < cleanTo && dirtyFrom < dirtyTo && cleanKey.apply(cleanTo - 1).equals(dirtyKey.apply(dirtyTo - 1))) { + pair(--cleanTo, --dirtyTo, confidence, true); + } + if (cleanFrom >= cleanTo || dirtyFrom >= dirtyTo) { + return; + } + + Map counts = new HashMap<>(); + for (int i = cleanFrom; i < cleanTo; i++) { + int[] entry = counts.computeIfAbsent(cleanKey.apply(i), k -> new int[]{0, 0, -1, -1}); + entry[0]++; + entry[2] = i; + } + for (int i = dirtyFrom; i < dirtyTo; i++) { + int[] entry = counts.get(dirtyKey.apply(i)); + if (entry != null) { + entry[1]++; + entry[3] = i; + } + } + List unique = new ArrayList<>(); + for (int[] entry : counts.values()) { + if (entry[0] == 1 && entry[1] == 1) { + unique.add(new int[]{entry[2], entry[3]}); + } + } + if (unique.isEmpty()) { + lcs(cleanFrom, cleanTo, dirtyFrom, dirtyTo, cleanKey, dirtyKey, confidence); + return; + } + unique.sort(Comparator.comparingInt(pair -> pair[0])); + List anchors = longestIncreasing(unique); + + int prevClean = cleanFrom - 1; + int prevDirty = dirtyFrom - 1; + for (int[] anchor : anchors) { + diff(prevClean + 1, anchor[0], prevDirty + 1, anchor[1], cleanKey, dirtyKey, confidence); + pair(anchor[0], anchor[1], confidence, true); + prevClean = anchor[0]; + prevDirty = anchor[1]; + } + diff(prevClean + 1, cleanTo, prevDirty + 1, dirtyTo, cleanKey, dirtyKey, confidence); + } + + private void lcs(int cleanFrom, int cleanTo, int dirtyFrom, int dirtyTo, IntFunction cleanKey, IntFunction dirtyKey, Confidence confidence) { + int n = cleanTo - cleanFrom; + int m = dirtyTo - dirtyFrom; + if ((long) n * m > MAX_LCS_CELLS) { + return; + } + int[][] table = new int[n + 1][m + 1]; + for (int i = n - 1; i >= 0; i--) { + for (int j = m - 1; j >= 0; j--) { + table[i][j] = cleanKey.apply(cleanFrom + i).equals(dirtyKey.apply(dirtyFrom + j)) + ? table[i + 1][j + 1] + 1 + : Math.max(table[i + 1][j], table[i][j + 1]); + } + } + int i = 0; + int j = 0; + while (i < n && j < m) { + if (cleanKey.apply(cleanFrom + i).equals(dirtyKey.apply(dirtyFrom + j))) { + pair(cleanFrom + i++, dirtyFrom + j++, confidence, true); + } else if (table[i + 1][j] >= table[i][j + 1]) { + i++; + } else { + j++; + } + } + } + + private static List longestIncreasing(List pairs) { + int[] tails = new int[pairs.size()]; + int[] previous = new int[pairs.size()]; + int length = 0; + for (int i = 0; i < pairs.size(); i++) { + int value = pairs.get(i)[1]; + int lo = 0; + int hi = length; + while (lo < hi) { + int mid = (lo + hi) >>> 1; + if (pairs.get(tails[mid])[1] < value) lo = mid + 1; + else hi = mid; + } + previous[i] = lo > 0 ? tails[lo - 1] : -1; + tails[lo] = i; + if (lo == length) length++; + } + List result = new ArrayList<>(); + for (int i = length > 0 ? tails[length - 1] : -1; i != -1; i = previous[i]) { + result.add(pairs.get(i)); + } + return result.reversed(); + } + + private void pair(int cleanIndex, int dirtyIndex, Confidence confidence, boolean inOrder) { + if (this.pairings.containsKey(cleanIndex) || this.reverse.containsKey(dirtyIndex)) { + return; + } + Confidence actual = confidence == Confidence.FINGERPRINT && inOrder && this.cleanPrints[cleanIndex].equals(this.dirtyPrints[dirtyIndex]) + ? Confidence.EXACT : confidence; + this.pairings.put(cleanIndex, new Pairing(dirtyIndex, actual, inOrder)); + this.reverse.put(dirtyIndex, cleanIndex); + } + + private static List realInsns(MethodNode method) { + List insns = new ArrayList<>(); + for (AbstractInsnNode insn : method.instructions) { + if (!isPseudo(insn)) { + insns.add(insn); + } + } + return insns; + } + + private static boolean isPseudo(AbstractInsnNode insn) { + return insn instanceof LabelNode || insn instanceof LineNumberNode || insn instanceof FrameNode; + } + + private static Fingerprint[] fingerprints(MethodNode method, List insns) { + DataFlow flow = DataFlow.of(method); + Fingerprint[] prints = new Fingerprint[insns.size()]; + boolean analyzable = true; + for (int i = 0; i < insns.size(); i++) { + AbstractInsnNode insn = insns.get(i); + List producers = List.of(); + if (analyzable) { + try { + producers = flow.operands(insn).stream().map(MethodAlignment::describeProducers).toList(); + } catch (RuntimeException e) { + analyzable = false; + } + } + prints[i] = new Fingerprint(describe(insn), producers); + } + return prints; + } + + private static String describeProducers(SourceValue value) { + return value.insns.size() == 1 ? describe(value.insns.iterator().next()) : "*"; + } + + private static String describe(AbstractInsnNode insn) { + int op = insn.getOpcode(); + return switch (insn) { + case MethodInsnNode m -> op + " " + m.owner + "." + m.name + m.desc; + case FieldInsnNode f -> op + " " + f.owner + "." + f.name + ":" + f.desc; + case TypeInsnNode t -> op + " " + t.desc; + case LdcInsnNode l -> "LDC " + l.cst.getClass().getSimpleName() + " " + l.cst; + case IntInsnNode n -> op + " " + n.operand; + case IincInsnNode n -> "IINC " + n.incr; + case MultiANewArrayInsnNode n -> op + " " + n.desc + " " + n.dims; + case TableSwitchInsnNode n -> op + " " + n.min + ".." + n.max; + case LookupSwitchInsnNode n -> op + " " + n.keys; + case InvokeDynamicInsnNode n -> op + " " + n.name + n.desc + " " + n.bsm.getName() + " " + Arrays.stream(n.bsmArgs) + .map(arg -> arg instanceof Handle handle ? handle.getDesc() : String.valueOf(arg)) + .toList(); + default -> String.valueOf(op); + }; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Candidate.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Candidate.java new file mode 100644 index 00000000..29331abb --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Candidate.java @@ -0,0 +1,13 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.model.Relocation; +import org.sinytra.adapter.model.TargetPair; + +public record Candidate(TargetPair target, AbstractInsnNode insn, MethodAlignment.Confidence confidence, @Nullable Relocation relocation) { + public Candidate(TargetPair target, AbstractInsnNode insn, MethodAlignment.Confidence confidence) { + this(target, insn, confidence, null); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java new file mode 100644 index 00000000..d9a2a414 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -0,0 +1,36 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.sinytra.adapter.analysis.injection.InjectionQuery; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.List; + +public final class Describer { + @Nullable + public static Configuration describe(MixinContext context, Recipe recipe, Candidate candidate) { + AtData cleanAt = recipe.clean().getAtData(); + if (cleanAt == null || candidate.target() != recipe.getDirtyTarget()) { + return null; + } + + AtData at = candidate.insn() instanceof MethodInsnNode call && cleanAt.getTarget().isPresent() ? cleanAt.withTarget(call) : cleanAt; + List selected = context.injections().find(candidate.target(), InjectionQuery.of(at.withOrdinal(null))); + int ordinal = selected.indexOf(candidate.insn()); + if (ordinal == -1) { + return null; + } + + AtData described = selected.size() == 1 || cleanAt.getOrdinal().isEmpty() ? at.withOrdinal(null) : at.withOrdinal(ordinal); + return MutableConfiguration.create().setAtData(described); + } + + private Describer() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java new file mode 100644 index 00000000..c3a70aa5 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java @@ -0,0 +1,88 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.SubResolver; + +import java.util.List; +import java.util.Objects; + +public final class LocatingSubResolver implements SubResolver { + private final List locators; + + public LocatingSubResolver(List locators) { + this.locators = locators; + } + + @Nullable + @Override + public Configuration resolve(MixinContext context, Recipe recipe) { + Targets targets = recipe.targets(); + if (targets == null) { + return null; + } + + List cleanInsns = context.injections().find(targets.clean()); + if (cleanInsns.isEmpty()) { + return null; + } + + Configuration shared = null; + for (AbstractInsnNode cleanInsn : cleanInsns) { + Configuration config = locate(context, recipe, targets, cleanInsn); + if (config == null || shared != null && !Objects.equals(config.getAtData(), shared.getAtData())) { + return null; + } + shared = config; + } + return shared; + } + + @Nullable + private Configuration locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { + for (Locator locator : this.locators) { + List candidates = locator.locate(context, recipe, targets, cleanInsn); + if (candidates.isEmpty()) { + continue; + } + Candidate candidate = pick(candidates); + Configuration config = candidate != null ? Describer.describe(context, recipe, candidate) : describeAll(context, recipe, candidates); + if (config != null) { + context.recordCtxAudit("Located injection point with %s (%s)", locator.getClass().getSimpleName(), + candidate != null ? candidate.confidence() : candidates.size() + " candidates with one @At"); + } + // A locator that finds several places can't tell which one is right, so don't guess with later locators either + return config; + } + return null; + } + + @Nullable + private static Configuration describeAll(MixinContext context, Recipe recipe, List candidates) { + Configuration shared = null; + for (Candidate candidate : candidates) { + Configuration config = Describer.describe(context, recipe, candidate); + if (config == null || shared != null && !Objects.equals(config.getAtData(), shared.getAtData())) { + return null; + } + shared = config; + } + return shared; + } + + @Nullable + private static Candidate pick(List candidates) { + if (candidates.size() == 1) { + return candidates.getFirst(); + } + List confident = candidates.stream() + .filter(c -> c.confidence() != MethodAlignment.Confidence.POSITIONAL) + .toList(); + return confident.size() == 1 ? confident.getFirst() : null; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Locator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Locator.java new file mode 100644 index 00000000..806f4de0 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Locator.java @@ -0,0 +1,12 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.List; + +public interface Locator { + List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn); +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java new file mode 100644 index 00000000..ee655d50 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java @@ -0,0 +1,38 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.List; + +public final class SameMethodLocator implements Locator { + @Override + public List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { + MethodAlignment alignment = MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()); + List matches = alignment.find(cleanInsn); + if (!matches.isEmpty()) { + return matches.stream() + .map(match -> new Candidate(targets.dirty(), match.dirty(), match.confidence())) + .toList(); + } + + if (!(cleanInsn instanceof MethodInsnNode cleanCall)) { + return List.of(); + } + return alignment.findDirtyAround(cleanInsn).stream() + .filter(insn -> insn instanceof MethodInsnNode dirtyCall && isReplacement(context, cleanCall, dirtyCall)) + .map(insn -> new Candidate(targets.dirty(), insn, MethodAlignment.Confidence.POSITIONAL)) + .toList(); + } + + private static boolean isReplacement(MixinContext context, MethodInsnNode clean, MethodInsnNode dirty) { + return clean.owner.equals(dirty.owner) && clean.name.equals(dirty.name) + || context.getTypeAdapter(Type.getObjectType(dirty.owner), Type.getObjectType(clean.owner)) != null + && Type.getArgumentTypes(clean.desc).length == Type.getArgumentTypes(dirty.desc).length; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java index 8202aec5..0a64eb9f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java @@ -8,6 +8,8 @@ import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; +import org.sinytra.adapter.pipeline.locate.SameMethodLocator; import org.sinytra.adapter.pipeline.resolver.CompoundResolver; import java.util.List; @@ -15,9 +17,8 @@ public class InjectionPointResolver extends CompoundResolver { public InjectionPointResolver() { - addSubResolver(new OverloadedInjectionPointSubResolver()); + addSubResolver(new LocatingSubResolver(List.of(new SameMethodLocator()))); addSubResolver(new InheritedInjectionPointSubResolver()); - addSubResolver(InjectionPointSubResolvers.REPLACED_TYPE); addSubResolver(InjectionPointSubResolvers.EXTRACTED_CALL); addSubResolver(InjectionPointSubResolvers.EXTRACTED_CODEPATH_CALL); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java index 7676819d..dec9034f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java @@ -1,64 +1,25 @@ package org.sinytra.adapter.pipeline.resolver.injection; -import com.google.common.collect.Multimap; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.compare.CodePaths; import org.sinytra.adapter.analysis.compare.CodePaths.CodePath; import org.sinytra.adapter.analysis.compare.CodePaths.CodePathStep; -import org.sinytra.adapter.analysis.compare.InstructionMatcher; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.analysis.method.MethodInsnMatcher; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; -import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.resolver.SubResolver; -import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.WeighedDisambiguation; import java.util.List; -import java.util.Objects; import java.util.Set; public class InjectionPointSubResolvers { - public static final SubResolver REPLACED_TYPE = (MixinContext context, Recipe recipe) -> { - Targets targets = recipe.targets(); - if (targets == null) return null; - - // Find single clean target minsn - List insns = context.injections().find(targets.clean()); - if (insns.isEmpty() || !(insns.getFirst() instanceof MethodInsnNode cleanInsn)) return null; - - InstructionMatcher cleanMatcher = MethodInsnMatcher.findSurroundingInstructions(cleanInsn); - Multimap dirtyCalls = MethodAnalyzer.getMethodCalls(targets.dirty().methodNode()); - List dirtyMatchers = dirtyCalls.values().stream() - .map(MethodInsnMatcher::findSurroundingInstructions) - .toList(); - - WeighedDisambiguation magicBlackBox = WeighedDisambiguation.builder() - .match(() -> testMatchers(context, cleanInsn, cleanMatcher, dirtyMatchers, false)) - .match(() -> testMatchers(context, cleanInsn, cleanMatcher, dirtyMatchers, true)) - .match(() -> testOverloadedMethods(context, cleanInsn, targets.clean(), targets.dirty())) - .resultsEqual(MethodQualifier::equals) - .build(); - - MethodQualifier replacement = magicBlackBox.findBestMatch(); - if (replacement != null) { - return MutableConfiguration.create() - .setAtData(recipe.clean().getAtData().withTarget(replacement)); - } - - return null; - }; - public static final SubResolver EXTRACTED_CALL = (MixinContext context, Recipe recipe) -> { if (context.isStatic()) return null; @@ -116,48 +77,4 @@ public class InjectionPointSubResolvers { .setTargetMethod(last.from().methodNode()) .inheritAtData(); }; - - private static List testMatchers(MixinContext context, MethodInsnNode cleanInsn, InstructionMatcher cleanMatcher, List dirtyMatchers, boolean partial) { - return dirtyMatchers.stream() - .map(m -> { - boolean before = cleanMatcher.testBefore(m); - boolean after = cleanMatcher.testAfter(m); - boolean match = partial ? before || after : before && after; - - MethodInsnNode dirtyInsn = (MethodInsnNode) m.insn(); - if (match && matchesMethodCall(context, cleanInsn, dirtyInsn)) { - return dirtyInsn; - } - return null; - }) - .filter(Objects::nonNull) - .map(MethodQualifier::create) - .toList(); - } - - private static List testOverloadedMethods(MixinContext context, MethodInsnNode cleanInsn, TargetPair cleanPair, TargetPair dirtyPair) { - ClassNode dirtyClass = context.dirtyLookup().getClass(cleanInsn.owner).orElse(null); - if (dirtyClass == null) { - return List.of(); - } - - List cleanParams = Parameters.getParameterTypes(cleanInsn.desc); - List methods = dirtyClass.methods.stream() - .filter(m -> { - if (m.name.equals(cleanInsn.name) && AdapterUtil.findMethod(cleanPair.classNode(), m.name, m.desc) == null) { - List dirtyParams = Parameters.getParameterTypes(m.desc); - return dirtyParams.size() > cleanParams.size() && dirtyParams.subList(0, cleanParams.size()).equals(cleanParams); - } - return false; - }) - .filter(m -> !MethodAnalyzer.findCalls(dirtyPair.methodNode(), MethodQualifier.create(m)).isEmpty()) - .toList(); - return methods.size() == 1 ? List.of(MethodQualifier.create(dirtyClass, methods.getFirst())) : List.of(); - } - - private static boolean matchesMethodCall(MixinContext context, MethodInsnNode cleanInsn, MethodInsnNode dirtyInsn) { - return cleanInsn.owner.equals(dirtyInsn.owner) && cleanInsn.name.equals(dirtyInsn.name) - || context.getTypeAdapter(Type.getObjectType(dirtyInsn.owner), Type.getObjectType(cleanInsn.owner)) != null - && Type.getArgumentTypes(cleanInsn.desc).length == Type.getArgumentTypes(dirtyInsn.desc).length; - } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java deleted file mode 100644 index 1d5ab83b..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/OverloadedInjectionPointSubResolver.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.injection; - -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.analysis.injection.InjectionQuery; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.Targets; -import org.sinytra.adapter.model.ann.AtData; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.MutableConfiguration; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.SubResolver; - -import java.util.List; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; - -public class OverloadedInjectionPointSubResolver implements SubResolver { - @Nullable - @Override - public Configuration resolve(MixinContext context, Recipe recipe) { - AtData at = recipe.clean().getAtData(); - if (at == null || !at.getValue().equals(AT_VAL_INVOKE) && !at.getValue().equals(AT_VAL_INVOKE_ASSIGN)) - return null; - - Targets targets = recipe.targets(); - if (targets == null) return null; - - // Temporarily modify @At data to target INVOKE if originally using INVOKE_ASSIGN - AtData tempAt = getInvokeAtData(at); - AbstractInsnNode cleanInsn = context.injections().resolve(targets.clean(), InjectionQuery.of(tempAt).keepingShift()); - if (!(cleanInsn instanceof MethodInsnNode cleanMinsn)) return null; - MethodQualifier cleanQualifier = MethodQualifier.create(cleanMinsn); - - TargetPair cleanTarget = context.cleanMethods().resolveDeclared(cleanQualifier); - if (cleanTarget == null) return null; - - TargetPair newCleanTarget = context.dirtyMethods().resolveDeclared(cleanQualifier); - if (newCleanTarget == null) return null; - - // Check if called clean method was marked as deprecated - if (!MethodAnalyzer.isDirtyDeprecatedMethod(cleanTarget.methodNode(), newCleanTarget.methodNode())) { - return null; - } - - // Check that the deprecated method is not called anymore - List cleanCallsInDirty = MethodAnalyzer.findCalls(targets.dirty().methodNode(), cleanQualifier); - if (!cleanCallsInDirty.isEmpty()) return null; - - // Find method calls to potential replacement method - MethodQualifier qualifier = cleanQualifier.ignoreDesc(); - List dirtyCalls = MethodAnalyzer.findCalls(targets.dirty().methodNode(), qualifier); - if (dirtyCalls.isEmpty()) return null; - - // All calls target the same overloaded method - boolean allEqual = dirtyCalls.stream().map(m -> m.desc).distinct().limit(2).count() <= 1; - if (allEqual) { - return MutableConfiguration.create() - .setAtData(recipe.clean().getAtData().withTarget(dirtyCalls.getFirst())); - } - - return null; - } - - private AtData getInvokeAtData(AtData at) { - if (at.getValue().equals(AT_VAL_INVOKE_ASSIGN)) { - return at.withValue(AT_VAL_INVOKE); - } - return at; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java index 022bb63c..0f5cd1fd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java @@ -1,6 +1,5 @@ package org.sinytra.adapter.pipeline.resolver.special; -import com.google.common.collect.Multimap; import com.mojang.datafixers.util.Pair; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; @@ -11,7 +10,6 @@ import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.analysis.method.MethodInsnMatcher; import org.sinytra.adapter.mixin.MixinBridge.LocalCandidate; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; @@ -31,12 +29,11 @@ import java.util.*; import java.util.function.Function; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_RETURN; public class InjectorOrdinalResolver implements Resolver { + // INVOKE ordinals are handled by the locators, see LocatingSubResolver private static final Map OFFSET_HANDLERS = Map.of( - AT_VAL_INVOKE, InvokeOffsetHandler.INSTANCE, AT_VAL_RETURN, ReturnOffsetHandler.INSTANCE ); @@ -71,7 +68,7 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { at.getProperty(AtData.Keys.ORDINAL).ifPresent(ordinal -> { String target = at.getTarget().orElse(null); OffsetUpdateHandler handler = OFFSET_HANDLERS.get(at.getValue()); - if (handler != null && (!handler.requiresTarget() || target != null)) { + if (handler != null) { OffsetUpdateHandler.Context handleContext = new OffsetUpdateHandler.Context(target, ordinal); HandlerInstance instance = new HandlerInstance<>(handler, handleContext, i -> MutableConfiguration.create() @@ -122,10 +119,6 @@ private interface UpdateHandler { private interface OffsetUpdateHandler extends UpdateHandler { record Context(@Nullable String target, int ordinal) { } - - default boolean requiresTarget() { - return false; - } } private record LocalVar(LocalVariableNode lvn, int ordinal, boolean relative) { @@ -147,42 +140,6 @@ public Configuration apply(MixinContext mixinContext, Recipe recipe) { } } - private static class InvokeOffsetHandler implements OffsetUpdateHandler { - public static final InvokeOffsetHandler INSTANCE = new InvokeOffsetHandler(); - - @Override - public boolean requiresTarget() { - return true; - } - - @Override - public Optional apply(MixinContext mixinContext, Recipe recipe, Context context) { - TargetPair cleanTarget = recipe.getCleanTarget(); - TargetPair dirtyTarget = recipe.getDirtyTarget(); - String target = context.target(); - int ordinal = context.ordinal(); - - Multimap cleanCallsMap = MethodAnalyzer.getMethodCalls(cleanTarget.methodNode()); - Multimap dirtyCallsMap = MethodAnalyzer.getMethodCalls(dirtyTarget.methodNode()); - - String remappedTarget = mixinContext.patchContext().remap(target); - Collection cleanCalls = cleanCallsMap.get(remappedTarget); - Collection dirtyCalls = dirtyCallsMap.get(remappedTarget); - - if (cleanCalls.size() != dirtyCalls.size()) { - List cleanMatchers = cleanCalls.stream().map(MethodInsnMatcher::findSurroundingInstructions).toList(); - List dirtyMatchers = dirtyCalls.stream().map(MethodInsnMatcher::findSurroundingInstructions).toList(); - - if (ordinal >= 0 && ordinal < cleanMatchers.size()) { - InstructionMatcher original = cleanMatchers.get(ordinal); - return AdapterUtil.findOne(dirtyMatchers, original::test).map(dirtyMatchers::indexOf); - } - } - - return Optional.empty(); - } - } - private static class ReturnOffsetHandler implements OffsetUpdateHandler { public static final OffsetUpdateHandler INSTANCE = new ReturnOffsetHandler(); diff --git a/core/src/main/java/org/sinytra/adapter/util/WeighedDisambiguation.java b/core/src/main/java/org/sinytra/adapter/util/WeighedDisambiguation.java deleted file mode 100644 index aa668ae9..00000000 --- a/core/src/main/java/org/sinytra/adapter/util/WeighedDisambiguation.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.sinytra.adapter.util; - -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.BiPredicate; -import java.util.function.Supplier; - -public class WeighedDisambiguation { - @Nullable - private final BiPredicate resultsEqual; - private final List>> resultSuppliers; - - private WeighedDisambiguation(BiPredicate resultsEqual, List>> resultSuppliers) { - this.resultsEqual = resultsEqual; - this.resultSuppliers = resultSuppliers; - } - - @Nullable - public R findBestMatch() { - return this.resultSuppliers.stream() - .map(Supplier::get) - .flatMap(r -> disambiguate(r).stream()) - .findFirst() - .orElse(null); - } - - public Optional disambiguate(List results) { - return results.size() == 1 - || !results.isEmpty() && this.resultsEqual != null && AdapterUtil.allElementsEqual(results, this.resultsEqual) - ? Optional.of(results.getFirst()) - : Optional.empty(); - } - - public static Builder builder() { - return new Builder<>(); - } - - public static class Builder { - private final List>> resultSuppliers = new ArrayList<>(); - private BiPredicate resultsEqual; - - public Builder resultsEqual(BiPredicate resultsEqual) { - this.resultsEqual = resultsEqual; - return this; - } - - public Builder match(Supplier> supplier) { - this.resultSuppliers.add(supplier); - return this; - } - - public WeighedDisambiguation build() { - return new WeighedDisambiguation<>(this.resultsEqual, this.resultSuppliers); - } - } -} From adcbae49b76f90b701870381fb1d8a81aa87673c Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 23:22:19 +0200 Subject: [PATCH 15/44] Add called method locator --- .../adapter/analysis/compare/CodePaths.java | 138 -------------- .../pipeline/locate/CalledMethodLocator.java | 175 ++++++++++++++++++ .../adapter/pipeline/locate/Describer.java | 27 ++- .../pipeline/locate/LocatingSubResolver.java | 15 +- .../injection/InjectionPointResolver.java | 5 +- .../injection/InjectionPointSubResolvers.java | 80 -------- .../resolver/target/TargetMethodResolver.java | 1 - .../target/TargetMethodSubResolvers.java | 20 -- 8 files changed, 209 insertions(+), 252 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java deleted file mode 100644 index 71f66836..00000000 --- a/core/src/main/java/org/sinytra/adapter/analysis/compare/CodePaths.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.sinytra.adapter.analysis.compare; - -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.LocalVariableNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.VarInsnNode; -import org.sinytra.adapter.analysis.flow.DataFlow.ImplicitCalls; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.util.AdapterUtil; - -import java.util.*; -import java.util.function.Predicate; - -public class CodePaths { - private static final Set IGNORED_PACKAGES = Set.of("java/", "javax/", "jdk/", "sun/", "com/sun/"); - private static final int MAX_DEPTH = 5; - - @Nullable - public static CodePath findCodePath(TargetPair from, TargetPair to, Set trackLocals, MixinContext context, Predicate entryFilter) { - if (from == null || to == null) { - return null; - } - - CodePath path = findCodePathRecursive(from, to, trackLocals, context, entryFilter, new HashMap<>(), 0); - if (path == null) { - return null; - } - - List steps = path.steps(); - - Map finalLocals = new Int2IntOpenHashMap(); - for (Integer original : trackLocals) { - Integer current = original; - - for (int i = 0; i < steps.size() - 1; i++) { - Integer next = steps.get(i).locals().get(current); - if (next == null) { - current = null; - break; - } - current = next; - } - - if (current != null) { - finalLocals.put(original, current); - } - } - - return new CodePath(path.from(), path.to(), steps, finalLocals); - } - - @Nullable - private static CodePath findCodePathRecursive(TargetPair from, TargetPair to, Set trackLocals, MixinContext context, - Predicate filter, Map visited, int depth - ) { - if (depth >= MAX_DEPTH) { - return null; - } - String key = from.classNode().name + "." + from.methodNode().name + from.methodNode().desc + trackLocals; - Integer visitedDepth = visited.get(key); - if (visitedDepth != null && visitedDepth <= depth) { - return null; - } - visited.put(key, depth); - - List topTierCalls = context.dataFlow(from).findTopLevelCalls(ImplicitCalls.UNWRAP); - - for (MethodInsnNode minsn : topTierCalls) { - if (!filter.test(minsn) - || IGNORED_PACKAGES.stream().anyMatch(minsn.owner::startsWith) - || !context.patchContext().environment().isKnownPackage(AdapterUtil.internalNameToPkg(minsn.owner)) - ) { - continue; - } - - TargetPair nextFrom = context.dirtyMethods().resolveInherited(MethodQualifier.create(minsn)); - if (nextFrom == null) continue; - - LocalVariableLookup lvt = LocalVariableLookup.of(nextFrom.methodNode()); - if (lvt == null) { - continue; - } - - Map locals = new Int2IntOpenHashMap(); - if (!trackLocals.isEmpty()) { - List> receiverInsns = context.dataFlow(from).findOperandExpressions(minsn); - for (int i = 0; i < receiverInsns.size(); i++) { - List argInsns = receiverInsns.get(i); - if (argInsns.size() != 1 || !(argInsns.getFirst() instanceof VarInsnNode varInsn)) continue; - - LocalVariableNode local = lvt.getByOrdinalOrNull(i); - if (local == null) continue; - - for (Integer index : trackLocals) { - if (varInsn.var == index) { - locals.put(index, local.index); - } - } - } - } - - if (!locals.keySet().containsAll(trackLocals)) { - continue; - } - - CodePathStep step = new CodePathStep(from, nextFrom, locals); - - if (nextFrom.classNode().name.equals(to.classNode().name) - && nextFrom.methodNode().name.equals(to.methodNode().name) - && nextFrom.methodNode().desc.equals(to.methodNode().desc) - ) { - return new CodePath(from, to, List.of(step), Map.of()); - } - - Set nextTrackLocals = Set.copyOf(locals.values()); - CodePath subPath = findCodePathRecursive(nextFrom, to, nextTrackLocals, context, call -> true, visited, depth + 1); - if (subPath != null) { - List steps = new ArrayList<>(); - steps.add(step); - steps.addAll(subPath.steps()); - return new CodePath(from, to, steps, Map.of()); - } - } - - return null; - } - - public record CodePath(TargetPair from, TargetPair to, List steps, Map locals) { - } - - public record CodePathStep(TargetPair from, TargetPair to, Map locals) { - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java new file mode 100644 index 00000000..c35e50d8 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java @@ -0,0 +1,175 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.objectweb.asm.tree.analysis.SourceValue; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.analysis.flow.DataFlow; +import org.sinytra.adapter.analysis.flow.DataFlow.ImplicitCalls; +import org.sinytra.adapter.analysis.injection.InjectionQuery; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.Relocation; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.AdapterUtil; + +import java.util.*; +import java.util.function.Predicate; + +/// Finds the injection point in a method the dirty target calls: a helper or lambda the code moved into, or another class it was extracted to. +/// The first call must be new in the dirty target. The nearest methods with a match win. +public final class CalledMethodLocator implements Locator { + private static final int MAX_DEPTH = 3; + private static final List IGNORED_PACKAGES = List.of("java/", "javax/", "jdk/", "sun/", "com/sun/"); + + /// A method reached from the dirty target + /// @param originLocals the local slots that hold the dirty target's instance + private record Callee(TargetPair method, Set originLocals) { + } + + @Override + public List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { + AtData cleanAt = recipe.clean().getAtData(); + if (cleanAt == null) { + return List.of(); + } + + TargetPair origin = targets.dirty(); + Set visited = new HashSet<>(); + visited.add(origin.methodNode()); + Set newCalls = new HashSet<>(MethodAnalyzer.findDirtyOnlyCalls(targets.clean().methodNode(), origin.methodNode(), false)); + Callee start = new Callee(origin, context.isStatic() ? Set.of() : Set.of(0)); + + List frontier = findCallees(context, start, newCalls::contains, visited); + for (int depth = 1; !frontier.isEmpty(); depth++) { + List candidates = new ArrayList<>(); + for (Callee callee : frontier) { + candidates.addAll(findIn(context, targets, cleanAt, cleanInsn, callee)); + } + if (!candidates.isEmpty() || depth == MAX_DEPTH) { + return candidates; + } + + List next = new ArrayList<>(); + for (Callee callee : frontier) { + next.addAll(findCallees(context, callee, call -> true, visited)); + } + frontier = next; + } + return List.of(); + } + + /// {@return the instructions the clean `@At` selects in the callee, matched to the clean instruction where the alignment can} + private static List findIn(MixinContext context, Targets targets, AtData cleanAt, AbstractInsnNode cleanInsn, Callee callee) { + List selected = context.injections().find(callee.method(), InjectionQuery.of(cleanAt.withOrdinal(null))); + if (selected.isEmpty()) { + return List.of(); + } + + Relocation relocation = relocation(targets.dirty(), callee); + List aligned = MethodAlignment.align(targets.clean().methodNode(), callee.method().methodNode()).find(cleanInsn).stream() + .filter(match -> selected.contains(match.dirty())) + .map(match -> new Candidate(callee.method(), match.dirty(), match.confidence(), relocation)) + .toList(); + if (!aligned.isEmpty()) { + return aligned; + } + return selected.stream() + .map(insn -> new Candidate(callee.method(), insn, MethodAlignment.Confidence.POSITIONAL, relocation)) + .toList(); + } + + /// {@return how a handler moved into the callee's class reaches the origin instance, or null if it stays in the origin class or can't} + @Nullable + private static Relocation relocation(TargetPair origin, Callee callee) { + MethodNode method = callee.method().methodNode(); + if (callee.method().classNode().name.equals(origin.classNode().name) || !MethodAnalyzer.isStatic(method)) { + return null; + } + Type[] args = Type.getArgumentTypes(method.desc); + int slot = 0; + for (int i = 0; i < args.length; i++) { + if (callee.originLocals().contains(slot)) { + return new Relocation.ReceivesOrigin(i); + } + slot += args[i].getSize(); + } + return null; + } + + /// {@return the methods the caller calls at the top level and the lambdas it creates, that weren't visited yet} + /// When the handler is an instance method, a callee must receive the origin instance. + private static List findCallees(MixinContext context, Callee caller, Predicate filter, Set visited) { + TargetPair from = caller.method(); + DataFlow flow = context.dataFlow(from); + List callees = new ArrayList<>(); + + for (MethodInsnNode call : flow.findTopLevelCalls(ImplicitCalls.UNWRAP)) { + if (!filter.test(call) + || IGNORED_PACKAGES.stream().anyMatch(call.owner::startsWith) + || !context.patchContext().environment().isKnownPackage(AdapterUtil.internalNameToPkg(call.owner)) + ) { + continue; + } + TargetPair method = context.dirtyMethods().resolveInherited(MethodQualifier.create(call)); + if (method != null && !visited.contains(method.methodNode())) { + addCallee(context, callees, visited, method, passedLocals(flow, call, call.getOpcode() != Opcodes.INVOKESTATIC, call.desc, caller.originLocals())); + } + } + + for (IndyHandle lambda : MethodAnalyzer.findOwnIndyHandles(from.classNode(), from.methodNode())) { + if (!visited.contains(lambda.method())) { + Handle handle = lambda.handle(); + Set locals = passedLocals(flow, lambda.indy(), handle.getTag() != Opcodes.H_INVOKESTATIC, handle.getDesc(), caller.originLocals()); + addCallee(context, callees, visited, new TargetPair(from.classNode(), lambda.method()), locals); + } + } + return callees; + } + + private static void addCallee(MixinContext context, List callees, Set visited, TargetPair method, Set originLocals) { + if (context.isStatic() || !originLocals.isEmpty()) { + visited.add(method.methodNode()); + callees.add(new Callee(method, originLocals)); + } + } + + /// {@return the callee's local slots that receive one of the caller's tracked locals} + /// The operands are the receiver, if any, followed by the leading arguments of the callee descriptor. Lambdas only take their captured arguments. + private static Set passedLocals(DataFlow flow, AbstractInsnNode insn, boolean hasReceiver, String calleeDesc, Set tracked) { + if (tracked.isEmpty()) { + return Set.of(); + } + List operands = flow.operands(insn); + Type[] args = Type.getArgumentTypes(calleeDesc); + Set passed = new HashSet<>(); + int slot = 0; + for (int i = 0; i < operands.size(); i++) { + int argIndex = hasReceiver ? i - 1 : i; + if (argIndex >= args.length) { + break; + } + if (isTrackedLoad(operands.get(i), tracked)) { + passed.add(slot); + } + slot += argIndex < 0 ? 1 : args[argIndex].getSize(); + } + return passed; + } + + private static boolean isTrackedLoad(SourceValue value, Set tracked) { + return value.insns.size() == 1 && value.insns.iterator().next() instanceof VarInsnNode load + && load.getOpcode() == Opcodes.ALOAD && tracked.contains(load.var); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java index d9a2a414..480a5e0d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -7,16 +7,18 @@ import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.List; +import java.util.Objects; public final class Describer { @Nullable public static Configuration describe(MixinContext context, Recipe recipe, Candidate candidate) { AtData cleanAt = recipe.clean().getAtData(); - if (cleanAt == null || candidate.target() != recipe.getDirtyTarget()) { + if (cleanAt == null) { return null; } @@ -27,8 +29,27 @@ public static Configuration describe(MixinContext context, Recipe recipe, Candid return null; } - AtData described = selected.size() == 1 || cleanAt.getOrdinal().isEmpty() ? at.withOrdinal(null) : at.withOrdinal(ordinal); - return MutableConfiguration.create().setAtData(described); + AtData described = cleanAt.getOrdinal().isEmpty() ? at.withOrdinal(null) : at.withOrdinal(ordinal); + MutableConfiguration config = MutableConfiguration.create().setAtData(described); + + if (candidate.target() != recipe.getDirtyTarget()) { + config.setTargetMethod(candidate.target().methodNode()); + String targetClass = candidate.target().classNode().name; + if (!targetClass.equals(recipe.dirty().getTargetClass())) { + config.setTargetClass(targetClass); + if (candidate.relocation() != null) { + config.setProperty(IntentKeys.RELOCATION, candidate.relocation()); + } + } + } + return config; + } + + /// {@return whether both configurations describe the same injection point} + public static boolean isSame(Configuration a, Configuration b) { + return Objects.equals(a.getAtData(), b.getAtData()) + && Objects.equals(a.getTargetMethod(), b.getTargetMethod()) + && Objects.equals(a.getTargetClass(), b.getTargetClass()); } private Describer() { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java index c3a70aa5..fdf3c04d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java @@ -9,8 +9,8 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.resolver.SubResolver; +import java.util.Comparator; import java.util.List; -import java.util.Objects; public final class LocatingSubResolver implements SubResolver { private final List locators; @@ -35,7 +35,7 @@ public Configuration resolve(MixinContext context, Recipe recipe) { Configuration shared = null; for (AbstractInsnNode cleanInsn : cleanInsns) { Configuration config = locate(context, recipe, targets, cleanInsn); - if (config == null || shared != null && !Objects.equals(config.getAtData(), shared.getAtData())) { + if (config == null || shared != null && !Describer.isSame(config, shared)) { return null; } shared = config; @@ -67,7 +67,7 @@ private static Configuration describeAll(MixinContext context, Recipe recipe, Li Configuration shared = null; for (Candidate candidate : candidates) { Configuration config = Describer.describe(context, recipe, candidate); - if (config == null || shared != null && !Objects.equals(config.getAtData(), shared.getAtData())) { + if (config == null || shared != null && !Describer.isSame(config, shared)) { return null; } shared = config; @@ -77,11 +77,12 @@ private static Configuration describeAll(MixinContext context, Recipe recipe, Li @Nullable private static Candidate pick(List candidates) { - if (candidates.size() == 1) { - return candidates.getFirst(); - } + MethodAlignment.Confidence best = candidates.stream() + .map(Candidate::confidence) + .min(Comparator.naturalOrder()) + .orElseThrow(); List confident = candidates.stream() - .filter(c -> c.confidence() != MethodAlignment.Confidence.POSITIONAL) + .filter(c -> c.confidence() == best) .toList(); return confident.size() == 1 ? confident.getFirst() : null; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java index 0a64eb9f..e5436bd1 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java @@ -8,6 +8,7 @@ import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.locate.CalledMethodLocator; import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; import org.sinytra.adapter.pipeline.resolver.CompoundResolver; @@ -17,10 +18,8 @@ public class InjectionPointResolver extends CompoundResolver { public InjectionPointResolver() { - addSubResolver(new LocatingSubResolver(List.of(new SameMethodLocator()))); + addSubResolver(new LocatingSubResolver(List.of(new SameMethodLocator(), new CalledMethodLocator()))); addSubResolver(new InheritedInjectionPointSubResolver()); - addSubResolver(InjectionPointSubResolvers.EXTRACTED_CALL); - addSubResolver(InjectionPointSubResolvers.EXTRACTED_CODEPATH_CALL); } @Override diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java deleted file mode 100644 index dec9034f..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointSubResolvers.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.injection; - -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.compare.CodePaths; -import org.sinytra.adapter.analysis.compare.CodePaths.CodePath; -import org.sinytra.adapter.analysis.compare.CodePaths.CodePathStep; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.Relocation; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.Targets; -import org.sinytra.adapter.model.config.key.IntentKeys; -import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.SubResolver; - -import java.util.List; -import java.util.Set; - -public class InjectionPointSubResolvers { - public static final SubResolver EXTRACTED_CALL = (MixinContext context, Recipe recipe) -> { - if (context.isStatic()) return null; - - Targets targets = recipe.targets(); - if (targets == null) return null; - - for (MethodInsnNode minsn : MethodAnalyzer.findDirtyOnlyCalls(targets.clean().methodNode(), targets.dirty().methodNode(), true)) { - // We only want static external methods - if (minsn.getOpcode() != Opcodes.INVOKESTATIC || minsn.owner.equals(targets.dirty().classNode().name)) - continue; - - List argInsns = context.dataFlow(targets.dirty()).resolveCallOperandProducers(minsn); - if (argInsns == null) continue; - - AbstractInsnNode self = argInsns.stream() - .filter(i -> i instanceof VarInsnNode vinsn && vinsn.var == 0) - .findFirst() - .orElse(null); - if (self == null) continue; - - int selfParamIndex = argInsns.indexOf(self); - MethodQualifier target = MethodQualifier.create(minsn); - - TargetPair pair = context.dirtyMethods().resolveDeclared(target); - if (pair != null && context.injections().matches(pair)) { - return recipe.dirty().copyClean() - .setTargetClass(minsn.owner) - .setTargetMethod(minsn) - .inheritAtData() - .setProperty(IntentKeys.RELOCATION, new Relocation.ReceivesOrigin(selfParamIndex)) - .inheritProperyIfAbsent(MixinKeys.INDEX) - .inheritProperyIfAbsent(MixinKeys.ORDINAL); - } - } - - return null; - }; - - public static final SubResolver EXTRACTED_CODEPATH_CALL = (MixinContext context, Recipe recipe) -> { - Targets targets = recipe.targets(); - if (targets == null) return null; - if (!(recipe.getCleanInjectionInsn() instanceof MethodInsnNode minsn)) return null; - - Set newCalls = Set.copyOf(MethodAnalyzer.findDirtyOnlyCalls(targets.clean().methodNode(), targets.dirty().methodNode(), true)); - if (newCalls.isEmpty()) return null; - Set trackLocals = context.isStatic() ? Set.of() : Set.of(0); - - TargetPair to = context.dirtyMethods().resolveInherited(MethodQualifier.create(minsn)); - CodePath path = CodePaths.findCodePath(targets.dirty(), to, trackLocals, context, newCalls::contains); - if (path == null) return null; - - CodePathStep last = path.steps().getLast(); - return recipe.dirty().copyClean() - .setTargetClass(last.from().classNode()) - .setTargetMethod(last.from().methodNode()) - .inheritAtData(); - }; -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java index 241fa1b7..26b9fc16 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java @@ -11,7 +11,6 @@ public TargetMethodResolver() { addSubResolver(new SplitTargetMethodSubResolver()); addSubResolver(TargetMethodSubResolvers.CHANGED_LAMBDA_INDEX); addSubResolver(TargetMethodSubResolvers.CHANGED_METHOD_PARAMS); - addSubResolver(TargetMethodSubResolvers.MOVED_INTO_LAMBDA); } @Override diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java index 6e5d4233..b7cb39c7 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java @@ -5,7 +5,6 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; @@ -72,25 +71,6 @@ public class TargetMethodSubResolvers { return null; }; - /** - * Handle cases where the target instructions have been moved into a lambda inside the target method - */ - public static final SubResolver MOVED_INTO_LAMBDA = (MixinContext context, Recipe recipe) -> { - TargetPair target = recipe.getNewCleanTarget(); - if (target == null) return null; - - // Find lambda invocations and search for target insns inside the lambda - for (IndyHandle handle : MethodAnalyzer.findOwnIndyHandles(target.classNode(), target.methodNode())) { - TargetPair lambda = new TargetPair(target.classNode(), handle.method()); - if (context.injections().matches(lambda)) { - return MutableConfiguration.create() - .setTargetMethod(lambda.methodNode()); - } - } - - return null; - }; - // Resolve overloaded method by call for when the name doesn't match. Original method must be marked @Deprecated // Example: BoneMealItem#growCrop -> applyBonemeal @Nullable From 2ad033bdcfa790308fc12726006ec215e87f08bd Mon Sep 17 00:00:00 2001 From: Su5eD Date: Mon, 12 Oct 2026 23:34:01 +0200 Subject: [PATCH 16/44] Migrate InheritedInjectionPointSubResolver --- .../pipeline/locate/SameMethodLocator.java | 7 ++ .../InheritedInjectionPointSubResolver.java | 80 ------------------- .../injection/InjectionPointResolver.java | 1 - .../mixin/DynamicMixinPatchTest.java | 32 ++++++++ .../mixin/pipeline/FlowerPotBlockMixin.java | 28 +++++++ .../pipeline/RangedBowAttackGoalMixin.java | 47 +++++++++++ 6 files changed, 114 insertions(+), 81 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java create mode 100644 test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/FlowerPotBlockMixin.java create mode 100644 test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/RangedBowAttackGoalMixin.java diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java index ee655d50..5a7b6594 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java @@ -4,6 +4,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.analysis.method.InheritanceHandler; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -32,7 +33,13 @@ public List locate(MixinContext context, Recipe recipe, Targets targe private static boolean isReplacement(MixinContext context, MethodInsnNode clean, MethodInsnNode dirty) { return clean.owner.equals(dirty.owner) && clean.name.equals(dirty.name) + || clean.name.equals(dirty.name) && clean.desc.equals(dirty.desc) && isRelated(context, clean.owner, dirty.owner) || context.getTypeAdapter(Type.getObjectType(dirty.owner), Type.getObjectType(clean.owner)) != null && Type.getArgumentTypes(clean.desc).length == Type.getArgumentTypes(dirty.desc).length; } + + private static boolean isRelated(MixinContext context, String a, String b) { + InheritanceHandler inheritance = context.environment().inheritanceHandler(context.dirtyLookup()); + return inheritance.isClassInherited(a, b) || inheritance.isClassInherited(b, a); + } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java deleted file mode 100644 index 6f4043fd..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InheritedInjectionPointSubResolver.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.injection; - -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.api.BytecodeFixerUpper; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.ann.AtData; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.MutableConfiguration; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.model.param.MethodParameters; -import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; -import org.sinytra.adapter.model.param.Parameter; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.ctx.PatchContext; -import org.sinytra.adapter.pipeline.injector.MixinFlag; -import org.sinytra.adapter.pipeline.resolver.SubResolver; - -import java.util.List; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; - -public class InheritedInjectionPointSubResolver implements SubResolver { - @Nullable - @Override - public Configuration resolve(MixinContext context, Recipe recipe) { - if (!recipe.hasInjectionPointValue(AT_VAL_INVOKE)) return null; - - AtData at = recipe.clean().getAtData(); - MethodQualifier atTarget = at.getTargetQualifier().orElse(null); - if (atTarget == null || atTarget.internalOwnerName() == null) return null; - - TargetPair targetPair = recipe.getDirtyTarget(); - if (targetPair == null) return null; - if (context.injections().matches(targetPair)) return null; - - String owner = atTarget.internalOwnerName(); - for (AbstractInsnNode insn : targetPair.methodNode().instructions) { - if (insn instanceof MethodInsnNode minsn - && minsn.name.equals(atTarget.name()) && minsn.desc.equals(atTarget.desc()) && !minsn.owner.equals(owner) - && (context.environment().inheritanceHandler(context.dirtyLookup()).isClassInherited(minsn.owner, owner) || isFixedField(minsn, context.patchContext())) - ) { - MutableConfiguration config = MutableConfiguration.create(); - config.setAtData(at.withTarget(minsn)); - - if (context.hasFlag(MixinFlag.AT_TARGET_SENSITIVE) && minsn.getOpcode() != Opcodes.INVOKESTATIC) { - MethodParameters params = recipe.clean().getParameters().copy(); - List callParams = params.get(ParamGroup.METHOD_PARAMS); - if (!callParams.isEmpty()) { - Parameter first = callParams.getFirst().extend() - .annotate(MixinAnnotations.COERCE, b -> b.visible(false)) - .build(); - callParams.set(0, first); - config.setParameters(params); - } - } - - return config; - } - } - - return null; - } - - private boolean isFixedField(AbstractInsnNode insn, PatchContext context) { - BytecodeFixerUpper bfu = context.environment().bytecodeFixerUpper(); - return bfu != null && InsnTraversal.untilLabel(insn, InsnTraversal.BACKWARD).stream() - .filter(FieldInsnNode.class::isInstance) - .map(FieldInsnNode.class::cast) - .findFirst() - .map(finsn -> bfu.getFieldTypeChange(finsn.owner, finsn.name) != null) - .orElse(false); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java index e5436bd1..93e9157b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java @@ -19,7 +19,6 @@ public class InjectionPointResolver extends CompoundResolver { public InjectionPointResolver() { addSubResolver(new LocatingSubResolver(List.of(new SameMethodLocator(), new CalledMethodLocator()))); - addSubResolver(new InheritedInjectionPointSubResolver()); } @Override diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java index 432a7368..e592a374 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java @@ -499,6 +499,38 @@ void testExtractByCodePath() throws Exception { ); } + // The call's owner was narrowed to a subclass + @Test + void testNarrowedCallOwner() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/pipeline/FlowerPotBlockMixin", + "emptyPotState", + assertTargetMethod(), + assertInjectionPoint() + ); + } + + // The call's owner was widened to a superclass + @Test + void testWidenedCallOwnerRedirect() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/pipeline/RangedBowAttackGoalMixin", + "redirectTarget", + assertTargetMethod(), + assertInjectionPoint() + ); + } + + @Test + void testWidenedCallOwnerWrapOperation() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/pipeline/RangedBowAttackGoalMixin", + "wrapTarget", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/FlowerPotBlockMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/FlowerPotBlockMixin.java new file mode 100644 index 00000000..a9ecdc89 --- /dev/null +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/FlowerPotBlockMixin.java @@ -0,0 +1,28 @@ +package org.sinytra.adapter.test.mixin.pipeline; + +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FlowerPotBlock; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(FlowerPotBlock.class) +public class FlowerPotBlockMixin { + // NeoForge calls defaultBlockState on the empty pot, a FlowerPotBlock, instead of on Blocks.FLOWER_POT + @Redirect( + method = "useWithoutItem(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/phys/BlockHitResult;)Lnet/minecraft/world/InteractionResult;", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;defaultBlockState()Lnet/minecraft/world/level/block/state/BlockState;") + ) + private BlockState emptyPotState(Block instance) { + return instance.defaultBlockState(); + } + + @Redirect( + method = "useWithoutItem(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/phys/BlockHitResult;)Lnet/minecraft/world/InteractionResult;", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/FlowerPotBlock;defaultBlockState()Lnet/minecraft/world/level/block/state/BlockState;") + ) + private BlockState emptyPotStateExpected(FlowerPotBlock instance) { + return instance.defaultBlockState(); + } +} diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/RangedBowAttackGoalMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/RangedBowAttackGoalMixin.java new file mode 100644 index 00000000..06d5f297 --- /dev/null +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/RangedBowAttackGoalMixin.java @@ -0,0 +1,47 @@ +package org.sinytra.adapter.test.mixin.pipeline; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.goal.RangedBowAttackGoal; +import net.minecraft.world.entity.monster.Monster; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(RangedBowAttackGoal.class) +public class RangedBowAttackGoalMixin { + // NeoForge widened the goal's mob from Monster to Mob, so the calls now go to Mob + @Redirect( + method = "tick()V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/monster/Monster;getTarget()Lnet/minecraft/world/entity/LivingEntity;") + ) + private LivingEntity redirectTarget(Monster instance) { + return instance.getTarget(); + } + + @Redirect( + method = "tick()V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Mob;getTarget()Lnet/minecraft/world/entity/LivingEntity;") + ) + private LivingEntity redirectTargetExpected(Mob instance) { + return instance.getTarget(); + } + + @WrapOperation( + method = "tick()V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/monster/Monster;getTarget()Lnet/minecraft/world/entity/LivingEntity;") + ) + private LivingEntity wrapTarget(Monster instance, Operation original) { + return original.call(instance); + } + + @WrapOperation( + method = "tick()V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Mob;getTarget()Lnet/minecraft/world/entity/LivingEntity;") + ) + private LivingEntity wrapTargetExpected(Mob instance, Operation original) { + return original.call(instance); + } +} From 42021bbfb5baba988afb02a2bd43527ea3748e94 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 21 Oct 2026 23:32:47 +0200 Subject: [PATCH 17/44] Improve InstanceOf handling --- .../injector/ModifyExpressionValueMixin.java | 7 +- .../pipeline/injector/RedirectMixin.java | 5 +- .../pipeline/injector/WrapOperationMixin.java | 5 + .../ComparingInjectionPointResolver.java | 100 +----------- .../special/ResolverSyntheticInstanceof.java | 142 ------------------ .../InstanceofMakesRedirectRedundant.java | 41 +++++ .../rewrite/InstanceofReplacement.java | 58 +++++++ .../rewrite/InstanceofToConstant.java | 104 +++++++++++++ .../InstanceofToModifyInstanceofValue.java | 35 +++++ .../adapter/pipeline/rewrite/Rewrite.java | 11 ++ .../pipeline/rewrite/RewriteResolver.java | 36 +++++ .../adapter/pipeline/rewrite/RewriteRule.java | 10 ++ 12 files changed, 309 insertions(+), 245 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ResolverSyntheticInstanceof.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofMakesRedirectRedundant.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofReplacement.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToModifyInstanceofValue.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/rewrite/Rewrite.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteResolver.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteRule.java diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java index ded583e9..4ade201e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java @@ -15,7 +15,10 @@ import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.resolver.injection.ArbitraryInjectionPointSubResolver; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.pipeline.resolver.special.ResolverSyntheticInstanceof; +import org.sinytra.adapter.pipeline.rewrite.InstanceofToModifyInstanceofValue; +import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; + +import java.util.List; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; @@ -50,7 +53,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res resolvers.getOrThrow(InjectionPointResolver.class) .addSubResolver(new ArbitraryInjectionPointSubResolver()); resolvers - .addBefore(InjectionPointResolver.class, new ResolverSyntheticInstanceof(true)); + .addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToModifyInstanceofValue()))); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index b4a27fd9..ef884cf2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -23,7 +23,8 @@ import org.sinytra.adapter.pipeline.processor.signature.SignatureProcessor; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.pipeline.resolver.special.ResolverSyntheticInstanceof; +import org.sinytra.adapter.pipeline.rewrite.InstanceofMakesRedirectRedundant; +import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; import java.util.List; @@ -55,7 +56,7 @@ public HandlerSignature signature() { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers. - addBefore(InjectionPointResolver.class, new ResolverSyntheticInstanceof(false)); + addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofMakesRedirectRedundant()))); processors .addAfter(SignatureProcessor.class, new DivertRedirectProcessor()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index 58e00209..22fb584b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -16,8 +16,12 @@ import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.resolver.injection.AtVariableAssignStoreSubResolver; import org.sinytra.adapter.pipeline.resolver.injection.ComparingInjectionPointResolver; +import org.sinytra.adapter.pipeline.rewrite.InstanceofToConstant; +import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; +import java.util.List; + import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; import static org.sinytra.adapter.model.param.HandlerSignature.slot; @@ -51,6 +55,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res resolvers.getOrThrow(InjectionPointResolver.class) .addSubResolverFirst(new ComparingInjectionPointResolver.WrapOperation()) .addSubResolver(new AtVariableAssignStoreSubResolver()); + resolvers.addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToConstant()))); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java index 551e11be..1ce448b5 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java @@ -1,31 +1,21 @@ package org.sinytra.adapter.pipeline.resolver.injection; -import com.google.common.collect.Multimap; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.compare.MethodLabelComparator; import org.sinytra.adapter.analysis.injection.InjectionQuery; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.ann.AtData; -import org.sinytra.adapter.model.ann.ConstantData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.key.IntentKeys; -import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.model.constant.TypeConstants; -import org.sinytra.adapter.model.param.MethodParameters; -import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; -import org.sinytra.adapter.model.param.Parameter; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.wrapop.WrapOpSurgeon; import org.sinytra.adapter.pipeline.resolver.SubResolver; import java.util.Collection; @@ -33,7 +23,6 @@ import java.util.Optional; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ORDINAL; public abstract class ComparingInjectionPointResolver implements SubResolver { @@ -107,99 +96,12 @@ public Configuration resolve(MixinContext context, Recipe recipe) { List> hunkLabels = comparisonResult.patchedLabels(); - return handleWrapOperationToInstanceOf(recipe, minsn, comparisonResult.cleanLabel(), hunkLabels, context) - .or(() -> handleWrapOperationAdaptedTarget(hunkLabels)) + return handleWrapOperationAdaptedTarget(hunkLabels) .or(() -> handleWrapOperationNewInjectionPoint(minsn, comparisonResult.cleanLabel(), hunkLabels)) .or(() -> handleTargetModification(hunkLabels, dirtyTarget, context)) .orElse(null); } - private static Optional handleWrapOperationToInstanceOf(Recipe recipe, MethodInsnNode cleanInjectionInsn, List cleanLabel, List> hunkLabels, MixinContext context) { - if (hunkLabels.size() != 1 || !(cleanLabel.getLast() instanceof JumpInsnNode) || cleanLabel.stream().anyMatch(i -> i instanceof TypeInsnNode)) - return Optional.empty(); - - List dirtyLabel = hunkLabels.getFirst(); - if (!(dirtyLabel.getLast() instanceof JumpInsnNode)) return Optional.empty(); - - List instanceOfCalls = dirtyLabel.stream() - .filter(TypeInsnNode.class::isInstance) - .map(TypeInsnNode.class::cast) - .toList(); - if (instanceOfCalls.size() != 1) return Optional.empty(); - TypeInsnNode instanceOfCall = instanceOfCalls.getFirst(); - - MethodNode methodNode = context.methodNode(); - LocalVariableLookup lvt = LocalVariableLookup.of(methodNode); - if (lvt == null) return Optional.empty(); - - LocalVariableNode instanceLocal = lvt.getByParameterOrdinalOrNull(0); - if (instanceLocal == null) return Optional.empty(); - - Configuration clean = recipe.clean(); - List inheritedParams = clean.getParameters().getTypes(ParamGroup.METHOD_PARAMS); - Multimap usedVars = WrapOpSurgeon.getUsedVars(lvt, inheritedParams, context); - - MutableConfiguration config = recipe.dirty().copyClean() - .removeProperty(MixinKeys.TARGET_AT) - .setProperty(MixinKeys.TARGET_CONSTANT, ConstantData.classValue(Type.getObjectType(instanceOfCall.desc))) - .inheritSignature(); - MethodParameters parameters = config.getParameters(); - Parameter newInstanceParam = Parameter.simple(TypeConstants.OBJECT_TYPE); - parameters.set(ParamGroup.METHOD_PARAMS, List.of(newInstanceParam)); - - if (usedVars.containsKey(instanceLocal.index)) { - TargetPair cleanTarget = recipe.getCleanTarget(); - LocalVariableLookup cleanLocals = recipe.cleanLocalsTable(); - LocalVariableLookup dirtyLocals = recipe.dirtyLocalsTable(); - if (cleanTarget == null || cleanLocals == null || dirtyLocals == null) return Optional.empty(); - - List originalCallArgs = context.dataFlow(cleanTarget).resolveCallOperandProducers(cleanInjectionInsn); - if (originalCallArgs == null || originalCallArgs.isEmpty() || !(originalCallArgs.getFirst() instanceof VarInsnNode originalInstanceInsn)) - return Optional.empty(); - TargetPair dirtyTarget = recipe.getDirtyTarget(); - LocalVariableNode originalInstanceLocal = cleanLocals.getInScopeOrNull(originalInstanceInsn.var, cleanInjectionInsn); - if (dirtyTarget == null || originalInstanceLocal == null) return Optional.empty(); - - int cleanOrdinal = context.locals().resolveLocalOrdinal(cleanTarget, cleanInjectionInsn, Type.getType(originalInstanceLocal.desc), originalInstanceInsn.var); - if (cleanOrdinal == -1) return Optional.empty(); - - int dirtyIndex = context.locals().resolveLocalIndex(dirtyTarget, instanceOfCall, Type.getType(instanceLocal.desc), cleanOrdinal); - LocalVariableNode dirtyLocal = dirtyLocals.getInScopeOrNull(dirtyIndex, instanceOfCall); - if (dirtyLocal == null) return Optional.empty(); - - Parameter replacementInstanceParam = Parameter.builder(dirtyLocal.desc) - .annotate(MixinAnnotations.LOCAL, b -> b - .put(PROPERTY_ORDINAL, cleanOrdinal)) - .build(); - parameters.add(ParamGroup.LOCALS, replacementInstanceParam); - // TODO exclude original.call(...) insns from being mapped - parameters.mapParameter(newInstanceParam, replacementInstanceParam); - } - - // TODO Find a way to get rid of this ugly hack - // Use "state.getBlock()" as the new Block parameter - if (instanceOfCall.getPrevious() instanceof MethodInsnNode m) { - String loadedType = Type.getReturnType(m.desc).getDescriptor(); - for (int paramVar : usedVars.keySet()) { - if (paramVar == instanceLocal.index) { - continue; - } - - LocalVariableNode node = lvt.getByIndexOrNull(paramVar); - if (node == null || !loadedType.equals(node.desc)) return Optional.empty(); - - int paramOrdinal = lvt.getParameterOrdinal(node); - List cleanParams = clean.getParameters().get(ParamGroup.METHOD_PARAMS); - if (paramOrdinal < 0 || paramOrdinal >= cleanParams.size()) return Optional.empty(); - - Parameter oldParam = cleanParams.get(paramOrdinal); - parameters.mapParameter(oldParam, newInstanceParam); - } - } - - return Optional.of(config); - } - private static Optional handleWrapOperationAdaptedTarget(List> hunkLabels) { if (hunkLabels.size() != 1) return Optional.empty(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ResolverSyntheticInstanceof.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ResolverSyntheticInstanceof.java deleted file mode 100644 index 8f416fb9..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ResolverSyntheticInstanceof.java +++ /dev/null @@ -1,142 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.special; - -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.compare.InsnComparator; -import org.sinytra.adapter.analysis.compare.InstructionMatcher; -import org.sinytra.adapter.analysis.method.MethodInsnMatcher; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.ann.AtData; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.Configurations; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.Resolver; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_SINYTRA_INSTANCEOF; - -/** - *

- * Handle targeting redirectors to injections points that are being replaced with instanceof checks. - *

- * Sometimes, mods include injectors that widen item comparison if-statements from comparing specific item instances to an instanceof call - * that applies to all items of a given type. While this is very nice of them, Forge already takes care of it for us, eliminating the needs for such a mixin. - *

- * Reference: stack.isOf(Items.CROSSBOW) -> stack.getItem() instanceof CrossbowItem in HeldItemRenderer#renderFirstPersonItem - */ -public record ResolverSyntheticInstanceof(boolean skipInsnComparison) implements Resolver { - - @Override - public ResolutionResult resolve(MixinContext context, Recipe recipe) { - if (!recipe.hasInjectionPointValue(AT_VAL_INVOKE)) - return ResolutionResult.pass(); - - TargetPair dirtyTarget = recipe.getDirtyTarget(); - AbstractInsnNode targetInsn = recipe.getCleanInjectionInsn(); - if (dirtyTarget == null || targetInsn == null) return ResolutionResult.pass(); - - List labelInsns = findLabelInsns(targetInsn); - // Ensure label contain an if statement - if (labelInsns.isEmpty() || !(labelInsns.getLast() instanceof JumpInsnNode)) return ResolutionResult.pass(); - - InstructionMatcher cleanMatcher = MethodInsnMatcher.findForwardInstructions(targetInsn); - if (cleanMatcher.after().isEmpty()) return ResolutionResult.pass(); - int firstOp = cleanMatcher.after().getFirst().getOpcode(); - // Find equivalent dirty code point - InsnList dirtyInsns = dirtyTarget.methodNode().instructions; - for (AbstractInsnNode insn : dirtyInsns) { - if (insn.getOpcode() == firstOp) { - AbstractInsnNode nextLabel = InsnTraversal.find(insn, InsnTraversal.FORWARD, LabelNode.class::isInstance); - InstructionMatcher dirtyMatcher = MethodInsnMatcher.findForwardInstructions(nextLabel); - if (cleanMatcher.test(dirtyMatcher)) { - // ModifyExpressionValue doesn't include the original instanceof call, so we can skip comparing instructions - if (this.skipInsnComparison) { - // Only convert to @ModifyInstanceofValue if the handler returns boolean. - // If it returns a non-boolean type (e.g. Block), the mixin is modifying the - // value fed into the instanceof check, not the boolean result of it. - // Converting in that case produces an invalid (Block)Block handler where (Z)Z is expected. - Type handlerReturnType = Type.getReturnType(context.methodNode().desc); - if (!handlerReturnType.equals(Type.BOOLEAN_TYPE)) { - return ResolutionResult.pass(); - } - - TypeInsnNode instanceOfInsn = (TypeInsnNode) findLabelInsns(insn).stream().filter(i -> i.getOpcode() == Opcodes.INSTANCEOF).findFirst().orElse(null); - if (instanceOfInsn == null) { - return ResolutionResult.pass(); - } - - int ordinal = getInstanceofOrdinal(dirtyInsns, instanceOfInsn); - Configuration config = Configurations.changeMixinType(recipe.dirty(), MixinAnnotations.MODIFY_INSTANCEOF_VAL, - AtData.builder(AT_VAL_SINYTRA_INSTANCEOF) - .target(instanceOfInsn.desc) - .ordinal(ordinal != 0 ? ordinal : null) - .build()); - - return ResolutionResult.replace(config); - } - - // Found the code point, now determine the contents of the updated if statement - List dirtyLabelInsns = findLabelInsns(insn); - - // Create a normalized method body insn list - List modLabelInsns = new ArrayList<>(); - for (AbstractInsnNode ins : context.methodNode().instructions) { - if (InsnTraversal.isDebugNode(ins)) { - continue; - } - modLabelInsns.add(ins.clone(Map.of())); - } - if (modLabelInsns.size() < 3 || dirtyLabelInsns.isEmpty()) { - continue; - } - // Remove first and last label insns - modLabelInsns.removeFirst(); - modLabelInsns.removeLast(); - - // Remove consumer insns (jump / return) - dirtyLabelInsns.removeLast(); - modLabelInsns.removeLast(); - - // Test whether the mixin method's body is functionally equal to the patched if statement - InstructionMatcher finalCleanMatcher = new InstructionMatcher(null, dirtyLabelInsns, List.of()); - InstructionMatcher finalDirtyMatcher = new InstructionMatcher(null, modLabelInsns, List.of()); - - // Disable mixin. Goodbye. - if (finalCleanMatcher.test(finalDirtyMatcher, InsnComparator.IGNORE_VAR_INDEX)) { - return ResolutionResult.replace(Configurations.DELETE); - } - } - } - } - - return ResolutionResult.pass(); - } - - // The instructions around insn within its label - private static List findLabelInsns(AbstractInsnNode insn) { - List list = new ArrayList<>(); - if (!(insn instanceof LabelNode)) { - list.addAll(InsnTraversal.untilLabel(insn, InsnTraversal.BACKWARD)); - } - list.addAll(InsnTraversal.untilLabel(insn, InsnTraversal.FORWARD)); - return list; - } - - private static int getInstanceofOrdinal(InsnList insns, TypeInsnNode insn) { - List instanceOfInsns = new ArrayList<>(); - for (AbstractInsnNode node : insns) { - if (node instanceof TypeInsnNode typeInsn && typeInsn.getOpcode() == Opcodes.INSTANCEOF && typeInsn.desc.equals(insn.desc)) { - instanceOfInsns.add(node); - } - } - return instanceOfInsns.indexOf(insn); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofMakesRedirectRedundant.java b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofMakesRedirectRedundant.java new file mode 100644 index 00000000..84793973 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofMakesRedirectRedundant.java @@ -0,0 +1,41 @@ +package org.sinytra.adapter.pipeline.rewrite; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.sinytra.adapter.analysis.compare.InsnComparator; +import org.sinytra.adapter.analysis.compare.InstructionMatcher; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.OpcodeUtil; + +import java.util.ArrayList; +import java.util.List; + +public final class InstanceofMakesRedirectRedundant implements RewriteRule { + @Nullable + @Override + public Rewrite apply(MixinContext context, Recipe recipe) { + TypeInsnNode instanceOf = InstanceofReplacement.find(context, recipe); + if (instanceOf == null) { + return null; + } + + List handler = new ArrayList<>(); + for (AbstractInsnNode insn : context.methodNode().instructions) { + if (insn.getOpcode() >= 0) { + handler.add(insn); + } + } + if (handler.isEmpty() || !OpcodeUtil.isReturnOpcode(handler.getLast().getOpcode())) { + return null; + } + handler.removeLast(); + + if (InstructionMatcher.test(InstanceofReplacement.findCondition(instanceOf), handler, InsnComparator.IGNORE_VAR_INDEX)) { + return new Rewrite.Delete("the handler repeats the dirty instanceof check"); + } + + return null; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofReplacement.java b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofReplacement.java new file mode 100644 index 00000000..4b7ead90 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofReplacement.java @@ -0,0 +1,58 @@ +package org.sinytra.adapter.pipeline.rewrite; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.ArrayList; +import java.util.List; + +public final class InstanceofReplacement { + @Nullable + public static TypeInsnNode find(MixinContext context, Recipe recipe) { + Targets targets = recipe.targets(); + if (targets == null + || !(recipe.getCleanInjectionInsn() instanceof MethodInsnNode cleanCall) + || !Type.getReturnType(cleanCall.desc).equals(Type.BOOLEAN_TYPE) + || !(nextReal(cleanCall) instanceof JumpInsnNode) + ) { + return null; + } + + List found = MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()).findDirtyAround(cleanCall).stream() + .filter(insn -> insn.getOpcode() == Opcodes.INSTANCEOF && nextReal(insn) instanceof JumpInsnNode) + .map(TypeInsnNode.class::cast) + .toList(); + return found.size() == 1 ? found.getFirst() : null; + } + + public static List findCondition(TypeInsnNode instanceOf) { + List insns = new ArrayList<>(InsnTraversal.untilLabel(instanceOf, InsnTraversal.BACKWARD).reversed()); + insns.add(instanceOf); + return insns; + } + + public static List findChecks(MethodNode method, String type) { + List checks = new ArrayList<>(); + for (AbstractInsnNode insn : method.instructions) { + if (insn.getOpcode() == Opcodes.INSTANCEOF && ((TypeInsnNode) insn).desc.equals(type)) { + checks.add((TypeInsnNode) insn); + } + } + return checks; + } + + @Nullable + private static AbstractInsnNode nextReal(AbstractInsnNode insn) { + return InsnTraversal.find(insn, InsnTraversal.FORWARD, i -> i.getOpcode() >= 0); + } + + private InstanceofReplacement() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java new file mode 100644 index 00000000..6812a433 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java @@ -0,0 +1,104 @@ +package org.sinytra.adapter.pipeline.rewrite; + +import com.google.common.collect.Multimap; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.ConstantData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.wrapop.WrapOpSurgeon; + +import java.util.List; + +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ORDINAL; + +public final class InstanceofToConstant implements RewriteRule { + @Nullable + @Override + public Rewrite apply(MixinContext context, Recipe recipe) { + TypeInsnNode instanceOfCall = InstanceofReplacement.find(context, recipe); + if (instanceOfCall == null || !(recipe.getCleanInjectionInsn() instanceof MethodInsnNode cleanCall)) return null; + + MethodNode methodNode = context.methodNode(); + LocalVariableLookup lvt = LocalVariableLookup.of(methodNode); + if (lvt == null) return null; + + LocalVariableNode instanceLocal = lvt.getByParameterOrdinalOrNull(0); + if (instanceLocal == null) return null; + + Configuration clean = recipe.clean(); + List inheritedParams = clean.getParameters().getTypes(ParamGroup.METHOD_PARAMS); + Multimap usedVars = WrapOpSurgeon.getUsedVars(lvt, inheritedParams, context); + + MutableConfiguration config = recipe.dirty().copy() + .removeProperty(MixinKeys.TARGET_AT) + .setProperty(MixinKeys.TARGET_CONSTANT, ConstantData.classValue(Type.getObjectType(instanceOfCall.desc))) + .inheritSignature(); + MethodParameters parameters = config.getParameters(); + Parameter newInstanceParam = Parameter.simple(TypeConstants.OBJECT_TYPE); + parameters.set(ParamGroup.METHOD_PARAMS, List.of(newInstanceParam)); + + if (usedVars.containsKey(instanceLocal.index)) { + TargetPair cleanTarget = recipe.getCleanTarget(); + LocalVariableLookup cleanLocals = recipe.cleanLocalsTable(); + LocalVariableLookup dirtyLocals = recipe.dirtyLocalsTable(); + if (cleanTarget == null || cleanLocals == null || dirtyLocals == null) return null; + + List originalCallArgs = context.dataFlow(cleanTarget).resolveCallOperandProducers(cleanCall); + if (originalCallArgs == null || originalCallArgs.isEmpty() || !(originalCallArgs.getFirst() instanceof VarInsnNode originalInstanceInsn)) + return null; + TargetPair dirtyTarget = recipe.getDirtyTarget(); + LocalVariableNode originalInstanceLocal = cleanLocals.getInScopeOrNull(originalInstanceInsn.var, cleanCall); + if (dirtyTarget == null || originalInstanceLocal == null) return null; + + int cleanOrdinal = context.locals().resolveLocalOrdinal(cleanTarget, cleanCall, Type.getType(originalInstanceLocal.desc), originalInstanceInsn.var); + if (cleanOrdinal == -1) return null; + + int dirtyIndex = context.locals().resolveLocalIndex(dirtyTarget, instanceOfCall, Type.getType(instanceLocal.desc), cleanOrdinal); + LocalVariableNode dirtyLocal = dirtyLocals.getInScopeOrNull(dirtyIndex, instanceOfCall); + if (dirtyLocal == null) return null; + + Parameter replacementInstanceParam = Parameter.builder(dirtyLocal.desc) + .annotate(MixinAnnotations.LOCAL, b -> b + .put(PROPERTY_ORDINAL, cleanOrdinal)) + .build(); + parameters.add(ParamGroup.LOCALS, replacementInstanceParam); + // TODO exclude original.call(...) insns from being mapped + parameters.mapParameter(newInstanceParam, replacementInstanceParam); + } + + // TODO Find a way to get rid of this ugly hack + // Use "state.getBlock()" as the new Block parameter + if (instanceOfCall.getPrevious() instanceof MethodInsnNode m) { + String loadedType = Type.getReturnType(m.desc).getDescriptor(); + for (int paramVar : usedVars.keySet()) { + if (paramVar == instanceLocal.index) { + continue; + } + + LocalVariableNode node = lvt.getByIndexOrNull(paramVar); + if (node == null || !loadedType.equals(node.desc)) return null; + + int paramOrdinal = lvt.getParameterOrdinal(node); + List cleanParams = clean.getParameters().get(ParamGroup.METHOD_PARAMS); + if (paramOrdinal < 0 || paramOrdinal >= cleanParams.size()) return null; + + Parameter oldParam = cleanParams.get(paramOrdinal); + parameters.mapParameter(oldParam, newInstanceParam); + } + } + + return new Rewrite.Replace(config); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToModifyInstanceofValue.java b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToModifyInstanceofValue.java new file mode 100644 index 00000000..fb50fd32 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToModifyInstanceofValue.java @@ -0,0 +1,35 @@ +package org.sinytra.adapter.pipeline.rewrite; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.TypeInsnNode; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configurations; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.List; + +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_SINYTRA_INSTANCEOF; + +public final class InstanceofToModifyInstanceofValue implements RewriteRule { + @Nullable + @Override + public Rewrite apply(MixinContext context, Recipe recipe) { + if (!Type.getReturnType(context.methodNode().desc).equals(Type.BOOLEAN_TYPE)) { + return null; + } + TypeInsnNode instanceOf = InstanceofReplacement.find(context, recipe); + if (instanceOf == null) { + return null; + } + + List checks = InstanceofReplacement.findChecks(recipe.getDirtyTarget().methodNode(), instanceOf.desc); + AtData at = AtData.builder(AT_VAL_SINYTRA_INSTANCEOF) + .target(instanceOf.desc) + .ordinal(checks.size() == 1 ? null : checks.indexOf(instanceOf)) + .build(); + return new Rewrite.Replace(Configurations.changeMixinType(recipe.dirty(), MixinAnnotations.MODIFY_INSTANCEOF_VAL, at)); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/Rewrite.java b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/Rewrite.java new file mode 100644 index 00000000..bc836d3d --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/Rewrite.java @@ -0,0 +1,11 @@ +package org.sinytra.adapter.pipeline.rewrite; + +import org.sinytra.adapter.model.config.Configuration; + +public sealed interface Rewrite { + record Replace(Configuration config) implements Rewrite { + } + + record Delete(String reason) implements Rewrite { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteResolver.java new file mode 100644 index 00000000..1e6d1e7c --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteResolver.java @@ -0,0 +1,36 @@ +package org.sinytra.adapter.pipeline.rewrite; + +import org.sinytra.adapter.model.config.Configurations; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.Resolver; + +import java.util.List; + +public final class RewriteResolver implements Resolver { + private final List rules; + + public RewriteResolver(List rules) { + this.rules = rules; + } + + @Override + public ResolutionResult resolve(MixinContext context, Recipe recipe) { + for (RewriteRule rule : this.rules) { + Rewrite rewrite = context.audited(rule, () -> rule.apply(context, recipe)); + switch (rewrite) { + case null -> { + } + case Rewrite.Replace(var config) -> { + context.recordCtxAudit("Rewrite with %s", rule.getClass().getSimpleName()); + return ResolutionResult.replace(config); + } + case Rewrite.Delete(String reason) -> { + context.recordCtxAudit("Delete with %s: %s", rule.getClass().getSimpleName(), reason); + return ResolutionResult.replace(Configurations.DELETE); + } + } + } + return ResolutionResult.pass(); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteRule.java b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteRule.java new file mode 100644 index 00000000..78f37900 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteRule.java @@ -0,0 +1,10 @@ +package org.sinytra.adapter.pipeline.rewrite; + +import org.jetbrains.annotations.Nullable; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +public interface RewriteRule { + @Nullable + Rewrite apply(MixinContext context, Recipe recipe); +} From d5fa1933b053edda45ced6709e218fb8e09939b5 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 21 Oct 2026 23:34:01 +0200 Subject: [PATCH 18/44] Add rewrite rule for shifted variable modifiers --- .../pipeline/PipelineMethodTransformer.java | 3 + .../injector/ModifyVariableMixin.java | 9 +- .../special/ModifyVarAtReturnResolver.java | 138 ------------------ .../special/ModifyVarUpgradeResolver.java | 86 ----------- .../rewrite/ShiftedVariableToExpression.java | 47 ++++++ .../mixin/DynamicMixinPatchTest.java | 11 ++ .../test/mc_26_1_2/mixin/MinecraftMixin.java | 29 ++++ 7 files changed, 94 insertions(+), 229 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarUpgradeResolver.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/rewrite/ShiftedVariableToExpression.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/MinecraftMixin.java diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java index fc394b29..f0f8e31d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java @@ -110,6 +110,9 @@ private PatchResult execute(MixinContext context, Configuration config) { String type = dirtyConfig.getMixinType(); MixinType lateMixinType = MixinTypes.getMixinType(Type.getType(type).getInternalName()); if (lateMixinType != null) { + if (lateMixinType != context.getMixinType()) { + cleanConfig.setParameters(lateMixinType.signature().parse(context.methodNode())); + } TxResult postResult = lateMixinType.postProcess(context, cleanConfig, dirtyConfig, recipe); if (postResult == TxResult.FAIL) { LOGGER.debug(MIXINPATCH, "Skipping mixin {} due to failed postProcess", mixinId); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index 19480bfe..37c40d33 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -22,8 +22,8 @@ import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.injection.ModifyVarInjectionPointSubResolver; import org.sinytra.adapter.pipeline.resolver.special.InjectorOrdinalResolver; -import org.sinytra.adapter.pipeline.resolver.special.ModifyVarAtReturnResolver; -import org.sinytra.adapter.pipeline.resolver.special.ModifyVarUpgradeResolver; +import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; +import org.sinytra.adapter.pipeline.rewrite.ShiftedVariableToExpression; import java.util.List; @@ -56,9 +56,8 @@ public HandlerSignature signature() { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers - .addFirst(new ModifyVarUpgradeResolver()) - .addBefore(InjectionPointResolver.class, new InjectorOrdinalResolver()) - .addBefore(InjectionPointResolver.class, new ModifyVarAtReturnResolver()); + .addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new ShiftedVariableToExpression()))) + .addBefore(InjectionPointResolver.class, new InjectorOrdinalResolver()); resolvers.getOrThrow(InjectionPointResolver.class) .addSubResolver(new ModifyVarInjectionPointSubResolver()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java deleted file mode 100644 index eb64412e..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarAtReturnResolver.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.special; - -import com.mojang.datafixers.util.Pair; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.VarInsnNode; -import org.sinytra.adapter.mixin.MixinBridge; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.Targets; -import org.sinytra.adapter.model.ann.AtData; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.Resolver; - -import java.util.List; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_RETURN; - -/** - * Original mixin: - * - *

{@code @ModifyVariable(
- *     method = "exampleMethod",
- *     at = @At("RETURN")
- * )
- * private void someMethodMixin(int original) {
- *     return original * 2;
- * }
- * }
- *

- * Original target: - * - *

{@code
- * public int exampleMethod() {
- *     int i = 10;
- *     // ...
- * <<< return i;
- * >>> return localvar$zfk000$someMethodMixin(i);
- * }
- * }
- *

- * Patched target: - *

{@code
- * public int exampleMethod() {
- *     int i = 10;
- *     // ...
- * <<< return EventHooks.wrapVariable(i);
- * >>> return EventHooks.wrapVariable(modify$zfk000$someMethodMixin(i));
- * }
- * }
- *

- * Patched mixin: - * - *

{@code @ModifyArg(
- *     method = "exampleMethod",
- *     at = @At(
- *         value = "INVOKE",
- *         target="Lcom/example/EventHooks;wrapVariable(I)I"
- *     ),
- *     index = 0
- * )
- * private void someMethodMixin(int original) {
- *     return original * 2;
- * }
- * }
- */ -public class ModifyVarAtReturnResolver implements Resolver { - @Override - public ResolutionResult resolve(MixinContext context, Recipe recipe) { - AtData at = recipe.clean().getAtData(); - if (!AT_VAL_RETURN.equals(at.getValue())) return ResolutionResult.pass(); - - // Find injection targets - Targets targets = recipe.targets(); - if (targets == null) return ResolutionResult.pass(); - - int ordinal = at.getOrdinal().orElse(-1); - Pair cleanTargetPair = getTargetPair(recipe.clean(), context, targets.clean(), ordinal); - // In CLEAN, previous insn is VarInsn for the modified variable - if (cleanTargetPair == null || !(cleanTargetPair.getFirst() instanceof VarInsnNode cleanVarInsn) || cleanVarInsn.var != cleanTargetPair.getSecond()) { - return ResolutionResult.pass(); - } - // In DIRTY, previous insn (as in, before the RETURN insn) is a method call - Pair dirtyTargetPair = getTargetPair(recipe.clean(), context, targets.dirty(), ordinal); - // In CLEAN, previous insn is VarInsn for the modified variable - if (dirtyTargetPair == null || !(dirtyTargetPair.getFirst() instanceof MethodInsnNode dirtyMinsn)) { - return ResolutionResult.pass(); - } - // Get method call argument instructions - List args = context.dataFlow(targets.dirty()).resolveCallOperandProducers(dirtyMinsn); - if (args == null) { - return ResolutionResult.pass(); - } - - for (int i = 0; i < args.size(); i++) { - AbstractInsnNode insn = args.get(i); - if (insn instanceof VarInsnNode varInsn && varInsn.var == cleanTargetPair.getSecond()) { - Configuration config = recipe.dirty().copyClean() - .setMixinType(MixinAnnotations.MODIFY_ARG) - .inheritTargetClass() - .inheritTargetMethod() - .setAtData(AtData.create(AT_VAL_INVOKE, dirtyMinsn)) - .inheritSignature() - .setProperty(MixinKeys.INDEX, i); - - String qualifier = MethodQualifier.create(dirtyMinsn).asDescriptor(); - context.recordCtxAudit("Redirect RETURN variable modifier to parameter %s of method call to %s", i, qualifier); - return ResolutionResult.replace(config); - } - } - - return ResolutionResult.pass(); - } - - private static Pair getTargetPair(Configuration clean, MixinContext context, TargetPair injectionTarget, int ordinal) { - // Find injection point insn - List targetInsns = context.injections().find(injectionTarget); - if (targetInsns.isEmpty()) return null; - - int index = ordinal == -1 ? targetInsns.size() - 1 : ordinal; - if (index >= targetInsns.size()) return null; - - AbstractInsnNode targetInsn = targetInsns.get(index); - // Find modified variable - MixinBridge.Session session = MixinBridge.session( - context.classNode().name, - context.methodNode(), injectionTarget.classNode(), injectionTarget.methodNode(), - context.environment().refmapHolder() - ); - int local = session.findLocal(context.methodAnnotation().unwrap(), clean.getReturnType(), targetInsn); - return Pair.of(targetInsn, local); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarUpgradeResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarUpgradeResolver.java deleted file mode 100644 index b9357413..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/ModifyVarUpgradeResolver.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.special; - -import org.sinytra.adapter.model.ann.AtData; -import org.sinytra.adapter.model.config.Configurations; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.Resolver; -import org.spongepowered.asm.mixin.injection.At; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; - -/** - * Upgrade a ModifyVar to a ModifyExpressionVal - * Original mixin: - * - *
{@code @ModifyVariable(
- *     method = "exampleMethod",
- *     at = @At(
- *         value = "INVOKE",
- *         target = "someOtherMethod()I",
- *         shift = At.Shift.BY,
- *         by = 2
- *     )
- * )
- * private void someMethodMixin(int original) {
- *     return original * 2;
- * }
- * }
- *

- * Original target: - * - *

{@code
- * public int exampleMethod() {
- *     int i = someOtherMethod();
- * >>> i = localvar$zfk000$someMethodMixin(i);
- *     // ...
- * }
- * }
- *

- * Patched target: - *

{@code
- * public int exampleMethod() {
- * <<< int i = someOtherMethod();
- * >>> int i = modifyExpressionValue$zfk000$someMethodMixin(someOtherMethod());
- *     // ...
- * }
- * }
- *

- * Patched mixin: - * - *

{@code @ModifyExpressionValue(
- *     method = "exampleMethod",
- *     at = @At(
- *         value = "INVOKE",
- *         target="someOtherMethod()I"
- *     )
- * )
- * private void someMethodMixin(int original) {
- *     return original * 2;
- * }
- * }
- */ -public class ModifyVarUpgradeResolver implements Resolver { - @Override - public ResolutionResult resolve(MixinContext context, Recipe recipe) { - if (!recipe.hasInjectionPointValue(AT_VAL_INVOKE) && !recipe.hasInjectionPointValue(AT_VAL_INVOKE_ASSIGN)) - return ResolutionResult.pass(); - - // Conditions must be met: - // - The injection target value is INVOKE or INVOKE_ASSIGN - // - The target is shifted BY 2 - AtData at = recipe.clean().getAtData(); - if (at == null || at.getTarget().isEmpty()) return ResolutionResult.pass(); - - At.Shift shift = at.getProperty(AtData.Keys.SHIFT).orElse(null); - if (shift != At.Shift.BY) return ResolutionResult.pass(); - - boolean byTwo = at.getProperty(AtData.Keys.BY).map(i -> i == 2).orElse(false); - if (!byTwo) return ResolutionResult.pass(); - - String target = at.getTargetOrThrow(); - return ResolutionResult.replace(Configurations.changeMixinType(recipe.dirty(), MixinAnnotations.MODIFY_EXPR_VAL, AtData.create(AT_VAL_INVOKE, target))); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/ShiftedVariableToExpression.java b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/ShiftedVariableToExpression.java new file mode 100644 index 00000000..9d3fee75 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/ShiftedVariableToExpression.java @@ -0,0 +1,47 @@ +package org.sinytra.adapter.pipeline.rewrite; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configurations; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.OpcodeUtil; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; + +public final class ShiftedVariableToExpression implements RewriteRule { + @Nullable + @Override + public Rewrite apply(MixinContext context, Recipe recipe) { + AtData at = recipe.clean().getAtData(); + if (at == null || at.getTarget().isEmpty() || !AT_VAL_INVOKE.equals(at.getValue()) && !AT_VAL_INVOKE_ASSIGN.equals(at.getValue()) + || at.getProperty(AtData.Keys.SHIFT).orElse(null) != At.Shift.BY || at.getProperty(AtData.Keys.BY).orElse(0) != 2 + ) { + return null; + } + + List calls = context.injections().find(recipe.getCleanTarget()); + if (calls.isEmpty() || !calls.stream().allMatch(ShiftedVariableToExpression::isStored)) { + return null; + } + + AtData expression = AtData.builder(AT_VAL_INVOKE) + .target(at.getTargetOrThrow()) + .ordinal(at.getOrdinal().orElse(null)) + .build(); + + return new Rewrite.Replace(Configurations.changeMixinType(recipe.dirty(), MixinAnnotations.MODIFY_EXPR_VAL, expression)); + } + + private static boolean isStored(AbstractInsnNode insn) { + AbstractInsnNode next = InsnTraversal.find(insn, InsnTraversal.FORWARD, i -> i.getOpcode() >= 0); + return OpcodeUtil.isStoreOpcode(insn.getOpcode()) || next != null && OpcodeUtil.isStoreOpcode(next.getOpcode()); + } +} diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 6e42890f..3b191fb2 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -74,6 +74,17 @@ void testModifyArgChangedTargetDesc() throws Exception { ); } + @Test + void testShiftedVariableToExpression() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/MinecraftMixin", + "modifyAttackedPos", + assertType(), + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/MinecraftMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/MinecraftMixin.java new file mode 100644 index 00000000..01df6b4d --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/MinecraftMixin.java @@ -0,0 +1,29 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + // NeoForge adds a local for the input event and the block position moves from index 6 to 7 + @ModifyVariable( + method = "startAttack", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/BlockHitResult;getBlockPos()Lnet/minecraft/core/BlockPos;", shift = At.Shift.BY, by = 2), + index = 6 + ) + private BlockPos modifyAttackedPos(BlockPos pos) { + return pos; + } + + @ModifyExpressionValue( + method = "startAttack()Z", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/BlockHitResult;getBlockPos()Lnet/minecraft/core/BlockPos;") + ) + private BlockPos modifyAttackedPosExpected(BlockPos pos) { + return pos; + } +} From 696e014d4871bcd973a26eb1e208d018e5aa5bcb Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 21 Oct 2026 23:38:07 +0200 Subject: [PATCH 19/44] Add locator for replaced calls --- .../pipeline/injector/InjectMixin.java | 4 +- .../pipeline/injector/ModifyArgMixin.java | 5 - .../injector/ModifyExpressionValueMixin.java | 3 - .../pipeline/injector/WrapOperationMixin.java | 2 - .../pipeline/locate/ReplacedCallLocator.java | 45 ++++++++ .../ArbitraryInjectionPointSubResolver.java | 100 ------------------ .../ComparingInjectionPointResolver.java | 80 -------------- .../injection/InjectionPointResolver.java | 3 +- .../test/mixin/MilkBucketItemMixin.java | 3 +- 9 files changed, 50 insertions(+), 195 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index a8d5d354..119d34fc 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -18,7 +18,6 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.ArbitraryInjectionPointSubResolver; import org.sinytra.adapter.pipeline.resolver.injection.AtVariableAssignStoreSubResolver; import org.sinytra.adapter.pipeline.resolver.injection.ComparingInjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; @@ -59,8 +58,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res .addBefore(InjectionPointResolver.class, new InjectorOrdinalResolver()); resolvers.getOrThrow(InjectionPointResolver.class) .addSubResolver(new AtVariableAssignStoreSubResolver()) - .addSubResolver(new ComparingInjectionPointResolver.Inject()) - .addSubResolver(new ArbitraryInjectionPointSubResolver()); + .addSubResolver(new ComparingInjectionPointResolver.Inject()); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java index 42c191f1..911335d2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java @@ -19,8 +19,6 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.ArbitraryInjectionPointSubResolver; -import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import java.util.List; @@ -51,9 +49,6 @@ public HandlerSignature signature() { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { - resolvers.getOrThrow(InjectionPointResolver.class) - .addSubResolver(new ArbitraryInjectionPointSubResolver()); - clean.setParameters(SIGNATURE.parse(context.methodNode())); return TxResult.SUCCESS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java index 4ade201e..ea1db61f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java @@ -13,7 +13,6 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.ArbitraryInjectionPointSubResolver; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import org.sinytra.adapter.pipeline.rewrite.InstanceofToModifyInstanceofValue; import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; @@ -50,8 +49,6 @@ public HandlerSignature signature() { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { - resolvers.getOrThrow(InjectionPointResolver.class) - .addSubResolver(new ArbitraryInjectionPointSubResolver()); resolvers .addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToModifyInstanceofValue()))); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index 22fb584b..1a2b5b36 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -15,7 +15,6 @@ import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.resolver.injection.AtVariableAssignStoreSubResolver; -import org.sinytra.adapter.pipeline.resolver.injection.ComparingInjectionPointResolver; import org.sinytra.adapter.pipeline.rewrite.InstanceofToConstant; import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; @@ -53,7 +52,6 @@ public HandlerSignature signature() { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers.getOrThrow(InjectionPointResolver.class) - .addSubResolverFirst(new ComparingInjectionPointResolver.WrapOperation()) .addSubResolver(new AtVariableAssignStoreSubResolver()); resolvers.addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToConstant()))); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java new file mode 100644 index 00000000..f640b37f --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java @@ -0,0 +1,45 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.sinytra.adapter.analysis.InsnTraversal; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.List; + +public final class ReplacedCallLocator implements Locator { + @Override + public List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { + if (!(cleanInsn instanceof MethodInsnNode cleanCall)) { + return List.of(); + } + + Type returnType = Type.getReturnType(cleanCall.desc); + List calls = MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()).findDirtyAround(cleanCall).stream() + .filter(insn -> insn instanceof MethodInsnNode call && Type.getReturnType(call.desc).equals(returnType)) + .map(MethodInsnNode.class::cast) + .toList(); + if (calls.size() > 1) { + AbstractInsnNode cleanConsumer = nextReal(cleanCall); + List usedAlike = calls.stream() + .filter(call -> nextReal(call) != null && cleanConsumer != null && nextReal(call).getOpcode() == cleanConsumer.getOpcode()) + .toList(); + if (!usedAlike.isEmpty()) { + calls = usedAlike; + } + } + return calls.stream() + .map(call -> new Candidate(targets.dirty(), call, MethodAlignment.Confidence.POSITIONAL)) + .toList(); + } + + @Nullable + private static AbstractInsnNode nextReal(AbstractInsnNode insn) { + return InsnTraversal.find(insn, InsnTraversal.FORWARD, i -> i.getOpcode() >= 0); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java deleted file mode 100644 index 4edeb341..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ArbitraryInjectionPointSubResolver.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.injection; - -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.compare.InsnComparator; -import org.sinytra.adapter.analysis.compare.InstructionMatcher; -import org.sinytra.adapter.analysis.method.MethodInsnMatcher; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.ann.AtData; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.MutableConfiguration; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.injector.MixinFlag; -import org.sinytra.adapter.pipeline.resolver.SubResolver; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import java.util.function.UnaryOperator; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; - -public class ArbitraryInjectionPointSubResolver implements SubResolver { - @Nullable - @Override - public Configuration resolve(MixinContext context, Recipe recipe) { - String injectionPointTarget = recipe.clean().getAtData().getTarget().orElse(null); - TargetPair dirtyTarget = recipe.getDirtyTarget(); - AbstractInsnNode cleanInsn = recipe.getCleanInjectionInsn(); - if (dirtyTarget == null || cleanInsn == null) return null; - - MethodInsnNode targetMethodCall = resolveTargetCallInsn(dirtyTarget.methodNode(), cleanInsn, context, injectionPointTarget); - if (targetMethodCall == null) return null; - - if (targetMethodCall.owner.equals(dirtyTarget.classNode().name)) { - TargetPair target = context.dirtyMethods().resolve(MethodQualifier.create(targetMethodCall)); - if (target == null) return null; - - // New method is in the same class? It's possible our target injection point was moved there - if (context.injections().matches(target)) { - return MutableConfiguration.create().setTargetMethod(targetMethodCall); - } - } - - return MutableConfiguration.create().setAtData(AtData.create(AT_VAL_INVOKE, targetMethodCall)); - } - - private MethodInsnNode resolveTargetCallInsn(MethodNode dirtyTargetMethod, AbstractInsnNode cleanInjectionInsn, MixinContext context, @Nullable String injectionPointTarget) { - AbstractInsnNode nextCallCandidate = findCandidates(MethodInsnMatcher.findBackwardsInstructions(cleanInjectionInsn).inverse(), dirtyTargetMethod, List::getLast); - - if (nextCallCandidate != null) { - return findReplacementInjectionPoint(nextCallCandidate, AbstractInsnNode::getNext, context, injectionPointTarget); - } else { - AbstractInsnNode previousCallCandidate = findCandidates(MethodInsnMatcher.findForwardInstructions(cleanInjectionInsn), dirtyTargetMethod, List::getFirst); - if (previousCallCandidate != null) { - return findReplacementInjectionPoint(previousCallCandidate, AbstractInsnNode::getPrevious, context, injectionPointTarget); - } - } - - return null; - } - - private static AbstractInsnNode findCandidates(InstructionMatcher cleanMatcher, MethodNode dirtyTargetMethod, Function, AbstractInsnNode> selector) { - // Find an common instruction in the clean and dirty target methods - if (cleanMatcher.after().isEmpty()) return null; - int firstOpcode = cleanMatcher.after().getFirst().getOpcode(); - List candidates = new ArrayList<>(); - - for (AbstractInsnNode insn : dirtyTargetMethod.instructions) { - if (InsnTraversal.isDebugNode(insn) || insn.getOpcode() != firstOpcode) { - continue; - } - - InstructionMatcher dirtyMatcher = MethodInsnMatcher.findForwardInstructionsDirect(insn); - if (cleanMatcher.test(dirtyMatcher, InsnComparator.IGNORE_VAR_INDEX)) { - // Find first method call past matched instruction - AbstractInsnNode lastInsn = selector.apply(dirtyMatcher.after()); - if (lastInsn != null) { - candidates.add(lastInsn); - } - } - } - - return !candidates.isEmpty() ? candidates.getFirst() : null; - } - - private static MethodInsnNode findReplacementInjectionPoint(AbstractInsnNode lastInsn, UnaryOperator flow, MixinContext context, @Nullable String injectionPointTarget) { - // Require matching return types for ModifyExpressionValue mixins - if (context.hasFlag(MixinFlag.RETURN_TYPE_SENSITIVE) && injectionPointTarget != null) { - Type desiredReturnType = Type.getReturnType(injectionPointTarget); - return (MethodInsnNode) InsnTraversal.find(lastInsn, flow, - v -> v instanceof MethodInsnNode minsn && Type.getReturnType(minsn.desc).equals(desiredReturnType)); - } - return (MethodInsnNode) InsnTraversal.find(lastInsn, flow, v -> v instanceof MethodInsnNode); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java index 1ce448b5..2b01ed67 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java @@ -2,7 +2,6 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.compare.MethodLabelComparator; import org.sinytra.adapter.analysis.injection.InjectionQuery; @@ -18,9 +17,7 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.resolver.SubResolver; -import java.util.Collection; import java.util.List; -import java.util.Optional; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; @@ -80,81 +77,4 @@ private static Configuration attemptExtractMixin(MethodInsnNode minsn, MixinCont .setProperty(IntentKeys.RELOCATION, new Relocation.CalledFrom(minsn)); } } - - public static class WrapOperation extends ComparingInjectionPointResolver { - @Nullable - @Override - public Configuration resolve(MixinContext context, Recipe recipe) { - TargetPair dirtyTarget = recipe.getDirtyTarget(); - if (dirtyTarget == null || recipe.getCleanTarget() == null) return null; - - AbstractInsnNode cleanInsn = recipe.getCleanInjectionInsn(); - if (!(cleanInsn instanceof MethodInsnNode minsn)) return null; - - MethodLabelComparator.ComparisonResult comparisonResult = MethodLabelComparator.findPatchedLabels(cleanInsn, recipe); - if (comparisonResult == null) return null; - - List> hunkLabels = comparisonResult.patchedLabels(); - - return handleWrapOperationAdaptedTarget(hunkLabels) - .or(() -> handleWrapOperationNewInjectionPoint(minsn, comparisonResult.cleanLabel(), hunkLabels)) - .or(() -> handleTargetModification(hunkLabels, dirtyTarget, context)) - .orElse(null); - } - - private static Optional handleWrapOperationAdaptedTarget(List> hunkLabels) { - if (hunkLabels.size() != 1) return Optional.empty(); - - List methodCalls = hunkLabels.getFirst().stream() - .filter(MethodInsnNode.class::isInstance) - .map(MethodInsnNode.class::cast) - .toList(); - if (methodCalls.size() != 1) return Optional.empty(); - MethodInsnNode insn = methodCalls.getLast(); - - return Optional.of(MutableConfiguration.create() - .setAtData(AtData.create(AT_VAL_INVOKE, insn))); - } - - private static Optional handleWrapOperationNewInjectionPoint(MethodInsnNode cleanInjectionInsn, List cleanLabel, List> hunkLabels) { - if (hunkLabels.size() != 1) - return Optional.empty(); - - Type cleanReturnType = Type.getReturnType(cleanInjectionInsn.desc); - List dirtyLabel = hunkLabels.getFirst(); - List cleanMethodCalls = cleanLabel.stream() - .filter(i -> i instanceof MethodInsnNode m - && m.owner.equals(cleanInjectionInsn.owner) - && Type.getReturnType(m.desc).equals(cleanReturnType)) - .map(i -> ((MethodInsnNode) i).name) - .toList(); - - List methodCalls = dirtyLabel.stream() - .filter(i -> i instanceof MethodInsnNode m && m.owner.equals(cleanInjectionInsn.owner) - && Type.getReturnType(m.desc).equals(cleanReturnType) - && !cleanMethodCalls.contains(m.name)) - .map(MethodInsnNode.class::cast) - .toList(); - if (methodCalls.size() != 1) return Optional.empty(); - - MethodInsnNode dirtyMinsn = methodCalls.getFirst(); - return Optional.of(MutableConfiguration.create() - .setAtData(AtData.create(AT_VAL_INVOKE, dirtyMinsn))); - } - - private static Optional handleTargetModification(List> hunkLabels, TargetPair dirtyTarget, MixinContext context) { - ClassNode dirtyClass = dirtyTarget.classNode(); - return hunkLabels.stream() - .flatMap(Collection::stream) - .filter(insn -> insn instanceof MethodInsnNode minsn && minsn.owner.equals(dirtyClass.name)) - .map(MethodInsnNode.class::cast) - .filter(minsn -> { - TargetPair pair = context.dirtyMethods().resolveDeclared(MethodQualifier.create(minsn)); - return pair != null && context.injections().matches(pair); - }) - .map(minsn -> MutableConfiguration.create() - .setAtData(AtData.create(AT_VAL_INVOKE, minsn))) - .findFirst(); - } - } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java index 93e9157b..2030dae8 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java @@ -10,6 +10,7 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.locate.CalledMethodLocator; import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; +import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; import org.sinytra.adapter.pipeline.resolver.CompoundResolver; @@ -18,7 +19,7 @@ public class InjectionPointResolver extends CompoundResolver { public InjectionPointResolver() { - addSubResolver(new LocatingSubResolver(List.of(new SameMethodLocator(), new CalledMethodLocator()))); + addSubResolver(new LocatingSubResolver(List.of(new SameMethodLocator(), new CalledMethodLocator(), new ReplacedCallLocator()))); } @Override diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/MilkBucketItemMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/MilkBucketItemMixin.java index 060e750f..fe204e32 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/MilkBucketItemMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/MilkBucketItemMixin.java @@ -28,7 +28,8 @@ private void onClearStatusEffect(ItemStack stack, Level level, LivingEntity user method = "finishUsingItem", at = @At( value = "INVOKE", - target = "Lnet/minecraft/world/entity/LivingEntity;removeEffectsCuredBy(Lnet/neoforged/neoforge/common/EffectCure;)Z" + target = "Lnet/minecraft/world/entity/LivingEntity;removeEffectsCuredBy(Lnet/neoforged/neoforge/common/EffectCure;)Z", + shift = At.Shift.BEFORE ) ) private void onClearStatusEffectExpected(ItemStack stack, Level level, LivingEntity user, CallbackInfoReturnable cir) { From 0cd2651a356a1258d67eb42aa16ed9fa0a098ff1 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 00:09:31 +0200 Subject: [PATCH 20/44] Remove obsolete subresolver --- .../pipeline/injector/InjectMixin.java | 2 - .../pipeline/injector/WrapOperationMixin.java | 3 - .../pipeline/locate/ReplacedCallLocator.java | 40 ++++++++- .../AtVariableAssignStoreSubResolver.java | 86 ------------------- 4 files changed, 39 insertions(+), 92 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index 119d34fc..fa838eaa 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -18,7 +18,6 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.AtVariableAssignStoreSubResolver; import org.sinytra.adapter.pipeline.resolver.injection.ComparingInjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.special.InjectorOrdinalResolver; @@ -57,7 +56,6 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res resolvers .addBefore(InjectionPointResolver.class, new InjectorOrdinalResolver()); resolvers.getOrThrow(InjectionPointResolver.class) - .addSubResolver(new AtVariableAssignStoreSubResolver()) .addSubResolver(new ComparingInjectionPointResolver.Inject()); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index 1a2b5b36..0e288b95 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -14,7 +14,6 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.AtVariableAssignStoreSubResolver; import org.sinytra.adapter.pipeline.rewrite.InstanceofToConstant; import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; @@ -51,8 +50,6 @@ public HandlerSignature signature() { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { - resolvers.getOrThrow(InjectionPointResolver.class) - .addSubResolver(new AtVariableAssignStoreSubResolver()); resolvers.addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToConstant()))); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java index f640b37f..10deb285 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java @@ -1,14 +1,20 @@ package org.sinytra.adapter.pipeline.locate; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.objectweb.asm.tree.analysis.SourceValue; import org.sinytra.adapter.analysis.InsnTraversal; import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.util.OpcodeUtil; import java.util.List; @@ -20,7 +26,8 @@ public List locate(MixinContext context, Recipe recipe, Targets targe } Type returnType = Type.getReturnType(cleanCall.desc); - List calls = MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()).findDirtyAround(cleanCall).stream() + MethodAlignment alignment = MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()); + List calls = alignment.findDirtyAround(cleanCall).stream() .filter(insn -> insn instanceof MethodInsnNode call && Type.getReturnType(call.desc).equals(returnType)) .map(MethodInsnNode.class::cast) .toList(); @@ -33,11 +40,42 @@ public List locate(MixinContext context, Recipe recipe, Targets targe calls = usedAlike; } } + if (calls.isEmpty() && !context.hasFlag(MixinFlag.RETURN_TYPE_SENSITIVE) && !context.hasFlag(MixinFlag.AT_TARGET_SENSITIVE)) { + MethodInsnNode storing = findStoringCall(context, targets, alignment, cleanCall); + calls = storing != null ? List.of(storing) : List.of(); + } return calls.stream() .map(call -> new Candidate(targets.dirty(), call, MethodAlignment.Confidence.POSITIONAL)) .toList(); } + /// {@return the dirty call whose value goes into the local the clean call's result was stored into, or null} + @Nullable + private static MethodInsnNode findStoringCall(MixinContext context, Targets targets, MethodAlignment alignment, MethodInsnNode cleanCall) { + AbstractInsnNode cleanStore = skipCasts(nextReal(cleanCall)); + if (cleanStore == null || !OpcodeUtil.isStoreOpcode(cleanStore.getOpcode()) + || !(alignment.resolve(cleanStore) instanceof VarInsnNode dirtyStore) + ) { + return null; + } + + DataFlow flow = context.dataFlow(targets.dirty()); + AbstractInsnNode producer = dirtyStore; + do { + List operands = flow.operands(producer); + producer = operands.size() == 1 && operands.getFirst().insns.size() == 1 ? operands.getFirst().insns.iterator().next() : null; + } while (producer != null && producer.getOpcode() == Opcodes.CHECKCAST); + return producer instanceof MethodInsnNode call ? call : null; + } + + @Nullable + private static AbstractInsnNode skipCasts(@Nullable AbstractInsnNode insn) { + while (insn != null && insn.getOpcode() == Opcodes.CHECKCAST) { + insn = nextReal(insn); + } + return insn; + } + @Nullable private static AbstractInsnNode nextReal(AbstractInsnNode insn) { return InsnTraversal.find(insn, InsnTraversal.FORWARD, i -> i.getOpcode() >= 0); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java deleted file mode 100644 index 0faed871..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/AtVariableAssignStoreSubResolver.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.injection; - -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; -import org.sinytra.adapter.model.Targets; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.MutableConfiguration; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.injector.MixinFlag; -import org.sinytra.adapter.pipeline.resolver.SubResolver; -import org.sinytra.adapter.util.OpcodeUtil; - -import java.util.List; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; - -/** - * Find our new injection point with relation to variable assignments - */ -public class AtVariableAssignStoreSubResolver implements SubResolver { - @Nullable - @Override - public Configuration resolve(MixinContext context, Recipe recipe) { - if (!recipe.hasInjectionPointValue(AT_VAL_INVOKE)) return null; - - Targets targets = recipe.targets(); - if (targets == null) return null; - AbstractInsnNode cleanInsn = recipe.getCleanInjectionInsn(); - if (cleanInsn == null) return null; - - // Check that the following instruction is a store operation - AbstractInsnNode next = findNextUsefulInsn(cleanInsn); - if (!(next instanceof VarInsnNode varInsn) || !OpcodeUtil.isStoreOpcode(varInsn.getOpcode())) { - return null; - } - // Find matching local in dirty target method - LocalVariableLookup cleanTable = recipe.cleanLocalsTable(); - LocalVariableLookup dirtyTable = recipe.dirtyLocalsTable(); - if (cleanTable == null || dirtyTable == null) { - return null; - } - LocalVariableNode cleanLocal = cleanTable.getByIndexOrNull(varInsn.var); - if (cleanLocal == null) { - return null; - } - List cleanLocals = cleanTable.getForType(cleanLocal); - List dirtyLocals = dirtyTable.getForType(cleanLocal); - if (cleanLocals.size() != dirtyLocals.size()) { - return null; - } - LocalVariableNode dirtyLocal = dirtyLocals.get(cleanLocals.indexOf(cleanLocal)); - // Find store insns - List cleanStoreInsns = LocalsAnalyzer.findStoreInsns(targets.clean().methodNode(), cleanLocal.index); - int cleanStoreInsnIndex = cleanStoreInsns.indexOf(varInsn); - if (cleanStoreInsnIndex == -1) { - return null; - } - List dirtyStoreInsns = LocalsAnalyzer.findStoreInsns(targets.dirty().methodNode(), dirtyLocal.index); - if (cleanStoreInsns.size() != dirtyStoreInsns.size()) { - return null; - } - AbstractInsnNode dirtyStoreInsn = dirtyStoreInsns.get(cleanStoreInsnIndex); - // Find first method call before dirty store - MethodInsnNode previousMethodCall = (MethodInsnNode) InsnTraversal.find(dirtyStoreInsn, InsnTraversal.BACKWARD, i -> i instanceof MethodInsnNode); - if (previousMethodCall == null) { - return null; - } - - // In case the mixin is call-sensitive, we try to keep the orignal injection point if the method was moved - if (context.hasFlag(MixinFlag.AT_TARGET_SENSITIVE) && !previousMethodCall.owner.equals(targets.dirty().classNode().name)) { - return null; - } - - // All checks have passed, proceed to patch method - return MutableConfiguration.create() - .setAtData(recipe.clean().getAtData().withTarget(previousMethodCall)); - } - - private static AbstractInsnNode findNextUsefulInsn(AbstractInsnNode insn) { - return InsnTraversal.find(insn, InsnTraversal.FORWARD, i -> !(i instanceof TypeInsnNode || i instanceof LabelNode || InsnTraversal.isDebugNode(i))); - } -} From 364996aaa1caa1dd6712d6cd1b6081913ebd0dc9 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 00:20:42 +0200 Subject: [PATCH 21/44] Add fallback nearest call locator --- .../compare/MethodLabelComparator.java | 130 ------------------ .../pipeline/injector/InjectMixin.java | 9 +- .../injector/ModifyReturnValueMixin.java | 5 - .../pipeline/locate/CalledMethodLocator.java | 29 ++-- .../adapter/pipeline/locate/Describer.java | 35 +++-- .../pipeline/locate/LocatingSubResolver.java | 23 ++-- .../pipeline/locate/NearestCallLocator.java | 45 ++++++ .../pipeline/locate/SameMethodLocator.java | 40 ++++++ .../ComparingInjectionPointResolver.java | 80 ----------- .../injection/InjectionPointResolver.java | 24 +++- .../special/InjectorOrdinalResolver.java | 123 ----------------- .../special/SliceBoundaryResolver.java | 107 ++++++-------- 12 files changed, 204 insertions(+), 446 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/analysis/compare/MethodLabelComparator.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/NearestCallLocator.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodLabelComparator.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodLabelComparator.java deleted file mode 100644 index 0f6f657f..00000000 --- a/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodLabelComparator.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.sinytra.adapter.analysis.compare; - -import com.mojang.datafixers.util.Pair; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FrameNode; -import org.objectweb.asm.tree.LabelNode; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.model.Targets; -import org.sinytra.adapter.pipeline.Recipe; - -import java.util.*; -import java.util.stream.Stream; - -public class MethodLabelComparator { - public record ComparisonResult(List> patchedLabels, List cleanLabel) { - } - - @Nullable - public static ComparisonResult findPatchedLabels(AbstractInsnNode cleanInjectionInsn, Recipe recipe) { - Targets targets = recipe.targets(); - if (targets == null) return null; - - List> cleanLabels = getLabelsInMethod(targets.clean().methodNode()); - List> cleanLabelsOriginal = List.copyOf(cleanLabels); - - List> cleanMatchedLabels = cleanLabels.stream() - .filter(insns -> insns.contains(cleanInjectionInsn)) - .toList(); - if (cleanMatchedLabels.size() != 1) { - return null; - } - List cleanLabel = cleanMatchedLabels.getFirst(); - - List> dirtyLabels = getLabelsInMethod(targets.dirty().methodNode()); - List> dirtyLabelsOriginal = List.copyOf(dirtyLabels); - - Map, List> matchedLabels = new LinkedHashMap<>(); - for (List cleanInsns : cleanLabelsOriginal) { - List> candidates = new ArrayList<>(); - - for (List dirtyInsns : dirtyLabels) { - if (InstructionMatcher.test(cleanInsns, dirtyInsns, InsnComparator.IGNORE_VAR_INDEX | InsnComparator.IGNORE_LINE_NUMBERS)) { - candidates.add(dirtyInsns); - } - } - // TODO Try and come up with something better - // This prevents messing up the order of labels - // Without this countermeasure, it might happen that a label that was deleted will match a seemingly identical label somewhere else in the method, which is wrong - // We disable any duplicated until we can properly handle such cases - if (candidates.size() == 1) { - List dirtyInsns = candidates.getFirst(); - matchedLabels.put(cleanInsns, dirtyInsns); - cleanLabels.remove(cleanInsns); - dirtyLabels.remove(dirtyInsns); - } - } - - Pair, List> patchRange = findPatchHunkRange(cleanLabel, cleanLabelsOriginal, matchedLabels); - if (patchRange == null) { - return null; - } - - List> patchedLabels; - int to = dirtyLabelsOriginal.indexOf(patchRange.getSecond()); - if (patchRange.getFirst() == null) { - patchedLabels = dirtyLabelsOriginal.subList(0, to); - } else { - int from = dirtyLabelsOriginal.indexOf(patchRange.getFirst()) + 1; - if (from < to) { - patchedLabels = dirtyLabelsOriginal.subList(dirtyLabelsOriginal.indexOf(patchRange.getFirst()) + 1, to); - } else { - return null; - } - } - return new ComparisonResult(patchedLabels, cleanLabel); - } - - @Nullable - private static Pair<@Nullable List, List> findPatchHunkRange(List cleanLabel, List> cleanLabels, Map, List> matchedLabels) { - // Find last matched dirty label BEFORE the injection point - List dirtyLabelBefore; - int cleanLabelOrdinal = cleanLabels.indexOf(cleanLabel); - if (cleanLabelOrdinal == 0) { - dirtyLabelBefore = null; - } else { - dirtyLabelBefore = Stream.iterate(cleanLabelOrdinal, i -> i >= 0, i -> i - 1) - .map(i -> matchedLabels.get(cleanLabels.get(i))) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); - if (dirtyLabelBefore == null) { - return null; - } - } - - // Find first matched dirty label AFTER the injection point - List dirtyLabelAfter = Stream.iterate(cleanLabels.indexOf(cleanLabel), i -> i < cleanLabels.size(), i -> i + 1) - .map(i -> matchedLabels.get(cleanLabels.get(i))) - .filter(Objects::nonNull) - .findFirst() - .orElse(null); - if (dirtyLabelAfter == null) { - return null; - } - - return Pair.of(dirtyLabelBefore, dirtyLabelAfter); - } - - private static List> getLabelsInMethod(MethodNode methodNode) { - List> list = new ArrayList<>(); - List workingList = null; - for (AbstractInsnNode insn : methodNode.instructions) { - if (insn instanceof FrameNode) { - continue; - } - if (insn instanceof LabelNode || workingList == null) { - if (workingList != null) { - list.add(workingList); - } - workingList = new ArrayList<>(); - } - workingList.add(insn); - } - if (workingList != null) { - list.add(workingList); - } - return list; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index fa838eaa..ea15f392 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -18,9 +18,9 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.ComparingInjectionPointResolver; +import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; +import org.sinytra.adapter.pipeline.locate.NearestCallLocator; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.pipeline.resolver.special.InjectorOrdinalResolver; import java.util.List; import java.util.Objects; @@ -53,10 +53,9 @@ public HandlerSignature signature() { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { - resolvers - .addBefore(InjectionPointResolver.class, new InjectorOrdinalResolver()); + // An @Inject only needs a position, so it can fall back to the nearest call resolvers.getOrThrow(InjectionPointResolver.class) - .addSubResolver(new ComparingInjectionPointResolver.Inject()); + .addSubResolver(new LocatingSubResolver(List.of(new NearestCallLocator()))); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java index b5249fec..6547a847 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java @@ -16,8 +16,6 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.pipeline.resolver.special.InjectorOrdinalResolver; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_RETURN; import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; @@ -59,9 +57,6 @@ public boolean canInject(MixinContext context, Configuration config) { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { - resolvers - .addBefore(InjectionPointResolver.class, new InjectorOrdinalResolver()); - clean.setParameters(SIGNATURE.parse(context.methodNode())); return TxResult.SUCCESS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java index c35e50d8..d3bb4d1a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java @@ -35,7 +35,8 @@ public final class CalledMethodLocator implements Locator { /// A method reached from the dirty target /// @param originLocals the local slots that hold the dirty target's instance - private record Callee(TargetPair method, Set originLocals) { + /// @param calledFrom the dirty target's call of a static method in another class that doesn't receive the instance + private record Callee(TargetPair method, Set originLocals, @Nullable MethodInsnNode calledFrom) { } @Override @@ -49,9 +50,9 @@ public List locate(MixinContext context, Recipe recipe, Targets targe Set visited = new HashSet<>(); visited.add(origin.methodNode()); Set newCalls = new HashSet<>(MethodAnalyzer.findDirtyOnlyCalls(targets.clean().methodNode(), origin.methodNode(), false)); - Callee start = new Callee(origin, context.isStatic() ? Set.of() : Set.of(0)); + Callee start = new Callee(origin, context.isStatic() ? Set.of() : Set.of(0), null); - List frontier = findCallees(context, start, newCalls::contains, visited); + List frontier = findCallees(context, start, newCalls::contains, visited, true); for (int depth = 1; !frontier.isEmpty(); depth++) { List candidates = new ArrayList<>(); for (Callee callee : frontier) { @@ -63,7 +64,7 @@ public List locate(MixinContext context, Recipe recipe, Targets targe List next = new ArrayList<>(); for (Callee callee : frontier) { - next.addAll(findCallees(context, callee, call -> true, visited)); + next.addAll(findCallees(context, callee, call -> true, visited, false)); } frontier = next; } @@ -105,12 +106,12 @@ private static Relocation relocation(TargetPair origin, Callee callee) { } slot += args[i].getSize(); } - return null; + return callee.calledFrom() != null ? new Relocation.CalledFrom(callee.calledFrom()) : null; } /// {@return the methods the caller calls at the top level and the lambdas it creates, that weren't visited yet} - /// When the handler is an instance method, a callee must receive the origin instance. - private static List findCallees(MixinContext context, Callee caller, Predicate filter, Set visited) { + /// When the handler is an instance method, a callee must receive the origin instance, unless the origin calls it statically. + private static List findCallees(MixinContext context, Callee caller, Predicate filter, Set visited, boolean fromOrigin) { TargetPair from = caller.method(); DataFlow flow = context.dataFlow(from); List callees = new ArrayList<>(); @@ -124,7 +125,9 @@ private static List findCallees(MixinContext context, Callee caller, Pre } TargetPair method = context.dirtyMethods().resolveInherited(MethodQualifier.create(call)); if (method != null && !visited.contains(method.methodNode())) { - addCallee(context, callees, visited, method, passedLocals(flow, call, call.getOpcode() != Opcodes.INVOKESTATIC, call.desc, caller.originLocals())); + Set locals = passedLocals(flow, call, call.getOpcode() != Opcodes.INVOKESTATIC, call.desc, caller.originLocals()); + boolean mirrorable = fromOrigin && call.getOpcode() == Opcodes.INVOKESTATIC && !method.classNode().name.equals(from.classNode().name); + addCallee(context, callees, visited, new Callee(method, locals, mirrorable ? call : null)); } } @@ -132,16 +135,16 @@ private static List findCallees(MixinContext context, Callee caller, Pre if (!visited.contains(lambda.method())) { Handle handle = lambda.handle(); Set locals = passedLocals(flow, lambda.indy(), handle.getTag() != Opcodes.H_INVOKESTATIC, handle.getDesc(), caller.originLocals()); - addCallee(context, callees, visited, new TargetPair(from.classNode(), lambda.method()), locals); + addCallee(context, callees, visited, new Callee(new TargetPair(from.classNode(), lambda.method()), locals, null)); } } return callees; } - private static void addCallee(MixinContext context, List callees, Set visited, TargetPair method, Set originLocals) { - if (context.isStatic() || !originLocals.isEmpty()) { - visited.add(method.methodNode()); - callees.add(new Callee(method, originLocals)); + private static void addCallee(MixinContext context, List callees, Set visited, Callee callee) { + if (context.isStatic() || !callee.originLocals().isEmpty() || callee.calledFrom() != null) { + visited.add(callee.method().methodNode()); + callees.add(callee); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java index 480a5e0d..49e2a9bb 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -13,23 +13,19 @@ import java.util.List; import java.util.Objects; +import java.util.function.Function; + +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; public final class Describer { @Nullable public static Configuration describe(MixinContext context, Recipe recipe, Candidate candidate) { AtData cleanAt = recipe.clean().getAtData(); - if (cleanAt == null) { - return null; - } - - AtData at = candidate.insn() instanceof MethodInsnNode call && cleanAt.getTarget().isPresent() ? cleanAt.withTarget(call) : cleanAt; - List selected = context.injections().find(candidate.target(), InjectionQuery.of(at.withOrdinal(null))); - int ordinal = selected.indexOf(candidate.insn()); - if (ordinal == -1) { + AtData described = cleanAt != null ? describeAt(context, cleanAt, candidate, InjectionQuery::of) : null; + if (described == null) { return null; } - - AtData described = cleanAt.getOrdinal().isEmpty() ? at.withOrdinal(null) : at.withOrdinal(ordinal); MutableConfiguration config = MutableConfiguration.create().setAtData(described); if (candidate.target() != recipe.getDirtyTarget()) { @@ -45,7 +41,24 @@ public static Configuration describe(MixinContext context, Recipe recipe, Candid return config; } - /// {@return whether both configurations describe the same injection point} + @Nullable + public static AtData describeAt(MixinContext context, AtData cleanAt, Candidate candidate, Function query) { + boolean changesKind = candidate.insn() instanceof MethodInsnNode && !AT_VAL_INVOKE.equals(cleanAt.getValue()) && !AT_VAL_INVOKE_ASSIGN.equals(cleanAt.getValue()); + AtData at; + if (changesKind) { + at = AtData.create(AT_VAL_INVOKE, (MethodInsnNode) candidate.insn()); + } else { + at = candidate.insn() instanceof MethodInsnNode call && cleanAt.getTarget().isPresent() ? cleanAt.withTarget(call) : cleanAt; + } + List selected = context.injections().find(candidate.target(), query.apply(at.withOrdinal(null))); + int ordinal = selected.indexOf(candidate.insn()); + if (ordinal == -1) { + return null; + } + boolean keepsOrdinal = changesKind ? selected.size() > 1 : cleanAt.getOrdinal().isPresent(); + return keepsOrdinal ? at.withOrdinal(ordinal) : at.withOrdinal(null); + } + public static boolean isSame(Configuration a, Configuration b) { return Objects.equals(a.getAtData(), b.getAtData()) && Objects.equals(a.getTargetMethod(), b.getTargetMethod()) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java index fdf3c04d..e7f61114 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java @@ -11,6 +11,7 @@ import java.util.Comparator; import java.util.List; +import java.util.function.Function; public final class LocatingSubResolver implements SubResolver { private final List locators; @@ -26,15 +27,19 @@ public Configuration resolve(MixinContext context, Recipe recipe) { if (targets == null) { return null; } + return locateAll(context, recipe, targets, context.injections().find(targets.clean()), this.locators, + candidate -> Describer.describe(context, recipe, candidate)); + } - List cleanInsns = context.injections().find(targets.clean()); + @Nullable + public static Configuration locateAll(MixinContext context, Recipe recipe, Targets targets, List cleanInsns, + List locators, Function describer) { if (cleanInsns.isEmpty()) { return null; } - Configuration shared = null; for (AbstractInsnNode cleanInsn : cleanInsns) { - Configuration config = locate(context, recipe, targets, cleanInsn); + Configuration config = locate(context, recipe, targets, cleanInsn, locators, describer); if (config == null || shared != null && !Describer.isSame(config, shared)) { return null; } @@ -44,29 +49,29 @@ public Configuration resolve(MixinContext context, Recipe recipe) { } @Nullable - private Configuration locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { - for (Locator locator : this.locators) { + private static Configuration locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn, + List locators, Function describer) { + for (Locator locator : locators) { List candidates = locator.locate(context, recipe, targets, cleanInsn); if (candidates.isEmpty()) { continue; } Candidate candidate = pick(candidates); - Configuration config = candidate != null ? Describer.describe(context, recipe, candidate) : describeAll(context, recipe, candidates); + Configuration config = candidate != null ? describer.apply(candidate) : describeAll(candidates, describer); if (config != null) { context.recordCtxAudit("Located injection point with %s (%s)", locator.getClass().getSimpleName(), candidate != null ? candidate.confidence() : candidates.size() + " candidates with one @At"); } - // A locator that finds several places can't tell which one is right, so don't guess with later locators either return config; } return null; } @Nullable - private static Configuration describeAll(MixinContext context, Recipe recipe, List candidates) { + private static Configuration describeAll(List candidates, Function describer) { Configuration shared = null; for (Candidate candidate : candidates) { - Configuration config = Describer.describe(context, recipe, candidate); + Configuration config = describer.apply(candidate); if (config == null || shared != null && !Describer.isSame(config, shared)) { return null; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/NearestCallLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/NearestCallLocator.java new file mode 100644 index 00000000..9847bee5 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/NearestCallLocator.java @@ -0,0 +1,45 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.OpcodeUtil; + +import java.util.List; + +public final class NearestCallLocator implements Locator { + @Override + public List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { + MethodAlignment alignment = MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()); + List around = alignment.findDirtyAround(cleanInsn); + AbstractInsnNode start = !around.isEmpty() ? around.getFirst() : findAlignedAfter(alignment, cleanInsn); + + for (AbstractInsnNode insn = start; insn != null; insn = insn.getNext()) { + if (insn instanceof MethodInsnNode call) { + return List.of(new Candidate(targets.dirty(), call, MethodAlignment.Confidence.POSITIONAL)); + } + if (insn instanceof JumpInsnNode || OpcodeUtil.isReturnOpcode(insn.getOpcode())) { + break; + } + } + return List.of(); + } + + @Nullable + private static AbstractInsnNode findAlignedAfter(MethodAlignment alignment, AbstractInsnNode cleanInsn) { + for (AbstractInsnNode insn = cleanInsn.getNext(); insn != null; insn = insn.getNext()) { + if (insn.getOpcode() >= 0) { + AbstractInsnNode dirty = alignment.resolve(insn); + if (dirty != null) { + return dirty; + } + } + } + return null; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java index 5a7b6594..5d52637f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java @@ -1,15 +1,21 @@ package org.sinytra.adapter.pipeline.locate; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.sinytra.adapter.analysis.compare.MethodAlignment; import org.sinytra.adapter.analysis.method.InheritanceHandler; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.OpcodeUtil; +import java.util.HashSet; import java.util.List; +import java.util.Set; public final class SameMethodLocator implements Locator { @Override @@ -22,6 +28,11 @@ public List locate(MixinContext context, Recipe recipe, Targets targe .toList(); } + if (OpcodeUtil.isReturnOpcode(cleanInsn.getOpcode())) { + AbstractInsnNode dirtyReturn = findReturnReached(alignment, cleanInsn); + return dirtyReturn != null ? List.of(new Candidate(targets.dirty(), dirtyReturn, MethodAlignment.Confidence.POSITIONAL)) : List.of(); + } + if (!(cleanInsn instanceof MethodInsnNode cleanCall)) { return List.of(); } @@ -31,6 +42,35 @@ public List locate(MixinContext context, Recipe recipe, Targets targe .toList(); } + @Nullable + private static AbstractInsnNode findReturnReached(MethodAlignment alignment, AbstractInsnNode cleanReturn) { + AbstractInsnNode dirty = null; + for (AbstractInsnNode insn = cleanReturn.getPrevious(); insn != null && dirty == null; insn = insn.getPrevious()) { + if (insn instanceof JumpInsnNode || OpcodeUtil.isReturnOpcode(insn.getOpcode()) || insn.getOpcode() == Opcodes.ATHROW) { + return null; + } + if (insn.getOpcode() >= 0) { + dirty = alignment.resolve(insn); + } + } + if (dirty == null) { + return null; + } + + Set visited = new HashSet<>(); + for (AbstractInsnNode insn = dirty.getNext(); insn != null && visited.add(insn); insn = insn.getNext()) { + if (insn.getOpcode() == cleanReturn.getOpcode()) { + return insn; + } + if (insn instanceof JumpInsnNode jump && jump.getOpcode() == Opcodes.GOTO) { + insn = jump.label; + } else if (insn instanceof JumpInsnNode || OpcodeUtil.isReturnOpcode(insn.getOpcode()) || insn.getOpcode() == Opcodes.ATHROW) { + return null; + } + } + return null; + } + private static boolean isReplacement(MixinContext context, MethodInsnNode clean, MethodInsnNode dirty) { return clean.owner.equals(dirty.owner) && clean.name.equals(dirty.name) || clean.name.equals(dirty.name) && clean.desc.equals(dirty.desc) && isRelated(context, clean.owner, dirty.owner) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java deleted file mode 100644 index 2b01ed67..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ComparingInjectionPointResolver.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.injection; - -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.compare.MethodLabelComparator; -import org.sinytra.adapter.analysis.injection.InjectionQuery; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.Relocation; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.Targets; -import org.sinytra.adapter.model.ann.AtData; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.MutableConfiguration; -import org.sinytra.adapter.model.config.key.IntentKeys; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.SubResolver; - -import java.util.List; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; - -public abstract class ComparingInjectionPointResolver implements SubResolver { - - public static class Inject extends ComparingInjectionPointResolver { - @Nullable - @Override - public Configuration resolve(MixinContext context, Recipe recipe) { - Targets targets = recipe.targets(); - if (targets == null) return null; - - AbstractInsnNode cleanInsn = recipe.getCleanInjectionInsn(); - MethodLabelComparator.ComparisonResult comparisonResult = MethodLabelComparator.findPatchedLabels(cleanInsn, recipe); - if (comparisonResult == null) return null; - - List> hunkLabels = comparisonResult.patchedLabels(); - - ClassNode cleanTargetClass = targets.clean().classNode(); - for (List insns : hunkLabels) { - for (AbstractInsnNode insn : insns) { - if (insn instanceof MethodInsnNode minsn && minsn.getOpcode() == Opcodes.INVOKESTATIC && !minsn.owner.equals(cleanTargetClass.name)) { - Configuration result = attemptExtractMixin(minsn, context, recipe.clean()); - if (result != null) { - return result; - } - } - } - } - - // If extraction fails, fall back to injecting at the nearest method call in dirty code - for (List insns : hunkLabels) { - for (AbstractInsnNode insn : insns) { - if (insn instanceof MethodInsnNode minsn) { - return MutableConfiguration.create() - .setAtData(AtData.create(AT_VAL_INVOKE, minsn)); - } - } - } - return null; - } - - private static Configuration attemptExtractMixin(MethodInsnNode minsn, MixinContext context, Configuration clean) { - // Looks like some code was moved into a static method outside this class - // Attempt extracting mixin - TargetPair target = context.dirtyMethods().resolveDeclared(MethodQualifier.create(minsn)); - if (target == null) return null; - -// MethodUpgrader.adjustInjectorOrdinalForNewMethod(minsn, context.legacy()); FIXME - List newTargetInsns = context.injections().find(target, InjectionQuery.current().ignoringOrdinal()); - if (newTargetInsns.isEmpty()) return null; - - return MutableConfiguration.create() - .setTargetClass(target.classNode().name) - .setTargetMethod(minsn) - .setAtData(clean.getAtData().withOrdinal(null)) // TODO Find new ordinal - .setProperty(IntentKeys.RELOCATION, new Relocation.CalledFrom(minsn)); - } - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java index 2030dae8..8daa77ff 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java @@ -5,10 +5,12 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.locate.CalledMethodLocator; +import org.sinytra.adapter.pipeline.locate.Candidate; import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; @@ -17,9 +19,10 @@ import java.util.List; public class InjectionPointResolver extends CompoundResolver { + private static final SameMethodLocator SAME_METHOD = new SameMethodLocator(); public InjectionPointResolver() { - addSubResolver(new LocatingSubResolver(List.of(new SameMethodLocator(), new CalledMethodLocator(), new ReplacedCallLocator()))); + addSubResolver(new LocatingSubResolver(List.of(SAME_METHOD, new CalledMethodLocator(), new ReplacedCallLocator()))); } @Override @@ -43,11 +46,28 @@ protected Configuration tryReuse(MixinContext context, Recipe recipe) { // Try reusing the original List insns = context.injections().find(dirtyTarget); - if (!insns.isEmpty()) { + if (!insns.isEmpty() && selectsLocatedInsns(context, recipe, dirtyTarget, insns)) { return dirty.copyClean() .inheritAtData(); } return null; } + + private static boolean selectsLocatedInsns(MixinContext context, Recipe recipe, TargetPair dirtyTarget, List dirtyInsns) { + TargetPair cleanTarget = recipe.getCleanTarget(); + if (cleanTarget == null || !cleanTarget.methodNode().name.equals(dirtyTarget.methodNode().name) + || !cleanTarget.methodNode().desc.equals(dirtyTarget.methodNode().desc) + ) { + return true; + } + Targets targets = new Targets(cleanTarget, dirtyTarget); + for (AbstractInsnNode cleanInsn : context.injections().find(cleanTarget)) { + List candidates = SAME_METHOD.locate(context, recipe, targets, cleanInsn); + if (!candidates.isEmpty() && candidates.stream().noneMatch(c -> dirtyInsns.contains(c.insn()))) { + return false; + } + } + return true; + } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java index 0f5cd1fd..d95d2275 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java @@ -5,15 +5,10 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.compare.InsnComparator; -import org.sinytra.adapter.analysis.compare.InstructionMatcher; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.mixin.MixinBridge.LocalCandidate; -import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.key.MixinKeys; @@ -23,20 +18,13 @@ import org.sinytra.adapter.pipeline.resolver.Resolver; import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.GeneratedVariables; -import org.sinytra.adapter.util.OpcodeUtil; import org.sinytra.adapter.util.SingleValueHandle; import java.util.*; import java.util.function.Function; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_RETURN; public class InjectorOrdinalResolver implements Resolver { - // INVOKE ordinals are handled by the locators, see LocatingSubResolver - private static final Map OFFSET_HANDLERS = Map.of( - AT_VAL_RETURN, ReturnOffsetHandler.INSTANCE - ); - @Override public ResolutionResult resolve(MixinContext context, Recipe recipe) { Type returnType = recipe.clean().getReturnType(); @@ -62,22 +50,8 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { private static List> getOffsetHandlers(MixinContext context, Recipe recipe, Type returnType) { Configuration cleanConfig = recipe.clean(); - AtData at = cleanConfig.getAtData(); List> handlers = new ArrayList<>(); - at.getProperty(AtData.Keys.ORDINAL).ifPresent(ordinal -> { - String target = at.getTarget().orElse(null); - OffsetUpdateHandler handler = OFFSET_HANDLERS.get(at.getValue()); - if (handler != null) { - OffsetUpdateHandler.Context handleContext = new OffsetUpdateHandler.Context(target, ordinal); - HandlerInstance instance = new HandlerInstance<>(handler, handleContext, i -> - MutableConfiguration.create() - .setAtData(at.withOrdinal(i)) - ); - handlers.add(instance); - } - }); - if (context.hasFlag(MixinFlag.TARGETS_VARIABLE)) { LocalVariableLookup cleanTable = recipe.cleanLocalsTable(); TargetPair cleanTarget = recipe.getCleanTarget(); @@ -116,11 +90,6 @@ private interface UpdateHandler { Optional apply(MixinContext mixinContext, Recipe recipe, T context); } - private interface OffsetUpdateHandler extends UpdateHandler { - record Context(@Nullable String target, int ordinal) { - } - } - private record LocalVar(LocalVariableNode lvn, int ordinal, boolean relative) { public LocalVar(LocalVariableNode lvn, int ordinal) { this(lvn, ordinal, false); @@ -140,98 +109,6 @@ public Configuration apply(MixinContext mixinContext, Recipe recipe) { } } - private static class ReturnOffsetHandler implements OffsetUpdateHandler { - public static final OffsetUpdateHandler INSTANCE = new ReturnOffsetHandler(); - - @Override - public Optional apply(MixinContext mixinContext, Recipe recipe, Context context) { - TargetPair cleanTarget = recipe.getCleanTarget(); - TargetPair dirtyTarget = recipe.getDirtyTarget(); - int ordinal = context.ordinal(); - - List cleanReturnInsns = MethodAnalyzer.findReturnInsns(cleanTarget.methodNode()); - List dirtyReturnInsns = MethodAnalyzer.findReturnInsns(dirtyTarget.methodNode()); - - if (cleanReturnInsns.size() == dirtyReturnInsns.size()) { - return Optional.empty(); - } - - if (ordinal < cleanReturnInsns.size()) { - AbstractInsnNode cleanInsn = cleanReturnInsns.get(ordinal); - InstructionMatcher original = new InstructionMatcher(cleanInsn, findReturnPrecedingInsns(cleanInsn), List.of()); - List dirtyMatchers = dirtyReturnInsns.stream() - .map(i -> new InstructionMatcher(i, findReturnPrecedingInsns(i), List.of())) - .toList(); - Optional match = AdapterUtil.findOne(dirtyMatchers, m -> original.test(m, InsnComparator.IGNORE_VAR_INDEX)); - if (match.isPresent()) { - return match.map(dirtyMatchers::indexOf); - } - } - - return findByMethodCalls(ordinal, cleanTarget, dirtyTarget); - } - - private static Optional findByMethodCalls(int ordinal, TargetPair cleanTarget, TargetPair dirtyTarget) { - List>> cleanCalls = getBlockCalls(cleanTarget.methodNode()); - List>> dirtyCalls = getBlockCalls(dirtyTarget.methodNode()); - - if (ordinal >= cleanCalls.size()) { - return Optional.empty(); - } - - List cleanCall = cleanCalls.get(ordinal).getSecond(); - for (int i = 0; i < dirtyCalls.size(); i++) { - Pair> dirtyCall = dirtyCalls.get(i); - boolean haveCommon = !Collections.disjoint(cleanCall, dirtyCall.getSecond()); - if (haveCommon) { - return Optional.of(i); - } - } - - return Optional.empty(); - } - - private static List>> getBlockCalls(MethodNode methodNode) { - List>> blockCalls = new ArrayList<>(); - - List minsns = new ArrayList<>(); - for (AbstractInsnNode insn : methodNode.instructions) { - if (insn instanceof MethodInsnNode minsn) { - minsns.add(MethodQualifier.create(minsn).asDescriptor()); - } - if (OpcodeUtil.isReturnOpcode(insn.getOpcode())) { - blockCalls.add(Pair.of((InsnNode) insn, minsns)); - minsns = new ArrayList<>(); - } - } - - // Remove non-unique calls - List> values = blockCalls.stream() - .map(Pair::getSecond) - .toList(); - Map counts = new HashMap<>(); - for (List list : values) { - for (String s : list) { - counts.merge(s, 1, Integer::sum); - } - } - for (List list : values) { - list.removeIf(s -> counts.getOrDefault(s, 0) > 1); - } - - return blockCalls; - } - - private static List findReturnPrecedingInsns(AbstractInsnNode insn) { - return InsnTraversal.walk(insn.getPrevious(), InsnTraversal.BACKWARD) - .takeWhile(prev -> !OpcodeUtil.isReturnOpcode(prev.getOpcode())) - .filter(prev -> !(prev instanceof LabelNode) && !InsnTraversal.isDebugNode(prev)) - .limit(6) - .toList() - .reversed(); - } - } - private static class ModifyVariableOffsetHandler implements UpdateHandler> { private static final ModifyVariableOffsetHandler INSTANCE = new ModifyVariableOffsetHandler(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java index 16b75431..622409f0 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java @@ -2,108 +2,79 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.analysis.injection.InjectionQuery; +import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.ann.SliceData; +import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.locate.Describer; +import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; +import org.sinytra.adapter.pipeline.locate.Locator; +import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; +import org.sinytra.adapter.pipeline.locate.SameMethodLocator; import org.sinytra.adapter.pipeline.resolver.Resolver; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import static org.sinytra.adapter.model.config.key.MixinKeys.SLICE; import static org.sinytra.adapter.model.config.key.MixinKeys.SLICES; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; public class SliceBoundaryResolver implements Resolver { + private static final List LOCATORS = List.of(new SameMethodLocator(), new ReplacedCallLocator()); + @Override public ResolutionResult resolve(MixinContext context, Recipe recipe) { - TargetPair dirtyTarget = recipe.getDirtyTarget(); - if (dirtyTarget == null) return ResolutionResult.pass(); + Targets targets = recipe.targets(); + if (targets == null) return ResolutionResult.pass(); SliceData slice = recipe.clean().getProperty(SLICE).orElse(null); if (slice != null) { - if (passesSliceCheck(slice, context, dirtyTarget)) - return ResolutionResult.pass(); - - SliceData fixedSlice = fixSliceData(slice, dirtyTarget.methodNode()); - if (fixedSlice == null) - return ResolutionResult.pass(); - - return ResolutionResult.success(MutableConfiguration.create().setProperty(SLICE, fixedSlice)); + SliceData fixed = fixSlice(context, recipe, targets, slice); + return fixed != null && fixed != slice ? ResolutionResult.success(MutableConfiguration.create().setProperty(SLICE, fixed)) : ResolutionResult.pass(); } List slices = recipe.clean().getProperty(SLICES).orElse(null); - if (slices == null || slices.isEmpty()) - return ResolutionResult.pass(); + if (slices == null || slices.isEmpty()) return ResolutionResult.pass(); List fixedSlices = new ArrayList<>(); boolean changed = false; for (SliceData s : slices) { - if (passesSliceCheck(s, context, dirtyTarget)) { - fixedSlices.add(s); - continue; - } - SliceData fixedSlice = fixSliceData(s, dirtyTarget.methodNode()); - if (fixedSlice == null) - return ResolutionResult.pass(); - fixedSlices.add(fixedSlice); - changed = true; + SliceData fixed = fixSlice(context, recipe, targets, s); + if (fixed == null) return ResolutionResult.pass(); + fixedSlices.add(fixed); + changed |= fixed != s; } - if (!changed) - return ResolutionResult.pass(); - - return ResolutionResult.success(MutableConfiguration.create().setProperty(SLICES, fixedSlices)); + return changed ? ResolutionResult.success(MutableConfiguration.create().setProperty(SLICES, fixedSlices)) : ResolutionResult.pass(); } @Nullable - private static SliceData fixSliceData(SliceData slice, MethodNode dirtyMethod) { - AtData from = slice.from(); - if (from != null) { - from = fixSlideInjectionPoint(from, dirtyMethod).orElse(null); - if (from == null) return null; - } - - AtData to = slice.to(); - if (to != null) { - to = fixSlideInjectionPoint(to, dirtyMethod).orElse(null); - if (to == null) return null; + private static SliceData fixSlice(MixinContext context, Recipe recipe, Targets targets, SliceData slice) { + AtData from = fixBoundary(context, recipe, targets, slice.from()); + AtData to = fixBoundary(context, recipe, targets, slice.to()); + if (slice.from() != null && from == null || slice.to() != null && to == null) { + return null; } - - return new SliceData(slice.id(), from, to); + return from == slice.from() && to == slice.to() ? slice : new SliceData(slice.id(), from, to); } - private static Optional fixSlideInjectionPoint(AtData boundary, MethodNode dirtyMethod) { - // Require only INVOKE values - if (!AT_VAL_INVOKE.equals(boundary.getValue())) - return Optional.empty(); - - // Find original target invocation qualifier - MethodQualifier target = boundary.getTargetQualifier().orElse(null); - if (target == null) return Optional.empty(); - - // Find method invocations with a matching name - List candidates = new ArrayList<>(); - for (AbstractInsnNode insn : dirtyMethod.instructions) { - if (insn instanceof MethodInsnNode minsn && minsn.name.equals(target.name())) { - candidates.add(minsn); - } + @Nullable + private static AtData fixBoundary(MixinContext context, Recipe recipe, Targets targets, @Nullable AtData boundary) { + if (boundary == null || context.injections().matches(targets.dirty(), InjectionQuery.sliceBoundary(boundary))) { + return boundary; } - if (candidates.size() != 1) return Optional.empty(); - MethodInsnNode insn = candidates.getFirst(); - return Optional.of(boundary.withTarget(insn)); - } - - private static boolean passesSliceCheck(SliceData slice, MixinContext context, TargetPair dirtyTarget) { - InsnList insns = context.injections().slice(dirtyTarget, slice); - return insns.size() != dirtyTarget.methodNode().instructions.size(); + List cleanInsns = context.injections().find(targets.clean(), InjectionQuery.of(boundary).ignoringSlice()); + Configuration located = LocatingSubResolver.locateAll(context, recipe, targets, cleanInsns, LOCATORS, candidate -> { + if (candidate.target() != targets.dirty()) { + return null; + } + AtData at = Describer.describeAt(context, boundary, candidate, a -> InjectionQuery.of(a).ignoringSlice()); + return at != null ? MutableConfiguration.create().setAtData(at) : null; + }); + return located != null ? located.getAtData() : null; } } From 384fcf23842d57fc926586ce2ce3c0dfb2c6ebd3 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 00:29:57 +0200 Subject: [PATCH 22/44] Add local index resolver --- .../analysis/locals/LocalAlignment.java | 42 +++ .../analysis/locals/LocalsAnalyzer.java | 5 + .../analysis/method/MethodInsnMatcher.java | 37 --- .../sinytra/adapter/mixin/MixinBridge.java | 7 +- .../constant/MixinAnnotationConstants.java | 1 + .../injector/ModifyVariableMixin.java | 7 +- .../pipeline/locate/CalledMethodLocator.java | 2 +- .../adapter/pipeline/locate/Describer.java | 109 ++++++- .../pipeline/locate/LocatingSubResolver.java | 8 +- .../ModifyVarInjectionPointSubResolver.java | 72 ----- .../special/InjectorOrdinalResolver.java | 299 ------------------ .../resolver/special/LocalResolver.java | 80 +++++ .../adapter/util/GeneratedVariables.java | 20 -- 13 files changed, 251 insertions(+), 438 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/analysis/locals/LocalAlignment.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalAlignment.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalAlignment.java new file mode 100644 index 00000000..f31deb9f --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalAlignment.java @@ -0,0 +1,42 @@ +package org.sinytra.adapter.analysis.locals; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; + +import java.util.function.UnaryOperator; + +public final class LocalAlignment { + private final MethodAlignment alignment; + + private LocalAlignment(MethodAlignment alignment) { + this.alignment = alignment; + } + + public static LocalAlignment of(MethodAlignment alignment) { + return new LocalAlignment(alignment); + } + + public int resolve(int cleanSlot, AbstractInsnNode cleanInsn) { + int slot = findAlignedUse(cleanSlot, cleanInsn, AbstractInsnNode::getNext); + return slot != -1 ? slot : findAlignedUse(cleanSlot, cleanInsn.getPrevious(), AbstractInsnNode::getPrevious); + } + + private int findAlignedUse(int slot, @Nullable AbstractInsnNode start, UnaryOperator direction) { + for (AbstractInsnNode insn = start; insn != null; insn = direction.apply(insn)) { + if (getSlot(insn) == slot) { + AbstractInsnNode dirty = this.alignment.resolve(insn); + if (dirty != null && dirty.getOpcode() == insn.getOpcode()) { + return getSlot(dirty); + } + } + } + return -1; + } + + private static int getSlot(AbstractInsnNode insn) { + return insn instanceof VarInsnNode var ? var.var : insn instanceof IincInsnNode iinc ? iinc.var : -1; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index 438e8806..082b188a 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -192,6 +192,11 @@ public List findSelectableLocals(TargetPair target, AbstractInsn .findLocalCandidates(type, insn, false); } + public int resolveSelectedLocal(TargetPair target, AbstractInsnNode insn, Type type) { + return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment().refmapHolder()) + .findLocal(this.context.methodAnnotation().unwrap(), type, insn); + } + public int resolveLocalOrdinal(TargetPair target, AbstractInsnNode insn, Type type, int index) { return findSelectableLocals(target, insn, type).stream() .filter(c -> c.index() == index) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodInsnMatcher.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodInsnMatcher.java index dc96595a..4e360450 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodInsnMatcher.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodInsnMatcher.java @@ -12,11 +12,6 @@ public class MethodInsnMatcher { private static final UnaryOperator FORWARD = InsnTraversal.FORWARD; private static final UnaryOperator BACKWARDS = InsnTraversal.BACKWARD; - private static final int INSN_RANGE = 5; - - public static InstructionMatcher findSurroundingInstructions(AbstractInsnNode insn) { - return findSurroundingInstructions(insn, INSN_RANGE); - } public static InstructionMatcher findSurroundingInstructions(AbstractInsnNode insn, int range) { LabelNode previousLabel = findFirstLabelInsn(insn, BACKWARDS); @@ -28,38 +23,6 @@ public static InstructionMatcher findSurroundingInstructions(AbstractInsnNode in return new InstructionMatcher(insn, previousInsns, nextInsns); } - public static InstructionMatcher findBackwardsInstructions(AbstractInsnNode insn) { - return findBackwardsInstructions(insn, INSN_RANGE); - } - - public static InstructionMatcher findBackwardsInstructions(AbstractInsnNode insn, int range) { - LabelNode previousLabel = findFirstLabelInsn(insn, BACKWARDS); - List previousInsns = getInsns(previousLabel, range, BACKWARDS); - - return new InstructionMatcher(insn, previousInsns, List.of()); - } - - public static InstructionMatcher findForwardInstructions(AbstractInsnNode insn) { - return findForwardInstructions(insn, INSN_RANGE); - } - - public static InstructionMatcher findForwardInstructions(AbstractInsnNode insn, int range) { - LabelNode nextLabel = findFirstLabelInsn(insn, FORWARD); - List nextInsns = getInsns(nextLabel, range, FORWARD); - - return new InstructionMatcher(insn, List.of(), nextInsns); - } - - public static InstructionMatcher findForwardInstructionsDirect(AbstractInsnNode insn) { - return findForwardInstructionsDirect(insn, INSN_RANGE); - } - - public static InstructionMatcher findForwardInstructionsDirect(AbstractInsnNode insn, int range) { - List nextInsns = getInsns(insn, range, FORWARD); - - return new InstructionMatcher(insn, List.of(), nextInsns); - } - @Nullable private static LabelNode findFirstLabelInsn(AbstractInsnNode insn, UnaryOperator operator) { return InsnTraversal.walk(insn, operator) diff --git a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java index 6bdc2f74..cc2700f1 100644 --- a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java +++ b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java @@ -18,6 +18,7 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.code.ISliceContext; import org.spongepowered.asm.mixin.injection.code.MethodSlice; +import org.spongepowered.asm.mixin.injection.modify.InvalidImplicitDiscriminatorException; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; import org.spongepowered.asm.mixin.injection.points.BeforeConstant; import org.spongepowered.asm.mixin.injection.selectors.ISelectorContext; @@ -184,7 +185,11 @@ public InsnList slice(AnnotationNode slice) { public int findLocal(AnnotationNode selector, Type type, AbstractInsnNode insn) { LocalVariableDiscriminator discriminator = LocalVariableDiscriminator.parse(selector); - return discriminator.findLocal(localsContext(type, discriminator.isArgsOnly(), insn)); + try { + return discriminator.findLocal(localsContext(type, discriminator.isArgsOnly(), insn)); + } catch (InvalidImplicitDiscriminatorException e) { + return -1; + } } public List findLocalCandidates(Type type, AbstractInsnNode insn, boolean argsOnly) { diff --git a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java index 2cb14a6f..3c8d765d 100644 --- a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java @@ -9,6 +9,7 @@ public class MixinAnnotationConstants { public static final String AT_VAL_INVOKE_ASSIGN = "INVOKE_ASSIGN"; public static final String AT_VAL_RETURN = "RETURN"; public static final String AT_VAL_STORE = "STORE"; + public static final String AT_VAL_LOAD = "LOAD"; public static final String AT_VAL_CONST = "CONSTANT"; public static final String AT_VAL_SINYTRA_INSTANCEOF = "sinytra:INSTANCEOF"; public static final String AT_SHIFT = "shift"; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index 37c40d33..297f62a2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -20,8 +20,7 @@ import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.pipeline.resolver.injection.ModifyVarInjectionPointSubResolver; -import org.sinytra.adapter.pipeline.resolver.special.InjectorOrdinalResolver; +import org.sinytra.adapter.pipeline.resolver.special.LocalResolver; import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; import org.sinytra.adapter.pipeline.rewrite.ShiftedVariableToExpression; @@ -57,9 +56,7 @@ public HandlerSignature signature() { public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers .addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new ShiftedVariableToExpression()))) - .addBefore(InjectionPointResolver.class, new InjectorOrdinalResolver()); - resolvers.getOrThrow(InjectionPointResolver.class) - .addSubResolver(new ModifyVarInjectionPointSubResolver()); + .addAfter(InjectionPointResolver.class, new LocalResolver()); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java index d3bb4d1a..c6f6d1e1 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java @@ -73,7 +73,7 @@ public List locate(MixinContext context, Recipe recipe, Targets targe /// {@return the instructions the clean `@At` selects in the callee, matched to the clean instruction where the alignment can} private static List findIn(MixinContext context, Targets targets, AtData cleanAt, AbstractInsnNode cleanInsn, Callee callee) { - List selected = context.injections().find(callee.method(), InjectionQuery.of(cleanAt.withOrdinal(null))); + List selected = Describer.findSelectable(context, callee.method(), cleanAt, InjectionQuery::of); if (selected.isEmpty()) { return List.of(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java index 49e2a9bb..844f1fb1 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -1,22 +1,36 @@ package org.sinytra.adapter.pipeline.locate; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.sinytra.adapter.analysis.InsnTraversal; import org.sinytra.adapter.analysis.injection.InjectionQuery; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.key.IntentKeys; +import org.sinytra.adapter.model.config.key.MixinKeys; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.util.OpcodeUtil; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.Function; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_LOAD; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_STORE; public final class Describer { @Nullable @@ -28,6 +42,15 @@ public static Configuration describe(MixinContext context, Recipe recipe, Candid } MutableConfiguration config = MutableConfiguration.create().setAtData(described); + if (isLocalAt(context, described) && candidate.insn() instanceof VarInsnNode var) { + AbstractInsnNode position = OpcodeUtil.isStoreOpcode(var.getOpcode()) ? var.getNext() : var; + MutableConfiguration local = describeLocal(context, recipe, candidate.target(), position, var.var); + if (local == null) { + return null; + } + config.mergeFrom(local); + } + if (candidate.target() != recipe.getDirtyTarget()) { config.setTargetMethod(candidate.target().methodNode()); String targetClass = candidate.target().classNode().name; @@ -43,6 +66,19 @@ public static Configuration describe(MixinContext context, Recipe recipe, Candid @Nullable public static AtData describeAt(MixinContext context, AtData cleanAt, Candidate candidate, Function query) { + if (isLocalAt(context, cleanAt)) { + if (!(candidate.insn() instanceof VarInsnNode var)) { + return null; + } + List uses = new ArrayList<>(); + for (AbstractInsnNode insn : candidate.target().methodNode().instructions) { + if (insn instanceof VarInsnNode use && use.getOpcode() == var.getOpcode() && use.var == var.var) { + uses.add(use); + } + } + return cleanAt.getOrdinal().isPresent() ? cleanAt.withOrdinal(uses.indexOf(var)) : cleanAt; + } + boolean changesKind = candidate.insn() instanceof MethodInsnNode && !AT_VAL_INVOKE.equals(cleanAt.getValue()) && !AT_VAL_INVOKE_ASSIGN.equals(cleanAt.getValue()); AtData at; if (changesKind) { @@ -59,10 +95,81 @@ public static AtData describeAt(MixinContext context, AtData cleanAt, Candidate return keepsOrdinal ? at.withOrdinal(ordinal) : at.withOrdinal(null); } + @Nullable + public static MutableConfiguration describeLocal(MixinContext context, Recipe recipe, TargetPair target, AbstractInsnNode insn, int slot) { + Configuration clean = recipe.clean(); + Type type = Type.getReturnType(context.methodNode().desc); + MutableConfiguration config = MutableConfiguration.create(); + + boolean argsOnly = clean.getProperty(MixinKeys.ARGS_ONLY).orElse(false); + if (argsOnly && !isParameter(target.methodNode(), slot)) { + argsOnly = false; + config.setProperty(MixinKeys.ARGS_ONLY, false); + } + + if (clean.hasProperty(MixinKeys.INDEX)) { + return config.setProperty(MixinKeys.INDEX, slot); + } + if (clean.hasProperty(MixinKeys.ORDINAL)) { + int ordinal = argsOnly ? getParameterOrdinal(target.methodNode(), slot, type) : context.locals().resolveLocalOrdinal(target, insn, type, slot); + return ordinal != -1 ? config.setProperty(MixinKeys.ORDINAL, ordinal) : null; + } + return config; + } + + public static List findSelectable(MixinContext context, TargetPair target, AtData at, Function query) { + if (!isLocalAt(context, at)) { + return context.injections().find(target, query.apply(at.withOrdinal(null))); + } + int opcode = Type.getReturnType(context.methodNode().desc).getOpcode(AT_VAL_STORE.equals(at.getValue()) ? Opcodes.ISTORE : Opcodes.ILOAD); + List uses = new ArrayList<>(); + for (AbstractInsnNode insn : target.methodNode().instructions) { + if (insn.getOpcode() == opcode) { + uses.add(insn); + } + } + return uses; + } + + public static AbstractInsnNode toLocatable(MixinContext context, AtData at, AbstractInsnNode selected) { + if (isLocalAt(context, at) && AT_VAL_STORE.equals(at.getValue()) && !OpcodeUtil.isStoreOpcode(selected.getOpcode())) { + AbstractInsnNode store = InsnTraversal.find(selected, InsnTraversal.BACKWARD, insn -> insn.getOpcode() >= 0); + if (store != null && OpcodeUtil.isStoreOpcode(store.getOpcode())) { + return store; + } + } + return selected; + } + public static boolean isSame(Configuration a, Configuration b) { return Objects.equals(a.getAtData(), b.getAtData()) && Objects.equals(a.getTargetMethod(), b.getTargetMethod()) - && Objects.equals(a.getTargetClass(), b.getTargetClass()); + && Objects.equals(a.getTargetClass(), b.getTargetClass()) + && Objects.equals(a.getProperty(MixinKeys.ORDINAL), b.getProperty(MixinKeys.ORDINAL)) + && Objects.equals(a.getProperty(MixinKeys.INDEX), b.getProperty(MixinKeys.INDEX)); + } + + private static boolean isLocalAt(MixinContext context, AtData at) { + return context.hasFlag(MixinFlag.TARGETS_VARIABLE) && (AT_VAL_STORE.equals(at.getValue()) || AT_VAL_LOAD.equals(at.getValue())); + } + + private static boolean isParameter(MethodNode method, int slot) { + return slot < (MethodAnalyzer.isStatic(method) ? 0 : 1) + Arrays.stream(Type.getArgumentTypes(method.desc)).mapToInt(Type::getSize).sum(); + } + + private static int getParameterOrdinal(MethodNode method, int slot, Type type) { + int current = MethodAnalyzer.isStatic(method) ? 0 : 1; + int ordinal = 0; + for (Type arg : Type.getArgumentTypes(method.desc)) { + if (current == slot) { + return arg.equals(type) ? ordinal : -1; + } + if (arg.equals(type)) { + ordinal++; + } + current += arg.getSize(); + } + return -1; } private Describer() { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java index e7f61114..3fb3c5f7 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java @@ -4,6 +4,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.sinytra.adapter.analysis.compare.MethodAlignment; import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -27,8 +28,11 @@ public Configuration resolve(MixinContext context, Recipe recipe) { if (targets == null) { return null; } - return locateAll(context, recipe, targets, context.injections().find(targets.clean()), this.locators, - candidate -> Describer.describe(context, recipe, candidate)); + AtData cleanAt = recipe.clean().getAtData(); + List cleanInsns = context.injections().find(targets.clean()).stream() + .map(insn -> cleanAt != null ? Describer.toLocatable(context, cleanAt, insn) : insn) + .toList(); + return locateAll(context, recipe, targets, cleanInsns, this.locators, candidate -> Describer.describe(context, recipe, candidate)); } @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java deleted file mode 100644 index d6ed71d7..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/ModifyVarInjectionPointSubResolver.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.injection; - -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.LocalVariableNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.VarInsnNode; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.Targets; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.SubResolver; - -import java.util.List; - -import static org.sinytra.adapter.model.config.key.MixinKeys.ORDINAL; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_STORE; - -public class ModifyVarInjectionPointSubResolver implements SubResolver { - - @Nullable - @Override - public Configuration resolve(MixinContext context, Recipe recipe) { - if (!recipe.clean().getAtData().getValue().equals(AT_VAL_STORE)) return null; - Targets targets = recipe.targets(); - if (targets == null) return null; - - // Find replacement - Integer ordinal = recipe.clean().getProperty(ORDINAL).orElse(null); - if (ordinal == null) return null; - - Type varType = Type.getReturnType(context.methodNode().desc); - - LocalVariableLookup lookup = recipe.cleanLocalsTable(); - AbstractInsnNode cleanInsn = recipe.getCleanInjectionInsn(); - if (lookup == null || cleanInsn == null) return null; - - int cleanIndex = context.locals().resolveLocalIndex(targets.clean(), cleanInsn, varType, ordinal); - LocalVariableNode desired = lookup.getInScopeOrNull(cleanIndex, cleanInsn); - if (desired == null) return null; - - // Find variable initializer insns - InsnList desiredInitializerInsns = LocalsAnalyzer.findInitializerInsns(targets.clean().methodNode(), desired.index); - - // Get all matching variables - for (MethodNode method : targets.dirty().classNode().methods) { - LocalVariableLookup dirtyLookup = LocalVariableLookup.of(method); - if (dirtyLookup == null) continue; - - LocalVariableNode match = LocalsAnalyzer.findLocalWithInitializer(method, desired.desc, desiredInitializerInsns).orElse(null); - if (match != null) { - List stores = LocalsAnalyzer.findStoreInsns(method, match.index); - if (stores.isEmpty()) continue; - - TargetPair candidate = new TargetPair(targets.dirty().classNode(), method); - int dirtyOrdinal = context.locals().resolveLocalOrdinal(candidate, stores.getFirst().getNext(), varType, match.index); - if (dirtyOrdinal == -1) continue; - - return recipe.dirty().copyClean() - .setProperty(ORDINAL, dirtyOrdinal) - .setTargetMethod(method) - .inheritAtData(); - } - } - return null; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java deleted file mode 100644 index d95d2275..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/InjectorOrdinalResolver.java +++ /dev/null @@ -1,299 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.special; - -import com.mojang.datafixers.util.Pair; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; -import org.sinytra.adapter.mixin.MixinBridge.LocalCandidate; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.MutableConfiguration; -import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.injector.MixinFlag; -import org.sinytra.adapter.pipeline.resolver.Resolver; -import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.GeneratedVariables; -import org.sinytra.adapter.util.SingleValueHandle; - -import java.util.*; -import java.util.function.Function; - - -public class InjectorOrdinalResolver implements Resolver { - @Override - public ResolutionResult resolve(MixinContext context, Recipe recipe) { - Type returnType = recipe.clean().getReturnType(); - if (returnType == null) return ResolutionResult.pass(); - - List> offsetHandlers = getOffsetHandlers(context, recipe, returnType); - if (offsetHandlers.isEmpty()) return ResolutionResult.pass(); - - if (recipe.targets() == null) return ResolutionResult.pass(); - - boolean applied = false; - MutableConfiguration merged = MutableConfiguration.create(); - for (HandlerInstance instance : offsetHandlers) { - Configuration config = instance.apply(context, recipe); - if (config != null) { - applied = true; - merged.mergeFrom(config); - } - } - - return applied ? ResolutionResult.success(merged) : ResolutionResult.pass(); - } - - private static List> getOffsetHandlers(MixinContext context, Recipe recipe, Type returnType) { - Configuration cleanConfig = recipe.clean(); - List> handlers = new ArrayList<>(); - - if (context.hasFlag(MixinFlag.TARGETS_VARIABLE)) { - LocalVariableLookup cleanTable = recipe.cleanLocalsTable(); - TargetPair cleanTarget = recipe.getCleanTarget(); - AbstractInsnNode cleanInsn = cleanTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)) : null; - if (cleanTable != null && cleanInsn != null) { - // Handle modified ordinals - cleanConfig.getProperty(MixinKeys.ORDINAL) - .flatMap(ordinal -> Optional.ofNullable(cleanTable.getInScopeOrNull(context.locals().resolveLocalIndex(cleanTarget, cleanInsn, returnType, ordinal), cleanInsn)) - .map(lvn -> new LocalVar(lvn, ordinal, true)) - .map(local -> new HandlerInstance<>(ModifyVariableOffsetHandler.INSTANCE, local, pair -> - MutableConfiguration.create() - .setProperty(MixinKeys.ORDINAL, pair.getFirst().ordinal()) - .mergeFrom(pair.getSecond()) - ))) - // Handle modified indexes - .or(() -> cleanConfig.getProperty(MixinKeys.INDEX) - .flatMap(i -> Optional.ofNullable(cleanTable.getInScopeOrNull(i, cleanInsn)) - .flatMap(lvn -> { - int ordinal = context.locals().resolveLocalOrdinal(cleanTarget, cleanInsn, Type.getType(lvn.desc), i); - return ordinal == -1 ? Optional.empty() : Optional.of(new LocalVar(lvn, ordinal, false)); - }) - .map(local -> new HandlerInstance<>(ModifyVariableOffsetHandler.INSTANCE, local, pair -> - MutableConfiguration.create() - .setProperty(MixinKeys.INDEX, pair.getFirst().lvn().index) - .mergeFrom(pair.getSecond()) - )) - )) - .ifPresent(handlers::add); - } - } - - return handlers; - } - - private interface UpdateHandler { - Optional apply(MixinContext mixinContext, Recipe recipe, T context); - } - - private record LocalVar(LocalVariableNode lvn, int ordinal, boolean relative) { - public LocalVar(LocalVariableNode lvn, int ordinal) { - this(lvn, ordinal, false); - } - } - - private record HandlerInstance(UpdateHandler handler, T context, Function applicator) { - @Nullable - public Configuration apply(MixinContext mixinContext, Recipe recipe) { - Optional updatedValue = this.handler.apply(mixinContext, recipe, this.context); - if (updatedValue.isPresent()) { - U value = updatedValue.get(); - mixinContext.recordCtxAudit("Update injection point ordinal from %s to %s", this.context, value); - return this.applicator.apply(value); - } - return null; - } - } - - private static class ModifyVariableOffsetHandler implements UpdateHandler> { - private static final ModifyVariableOffsetHandler INSTANCE = new ModifyVariableOffsetHandler(); - - @Override - public Optional> apply(MixinContext mixinContext, Recipe recipe, LocalVar local) { - MethodNode methodNode = mixinContext.methodNode(); - - Type[] args = Type.getArgumentTypes(methodNode.desc); - if (args.length < 1) { - return Optional.empty(); - } - - Type targetType = args[0]; - // Gradually expand supported types over time as necessary - if (!targetType.equals(Type.BOOLEAN_TYPE) && !targetType.equals(Type.INT_TYPE) && !targetType.equals(Type.FLOAT_TYPE)) { - return Optional.empty(); - } - - if (mixinContext.methodAnnotation().getValue("slice").isPresent() && local.relative()) { - return Optional.empty(); - } - - TargetPair cleanTarget = recipe.getCleanTarget(); - TargetPair dirtyTarget = recipe.getDirtyTarget(); - if (cleanTarget == null || dirtyTarget == null) { - return Optional.empty(); - } - AbstractInsnNode cleanInsn = AdapterUtil.getFirstOrNull(mixinContext.injections().find(cleanTarget)); - AbstractInsnNode dirtyInsn = AdapterUtil.getFirstOrNull(mixinContext.injections().find(dirtyTarget)); - if (cleanInsn == null || dirtyInsn == null) { - return Optional.empty(); - } - InjectionPoints points = new InjectionPoints(cleanTarget, cleanInsn, dirtyTarget, dirtyInsn); - - return tryFindUpdatedIndex(mixinContext, targetType, recipe, local, points) - .>map(var -> Pair.of(var, null)) - .or(() -> tryFindSyntheticVariableIndex(mixinContext, methodNode, recipe, local, points)); - } - - private record InjectionPoints(TargetPair cleanTarget, AbstractInsnNode cleanInsn, TargetPair dirtyTarget, AbstractInsnNode dirtyInsn) { - } - - private static int indexOfOrdinal(List candidates, int ordinal) { - return candidates.stream().filter(c -> c.ordinal() == ordinal).mapToInt(LocalCandidate::index).findFirst().orElse(-1); - } - - /** - * Handle situations where a mixin is attempting to modify a variable that is used immediately after its modified. - * However, due to the nature of binary patches, a new variable might have been introduced earlier in the method, which is being used in its place now. - * In these cases, we'll find the new variable and update the mixin's index - *

- * As an example, let's have a look at LivingEntity#actuallyHurt - *

{@code
-         * == Original code ==
-         *    INVOKEVIRTUAL net/minecraft/world/entity/player/Player.getHealth ()F
-         *    FLOAD 2
-         *    FSUB
-         *    INVOKEVIRTUAL net/minecraft/world/entity/player/Player.setHealth (F)V
-         * == Patched code ==
-         * >> INVOKESPECIAL 
-         * >> FSTORE 2
-         *    INVOKEVIRTUAL net/minecraft/world/entity/player/Player.getHealth ()F
-         * != FLOAD 3
-         *    FSUB
-         *    INVOKEVIRTUAL net/minecraft/world/entity/player/Player.setHealth (F)V
-         * == Resulting code ==
-         * >> INVOKESPECIAL 
-         * >> FSTORE 3
-         *    INVOKEVIRTUAL net/minecraft/world/entity/player/Player.getHealth ()F
-         *    FLOAD 3
-         *    FSUB
-         *    INVOKEVIRTUAL net/minecraft/world/entity/player/Player.setHealth (F)V
-         * }
- */ - private static Optional> tryFindSyntheticVariableIndex(MixinContext context, MethodNode methodNode, Recipe recipe, LocalVar local, InjectionPoints points) { - int ordinal = local.ordinal(); - Type variableType = Type.getReturnType(methodNode.desc); - LocalVariableLookup cleanTable = recipe.cleanLocalsTable(); - LocalVariableLookup dirtyTable = recipe.dirtyLocalsTable(); - if (cleanTable == null || dirtyTable == null) { - return Optional.empty(); - } - List cleanLocals = context.locals().findSelectableLocals(points.cleanTarget(), points.cleanInsn(), variableType); - List dirtyLocals = context.locals().findSelectableLocals(points.dirtyTarget(), points.dirtyInsn(), variableType); - if (cleanLocals.size() == dirtyLocals.size()) { - int variableIndex = indexOfOrdinal(dirtyLocals, ordinal); - if (variableIndex != -1) { - AbstractInsnNode cleanInsn = recipe.getCleanInjectionInsn(); - AbstractInsnNode dirtyInsn = context.injections().resolve(recipe.getDirtyTarget()); - if (cleanInsn != null && dirtyInsn != null) { - for (AbstractInsnNode insn : InsnTraversal.untilLabel(cleanInsn, InsnTraversal.FORWARD, true)) { - SingleValueHandle handle = AdapterUtil.handleLocalVarInsnValue(insn); - if (handle != null && handle.get() == variableIndex) { - // We found out the variable is used right after our injection point - // Now let's check if it its index remain the same in the dirty target - List> dirtyVars = getUsedVariablesInLabel(dirtyInsn, insn.getOpcode()); - if (dirtyVars.size() == 1) { - int dirtyIndex = dirtyVars.getFirst().get(); - if (dirtyIndex != variableIndex) { - // Find new ordinal by index - LocalVariableNode lvn = dirtyTable.getByIndexOrNull(dirtyIndex); - if (lvn == null) { - return Optional.empty(); - } - int dirtyOrdinal = context.locals().resolveLocalOrdinal(points.dirtyTarget(), points.dirtyInsn(), Type.getType(lvn.desc), dirtyIndex); - if (dirtyOrdinal == -1) { - return Optional.empty(); - } - return Optional.of(Pair.of( - new LocalVar(lvn, dirtyOrdinal), - MutableConfiguration.create() - .setProperty(MixinKeys.ARGS_ONLY, false) - )); - } - } - break; - } - } - } - } - } - return Optional.empty(); - } - - private static List> getUsedVariablesInLabel(AbstractInsnNode start, int opcode) { - List> list = new ArrayList<>(); - for (AbstractInsnNode insn : InsnTraversal.untilLabel(start, InsnTraversal.FORWARD, true)) { - if (insn.getOpcode() == opcode) { - SingleValueHandle handle = AdapterUtil.handleLocalVarInsnValue(insn); - if (handle != null) { - list.add(handle); - } - } - } - return list; - } - - private static Optional tryFindUpdatedIndex(MixinContext context, Type targetType, Recipe recipe, LocalVar local, InjectionPoints points) { - int ordinal = local.ordinal(); - LocalVariableLookup cleanVarLookup = recipe.cleanLocalsTable(); - LocalVariableLookup dirtyVarLookup = recipe.dirtyLocalsTable(); - if (cleanVarLookup == null || dirtyVarLookup == null) { - return Optional.empty(); - } - List cleanLocals = context.locals().findSelectableLocals(points.cleanTarget(), points.cleanInsn(), targetType); - LocalVariableNode cleanLocal = cleanVarLookup.getInScopeOrNull(indexOfOrdinal(cleanLocals, ordinal), points.cleanInsn()); - if (cleanLocal == null) { - return Optional.empty(); - } - if (!GeneratedVariables.isGeneratedVariableName(cleanLocal.name, Type.getType(cleanLocal.desc))) { - return Optional.empty(); - } - - List dirtyLocals = context.locals().findSelectableLocals(points.dirtyTarget(), points.dirtyInsn(), targetType); - int dirtyIndex = indexOfOrdinal(dirtyLocals, ordinal); - if (cleanLocals.size() != dirtyLocals.size() || dirtyIndex == -1) { - return findReplacementLocal(points.cleanTarget().methodNode(), points.dirtyTarget().methodNode(), cleanLocal) - .flatMap(var -> { - int dirtyOrdinal = context.locals().resolveLocalOrdinal(points.dirtyTarget(), points.dirtyInsn(), Type.getType(var.desc), var.index); - return dirtyOrdinal == -1 ? Optional.empty() : Optional.of(new LocalVar(var, dirtyOrdinal)); - }); - } - LocalVariableNode dirtyLocal = dirtyVarLookup.getInScopeOrNull(dirtyIndex, points.dirtyInsn()); - if (dirtyLocal == null) { - return Optional.empty(); - } - if (!local.relative() && dirtyLocal.index == local.lvn().index) { - return Optional.empty(); - } - OptionalInt dirtyNameOrdinal = GeneratedVariables.getGeneratedVariableOrdinal(dirtyLocal.name, Type.getType(dirtyLocal.desc)); - if (dirtyNameOrdinal.isEmpty() || local.relative() && ordinal == dirtyNameOrdinal.getAsInt()) { - return Optional.empty(); - } - - if (cleanLocal.index != dirtyLocal.index && !local.relative()) { - return Optional.of(new LocalVar(dirtyLocal, ordinal)); - } - - return Optional.empty(); - } - - private static Optional findReplacementLocal(MethodNode cleanMethod, MethodNode dirtyMethod, LocalVariableNode desired) { - // Find variable initializer insns - InsnList desiredInitializerInsns = LocalsAnalyzer.findInitializerInsns(cleanMethod, desired.index); - return LocalsAnalyzer.findLocalWithInitializer(dirtyMethod, desired.desc, desiredInitializerInsns); - } - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java new file mode 100644 index 00000000..081e0e5d --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java @@ -0,0 +1,80 @@ +package org.sinytra.adapter.pipeline.resolver.special; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LocalVariableNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.analysis.injection.InjectionQuery; +import org.sinytra.adapter.analysis.locals.LocalAlignment; +import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.locate.Describer; +import org.sinytra.adapter.pipeline.resolver.Resolver; +import org.sinytra.adapter.util.AdapterUtil; + +public final class LocalResolver implements Resolver { + @Override + public ResolutionResult resolve(MixinContext context, Recipe recipe) { + Configuration dirty = recipe.dirty(); + AtData at = dirty.getAtData(); + if (at == null || !recipe.clean().hasProperty(MixinKeys.ORDINAL) && !recipe.clean().hasProperty(MixinKeys.INDEX) + || dirty.hasProperty(MixinKeys.ORDINAL) || dirty.hasProperty(MixinKeys.INDEX) + ) { + return ResolutionResult.pass(); + } + + TargetPair cleanTarget = recipe.getCleanTarget(); + TargetPair dirtyTarget = recipe.getDirtyTarget(); + if (cleanTarget == null || dirtyTarget == null) { + return ResolutionResult.pass(); + } + AbstractInsnNode cleanInsn = AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)); + AbstractInsnNode dirtyInsn = AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget, InjectionQuery.of(at))); + if (cleanInsn == null || dirtyInsn == null) { + return ResolutionResult.pass(); + } + + Type type = Type.getReturnType(context.methodNode().desc); + int cleanSlot = context.locals().resolveSelectedLocal(cleanTarget, cleanInsn, type); + if (cleanSlot < 0) { + return ResolutionResult.pass(); + } + int dirtySlot = findDirtySlot(recipe, cleanTarget, dirtyTarget, cleanSlot, cleanInsn); + if (dirtySlot < 0 || context.locals().resolveSelectedLocal(dirtyTarget, dirtyInsn, type) == dirtySlot) { + return ResolutionResult.pass(); + } + + MutableConfiguration local = Describer.describeLocal(context, recipe, dirtyTarget, dirtyInsn, dirtySlot); + if (local == null) { + return ResolutionResult.pass(); + } + context.recordCtxAudit("Select local %s instead of %s", dirtySlot, cleanSlot); + return ResolutionResult.success(local); + } + + private static int findDirtySlot(Recipe recipe, TargetPair cleanTarget, TargetPair dirtyTarget, int cleanSlot, AbstractInsnNode cleanInsn) { + if (cleanTarget.methodNode().name.equals(dirtyTarget.methodNode().name) && cleanTarget.methodNode().desc.equals(dirtyTarget.methodNode().desc)) { + MethodAlignment alignment = MethodAlignment.align(cleanTarget.methodNode(), dirtyTarget.methodNode()); + return LocalAlignment.of(alignment).resolve(cleanSlot, cleanInsn); + } + + LocalVariableLookup cleanLocals = recipe.cleanLocalsTable(); + LocalVariableNode cleanLocal = cleanLocals != null ? cleanLocals.getInScopeOrNull(cleanSlot, cleanInsn) : null; + if (cleanLocal == null) { + return -1; + } + + InsnList initializer = LocalsAnalyzer.findInitializerInsns(cleanTarget.methodNode(), cleanSlot); + return LocalsAnalyzer.findLocalWithInitializer(dirtyTarget.methodNode(), cleanLocal.desc, initializer) + .map(local -> local.index) + .orElse(-1); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/util/GeneratedVariables.java b/core/src/main/java/org/sinytra/adapter/util/GeneratedVariables.java index 7d82ebdd..435554b8 100644 --- a/core/src/main/java/org/sinytra/adapter/util/GeneratedVariables.java +++ b/core/src/main/java/org/sinytra/adapter/util/GeneratedVariables.java @@ -1,11 +1,9 @@ package org.sinytra.adapter.util; -import com.google.common.base.Strings; import com.mojang.datafixers.util.Pair; import org.objectweb.asm.Type; import java.util.*; -import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -60,11 +58,6 @@ public static boolean isGeneratedVariableName(String name, Type type) { return knownGenerated; } - public static OptionalInt getGeneratedVariableOrdinal(String name, Type type) { - GeneratedVarName generator = GENERATED_VAR_NAMES.get(type); - return generator != null ? generator.getOrdinal(name) : OptionalInt.empty(); - } - public static class GeneratedVarName { private final Set types; private final Pattern pattern; @@ -87,18 +80,5 @@ public Set getTypes() { public boolean test(String str) { return this.pattern.matcher(str).matches(); } - - public OptionalInt getOrdinal(String str) { - Matcher matcher = this.pattern.matcher(str); - if (matcher.matches()) { - String ordinal = matcher.group("index"); - if (Strings.isNullOrEmpty(ordinal)) { - return OptionalInt.of(0); - } - int intOrdinal = Integer.parseInt(ordinal); - return OptionalInt.of(intOrdinal); - } - return OptionalInt.empty(); - } } } From 362380f4f16c7142565cdc92e1f00e7a2c8eecd5 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 15:17:49 +0200 Subject: [PATCH 23/44] Rework param distance calculation --- .../analysis/locals/LocalsAnalyzer.java | 9 +- .../analysis/params/EnhancedParamsDiff.java | 512 ------------------ .../adapter/analysis/params/Param.java | 12 + .../analysis/params/ParamDiffResolver.java | 39 +- .../analysis/params/ParametersDiff.java | 135 ----- .../adapter/analysis/params/ParamsDiff.java | 4 + .../analysis/params/ParamsEditDistance.java | 272 ++++++++++ .../adapter/analysis/params/TypeRelation.java | 45 ++ .../UnsupportedParamsDiffException.java | 7 - .../signature/ParameterCastStep.java | 5 +- .../processor/signature/ParametersStep.java | 7 +- .../signature/RedirectedCallStep.java | 5 +- .../adapter/util/GeneratedVariables.java | 5 +- .../patch/test/ParamDiffResolverTest.java | 4 +- .../patch/test/ParameterComparisonTest.java | 108 +--- ...est.java => ParamsDiffRegressionTest.java} | 42 +- 16 files changed, 417 insertions(+), 794 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/analysis/params/EnhancedParamsDiff.java create mode 100644 core/src/main/java/org/sinytra/adapter/analysis/params/Param.java delete mode 100644 core/src/main/java/org/sinytra/adapter/analysis/params/ParametersDiff.java create mode 100644 core/src/main/java/org/sinytra/adapter/analysis/params/ParamsDiff.java create mode 100644 core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java create mode 100644 core/src/main/java/org/sinytra/adapter/analysis/params/TypeRelation.java delete mode 100644 core/src/main/java/org/sinytra/adapter/analysis/params/UnsupportedParamsDiffException.java rename test/src/test/java/org/sinytra/adapter/patch/test/{EnhancedParamsDiffTest.java => ParamsDiffRegressionTest.java} (89%) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index 082b188a..951b65d6 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -11,7 +11,8 @@ import org.sinytra.adapter.analysis.InsnTraversal; import org.sinytra.adapter.analysis.compare.InstructionMatcher; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; +import org.sinytra.adapter.analysis.params.ParamsEditDistance; +import org.sinytra.adapter.analysis.params.TypeRelation; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.analysis.params.ParamDiffResolver; import org.sinytra.adapter.edit.param.TransformParameters; @@ -112,8 +113,7 @@ public CapturedLocalsInfo resolveCapturedLocalsInfo(TargetPair dirtyTarget) { List availableTypes = available.stream().map(LocalVariable::type).toList(); // Compare expected and available params - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(capturedLocals.expected(), availableTypes); - if (diff == null) return null; + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(capturedLocals.expected(), availableTypes).snapshot(); return new CapturedLocalsInfo(capturedLocals, diff, availableTypes); } @@ -244,8 +244,7 @@ public List findDirtyCapturedParams(Configuration clean, Configuration dir MethodNode cleanTarget = cleanPair.methodNode(); MethodNode dirtyTarget = dirtyPair.methodNode(); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.compareMethodParameters(cleanTarget, dirtyTarget); - if (diff == null) return dirtyCaptured; + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffParameters(cleanTarget, dirtyTarget, TypeRelation.STATIC).snapshot(); List cleanTargetParams = Parameters.getParameterTypes(cleanTarget.desc); if (cleanCaptured.size() > cleanTargetParams.size()) return dirtyCaptured; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/EnhancedParamsDiff.java b/core/src/main/java/org/sinytra/adapter/analysis/params/EnhancedParamsDiff.java deleted file mode 100644 index 20f214c3..00000000 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/EnhancedParamsDiff.java +++ /dev/null @@ -1,512 +0,0 @@ -package org.sinytra.adapter.analysis.params; - -import com.google.common.collect.MapDifference; -import com.google.common.collect.Maps; -import com.mojang.datafixers.util.Pair; -import com.mojang.logging.LogUtils; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.LocalVariableNode; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.util.GeneratedVariables; -import org.slf4j.Logger; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; - -import java.util.*; - -public class EnhancedParamsDiff { - private static final Logger LOGGER = LogUtils.getLogger(); - private static final boolean DEBUG = Boolean.getBoolean("adapter.core.paramdiff.debug"); - - @Nullable - public static LayeredParamsDiffSnapshot createLayered(List clean, List dirty) { - LayeredParamsDiffSnapshot.Builder builder = LayeredParamsDiffSnapshot.builder(); - try { - buildDiff(builder, clean, dirty); - } catch (UnsupportedParamsDiffException e) { - LOGGER.debug("Cannot compare parameters {} and {}: {}", clean, dirty, e.getMessage()); - return null; - } - // TODO Sanity check - return builder.build(); - } - - @Nullable - public static LayeredParamsDiffSnapshot compareMethodParameters(MethodNode clean, MethodNode dirty) { - if (clean.localVariables == null || dirty.localVariables == null) { - return LayeredParamsDiffSnapshot.EMPTY; - } - - List cleanParams = getParameterLocals(clean); - List dirtyParams = getParameterLocals(dirty); - boolean compareNames = cleanParams.stream().allMatch(t -> t.name() != null) && dirtyParams.stream().anyMatch(TypeWithContext::isGenerated); - - LayeredParamsDiffSnapshot.Builder builder = LayeredParamsDiffSnapshot.builder(); - try { - buildDiffWithContext(builder, cleanParams, dirtyParams, compareNames); - } catch (UnsupportedParamsDiffException e) { - LOGGER.debug("Cannot compare parameters of {}{} and {}{}: {}", clean.name, clean.desc, dirty.name, dirty.desc, e.getMessage()); - return null; - } - return builder.build(); - } - - private static List getParameterLocals(MethodNode method) { - LocalVariableLookup lookup = new LocalVariableLookup(method); - List params = new ArrayList<>(); - int paramCount = Type.getArgumentCount(method.desc); - for (int i = 0; i < paramCount; i++) { - LocalVariableNode lvn = lookup.getByParameterOrdinalOrNull(i); - if (lvn == null) { - break; - } - params.add(TypeWithContext.of(lvn, i)); - } - return params; - } - - private static void buildDiff(LayeredParamsDiffSnapshot.Builder builder, List clean, List dirty) { - buildDiffWithContext(builder, createPositionedList(clean), createPositionedList(dirty), false); - } - - private static void buildDiffWithContext(LayeredParamsDiffSnapshot.Builder builder, List cleanQueue, List dirtyQueue, boolean compareNames) { - int dirtySize = dirtyQueue.size(); - boolean sameSize = cleanQueue.size() == dirtyQueue.size(); - - if (DEBUG) { - LOGGER.info("Comparing types:\n{}", printTable(cleanQueue, dirtyQueue)); - } - - while (!cleanQueue.isEmpty()) { - // Look ahead for matching types at the beginning of the list - // If the first two are equal, remove the first ones and repeat - if (predictParameterMatch(builder, cleanQueue, dirtyQueue, compareNames, sameSize)) { - cleanQueue.removeFirst(); - dirtyQueue.removeFirst(); - continue; - } - // Handle replaced types, needs improving - if (tryReplacingParams(builder, cleanQueue, dirtyQueue)) { - continue; - } - // Check for swapped / reordered types. Also handles unique-type insertions - SwapResult swapResult = checkForSwaps(builder, cleanQueue, dirtyQueue); - if (swapResult != null) { - dirtyQueue.removeAll(swapResult.removeDirty()); - cleanQueue.clear(); - dirtyQueue.clear(); - break; - } - // Find the smallest set of matching types by locating the position of the next clean type in the dirty list - int dirtyTypeIndex = cleanQueue.size() == 1 ? dirtySize - 1 : lookAhead(dirtyQueue, cleanQueue.get(1)); - if (dirtyTypeIndex != -1) { - boolean matchesName = !compareNames || cleanQueue.size() < 2 || dirtyQueue.size() < 2 || cleanQueue.get(1).matches(dirtyQueue.get(1)); - int offset = cleanQueue.get(0).sameType(dirtyQueue.get(0)) && matchesName ? 1 : 2; - List compareClean = extract(cleanQueue, matchesName ? 2 : 1); - List compareDirty = extract(dirtyQueue, dirtyTypeIndex + offset); - - compare(builder, compareClean, compareDirty); - } - // If there is no match for the first available type, look further ahead. This is good for handling cases - // where the sizes of both sets are equal and the first couple types have been replaced / modified - else if (cleanQueue.size() == dirtyQueue.size() && (dirtyTypeIndex = findClosestMatch(cleanQueue, dirtyQueue)) > 1) { - List compareClean = extract(cleanQueue, dirtyTypeIndex); - List compareDirty = extract(dirtyQueue, dirtyTypeIndex); - - compare(builder, compareClean, compareDirty); - } else if (cleanQueue.size() > 1) { - TypeWithContext type = cleanQueue.remove(1); - if (DEBUG) { - LOGGER.info("Removing type {}", type); - } - builder.remove(type.pos()); - } else { - throw new UnsupportedParamsDiffException("undefined behavior"); - } - } - if (!dirtyQueue.isEmpty()) { - for (TypeWithContext type : dirtyQueue) { - builder.insert(type.pos(), type.type()); - } - } - } - - private static boolean predictParameterMatch(LayeredParamsDiffSnapshot.Builder builder, List cleanQueue, List dirtyQueue, boolean compareNames, boolean sameSize) { - // For same-sized comparisons, we can be certain there have been no insertions - if (sameSize && !cleanQueue.isEmpty() && !dirtyQueue.isEmpty()) { - if (!cleanQueue.get(0).sameType(dirtyQueue.get(0))) { - if (checkMovedParam(builder, cleanQueue, dirtyQueue, 0)) { - return true; - } - // If the first parameters differ, it's possible one of them has been removed - // == Clean == - // 0 Foo - // 1 Bar - // 2 World - // == Dirty == - // 0 Bar - // 1 World - // 2 Sheep - if (cleanQueue.size() > 2 && dirtyQueue.size() > 2 && cleanQueue.get(1).matches(dirtyQueue.get(0)) && cleanQueue.get(2).matches(dirtyQueue.get(1))) { - builder.remove(cleanQueue.getFirst().pos()); - cleanQueue.removeFirst(); - return true; - } - return false; - } - return true; - } else if (cleanQueue.size() > 1 && dirtyQueue.size() > 1) { - if (cleanQueue.get(0).sameType(dirtyQueue.get(0))) { - if (cleanQueue.get(1).matches(dirtyQueue.get(1))) { - // If the first two parameters match, we can be sure the first ones are identical without any insertions - return true; - } - // Comparing names can be useful in cases where a parameter is inserted in between multiple ones of the same type - else if (compareNames) { - // Check for inserted parameters - // == Clean == - // 0 F one - // 1 F two - // 2 F three - // == Dirty == - // 0 F one - // 1 F - // 2 F two - if (dirtyQueue.size() > 2 && cleanQueue.get(1).matches(dirtyQueue.get(2))) { - builder.insert(dirtyQueue.get(1).pos(), dirtyQueue.get(1).type()); - cleanQueue.removeFirst(); - dirtyQueue.removeFirst(); - dirtyQueue.removeFirst(); - return true; - } - } else { - return checkMovedParam(builder, cleanQueue, dirtyQueue, 1); - } - } - return false; - } - return false; - } - - private static boolean checkMovedParam(LayeredParamsDiffSnapshot.Builder builder, List cleanQueue, List dirtyQueue, int index) { - TypeWithContext param = cleanQueue.get(index); - TypeWithContext dirtyParam = dirtyQueue.get(index); - Map cleanGroup = groupTypes(cleanQueue); - Map dirtyGroup = groupTypes(dirtyQueue); - if (cleanGroup.get(param.type()) == 1 && dirtyGroup.getOrDefault(param.type(), 0) == 1 - // Make sure the next param isn't injected - && Objects.equals(cleanGroup.getOrDefault(dirtyParam.type(), 0), dirtyGroup.get(dirtyParam.type())) - ) { - // The parameter has been moved - TypeWithContext newParam = dirtyQueue.stream().filter(t -> t.type().equals(param.type())).findFirst().orElseThrow(); - if (Math.abs(param.pos() - newParam.pos()) == 1) { - builder.swap(param.pos(), newParam.pos()); - } else { - builder.move(param.pos(), newParam.pos()); - } - cleanQueue.remove(param); - dirtyQueue.remove(newParam); - return true; - } - return false; - } - - private static int findClosestMatch(List cleanQueue, List dirtyQueue) { - for (TypeWithContext typeWithContext : cleanQueue) { - int pos = lookAhead(dirtyQueue, typeWithContext); - if (pos != -1) { - return pos; - } - } - return -1; - } - - private static boolean tryReplacingParams(LayeredParamsDiffSnapshot.Builder builder, List cleanQueue, List dirtyQueue) { - if (replaceType(builder, 0, cleanQueue, dirtyQueue)) { - return true; - } else if (cleanQueue.size() == 2 && dirtyQueue.size() == 2) { - // Handle swapped parameters in simple cases - if (cleanQueue.get(0).sameType(dirtyQueue.get(1)) && cleanQueue.get(1).sameType(dirtyQueue.get(0))) { - builder.swap(dirtyQueue.get(0).pos(), dirtyQueue.get(1).pos()); - cleanQueue.clear(); - dirtyQueue.clear(); - return true; - } - return replaceType(builder, 1, cleanQueue, dirtyQueue); - } - return false; - } - - private static boolean replaceType(LayeredParamsDiffSnapshot.Builder builder, int index, List cleanQueue, List dirtyQueue) { - if (index >= cleanQueue.size() || index >= dirtyQueue.size()) { - return false; - } - TypeWithContext cleanType = cleanQueue.get(index); - TypeWithContext dirtyType = dirtyQueue.get(index); - int next = index + 1; - // Handle possible insertions - if (cleanQueue.size() != dirtyQueue.size() && dirtyQueue.size() > next && cleanType.sameType(dirtyQueue.get(next)) || isPossiblyInjected(next, cleanQueue, dirtyQueue)) { - return false; - } - if (!cleanType.sameType(dirtyType)) { - if (DEBUG) { - LOGGER.info("Replacing {} with {}", cleanType, dirtyType); - } - cleanQueue.remove(index); - dirtyQueue.remove(index); - builder.replace(dirtyType.pos(), dirtyType.type()); - return true; - } - return false; - } - - private static boolean isPossiblyInjected(int index, List cleanQueue, List dirtyQueue) { - for (int i = index; i < cleanQueue.size() && i < dirtyQueue.size(); i++) { - if (!cleanQueue.get(i).sameType(dirtyQueue.get(i))) { - return true; - } - } - return false; - } - - private record SwapResult(List removeDirty) { - } - - private static Comparator maybeInverse(Comparator comparator, boolean inverse) { - return inverse ? comparator.reversed() : comparator; - } - - private static List> sortMapDiff(Map map, List context, boolean descending) { - return map.entrySet().stream() - .map(e -> { - Type type = e.getKey(); - Integer count = e.getValue(); - if (count == 1) { - TypeWithContext inserted = context.stream().filter(t -> t.type().equals(type)).findFirst().orElseThrow(); - return Pair.of(type, inserted); - } - return null; - }) - .filter(Objects::nonNull) - .sorted(maybeInverse(Comparator.comparingInt(p -> p.getSecond().pos()), descending)) - .toList(); - } - - @Nullable - private static SwapResult checkForSwaps(LayeredParamsDiffSnapshot.Builder builder, List clean, List dirty) { - Map cleanGroup = groupTypes(clean); - Map dirtyGroup = groupTypes(dirty); - MapDifference diff = Maps.difference(cleanGroup, dirtyGroup); - - List rearrangeClean = new ArrayList<>(clean); - List rearrangeDirty = new ArrayList<>(dirty); - List removeDirty = new ArrayList<>(); - LayeredParamsDiffSnapshot.Builder tempDiff = LayeredParamsDiffSnapshot.builder(); - // Remove inserted parameters - if (diff.entriesOnlyOnLeft().isEmpty() && !diff.entriesOnlyOnRight().isEmpty()) { - // Sort by insertion order first - for (Pair p : sortMapDiff(diff.entriesOnlyOnRight(), dirty, false)) { - Type type = p.getFirst(); - TypeWithContext inserted = p.getSecond(); - - dirtyGroup.remove(type); - rearrangeDirty.remove(inserted); - removeDirty.add(inserted); - int offset = inserted.pos() + (int) builder.getRemovals().stream().filter(i -> i < inserted.pos()).count(); - tempDiff.insert(offset, inserted.type()); - } - } else if (!diff.entriesOnlyOnLeft().isEmpty() && diff.entriesOnlyOnRight().isEmpty()) { - // Sort by insertion order first - for (Pair p : sortMapDiff(diff.entriesOnlyOnLeft(), clean, true)) { - Type type = p.getFirst(); - TypeWithContext inserted = p.getSecond(); - - int offset = inserted.pos() - (int) builder.getRemovals().stream().filter(i -> i < inserted.pos()).count(); - tempDiff.remove(offset); - cleanGroup.remove(type); - rearrangeClean.remove(inserted); - } - } - - if (!sameTypeCount(cleanGroup, dirtyGroup)) { - return null; - } - if (DEBUG) { - LOGGER.info("Checking for swaps in parameters:\n{}", printTable(rearrangeClean, rearrangeDirty)); - } - - // Remove leading equal types - while (!rearrangeClean.isEmpty() && rearrangeClean.getFirst().sameType(rearrangeDirty.getFirst())) { - rearrangeClean.removeFirst(); - rearrangeDirty.removeFirst(); - } - // Remove trailing equal types - while (!rearrangeClean.isEmpty() && rearrangeClean.getLast().sameType(rearrangeDirty.getLast())) { - rearrangeClean.removeLast(); - rearrangeDirty.removeLast(); - } - if (!rearrangeClean.isEmpty() && !rearrangeDirty.isEmpty()) { - builder.merge(tempDiff.build()); - // Run rearrangement - rearrange(builder, rearrangeClean, rearrangeDirty); - return new SwapResult(removeDirty); - } else if (rearrangeClean.isEmpty() && rearrangeDirty.isEmpty()) { - builder.merge(tempDiff.build()); - return new SwapResult(removeDirty); - } - return null; - } - - private static void rearrange(LayeredParamsDiffSnapshot.Builder builder, List clean, List dirty) { - record Rearrangement(TypeWithContext cleanType, TypeWithContext dirtyType, int fromRelative, int toRelative) { - } - - Map cleanGroup = groupTypes(clean); - Map dirtyGroup = groupTypes(dirty); - if (!sameTypeCount(cleanGroup, dirtyGroup)) { - return; - } - if (DEBUG) { - LOGGER.info("Rearranging parameters:\n{}", printTable(clean, dirty)); - } - - List rearrangements = new ArrayList<>(); - for (TypeWithContext cleanType : clean) { - if (cleanGroup.get(cleanType.type()) == 1 && dirtyGroup.get(cleanType.type()) == 1) { - // Determine new type pos - TypeWithContext dirtyType = dirty.stream().filter(cleanType::sameType).findFirst().orElseThrow(); - rearrangements.add(new Rearrangement(cleanType, dirtyType, clean.indexOf(cleanType), dirty.indexOf(dirtyType))); - } - } - rearrangements.sort(Comparator.comparingInt(Rearrangement::toRelative)); - - List rearrangeClean = new ArrayList<>(clean); - List rearrangeDirty = new ArrayList<>(dirty); - for (Rearrangement rearrangement : rearrangements) { - boolean same = true; - for (int i = 0; i < rearrangeClean.size(); i++) { - if (!rearrangeClean.get(i).sameType(rearrangeDirty.get(i))) { - same = false; - } - } - if (same) { - break; - } - if (DEBUG) { - LOGGER.info("Moving param {} to index {}", rearrangement.cleanType(), rearrangement.dirtyType().pos()); - } - // The original pos in the dirty list might differ due to prior insertions - int offsetOriginalPos = rearrangeDirty.get(rearrangement.fromRelative()).pos(); - int destPos = rearrangement.dirtyType().pos(); - if (Math.abs(offsetOriginalPos - destPos) == 1) { - builder.swap(offsetOriginalPos, destPos); - } else { - builder.move(offsetOriginalPos, destPos); - } - rearrangeClean.add(rearrangement.toRelative(), rearrangeClean.remove(rearrangement.fromRelative())); - } - } - - private static void compare(LayeredParamsDiffSnapshot.Builder builder, List clean, List dirty) { - if (DEBUG) { - LOGGER.info("Running comparison for:\n{}", printTable(clean, dirty)); - } - - List cleanTypes = clean.stream().map(TypeWithContext::toMethodParameter).toList(); - List dirtyTypes = dirty.stream().map(TypeWithContext::toMethodParameter).toList(); - ParametersDiff diff = ParametersDiff.compareParameters(cleanTypes, dirtyTypes, false); - if (DEBUG) { - LOGGER.info("Comparison results:\n\tInserted: {}\n\tReplaced: {}\n\tSwapped: {}\n\tRemoved: {}", diff.insertions(), diff.replacements(), diff.swaps(), diff.removals()); - } - int indexOffset = !dirty.isEmpty() ? dirty.getFirst().pos() : 0; - builder.merge(diff.toSnapshot(), indexOffset); - } - - private static List createPositionedList(List list) { - List ret = new ArrayList<>(); - for (int i = 0; i < list.size(); i++) { - ret.add(new TypeWithContext(list.get(i), i)); - } - return ret; - } - - private static boolean sameTypeCount(Map cleanGroup, Map dirtyGroup) { - if (cleanGroup.size() != dirtyGroup.size()) { - return false; - } - for (Map.Entry entry : cleanGroup.entrySet()) { - Integer dirtyCount = dirtyGroup.get(entry.getKey()); - if (dirtyCount == null || dirtyCount.intValue() != entry.getValue()) { - return false; - } - } - return true; - } - - private static List extract(List list, int amount) { - List res = new ArrayList<>(); - for (int i = 0; i < amount && !list.isEmpty(); i++) { - res.add(list.removeFirst()); - } - return res; - } - - private static int lookAhead(List types, TypeWithContext target) { - for (int i = 0; i < types.size(); i++) { - if (target.sameType(types.get(i))) { - return i; - } - } - return -1; - } - - private static Map groupTypes(List list) { - Map map = new HashMap<>(); - for (TypeWithContext type : list) { - map.compute(type.type(), (t, old) -> old == null ? 1 : old + 1); - } - return map; - } - - private static String printTable(List clean, List dirty) { - StringBuilder builder = new StringBuilder(); - builder.append("\t| %5s | %25s%s%26s | %25s%s%26s |\n".formatted("Index", "", "Clean", "", "", "Dirty", "")); - builder.append("\t"); - builder.repeat("=", 127); - builder.append("\n"); - int max = Math.max(clean.size(), dirty.size()); - for (int i = 0; i < max; i++) { - builder.append("\t| %-5s | %56s | %-56s |\n".formatted(i, i < clean.size() ? clean.get(i).type() : "", i < dirty.size() ? dirty.get(i).type() : "")); - } - builder.append("\t"); - builder.repeat("=", 127); - return builder.toString(); - } - - private record TypeWithContext(@Nullable String name, Type type, int pos, boolean isGenerated) { - public TypeWithContext(Type type, int pos) { - this(null, type, pos, false); - } - - public static TypeWithContext of(LocalVariableNode lvn, int pos) { - Type type = Type.getType(lvn.desc); - return new TypeWithContext(lvn.name, type, pos, lvn.name != null && GeneratedVariables.isGeneratedVariableName(lvn.name, type)); - } - - public ParametersDiff.MethodParameter toMethodParameter() { - return new ParametersDiff.MethodParameter(this.type, this.isGenerated); - } - - public boolean sameType(TypeWithContext other) { - return type().equals(other.type()); - } - - public boolean sameName(TypeWithContext other) { - return this.name == null || other.name() == null || this.isGenerated == other.isGenerated(); - } - - public boolean matches(TypeWithContext other) { - return sameType(other) && sameName(other); - } - } -} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/Param.java b/core/src/main/java/org/sinytra/adapter/analysis/params/Param.java new file mode 100644 index 00000000..7292ebf0 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/Param.java @@ -0,0 +1,12 @@ +package org.sinytra.adapter.analysis.params; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; + +import java.util.List; + +public record Param(Type type, @Nullable String name) { + public static List of(List types) { + return types.stream().map(type -> new Param(type, null)).toList(); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java index 37cd8c1a..06898c55 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java @@ -3,14 +3,7 @@ import it.unimi.dsi.fastutil.objects.ReferenceArrayList; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; -import org.sinytra.adapter.edit.param.InjectParameterTransform; -import org.sinytra.adapter.edit.param.InlineParameterTransformer; -import org.sinytra.adapter.edit.param.MoveParametersTransformer; -import org.sinytra.adapter.edit.param.ParameterTransformer; -import org.sinytra.adapter.edit.param.RemoveParameterTransformer; -import org.sinytra.adapter.edit.param.ReplaceParametersTransformer; -import org.sinytra.adapter.edit.param.SubstituteParameterTransformer; -import org.sinytra.adapter.edit.param.SwapParametersTransformer; +import org.sinytra.adapter.edit.param.*; import java.util.ArrayList; import java.util.List; @@ -29,8 +22,9 @@ public PositionedParam(int cleanPos, Type type) { } } - public record ParamState(Type type, int cleanIndex, int dirtyIndex) {} - + public record ParamState(Type type, int cleanIndex, int dirtyIndex) { + } + public static class ParamEvalResult { private final List original; private final Map params; @@ -49,7 +43,23 @@ public ParamState getUpdated(Type param) { } } + public static List applyTypes(List input, LayeredParamsDiffSnapshot diff) { + return simulate(input, diff).stream().map(param -> param.type).toList(); + } + public static ParamEvalResult resolve(List input, LayeredParamsDiffSnapshot diff) { + List state = simulate(input, diff); + + List result = new ArrayList<>(); + for (int i = 0; i < state.size(); i++) { + PositionedParam param = state.get(i); + result.add(new ParamState(param.type, param.cleanPos, i)); + } + + return new ParamEvalResult(input, result); + } + + private static List simulate(List input, LayeredParamsDiffSnapshot diff) { List state = new ArrayList<>(); for (int i = 0; i < input.size(); i++) { Type type = input.get(i); @@ -75,13 +85,6 @@ case SwapParametersTransformer(int from, int to) -> { case null, default -> throw new RuntimeException("Unknown parameter modification: " + modification); } } - - List result = new ArrayList<>(state.size()); - for (int i = 0; i < state.size(); i++) { - PositionedParam param = state.get(i); - result.add(new ParamState(param.type, param.cleanPos, i)); - } - - return new ParamEvalResult(input, result); + return state; } } diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/ParametersDiff.java b/core/src/main/java/org/sinytra/adapter/analysis/params/ParametersDiff.java deleted file mode 100644 index 5deb8de8..00000000 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/ParametersDiff.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.sinytra.adapter.analysis.params; - -import com.mojang.datafixers.util.Pair; -import org.objectweb.asm.Type; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -/** - * @deprecated Use {@link EnhancedParamsDiff} where possible - */ -@Deprecated -public record ParametersDiff( - int originalCount, - List> insertions, - List> replacements, - List> swaps, - List removals, - List> moves -) { - public record MethodParameter(Type type, boolean isGeneratedType) { - public boolean matchName(MethodParameter other) { - return this.isGeneratedType == other.isGeneratedType; - } - } - - public LayeredParamsDiffSnapshot toSnapshot() { - LayeredParamsDiffSnapshot.Builder builder = LayeredParamsDiffSnapshot.builder(); - this.insertions.forEach(p -> builder.insert(p.getFirst(), p.getSecond())); - this.replacements.forEach(p -> builder.replace(p.getFirst(), p.getSecond())); - this.swaps.forEach(p -> builder.swap(p.getFirst(), p.getSecond())); - this.removals.forEach(builder::remove); - this.moves.forEach(p -> builder.move(p.getFirst(), p.getSecond())); - return builder.build(); - } - - public static ParametersDiff compareTypeParameters(Type[] parameterTypes, Type[] newParameterTypes) { - List cleanParameters = Stream.of(parameterTypes) - .map(type -> new MethodParameter(type, false)) - .toList(); - List dirtyParameters = Stream.of(newParameterTypes) - .map(type -> new MethodParameter(type, false)) - .toList(); - return compareParameters(cleanParameters, dirtyParameters, false); - } - - public static ParametersDiff compareParameters(List cleanParameters, List dirtyParameters, boolean lvtIndexes) { - // Indexes we insert new params at - List> insertions = new ArrayList<>(); - // Indexes we replace params at - List> replacements = new ArrayList<>(); - // Indexes to swap one for another - List> swaps = new ArrayList<>(); - List removals = new ArrayList<>(); - int i = 0; - int j = 0; - int lvtIndex = 0; - // New params are expected to be at least the same size as the old ones, so we use them for the outer loop - outer: - while (j < dirtyParameters.size()) { - boolean skipJIncr = false; - // Start iterating over the original params - if (i < cleanParameters.size()) { - MethodParameter cleanParam = cleanParameters.get(i); - MethodParameter dirtyParam = dirtyParameters.get(j); - // Check if old and new params at this index are the same - boolean sameType = cleanParam.type.equals(dirtyParam.type); - if (!sameType || !cleanParam.matchName(dirtyParam)) { - boolean handled = false; - boolean removing = false; - // Check if the params have been swapped - if (i + 1 < cleanParameters.size() && j + 1 < dirtyParameters.size()) { - MethodParameter nextCleanParam = cleanParameters.get(i + 1); - MethodParameter nextDirtyParam = dirtyParameters.get(j + 1); - // Detect swapped params - if (nextCleanParam.type.equals(dirtyParam.type) && nextDirtyParam.equals(cleanParam)) { - swaps.add(Pair.of(j, j + 1)); - i++; - lvtIndex++; - j++; - handled = true; - } - // Detect removed parameters, check the next 2 params for matching types (if possible) - if (nextCleanParam.equals(dirtyParam) && (i + 2 >= cleanParameters.size() || cleanParameters.get(i + 2).equals(nextDirtyParam))) { - removing = true; - } - } - if (!handled) { - // If not, it is possible a new param was injected onto this index. - // In that case, the original param was moved further down the array, and we must find it. - for (int k = j + 1; k < dirtyParameters.size(); k++) { - MethodParameter dirtyParamAhead = dirtyParameters.get(k); - if (cleanParam.type.equals(dirtyParamAhead.type) && (sameType || cleanParam.matchName(dirtyParamAhead))) { - // If the param is found, add all parameters between the original and new pos to the insertion list - for (; j < k; j++, lvtIndex++) { - insertions.add(Pair.of(lvtIndex, dirtyParameters.get(j).type)); - } - // Continue onto the next params - continue outer; - } - } - if (removing) { - removals.add(j); - skipJIncr = true; - } - // If the param is not found, then it was likely replaced - else if (!cleanParam.type.equals(dirtyParam.type)) { - replacements.add(Pair.of(lvtIndex, dirtyParam.type)); - } - } - } - i++; - lvtIndex += lvtIndexes ? dirtyParam.type.getSize() : 1; - } - // For appending parameters at the end of the list - else { - Type type = dirtyParameters.get(j).type; - insertions.add(Pair.of(lvtIndex, type)); - lvtIndex += lvtIndexes ? type.getSize() : 1; - } - if (!skipJIncr) { - j++; - } - } - if (j - i + removals.size() != insertions.size()) { - throw new UnsupportedParamsDiffException("Unexpected difference in params size"); - } - return new ParametersDiff(i, insertions, replacements, swaps, removals, List.of()); - } - - public boolean isEmpty() { - return this.insertions.isEmpty() && this.replacements.isEmpty() && this.swaps.isEmpty() && this.removals.isEmpty() && this.moves.isEmpty(); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsDiff.java b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsDiff.java new file mode 100644 index 00000000..055349b0 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsDiff.java @@ -0,0 +1,4 @@ +package org.sinytra.adapter.analysis.params; + +public record ParamsDiff(LayeredParamsDiffSnapshot snapshot, double cost) { +} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java new file mode 100644 index 00000000..f3b79825 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java @@ -0,0 +1,272 @@ +package org.sinytra.adapter.analysis.params; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.util.GeneratedVariables; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Diffs two parameter lists by weighted edit distance with swaps (Damerau-Levenshtein distance) + */ +public final class ParamsEditDistance { + private static final int INSERT = 100; + private static final int DELETE = 100; + private static final int SWAP = 100; + private static final int CONVERTIBLE = 50; + private static final int UNRELATED = 100; + private static final int OTHER_NAME = 1; + + private enum Operation { + MATCH, + DELETE, + INSERT, + SWAP + } + + private record Step(Operation op, int clean, int dirty, int swapClean, int swapDirty) { + public Step(Operation op, int clean, int dirty) { + this(op, clean, dirty, -1, -1); + } + } + + private record Table(int[][] costs, int[][][] swapFrom) { + } + + /// Represents a temp working parameter + /// @param target target dirty index or -1 if it's removed + private record Slot(Type type, int target) { + } + + public static ParamsDiff diff(List clean, List dirty, TypeRelation relation) { + Table table = computeCosts(clean, dirty, relation); + List steps = backtrack(clean, dirty, relation, table); + return new ParamsDiff(toSnapshot(clean, dirty, steps), table.costs()[clean.size()][dirty.size()] / 100.0); + } + + public static ParamsDiff diffTypes(List clean, List dirty) { + return diff(Param.of(clean), Param.of(dirty), TypeRelation.STATIC); + } + + public static ParamsDiff diffParameters(MethodNode clean, MethodNode dirty, TypeRelation relation) { + return diff(getParameters(clean), getParameters(dirty), relation); + } + + private static List getParameters(MethodNode method) { + LocalVariableLookup lookup = LocalVariableLookup.of(method); + Type[] types = Type.getArgumentTypes(method.desc); + List params = new ArrayList<>(types.length); + + for (int i = 0; i < types.length; i++) { + LocalVariableNode local = lookup != null ? lookup.getByParameterOrdinalOrNull(i) : null; + boolean stable = local != null && !GeneratedVariables.isPositionalName(local.name, types[i]); + params.add(new Param(types[i], stable ? local.name : null)); + } + + return params; + } + + private static Table computeCosts(List clean, List dirty, TypeRelation relation) { + int cleanSize = clean.size(); + int dirtySize = dirty.size(); + + int[][] costs = new int[cleanSize + 1][dirtySize + 1]; + int[][][] swapFrom = new int[cleanSize + 1][dirtySize + 1][]; + + for (int i = 0; i <= cleanSize; i++) { + for (int j = 0; j <= dirtySize; j++) { + if (i == 0 || j == 0) { + costs[i][j] = i * DELETE + j * INSERT; + continue; + } + + int cost = Math.min(costs[i - 1][j] + DELETE, costs[i][j - 1] + INSERT); + cost = Math.min(cost, costs[i - 1][j - 1] + getMatchCost(clean.get(i - 1), dirty.get(j - 1), relation)); + + for (int k = 1; k < i; k++) { + // Swapping the same types changes nothing + if (relation.relate(clean.get(k - 1).type(), clean.get(i - 1).type()) == TypeRelation.Kind.SAME) { + continue; + } + + for (int l = 1; l < j; l++) { + int swap = getSwapCost(clean, dirty, relation, costs, i, j, k, l); + if (swap < cost) { + cost = swap; + swapFrom[i][j] = new int[]{k, l}; + } + } + } + + costs[i][j] = cost; + } + } + return new Table(costs, swapFrom); + } + + private static int getSwapCost(List clean, List dirty, TypeRelation relation, int[][] costs, + int cleanLate, int dirtyLate, int cleanEarly, int dirtyEarly + ) { + // Turn the parameters before the swapped two into each other + int before = costs[cleanEarly - 1][dirtyEarly - 1]; + // Delete the clean parameters between swapped params and insert the dirty ones + int between = (cleanLate - cleanEarly - 1) * DELETE + (dirtyLate - dirtyEarly - 1) * INSERT; + // The earlier clean parameter takes the later dirty position and the later one the earlier position + int exchanged = getMatchCost(clean.get(cleanEarly - 1), dirty.get(dirtyLate - 1), relation) + + getMatchCost(clean.get(cleanLate - 1), dirty.get(dirtyEarly - 1), relation); + return before + between + exchanged + SWAP; + } + + private static List backtrack(List clean, List dirty, TypeRelation relation, Table table) { + int[][] costs = table.costs(); + List steps = new ArrayList<>(); + int i = clean.size(); + int j = dirty.size(); + + while (i > 0 || j > 0) { + int cost = costs[i][j]; + int[] swap = i > 0 && j > 0 ? table.swapFrom()[i][j] : null; + + if (j > 0 && cost == costs[i][j - 1] + INSERT) { + steps.add(new Step(Operation.INSERT, -1, --j)); + } else if (i > 0 && cost == costs[i - 1][j] + DELETE) { + steps.add(new Step(Operation.DELETE, --i, -1)); + } else if (swap != null && cost == getSwapCost(clean, dirty, relation, costs, i, j, swap[0], swap[1])) { + int k = swap[0]; + int l = swap[1]; + + // clean k - 1 goes to dirty j - 1 + // clean i - 1 to the dirty l - 1 + // the rest between them is replaced + for (int insert = j - 2; insert >= l; insert--) { + steps.add(new Step(Operation.INSERT, -1, insert)); + } + for (int delete = i - 2; delete >= k; delete--) { + steps.add(new Step(Operation.DELETE, delete, -1)); + } + + steps.add(new Step(Operation.SWAP, k - 1, j - 1, i - 1, l - 1)); + i = k - 1; + j = l - 1; + } else { + steps.add(new Step(Operation.MATCH, --i, --j)); + } + } + + return steps.reversed(); + } + + private static LayeredParamsDiffSnapshot toSnapshot(List clean, List dirty, List steps) { + // Where each clean parameter goes + int[] targets = new int[clean.size()]; + List insertions = new ArrayList<>(); + + for (Step step : steps) { + switch (step.op()) { + case MATCH -> targets[step.clean()] = step.dirty(); + case SWAP -> { + targets[step.clean()] = step.dirty(); + targets[step.swapClean()] = step.swapDirty(); + } + case DELETE -> targets[step.clean()] = -1; + case INSERT -> insertions.add(step.dirty()); + } + } + + // A deleted parameter inserted again elsewhere is a move + List moved = new ArrayList<>(); + for (Step step : steps) { + if (step.op() == Operation.DELETE) { + Type type = clean.get(step.clean()).type(); + insertions.stream() + .filter(j -> dirty.get(j).type().equals(type)) + .findFirst() + .ifPresent(j -> { + insertions.remove(j); + targets[step.clean()] = j; + moved.add(step.clean()); + }); + } + } + + LayeredParamsDiffSnapshot.Builder builder = LayeredParamsDiffSnapshot.builder(); + List state = new ArrayList<>(); + for (int i = 0; i < clean.size(); i++) { + state.add(new Slot(clean.get(i).type(), targets[i])); + } + + for (int index = state.size() - 1; index >= 0; index--) { + if (state.get(index).target() == -1) { + builder.remove(index); + state.remove(index); + } + } + + for (int index = 0; index < state.size(); index++) { + Slot slot = state.get(index); + Type type = dirty.get(slot.target()).type(); + if (!slot.type().equals(type)) { + builder.replace(index, type); + state.set(index, new Slot(type, slot.target())); + } + } + + for (Step step : steps) { + if (step.op() == Operation.SWAP) { + int from = getIndexOfTarget(state, step.dirty()); + int to = getIndexOfTarget(state, step.swapDirty()); + builder.swap(from, to); + state.set(from, state.set(to, state.get(from))); + } + } + + // Moved parameters that aren't placed yet may still be in the way + // Place each after the last one that goes before it + moved.sort(Comparator.comparingInt(i -> targets[i])); + for (int cleanIndex : moved) { + int from = getIndexOfTarget(state, targets[cleanIndex]); + Slot slot = state.remove(from); + int to = 0; + for (int index = 0; index < state.size(); index++) { + if (state.get(index).target() < slot.target()) { + to = index + 1; + } + } + builder.move(from, to); + state.add(to, slot); + } + + Collections.sort(insertions); + for (int j : insertions) { + builder.insert(j, dirty.get(j).type()); + state.add(j, new Slot(dirty.get(j).type(), j)); + } + return builder.build(); + } + + private static int getMatchCost(Param clean, Param dirty, TypeRelation relation) { + int cost = switch (relation.relate(clean.type(), dirty.type())) { + case SAME -> 0; + case CONVERTIBLE -> CONVERTIBLE; + case UNRELATED -> UNRELATED; + }; + return clean.name() != null && dirty.name() != null && !clean.name().equals(dirty.name()) ? cost + OTHER_NAME : cost; + } + + private static int getIndexOfTarget(List state, int target) { + for (int index = 0; index < state.size(); index++) { + if (state.get(index).target() == target) { + return index; + } + } + throw new IllegalStateException("No parameter goes to " + target); + } + + private ParamsEditDistance() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/TypeRelation.java b/core/src/main/java/org/sinytra/adapter/analysis/params/TypeRelation.java new file mode 100644 index 00000000..86bec97a --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/TypeRelation.java @@ -0,0 +1,45 @@ +package org.sinytra.adapter.analysis.params; + +import org.objectweb.asm.Type; + +public interface TypeRelation { + enum Kind { + /// The same type + SAME, + /// A different type we can convert, such as a supertype or a number + CONVERTIBLE, + /// A type with nothing in common + UNRELATED + } + + Kind relate(Type clean, Type dirty); + + /// Class context-free comparison + TypeRelation STATIC = (clean, dirty) -> { + if (clean.equals(dirty)) { + return Kind.SAME; + } + if (isReference(clean) && isReference(dirty) && (isObject(clean) || isObject(dirty))) { + return Kind.CONVERTIBLE; + } + if (isNumber(clean) && isNumber(dirty)) { + return Kind.CONVERTIBLE; + } + return Kind.UNRELATED; + }; + + private static boolean isReference(Type type) { + return type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY; + } + + private static boolean isObject(Type type) { + return type.getSort() == Type.OBJECT && type.getInternalName().equals("java/lang/Object"); + } + + private static boolean isNumber(Type type) { + return switch (type.getSort()) { + case Type.BYTE, Type.SHORT, Type.CHAR, Type.INT, Type.LONG, Type.FLOAT, Type.DOUBLE -> true; + default -> false; + }; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/UnsupportedParamsDiffException.java b/core/src/main/java/org/sinytra/adapter/analysis/params/UnsupportedParamsDiffException.java deleted file mode 100644 index c4c3a5d7..00000000 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/UnsupportedParamsDiffException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sinytra.adapter.analysis.params; - -public class UnsupportedParamsDiffException extends IllegalStateException { - public UnsupportedParamsDiffException(String message) { - super(message); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java index 8e50f3cc..c57d29a7 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java @@ -5,7 +5,7 @@ import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; +import org.sinytra.adapter.analysis.params.ParamsEditDistance; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.edit.param.ReplaceParametersTransformer; import org.sinytra.adapter.model.config.Configuration; @@ -48,8 +48,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) List cleanTypes = cleanParams.getTypes(MethodParameters.ParamGroup.METHOD_PARAMS); List dirtyTypes = dirtyParams.getTypes(MethodParameters.ParamGroup.METHOD_PARAMS); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(cleanTypes, dirtyTypes); - if (diff == null) return TxResult.PASS; + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanTypes, dirtyTypes).snapshot(); MethodNode methodNode = context.methodNode(); LocalVariableLookup lookup = LocalVariableLookup.of(context.methodNode()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java index 5764f0c3..2bb4ee69 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java @@ -3,7 +3,7 @@ import com.mojang.datafixers.util.Pair; import org.objectweb.asm.Type; import org.objectweb.asm.tree.VarInsnNode; -import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; +import org.sinytra.adapter.analysis.params.ParamsEditDistance; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.edit.param.ParamTransformationUtil; @@ -55,10 +55,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) } private boolean applyDiff(List clean, List dirty, MixinContext context, int offset) { - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(clean, dirty); - if (diff == null) { - return false; - } + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); if (!diff.isEmpty()) { PatchResult result = diff.offset(offset).asParameterTransformer(false) .apply(context); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java index f0bee6e0..3069d69a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java @@ -6,7 +6,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; +import org.sinytra.adapter.analysis.params.ParamsEditDistance; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.edit.param.InjectParameterTransform; import org.sinytra.adapter.model.MethodQualifier; @@ -37,8 +37,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) List cleanParams = recipe.clean().getParameters().getTypes(ParamGroup.METHOD_PARAMS); List dirtyParams = dirtyMethodParams.getTypes(ParamGroup.METHOD_PARAMS); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(cleanParams, dirtyParams); - if (diff == null) return TxResult.PASS; + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanParams, dirtyParams).snapshot(); List> callInsns = DataFlow.of(context.methodNode(), DataFlow.Producers.COMPUTER).findCallsWithProducers(cleanTargetQual); if (callInsns.isEmpty()) { diff --git a/core/src/main/java/org/sinytra/adapter/util/GeneratedVariables.java b/core/src/main/java/org/sinytra/adapter/util/GeneratedVariables.java index 435554b8..71d83282 100644 --- a/core/src/main/java/org/sinytra/adapter/util/GeneratedVariables.java +++ b/core/src/main/java/org/sinytra/adapter/util/GeneratedVariables.java @@ -8,7 +8,6 @@ import java.util.stream.Stream; public class GeneratedVariables { - private static final String MOJMAP_PARAM_NAME_PREFIX = "p_"; // Obfuscated variable names consist of '$$' followed by a number private static final String OBF_VAR_PATTERN = "^\\$\\$\\d+$"; private static final Map GENERATED_VAR_NAMES = new HashMap<>(); @@ -42,8 +41,8 @@ public class GeneratedVariables { }); } - public static boolean isGeneratedVariableName(String name, Type type) { - if (name.startsWith(MOJMAP_PARAM_NAME_PREFIX) || name.matches(OBF_VAR_PATTERN)) { + public static boolean isPositionalName(String name, Type type) { + if (name.matches(OBF_VAR_PATTERN)) { return true; } GeneratedVarName generator = GENERATED_VAR_NAMES.get(type); diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java index 773dc760..3d5d68cf 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java @@ -2,7 +2,7 @@ import org.junit.jupiter.api.Test; import org.objectweb.asm.Type; -import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; +import org.sinytra.adapter.analysis.params.ParamsEditDistance; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.analysis.params.ParamDiffResolver; import org.sinytra.adapter.analysis.params.ParamDiffResolver.ParamEvalResult; @@ -65,7 +65,7 @@ private static void testParams(List clean, List dirty, int oldPos, i private static void testParams(List clean, List dirty, int oldPos, int newPos, Type newType) { Type original = clean.get(oldPos); - LayeredParamsDiffSnapshot snapshot = EnhancedParamsDiff.createLayered(clean, dirty); + LayeredParamsDiffSnapshot snapshot = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); ParamEvalResult result = ParamDiffResolver.resolve(clean, snapshot); ParamState updated = result.getUpdated(original); diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/ParameterComparisonTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/ParameterComparisonTest.java index b769cda3..307b73b2 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/ParameterComparisonTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/ParameterComparisonTest.java @@ -4,9 +4,9 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; -import org.sinytra.adapter.analysis.params.ParametersDiff; +import org.sinytra.adapter.analysis.params.ParamsEditDistance; +import org.sinytra.adapter.analysis.params.TypeRelation; import org.sinytra.adapter.patch.test_main.mixin.MinecraftMixinPatchTest; import java.io.IOException; @@ -18,77 +18,42 @@ public class ParameterComparisonTest { @Test public void testCompareInsertedParameters() { - Type[] original = new Type[]{Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class)}; - Type[] modified = new Type[]{Type.getType(String.class), Type.DOUBLE_TYPE, Type.FLOAT_TYPE, Type.INT_TYPE, Type.getType(Object.class)}; - - ParametersDiff diff = ParametersDiff.compareTypeParameters(original, modified); - System.out.println("Original originalCount: " + original.length); - System.out.println("Actual originalCount: " + diff.originalCount()); - assertEquals(original.length, diff.originalCount()); - - System.out.println("Insertions:"); - diff.insertions().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); + LayeredParamsDiffSnapshot diff = diff( + List.of(Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class)), + List.of(Type.getType(String.class), Type.DOUBLE_TYPE, Type.FLOAT_TYPE, Type.INT_TYPE, Type.getType(Object.class)) + ); assertEquals(2, diff.insertions().size()); - - System.out.println("Replacements:"); - diff.replacements().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); assertTrue(diff.replacements().isEmpty()); } @Test public void testCompareReplacedParameters() { - Type[] original = new Type[]{Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class)}; - Type[] modified = new Type[]{Type.getType(String.class), Type.INT_TYPE, Type.getType(List.class)}; - - ParametersDiff diff = ParametersDiff.compareTypeParameters(original, modified); - System.out.println("Original originalCount: " + original.length); - System.out.println("Actual originalCount: " + diff.originalCount()); - assertEquals(original.length, diff.originalCount()); - - System.out.println("Insertions:"); - diff.insertions().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); + LayeredParamsDiffSnapshot diff = diff( + List.of(Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class)), + List.of(Type.getType(String.class), Type.INT_TYPE, Type.getType(List.class)) + ); assertTrue(diff.insertions().isEmpty()); - - System.out.println("Replacements:"); - diff.replacements().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); assertEquals(1, diff.replacements().size()); } @Test public void testCompareCombinedParameters() { - Type[] original = new Type[]{Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class)}; - Type[] modified = new Type[]{Type.getType(String.class), Type.FLOAT_TYPE, Type.INT_TYPE, Type.getType(List.class), Type.DOUBLE_TYPE}; - - ParametersDiff diff = ParametersDiff.compareTypeParameters(original, modified); - System.out.println("Original originalCount: " + original.length); - System.out.println("Actual originalCount: " + diff.originalCount()); - assertEquals(original.length, diff.originalCount()); - - System.out.println("Insertions:"); - diff.insertions().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); + LayeredParamsDiffSnapshot diff = diff( + List.of(Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class)), + List.of(Type.getType(String.class), Type.FLOAT_TYPE, Type.INT_TYPE, Type.getType(List.class), Type.DOUBLE_TYPE) + ); assertEquals(2, diff.insertions().size()); - - System.out.println("Replacements:"); - diff.replacements().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); assertEquals(1, diff.replacements().size()); } @Test public void testCompareAppendedParameters() { - Type[] original = new Type[]{Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class)}; - Type[] modified = new Type[]{Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class), Type.FLOAT_TYPE, Type.DOUBLE_TYPE}; - - ParametersDiff diff = ParametersDiff.compareTypeParameters(original, modified); - System.out.println("Original originalCount: " + original.length); - System.out.println("Actual originalCount: " + diff.originalCount()); - assertEquals(original.length, diff.originalCount()); - - System.out.println("Insertions:"); - diff.insertions().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); + LayeredParamsDiffSnapshot diff = diff( + List.of(Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class)), + List.of(Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class), Type.FLOAT_TYPE, Type.DOUBLE_TYPE) + ); assertEquals(2, diff.insertions().size()); - - System.out.println("Replacements:"); - diff.replacements().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); + assertEquals(3, diff.insertions().getFirst().index()); assertTrue(diff.replacements().isEmpty()); } @@ -100,43 +65,26 @@ public void testMethodParameterNameComparison() throws IOException { MethodNode cleanMethod = cleanNode.methods.stream().filter(m -> m.name.equals("namedInsertionTest")).findFirst().orElseThrow(); MethodNode dirtyMethod = dirtyNode.methods.stream().filter(m -> m.name.equals("namedInsertionTest")).findFirst().orElseThrow(); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.compareMethodParameters(cleanMethod, dirtyMethod); - - System.out.println("Insertions:"); - diff.insertions().forEach(param -> System.out.println("AT " + param.index() + " TYPE " + param.type())); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffParameters(cleanMethod, dirtyMethod, TypeRelation.STATIC).snapshot(); assertEquals(1, diff.insertions().size()); assertEquals(7, diff.insertions().getFirst().index()); assertEquals(Type.FLOAT_TYPE, diff.insertions().getFirst().type()); - - System.out.println("Replacements:"); - diff.replacements().forEach(param -> System.out.println("AT " + param.index() + " TYPE " + param.type())); assertTrue(diff.replacements().isEmpty()); } @Test public void testCompareRemovedParameters() { - Type[] original = new Type[]{Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class), Type.FLOAT_TYPE}; - Type[] modified = new Type[]{Type.getType(String.class), Type.getType(Object.class), Type.FLOAT_TYPE, Type.DOUBLE_TYPE}; - - ParametersDiff diff = ParametersDiff.compareTypeParameters(original, modified); - System.out.println("Original originalCount: " + original.length); - System.out.println("Actual originalCount: " + diff.originalCount()); - assertEquals(original.length, diff.originalCount()); - - System.out.println("Insertions:"); - diff.insertions().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); + LayeredParamsDiffSnapshot diff = diff( + List.of(Type.getType(String.class), Type.INT_TYPE, Type.getType(Object.class), Type.FLOAT_TYPE), + List.of(Type.getType(String.class), Type.getType(Object.class), Type.FLOAT_TYPE, Type.DOUBLE_TYPE) + ); assertEquals(1, diff.insertions().size()); - - System.out.println("Replacements:"); - diff.replacements().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); assertTrue(diff.replacements().isEmpty()); - - System.out.println("Swaps:"); - diff.swaps().forEach(param -> System.out.println("AT " + param.getFirst() + " TYPE " + param.getSecond())); assertTrue(diff.swaps().isEmpty()); - - System.out.println("Removals:"); - diff.removals().forEach(param -> System.out.println("AT " + param)); assertEquals(1, diff.removals().size()); } + + private static LayeredParamsDiffSnapshot diff(List clean, List dirty) { + return ParamsEditDistance.diffTypes(clean, dirty).snapshot(); + } } diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/EnhancedParamsDiffTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/ParamsDiffRegressionTest.java similarity index 89% rename from test/src/test/java/org/sinytra/adapter/patch/test/EnhancedParamsDiffTest.java rename to test/src/test/java/org/sinytra/adapter/patch/test/ParamsDiffRegressionTest.java index 9876964e..b2443abb 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/EnhancedParamsDiffTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/ParamsDiffRegressionTest.java @@ -2,7 +2,7 @@ import org.junit.jupiter.api.Test; import org.objectweb.asm.Type; -import org.sinytra.adapter.analysis.params.EnhancedParamsDiff; +import org.sinytra.adapter.analysis.params.ParamsEditDistance; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.edit.param.InjectParameterTransform; import org.sinytra.adapter.edit.param.RemoveParameterTransformer; @@ -13,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class EnhancedParamsDiffTest { +public class ParamsDiffRegressionTest { @Test void testMovedParameter() { @@ -37,7 +37,7 @@ void testMovedParameter() { Type.BOOLEAN_TYPE ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(clean, dirty); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); assertEquals(1, diff.insertions().size()); assertTrue(diff.replacements().isEmpty()); assertTrue(diff.removals().isEmpty()); @@ -60,7 +60,7 @@ void testReplacedParameters() { Type.getObjectType("com/llamalad7/mixinextras/sugar/ref/LocalRef") ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(clean, dirty); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); assertTrue(diff.insertions().isEmpty()); assertEquals(2, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -81,7 +81,7 @@ void testRemovedParameter() { Type.getType("Lnet/minecraft/world/item/Item;") ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(clean, dirty); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); assertTrue(diff.insertions().isEmpty()); assertTrue(diff.replacements().isEmpty()); assertEquals(1, diff.removals().size()); @@ -105,7 +105,7 @@ void testCompareSwappedParameters() { Type.getObjectType("net/minecraft/world/item/ItemStack") ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertTrue(diff.insertions().isEmpty()); assertTrue(diff.replacements().isEmpty()); assertTrue(diff.removals().isEmpty()); @@ -128,7 +128,7 @@ void testCompareInsertedParameters() { Type.getType(Object.class) ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(2, diff.insertions().size()); assertTrue(diff.replacements().isEmpty()); assertTrue(diff.removals().isEmpty()); @@ -152,7 +152,7 @@ void testCompareInsertedParameters2() { Type.getType(Object.class) ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(1, diff.insertions().size()); assertEquals(2, diff.insertions().getFirst().index()); assertTrue(diff.replacements().isEmpty()); @@ -186,7 +186,7 @@ void testCompareInsertedParameters3() { new InjectParameterTransform(7, Type.INT_TYPE) ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(6, diff.insertions().size()); assertEquals(expectedInsertions, diff.insertions()); assertTrue(diff.replacements().isEmpty()); @@ -230,7 +230,7 @@ void testCompareInsertedParametersOrder() { new InjectParameterTransform(12, Type.getType(String.class)) ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(9, diff.insertions().size()); assertEquals(expectedInsertions, diff.insertions()); assertTrue(diff.replacements().isEmpty()); @@ -271,7 +271,7 @@ void testCompareInsertedParametersIndexOffset() { Type.INT_TYPE ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(5, diff.insertions().size()); assertEquals(1, diff.insertions().get(0).index()); assertEquals(9, diff.insertions().get(1).index()); @@ -297,7 +297,7 @@ void testCompareReplacedParameters() { Type.getType(List.class) ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertTrue(diff.insertions().isEmpty()); assertEquals(1, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -318,7 +318,7 @@ void testCompareReplacedParameters2() { Type.getType(List.class) ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertTrue(diff.insertions().isEmpty()); assertEquals(1, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -339,7 +339,7 @@ void testCompareReplacedParameters3() { Type.getObjectType("com/llamalad7/mixinextras/injector/wrapoperation/Operation") ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertTrue(diff.insertions().isEmpty()); assertEquals(1, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -362,7 +362,7 @@ void testCompareCombinedParameters() { Type.DOUBLE_TYPE ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(2, diff.insertions().size()); assertEquals(1, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -386,7 +386,7 @@ void testCompareInsertedParametersAtDifferentPlaces() { Type.DOUBLE_TYPE ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(3, diff.insertions().size()); assertEquals(new InjectParameterTransform(1, Type.FLOAT_TYPE), diff.insertions().get(0)); assertEquals(new InjectParameterTransform(4, Type.FLOAT_TYPE), diff.insertions().get(1)); @@ -412,7 +412,7 @@ void testCompareRemovedParameters() { Type.DOUBLE_TYPE ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(1, diff.insertions().size()); assertEquals(new InjectParameterTransform(3, Type.DOUBLE_TYPE), diff.insertions().getFirst()); assertTrue(diff.replacements().isEmpty()); @@ -441,7 +441,7 @@ void testCompareReorderedParameters() { Type.getType(Set.class) ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(1, diff.insertions().size()); assertEquals(new InjectParameterTransform(2, Type.getType(Deque.class)), diff.insertions().getFirst()); assertTrue(diff.replacements().isEmpty()); @@ -459,7 +459,7 @@ void testCompareComplexChanges() { Type.getType(Object.class) ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); assertEquals(1, diff.replacements().size()); assertEquals(new ReplaceParametersTransformer(0, Type.getType(Object.class)), diff.replacements().getFirst()); assertEquals(1, diff.removals().size()); @@ -480,7 +480,7 @@ void testRemovedParamsOrder() { Type.getType(String.class) ); - LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(original, modified); - assertEquals(List.of(1, 4, 3, 2, 1), diff.removals().stream().map(RemoveParameterTransformer::index).toList()); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + assertEquals(List.of(5, 4, 3, 2, 1), diff.removals().stream().map(RemoveParameterTransformer::index).toList()); } } From ef4aef29293dddac899665b5dd1ff22c19496106 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 21 Oct 2026 23:57:22 +0200 Subject: [PATCH 24/44] Rework mixin relocation --- .../adapter/edit/RelocationGenerator.java | 203 ++++++++++ .../sinytra/adapter/model/RelocationPlan.java | 16 + .../sinytra/adapter/model/ValueSource.java | 11 + .../extract/ExtractMixinProcessor.java | 379 +----------------- .../extract/MirrorableExtractMixin.java | 9 +- .../extract/ProxyExtractMixinSub.java | 71 ---- .../processor/extract/RelocationSolver.java | 143 +++++++ .../mixin/DynamicMixinPatchTest.java | 9 + .../pipeline/BeaconBlockEntityMixin.java | 25 ++ .../mixin/MinecraftMixinPatchTest.java | 17 +- 10 files changed, 446 insertions(+), 437 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java create mode 100644 core/src/main/java/org/sinytra/adapter/model/RelocationPlan.java create mode 100644 core/src/main/java/org/sinytra/adapter/model/ValueSource.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/RelocationSolver.java diff --git a/core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java b/core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java new file mode 100644 index 00000000..e6e21947 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java @@ -0,0 +1,203 @@ +package org.sinytra.adapter.edit; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.GeneratorAdapter; +import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.model.RelocationPlan; +import org.sinytra.adapter.model.ValueSource; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.AdapterUtil; +import org.sinytra.adapter.util.OpcodeUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ARGS_ONLY; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_INDEX; + +public final class RelocationGenerator { + public static MethodNode generateMirror(MixinContext context, String destinationClass, RelocationPlan.Mirror plan) { + MethodNode handler = context.methodNode(); + ClassNode handlerOwner = plan.handlerOwner(); + boolean isStatic = plan.isStatic(); + Type[] handlerArgs = Type.getArgumentTypes(handler.desc); + + int forwardedCount = (int) plan.params().stream().filter(ValueSource.Forwarded.class::isInstance).count(); + List params = new ArrayList<>(); + for (int i = 0; i < forwardedCount; i++) { + params.add(null); + } + for (int i = 0; i < plan.params().size(); i++) { + if (plan.params().get(i) instanceof ValueSource.Forwarded(int param)) { + params.set(param, handlerArgs[i]); + } + } + + Map paramIndices = new HashMap<>(); + List captured = new ArrayList<>(); + List sources = new ArrayList<>(); + if (plan.receiver() != null) { + sources.add(plan.receiver()); + } + sources.addAll(plan.params()); + for (ValueSource source : sources) { + if (source instanceof ValueSource.Captured capture && !paramIndices.containsKey(capture)) { + paramIndices.put(capture, params.size()); + params.add(capture.type()); + captured.add(capture); + } + } + + String name = handler.name + "$adapter$mirror$" + AdapterUtil.randomString(5); + String desc = Type.getMethodDescriptor(Type.getReturnType(handler.desc), params.toArray(Type[]::new)); + MethodNode mirror = new MethodNode(Opcodes.ACC_PRIVATE | (isStatic ? Opcodes.ACC_STATIC : 0), name, desc, null, null); + mirror.visibleAnnotations = new ArrayList<>(handler.visibleAnnotations); + copyParameterAnnotations(handler, mirror, plan.params()); + for (ValueSource.Captured capture : captured) { + AnnotationVisitor local = mirror.visitParameterAnnotation(paramIndices.get(capture), MixinAnnotations.LOCAL, false); + local.visit(PROPERTY_ARGS_ONLY, capture.argsOnly()); + local.visit(PROPERTY_INDEX, capture.slot()); + } + + GeneratorAdapter gen = new GeneratorAdapter(mirror, mirror.access, mirror.name, mirror.desc); + gen.newLabel(); + int stack = 0; + if (plan.receiver() != null) { + stack += load(gen, plan.receiver(), paramIndices); + Type ownerType = Type.getObjectType(handlerOwner.name); + if (!params.get(argIndex(plan.receiver(), paramIndices)).equals(ownerType)) { + gen.checkCast(ownerType); + } + } + for (ValueSource source : plan.params()) { + stack += load(gen, source, paramIndices); + } + invokeHandler(gen, handler, handlerOwner); + gen.newLabel(); + gen.returnValue(); + gen.newLabel(); + gen.endMethod(); + mirror.maxStack = Math.max(stack, Type.getReturnType(handler.desc).getSize()); + mirror.maxLocals = (Type.getArgumentsAndReturnSizes(desc) >> 2) - (isStatic ? 1 : 0); + + handler.access = OpcodeUtil.setAccessVisibility(handler.access, Opcodes.ACC_PUBLIC); + handler.visibleAnnotations.remove(context.methodAnnotation().unwrap()); + handler.visitAnnotation(MixinAnnotations.UNIQUE, true); + + ClassNode generatedTarget = getGeneratedTarget(context, destinationClass); + synchronized (generatedTarget) { + generatedTarget.methods.add(mirror); + } + return mirror; + } + + public static void generateMove(MixinContext context, String destinationClass, RelocationPlan.Move plan) { + ClassNode mixin = context.classNode(); + ClassNode generatedTarget = getGeneratedTarget(context, destinationClass); + List moved = new ArrayList<>(plan.lambdas()); + moved.addFirst(context.methodNode()); + + plan.widenedFields().forEach(field -> field.access = widenAccess(field.access)); + plan.widenedMethods().forEach(method -> method.access = widenAccess(method.access)); + for (MethodNode method : moved) { + for (AbstractInsnNode insn : method.instructions) { + if (insn instanceof MethodInsnNode methodInsn && methodInsn.owner.equals(mixin.name)) { + methodInsn.owner = destinationClass; + } else if (insn instanceof FieldInsnNode fieldInsn && fieldInsn.owner.equals(mixin.name)) { + fieldInsn.owner = destinationClass; + } else if (insn instanceof InvokeDynamicInsnNode indy) { + retargetLambdas(indy, mixin, plan.lambdas(), generatedTarget); + } + } + if (!MethodAnalyzer.isStatic(method) && method.localVariables != null) { + method.localVariables.stream() + .filter(local -> local.index == 0) + .findFirst() + .ifPresent(local -> local.desc = Type.getObjectType(generatedTarget.name).getDescriptor()); + } + } + + synchronized (generatedTarget) { + generatedTarget.methods.addAll(moved); + } + context.patchContext().postApply(() -> mixin.methods.removeAll(moved)); + } + + private static ClassNode getGeneratedTarget(MixinContext context, String destinationClass) { + PatchEnvironment environment = context.environment(); + String parent = environment.dirtyClassLookup().getClass(destinationClass).map(cls -> cls.superName).orElse(null); + ClassNode generatedTarget = environment.classGenerator().getOrGenerateMixinClass(context.classNode(), destinationClass, parent); + environment.refmapHolder().copyEntries(context.classNode().name, generatedTarget.name); + return generatedTarget; + } + + private static void retargetLambdas(InvokeDynamicInsnNode indy, ClassNode mixin, List lambdas, ClassNode generatedTarget) { + for (int i = 0; i < indy.bsmArgs.length; i++) { + if (indy.bsmArgs[i] instanceof Handle handle && handle.getOwner().equals(mixin.name) + && lambdas.stream().anyMatch(lambda -> lambda.name.equals(handle.getName()) && lambda.desc.equals(handle.getDesc())) + ) { + indy.bsmArgs[i] = new Handle(handle.getTag(), generatedTarget.name, handle.getName(), handle.getDesc(), handle.isInterface()); + } + } + } + + private static int widenAccess(int access) { + int visibility = OpcodeUtil.getAccessVisibility(access); + if (visibility == Opcodes.ACC_PRIVATE || visibility == 0) { + return OpcodeUtil.setAccessVisibility(access, Opcodes.ACC_PUBLIC) | Opcodes.ACC_SYNTHETIC; + } + return access; + } + + public static void invokeHandler(GeneratorAdapter gen, MethodNode handler, ClassNode handlerOwner) { + boolean isInterface = (handlerOwner.access & Opcodes.ACC_INTERFACE) != 0; + int opcode = MethodAnalyzer.isStatic(handler) ? Opcodes.INVOKESTATIC : isInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL; + gen.visitMethodInsn(opcode, handlerOwner.name, handler.name, handler.desc, isInterface); + } + + private static int load(GeneratorAdapter gen, ValueSource source, Map paramIndices) { + int arg = argIndex(source, paramIndices); + gen.loadArg(arg); + return gen.getArgumentTypes()[arg].getSize(); + } + + private static int argIndex(ValueSource source, Map paramIndices) { + return switch (source) { + case ValueSource.Forwarded(int param) -> param; + case ValueSource.Captured capture -> paramIndices.get(capture); + }; + } + + private static void copyParameterAnnotations(MethodNode handler, MethodNode mirror, List sources) { + int count = Type.getArgumentCount(mirror.desc); + mirror.visibleParameterAnnotations = copyParameterAnnotations(handler.visibleParameterAnnotations, sources, count); + mirror.invisibleParameterAnnotations = copyParameterAnnotations(handler.invisibleParameterAnnotations, sources, count); + } + + @SuppressWarnings("unchecked") + @Nullable + private static List[] copyParameterAnnotations(@Nullable List[] annotations, List sources, int count) { + if (annotations == null) { + return null; + } + List[] copy = new List[count]; + for (int i = 0; i < sources.size() && i < annotations.length; i++) { + if (sources.get(i) instanceof ValueSource.Forwarded(int param) && annotations[i] != null) { + copy[param] = new ArrayList<>(annotations[i]); + } + } + return copy; + } + + private RelocationGenerator() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/model/RelocationPlan.java b/core/src/main/java/org/sinytra/adapter/model/RelocationPlan.java new file mode 100644 index 00000000..e0d204e9 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/model/RelocationPlan.java @@ -0,0 +1,16 @@ +package org.sinytra.adapter.model; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.List; + +public sealed interface RelocationPlan { + record Move(List lambdas, List widenedFields, List widenedMethods) implements RelocationPlan { + } + + record Mirror(ClassNode handlerOwner, boolean isStatic, @Nullable ValueSource receiver, List params) implements RelocationPlan { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/model/ValueSource.java b/core/src/main/java/org/sinytra/adapter/model/ValueSource.java new file mode 100644 index 00000000..e1283e84 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/model/ValueSource.java @@ -0,0 +1,11 @@ +package org.sinytra.adapter.model; + +import org.objectweb.asm.Type; + +public sealed interface ValueSource { + record Forwarded(int param) implements ValueSource { + } + + record Captured(Type type, int slot, boolean argsOnly) implements ValueSource { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java index bf6c4eca..9d21173a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java @@ -1,380 +1,43 @@ package org.sinytra.adapter.pipeline.processor.extract; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Handle; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.locals.LocalVariable; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; +import org.objectweb.asm.tree.MethodInsnNode; import org.sinytra.adapter.api.PatchResult; -import org.sinytra.adapter.edit.param.TransformParameters; +import org.sinytra.adapter.edit.RelocationGenerator; import org.sinytra.adapter.model.Relocation; -import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.key.IntentKeys; -import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processor; -import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.OpcodeUtil; -import org.sinytra.adapter.util.SingleValueHandle; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.stream.IntStream; - -// TODO Cleanup extraction process + proxy/mirroring public class ExtractMixinProcessor implements Processor { @Override public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { - if (recipe.clean().getTargetClass().equals(dirty.getTargetClass())) return TxResult.PASS; - - PatchResult result = apply(dirty.getTargetClass(), context, recipe); - Relocation relocation = dirty.getProperty(IntentKeys.RELOCATION).orElse(null); - if (result == PatchResult.PASS && relocation instanceof Relocation.CalledFrom(MethodInsnNode call)) { - result = MirrorableExtractMixin.apply(context, recipe, dirty.getTargetClass(), call); - } - if (result == PatchResult.PASS && relocation instanceof Relocation.ReceivesOrigin) { - return ProxyExtractMixinSub.INSTANCE.process(context, dirty, recipe); - } - - return result == PatchResult.PASS ? TxResult.FAIL : TxResult.SUCCESS; - } - - // TODO Prioritize mirroring over extraction, remove locals recreation - public static PatchResult apply(String targetClassName, MixinContext context, Recipe recipe) { - // Sanity check - boolean isStatic = context.isStatic(); - ClassNode classNode = context.classNode(); - MethodNode methodNode = context.methodNode(); - - TargetPair cleanTarget = recipe.getCleanTarget(); - String owner = Optional.ofNullable(cleanTarget).map(t -> t.classNode().name).orElse(targetClassName); - boolean isInherited = context.environment().inheritanceHandler(context.dirtyLookup()).isClassInherited(targetClassName, owner); - Candidates candidates = findCandidates(classNode, methodNode); - if (!candidates.canMove(classNode, isInherited)) return PatchResult.PASS; - - ClassNode targetClass = context.environment().dirtyClassLookup().getClass(targetClassName).orElse(null); - // Get or generate new mixin class - ClassNode generatedTarget = context.environment().classGenerator().getOrGenerateMixinClass(classNode, targetClassName, targetClass != null ? targetClass.superName : null); - context.environment().refmapHolder().copyEntries(classNode.name, generatedTarget.name); - - // Add mixin methods from original to generated class - for (MethodNode method : candidates.methods()) { - generatedTarget.methods.add(method); - updateOwnerRefereces(method, classNode, targetClassName); - if (!isStatic && method.localVariables != null) { - method.localVariables.stream().filter(l -> l.index == 0).findFirst().ifPresent(lvn -> lvn.desc = Type.getObjectType(generatedTarget.name).getDescriptor()); - } - } - - candidates.handleUpdates().forEach(c -> c.accept(generatedTarget)); - - // Take care of captured locals - PatchResult result = PatchResult.PASS; - boolean capturesLocals = recipe.clean().hasProperty(MixinKeys.LOCALS); - if (capturesLocals) { - result = result.or(recreateLocalVariables(methodNode, context, recipe, generatedTarget)); - } + String destinationClass = dirty.getTargetClass(); + if (recipe.clean().getTargetClass().equals(destinationClass)) return TxResult.PASS; - context.recordCtxAudit("Extract mixin to target %s", targetClassName); - // Remove original method - context.patchContext().postApply(() -> classNode.methods.removeAll(candidates.methods)); - return result.or(PatchResult.APPLY); - } - - record Candidates(List methods, List> handleUpdates) { - public boolean canMove(ClassNode classNode, boolean isInherited) { - List accessFixes = new ArrayList<>(); - for (MethodNode methodNode : this.methods) { - for (AbstractInsnNode insn : methodNode.instructions) { - if (insn instanceof FieldInsnNode finsn && finsn.owner.equals(classNode.name) && !isInheritedField(classNode, finsn, isInherited, accessFixes) - || insn instanceof MethodInsnNode minsn && minsn.owner.equals(classNode.name) && !isInheritedMethod(classNode, minsn, isInherited, accessFixes) - ) { - // We can't move methods that access their class instance - return false; - } - } + switch (RelocationSolver.solve(context, recipe, dirty)) { + case RelocationSolver.Result.Solved(RelocationPlan.Move move) -> { + RelocationGenerator.generateMove(context, destinationClass, move); + context.recordCtxAudit("Move mixin to target %s", destinationClass); } - accessFixes.forEach(Runnable::run); - return true; - } - } - - private static Candidates findCandidates(ClassNode classNode, MethodNode methodNode) { - List methods = new ArrayList<>(); - List> handleUpdates = new ArrayList<>(); - methods.add(methodNode); - for (IndyHandle lambda : MethodAnalyzer.findOwnIndyHandles(classNode, methodNode)) { - Handle handle = lambda.handle(); - if (handle.getName().startsWith(MethodAnalyzer.LAMBDA_PREFIX + methodNode.name)) { - methods.add(lambda.method()); - handleUpdates.add(t -> lambda.indy().bsmArgs[lambda.argIndex()] = new Handle(handle.getTag(), t.name, handle.getName(), handle.getDesc(), handle.isInterface())); + case RelocationSolver.Result.Solved(RelocationPlan.Mirror mirror) -> { + RelocationGenerator.generateMirror(context, destinationClass, mirror); + context.recordCtxAudit("Mirror mixin to target %s", destinationClass); } - } - return new Candidates(methods, handleUpdates); - } - - private static void updateOwnerRefereces(MethodNode methodNode, ClassNode originalClass, String targetClass) { - for (AbstractInsnNode insn : methodNode.instructions) { - if (insn instanceof MethodInsnNode minsn && minsn.owner.equals(originalClass.name)) { - minsn.owner = targetClass; - } else if (insn instanceof FieldInsnNode finsn && finsn.owner.equals(originalClass.name)) { - finsn.owner = targetClass; - } - } - } - - private static boolean isInheritedField(ClassNode cls, FieldInsnNode finsn, boolean isTargetInherited, List accessUpdates) { - return cls.fields.stream() - .filter(f -> f.name.equals(finsn.name)) - .findFirst() - .map(f -> isInheritedMember(f.visibleAnnotations, SingleValueHandle.of(() -> f.access, a -> f.access = a), isTargetInherited, accessUpdates)) - .orElse(false); - } - - private static boolean isInheritedMethod(ClassNode cls, MethodInsnNode minsn, boolean isTargetInherited, List accessUpdates) { - return Optional.ofNullable(AdapterUtil.findMethod(cls, minsn.name, minsn.desc)) - .map(m -> isInheritedMember(m.visibleAnnotations, SingleValueHandle.of(() -> m.access, a -> m.access = a), isTargetInherited, accessUpdates)) - .orElse(false); - } - - private static boolean isInheritedMember(@Nullable List annotations, SingleValueHandle access, boolean isTargetInherited, List accessUpdates) { - if (AdapterUtil.hasAnnotation(annotations, MixinAnnotations.SHADOW)) { - return true; - } - if (isTargetInherited) { - accessUpdates.add(() -> access.set(fixAccess(access.get()))); - return true; - } - return false; - } - - private static int fixAccess(int access) { - int visibility = OpcodeUtil.getAccessVisibility(access); - // Lower than protected - if (visibility == Opcodes.ACC_PRIVATE || visibility == 0) { - // Widen to protected - // Add synthetic to avoid mixin complaining about non-private static members being present - // Setting the access to public will prevent the member from being renamed - return OpcodeUtil.setAccessVisibility(access, Opcodes.ACC_PUBLIC) | Opcodes.ACC_SYNTHETIC; - } - return access; - } - - /** - * We can't bring captured locals along when extracting a mixin, so our best bet is to recreate them from scratch. - *

- * Consider the following mixin injector into Gui#renderPlayerHealth, capturing 15 local variables of which only 2 are used. - *

{@code @Inject(
-     *     method = "renderPlayerHealth",
-     *     at = @At(
-     *         value = "INVOKE",
-     *         target = "someInvocation",
-     *     ),
-     *     locals = LocalCapture.CAPTURE_FAILEXCEPTION
-     * )
-     * private void renderArmor(GuiGraphics context, CallbackInfo info, Player player, int i, boolean bl, long l, int j, FoodData foodData, int k, int left, int n, int o, float f, int p, int q, int r, int top) {
-     *     return drawArmor(context, left, top);
-     * }
-     * }
- *

- * We first filter out unused captured locals. - *

{@code @Inject(
-     *     method = "renderPlayerHealth",
-     *     at = @At(
-     *         value = "INVOKE",
-     *         target = "someInvocation",
-     *     ),
-     *     locals = LocalCapture.CAPTURE_FAILEXCEPTION
-     * )
-     * private void renderArmor(GuiGraphics context, CallbackInfo info, int left, int top) {
-     *     return drawArmor(context, left, top);
-     * }
-     * }
- *

- * Next, we create a prefixed copy of the method for overloading. - * The remaining captured locals are also removed from the original, as we provide them ourselves. - *

{@code @Inject(
-     *     method = "renderPlayerHealth",
-     *     at = @At(
-     *         value = "INVOKE",
-     *         target = "someInvocation",
-     *     )
-     * )
-     * private void renderArmor(GuiGraphics context, CallbackInfo info) {
-     *
-     * }
-     *
-     * private void adapter$bridge$renderArmor(GuiGraphics context, CallbackInfo info, int left, int top) {
-     *     return drawArmor(context, left, top);
-     * }}
- *

- * Finally, we load the local variable initializer instructions and call the overloaded method. - * Local variables that are referenced multiple times in the original target's code will be stored as local variables - * in the injector for reuse. - *

{@code @Inject(
-     *     method = "renderPlayerHealth",
-     *     at = @At(
-     *         value = "INVOKE",
-     *         target = "someInvocation",
-     *     )
-     * )
-     * private void renderArmor(GuiGraphics context, CallbackInfo info) {
-     *     int shared = context.someMethod();
-     *     return adapter$bridge$renderArmor(context, shared.getLeft(), shared.getTop());
-     * }
-     *
-     * private void adapter$bridge$renderArmor(GuiGraphics context, CallbackInfo info, int left, int top) {
-     *     return drawArmor(context, left, top);
-     * }
-     * }
- */ - private static PatchResult recreateLocalVariables(MethodNode methodNode, MixinContext context, Recipe recipe, ClassNode extractClass) { - LocalsAnalyzer.CapturedLocals capturedLocals = context.locals().resolveCapturedLocals(recipe.getDirtyTarget()); - if (capturedLocals == null) { - return PatchResult.PASS; - } - int paramLocalStart = capturedLocals.paramLocalStart(); - - // Mixin requires capturing locals in their original order, so we must filter out unused ones - LocalsAnalyzer.CapturedLocalsTransform transform = LocalsAnalyzer.analyzeCapturedLocals(capturedLocals, methodNode); - LocalsAnalyzer.CapturedLocalsUsage usage = context.locals().resolveUsage(capturedLocals, transform); - // Used captured locals in parameter order - List used = transform.used().stream().sorted().toList(); - LocalVariableLookup targetTable = usage.targetTable(); - Int2ObjectMap usedLocals = usage.usedLocals(); - Int2ObjectMap varInsnLists = usage.varInsnLists(); - Int2IntMap usageCount = usage.usageCount(); - - // Validate all local variable lookups before the method is modified - if (targetTable == null || !usedLocals.keySet().containsAll(used) - || usageCount.int2IntEntrySet().stream().anyMatch(e -> e.getIntValue() > 1 && targetTable.getByIndexOrNull(e.getIntKey()) == null) - ) { - return PatchResult.PASS; - } - - // Remove unused captured locals, if there are any - if (!transform.remover().isEmpty() && transform.remover().apply(context) == PatchResult.PASS) { - return PatchResult.PASS; - } - - // Create a copy of the method to call - MethodNode copy = new MethodNode(Opcodes.ACC_PRIVATE | (capturedLocals.isStatic() ? Opcodes.ACC_STATIC : 0), "adapter$bridge$" + methodNode.name, methodNode.desc, null, null); - methodNode.accept(copy); - copy.visibleAnnotations = null; - copy.invisibleAnnotations = null; - methodNode.instructions = new InsnList(); - // Remove used locals from the original method, as we'll be providing them ourselves - TransformParameters cleanupPatch = TransformParameters.builder() - .chain(b -> IntStream.range(paramLocalStart, paramLocalStart + used.size()).boxed() - .sorted(Collections.reverseOrder()) - .forEach(b::remove)) - .build(); - if (!cleanupPatch.isEmpty()) { - cleanupPatch.apply(context); - } - - // Determine parameter slots of the remaining method parameters - Type[] remainingParams = Type.getArgumentTypes(methodNode.desc); - int[] paramSlots = new int[remainingParams.length]; - int nextSlot = capturedLocals.isStatic() ? 0 : 1; - for (int i = 0; i < remainingParams.length; i++) { - paramSlots[i] = nextSlot; - nextSlot += remainingParams[i].getSize(); - } - - // Save reused variables. Locals that are only referenced once will be inlined instead - InsnList replacementInsns = new InsnList(); - AtomicInteger nextAvailableIndex = new AtomicInteger(nextSlot); - usageCount.forEach((index, count) -> { - if (count == 1) { - int usages = 0; - for (Int2ObjectMap.Entry entry : varInsnLists.int2ObjectEntrySet()) { - for (AbstractInsnNode insn : entry.getValue()) { - if (insn instanceof VarInsnNode varInsn && varInsn.var == index) { - // Copy the initializers, as the variable may be loaded on its own as well - InsnList varInitializers = varInsnLists.get(varInsn.var); - entry.getValue().insert(varInsn, AdapterUtil.insnList(AdapterUtil.cloneInsns(Arrays.asList(varInitializers.toArray())))); - entry.getValue().remove(varInsn); - usages++; - } - } - } - if (usages > 1) { - throw new IllegalStateException("Expected only one reference to variable " + index); - } - } - }); - LabelNode end = new LabelNode(); - Map newIndices = new HashMap<>(); - usageCount.forEach((index, count) -> { - if (count > 1) { - LocalVariableNode node = targetTable.getByIndexOrNull(index); - Type type = Type.getType(node.desc); - int newIndex = nextAvailableIndex.getAndAdd(type.getSize()); - LabelNode start = new LabelNode(); - methodNode.localVariables.add(new LocalVariableNode(node.name, node.desc, node.signature, start, end, newIndex)); - InsnList insns = varInsnLists.get((int) index); - replacementInsns.add(insns); - replacementInsns.add(new VarInsnNode(type.getOpcode(Opcodes.ISTORE), newIndex)); - replacementInsns.add(start); - // Update initializers to point to the new index instead - varInsnLists.remove((int) index); - varInsnLists.forEach((varIndex, varInsns) -> { - for (AbstractInsnNode insn : varInsns) { - if (insn instanceof VarInsnNode varInsn && varInsn.var == index) { - varInsn.var = newIndex; - } - } - }); - newIndices.put(index, newIndex); - } - }); - - // Load the instance and the parameters preceding the captured locals - if (!capturedLocals.isStatic()) { - replacementInsns.add(new VarInsnNode(Opcodes.ALOAD, 0)); - } - for (int i = 0; i < paramLocalStart && i < remainingParams.length; i++) { - replacementInsns.add(new VarInsnNode(remainingParams[i].getOpcode(Opcodes.ILOAD), paramSlots[i])); - } - // Load recreated locals - for (int ordinal : used) { - LocalVariable local = usedLocals.get(ordinal); - InsnList insns = varInsnLists.get(local.index()); - if (insns != null) { - replacementInsns.add(insns); - } else { - int newIndex = newIndices.getOrDefault(local.index(), -1); - if (newIndex == -1) { - throw new IllegalArgumentException("Missing new index for var " + local.index()); + case RelocationSolver.Result.Unbound(String reason) -> { + // TODO Port onto the solver + if (dirty.getProperty(IntentKeys.RELOCATION).orElse(null) instanceof Relocation.CalledFrom(MethodInsnNode call) + && MirrorableExtractMixin.apply(context, recipe, destinationClass, call) != PatchResult.PASS + ) { + return TxResult.SUCCESS; } - replacementInsns.add(new VarInsnNode(local.type().getOpcode(Opcodes.ILOAD), newIndex)); + context.recordCtxAudit("Can't relocate mixin to target %s: %s", destinationClass, reason); + return TxResult.FAIL; } } - // Load the remaining parameters following the captured locals (MixinExtras sugar parameters) - for (int i = paramLocalStart; i < remainingParams.length; i++) { - replacementInsns.add(new VarInsnNode(remainingParams[i].getOpcode(Opcodes.ILOAD), paramSlots[i])); - } - // Call overloaded method - replacementInsns.add(new MethodInsnNode(capturedLocals.isStatic() ? Opcodes.INVOKESTATIC : Opcodes.INVOKEVIRTUAL, extractClass.name, copy.name, copy.desc)); - replacementInsns.add(new LabelNode()); - replacementInsns.add(new InsnNode(OpcodeUtil.getReturnOpcode(methodNode))); - replacementInsns.add(end); - methodNode.instructions = replacementInsns; - - extractClass.methods.add(copy); - return PatchResult.COMPUTE_FRAMES; + return TxResult.SUCCESS; } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java index 1961ca31..c0c2da06 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java @@ -8,6 +8,7 @@ import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.edit.RelocationGenerator; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.constant.MixinAnnotations; @@ -123,17 +124,11 @@ public static MethodNode generateMirror(MixinContext context, String destination for (int argIndex : argIndices) { gen.loadArg(argIndex); } - invokeHandler(gen, methodNode, handlerOwner); + RelocationGenerator.invokeHandler(gen, methodNode, handlerOwner); gen.newLabel(); gen.returnValue(); gen.newLabel(); gen.endMethod(); return mirror; } - - static void invokeHandler(GeneratorAdapter gen, MethodNode handler, ClassNode handlerOwner) { - boolean isInterface = (handlerOwner.access & Opcodes.ACC_INTERFACE) != 0; - int opcode = MethodAnalyzer.isStatic(handler) ? Opcodes.INVOKESTATIC : isInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL; - gen.visitMethodInsn(opcode, handlerOwner.name, handler.name, handler.desc, isInterface); - } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java deleted file mode 100644 index b5654ec2..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ProxyExtractMixinSub.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.sinytra.adapter.pipeline.processor.extract; - -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.model.Relocation; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.key.IntentKeys; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.model.param.Parameters; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.TxResult; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processor; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.IntStream; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ARGS_ONLY; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_INDEX; - -// Create an identical mixin in the destination class and call the old one -public class ProxyExtractMixinSub implements Processor { - public static final ProxyExtractMixinSub INSTANCE = new ProxyExtractMixinSub(); - - @Override - public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { - if (recipe.clean().getTargetClass().equals(dirty.getTargetClass())) - return TxResult.PASS; - - if (!(dirty.getProperty(IntentKeys.RELOCATION).orElse(null) instanceof Relocation.ReceivesOrigin(int originParamIndex)) || context.locals().capturesLocals()) - return TxResult.FAIL; - - TargetPair dirtyTarget = recipe.getNewCleanTarget(); - if (dirtyTarget == null) return TxResult.FAIL; - - MethodNode methodNode = context.methodNode(); - - if (dirty.getTargetMethod() == null || dirty.getTargetMethod().desc() == null) return TxResult.FAIL; - - List destParams = Parameters.getParameterTypes(dirty.getTargetMethod().desc()); - Type instanceType = destParams.get(originParamIndex); - String destinationClass = dirty.getTargetClass(); - - // Cool, out instance is passed into the method. Now let's inject there and call the old mixin method - List originalParams = Parameters.getParameterTypes(methodNode.desc); - List newParams = new ArrayList<>(originalParams); - int capturedInstanceParamIndex = newParams.size(); - newParams.add(instanceType); - String newDesc = Type.getMethodDescriptor(Type.getReturnType(methodNode.desc), newParams.toArray(Type[]::new)); - - // The original handler is merged into the original target class - Type handlerOwner = Type.getObjectType(dirtyTarget.classNode().name); - MethodNode mirror = MirrorableExtractMixin.generateMirror(context, destinationClass, newDesc, dirtyTarget.classNode(), gen -> { - gen.loadArg(capturedInstanceParamIndex); - if (!instanceType.equals(handlerOwner)) { - gen.checkCast(handlerOwner); - } - }, IntStream.range(0, originalParams.size()).toArray()); - // Add @Local to captured instance param - AnnotationVisitor visitor = mirror.visitParameterAnnotation(capturedInstanceParamIndex, MixinAnnotations.LOCAL, false); - visitor.visit(PROPERTY_ARGS_ONLY, true); - visitor.visit(PROPERTY_INDEX, originParamIndex); - mirror.maxLocals = mirror.maxStack = 999; - - context.recordCtxAudit("Proxy (mirror) mixin to target %s", destinationClass); - return TxResult.SUCCESS; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/RelocationSolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/RelocationSolver.java new file mode 100644 index 00000000..f015608e --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/RelocationSolver.java @@ -0,0 +1,143 @@ +package org.sinytra.adapter.pipeline.processor.extract; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.Relocation; +import org.sinytra.adapter.model.RelocationPlan; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ValueSource; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.IntentKeys; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.AdapterUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +public final class RelocationSolver { + public sealed interface Result { + record Solved(RelocationPlan plan) implements Result { + } + + record Unbound(String reason) implements Result { + } + } + + public static Result solve(MixinContext context, Recipe recipe, Configuration dirty) { + List params = Parameters.parse(context.methodNode()); + if (context.locals().capturesLocals()) { + return new Result.Unbound("the handler captures locals of the original target"); + } + + String destination = dirty.getTargetClass(); + RelocationPlan.Move move = findMove(context, recipe, destination); + if (move != null) { + return new Result.Solved(move); + } + + Relocation relocation = dirty.getProperty(IntentKeys.RELOCATION).orElse(null); + if (relocation == null) { + return new Result.Unbound("the handler uses its own class, and nothing at the new target holds its instance"); + } + MethodQualifier targetMethod = dirty.getTargetMethod(); + TargetPair newTarget = targetMethod != null + ? context.dirtyMethods().resolveDeclared(targetMethod.withOwner(Type.getObjectType(destination))) + : null; + if (newTarget == null) { + return new Result.Unbound("the new target method doesn't exist"); + } + + TargetPair handlerTarget = recipe.getNewCleanTarget(); + if (handlerTarget == null) { + return new Result.Unbound("the original target method doesn't exist"); + } + boolean isStatic = MethodAnalyzer.isStatic(newTarget.methodNode()); + if (params.stream().anyMatch(Parameter::isLocalOrShare)) { + return new Result.Unbound("the handler has @Local or @Share parameters, which the generated injector can't pass on"); + } + + List forwarded = IntStream.range(0, params.size()).mapToObj(ValueSource.Forwarded::new).toList(); + if (context.isStatic()) { + return new Result.Solved(new RelocationPlan.Mirror(handlerTarget.classNode(), isStatic, null, forwarded)); + } + ValueSource receiver = findReceiver(relocation, newTarget); + if (receiver == null) { + return new Result.Unbound("nothing at the new target holds the original instance"); + } + return new Result.Solved(new RelocationPlan.Mirror(handlerTarget.classNode(), isStatic, receiver, forwarded)); + } + + @Nullable + private static RelocationPlan.Move findMove(MixinContext context, Recipe recipe, String destination) { + ClassNode mixin = context.classNode(); + MethodNode handler = context.methodNode(); + TargetPair cleanTarget = recipe.getCleanTarget(); + String origin = cleanTarget != null ? cleanTarget.classNode().name : destination; + boolean inherited = context.environment().inheritanceHandler(context.dirtyLookup()).isClassInherited(destination, origin); + + List lambdas = MethodAnalyzer.findOwnIndyHandles(mixin, handler).stream() + .filter(lambda -> lambda.handle().getName().startsWith(MethodAnalyzer.LAMBDA_PREFIX + handler.name)) + .map(MethodAnalyzer.IndyHandle::method) + .toList(); + List moved = new ArrayList<>(lambdas); + moved.addFirst(handler); + + List widenedFields = new ArrayList<>(); + List widenedMethods = new ArrayList<>(); + for (MethodNode method : moved) { + for (AbstractInsnNode insn : method.instructions) { + if (insn instanceof FieldInsnNode fieldInsn && fieldInsn.owner.equals(mixin.name)) { + FieldNode field = AdapterUtil.findOne(mixin.fields, f -> f.name.equals(fieldInsn.name)).orElse(null); + if (field == null || !isAvailable(field.visibleAnnotations, inherited)) { + return null; + } + addWidened(widenedFields, field, field.visibleAnnotations); + } else if (insn instanceof MethodInsnNode methodInsn && methodInsn.owner.equals(mixin.name)) { + MethodNode member = AdapterUtil.findMethod(mixin, methodInsn.name, methodInsn.desc); + if (member == null || !isAvailable(member.visibleAnnotations, inherited)) { + return null; + } + addWidened(widenedMethods, member, member.visibleAnnotations); + } + } + } + return new RelocationPlan.Move(lambdas, widenedFields, widenedMethods); + } + + private static boolean isAvailable(@Nullable List annotations, boolean inherited) { + return inherited || AdapterUtil.hasAnnotation(annotations, MixinAnnotations.SHADOW); + } + + private static void addWidened(List widened, T member, @Nullable List annotations) { + if (!AdapterUtil.hasAnnotation(annotations, MixinAnnotations.SHADOW) && !widened.contains(member)) { + widened.add(member); + } + } + + @Nullable + private static ValueSource findReceiver(Relocation relocation, TargetPair newTarget) { + if (relocation instanceof Relocation.ReceivesOrigin(int argIndex)) { + Type[] args = Type.getArgumentTypes(newTarget.methodNode().desc); + if (argIndex >= args.length) { + return null; + } + int slot = MethodAnalyzer.isStatic(newTarget.methodNode()) ? 0 : 1; + for (int i = 0; i < argIndex; i++) { + slot += args[i].getSize(); + } + return new ValueSource.Captured(args[argIndex], slot, true); + } + return null; + } + + private RelocationSolver() { + } +} diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java index e592a374..9f716142 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java @@ -499,6 +499,15 @@ void testExtractByCodePath() throws Exception { ); } + @Test + void testExtractCapturingLocals() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin", + "captureBeamBlock", + assertAudit("Can't relocate") + ); + } + // The call's owner was narrowed to a subclass @Test void testNarrowedCallOwner() throws Exception { diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java index 469d0637..d7bbd1c7 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java @@ -4,10 +4,17 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.item.DyeColor; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BeaconBeamBlock; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BeaconBlockEntity; +import net.minecraft.world.level.block.state.BlockState; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @Mixin(BeaconBlockEntity.class) public abstract class BeaconBlockEntityMixin { @@ -27,4 +34,22 @@ private static int replaceDyeColor(DyeColor instance, @Local(ordinal = 1) BlockP private static int replaceDyeColorExpected(DyeColor instance, @Local(ordinal = 1) BlockPos pos, @Local(argsOnly = true) Level level) { return instance.getTextureDiffuseColor(); } + + @Inject( + method = "tick(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BeaconBlockEntity;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I"), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private static void captureBeamBlock(Level level, BlockPos pos, BlockState state, BeaconBlockEntity blockEntity, CallbackInfo ci, int i, int j, int k, BlockPos blockPos, @Coerce Object section, int l, int m, BlockState blockState, Block block, BeaconBeamBlock beamBlock) { + System.out.println(beamBlock); + } + + @Inject( + method = "tick(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BeaconBlockEntity;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I"), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private static void captureBeamBlockExpected(Level level, BlockPos pos, BlockState state, BeaconBlockEntity blockEntity, CallbackInfo ci, int i, int j, int k, BlockPos blockPos, @Coerce Object section, int l, int m, BlockState blockState, Block block, BeaconBeamBlock beamBlock) { + System.out.println(beamBlock); + } } diff --git a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java index 25111c87..71630a77 100644 --- a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java +++ b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java @@ -28,8 +28,8 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; import java.util.stream.StreamSupport; +import java.util.stream.Stream; import java.util.zip.ZipFile; import static org.junit.jupiter.api.Assertions.*; @@ -210,6 +210,21 @@ protected AssertCallback assertHasGeneratedMethod(String targetClass) { return (patched, expected, env) -> { MixinClassGenerator.GeneratedClass generatedClass = env.classGenerator().getGeneratedMixinClasses().get(targetClass); assertNotNull(generatedClass, "Missing generated class for " + targetClass); + boolean callsPatched = generatedClass.node().methods.stream() + .filter(m -> m.visibleAnnotations != null && !m.visibleAnnotations.isEmpty()) + .anyMatch(m -> StreamSupport.stream(m.instructions.spliterator(), false) + .anyMatch(insn -> insn instanceof MethodInsnNode call && call.name.equals(patched.name) && call.desc.equals(patched.desc))); + Assertions.assertThat(callsPatched).as("Generated injector calls " + patched.name).isTrue(); + }; + } + + protected AssertCallback assertAudit(String text) { + return (patched, expected, env) -> { + boolean found = env.auditTrail().getAuditTrail().values().stream() + .filter(log -> (patched.name + patched.desc).equals(log.originalMethod())) + .flatMap(log -> log.entries().stream()) + .anyMatch(entry -> entry.second().toString().contains(text)); + Assertions.assertThat(found).as("Audit log of " + patched.name + " contains \"" + text + "\"").isTrue(); }; } From 9dd7d40024b34254f5fd7f7513fc1437cb4d0772 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 16:04:10 +0200 Subject: [PATCH 25/44] Finalize extraction rework --- .../adapter/edit/RelocationGenerator.java | 143 +++++++++++++++++- .../sinytra/adapter/pipeline/PatcherImpl.java | 4 +- .../org/sinytra/adapter/pipeline/Recipe.java | 13 +- .../adapter/pipeline/ctx/PatchContext.java | 2 + .../pipeline/ctx/PatchContextImpl.java | 20 ++- .../pipeline/processor/Processors.java | 4 +- .../processor/SplitCancellationProcessor.java | 35 ++++- .../SplitMethodCancellationHelper.java | 136 ----------------- .../processor/TargetMethodProcessor.java | 5 +- .../extract/MirrorableExtractMixin.java | 134 ---------------- .../RelocationProcessor.java} | 14 +- .../RelocationSolver.java | 8 +- .../org/sinytra/adapter/util/AdapterUtil.java | 12 -- .../mixin/DynamicMixinPatchTest.java | 94 +++++++++++- .../sinytra/adapter/test/mixin/GuiMixin.java | 18 +++ .../adapter/test/mixin/LivingEntityMixin.java | 10 ++ .../pipeline/BeaconBlockEntityMixin.java | 16 ++ .../test/mixin/pipeline/BlockMixin.java | 28 ++++ .../mixin/MinecraftMixinPatchTest.java | 27 ++++ 19 files changed, 407 insertions(+), 316 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitMethodCancellationHelper.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{extract/ExtractMixinProcessor.java => relocate/RelocationProcessor.java} (68%) rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{extract => relocate}/RelocationSolver.java (94%) create mode 100644 test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BlockMixin.java diff --git a/core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java b/core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java index e6e21947..7cdf5bac 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java +++ b/core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java @@ -3,15 +3,19 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; +import org.objectweb.asm.commons.Method; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.ValueSource; import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.OpcodeUtil; @@ -57,7 +61,7 @@ public static MethodNode generateMirror(MixinContext context, String destination } } - String name = handler.name + "$adapter$mirror$" + AdapterUtil.randomString(5); + String name = handler.name + "$adapter$mirror$" + simpleName(context.classNode()); String desc = Type.getMethodDescriptor(Type.getReturnType(handler.desc), params.toArray(Type[]::new)); MethodNode mirror = new MethodNode(Opcodes.ACC_PRIVATE | (isStatic ? Opcodes.ACC_STATIC : 0), name, desc, null, null); mirror.visibleAnnotations = new ArrayList<>(handler.visibleAnnotations); @@ -95,8 +99,14 @@ public static MethodNode generateMirror(MixinContext context, String destination ClassNode generatedTarget = getGeneratedTarget(context, destinationClass); synchronized (generatedTarget) { + mirror.name = uniqueMethodName(generatedTarget, mirror.name); generatedTarget.methods.add(mirror); } + context.patchContext().onRollback(() -> { + synchronized (generatedTarget) { + generatedTarget.methods.remove(mirror); + } + }); return mirror; } @@ -105,6 +115,7 @@ public static void generateMove(MixinContext context, String destinationClass, R ClassNode generatedTarget = getGeneratedTarget(context, destinationClass); List moved = new ArrayList<>(plan.lambdas()); moved.addFirst(context.methodNode()); + registerMoveRollback(context, generatedTarget, plan, moved); plan.widenedFields().forEach(field -> field.access = widenAccess(field.access)); plan.widenedMethods().forEach(method -> method.access = widenAccess(method.access)); @@ -132,6 +143,134 @@ public static void generateMove(MixinContext context, String destinationClass, R context.patchContext().postApply(() -> mixin.methods.removeAll(moved)); } + public static void generateCancellers(MixinContext context, ClassNode targetClass, List laterPieces) { + MethodNode handler = context.methodNode(); + boolean isStatic = context.isStatic(); + ClassNode generatedTarget = getGeneratedTarget(context, targetClass.name); + FieldNode tracker = new FieldNode(Opcodes.ACC_PRIVATE | (isStatic ? Opcodes.ACC_STATIC : 0), + "adapter$cancelled$" + simpleName(context.classNode()) + "$" + handler.name, Type.BOOLEAN_TYPE.getDescriptor(), null, null); + List cancellers = new ArrayList<>(); + synchronized (generatedTarget) { + String base = tracker.name; + for (int i = 1; generatedTarget.fields.stream().anyMatch(f -> f.name.equals(tracker.name)); i++) { + tracker.name = base + "$" + i; + } + generatedTarget.fields.add(tracker); + for (int i = 0; i < laterPieces.size(); i++) { + MethodNode canceller = generateCanceller(generatedTarget, tracker, handler, laterPieces.get(i), isStatic, i == laterPieces.size() - 1); + canceller.name = uniqueMethodName(generatedTarget, canceller.name); + generatedTarget.methods.add(canceller); + cancellers.add(canceller); + } + } + context.patchContext().onRollback(() -> { + synchronized (generatedTarget) { + generatedTarget.fields.remove(tracker); + generatedTarget.methods.removeAll(cancellers); + } + }); + + MethodQualifier cancel = new MethodQualifier(TypeConstants.CI_TYPE.getDescriptor(), "cancel", "()V"); + for (MethodInsnNode call : MethodAnalyzer.findCalls(handler, cancel)) { + InsnList set = new InsnList(); + if (!isStatic) { + set.add(new VarInsnNode(Opcodes.ALOAD, 0)); + } + set.add(new InsnNode(Opcodes.ICONST_1)); + set.add(new FieldInsnNode(isStatic ? Opcodes.PUTSTATIC : Opcodes.PUTFIELD, targetClass.name, tracker.name, tracker.desc)); + handler.instructions.insertBefore(call, set); + } + } + + private static MethodNode generateCanceller(ClassNode generatedTarget, FieldNode tracker, MethodNode handler, MethodNode piece, boolean isStatic, boolean last) { + String desc = Type.getMethodDescriptor(Type.VOID_TYPE, TypeConstants.CI_TYPE); + MethodNode canceller = new MethodNode(Opcodes.ACC_PRIVATE | (isStatic ? Opcodes.ACC_STATIC : 0), handler.name + "$adapter$canceller$" + piece.name, desc, null, null); + AnnotationVisitor inject = canceller.visitAnnotation(MixinAnnotations.INJECT, true); + AnnotationVisitor method = inject.visitArray("method"); + method.visit(null, piece.name + piece.desc); + method.visitEnd(); + AnnotationVisitor at = inject.visitArray("at"); + AnnotationVisitor head = at.visitAnnotation(null, MixinAnnotations.AT); + head.visit("value", "HEAD"); + head.visitEnd(); + at.visitEnd(); + inject.visit("cancellable", Boolean.TRUE); + inject.visitEnd(); + + Type owner = Type.getObjectType(generatedTarget.name); + GeneratorAdapter gen = new GeneratorAdapter(canceller, canceller.access, canceller.name, canceller.desc); + Label end = new Label(); + gen.newLabel(); + if (isStatic) { + gen.getStatic(owner, tracker.name, Type.BOOLEAN_TYPE); + } else { + gen.loadThis(); + gen.getField(owner, tracker.name, Type.BOOLEAN_TYPE); + } + gen.visitJumpInsn(Opcodes.IFEQ, end); + if (last) { + gen.newLabel(); + if (isStatic) { + gen.push(false); + gen.putStatic(owner, tracker.name, Type.BOOLEAN_TYPE); + } else { + gen.loadThis(); + gen.push(false); + gen.putField(owner, tracker.name, Type.BOOLEAN_TYPE); + } + } + gen.newLabel(); + gen.loadArg(0); + gen.invokeVirtual(TypeConstants.CI_TYPE, new Method("cancel", "()V")); + gen.visitLabel(end); + gen.returnValue(); + gen.newLabel(); + gen.endMethod(); + canceller.maxStack = 2; + canceller.maxLocals = isStatic ? 1 : 2; + return canceller; + } + + private static String uniqueMethodName(ClassNode generatedTarget, String name) { + String unique = name; + for (int i = 1; hasMethodNamed(generatedTarget, unique); i++) { + unique = name + "$" + i; + } + return unique; + } + + private static boolean hasMethodNamed(ClassNode cls, String name) { + return cls.methods.stream().anyMatch(m -> m.name.equals(name)); + } + + private static String simpleName(ClassNode cls) { + return cls.name.substring(cls.name.lastIndexOf('/') + 1); + } + + private static void registerMoveRollback(MixinContext context, ClassNode generatedTarget, RelocationPlan.Move plan, List moved) { + ClassNode mixin = context.classNode(); + int[] fieldAccess = plan.widenedFields().stream().mapToInt(field -> field.access).toArray(); + int[] methodAccess = plan.widenedMethods().stream().mapToInt(method -> method.access).toArray(); + List lambdaCopies = plan.lambdas().stream().map(AdapterUtil::copyMethod).toList(); + context.patchContext().onRollback(() -> { + synchronized (generatedTarget) { + generatedTarget.methods.removeAll(moved); + } + for (int i = 0; i < fieldAccess.length; i++) { + plan.widenedFields().get(i).access = fieldAccess[i]; + } + for (int i = 0; i < methodAccess.length; i++) { + plan.widenedMethods().get(i).access = methodAccess[i]; + } + for (int i = 0; i < lambdaCopies.size(); i++) { + int index = mixin.methods.indexOf(plan.lambdas().get(i)); + if (index != -1) { + mixin.methods.set(index, lambdaCopies.get(i)); + } + } + }); + } + private static ClassNode getGeneratedTarget(MixinContext context, String destinationClass) { PatchEnvironment environment = context.environment(); String parent = environment.dirtyClassLookup().getClass(destinationClass).map(cls -> cls.superName).orElse(null); @@ -158,7 +297,7 @@ private static int widenAccess(int access) { return access; } - public static void invokeHandler(GeneratorAdapter gen, MethodNode handler, ClassNode handlerOwner) { + private static void invokeHandler(GeneratorAdapter gen, MethodNode handler, ClassNode handlerOwner) { boolean isInterface = (handlerOwner.access & Opcodes.ACC_INTERFACE) != 0; int opcode = MethodAnalyzer.isStatic(handler) ? Opcodes.INVOKESTATIC : isInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL; gen.visitMethodInsn(opcode, handlerOwner.name, handler.name, handler.desc, isInterface); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java index e8312260..4bf64d9e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java @@ -89,7 +89,7 @@ private PatchResult processMixin(ClassNode classNode, ClassTarget classTarget, P this.environment.auditTrail().prepareMethod(mixinContext); - int checkpoint = patchContext.checkpoint(); + PatchContextImpl.Checkpoint checkpoint = patchContext.checkpoint(); PatchResult result; try { result = applyPatches(mixinContext, configuration); @@ -109,7 +109,7 @@ private PatchResult processMixin(ClassNode classNode, ClassTarget classTarget, P return result; } - private static void rollback(ClassNode classNode, PatchContextImpl patchContext, int checkpoint, MixinContext mixinContext) { + private static void rollback(ClassNode classNode, PatchContextImpl patchContext, PatchContextImpl.Checkpoint checkpoint, MixinContext mixinContext) { int index = classNode.methods.indexOf(mixinContext.methodNode()); if (index != -1) { classNode.methods.set(index, mixinContext.unmodifiedMethodNode()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java index 8fae02ad..fd44f2b8 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/Recipe.java @@ -1,6 +1,7 @@ package org.sinytra.adapter.pipeline; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.method.MethodLocator; @@ -58,10 +59,20 @@ private Recipe(Configuration clean, Configuration dirty, Resolvers resolvers, Pr this.cleanLocalsTable = Cached.of(() -> getLocalsTable(getCleanTarget())); } - this.dirtyTarget = Cached.keyed(dirty::getTargetMethod, target -> findTarget(context.dirtyMethods(), target)); + this.dirtyTarget = Cached.keyed(this::getDirtyTargetQualifier, target -> findTarget(context.dirtyMethods(), target)); this.dirtyLocalsTable = Cached.keyed(this::getDirtyTarget, target -> getLocalsTable(target)); } + @Nullable + private MethodQualifier getDirtyTargetQualifier() { + MethodQualifier target = this.dirty.getTargetMethod(); + String targetClass = this.dirty.getTargetClass(); + if (target != null && target.owner() == null && targetClass != null && !targetClass.equals(this.clean.getTargetClass())) { + return target.withOwner(Type.getObjectType(targetClass)); + } + return target; + } + @Nullable private static TargetPair findTarget(MethodLocator locator, @Nullable MethodQualifier target) { return target != null ? locator.resolve(target) : null; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java index feba3453..37840bbf 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java @@ -19,4 +19,6 @@ static PatchContext create(ClassNode classNode, List targetTypes, PatchEnv PatchEnvironment environment(); void postApply(Runnable consumer); + + void onRollback(Runnable undo); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java index 2e3a13db..ea9c54be 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java @@ -12,6 +12,10 @@ public class PatchContextImpl implements PatchContext { private final List targetTypes; private final PatchEnvironment environment; private final List postApply = new ArrayList<>(); + private final List undo = new ArrayList<>(); + + public record Checkpoint(int postApply, int undo) { + } public PatchContextImpl(ClassNode classNode, List targetTypes, PatchEnvironment environment) { this.classNode = classNode; @@ -44,12 +48,20 @@ public void postApply(Runnable consumer) { this.postApply.add(consumer); } - public int checkpoint() { - return this.postApply.size(); + @Override + public void onRollback(Runnable undo) { + this.undo.add(undo); + } + + public Checkpoint checkpoint() { + return new Checkpoint(this.postApply.size(), this.undo.size()); } - public void rollback(int checkpoint) { - this.postApply.subList(checkpoint, this.postApply.size()).clear(); + public void rollback(Checkpoint checkpoint) { + this.postApply.subList(checkpoint.postApply(), this.postApply.size()).clear(); + List undone = this.undo.subList(checkpoint.undo(), this.undo.size()); + undone.reversed().forEach(Runnable::run); + undone.clear(); } public void run() { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java index f51a8f51..0ae9e18c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java @@ -1,6 +1,6 @@ package org.sinytra.adapter.pipeline.processor; -import org.sinytra.adapter.pipeline.processor.extract.ExtractMixinProcessor; +import org.sinytra.adapter.pipeline.processor.relocate.RelocationProcessor; import org.sinytra.adapter.pipeline.processor.signature.SignatureProcessor; import org.sinytra.adapter.util.OrderedRegistry; @@ -29,6 +29,6 @@ private void registerDefaultProcessors() { // Late extract processor - must come last // Changed class target - add(new ExtractMixinProcessor()); + add(new RelocationProcessor()); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java index 1e676bb4..76135fa5 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java @@ -1,5 +1,9 @@ package org.sinytra.adapter.pipeline.processor; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.edit.RelocationGenerator; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.key.IntentKeys; @@ -7,6 +11,9 @@ import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import java.util.List; +import java.util.stream.IntStream; + public class SplitCancellationProcessor implements Processor { @Override public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { @@ -14,12 +21,34 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe return TxResult.PASS; } - TargetPair dirtyTarget = recipe.getDirtyTarget(); - if (dirtyTarget == null) { + TargetPair cleanTarget = recipe.getCleanTarget(); + TargetPair split = recipe.getNewCleanTarget(); + TargetPair piece = recipe.getDirtyTarget(); + if (cleanTarget == null || split == null || piece == null + || !MethodAnalyzer.isDirtyDeprecatedMethod(cleanTarget.methodNode(), split.methodNode()) + || Type.getReturnType(split.methodNode().desc) != Type.VOID_TYPE + ) { + return TxResult.PASS; + } + + List pieces = context.dataFlow(split).findTopLevelOwnCalls(); + MethodNode target = piece.methodNode(); + int index = IntStream.range(0, pieces.size()) + .filter(i -> pieces.get(i).name.equals(target.name) && pieces.get(i).desc.equals(target.desc)) + .findFirst() + .orElse(-1); + List laterPieces = index != -1 ? pieces.subList(index + 1, pieces.size()) : List.of(); + if (laterPieces.isEmpty()) { + return TxResult.PASS; + } + + if (laterPieces.stream().anyMatch(m -> MethodAnalyzer.isStatic(m) != context.isStatic())) { + context.recordCtxAudit("Can't propagate cancellation: the pieces of %s differ in static-ness", split.methodNode().name); return TxResult.PASS; } - SplitMethodCancellationHelper.handle(context, recipe, dirtyTarget.methodNode()); + RelocationGenerator.generateCancellers(context, split.classNode(), laterPieces); + context.recordCtxAudit("Propagate cancellation to %s", laterPieces.stream().map(m -> m.name).toList()); return TxResult.SUCCESS; } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitMethodCancellationHelper.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitMethodCancellationHelper.java deleted file mode 100644 index 9b5e166b..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitMethodCancellationHelper.java +++ /dev/null @@ -1,136 +0,0 @@ -package org.sinytra.adapter.pipeline.processor; - -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.Label; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.commons.Method; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.api.MixinClassGenerator; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.model.constant.TypeConstants; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.util.AdapterUtil; - -import java.util.List; -import java.util.stream.IntStream; - -public final class SplitMethodCancellationHelper { - - public static void handle(MixinContext context, Recipe recipe, MethodNode newTarget) { - TargetPair cleanTarget = recipe.getCleanTarget(); - if (cleanTarget == null) return; - TargetPair newCleanTarget = recipe.getNewCleanTarget(); - if (newCleanTarget == null) return; - - ClassNode originalClassTarget = newCleanTarget.classNode(); - MethodNode originalMethodTarget = newCleanTarget.methodNode(); - - if (!MethodAnalyzer.isDirtyDeprecatedMethod(cleanTarget.methodNode(), originalMethodTarget) || Type.getReturnType(originalMethodTarget.desc) != Type.VOID_TYPE) { - return; - } - - List invocations = context.dataFlow(newCleanTarget).findTopLevelOwnCalls(); - if (invocations == null) { - return; - } - int index = IntStream.range(0, invocations.size()) - .filter(i -> invocations.get(i).name.equals(newTarget.name) && invocations.get(i).desc.equals(newTarget.desc)) - .findFirst() - .orElse(-1); - if (index == -1) { - return; - } - - MixinClassGenerator generator = context.patchContext().environment().classGenerator(); - ClassNode generatedTarget = generator.getOrGenerateMixinClass(context.classNode(), originalClassTarget.name, null); - - String fieldName = "adapter$canceller$" + originalMethodTarget.name + "$" + AdapterUtil.randomString(5); - int fieldAccess = Opcodes.ACC_PRIVATE | (context.isStatic() ? Opcodes.ACC_STATIC : 0); - FieldNode trackerField = (FieldNode) generatedTarget.visitField(fieldAccess, fieldName, Type.BOOLEAN_TYPE.getDescriptor(), null, null); - - for (int i = index + 1; i < invocations.size(); i++) { - generateCancellerMethod(generatedTarget, trackerField, originalClassTarget, invocations.get(i), context, i == invocations.size() - 1); - } - - context.recordCtxAudit("Generate cancellation handler mixin"); - } - - private static void generateCancellerMethod(ClassNode generatedTarget, FieldNode trackerField, ClassNode originalClassTarget, MethodNode newTarget, MixinContext context, boolean reset) { - String name = context.methodNode().name + "$adapter$canceller$" + AdapterUtil.randomString(5); - String desc = Type.getMethodDescriptor(Type.VOID_TYPE, TypeConstants.CI_TYPE); - MethodNode invokerMixinMethod = (MethodNode) generatedTarget.visitMethod(Opcodes.ACC_PRIVATE | (context.isStatic() ? Opcodes.ACC_STATIC : 0), name, desc, null, null); - { - AnnotationVisitor injectAnn = invokerMixinMethod.visitAnnotation(MixinAnnotations.INJECT, true); - { - AnnotationVisitor methodValue = injectAnn.visitArray("method"); - methodValue.visit(null, newTarget.name + newTarget.desc); - methodValue.visitEnd(); - } - { - AnnotationVisitor atValue = injectAnn.visitArray("at"); - { - AnnotationVisitor atAnn = atValue.visitAnnotation(null, MixinAnnotations.AT); - atAnn.visit("value", "HEAD"); - atAnn.visitEnd(); - } - atValue.visitEnd(); - } - injectAnn.visit("cancellable", Boolean.TRUE); - injectAnn.visitEnd(); - } - // Generate logic - boolean isStatic = context.isStatic(); - Type generatedType = Type.getObjectType(generatedTarget.name); - GeneratorAdapter gen = new GeneratorAdapter(invokerMixinMethod, invokerMixinMethod.access, invokerMixinMethod.name, invokerMixinMethod.desc); - Label endLabel = new Label(); - gen.newLabel(); - if (isStatic) { - gen.getStatic(generatedType, trackerField.name, Type.BOOLEAN_TYPE); - } else { - gen.loadThis(); - gen.getField(generatedType, trackerField.name, Type.BOOLEAN_TYPE); - } - gen.visitJumpInsn(Opcodes.IFEQ, endLabel); - { - if (reset) { - gen.newLabel(); - if (isStatic) { - gen.visitInsn(Opcodes.ICONST_0); - gen.putStatic(generatedType, trackerField.name, Type.BOOLEAN_TYPE); - } else { - gen.loadThis(); - gen.visitInsn(Opcodes.ICONST_0); - gen.putField(generatedType, trackerField.name, Type.BOOLEAN_TYPE); - } - } - gen.newLabel(); - gen.loadArg(0); - gen.invokeVirtual(TypeConstants.CI_TYPE, new Method("cancel", "()V")); - } - gen.visitLabel(endLabel); - gen.returnValue(); - gen.newLabel(); - gen.endMethod(); - // Modify mixin to set the field value - MethodQualifier qualifier = new MethodQualifier(TypeConstants.CI_TYPE.getDescriptor(), "cancel", "()V"); - InsnList methodInsns = context.methodNode().instructions; - for (MethodInsnNode minsn : MethodAnalyzer.findCalls(context.methodNode(), qualifier)) { - InsnList list = new InsnList(); - if (isStatic) { - list.add(new InsnNode(Opcodes.ICONST_1)); - list.add(new FieldInsnNode(Opcodes.PUTSTATIC, originalClassTarget.name, trackerField.name, trackerField.desc)); - } else { - list.add(new VarInsnNode(Opcodes.ALOAD, 0)); - list.add(new InsnNode(Opcodes.ICONST_1)); - list.add(new FieldInsnNode(Opcodes.PUTFIELD, originalClassTarget.name, trackerField.name, trackerField.desc)); - } - methodInsns.insertBefore(minsn, list); - } - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java index c2d182a2..55a1665e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/TargetMethodProcessor.java @@ -9,13 +9,16 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.List; +import java.util.Objects; public class TargetMethodProcessor implements Processor { @Override public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { if (dirty.getTargetMethod() == null) return TxResult.FAIL; - upgradeCapturedLocals(context.methodNode(), context, recipe); + if (Objects.equals(recipe.clean().getTargetClass(), dirty.getTargetClass())) { + upgradeCapturedLocals(context.methodNode(), context, recipe); + } return TxResult.SUCCESS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java deleted file mode 100644 index c0c2da06..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/MirrorableExtractMixin.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.sinytra.adapter.pipeline.processor.extract; - -import com.google.common.collect.ImmutableList; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.GeneratorAdapter; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.api.PatchEnvironment; -import org.sinytra.adapter.api.PatchResult; -import org.sinytra.adapter.edit.RelocationGenerator; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.model.constant.TypeConstants; -import org.sinytra.adapter.model.param.Parameters; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.OpcodeUtil; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_METHOD; - -// TODO Cleanup -public class MirrorableExtractMixin { - - public static PatchResult apply(MixinContext context, Recipe recipe, String destinationClass, MethodInsnNode destinationMethodInvocation) { - TargetPair dirtyTarget = recipe.getNewCleanTarget(); - if (dirtyTarget == null) return PatchResult.PASS; - - MethodNode methodNode = context.methodNode(); - - Type selfType = Type.getObjectType(dirtyTarget.classNode().name); - Type[] params = Type.getArgumentTypes(destinationMethodInvocation.desc); - int selfIndex = Stream.of(Stream.iterate(0, i -> i < params.length, i -> i + 1) - .filter(i -> params[i].equals(selfType)) - .toList()) - .filter(list -> list.size() == 1) - .map(List::getFirst) - .findFirst() - .orElse(-1); - if (selfIndex == -1) { - return PatchResult.PASS; - } - - List callInsns = context.dataFlow(dirtyTarget).resolveCallOperandProducers(destinationMethodInvocation); - if (callInsns == null || callInsns.size() <= selfIndex) { - return PatchResult.PASS; - } - AbstractInsnNode selfParamInsn = callInsns.get(selfIndex); - if (!(selfParamInsn instanceof VarInsnNode varInsn) || varInsn.getOpcode() != Opcodes.ALOAD || varInsn.var != 0) { - return PatchResult.PASS; - } - List originalParams = Parameters.getParameterTypes(methodNode.desc); - List newParams = ImmutableList.builder().add(Type.getArgumentTypes(destinationMethodInvocation.desc)).add(TypeConstants.CI_TYPE).build(); - // Make sure we have all required params, matching parameters of the same type in order - int[] argMapping = new int[originalParams.size()]; - boolean[] mappedArgs = new boolean[newParams.size()]; - for (int i = 0; i < originalParams.size(); i++) { - int argIndex = -1; - for (int j = 0; j < newParams.size(); j++) { - if (!mappedArgs[j] && newParams.get(j).equals(originalParams.get(i))) { - argIndex = j; - break; - } - } - if (argIndex == -1) { - return PatchResult.PASS; - } - mappedArgs[argIndex] = true; - argMapping[i] = argIndex; - } - - // Change target. The annotation is carried over to the mirror. - context.methodAnnotation() - .setOrAppendNonNull(AT_METHOD, List.of( - MethodQualifier.create(destinationMethodInvocation).asDescriptor() - )); - - String desc = Type.getMethodDescriptor(Type.VOID_TYPE, newParams.toArray(Type[]::new)); - generateMirror(context, destinationClass, desc, dirtyTarget.classNode(), gen -> gen.loadArg(selfIndex), argMapping); - - context.recordCtxAudit("Extract (mirror) mixin to target %s", destinationClass); - return PatchResult.APPLY; - } - - /** - * Generate a mixin method in the generated mixin class for {@code destinationClass}. It carries the original injector annotation - * and calls the original handler, which is turned into a public {@code @Unique} method. - * - * @param handlerOwner the class the original handler is merged into - * @param loadInstance loads the handler's instance, if the handler isn't static - * @param argIndices the mirror parameter to pass as each handler parameter - * @return the generated mirror method - */ - public static MethodNode generateMirror(MixinContext context, String destinationClass, String desc, ClassNode handlerOwner, Consumer loadInstance, int[] argIndices) { - ClassNode classNode = context.classNode(); - MethodNode methodNode = context.methodNode(); - PatchEnvironment environment = context.environment(); - ClassNode generatedTarget = environment.classGenerator().getOrGenerateMixinClass(classNode, destinationClass, null); - environment.refmapHolder().copyEntries(classNode.name, generatedTarget.name); - - // Generate a method with the same injector annotation - String name = methodNode.name + "$adapter$mirror$" + AdapterUtil.randomString(5); - MethodNode mirror = (MethodNode) generatedTarget.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, name, desc, null, null); - mirror.visibleAnnotations = new ArrayList<>(methodNode.visibleAnnotations); - - // Make original mixin a unique public method - methodNode.access = OpcodeUtil.setAccessVisibility(methodNode.access, Opcodes.ACC_PUBLIC); - methodNode.visibleAnnotations.remove(context.methodAnnotation().unwrap()); - methodNode.visitAnnotation(MixinAnnotations.UNIQUE, true); - - // Now call the original mixin - GeneratorAdapter gen = new GeneratorAdapter(mirror, mirror.access, mirror.name, mirror.desc); - gen.newLabel(); - if (!MethodAnalyzer.isStatic(methodNode)) { - loadInstance.accept(gen); - } - for (int argIndex : argIndices) { - gen.loadArg(argIndex); - } - RelocationGenerator.invokeHandler(gen, methodNode, handlerOwner); - gen.newLabel(); - gen.returnValue(); - gen.newLabel(); - gen.endMethod(); - return mirror; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationProcessor.java similarity index 68% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationProcessor.java index 9d21173a..f7fac5bf 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/ExtractMixinProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationProcessor.java @@ -1,18 +1,14 @@ -package org.sinytra.adapter.pipeline.processor.extract; +package org.sinytra.adapter.pipeline.processor.relocate; -import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.edit.RelocationGenerator; -import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processor; -public class ExtractMixinProcessor implements Processor { +public class RelocationProcessor implements Processor { @Override public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { String destinationClass = dirty.getTargetClass(); @@ -28,12 +24,6 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe context.recordCtxAudit("Mirror mixin to target %s", destinationClass); } case RelocationSolver.Result.Unbound(String reason) -> { - // TODO Port onto the solver - if (dirty.getProperty(IntentKeys.RELOCATION).orElse(null) instanceof Relocation.CalledFrom(MethodInsnNode call) - && MirrorableExtractMixin.apply(context, recipe, destinationClass, call) != PatchResult.PASS - ) { - return TxResult.SUCCESS; - } context.recordCtxAudit("Can't relocate mixin to target %s: %s", destinationClass, reason); return TxResult.FAIL; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/RelocationSolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java similarity index 94% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/RelocationSolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java index f015608e..7e082d51 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/extract/RelocationSolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java @@ -1,10 +1,9 @@ -package org.sinytra.adapter.pipeline.processor.extract; +package org.sinytra.adapter.pipeline.processor.relocate; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.TargetPair; @@ -47,10 +46,7 @@ public static Result solve(MixinContext context, Recipe recipe, Configuration di if (relocation == null) { return new Result.Unbound("the handler uses its own class, and nothing at the new target holds its instance"); } - MethodQualifier targetMethod = dirty.getTargetMethod(); - TargetPair newTarget = targetMethod != null - ? context.dirtyMethods().resolveDeclared(targetMethod.withOwner(Type.getObjectType(destination))) - : null; + TargetPair newTarget = recipe.getDirtyTarget(); if (newTarget == null) { return new Result.Unbound("the new target method doesn't exist"); } diff --git a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java index 849a1b94..1685c593 100644 --- a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java +++ b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java @@ -40,18 +40,6 @@ public static MethodNode copyMethod(MethodNode original) { return copy; } - public static String randomString(int length) { - int leftLimit = 48; // numeral '0' - int rightLimit = 122; // letter 'z' - Random random = new Random(); - - return random.ints(leftLimit, rightLimit + 1) - .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)) - .limit(length) - .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) - .toString(); - } - public static boolean isAnonymousClass(String name) { // Regex: second to last char in class name must be '$', and the class name must end with a number return name.matches("^.+\\$\\d+$"); diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java index 9f716142..21f73ae5 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java @@ -1,22 +1,35 @@ package org.sinytra.adapter.patch.test.mc_1_21_1.mixin; +import com.google.common.collect.ImmutableMultimap; import com.mojang.logging.LogUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; +import org.assertj.core.api.Assertions; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.api.ClassLookup; import org.sinytra.adapter.api.DynamicPatches; +import org.sinytra.adapter.api.MethodTransformer; +import org.sinytra.adapter.api.MixinClassGenerator; import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.api.Patcher; import org.sinytra.adapter.api.RefmapHolder; +import org.sinytra.adapter.api.TxPhase; +import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.patch.test_main.mixin.MinecraftMixinPatchTest; import org.sinytra.adapter.types.FieldTypeUsageTransformer; import org.slf4j.Logger; import org.spongepowered.asm.mixin.FabricUtil; import java.util.List; +import java.util.stream.StreamSupport; public class DynamicMixinPatchTest extends MinecraftMixinPatchTest { private static final Logger LOGGER = LogUtils.getLogger(); @@ -46,7 +59,15 @@ public void copyEntries(String from, String to) { ); patcher = Patcher.builder(patchEnvironment) .classTransformer(new FieldTypeUsageTransformer()) - .methodTransformers(DynamicPatches.methodTransformers(List.of())) + .methodTransformers(ImmutableMultimap.builder() + .putAll(DynamicPatches.methodTransformers(List.of())) + .put(TxPhase.VALIDATED, (context, config) -> { + if (context.methodNode().name.startsWith("failAfter")) { + throw new IllegalStateException("Simulated failure"); + } + return PatchResult.PASS; + }) + .build()) .build(); } @@ -298,6 +319,49 @@ void testSplitMethodInjectionTarget() throws Exception { ); } + @Test + void testSplitMethodCancellation() throws Exception { + assertPatched("org/sinytra/adapter/test/mixin/GuiMixin", "cancelAfterArmor", (patched, expected, env) -> { + Assertions.assertThat(getTargetMethods(patched)).containsExactly("renderArmorLevel(Lnet/minecraft/client/gui/GuiGraphics;)V"); + + ClassNode generated = env.classGenerator().getGeneratedMixinClasses().get("org/sinytra/adapter/test/mixin/adapter_generated_net_minecraft_client_gui_Gui").node(); + String tracker = "adapter$cancelled$GuiMixin$cancelAfterArmor"; + Assertions.assertThat(generated.fields).anyMatch(f -> f.name.equals(tracker) && f.desc.equals("Z")); + + AbstractInsnNode cancel = StreamSupport.stream(patched.instructions.spliterator(), false) + .filter(insn -> insn instanceof MethodInsnNode call && call.name.equals("cancel")) + .findFirst() + .orElseThrow(); + Assertions.assertThat(cancel.getPrevious()).matches(insn -> insn instanceof FieldInsnNode set + && set.getOpcode() == Opcodes.PUTFIELD && set.owner.equals("net/minecraft/client/gui/Gui") && set.name.equals(tracker)); + + List cancelled = generated.methods.stream() + .filter(m -> m.name.startsWith("cancelAfterArmor$adapter$canceller$")) + .flatMap(m -> getTargetMethods(m).stream()) + .toList(); + Assertions.assertThat(cancelled).containsExactly( + "renderFoodLevel(Lnet/minecraft/client/gui/GuiGraphics;)V", + "renderAirLevel(Lnet/minecraft/client/gui/GuiGraphics;)V" + ); + }); + } + + @Test + void testRollbackSplitMethodCancellation() throws Exception { + assertPatched("org/sinytra/adapter/test/mixin/GuiMixin", "failAfterCancelling", (patched, expected, env) -> { + Assertions.assertThat(getTargetMethods(patched)).containsExactly("renderPlayerHealth"); + MixinClassGenerator.GeneratedClass generated = env.classGenerator().getGeneratedMixinClasses().get("org/sinytra/adapter/test/mixin/adapter_generated_net_minecraft_client_gui_Gui"); + if (generated != null) { + Assertions.assertThat(generated.node().fields).noneMatch(f -> f.name.contains("failAfterCancelling")); + Assertions.assertThat(generated.node().methods).noneMatch(m -> m.name.startsWith("failAfterCancelling")); + } + }); + } + + private static List getTargetMethods(MethodNode method) { + return new AnnotationHandle(method.visibleAnnotations.getFirst()).>getValue("method").orElseThrow().get(); + } + @Test void testMethodInjectionTargetParamsChanged() throws Exception { assertSameCode( @@ -508,6 +572,34 @@ void testExtractCapturingLocals() throws Exception { ); } + @Test + void testMirrorStaticHandler() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/pipeline/BlockMixin", + "onSpawnAfterBreak", + assertUnique(), + assertHasGeneratedMethod("org/sinytra/adapter/test/mixin/pipeline/adapter_generated_net_neoforged_neoforge_common_CommonHooks") + ); + } + + @Test + void testRollbackMirror() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/LivingEntityMixin", + "failAfterMirror", + assertNotRelocated("org/sinytra/adapter/test/mixin/adapter_generated_net_neoforged_neoforge_common_CommonHooks") + ); + } + + @Test + void testRollbackMove() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin", + "failAfterMove", + assertNotRelocated("org/sinytra/adapter/test/mixin/pipeline/adapter_generated_net_neoforged_neoforge_common_extensions_IBlockExtension") + ); + } + // The call's owner was narrowed to a subclass @Test void testNarrowedCallOwner() throws Exception { diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java index 61c95cc1..26196a46 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java @@ -164,4 +164,22 @@ private int moveAirUp(int y) { private int moveAirUpExpected(int y) { return y; } + + @Inject( + method = "renderPlayerHealth", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;renderArmor(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/entity/player/Player;IIII)V"), + cancellable = true + ) + private void cancelAfterArmor(GuiGraphics guiGraphics, CallbackInfo ci) { + ci.cancel(); + } + + @Inject( + method = "renderPlayerHealth", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;renderArmor(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/entity/player/Player;IIII)V"), + cancellable = true + ) + private void failAfterCancelling(GuiGraphics guiGraphics, CallbackInfo ci) { + ci.cancel(); + } } diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/LivingEntityMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/LivingEntityMixin.java index 2490f84c..f84e1048 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/LivingEntityMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/LivingEntityMixin.java @@ -73,6 +73,16 @@ public void testFrostWalkerExpected(CallbackInfo ci) { ourUniqueMethod(); } + @Inject(method = "baseTick", at = @At(value = "FIELD", target = "Lnet/minecraft/world/level/Level;isClientSide:Z", ordinal = 2)) + public void failAfterMirror(CallbackInfo ci) { + ourUniqueMethod(); + } + + @Inject(method = "baseTick", at = @At(value = "FIELD", target = "Lnet/minecraft/world/level/Level;isClientSide:Z", ordinal = 2)) + public void failAfterMirrorExpected(CallbackInfo ci) { + ourUniqueMethod(); + } + @Unique private void ourUniqueMethod() { // Noop diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java index d7bbd1c7..e681180b 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java @@ -35,6 +35,22 @@ private static int replaceDyeColorExpected(DyeColor instance, @Local(ordinal = 1 return instance.getTextureDiffuseColor(); } + @Redirect( + method = "tick(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BeaconBlockEntity;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I") + ) + private static int failAfterMove(DyeColor instance) { + return instance.getTextureDiffuseColor(); + } + + @Redirect( + method = "tick(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BeaconBlockEntity;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I") + ) + private static int failAfterMoveExpected(DyeColor instance) { + return instance.getTextureDiffuseColor(); + } + @Inject( method = "tick(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BeaconBlockEntity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I"), diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BlockMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BlockMixin.java new file mode 100644 index 00000000..a2e1a8d5 --- /dev/null +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BlockMixin.java @@ -0,0 +1,28 @@ +package org.sinytra.adapter.test.mixin.pipeline; + +import net.minecraft.world.level.block.Block; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Block.class) +public abstract class BlockMixin { + @Inject( + method = "dropResources(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;spawnAfterBreak(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/item/ItemStack;Z)V") + ) + private static void onSpawnAfterBreak(CallbackInfo ci) { + countDrop(); + } + + @Unique + public static void onSpawnAfterBreakExpected(CallbackInfo ci) { + countDrop(); + } + + @Unique + private static void countDrop() { + } +} diff --git a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java index 71630a77..fb162420 100644 --- a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java +++ b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java @@ -120,6 +120,15 @@ protected final void assertSameCodeExtracted( Stream.of(assertions).forEach(c -> c.accept(patched, expected, result.env())); } + protected final void assertPatched(String className, String testName, AssertCallback... assertions) throws Exception { + LoadResult result = load(className, List.of(testName)); + MethodNode patched = result.patched.methods.stream() + .filter(m -> m.name.equals(testName)) + .findFirst() + .orElseThrow(); + Stream.of(assertions).forEach(c -> c.accept(patched, null, result.env())); + } + protected final void assertSameField( String className, String testName @@ -218,6 +227,24 @@ protected AssertCallback assertHasGeneratedMethod(String targetClass) { }; } + /// The generated class, if there is one, has neither the patched handler and its lambdas nor an injector that calls the handler + protected AssertCallback assertNotRelocated(String targetClass) { + return (patched, expected, env) -> { + MixinClassGenerator.GeneratedClass generatedClass = env.classGenerator().getGeneratedMixinClasses().get(targetClass); + if (generatedClass == null) { + return; + } + List leftovers = generatedClass.node().methods.stream() + .filter(m -> m.name.equals(patched.name) || m.name.startsWith("lambda$" + patched.name + "$") + || StreamSupport.stream(m.instructions.spliterator(), false) + .anyMatch(insn -> insn instanceof MethodInsnNode call && call.name.equals(patched.name))) + .map(m -> m.name) + .toList(); + Assertions.assertThat(leftovers).as("Generated methods left by " + patched.name).isEmpty(); + }; + } + + /// The audit log of the patched method has an entry containing the text protected AssertCallback assertAudit(String text) { return (patched, expected, env) -> { boolean found = env.auditTrail().getAuditTrail().values().stream() From 90d300c76bdde10894e45d5354f0686c66db478d Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 17:13:34 +0200 Subject: [PATCH 26/44] Sugar processor --- .../analysis/injection/InjectionLocator.java | 2 +- .../analysis/locals/LocalsAnalyzer.java | 5 +- .../analysis/method/MethodLocator.java | 4 +- .../analysis/params/ParamsEditDistance.java | 6 +- .../adapter/analysis/params/TypeRelation.java | 14 ++ .../adapter/api/ClassPatchContext.java | 21 ++ .../sinytra/adapter/api/ClassTransformer.java | 4 +- .../adapter/api/MethodPatchContext.java | 17 ++ .../adapter/api/MethodTransformer.java | 5 +- .../edit/param/InjectParameterTransform.java | 15 -- .../edit/param/ParamTransformationUtil.java | 26 +++ .../param/ReplaceParametersTransformer.java | 19 +- .../adapter/model/MethodQualifier.java | 2 +- .../org/sinytra/adapter/model/Relocation.java | 4 +- .../org/sinytra/adapter/model/ann/AtData.java | 3 +- .../adapter/model/config/key/MixinKeys.java | 2 +- .../constant/MixinAnnotationConstants.java | 1 + .../adapter/model/param/RedirectedInsn.java | 56 +++++ .../adapter/pipeline/AppliedLocalsCheck.java | 62 +++++ .../adapter/pipeline/HandlerValidator.java | 13 +- .../adapter/pipeline/MultiTargetSplitter.java | 128 ++++++++++ .../sinytra/adapter/pipeline/PatcherImpl.java | 29 ++- .../pipeline/PipelineMethodTransformer.java | 12 +- .../cls/DynamicAnonClassIndexPatch.java | 7 +- .../DynamicAnonymousShadowFieldTypePatch.java | 7 +- .../adapter/pipeline/ctx/MixinContext.java | 26 ++- .../adapter/pipeline/ctx/PatchContext.java | 21 +- .../pipeline/ctx/PatchContextImpl.java | 15 +- .../pipeline/injector/HandlerSignatures.java | 26 ++- .../injector/ModifyVariableMixin.java | 12 +- .../pipeline/injector/RedirectMixin.java | 33 ++- .../pipeline/locate/CalledMethodLocator.java | 38 +-- .../adapter/pipeline/locate/Describer.java | 44 +++- .../adapter/pipeline/locate/LocalLocator.java | 56 +++++ .../adapter/pipeline/locate/PassedLocals.java | 104 +++++++++ .../pipeline/manual/MethodPatchProcessor.java | 2 +- .../LocalCaptureUpgradeTransformer.java | 6 + .../pipeline/processor/Processors.java | 2 + .../processor/SugarLocalProcessor.java | 219 ++++++++++++++++++ .../processor/relocate/RelocationSolver.java | 31 ++- .../signature/ParameterCastStep.java | 2 +- .../processor/signature/ParametersStep.java | 2 +- .../signature/RedirectedCallStep.java | 2 +- .../injection/InjectionPointResolver.java | 22 +- .../resolver/special/LocalResolver.java | 27 +-- .../types/FieldAccessorTypeTransformer.java | 7 +- .../types/FieldTypeUsageTransformer.java | 5 +- .../mixin/DynamicMixinPatchTest.java | 95 +++++++- .../test/mixin/FarmLandBlockMixin.java | 16 ++ .../sinytra/adapter/test/mixin/GuiMixin.java | 9 + .../adapter/test/mixin/LivingEntityMixin.java | 24 ++ .../pipeline/BeaconBlockEntityMixin.java | 27 ++- .../test/mixin/pipeline/FaceBakeryMixin.java | 28 +++ .../pipeline/MultiPartBakedModelMixin.java | 31 +++ .../mixin/DynamicMixinPatchTest.java | 21 +- .../mc_26_1_2/mixin/MultiPartModelMixin.java | 44 ++++ .../mixin/MinecraftMixinPatchTest.java | 17 ++ 57 files changed, 1289 insertions(+), 189 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/api/ClassPatchContext.java create mode 100644 core/src/main/java/org/sinytra/adapter/api/MethodPatchContext.java create mode 100644 core/src/main/java/org/sinytra/adapter/model/param/RedirectedInsn.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/LocalLocator.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/PassedLocals.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java create mode 100644 test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/FaceBakeryMixin.java create mode 100644 test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/MultiPartBakedModelMixin.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/MultiPartModelMixin.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java index 7c54ddd8..c20099c2 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java @@ -121,7 +121,7 @@ private MixinBridge.Session session(TargetPair target) { @Nullable private AnnotationNode getSlice() { return this.context.methodAnnotation().getValue(PROPERTY_SLICE) - .map(handle -> handle.get() instanceof List list ? (AnnotationNode) list.getFirst() : (AnnotationNode) handle.get()) + .map(handle -> handle.get() instanceof List list ? (list.isEmpty() ? null : (AnnotationNode) list.getFirst()) : (AnnotationNode) handle.get()) .orElse(null); } diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index 951b65d6..0a470421 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -12,7 +12,6 @@ import org.sinytra.adapter.analysis.compare.InstructionMatcher; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.params.ParamsEditDistance; -import org.sinytra.adapter.analysis.params.TypeRelation; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.analysis.params.ParamDiffResolver; import org.sinytra.adapter.edit.param.TransformParameters; @@ -113,7 +112,7 @@ public CapturedLocalsInfo resolveCapturedLocalsInfo(TargetPair dirtyTarget) { List availableTypes = available.stream().map(LocalVariable::type).toList(); // Compare expected and available params - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(capturedLocals.expected(), availableTypes).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(capturedLocals.expected(), availableTypes, this.context.typeRelation()).snapshot(); return new CapturedLocalsInfo(capturedLocals, diff, availableTypes); } @@ -244,7 +243,7 @@ public List findDirtyCapturedParams(Configuration clean, Configuration dir MethodNode cleanTarget = cleanPair.methodNode(); MethodNode dirtyTarget = dirtyPair.methodNode(); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffParameters(cleanTarget, dirtyTarget, TypeRelation.STATIC).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffParameters(cleanTarget, dirtyTarget, this.context.typeRelation()).snapshot(); List cleanTargetParams = Parameters.getParameterTypes(cleanTarget.desc); if (cleanCaptured.size() > cleanTargetParams.size()) return dirtyCaptured; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java index 348a90a7..34ed104f 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java @@ -91,7 +91,7 @@ private TargetPair resolveSingle(MethodQualifier qualifier, boolean useDefaultOw @Nullable private Pair> findCandidates(@Nullable MethodQualifier qualifier, boolean useDefaultOwner, boolean ignoreDesc) { - if (qualifier == null || qualifier.name() == null) { + if (qualifier == null || qualifier.name() == null && qualifier.desc() == null) { return null; } @@ -114,7 +114,7 @@ private Pair> findCandidates(@Nullable MethodQualifi // Find target method in class String desc = qualifier.desc(); List candidates = targetClass.methods.stream() - .filter(mtd -> mtd.name.equals(qualifier.name()) && (ignoreDesc || desc == null || mtd.desc.equals(desc))) + .filter(mtd -> ignoreDesc ? qualifier.ignoreDesc().matches(null, mtd.name, null) : qualifier.withOwner(null).matches(null, mtd.name, mtd.desc)) .toList(); // If there's multiple candidates, try removing bouncer methods diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java index f3b79825..2fd9ee2b 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java @@ -50,7 +50,11 @@ public static ParamsDiff diff(List clean, List dirty, TypeRelation } public static ParamsDiff diffTypes(List clean, List dirty) { - return diff(Param.of(clean), Param.of(dirty), TypeRelation.STATIC); + return diffTypes(clean, dirty, TypeRelation.STATIC); + } + + public static ParamsDiff diffTypes(List clean, List dirty, TypeRelation relation) { + return diff(Param.of(clean), Param.of(dirty), relation); } public static ParamsDiff diffParameters(MethodNode clean, MethodNode dirty, TypeRelation relation) { diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/TypeRelation.java b/core/src/main/java/org/sinytra/adapter/analysis/params/TypeRelation.java index 86bec97a..b9d24d5a 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/TypeRelation.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/TypeRelation.java @@ -1,6 +1,7 @@ package org.sinytra.adapter.analysis.params; import org.objectweb.asm.Type; +import org.sinytra.adapter.analysis.method.InheritanceHandler; public interface TypeRelation { enum Kind { @@ -28,6 +29,19 @@ enum Kind { return Kind.UNRELATED; }; + static TypeRelation inheritance(InheritanceHandler inheritance) { + return (clean, dirty) -> { + Kind kind = STATIC.relate(clean, dirty); + if (kind == Kind.UNRELATED && clean.getSort() == Type.OBJECT && dirty.getSort() == Type.OBJECT + && (inheritance.isClassInherited(clean.getInternalName(), dirty.getInternalName()) + || inheritance.isClassInherited(dirty.getInternalName(), clean.getInternalName())) + ) { + return Kind.CONVERTIBLE; + } + return kind; + }; + } + private static boolean isReference(Type type) { return type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY; } diff --git a/core/src/main/java/org/sinytra/adapter/api/ClassPatchContext.java b/core/src/main/java/org/sinytra/adapter/api/ClassPatchContext.java new file mode 100644 index 00000000..e376b940 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/api/ClassPatchContext.java @@ -0,0 +1,21 @@ +package org.sinytra.adapter.api; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.sinytra.adapter.pipeline.ctx.PatchContextImpl; + +import java.util.List; + +public interface ClassPatchContext { + static ClassPatchContext of(ClassNode classNode, List targetTypes, PatchEnvironment environment) { + return new PatchContextImpl(classNode, null, targetTypes, environment); + } + + ClassNode classNode(); + + List targetTypes(); + + PatchEnvironment environment(); + + String remap(String reference); +} diff --git a/core/src/main/java/org/sinytra/adapter/api/ClassTransformer.java b/core/src/main/java/org/sinytra/adapter/api/ClassTransformer.java index 2deb8e99..7d32f3d4 100644 --- a/core/src/main/java/org/sinytra/adapter/api/ClassTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/api/ClassTransformer.java @@ -1,9 +1,7 @@ package org.sinytra.adapter.api; import org.objectweb.asm.tree.ClassNode; -import org.sinytra.adapter.model.ann.ClassTarget; -import org.sinytra.adapter.pipeline.ctx.PatchContext; public interface ClassTransformer { - PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchContext context); + PatchResult apply(ClassNode classNode, ClassPatchContext context); } diff --git a/core/src/main/java/org/sinytra/adapter/api/MethodPatchContext.java b/core/src/main/java/org/sinytra/adapter/api/MethodPatchContext.java new file mode 100644 index 00000000..642561d2 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/api/MethodPatchContext.java @@ -0,0 +1,17 @@ +package org.sinytra.adapter.api; + +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +public interface MethodPatchContext { + ClassNode classNode(); + + MethodNode methodNode(); + + AnnotationNode injectorAnnotation(); + + PatchEnvironment environment(); + + void recordAudit(Object actor, String message, Object... args); +} diff --git a/core/src/main/java/org/sinytra/adapter/api/MethodTransformer.java b/core/src/main/java/org/sinytra/adapter/api/MethodTransformer.java index 5daf1af5..bfe57087 100644 --- a/core/src/main/java/org/sinytra/adapter/api/MethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/api/MethodTransformer.java @@ -1,8 +1,5 @@ package org.sinytra.adapter.api; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.pipeline.ctx.MixinContext; - public interface MethodTransformer { - PatchResult apply(MixinContext context, Configuration config); + PatchResult apply(MethodPatchContext context); } diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java b/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java index acc24e60..ddc3a32a 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/InjectParameterTransform.java @@ -10,14 +10,12 @@ import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.injector.MixinFlag; import org.sinytra.adapter.util.AdapterUtil; import java.util.Comparator; import java.util.List; import static org.sinytra.adapter.edit.param.ParamTransformationUtil.getLVTIndex; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_INDEX; // TODO Cleanup param transformers public record InjectParameterTransform(int index, Type type) implements ParameterTransformer { @@ -35,19 +33,6 @@ public PatchResult apply(ClassNode classNode, MethodNode methodNode, MixinContex } AnnotationHandle annotation = context.methodAnnotation(); - if (context.hasFlag(MixinFlag.TARGETS_VARIABLE)) { - if (index < 1) { - return PatchResult.PASS; - } - int insertedLvtIndex = getLVTIndex(parameters.subList(1, parameters.size()), isNonStatic, index - 1); - annotation.getValue(PROPERTY_INDEX).ifPresent(indexHandle -> { - int indexValue = indexHandle.get(); - if (indexValue >= insertedLvtIndex) { - indexHandle.set(indexValue + this.type.getSize()); - } - }); - return PatchResult.APPLY; - } if (annotation.matchesDesc(MixinAnnotations.MODIFY_ARGS)) { return ModifyArgsOffsetUpgrader.upgradeAfterParamInsert(methodNode, index) ? PatchResult.APPLY : PatchResult.PASS; } diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java b/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java index ca95da69..e2b2c6a6 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ParamTransformationUtil.java @@ -5,10 +5,12 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.param.Annotation; import org.sinytra.adapter.model.param.Parameter; import org.sinytra.adapter.util.AdapterUtil; @@ -67,6 +69,30 @@ public static void reorderParameters(MethodNode methodNode, List parameter } } + public static void retypeParameter(MethodNode method, int index, Type type, boolean castLoads) { + Type[] args = Type.getArgumentTypes(method.desc); + Type old = args[index]; + int slot = getLVTIndex(Arrays.asList(args), !MethodAnalyzer.isStatic(method), index); + args[index] = type; + method.desc = Type.getMethodDescriptor(Type.getReturnType(method.desc), args); + if (method.localVariables != null) { + method.localVariables.stream() + .filter(local -> local.index == slot) + .findFirst() + .ifPresent(local -> { + local.desc = type.getDescriptor(); + local.signature = null; + }); + } + if (castLoads) { + for (AbstractInsnNode insn : method.instructions) { + if (insn instanceof VarInsnNode load && load.getOpcode() == Opcodes.ALOAD && load.var == slot) { + method.instructions.insert(load, new TypeInsnNode(Opcodes.CHECKCAST, old.getInternalName())); + } + } + } + } + public static int getLVTIndex(List parameters, boolean nonStatic, int index) { int lvt = nonStatic ? 1 : 0; for (int i = 0; i < index; i++) { diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java index 24f9ef10..3054b13a 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/edit/param/ReplaceParametersTransformer.java @@ -1,6 +1,7 @@ package org.sinytra.adapter.edit.param; import com.mojang.logging.LogUtils; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; @@ -10,7 +11,6 @@ import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.api.TypeAdapter; import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.slf4j.Logger; @@ -35,12 +35,13 @@ public ReplaceParametersTransformer offset(int offset) { public PatchResult apply(ClassNode classNode, MethodNode methodNode, MixinContext context, List parameters, int offset) { final int paramIndex = this.index + offset; - if (Type.getArgumentCount(methodNode.desc) <= paramIndex) { + if (paramIndex >= parameters.size()) { return PatchResult.PASS; } + boolean nonStatic = !context.isStatic(); LocalVariableLookup lvtLookup = LocalVariableLookup.of(methodNode); - LocalVariableNode localVar = lvtLookup == null ? null : lvtLookup.getByParameterOrdinalOrNull(paramIndex); + LocalVariableNode localVar = lvtLookup == null ? null : findParameterLocal(lvtLookup, ParamTransformationUtil.getLVTIndex(parameters, nonStatic, paramIndex)); if (localVar == null) { LOGGER.info(MIXINPATCH, "Cannot replace parameter {}, missing local variable info in {}.{}", paramIndex, classNode.name, methodNode.name); return PatchResult.PASS; @@ -68,10 +69,9 @@ public PatchResult apply(ClassNode classNode, MethodNode methodNode, MixinContex TypeAdapter typeFix = context.getTypeAdapter(type, originalType); // If this is a wrap operation, make an educated guess and try adapting the instance type if (typeFix == null && context.methodAnnotation().matchesDesc(MixinAnnotations.WRAP_OPERATION)) { - List params = Parameters.getParameterTypes(methodNode.desc); - LocalVariableNode firstParam = lvtLookup.getByParameterOrdinalOrNull(0); - if (!params.isEmpty() && firstParam != null) { - typeFix = context.getTypeAdapter(params.getFirst(), originalType); + LocalVariableNode firstParam = parameters.isEmpty() ? null : findParameterLocal(lvtLookup, nonStatic ? 1 : 0); + if (firstParam != null) { + typeFix = context.getTypeAdapter(parameters.getFirst(), originalType); if (typeFix != null) { varInsn.var = firstParam.index; } @@ -96,4 +96,9 @@ public PatchResult apply(ClassNode classNode, MethodNode methodNode, MixinContex return PatchResult.COMPUTE_FRAMES; } + + @Nullable + private static LocalVariableNode findParameterLocal(LocalVariableLookup lookup, int slot) { + return lookup.getLocals().stream().filter(local -> local.index == slot).findFirst().orElse(null); + } } diff --git a/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java b/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java index e6c09aaa..c0433df1 100644 --- a/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java +++ b/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java @@ -51,7 +51,7 @@ public boolean matches(MethodInsnNode insn) { public boolean matches(@Nullable String owner, @Nullable String name, @Nullable String desc) { return (this.owner == null || this.owner.equals(owner)) - && this.name != null && this.name.equals(name) + && (this.name != null ? this.name.equals(name) : this.desc != null && name != null && !name.startsWith("<")) && (this.desc == null || this.desc.equals(desc)); } diff --git a/core/src/main/java/org/sinytra/adapter/model/Relocation.java b/core/src/main/java/org/sinytra/adapter/model/Relocation.java index 875c5f49..deccdbb8 100644 --- a/core/src/main/java/org/sinytra/adapter/model/Relocation.java +++ b/core/src/main/java/org/sinytra/adapter/model/Relocation.java @@ -1,9 +1,7 @@ package org.sinytra.adapter.model; -import org.objectweb.asm.tree.MethodInsnNode; - public sealed interface Relocation { - record CalledFrom(MethodInsnNode call) implements Relocation { + record CalledFrom() implements Relocation { } record ReceivesOrigin(int argIndex) implements Relocation { diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java index 0faf8d28..d793bcd1 100644 --- a/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java @@ -17,7 +17,7 @@ public class AtData extends AnnotationData { public static final PropertyContainerTemplate TEMPLATE = PropertyContainerTemplate.builder() .require(Keys.VALUE) - .keys(Keys.TARGET, Keys.ORDINAL, Keys.SHIFT, Keys.BY, Keys.ARGS) + .keys(Keys.TARGET, Keys.ORDINAL, Keys.OPCODE, Keys.SHIFT, Keys.BY, Keys.ARGS) .addConstraint(AtData::validateInjectionPointType) .build(); @@ -135,6 +135,7 @@ public static class Keys { .serializable() .build(); public static final PropertyKey ORDINAL = PropertyKey.create("ordinal", Integer.class); + public static final PropertyKey OPCODE = PropertyKey.create("opcode", Integer.class); public static final PropertyKey SHIFT = PropertyKey.create("shift", At.Shift.class); public static final PropertyKey BY = PropertyKey.create("by", Integer.class); public static final PropertyKey> ARGS = (PropertyKey) PropertyKey.create("args", List.class); diff --git a/core/src/main/java/org/sinytra/adapter/model/config/key/MixinKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/MixinKeys.java index 8e4bc32c..3e698b96 100644 --- a/core/src/main/java/org/sinytra/adapter/model/config/key/MixinKeys.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/key/MixinKeys.java @@ -21,7 +21,7 @@ public class MixinKeys { // Get method targets String methodRef = ControlKeys.parseSingle(value, String.class); if (methodRef == null) { - // We only support single method targets for now + // Several targets are split into one handler each by MultiTargetSplitter return null; } // Resolve method reference diff --git a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java index 3c8d765d..d4eeb989 100644 --- a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java @@ -8,6 +8,7 @@ public class MixinAnnotationConstants { public static final String AT_VAL_INVOKE = "INVOKE"; public static final String AT_VAL_INVOKE_ASSIGN = "INVOKE_ASSIGN"; public static final String AT_VAL_RETURN = "RETURN"; + public static final String AT_VAL_NEW = "NEW"; public static final String AT_VAL_STORE = "STORE"; public static final String AT_VAL_LOAD = "LOAD"; public static final String AT_VAL_CONST = "CONSTANT"; diff --git a/core/src/main/java/org/sinytra/adapter/model/param/RedirectedInsn.java b/core/src/main/java/org/sinytra/adapter/model/param/RedirectedInsn.java new file mode 100644 index 00000000..8c7ce683 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/model/param/RedirectedInsn.java @@ -0,0 +1,56 @@ +package org.sinytra.adapter.model.param; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.TypeInsnNode; + +import java.util.List; + +public record RedirectedInsn(List operands, Type result) { + @Nullable + public static RedirectedInsn of(AbstractInsnNode insn) { + return switch (insn) { + case MethodInsnNode call -> new RedirectedInsn( + Parameters.getCallOperandTypes(call.getOpcode() == Opcodes.INVOKESTATIC ? null : Type.getObjectType(call.owner), call.desc), + Type.getReturnType(call.desc) + ); + case FieldInsnNode field -> { + Type owner = Type.getObjectType(field.owner); + Type type = Type.getType(field.desc); + yield switch (field.getOpcode()) { + case Opcodes.GETFIELD -> new RedirectedInsn(List.of(owner), type); + case Opcodes.GETSTATIC -> new RedirectedInsn(List.of(), type); + case Opcodes.PUTFIELD -> new RedirectedInsn(List.of(owner, type), Type.VOID_TYPE); + case Opcodes.PUTSTATIC -> new RedirectedInsn(List.of(type), Type.VOID_TYPE); + default -> null; + }; + } + case TypeInsnNode creation when creation.getOpcode() == Opcodes.NEW -> { + MethodInsnNode constructor = findConstructorCall(creation); + yield constructor != null ? new RedirectedInsn(Parameters.getParameterTypes(constructor.desc), Type.getObjectType(creation.desc)) : null; + } + default -> null; + }; + } + + @Nullable + public static MethodInsnNode findConstructorCall(TypeInsnNode creation) { + int depth = 0; + for (AbstractInsnNode insn = creation.getNext(); insn != null; insn = insn.getNext()) { + if (insn instanceof TypeInsnNode nested && nested.getOpcode() == Opcodes.NEW && nested.desc.equals(creation.desc)) { + depth++; + } else if (insn instanceof MethodInsnNode call && call.getOpcode() == Opcodes.INVOKESPECIAL && call.name.equals("") + && call.owner.equals(creation.desc)) { + if (depth == 0) { + return call; + } + depth--; + } + } + return null; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java b/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java new file mode 100644 index 00000000..34f13a0b --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java @@ -0,0 +1,62 @@ +package org.sinytra.adapter.pipeline; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.mixin.MixinBridge; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.pipeline.locate.LocalLocator; +import org.sinytra.adapter.pipeline.processor.SugarLocalProcessor; +import org.sinytra.adapter.util.AdapterUtil; + +public final class AppliedLocalsCheck { + public static void check(Object actor, MixinContext context, TargetPair cleanTarget, TargetPair dirtyTarget) { + AbstractInsnNode cleanInsn = AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)); + AbstractInsnNode dirtyInsn = AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget)); + if (cleanInsn == null || dirtyInsn == null) { + return; + } + + MethodNode handler = context.methodNode(); + if (context.hasFlag(MixinFlag.TARGETS_VARIABLE)) { + Type type = Type.getReturnType(handler.desc); + int cleanSlot = context.locals().resolveSelectedLocal(cleanTarget, cleanInsn, type); + int slot = context.locals().resolveSelectedLocal(dirtyTarget, dirtyInsn, type); + report(actor, context, "The variable selector", cleanTarget, cleanSlot, cleanInsn, dirtyTarget, slot); + } + + Type[] params = Type.getArgumentTypes(handler.desc); + MixinBridge.Session cleanSession = null; + MixinBridge.Session dirtySession = null; + for (int i = 0; i < params.length; i++) { + AnnotationNode local = SugarLocalProcessor.getLocalAnnotation(handler, i); + if (local == null) { + continue; + } + if (cleanSession == null) { + cleanSession = MixinBridge.session(context.classNode().name, handler, cleanTarget.classNode(), cleanTarget.methodNode(), context.environment().refmapHolder()); + dirtySession = MixinBridge.session(context.classNode().name, handler, dirtyTarget.classNode(), dirtyTarget.methodNode(), context.environment().refmapHolder()); + } + int cleanSlot = cleanSession.findLocal(local, params[i], cleanInsn); + int slot = dirtySession.findLocal(local, params[i], dirtyInsn); + report(actor, context, "@Local parameter " + i, cleanTarget, cleanSlot, cleanInsn, dirtyTarget, slot); + } + } + + private static void report(Object actor, MixinContext context, String selector, TargetPair cleanTarget, int cleanSlot, AbstractInsnNode cleanInsn, + TargetPair dirtyTarget, int slot) { + if (cleanSlot == -1 || slot == -1) { + return; + } + int expected = LocalLocator.resolve(context, cleanTarget, dirtyTarget, cleanSlot, cleanInsn, dirtyTarget); + if (expected != -1 && expected != slot) { + context.recordAudit(actor, "Warning: %s selects local %s, but local %s may now hold what clean local %s held", selector, slot, expected, cleanSlot); + } + } + + private AppliedLocalsCheck() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java b/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java index 99d7a853..b8c1656c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java @@ -1,10 +1,8 @@ package org.sinytra.adapter.pipeline; import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AtData; @@ -17,6 +15,7 @@ import org.sinytra.adapter.model.param.MethodParameters; import org.sinytra.adapter.model.param.Parameter; import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.model.param.RedirectedInsn; import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.Arrays; @@ -27,13 +26,12 @@ public final class HandlerValidator { @Nullable public static String validate(MixinContext context, Configuration dirty, Recipe recipe) { if (dirty.getProperty(ControlKeys.DELETE).orElse(false) - || !Objects.equals(recipe.clean().getTargetClass(), dirty.getTargetClass()) || !Objects.equals(recipe.clean().getProperty(ControlKeys.MIXIN_TYPE), dirty.getProperty(ControlKeys.MIXIN_TYPE)) || dirty.getTargetMethod() == null) { return null; } - TargetPair target = context.dirtyMethods().resolve(dirty.getTargetMethod()); + TargetPair target = recipe.getDirtyTarget(); if (target == null) { return "dirty target method %s not found".formatted(dirty.getTargetMethod().asDescriptor()); } @@ -70,13 +68,13 @@ private static String validateSignature(MixinContext context, HandlerSignature s return "handler returns %s instead of the expected %s".formatted(handlerReturn, dirty.getReturnType()); } - MethodInsnNode call = insn instanceof MethodInsnNode minsn ? minsn : null; + RedirectedInsn redirected = RedirectedInsn.of(insn); List targetArgs = Parameters.getParameterTypes(target.methodNode().desc); Type targetReturn = Type.getReturnType(target.methodNode().desc); Type expectedReturn = switch (signature.returns()) { case VOID -> Type.VOID_TYPE; - case CALL_RETURN -> call != null ? Type.getReturnType(call.desc) : null; + case CALL_RETURN -> redirected != null ? redirected.result() : null; case TARGET_RETURN -> targetReturn; case CALL_ARG, VARIABLE -> null; }; @@ -88,8 +86,7 @@ private static String validateSignature(MixinContext context, HandlerSignature s if (!params.has(slot.group())) continue; List group = params.get(slot.group()); String problem = switch (slot.source()) { - case CALL_OPERANDS -> call == null ? null : checkTypes("call operand", group, Parameters.getCallOperandTypes( - call.getOpcode() == Opcodes.INVOKESTATIC ? null : Type.getObjectType(call.owner), call.desc)); + case CALL_OPERANDS -> redirected == null ? null : checkTypes("call operand", group, redirected.operands()); case OPERATION -> checkTypes("operation", group, List.of(TypeConstants.OPERATION_TYPE)); case CALLBACK_INFO -> group.isEmpty() ? null : checkTypes("callback info", group, List.of(targetReturn.equals(Type.VOID_TYPE) ? TypeConstants.CI_TYPE : TypeConstants.CIR_TYPE)); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java b/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java new file mode 100644 index 00000000..890ba19d --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java @@ -0,0 +1,128 @@ +package org.sinytra.adapter.pipeline; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.mixin.MixinBridge; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.AnnotationHandle; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.model.config.key.ControlKeys; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.util.AdapterUtil; + +import java.util.ArrayList; +import java.util.List; + +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_METHOD; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_AT; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_SLICE; + +public final class MultiTargetSplitter { + private static final MultiTargetSplitter INSTANCE = new MultiTargetSplitter(); + + public static List split(ClassNode classNode, ClassTarget classTarget, MixinParser.MixinMethodHandle mixin, PatchEnvironment environment) { + if (mixin.properties().hasProperty(MixinKeys.TARGET_METHOD)) { + return List.of(mixin); + } + String targetClass = mixin.properties().getProperty(ControlKeys.TARGET_CLASS).orElse(null); + ClassNode clean = targetClass != null ? environment.cleanClassLookup().getClass(targetClass).orElse(null) : null; + List targets = clean != null ? findTargets(classNode, mixin.methodAnnotation(), clean, environment) : List.of(); + if (targets.size() < 2 || targets.stream().noneMatch(target -> needsPatching(classNode, mixin, targetClass, target, environment))) { + return List.of(mixin); + } + + MethodNode handler = mixin.methodNode(); + List methods = new ArrayList<>(); + methods.add(handler); + int position = classNode.methods.indexOf(handler); + for (int i = 1; i < targets.size(); i++) { + MethodNode copy = AdapterUtil.copyMethod(handler); + String name = handler.name + "$adapter$target$" + i; + for (int suffix = 1; AdapterUtil.findMethod(classNode, name, copy.desc) != null; suffix++) { + name = handler.name + "$adapter$target$" + i + "$" + suffix; + } + copy.name = name; + AnnotationHandle annotation = findAnnotation(copy, mixin.methodAnnotation().getDesc()); + annotation.setOrAppendNonNull(AT_METHOD, List.of(asSelector(targets.get(i)))); + annotation.setOrAppendNonNull("require", 0); + classNode.methods.add(position + i, copy); + methods.add(copy); + } + mixin.methodAnnotation().setOrAppendNonNull(AT_METHOD, List.of(asSelector(targets.getFirst()))); + environment.auditTrail().recordAudit(INSTANCE, classNode, "Split %s into one handler per target: %s", handler.name, + targets.stream().map(MultiTargetSplitter::asSelector).toList()); + + List handles = new ArrayList<>(); + for (MethodNode method : methods) { + MixinParser.MixinMethodHandle handle = MixinParser.parseMixin(classTarget, method, ref -> environment.refmapHolder().remap(classNode.name, ref), environment); + if (handle != null) { + handles.add(handle); + } + } + return handles; + } + + private static List findTargets(ClassNode mixinClass, AnnotationHandle annotation, ClassNode clean, PatchEnvironment environment) { + Object value = annotation.getValue(AT_METHOD).map(v -> v.get()).orElse(null); + List selectors = value instanceof List list ? list : value != null ? List.of(value) : List.of(); + List targets = new ArrayList<>(); + for (Object selector : selectors) { + if (!(selector instanceof String string)) { + return List.of(); + } + MixinBridge.MemberSelector member = MixinBridge.parseMemberSelector(environment.refmapHolder().remap(mixinClass.name, string)); + if (member == null || member.field() || member.name() == null && member.desc() == null) { + return List.of(); + } + MethodQualifier qualifier = new MethodQualifier(null, member.name(), member.desc()); + int matches = 0; + for (MethodNode method : clean.methods) { + if (matches < member.maxMatches() && qualifier.matches(null, method.name, method.desc)) { + matches++; + if (!targets.contains(method)) { + targets.add(method); + } + } + } + } + return targets; + } + + private static boolean needsPatching(ClassNode mixinClass, MixinParser.MixinMethodHandle mixin, String targetClass, MethodNode target, PatchEnvironment environment) { + ClassNode dirty = environment.dirtyClassLookup().getClass(targetClass).orElse(null); + MethodNode dirtyTarget = dirty != null ? AdapterUtil.findMethod(dirty, target.name, target.desc) : null; + if (dirtyTarget == null) { + return true; + } + AnnotationHandle annotation = mixin.methodAnnotation(); + AnnotationNode at = annotation.getNested(PROPERTY_AT).map(AnnotationHandle::unwrap).orElse(null); + if (at == null) { + return false; + } + AnnotationNode slice = annotation.getValue(PROPERTY_SLICE) + .map(handle -> handle.get() instanceof List list ? (list.isEmpty() ? null : (AnnotationNode) list.getFirst()) : (AnnotationNode) handle.get()) + .orElse(null); + return MixinBridge.session(mixinClass.name, mixin.methodNode(), dirty, dirtyTarget, environment.refmapHolder()) + .findInjectionPoint(annotation.unwrap(), at, slice) + .isEmpty(); + } + + @Nullable + private static AnnotationHandle findAnnotation(MethodNode method, String desc) { + return method.visibleAnnotations.stream() + .filter(annotation -> annotation.desc.equals(desc)) + .findFirst() + .map(AnnotationHandle::new) + .orElse(null); + } + + private static String asSelector(MethodNode method) { + return method.name + method.desc; + } + + private MultiTargetSplitter() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java index 4bf64d9e..5af439ad 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java @@ -20,6 +20,7 @@ import org.sinytra.adapter.pipeline.injector.MixinType; import org.slf4j.Logger; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -46,11 +47,11 @@ public PatchResult process(ClassNode classNode) { if (classTarget == null || classTarget.getTypes().isEmpty()) return PatchResult.PASS; PatchResult result = PatchResult.PASS; - PatchContextImpl context = new PatchContextImpl(classNode, classTarget.getTypes(), this.environment); + PatchContextImpl context = new PatchContextImpl(classNode, classTarget, classTarget.getTypes(), this.environment); // Class-level transformations for (ClassTransformer patch : this.classPatches) { - result = result.or(patch.apply(classNode, classTarget, context)); + result = result.or(patch.apply(classNode, context)); } // Parse mixin methods @@ -58,14 +59,18 @@ public PatchResult process(ClassNode classNode) { MixinParser.MixinClassHandle mixinClass = MixinParser.parseMixins(classTarget, classNode, this.environment); if (mixinClass == null) return result; - // Mixin-level transformations + // Mixin-level transformations with one handler per target method + List mixins = new ArrayList<>(); for (MixinParser.MixinMethodHandle mixin : mixinClass.mixins()) { + mixins.addAll(MultiTargetSplitter.split(classNode, classTarget, mixin, this.environment)); + } + for (MixinParser.MixinMethodHandle mixin : mixins) { PatchResult subResult = processMixin(classNode, classTarget, context, mixin); result = result.or(subResult); } context.run(); - MixinGroups groups = MixinGroups.create(mixinClass.mixins().stream().map(MixinMethodHandle::methodNode).toList()); + MixinGroups groups = MixinGroups.create(mixins.stream().map(MixinMethodHandle::methodNode).toList()); this.environment.auditTrail().processGroups(classNode, groups); return result; @@ -79,14 +84,14 @@ private PatchResult processMixin(ClassNode classNode, ClassTarget classTarget, P AnnotationHandle atHandle = mixin.methodAnnotation().getNested(MixinAnnotationConstants.PROPERTY_AT).orElse(null); MixinType mixinType = mixin.mixinType(); Set flags = mixinType.getFlags(); - MixinContext mixinContext = new MixinContext(mixinType, patchContext, classTarget, classNode, mixin.methodNode(), mixin.methodAnnotation(), atHandle, flags); - String mixinId = mixinContext.getMixinId(); - // Build base config PropertyContainerTemplate template = mixin.mixinType().getConfigurationTemplate(); MutableConfiguration configuration = MutableConfiguration.create(template); configuration.mergeFrom(mixin.properties()); + MixinContext mixinContext = new MixinContext(mixinType, patchContext, classTarget, classNode, mixin.methodNode(), mixin.methodAnnotation(), atHandle, flags, configuration); + String mixinId = mixinContext.getMixinId(); + this.environment.auditTrail().prepareMethod(mixinContext); PatchContextImpl.Checkpoint checkpoint = patchContext.checkpoint(); @@ -122,7 +127,7 @@ private PatchResult applyPatches(MixinContext mixinContext, MutableConfiguration String mixinId = mixinContext.getMixinId(); // << RUN EARLY PHASE - PatchResult result = runPhase(TxPhase.EARLY, mixinContext, configuration); + PatchResult result = runPhase(TxPhase.EARLY, mixinContext); // Complete clean config TxResult preResult = mixinType.preProcess(mixinContext, configuration, mixinContext.getResolvers(), mixinContext.getProcessors()); @@ -132,7 +137,7 @@ private PatchResult applyPatches(MixinContext mixinContext, MutableConfiguration } // << RUN LOADED PHASE - result = result.or(runPhase(TxPhase.LOADED, mixinContext, configuration)); + result = result.or(runPhase(TxPhase.LOADED, mixinContext)); // Validate clean config if (!configuration.validate()) { @@ -145,13 +150,13 @@ private PatchResult applyPatches(MixinContext mixinContext, MutableConfiguration MethodNode methodNode = mixinContext.methodNode(); methodNode.maxLocals = methodNode.maxStack = 999; - return result.or(runPhase(TxPhase.VALIDATED, mixinContext, configuration)); + return result.or(runPhase(TxPhase.VALIDATED, mixinContext)); } - private PatchResult runPhase(TxPhase phase, MixinContext mixinContext, MutableConfiguration configuration) { + private PatchResult runPhase(TxPhase phase, MixinContext mixinContext) { PatchResult result = PatchResult.PASS; for (MethodTransformer transformer : this.methodTransformers.get(phase)) { - result = result.or(mixinContext.audited(transformer, () -> transformer.apply(mixinContext, configuration))); + result = result.or(mixinContext.audited(transformer, () -> transformer.apply(mixinContext))); } return result; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java index f0f8e31d..16af66fd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java @@ -7,6 +7,7 @@ import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.api.AuditTrail; import org.sinytra.adapter.api.MethodPatch; +import org.sinytra.adapter.api.MethodPatchContext; import org.sinytra.adapter.api.MethodTransformer; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.model.TargetPair; @@ -44,13 +45,22 @@ public PipelineMethodTransformer(List methodPatches, boolean patche } @Override + public PatchResult apply(MethodPatchContext patchContext) { + MixinContext context = (MixinContext) patchContext; + return apply(context, context.configuration()); + } + public PatchResult apply(MixinContext context, Configuration config) { TargetPair cleanTarget = context.cleanMethods().resolve(config.getTargetMethod()); if (cleanTarget == null) return PatchResult.PASS; TargetPair dirtyTarget = context.dirtyMethods().resolve(config.getTargetMethod()); - if (!this.patchResolver.matches(config) && !failsDirtyInjectionCheck(context, config, dirtyTarget) && hasValidSlice(context, config, dirtyTarget)) + if (!this.patchResolver.matches(config) && !failsDirtyInjectionCheck(context, config, dirtyTarget) && hasValidSlice(context, config, dirtyTarget)) { + if (dirtyTarget != null && !this.patchesOnly) { + AppliedLocalsCheck.check(this, context, cleanTarget, dirtyTarget); + } return PatchResult.PASS; + } LOGGER.debug(MIXINPATCH, "Considering method {}", context.getMixinId()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java b/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java index 611723e3..438bb84e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java @@ -4,6 +4,7 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InnerClassNode; import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.api.ClassPatchContext; import org.sinytra.adapter.api.ClassTransformer; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.model.MethodQualifier; @@ -19,7 +20,11 @@ */ public class DynamicAnonClassIndexPatch implements ClassTransformer { @Override - public PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchContext context) { + public PatchResult apply(ClassNode classNode, ClassPatchContext patchContext) { + if (!(patchContext instanceof PatchContext context) || context.classTarget() == null) { + return PatchResult.PASS; + } + ClassTarget classTarget = context.classTarget(); Type singleTarget = classTarget.getSingle(); if (singleTarget == null) return PatchResult.PASS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java b/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java index 8e9c913d..6732111b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java @@ -4,11 +4,10 @@ import com.google.common.collect.Multimap; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.api.ClassPatchContext; import org.sinytra.adapter.api.ClassTransformer; import org.sinytra.adapter.api.PatchResult; -import org.sinytra.adapter.model.ann.ClassTarget; import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.pipeline.ctx.PatchContext; import org.sinytra.adapter.util.AdapterUtil; import java.util.Collection; @@ -17,8 +16,8 @@ public class DynamicAnonymousShadowFieldTypePatch implements ClassTransformer { @Override - public PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchContext context) { - Type singleTarget = classTarget.getSingle(); + public PatchResult apply(ClassNode classNode, ClassPatchContext context) { + Type singleTarget = context.targetTypes().size() == 1 ? context.targetTypes().getFirst() : null; if (singleTarget == null) return PatchResult.PASS; String target = singleTarget.getInternalName(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java index e24374c8..a67ea1ac 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java @@ -2,6 +2,7 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.flow.DataFlow; @@ -9,8 +10,10 @@ import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.analysis.method.MethodLocator; +import org.sinytra.adapter.analysis.params.TypeRelation; import org.sinytra.adapter.api.BytecodeFixerUpper; import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.MethodPatchContext; import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.TypeAdapter; import org.sinytra.adapter.model.MethodQualifier; @@ -19,6 +22,7 @@ import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.ClassTarget; import org.sinytra.adapter.model.ann.RefMapper; +import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.pipeline.audit.Auditor; import org.sinytra.adapter.pipeline.injector.MixinFlag; import org.sinytra.adapter.pipeline.injector.MixinType; @@ -33,7 +37,7 @@ import java.util.Set; import java.util.function.Supplier; -public class MixinContext implements RefMapper, Auditor { +public class MixinContext implements MethodPatchContext, RefMapper, Auditor { private final MixinType mixinType; private final PatchContext patchContext; private final ClassTarget classTarget; @@ -41,6 +45,7 @@ public class MixinContext implements RefMapper, Auditor { private final MethodNode methodNode; private final MethodNode originalMethodNode; private final Set flags; + private final MutableConfiguration configuration; @Nullable private String rejection; @@ -58,7 +63,7 @@ public class MixinContext implements RefMapper, Auditor { private final Resolvers resolvers = new Resolvers(); private final Processors processors = new Processors(); - public MixinContext(MixinType mixinType, PatchContext patchContext, ClassTarget classTarget, ClassNode classNode, MethodNode methodNode, AnnotationHandle methodAnnotation, AnnotationHandle injectionPointAnnotation, Set flags) { + public MixinContext(MixinType mixinType, PatchContext patchContext, ClassTarget classTarget, ClassNode classNode, MethodNode methodNode, AnnotationHandle methodAnnotation, AnnotationHandle injectionPointAnnotation, Set flags, MutableConfiguration configuration) { this.mixinType = mixinType; this.patchContext = patchContext; this.classTarget = classTarget; @@ -67,6 +72,7 @@ public MixinContext(MixinType mixinType, PatchContext patchContext, ClassTarget this.methodAnnotation = methodAnnotation; this.injectionPointAnnotation = injectionPointAnnotation; this.flags = flags; + this.configuration = configuration; this.injections = new InjectionLocator(this); this.locals = new LocalsAnalyzer(this); @@ -105,14 +111,20 @@ public ClassTarget classTarget() { return this.classTarget; } + @Override public ClassNode classNode() { return this.classNode; } + @Override public MethodNode methodNode() { return this.methodNode; } + public MutableConfiguration configuration() { + return this.configuration; + } + public MethodNode unmodifiedMethodNode() { return this.originalMethodNode; } @@ -121,6 +133,10 @@ public MethodLocator cleanMethods() { return this.cleanMethods; } + public TypeRelation typeRelation() { + return TypeRelation.inheritance(this.patchContext.environment().inheritanceHandler(dirtyLookup())); + } + public MethodLocator dirtyMethods() { return this.dirtyMethods; } @@ -156,6 +172,11 @@ public AnnotationHandle methodAnnotation() { return this.methodAnnotation; } + @Override + public AnnotationNode injectorAnnotation() { + return this.methodAnnotation.unwrap(); + } + @Nullable public AnnotationHandle injectionPointAnnotation() { return this.injectionPointAnnotation; @@ -184,6 +205,7 @@ public List targetTypes() { return this.classTarget.getTypes(); } + @Override public PatchEnvironment environment() { return patchContext().environment(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java index 37840bbf..f848cec8 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContext.java @@ -1,22 +1,13 @@ package org.sinytra.adapter.pipeline.ctx; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.ClassNode; -import org.sinytra.adapter.api.PatchEnvironment; +import org.jetbrains.annotations.Nullable; +import org.sinytra.adapter.api.ClassPatchContext; +import org.sinytra.adapter.model.ann.ClassTarget; import org.sinytra.adapter.model.ann.RefMapper; -import java.util.List; - -public interface PatchContext extends RefMapper { - static PatchContext create(ClassNode classNode, List targetTypes, PatchEnvironment environment) { - return new PatchContextImpl(classNode, targetTypes, environment); - } - - ClassNode classNode(); - - List targetTypes(); - - PatchEnvironment environment(); +public interface PatchContext extends ClassPatchContext, RefMapper { + @Nullable + ClassTarget classTarget(); void postApply(Runnable consumer); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java index ea9c54be..0c97ca26 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/PatchContextImpl.java @@ -1,14 +1,18 @@ package org.sinytra.adapter.pipeline.ctx; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.model.ann.ClassTarget; import java.util.ArrayList; import java.util.List; public class PatchContextImpl implements PatchContext { private final ClassNode classNode; + @Nullable + private final ClassTarget classTarget; private final List targetTypes; private final PatchEnvironment environment; private final List postApply = new ArrayList<>(); @@ -17,8 +21,9 @@ public class PatchContextImpl implements PatchContext { public record Checkpoint(int postApply, int undo) { } - public PatchContextImpl(ClassNode classNode, List targetTypes, PatchEnvironment environment) { + public PatchContextImpl(ClassNode classNode, @Nullable ClassTarget classTarget, List targetTypes, PatchEnvironment environment) { this.classNode = classNode; + this.classTarget = classTarget; this.targetTypes = targetTypes; this.environment = environment; } @@ -28,9 +33,15 @@ public ClassNode classNode() { return this.classNode; } + @Override + @Nullable + public ClassTarget classTarget() { + return this.classTarget; + } + @Override public List targetTypes() { - return this.targetTypes; + return this.classTarget != null ? this.classTarget.getTypes() : this.targetTypes; } @Override diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java index 702580f6..a027541a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java @@ -12,10 +12,12 @@ import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.constant.MixinAnnotationConstants; import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.model.param.HandlerSignature; import org.sinytra.adapter.model.param.MethodParameters; import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.model.param.RedirectedInsn; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -29,8 +31,9 @@ public static TxResult apply(MixinContext context, HandlerSignature signature, C Type valueType = switch (signature.returns()) { case VOID -> Type.VOID_TYPE; case CALL_RETURN -> { + RedirectedInsn redirected = isInvoke(dirty) ? null : findRedirected(context, recipe, dirty); MethodQualifier call = getCallQualifier(dirty); - yield call != null ? Type.getReturnType(call.desc()) : null; + yield redirected != null ? redirected.result() : call != null ? Type.getReturnType(call.desc()) : null; } case TARGET_RETURN -> dirty.getTargetMethod() != null && dirty.getTargetMethod().desc() != null ? Type.getReturnType(dirty.getTargetMethod().desc()) : null; @@ -49,7 +52,8 @@ public static TxResult apply(MixinContext context, HandlerSignature signature, C params.putTypes(slot.group(), Parameters.getParameterTypes(dirty.getTargetMethod().desc())); } case CALL_OPERANDS -> { - List operands = findCallOperands(context, dirty); + RedirectedInsn redirected = isInvoke(dirty) ? null : findRedirected(context, recipe, dirty); + List operands = redirected != null ? redirected.operands() : findCallOperands(context, recipe, dirty); if (operands == null) return TxResult.FAIL; params.putTypes(slot.group(), operands); @@ -74,12 +78,26 @@ private static MethodQualifier getCallQualifier(Configuration dirty) { return call != null && call.desc() != null ? call : null; } + private static boolean isInvoke(Configuration dirty) { + AtData at = dirty.getAtData(); + return at == null || MixinAnnotationConstants.AT_VAL_INVOKE.equals(at.getValue()) || MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN.equals(at.getValue()); + } + + @Nullable + private static RedirectedInsn findRedirected(MixinContext context, Recipe recipe, Configuration dirty) { + TargetPair dirtyTarget = recipe.getDirtyTarget(); + AbstractInsnNode insn = dirtyTarget != null && dirty.getAtData() != null + ? AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget, InjectionQuery.of(dirty.getAtData()))) + : null; + return insn != null ? RedirectedInsn.of(insn) : null; + } + @Nullable - private static List findCallOperands(MixinContext context, Configuration dirty) { + private static List findCallOperands(MixinContext context, Recipe recipe, Configuration dirty) { MethodQualifier call = getCallQualifier(dirty); if (call == null) return null; - TargetPair dirtyTarget = dirty.getTargetMethod() != null ? context.dirtyMethods().resolve(dirty.getTargetMethod()) : null; + TargetPair dirtyTarget = recipe.getDirtyTarget(); AbstractInsnNode insn = dirtyTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget, InjectionQuery.of(dirty.getAtData()))) : null; boolean isStatic; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index 297f62a2..d7714aec 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -28,6 +28,7 @@ import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; import static org.sinytra.adapter.model.param.HandlerSignature.slot; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.LOCALS; import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.SINGLE_ANY; @@ -40,6 +41,7 @@ public class ModifyVariableMixin extends AbstractMixinType { private static final HandlerSignature SIGNATURE = HandlerSignature.of( ValueSource.VARIABLE, slot(SINGLE_ANY, VALUE), + slot(CAPTURED_PARAMS, CAPTURED_TARGET_ARGS), slot(LOCALS, KEEP) ); @@ -73,8 +75,9 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo boolean argsOnly = clean.getProperty(MixinKeys.ARGS_ONLY).orElse(false); String cleanDesc = clean.getTargetMethod().desc(); - if (argsOnly && cleanDesc != null && !dirty.getTargetMethod().desc().equals(cleanDesc)) { - Type cleanVarType = clean.getParameters().getTypes(SINGLE_ANY).getFirst(); + boolean targetChanged = cleanDesc != null && !dirty.getTargetMethod().desc().equals(cleanDesc); + Type cleanVarType = clean.getParameters().getTypes(SINGLE_ANY).getFirst(); + if (argsOnly && targetChanged) { List cleanTargetMethodParams = Parameters.getParameterTypes(cleanDesc); int cleanIndex = cleanTargetMethodParams.indexOf(cleanVarType); @@ -89,7 +92,10 @@ public TxResult postProcess(MixinContext context, Configuration clean, MutableCo if (adapter != null) { return HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, dirtyVarType); } - return TxResult.SUCCESS; + } + + if (targetChanged && !clean.getParameters().getTypes(CAPTURED_PARAMS).isEmpty()) { + return HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, cleanVarType); } dirty.inheritSignature(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index ef884cf2..36cd2ec4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -1,6 +1,7 @@ package org.sinytra.adapter.pipeline.injector; import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; @@ -15,6 +16,7 @@ import org.sinytra.adapter.model.param.MethodParameters; import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.model.param.RedirectedInsn; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -25,6 +27,7 @@ import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import org.sinytra.adapter.pipeline.rewrite.InstanceofMakesRedirectRedundant; import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; +import org.sinytra.adapter.util.AdapterUtil; import java.util.List; @@ -60,18 +63,28 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res processors .addAfter(SignatureProcessor.class, new DivertRedirectProcessor()); - if (clean.getAtData() == null || !MixinAnnotationConstants.AT_VAL_INVOKE.equals(clean.getAtData().getValue())) + if (clean.getAtData() == null) return TxResult.FAIL; - MethodQualifier targetDesc = clean.getAtData().getTargetQualifier().orElse(null); - if (targetDesc == null || targetDesc.desc() == null) - return TxResult.FAIL; - - List methodParams = Parameters.getParameterTypes(context.methodNode().desc); - boolean isStatic = isStaticRedirect(context, targetDesc, methodParams, Parameters.getParameterTypes(targetDesc.desc())); - if (!isStatic && methodParams.isEmpty()) - return TxResult.FAIL; - List callTypes = Parameters.getCallOperandTypes(isStatic ? null : methodParams.getFirst(), targetDesc.desc()); + List callTypes; + if (MixinAnnotationConstants.AT_VAL_INVOKE.equals(clean.getAtData().getValue())) { + MethodQualifier targetDesc = clean.getAtData().getTargetQualifier().orElse(null); + if (targetDesc == null || targetDesc.desc() == null) + return TxResult.FAIL; + + List methodParams = Parameters.getParameterTypes(context.methodNode().desc); + boolean isStatic = isStaticRedirect(context, targetDesc, methodParams, Parameters.getParameterTypes(targetDesc.desc())); + if (!isStatic && methodParams.isEmpty()) + return TxResult.FAIL; + callTypes = Parameters.getCallOperandTypes(isStatic ? null : methodParams.getFirst(), targetDesc.desc()); + } else { + TargetPair cleanTarget = clean.getTargetMethod() != null ? context.cleanMethods().resolve(clean.getTargetMethod()) : null; + AbstractInsnNode insn = cleanTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)) : null; + RedirectedInsn redirected = insn != null ? RedirectedInsn.of(insn) : null; + if (redirected == null) + return TxResult.FAIL; + callTypes = redirected.operands(); + } MethodParameters capturedMethodParams = MethodParameters.create( Parameters.parse(context.methodNode(), callTypes.size(), -1), diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java index c6f6d1e1..8a18b3b9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java @@ -7,8 +7,6 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.VarInsnNode; -import org.objectweb.asm.tree.analysis.SourceValue; import org.sinytra.adapter.analysis.compare.MethodAlignment; import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.analysis.flow.DataFlow.ImplicitCalls; @@ -73,7 +71,7 @@ public List locate(MixinContext context, Recipe recipe, Targets targe /// {@return the instructions the clean `@At` selects in the callee, matched to the clean instruction where the alignment can} private static List findIn(MixinContext context, Targets targets, AtData cleanAt, AbstractInsnNode cleanInsn, Callee callee) { - List selected = Describer.findSelectable(context, callee.method(), cleanAt, InjectionQuery::of); + List selected = Describer.findSelectable(context, callee.method(), cleanAt, at -> InjectionQuery.of(at).ignoringSlice()); if (selected.isEmpty()) { return List.of(); } @@ -106,7 +104,7 @@ private static Relocation relocation(TargetPair origin, Callee callee) { } slot += args[i].getSize(); } - return callee.calledFrom() != null ? new Relocation.CalledFrom(callee.calledFrom()) : null; + return callee.calledFrom() != null ? new Relocation.CalledFrom() : null; } /// {@return the methods the caller calls at the top level and the lambdas it creates, that weren't visited yet} @@ -125,7 +123,7 @@ private static List findCallees(MixinContext context, Callee caller, Pre } TargetPair method = context.dirtyMethods().resolveInherited(MethodQualifier.create(call)); if (method != null && !visited.contains(method.methodNode())) { - Set locals = passedLocals(flow, call, call.getOpcode() != Opcodes.INVOKESTATIC, call.desc, caller.originLocals()); + Set locals = PassedLocals.findPassedSlots(flow, call, call.getOpcode() != Opcodes.INVOKESTATIC, call.desc, caller.originLocals()); boolean mirrorable = fromOrigin && call.getOpcode() == Opcodes.INVOKESTATIC && !method.classNode().name.equals(from.classNode().name); addCallee(context, callees, visited, new Callee(method, locals, mirrorable ? call : null)); } @@ -134,7 +132,7 @@ private static List findCallees(MixinContext context, Callee caller, Pre for (IndyHandle lambda : MethodAnalyzer.findOwnIndyHandles(from.classNode(), from.methodNode())) { if (!visited.contains(lambda.method())) { Handle handle = lambda.handle(); - Set locals = passedLocals(flow, lambda.indy(), handle.getTag() != Opcodes.H_INVOKESTATIC, handle.getDesc(), caller.originLocals()); + Set locals = PassedLocals.findPassedSlots(flow, lambda.indy(), handle.getTag() != Opcodes.H_INVOKESTATIC, handle.getDesc(), caller.originLocals()); addCallee(context, callees, visited, new Callee(new TargetPair(from.classNode(), lambda.method()), locals, null)); } } @@ -147,32 +145,4 @@ private static void addCallee(MixinContext context, List callees, Set passedLocals(DataFlow flow, AbstractInsnNode insn, boolean hasReceiver, String calleeDesc, Set tracked) { - if (tracked.isEmpty()) { - return Set.of(); - } - List operands = flow.operands(insn); - Type[] args = Type.getArgumentTypes(calleeDesc); - Set passed = new HashSet<>(); - int slot = 0; - for (int i = 0; i < operands.size(); i++) { - int argIndex = hasReceiver ? i - 1 : i; - if (argIndex >= args.length) { - break; - } - if (isTrackedLoad(operands.get(i), tracked)) { - passed.add(slot); - } - slot += argIndex < 0 ? 1 : args[argIndex].getSize(); - } - return passed; - } - - private static boolean isTrackedLoad(SourceValue value, Set tracked) { - return value.insns.size() == 1 && value.insns.iterator().next() instanceof VarInsnNode load - && load.getOpcode() == Opcodes.ALOAD && tracked.contains(load.var); - } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java index 844f1fb1..6e61160d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -6,16 +6,19 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; import org.sinytra.adapter.analysis.InsnTraversal; import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.ann.SliceData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.RedirectedInsn; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.injector.MixinFlag; @@ -30,17 +33,28 @@ import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE_ASSIGN; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_LOAD; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_NEW; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_STORE; public final class Describer { @Nullable public static Configuration describe(MixinContext context, Recipe recipe, Candidate candidate) { AtData cleanAt = recipe.clean().getAtData(); - AtData described = cleanAt != null ? describeAt(context, cleanAt, candidate, InjectionQuery::of) : null; + TargetPair origin = recipe.getDirtyTarget(); + boolean otherMethod = origin == null || !candidate.target().classNode().name.equals(origin.classNode().name) + || !candidate.target().methodNode().name.equals(origin.methodNode().name) || !candidate.target().methodNode().desc.equals(origin.methodNode().desc); + Function query = otherMethod ? at -> InjectionQuery.of(at).ignoringSlice() : InjectionQuery::of; + AtData described = cleanAt != null ? describeAt(context, cleanAt, candidate, query) : null; if (described == null) { return null; } MutableConfiguration config = MutableConfiguration.create().setAtData(described); + if (otherMethod && recipe.clean().getProperty(MixinKeys.SLICES).map(slices -> !slices.isEmpty()).orElse(false)) { + config.setProperty(MixinKeys.SLICES, List.of()); + } + if (otherMethod && recipe.clean().hasProperty(MixinKeys.SLICE)) { + config.setProperty(MixinKeys.SLICE, new SliceData(null, null, null)); + } if (isLocalAt(context, described) && candidate.insn() instanceof VarInsnNode var) { AbstractInsnNode position = OpcodeUtil.isStoreOpcode(var.getOpcode()) ? var.getNext() : var; @@ -83,6 +97,11 @@ public static AtData describeAt(MixinContext context, AtData cleanAt, Candidate AtData at; if (changesKind) { at = AtData.create(AT_VAL_INVOKE, (MethodInsnNode) candidate.insn()); + } else if (candidate.insn() instanceof TypeInsnNode creation && AT_VAL_NEW.equals(cleanAt.getValue()) && cleanAt.getTarget().isPresent()) { + at = retargetCreation(cleanAt, creation); + if (at == null) { + return null; + } } else { at = candidate.insn() instanceof MethodInsnNode call && cleanAt.getTarget().isPresent() ? cleanAt.withTarget(call) : cleanAt; } @@ -117,6 +136,23 @@ public static MutableConfiguration describeLocal(MixinContext context, Recipe re return config; } + @Nullable + private static AtData retargetCreation(AtData cleanAt, TypeInsnNode creation) { + String target = cleanAt.getTargetOrThrow(); + boolean descriptor = target.startsWith("("); + if (!descriptor && !target.contains("")) { + return cleanAt; + } + MethodInsnNode constructor = RedirectedInsn.findConstructorCall(creation); + if (constructor == null) { + return null; + } + Type owner = Type.getObjectType(creation.desc); + return cleanAt.withTarget(descriptor + ? Type.getMethodDescriptor(owner, Type.getArgumentTypes(constructor.desc)) + : owner.getDescriptor() + "" + constructor.desc); + } + public static List findSelectable(MixinContext context, TargetPair target, AtData at, Function query) { if (!isLocalAt(context, at)) { return context.injections().find(target, query.apply(at.withOrdinal(null))); @@ -149,15 +185,15 @@ public static boolean isSame(Configuration a, Configuration b) { && Objects.equals(a.getProperty(MixinKeys.INDEX), b.getProperty(MixinKeys.INDEX)); } - private static boolean isLocalAt(MixinContext context, AtData at) { + public static boolean isLocalAt(MixinContext context, AtData at) { return context.hasFlag(MixinFlag.TARGETS_VARIABLE) && (AT_VAL_STORE.equals(at.getValue()) || AT_VAL_LOAD.equals(at.getValue())); } - private static boolean isParameter(MethodNode method, int slot) { + public static boolean isParameter(MethodNode method, int slot) { return slot < (MethodAnalyzer.isStatic(method) ? 0 : 1) + Arrays.stream(Type.getArgumentTypes(method.desc)).mapToInt(Type::getSize).sum(); } - private static int getParameterOrdinal(MethodNode method, int slot, Type type) { + public static int getParameterOrdinal(MethodNode method, int slot, Type type) { int current = MethodAnalyzer.isStatic(method) ? 0 : 1; int ordinal = 0; for (Type arg : Type.getArgumentTypes(method.desc)) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocalLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocalLocator.java new file mode 100644 index 00000000..b0a4a893 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocalLocator.java @@ -0,0 +1,56 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LocalVariableNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.analysis.locals.LocalAlignment; +import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +public final class LocalLocator { + public static int resolve(MixinContext context, Recipe recipe, TargetPair cleanTarget, int cleanSlot, AbstractInsnNode cleanInsn, TargetPair dirtyTarget) { + return resolve(context, cleanTarget, recipe.getNewCleanTarget(), cleanSlot, cleanInsn, dirtyTarget); + } + + public static int resolve(MixinContext context, TargetPair cleanTarget, @Nullable TargetPair origin, int cleanSlot, AbstractInsnNode cleanInsn, TargetPair dirtyTarget) { + int originSlot = origin != null + ? LocalAlignment.of(MethodAlignment.align(cleanTarget.methodNode(), origin.methodNode())).resolve(cleanSlot, cleanInsn) + : -1; + if (originSlot == -1 && origin != null && origin.methodNode().desc.equals(cleanTarget.methodNode().desc) + && Describer.isParameter(cleanTarget.methodNode(), cleanSlot)) { + originSlot = cleanSlot; + } + if (origin != null && isSame(origin, dirtyTarget)) { + return originSlot; + } + if (originSlot != -1) { + int passed = PassedLocals.resolve(context, origin, originSlot, dirtyTarget); + if (passed != -1) { + return passed; + } + } + + LocalVariableLookup cleanLocals = LocalVariableLookup.of(cleanTarget.methodNode()); + LocalVariableNode cleanLocal = cleanLocals != null ? cleanLocals.getInScopeOrNull(cleanSlot, cleanInsn) : null; + if (cleanLocal == null) { + return -1; + } + InsnList initializer = LocalsAnalyzer.findInitializerInsns(cleanTarget.methodNode(), cleanSlot); + return LocalsAnalyzer.findLocalWithInitializer(dirtyTarget.methodNode(), cleanLocal.desc, initializer) + .map(local -> local.index) + .orElse(-1); + } + + private static boolean isSame(TargetPair a, TargetPair b) { + return a.classNode().name.equals(b.classNode().name) && a.methodNode().name.equals(b.methodNode().name) + && a.methodNode().desc.equals(b.methodNode().desc); + } + + private LocalLocator() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/PassedLocals.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/PassedLocals.java new file mode 100644 index 00000000..5b40b50e --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/PassedLocals.java @@ -0,0 +1,104 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.objectweb.asm.tree.analysis.SourceValue; +import org.sinytra.adapter.analysis.flow.DataFlow; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.OpcodeUtil; + +import java.util.*; + +public final class PassedLocals { + private static final int MAX_DEPTH = 3; + + private record Step(TargetPair method, int slot) { + } + + public static int resolve(MixinContext context, TargetPair caller, int slot, TargetPair callee) { + Set visited = new HashSet<>(); + visited.add(caller.methodNode()); + List frontier = List.of(new Step(caller, slot)); + for (int depth = 0; depth < MAX_DEPTH && !frontier.isEmpty(); depth++) { + List next = new ArrayList<>(); + for (Step step : frontier) { + for (Step passed : findPassed(context, step)) { + if (isSame(passed.method(), callee)) { + return passed.slot(); + } + if (visited.add(passed.method().methodNode())) { + next.add(passed); + } + } + } + frontier = next; + } + return -1; + } + + public static Set findPassedSlots(DataFlow flow, AbstractInsnNode insn, boolean hasReceiver, String calleeDesc, Set tracked) { + if (tracked.isEmpty()) { + return Set.of(); + } + List operands = flow.operands(insn); + Type[] args = Type.getArgumentTypes(calleeDesc); + Set passed = new HashSet<>(); + int slot = 0; + for (int i = 0; i < operands.size(); i++) { + int argIndex = hasReceiver ? i - 1 : i; + if (argIndex >= args.length) { + break; + } + if (isTrackedLoad(operands.get(i), tracked)) { + passed.add(slot); + } + slot += argIndex < 0 ? 1 : args[argIndex].getSize(); + } + return passed; + } + + private static List findPassed(MixinContext context, Step step) { + TargetPair from = step.method(); + DataFlow flow = context.dataFlow(from); + Set tracked = Set.of(step.slot()); + List passed = new ArrayList<>(); + for (AbstractInsnNode insn : from.methodNode().instructions) { + if (insn instanceof MethodInsnNode call) { + TargetPair method = context.dirtyMethods().resolveInherited(MethodQualifier.create(call)); + if (method != null) { + findPassedSlots(flow, call, call.getOpcode() != Opcodes.INVOKESTATIC, call.desc, tracked) + .forEach(slot -> passed.add(new Step(method, slot))); + } + } + } + for (IndyHandle lambda : MethodAnalyzer.findOwnIndyHandles(from.classNode(), from.methodNode())) { + Handle handle = lambda.handle(); + TargetPair method = new TargetPair(from.classNode(), lambda.method()); + findPassedSlots(flow, lambda.indy(), handle.getTag() != Opcodes.H_INVOKESTATIC, handle.getDesc(), tracked) + .forEach(slot -> passed.add(new Step(method, slot))); + } + return passed; + } + + private static boolean isTrackedLoad(SourceValue value, Set tracked) { + return value.insns.size() == 1 && value.insns.iterator().next() instanceof VarInsnNode load + && OpcodeUtil.isLoadOpcode(load.getOpcode()) && tracked.contains(load.var); + } + + private static boolean isSame(TargetPair a, TargetPair b) { + return a.classNode().name.equals(b.classNode().name) && a.methodNode().name.equals(b.methodNode().name) + && a.methodNode().desc.equals(b.methodNode().desc); + } + + private PassedLocals() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchProcessor.java index 76761e7a..9b67f4b8 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchProcessor.java @@ -13,7 +13,7 @@ public record MethodPatchProcessor(List transforms) implement @Override public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { for (MethodTransformer transformer : this.transforms) { - transformer.apply(context, recipe.clean()); + transformer.apply(context); } return TxResult.SUCCESS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java index bebf7b3d..42ef66cb 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java @@ -9,6 +9,7 @@ import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.api.AuditTrail; +import org.sinytra.adapter.api.MethodPatchContext; import org.sinytra.adapter.api.MethodTransformer; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.mixin.MixinBridge.LocalCandidate; @@ -25,6 +26,11 @@ public class LocalCaptureUpgradeTransformer implements MethodTransformer { @Override + public PatchResult apply(MethodPatchContext patchContext) { + MixinContext context = (MixinContext) patchContext; + return apply(context, context.configuration()); + } + public PatchResult apply(MixinContext context, Configuration config) { // Check requirements Targets targets = context.targets(config.getTargetMethod()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java index 0ae9e18c..b25dc01c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java @@ -24,6 +24,8 @@ private void registerDefaultProcessors() { add(new TargetMethodProcessor()); // Mixin method signature add(new SignatureProcessor()); + // Sugar @Local parameters, once the signature fits the dirty target + add(new SugarLocalProcessor()); // Side effects that must only happen once the mixin is otherwise patched add(new SplitCancellationProcessor()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java new file mode 100644 index 00000000..be527ff3 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java @@ -0,0 +1,219 @@ +package org.sinytra.adapter.pipeline.processor; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.analysis.injection.InjectionQuery; +import org.sinytra.adapter.analysis.locals.LocalVariableLookup; +import org.sinytra.adapter.analysis.method.InheritanceHandler; +import org.sinytra.adapter.edit.param.ParamTransformationUtil; +import org.sinytra.adapter.mixin.MixinBridge; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.locate.Describer; +import org.sinytra.adapter.pipeline.locate.LocalLocator; +import org.sinytra.adapter.util.AdapterUtil; + +import java.util.ArrayList; +import java.util.List; + +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ARGS_ONLY; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_INDEX; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ORDINAL; + +public class SugarLocalProcessor implements Processor { + @Override + public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { + TargetPair cleanTarget = recipe.getCleanTarget(); + TargetPair dirtyTarget = recipe.getDirtyTarget(); + AtData dirtyAt = dirty.getAtData(); + if (cleanTarget == null || dirtyTarget == null || dirtyAt == null) { + return TxResult.PASS; + } + + MethodNode cleanHandler = context.unmodifiedMethodNode(); + MethodNode handler = context.methodNode(); + List cleanLocals = findLocalParameters(cleanHandler); + List locals = findLocalParameters(handler); + if (locals.isEmpty() || cleanLocals.size() != locals.size()) { + return TxResult.PASS; + } + AbstractInsnNode cleanInsn = AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)); + AbstractInsnNode dirtyInsn = AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget, InjectionQuery.of(dirtyAt))); + if (cleanInsn == null || dirtyInsn == null) { + return TxResult.PASS; + } + + String mixin = context.classNode().name; + MixinBridge.Session cleanSession = MixinBridge.session(mixin, cleanHandler, cleanTarget.classNode(), cleanTarget.methodNode(), context.environment().refmapHolder()); + MixinBridge.Session dirtySession = MixinBridge.session(mixin, handler, dirtyTarget.classNode(), dirtyTarget.methodNode(), context.environment().refmapHolder()); + TargetPair origin = recipe.getNewCleanTarget(); + boolean sameMethod = origin != null && origin.classNode().name.equals(dirtyTarget.classNode().name) + && origin.methodNode().name.equals(dirtyTarget.methodNode().name) && origin.methodNode().desc.equals(dirtyTarget.methodNode().desc); + boolean changed = false; + for (int i = 0; i < locals.size(); i++) { + int cleanIndex = cleanLocals.get(i); + int index = locals.get(i); + AnnotationNode cleanSelector = getLocalAnnotation(cleanHandler, cleanIndex); + AnnotationNode selector = getLocalAnnotation(handler, index); + Type cleanType = Type.getArgumentTypes(cleanHandler.desc)[cleanIndex]; + Type type = Type.getArgumentTypes(handler.desc)[index]; + + int current = dirtySession.findLocal(selector, type, dirtyInsn); + if (current != -1 && sameMethod) { + continue; + } + int cleanSlot = cleanSession.findLocal(cleanSelector, cleanType, cleanInsn); + if (cleanSlot == -1) { + continue; + } + int slot = LocalLocator.resolve(context, recipe, cleanTarget, cleanSlot, cleanInsn, dirtyTarget); + if (slot == -1 || current == slot) { + continue; + } + + Type dirtyType = getLocalType(dirtyTarget.methodNode(), slot, dirtyInsn); + Boolean widened = dirtyType == null ? null : compareTypes(context, type, dirtyType); + if (widened == null) { + context.recordCtxAudit("Can't select local %s for @Local parameter %s of type %s", slot, index, type); + continue; + } + AnnotationNode newSelector = describeSelector(context, dirtySession, selector, dirtyTarget, dirtyInsn, slot, dirtyType); + if (newSelector == null) { + context.recordCtxAudit("Can't select local %s for @Local parameter %s", slot, index); + continue; + } + + selector.values = newSelector.values; + if (!dirtyType.equals(type)) { + ParamTransformationUtil.retypeParameter(handler, index, dirtyType, widened); + } + updateModel(dirty.getParameters(), handler, index); + context.recordCtxAudit("Select local %s of type %s for @Local parameter %s", slot, dirtyType, index); + changed = true; + } + return changed ? TxResult.SUCCESS : TxResult.PASS; + } + + private static List findLocalParameters(MethodNode method) { + List indices = new ArrayList<>(); + int count = Type.getArgumentCount(method.desc); + for (int i = 0; i < count; i++) { + if (getLocalAnnotation(method, i) != null) { + indices.add(i); + } + } + return indices; + } + + @Nullable + public static AnnotationNode getLocalAnnotation(MethodNode method, int index) { + for (List[] annotations : List.of( + method.invisibleParameterAnnotations != null ? method.invisibleParameterAnnotations : new List[0], + method.visibleParameterAnnotations != null ? method.visibleParameterAnnotations : new List[0] + )) { + if (index < annotations.length && annotations[index] != null) { + for (AnnotationNode annotation : annotations[index]) { + if (annotation.desc.equals(MixinAnnotations.LOCAL)) { + return annotation; + } + } + } + } + return null; + } + + @Nullable + private static Type getLocalType(MethodNode method, int slot, AbstractInsnNode insn) { + LocalVariableLookup lookup = LocalVariableLookup.of(method); + LocalVariableNode local = lookup != null ? lookup.getInScopeOrNull(slot, insn) : null; + if (local == null && lookup != null && Describer.isParameter(method, slot)) { + local = lookup.getLocals().stream().filter(l -> l.index == slot).findFirst().orElse(null); + } + return local != null ? Type.getType(local.desc) : null; + } + + @Nullable + private static Boolean compareTypes(MixinContext context, Type type, Type dirtyType) { + if (type.equals(dirtyType)) { + return false; + } + if (type.getSort() != Type.OBJECT || dirtyType.getSort() != Type.OBJECT) { + return null; + } + InheritanceHandler inheritance = context.environment().inheritanceHandler(context.dirtyLookup()); + if (inheritance.isClassInherited(dirtyType.getInternalName(), type.getInternalName())) { + return false; + } + if (inheritance.isClassInherited(type.getInternalName(), dirtyType.getInternalName())) { + return true; + } + return null; + } + + @Nullable + private static AnnotationNode describeSelector(MixinContext context, MixinBridge.Session session, AnnotationNode selector, TargetPair target, + AbstractInsnNode insn, int slot, Type type) { + boolean argsOnly = Boolean.TRUE.equals(getValue(selector, PROPERTY_ARGS_ONLY)) && Describer.isParameter(target.methodNode(), slot); + AnnotationNode adjusted = new AnnotationNode(selector.desc); + if (argsOnly) { + adjusted.visit(PROPERTY_ARGS_ONLY, true); + } + if (getValue(selector, PROPERTY_INDEX) != null) { + adjusted.visit(PROPERTY_INDEX, slot); + return session.findLocal(adjusted, type, insn) == slot ? adjusted : null; + } + if (getValue(selector, PROPERTY_ORDINAL) == null && session.findLocal(adjusted, type, insn) == slot) { + return adjusted; + } + int ordinal = argsOnly + ? Describer.getParameterOrdinal(target.methodNode(), slot, type) + : context.locals().resolveLocalOrdinal(target, insn, type, slot); + if (ordinal != -1) { + adjusted.visit(PROPERTY_ORDINAL, ordinal); + if (session.findLocal(adjusted, type, insn) == slot) { + return adjusted; + } + } + return null; + } + + @Nullable + private static Object getValue(AnnotationNode annotation, String key) { + if (annotation.values != null) { + for (int i = 0; i + 1 < annotation.values.size(); i += 2) { + if (key.equals(annotation.values.get(i))) { + return annotation.values.get(i + 1); + } + } + } + return null; + } + + private static void updateModel(@Nullable MethodParameters params, MethodNode handler, int index) { + if (params == null) { + return; + } + Parameter parameter = Parameters.parse(handler, index, index + 1).getFirst(); + int offset = 0; + for (MethodParameters.ParamGroup group : params.getOrder()) { + List members = params.get(group); + if (index < offset + members.size()) { + members.set(index - offset, parameter); + return; + } + offset += members.size(); + } + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java index 7e082d51..eb0186c2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java @@ -3,14 +3,18 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ValueSource; +import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.param.Annotation; import org.sinytra.adapter.model.param.Parameter; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; @@ -56,8 +60,11 @@ public static Result solve(MixinContext context, Recipe recipe, Configuration di return new Result.Unbound("the original target method doesn't exist"); } boolean isStatic = MethodAnalyzer.isStatic(newTarget.methodNode()); - if (params.stream().anyMatch(Parameter::isLocalOrShare)) { - return new Result.Unbound("the handler has @Local or @Share parameters, which the generated injector can't pass on"); + if (params.stream().anyMatch(Parameter::isShare)) { + return new Result.Unbound("the handler has @Share parameters, which the generated injector can't share"); + } + if (!selectsLocals(context, recipe, dirty, newTarget, params)) { + return new Result.Unbound("a @Local parameter selects nothing at the new target"); } List forwarded = IntStream.range(0, params.size()).mapToObj(ValueSource.Forwarded::new).toList(); @@ -118,6 +125,26 @@ private static void addWidened(List widened, T member, @Nullable List params) { + if (params.stream().noneMatch(Parameter::isLocal)) { + return true; + } + AtData at = dirty.getAtData(); + AbstractInsnNode insn = at != null ? AdapterUtil.getFirstOrNull(context.injections().find(newTarget, InjectionQuery.of(at))) : null; + if (insn == null) { + return false; + } + MixinBridge.Session session = MixinBridge.session(context.classNode().name, context.methodNode(), newTarget.classNode(), newTarget.methodNode(), context.environment().refmapHolder()); + for (Parameter param : params) { + for (Annotation annotation : param.annotations()) { + if (annotation.getDesc().equals(MixinAnnotations.LOCAL) && session.findLocal(annotation.toAnnotationNode(), param.type(), insn) == -1) { + return false; + } + } + } + return true; + } + @Nullable private static ValueSource findReceiver(Relocation relocation, TargetPair newTarget) { if (relocation instanceof Relocation.ReceivesOrigin(int argIndex)) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java index c57d29a7..e53696d7 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java @@ -48,7 +48,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) List cleanTypes = cleanParams.getTypes(MethodParameters.ParamGroup.METHOD_PARAMS); List dirtyTypes = dirtyParams.getTypes(MethodParameters.ParamGroup.METHOD_PARAMS); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanTypes, dirtyTypes).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanTypes, dirtyTypes, context.typeRelation()).snapshot(); MethodNode methodNode = context.methodNode(); LocalVariableLookup lookup = LocalVariableLookup.of(context.methodNode()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java index 2bb4ee69..a806d1f3 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java @@ -55,7 +55,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) } private boolean applyDiff(List clean, List dirty, MixinContext context, int offset) { - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty, context.typeRelation()).snapshot(); if (!diff.isEmpty()) { PatchResult result = diff.offset(offset).asParameterTransformer(false) .apply(context); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java index 3069d69a..1cf6e7c9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java @@ -37,7 +37,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) List cleanParams = recipe.clean().getParameters().getTypes(ParamGroup.METHOD_PARAMS); List dirtyParams = dirtyMethodParams.getTypes(ParamGroup.METHOD_PARAMS); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanParams, dirtyParams).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanParams, dirtyParams, context.typeRelation()).snapshot(); List> callInsns = DataFlow.of(context.methodNode(), DataFlow.Producers.COMPUTER).findCallsWithProducers(cleanTargetQual); if (callInsns.isEmpty()) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java index 8daa77ff..9d8bce07 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java @@ -6,15 +6,19 @@ import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.locate.CalledMethodLocator; import org.sinytra.adapter.pipeline.locate.Candidate; +import org.sinytra.adapter.pipeline.locate.Describer; +import org.sinytra.adapter.pipeline.locate.LocalLocator; import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; import org.sinytra.adapter.pipeline.resolver.CompoundResolver; +import org.sinytra.adapter.util.AdapterUtil; import java.util.List; @@ -46,7 +50,7 @@ protected Configuration tryReuse(MixinContext context, Recipe recipe) { // Try reusing the original List insns = context.injections().find(dirtyTarget); - if (!insns.isEmpty() && selectsLocatedInsns(context, recipe, dirtyTarget, insns)) { + if (!insns.isEmpty() && selectsLocatedInsns(context, recipe, dirtyTarget, insns) && selectsSameLocal(context, recipe, dirtyTarget, insns.getFirst())) { return dirty.copyClean() .inheritAtData(); } @@ -54,6 +58,22 @@ protected Configuration tryReuse(MixinContext context, Recipe recipe) { return null; } + private static boolean selectsSameLocal(MixinContext context, Recipe recipe, TargetPair dirtyTarget, AbstractInsnNode dirtyInsn) { + TargetPair cleanTarget = recipe.getCleanTarget(); + AtData cleanAt = recipe.clean().getAtData(); + if (cleanTarget == null || cleanAt == null || !Describer.isLocalAt(context, cleanAt)) { + return true; + } + AbstractInsnNode cleanInsn = AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)); + if (cleanInsn == null) { + return true; + } + Type type = Type.getReturnType(context.methodNode().desc); + int cleanSlot = context.locals().resolveSelectedLocal(cleanTarget, cleanInsn, type); + int expected = cleanSlot != -1 ? LocalLocator.resolve(context, recipe, cleanTarget, cleanSlot, cleanInsn, dirtyTarget) : -1; + return expected == -1 || context.locals().resolveSelectedLocal(dirtyTarget, dirtyInsn, type) == expected; + } + private static boolean selectsLocatedInsns(MixinContext context, Recipe recipe, TargetPair dirtyTarget, List dirtyInsns) { TargetPair cleanTarget = recipe.getCleanTarget(); if (cleanTarget == null || !cleanTarget.methodNode().name.equals(dirtyTarget.methodNode().name) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java index 081e0e5d..88257e29 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java @@ -2,13 +2,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.LocalVariableNode; -import org.sinytra.adapter.analysis.compare.MethodAlignment; import org.sinytra.adapter.analysis.injection.InjectionQuery; -import org.sinytra.adapter.analysis.locals.LocalAlignment; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; -import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; @@ -17,6 +11,7 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.locate.Describer; +import org.sinytra.adapter.pipeline.locate.LocalLocator; import org.sinytra.adapter.pipeline.resolver.Resolver; import org.sinytra.adapter.util.AdapterUtil; @@ -47,7 +42,7 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { if (cleanSlot < 0) { return ResolutionResult.pass(); } - int dirtySlot = findDirtySlot(recipe, cleanTarget, dirtyTarget, cleanSlot, cleanInsn); + int dirtySlot = LocalLocator.resolve(context, recipe, cleanTarget, cleanSlot, cleanInsn, dirtyTarget); if (dirtySlot < 0 || context.locals().resolveSelectedLocal(dirtyTarget, dirtyInsn, type) == dirtySlot) { return ResolutionResult.pass(); } @@ -59,22 +54,4 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { context.recordCtxAudit("Select local %s instead of %s", dirtySlot, cleanSlot); return ResolutionResult.success(local); } - - private static int findDirtySlot(Recipe recipe, TargetPair cleanTarget, TargetPair dirtyTarget, int cleanSlot, AbstractInsnNode cleanInsn) { - if (cleanTarget.methodNode().name.equals(dirtyTarget.methodNode().name) && cleanTarget.methodNode().desc.equals(dirtyTarget.methodNode().desc)) { - MethodAlignment alignment = MethodAlignment.align(cleanTarget.methodNode(), dirtyTarget.methodNode()); - return LocalAlignment.of(alignment).resolve(cleanSlot, cleanInsn); - } - - LocalVariableLookup cleanLocals = recipe.cleanLocalsTable(); - LocalVariableNode cleanLocal = cleanLocals != null ? cleanLocals.getInScopeOrNull(cleanSlot, cleanInsn) : null; - if (cleanLocal == null) { - return -1; - } - - InsnList initializer = LocalsAnalyzer.findInitializerInsns(cleanTarget.methodNode(), cleanSlot); - return LocalsAnalyzer.findLocalWithInitializer(dirtyTarget.methodNode(), cleanLocal.desc, initializer) - .map(local -> local.index) - .orElse(-1); - } } diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java index a3f30405..d462b5fd 100644 --- a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java @@ -9,6 +9,7 @@ import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.BytecodeFixerUpper; +import org.sinytra.adapter.api.ClassPatchContext; import org.sinytra.adapter.api.ClassTransformer; import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.PatchResult; @@ -16,10 +17,8 @@ import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.AnnotationValueHandle; -import org.sinytra.adapter.model.ann.ClassTarget; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.model.constant.TypeConstants; -import org.sinytra.adapter.pipeline.ctx.PatchContext; import org.sinytra.adapter.util.OpcodeUtil; import java.util.Optional; @@ -29,7 +28,7 @@ public class FieldAccessorTypeTransformer implements ClassTransformer { private static final int PRIORITY_MAX = 9999; @Override - public PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchContext context) { + public PatchResult apply(ClassNode classNode, ClassPatchContext context) { BytecodeFixerUpper bfu = context.environment().bytecodeFixerUpper(); if (bfu == null || context.targetTypes().size() != 1) return PatchResult.PASS; @@ -48,7 +47,7 @@ public PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchCont return result; } - private PatchResult redirectAccessor(ClassNode classNode, MethodNode methodNode, AnnotationNode accessor, Type owner, BytecodeFixerUpper bfu, PatchContext context) { + private PatchResult redirectAccessor(ClassNode classNode, MethodNode methodNode, AnnotationNode accessor, Type owner, BytecodeFixerUpper bfu, ClassPatchContext context) { String fieldFqn = getAccessorTargetFieldName(classNode.name, methodNode, new AnnotationHandle(accessor), context.environment()) .orElse(null); if (fieldFqn == null) return PatchResult.PASS; diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java index c67a5128..cfbe1204 100644 --- a/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java @@ -7,11 +7,10 @@ import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.flow.DataFlow; import org.sinytra.adapter.api.BytecodeFixerUpper; +import org.sinytra.adapter.api.ClassPatchContext; import org.sinytra.adapter.api.ClassTransformer; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.api.TypeAdapter; -import org.sinytra.adapter.model.ann.ClassTarget; -import org.sinytra.adapter.pipeline.ctx.PatchContext; import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; @@ -25,7 +24,7 @@ public class FieldTypeUsageTransformer implements ClassTransformer { private static final Logger LOGGER = LogUtils.getLogger(); @Override - public PatchResult apply(ClassNode classNode, ClassTarget classTarget, PatchContext context) { + public PatchResult apply(ClassNode classNode, ClassPatchContext context) { BytecodeFixerUpper bfu = context.environment().bytecodeFixerUpper(); boolean applied = false; if (bfu != null) { diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java index 21f73ae5..ab465c0e 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java @@ -61,7 +61,7 @@ public void copyEntries(String from, String to) { .classTransformer(new FieldTypeUsageTransformer()) .methodTransformers(ImmutableMultimap.builder() .putAll(DynamicPatches.methodTransformers(List.of())) - .put(TxPhase.VALIDATED, (context, config) -> { + .put(TxPhase.VALIDATED, context -> { if (context.methodNode().name.startsWith("failAfter")) { throw new IllegalStateException("Simulated failure"); } @@ -276,6 +276,16 @@ void testUpdatedInjectionPointModifyExprVal() throws Exception { ); } + @Test + void testDescriptorOnlyTargetSelector() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/FarmLandBlockMixin", + "isNearWaterByDescriptor", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Test void testUpdatedInjectionPointModifyExprValConst() throws Exception { assertSameCode( @@ -358,6 +368,16 @@ void testRollbackSplitMethodCancellation() throws Exception { }); } + @Test + void testMultiTargetSplit() throws Exception { + LoadResult result = load("org/sinytra/adapter/test/mixin/GuiMixin", List.of("modifyStatusBarY")); + MethodNode original = result.patched().methods.stream().filter(m -> m.name.equals("modifyStatusBarY")).findFirst().orElseThrow(); + MethodNode copy = result.patched().methods.stream().filter(m -> m.name.equals("modifyStatusBarY$adapter$target$1")).findFirst().orElseThrow(); + Assertions.assertThat(getTargetMethods(original)).containsExactly("renderAirLevel(Lnet/minecraft/client/gui/GuiGraphics;)V"); + Assertions.assertThat(getTargetMethods(copy)).containsExactly("renderVehicleHealth(Lnet/minecraft/client/gui/GuiGraphics;)V"); + Assertions.assertThat(new AnnotationHandle(copy.visibleAnnotations.getFirst()).getValue("require").orElseThrow().get()).isZero(); + } + private static List getTargetMethods(MethodNode method) { return new AnnotationHandle(method.visibleAnnotations.getFirst()).>getValue("method").orElseThrow().get(); } @@ -389,7 +409,46 @@ void testCompareModifiedMethod() throws Exception { "org/sinytra/adapter/test/mixin/LivingEntityMixin", "onUnderwater", assertUnique(), - assertHasGeneratedMethod("org/sinytra/adapter/test/mixin/adapter_generated_net_neoforged_neoforge_common_CommonHooks") + assertHasGeneratedMethod("org/sinytra/adapter/test/mixin/adapter_generated_net_neoforged_neoforge_common_CommonHooks"), + (patched, expected, env) -> { + ClassNode generated = env.classGenerator().getGeneratedMixinClasses().get("org/sinytra/adapter/test/mixin/adapter_generated_net_neoforged_neoforge_common_CommonHooks").node(); + MethodNode mirror = generated.methods.stream().filter(m -> m.name.startsWith("onUnderwater$adapter$mirror")).findFirst().orElseThrow(); + List slices = new AnnotationHandle(mirror.visibleAnnotations.getFirst()).>getValue("slice").map(v -> v.get()).orElse(List.of()); + Assertions.assertThat(slices).isEmpty(); + } + ); + } + + @Test + void testRedirectFieldMirrored() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/LivingEntityMixin", + "redirectClientSide", + assertUnique(), + assertHasGeneratedMethod("org/sinytra/adapter/test/mixin/adapter_generated_net_neoforged_neoforge_common_CommonHooks"), + (patched, expected, env) -> { + ClassNode generated = env.classGenerator().getGeneratedMixinClasses().get("org/sinytra/adapter/test/mixin/adapter_generated_net_neoforged_neoforge_common_CommonHooks").node(); + MethodNode mirror = generated.methods.stream().filter(m -> m.name.startsWith("redirectClientSide$adapter$mirror")).findFirst().orElseThrow(); + Assertions.assertThat(mirror.desc).isEqualTo("(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/LivingEntity;)Z"); + } + ); + } + + @Test + void testRedirectChangedConstructor() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/pipeline/FaceBakeryMixin", + "createQuad", + assertInjectionPoint() + ); + } + + @Test + void testAppliedLocalWarning() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/LivingEntityMixin", + "modifyHealthDamage", + assertAudit("Warning: The variable selector selects local 2") ); } @@ -559,7 +618,19 @@ void testExtractByCodePath() throws Exception { "org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin", "replaceDyeColor", "org/sinytra/adapter/test/mixin/pipeline/adapter_generated_net_neoforged_neoforge_common_extensions_IBlockExtension", - assertTargetMethod() + assertTargetMethod(), + assertLocalSelectors() + ); + } + + @Test + void testExtractWidenedLocal() throws Exception { + assertSameCodeExtracted( + "org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin", + "replaceDyeColorOnClient", + "org/sinytra/adapter/test/mixin/pipeline/adapter_generated_net_neoforged_neoforge_common_extensions_IBlockExtension", + assertTargetMethod(), + assertLocalSelectors() ); } @@ -593,6 +664,12 @@ void testRollbackMirror() throws Exception { @Test void testRollbackMove() throws Exception { + LoadResult result = load("org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin", List.of("failAfterMove")); + MethodNode lambda = result.patched().methods.stream().filter(m -> m.name.startsWith("lambda$failAfterMove$")).findFirst().orElseThrow(); + MethodNode expected = result.expected().methods.stream().filter(m -> m.name.equals(lambda.name)).findFirst().orElseThrow(); + Assertions.assertThat(StreamSupport.stream(lambda.instructions.spliterator(), false).toList()) + .usingElementComparator(new InsnComparator()) + .isEqualTo(StreamSupport.stream(expected.instructions.spliterator(), false).toList()); assertSameCode( "org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin", "failAfterMove", @@ -632,10 +709,20 @@ void testWidenedCallOwnerWrapOperation() throws Exception { ); } + @Test + void testTargetInheritedDelegatingDefault() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/pipeline/MultiPartBakedModelMixin", + "onGetQuads", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); - patched.methods.removeIf(m -> !allowedMethods.contains(m.name)); + patched.methods.removeIf(m -> !allowedMethods.contains(m.name) && allowedMethods.stream().noneMatch(name -> m.name.startsWith("lambda$" + name + "$"))); patcher.process(patched); return new LoadResult(patchEnvironment, patched, loadClass(className)); } diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/FarmLandBlockMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/FarmLandBlockMixin.java index 3e47fab6..d8b262d5 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/FarmLandBlockMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/FarmLandBlockMixin.java @@ -26,4 +26,20 @@ private static boolean isFarmlandNearWater(boolean original, @Local(ordinal = 0) private static boolean isFarmlandNearWaterExpected(boolean original, @Local(ordinal = 0) LevelReader world, @Local(ordinal = 1) BlockPos blockPos) { return original; } + + @ModifyExpressionValue( + method = "(Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;)Z", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/material/FluidState;is(Lnet/minecraft/tags/TagKey;)Z") + ) + private static boolean isNearWaterByDescriptor(boolean original) { + return original; + } + + @ModifyExpressionValue( + method = "(Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;)Z", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;canBeHydrated(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/material/FluidState;Lnet/minecraft/core/BlockPos;)Z") + ) + private static boolean isNearWaterByDescriptorExpected(boolean original) { + return original; + } } diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java index 26196a46..e7eff6fa 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java @@ -182,4 +182,13 @@ private void cancelAfterArmor(GuiGraphics guiGraphics, CallbackInfo ci) { private void failAfterCancelling(GuiGraphics guiGraphics, CallbackInfo ci) { ci.cancel(); } + + @ModifyArg( + method = {"renderPlayerHealth", "renderVehicleHealth"}, + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;blitSprite(Lnet/minecraft/resources/ResourceLocation;IIII)V"), + index = 2 + ) + public int modifyStatusBarY(int y) { + return y; + } } diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/LivingEntityMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/LivingEntityMixin.java index f84e1048..8760c804 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/LivingEntityMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/LivingEntityMixin.java @@ -4,11 +4,13 @@ import com.llamalad7.mixinextras.sugar.Local; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -83,6 +85,28 @@ public void failAfterMirrorExpected(CallbackInfo ci) { ourUniqueMethod(); } + @Redirect(method = "baseTick", at = @At(value = "FIELD", target = "Lnet/minecraft/world/level/Level;isClientSide:Z", ordinal = 2)) + public boolean redirectClientSide(Level level) { + ourUniqueMethod(); + return level.isClientSide; + } + + @Unique + public boolean redirectClientSideExpected(Level level) { + ourUniqueMethod(); + return level.isClientSide; + } + + @ModifyVariable(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getHealth()F"), argsOnly = true) + private float modifyHealthDamage(float amount) { + return amount; + } + + @ModifyVariable(method = "actuallyHurt", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;getHealth()F"), argsOnly = true) + private float modifyHealthDamageExpected(float amount) { + return amount; + } + @Unique private void ourUniqueMethod() { // Noop diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java index e681180b..1f7df2c5 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/BeaconBlockEntityMixin.java @@ -4,6 +4,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.item.DyeColor; import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.BeaconBeamBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BeaconBlockEntity; @@ -16,6 +17,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import java.util.function.IntSupplier; + @Mixin(BeaconBlockEntity.class) public abstract class BeaconBlockEntityMixin { // https://github.com/DiemondPlayer/Unidye/blob/1.21/src/main/java/net/diemond_player/unidye/mixin/BeaconBlockEntityMixin.java @@ -31,16 +34,33 @@ private static int replaceDyeColor(DyeColor instance, @Local(ordinal = 1) BlockP method = "getBeaconColorMultiplier(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/BlockPos;)Ljava/lang/Integer;", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I") ) - private static int replaceDyeColorExpected(DyeColor instance, @Local(ordinal = 1) BlockPos pos, @Local(argsOnly = true) Level level) { + private static int replaceDyeColorExpected(DyeColor instance, @Local(ordinal = 0) BlockPos pos, @Local(argsOnly = true) LevelReader level) { return instance.getTextureDiffuseColor(); } + @Redirect( + method = "tick(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BeaconBlockEntity;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I") + ) + private static int replaceDyeColorOnClient(DyeColor instance, @Local(argsOnly = true) Level level) { + return level.isClientSide ? 0 : instance.getTextureDiffuseColor(); + } + + @Redirect( + method = "getBeaconColorMultiplier(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/BlockPos;)Ljava/lang/Integer;", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I") + ) + private static int replaceDyeColorOnClientExpected(DyeColor instance, @Local(argsOnly = true) LevelReader level) { + return ((Level) level).isClientSide ? 0 : instance.getTextureDiffuseColor(); + } + @Redirect( method = "tick(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BeaconBlockEntity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I") ) private static int failAfterMove(DyeColor instance) { - return instance.getTextureDiffuseColor(); + IntSupplier color = () -> instance.getTextureDiffuseColor(); + return color.getAsInt(); } @Redirect( @@ -48,7 +68,8 @@ private static int failAfterMove(DyeColor instance) { at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/DyeColor;getTextureDiffuseColor()I") ) private static int failAfterMoveExpected(DyeColor instance) { - return instance.getTextureDiffuseColor(); + IntSupplier color = () -> instance.getTextureDiffuseColor(); + return color.getAsInt(); } @Inject( diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/FaceBakeryMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/FaceBakeryMixin.java new file mode 100644 index 00000000..9f50b0aa --- /dev/null +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/FaceBakeryMixin.java @@ -0,0 +1,28 @@ +package org.sinytra.adapter.test.mixin.pipeline; + +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.FaceBakery; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(FaceBakery.class) +public class FaceBakeryMixin { + @Redirect( + method = "bakeQuad", + at = @At(value = "NEW", target = "([IILnet/minecraft/core/Direction;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;Z)Lnet/minecraft/client/renderer/block/model/BakedQuad;") + ) + private BakedQuad createQuad(int[] vertices, int tintIndex, Direction direction, TextureAtlasSprite sprite, boolean shade) { + return null; + } + + @Redirect( + method = "bakeQuad", + at = @At(value = "NEW", target = "([IILnet/minecraft/core/Direction;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;ZZ)Lnet/minecraft/client/renderer/block/model/BakedQuad;") + ) + private BakedQuad createQuadExpected(int[] vertices, int tintIndex, Direction direction, TextureAtlasSprite sprite, boolean shade, boolean adapter_injected_5) { + return null; + } +} diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/MultiPartBakedModelMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/MultiPartBakedModelMixin.java new file mode 100644 index 00000000..cd7e0b16 --- /dev/null +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/MultiPartBakedModelMixin.java @@ -0,0 +1,31 @@ +package org.sinytra.adapter.test.mixin.pipeline; + +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.resources.model.MultiPartBakedModel; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.client.model.data.ModelData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; + +@Mixin(MultiPartBakedModel.class) +public class MultiPartBakedModelMixin { + @Inject(method = "getQuads", at = @At("HEAD"), cancellable = true) + private void onGetQuads(BlockState state, Direction side, RandomSource random, CallbackInfoReturnable> cir) { + cir.setReturnValue(List.of()); + } + + @Inject( + method = "getQuads(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/Direction;Lnet/minecraft/util/RandomSource;Lnet/neoforged/neoforge/client/model/data/ModelData;Lnet/minecraft/client/renderer/RenderType;)Ljava/util/List;", + at = @At("HEAD"), + cancellable = true + ) + private void onGetQuadsExpected(BlockState state, Direction side, RandomSource random, ModelData adapter_injected_3, RenderType adapter_injected_4, CallbackInfoReturnable> cir) { + cir.setReturnValue(List.of()); + } +} diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 3b191fb2..0cab2200 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -85,10 +85,29 @@ void testShiftedVariableToExpression() throws Exception { ); } + @Test + void testTargetInheritedDelegatingDefault() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/MultiPartModelMixin", + "onSetSeed", + assertTargetMethod(), + assertInjectionPoint() + ); + } + + @Test + void testModifyVariableCapturedTargetArgs() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/MultiPartModelMixin", + "modifyRandom", + assertTargetMethod() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); - patched.methods.removeIf(m -> !allowedMethods.contains(m.name)); + patched.methods.removeIf(m -> !allowedMethods.contains(m.name) && allowedMethods.stream().noneMatch(name -> m.name.startsWith("lambda$" + name + "$"))); patcher.process(patched); return new LoadResult(patchEnvironment, patched, loadClass(className)); } diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/MultiPartModelMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/MultiPartModelMixin.java new file mode 100644 index 00000000..b3a905d9 --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/MultiPartModelMixin.java @@ -0,0 +1,44 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import net.minecraft.client.renderer.block.BlockAndTintGetter; +import net.minecraft.client.renderer.block.dispatch.multipart.MultiPartModel; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(MultiPartModel.class) +public class MultiPartModelMixin { + @Inject(method = "collectParts", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/RandomSource;setSeed(J)V")) + private void onSetSeed(RandomSource random, List output, CallbackInfo ci) { + random.nextInt(); + } + + @Inject( + method = "collectParts(Lnet/minecraft/client/renderer/block/BlockAndTintGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/util/RandomSource;Ljava/util/List;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/util/RandomSource;setSeed(J)V") + ) + private void onSetSeedExpected(BlockAndTintGetter adapter_injected_0, BlockPos adapter_injected_1, BlockState adapter_injected_2, RandomSource random, List output, CallbackInfo ci) { + random.nextInt(); + } + + @ModifyVariable(method = "collectParts", at = @At("HEAD"), argsOnly = true) + private RandomSource modifyRandom(RandomSource value, RandomSource random) { + return random; + } + + @ModifyVariable( + method = "collectParts(Lnet/minecraft/client/renderer/block/BlockAndTintGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/util/RandomSource;Ljava/util/List;)V", + at = @At("HEAD"), + argsOnly = true + ) + private RandomSource modifyRandomExpected(RandomSource value, BlockAndTintGetter adapter_injected_1, BlockPos adapter_injected_2, BlockState adapter_injected_3, RandomSource random) { + return random; + } +} diff --git a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java index fb162420..199e46ea 100644 --- a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java +++ b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java @@ -20,6 +20,7 @@ import java.io.InputStream; import java.lang.reflect.Field; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -244,6 +245,22 @@ protected AssertCallback assertNotRelocated(String targetClass) { }; } + protected AssertCallback assertLocalSelectors() { + return (patched, expected, env) -> { + Function> selectors = method -> { + List values = new ArrayList<>(); + List[] annotations = Objects.requireNonNullElseGet(method.invisibleParameterAnnotations, () -> new List[0]); + for (List list : annotations) { + if (list != null) { + list.stream().filter(a -> a.desc.equals(MixinAnnotations.LOCAL)).forEach(a -> values.add(String.valueOf(a.values))); + } + } + return values; + }; + Assertions.assertThat(selectors.apply(patched)).as("@Local selectors").isEqualTo(selectors.apply(expected)); + }; + } + /// The audit log of the patched method has an entry containing the text protected AssertCallback assertAudit(String text) { return (patched, expected, env) -> { From 3ef12714a88443b5629ce6241b47901921d076d7 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 17:24:48 +0200 Subject: [PATCH 27/44] Fix NPE in locals lookup --- .../analysis/injection/InjectionLocator.java | 2 +- .../analysis/locals/LocalsAnalyzer.java | 4 +- .../sinytra/adapter/mixin/MixinBridge.java | 91 ++++++++++++++++--- .../adapter/pipeline/AppliedLocalsCheck.java | 4 +- .../adapter/pipeline/MultiTargetSplitter.java | 2 +- .../pipeline/PipelineMethodTransformer.java | 6 +- .../processor/SugarLocalProcessor.java | 4 +- .../processor/relocate/RelocationSolver.java | 2 +- 8 files changed, 94 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java index c20099c2..f91f1b60 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java @@ -115,7 +115,7 @@ private List compute(TargetPair target, InjectionQuery query) } private MixinBridge.Session session(TargetPair target) { - return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment().refmapHolder()); + return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment()); } @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index 0a470421..15ac7a97 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -187,12 +187,12 @@ public CapturedLocalsUsage resolveUsage(CapturedLocals captured, CapturedLocalsT } public List findSelectableLocals(TargetPair target, AbstractInsnNode insn, Type type) { - return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment().refmapHolder()) + return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment()) .findLocalCandidates(type, insn, false); } public int resolveSelectedLocal(TargetPair target, AbstractInsnNode insn, Type type) { - return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment().refmapHolder()) + return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment()) .findLocal(this.context.methodAnnotation().unwrap(), type, insn); } diff --git a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java index cc2700f1..a33d3684 100644 --- a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java +++ b/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java @@ -6,8 +6,10 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; +import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.RefmapHolder; import org.slf4j.Logger; +import org.spongepowered.asm.mixin.FabricUtil; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.extensibility.IMixinConfig; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; @@ -27,6 +29,7 @@ import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.mixin.refmap.IReferenceMapper; import org.spongepowered.asm.mixin.struct.AnnotatedMethodInfo; +import org.spongepowered.asm.mixin.struct.SpecialMethodInfo; import org.spongepowered.asm.mixin.transformer.ClassInfo; import org.spongepowered.asm.mixin.transformer.ext.Extensions; import org.spongepowered.asm.util.Annotations; @@ -40,6 +43,7 @@ import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; public final class MixinBridge { private static final Logger LOGGER = LogUtils.getLogger(); @@ -72,8 +76,12 @@ public final class MixinBridge { } } - public static Session session(String mixinClass, MethodNode mixinMethod, ClassNode targetClass, MethodNode targetMethod, RefmapHolder refmapHolder) { - return new Session(new ClassMixinContext(mixinClass, targetClass.name, refmapHolder), mixinMethod, targetClass, targetMethod); + public static Session session(String mixinClass, MethodNode mixinMethod, ClassNode targetClass, MethodNode targetMethod, PatchEnvironment environment) { + return session(mixinClass, mixinMethod, targetClass, targetMethod, environment.refmapHolder(), environment.fabricLVTCompatibility()); + } + + public static Session session(String mixinClass, MethodNode mixinMethod, ClassNode targetClass, MethodNode targetMethod, RefmapHolder refmapHolder, int lvtCompatibility) { + return new Session(new ClassMixinContext(mixinClass, targetClass.name, refmapHolder, lvtCompatibility), mixinMethod, targetClass, targetMethod, lvtCompatibility); } public static LocalVariableNode[] getLocalsAt(ClassNode targetClass, MethodNode targetMethod, AbstractInsnNode insn, int lvtCompatLevel) { @@ -146,14 +154,16 @@ public record LocalCandidate(int ordinal, int index) { public static final class Session { private final IMixinContext mixinContext; + private final int lvtCompatibility; private final MethodNode mixinMethod; private final ClassNode targetClass; private final MethodNode targetMethod; @Nullable private Target target; - private Session(IMixinContext mixinContext, MethodNode mixinMethod, ClassNode targetClass, MethodNode targetMethod) { + private Session(IMixinContext mixinContext, MethodNode mixinMethod, ClassNode targetClass, MethodNode targetMethod, int lvtCompatibility) { this.mixinContext = mixinContext; + this.lvtCompatibility = lvtCompatibility; this.mixinMethod = mixinMethod; this.targetClass = targetClass; this.targetMethod = targetMethod; @@ -206,7 +216,7 @@ public List findLocalCandidates(Type type, AbstractInsnNode insn } private LocalVariableDiscriminator.Context localsContext(Type type, boolean argsOnly, AbstractInsnNode insn) { - InjectionInfo injectionInfo = createInjectionInfo(CallbackInjectionInfo.class, this.mixinContext); + InjectionInfo injectionInfo = createInjectionInfo(CallbackInjectionInfo.class, this.mixinContext, this.lvtCompatibility); // The context reads the locals through Locals.getLocalsAt, which isn't thread safe synchronized (LOCALS_LOCK) { return new LocalVariableDiscriminator.Context(injectionInfo, type, argsOnly, target(), insn); @@ -217,7 +227,7 @@ private List find(InjectionPoint point, @Nullable AnnotationNo List targetInsns = new ArrayList<>(); if (LOCAL_VARIABLE_INJECTION_POINT_CLASS.isInstance(point)) { // Local variable injection points need a ModifyVariable injector, and always search the whole method - InjectionInfo info = createInjectionInfo(ModifyVariableInjectionInfo.class, this.mixinContext); + InjectionInfo info = createInjectionInfo(ModifyVariableInjectionInfo.class, this.mixinContext, this.lvtCompatibility); LOCAL_VARIABLE_INJECTION_POINT_FIND.invoke(point, info, this.targetMethod.instructions, targetInsns, target()); } else { InsnList instructions = slice != null ? slice(slice) : this.targetMethod.instructions; @@ -239,16 +249,66 @@ private Target target() { } } - private static InjectionInfo createInjectionInfo(Class type, IMixinContext mixinContext) { + private static InjectionInfo createInjectionInfo(Class type, IMixinContext mixinContext, int lvtCompatibility) { try { InjectionInfo info = (InjectionInfo) UNSAFE.allocateInstance(type); ANNOTATED_METHOD_INFO_CONTEXT.set(info, mixinContext); + CompatibilityContexts.attach(info, lvtCompatibility); return info; } catch (InstantiationException e) { throw new RuntimeException(e); } } + private static final class CompatibilityContexts { + @Nullable + private static final VarHandle[] HANDLES; + @Nullable + private static final Class[] CLASSES; + private static final Map CONTEXTS = new ConcurrentHashMap<>(); + + static { + VarHandle[] handles = null; + Class[] classes = null; + try { + Class targetContext = Class.forName("org.spongepowered.asm.mixin.transformer.MixinTargetContext"); + Class mixinInfo = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo"); + Class mixinConfig = Class.forName("org.spongepowered.asm.mixin.transformer.MixinConfig"); + handles = new VarHandle[]{ + TRUSTED_LOOKUP.findVarHandle(SpecialMethodInfo.class, "mixin", targetContext), + TRUSTED_LOOKUP.findVarHandle(targetContext, "mixin", mixinInfo), + TRUSTED_LOOKUP.findVarHandle(mixinInfo, "parent", mixinConfig), + TRUSTED_LOOKUP.findVarHandle(mixinConfig, "decorations", Map.class) + }; + classes = new Class[]{targetContext, mixinInfo, mixinConfig}; + } catch (Throwable t) { + LOGGER.debug("Mixin target contexts are unavailable, local variable compatibility uses the selector context", t); + } + HANDLES = handles; + CLASSES = classes; + } + + public static void attach(InjectionInfo info, int lvtCompatibility) { + if (HANDLES != null) { + HANDLES[0].set(info, CONTEXTS.computeIfAbsent(lvtCompatibility, CompatibilityContexts::create)); + } + } + + private static Object create(int lvtCompatibility) { + try { + Object config = UNSAFE.allocateInstance(CLASSES[2]); + HANDLES[3].set(config, new HashMap<>(Map.of(FabricUtil.KEY_COMPATIBILITY, lvtCompatibility))); + Object mixinInfo = UNSAFE.allocateInstance(CLASSES[1]); + HANDLES[2].set(mixinInfo, config); + Object context = UNSAFE.allocateInstance(CLASSES[0]); + HANDLES[1].set(context, mixinInfo); + return context; + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + } + } + private record MethodSliceContext(IMixinContext context, MethodNode methodNode) implements ISliceContext { @Override public IMixinContext getMixin() { @@ -289,12 +349,23 @@ public String remapWithContext(String context, String className, String referenc //@formatter:on } - private static class DummyMixinConfig implements IMixinConfig { + private record DummyMixinConfig(int lvtCompatibility) implements IMixinConfig { @Override public MixinEnvironment getEnvironment() { return MixinEnvironment.getCurrentEnvironment(); } + @Override + public boolean hasDecoration(String key) { + return FabricUtil.KEY_COMPATIBILITY.equals(key); + } + + @Override + @SuppressWarnings("unchecked") + public V getDecoration(String key) { + return FabricUtil.KEY_COMPATIBILITY.equals(key) ? (V) (Integer) this.lvtCompatibility : null; + } + //@formatter:off @Override public IMixinConfigSource getSource() {throw new UnsupportedOperationException();} @Override public String getCleanSourceId() {return "dummy";} @@ -305,8 +376,6 @@ public MixinEnvironment getEnvironment() { @Override public boolean isRequired() {throw new UnsupportedOperationException();} @Override public Set getTargets() {throw new UnsupportedOperationException();} @Override public void decorate(String key, V value) {} - @Override public boolean hasDecoration(String key) {return false;} - @Override public V getDecoration(String key) {return null;} //@formatter:on } @@ -335,11 +404,11 @@ private static final class ClassMixinContext implements IMixinContext { private final ReferenceRemapper referenceRemapper; private final IMixinInfo mixinInfo; - public ClassMixinContext(String className, String targetClass, RefmapHolder refmapHolder) { + public ClassMixinContext(String className, String targetClass, RefmapHolder refmapHolder, int lvtCompatibility) { this.className = className; this.targetClass = targetClass; this.referenceRemapper = new ReferenceRemapper(refmapHolder); - this.mixinInfo = new DummyMixinInfo(new DummyMixinConfig()); + this.mixinInfo = new DummyMixinInfo(new DummyMixinConfig(lvtCompatibility)); } @Override diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java b/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java index 34f13a0b..b033b67a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java @@ -37,8 +37,8 @@ public static void check(Object actor, MixinContext context, TargetPair cleanTar continue; } if (cleanSession == null) { - cleanSession = MixinBridge.session(context.classNode().name, handler, cleanTarget.classNode(), cleanTarget.methodNode(), context.environment().refmapHolder()); - dirtySession = MixinBridge.session(context.classNode().name, handler, dirtyTarget.classNode(), dirtyTarget.methodNode(), context.environment().refmapHolder()); + cleanSession = MixinBridge.session(context.classNode().name, handler, cleanTarget.classNode(), cleanTarget.methodNode(), context.environment()); + dirtySession = MixinBridge.session(context.classNode().name, handler, dirtyTarget.classNode(), dirtyTarget.methodNode(), context.environment()); } int cleanSlot = cleanSession.findLocal(local, params[i], cleanInsn); int slot = dirtySession.findLocal(local, params[i], dirtyInsn); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java b/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java index 890ba19d..bc872fc3 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java @@ -105,7 +105,7 @@ private static boolean needsPatching(ClassNode mixinClass, MixinParser.MixinMeth AnnotationNode slice = annotation.getValue(PROPERTY_SLICE) .map(handle -> handle.get() instanceof List list ? (list.isEmpty() ? null : (AnnotationNode) list.getFirst()) : (AnnotationNode) handle.get()) .orElse(null); - return MixinBridge.session(mixinClass.name, mixin.methodNode(), dirty, dirtyTarget, environment.refmapHolder()) + return MixinBridge.session(mixinClass.name, mixin.methodNode(), dirty, dirtyTarget, environment) .findInjectionPoint(annotation.unwrap(), at, slice) .isEmpty(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java index 16af66fd..4b6ba5d8 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java @@ -57,7 +57,11 @@ public PatchResult apply(MixinContext context, Configuration config) { TargetPair dirtyTarget = context.dirtyMethods().resolve(config.getTargetMethod()); if (!this.patchResolver.matches(config) && !failsDirtyInjectionCheck(context, config, dirtyTarget) && hasValidSlice(context, config, dirtyTarget)) { if (dirtyTarget != null && !this.patchesOnly) { - AppliedLocalsCheck.check(this, context, cleanTarget, dirtyTarget); + try { + AppliedLocalsCheck.check(this, context, cleanTarget, dirtyTarget); + } catch (Exception e) { + LOGGER.debug(MIXINPATCH, "Failed to check the local selectors of applying mixin {}", context.getMixinId(), e); + } } return PatchResult.PASS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java index be527ff3..4de60150 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java @@ -56,8 +56,8 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe } String mixin = context.classNode().name; - MixinBridge.Session cleanSession = MixinBridge.session(mixin, cleanHandler, cleanTarget.classNode(), cleanTarget.methodNode(), context.environment().refmapHolder()); - MixinBridge.Session dirtySession = MixinBridge.session(mixin, handler, dirtyTarget.classNode(), dirtyTarget.methodNode(), context.environment().refmapHolder()); + MixinBridge.Session cleanSession = MixinBridge.session(mixin, cleanHandler, cleanTarget.classNode(), cleanTarget.methodNode(), context.environment()); + MixinBridge.Session dirtySession = MixinBridge.session(mixin, handler, dirtyTarget.classNode(), dirtyTarget.methodNode(), context.environment()); TargetPair origin = recipe.getNewCleanTarget(); boolean sameMethod = origin != null && origin.classNode().name.equals(dirtyTarget.classNode().name) && origin.methodNode().name.equals(dirtyTarget.methodNode().name) && origin.methodNode().desc.equals(dirtyTarget.methodNode().desc); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java index eb0186c2..1551e895 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java @@ -134,7 +134,7 @@ private static boolean selectsLocals(MixinContext context, Recipe recipe, Config if (insn == null) { return false; } - MixinBridge.Session session = MixinBridge.session(context.classNode().name, context.methodNode(), newTarget.classNode(), newTarget.methodNode(), context.environment().refmapHolder()); + MixinBridge.Session session = MixinBridge.session(context.classNode().name, context.methodNode(), newTarget.classNode(), newTarget.methodNode(), context.environment()); for (Parameter param : params) { for (Annotation annotation : param.annotations()) { if (annotation.getDesc().equals(MixinAnnotations.LOCAL) && session.findLocal(annotation.toAnnotationNode(), param.type(), insn) == -1) { From 0cd303968057c790198c6456a63bb2dd8c60a099 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Thu, 22 Oct 2026 16:49:56 +0200 Subject: [PATCH 28/44] Add support for WrapWithCondition Fixes Do a Barrel Roll compat --- .../model/constant/MixinAnnotations.java | 2 + .../adapter/model/param/HandlerSignature.java | 4 +- .../adapter/pipeline/HandlerValidator.java | 1 + .../pipeline/injector/HandlerSignatures.java | 1 + .../adapter/pipeline/injector/MixinTypes.java | 5 +- .../injector/WrapWithConditionMixin.java | 76 +++++++++++++++++++ .../mixin/DynamicMixinPatchTest.java | 10 +++ .../test/mc_26_1_2/mixin/CameraMixin.java | 19 +++++ 8 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin.java diff --git a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java index 06465f0e..9a09f69b 100644 --- a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java @@ -20,6 +20,8 @@ public class MixinAnnotations { public static final String WRAP_OPERATION = "Lcom/llamalad7/mixinextras/injector/wrapoperation/WrapOperation;"; public static final String WRAP_OPERATION_INTERNAL_NAME = "com/llamalad7/mixinextras/injector/wrapoperation/WrapOperation"; public static final String WRAP_WITH_CONDITION = "Lcom/llamalad7/mixinextras/injector/WrapWithCondition;"; + public static final String WRAP_WITH_CONDITION_INTERNAL_NAME = "com/llamalad7/mixinextras/injector/WrapWithCondition"; + public static final String WRAP_WITH_CONDITION_V2_INTERNAL_NAME = "com/llamalad7/mixinextras/injector/v2/WrapWithCondition"; public static final String LOCAL = "Lcom/llamalad7/mixinextras/sugar/Local;"; public static final String SHARE = "Lcom/llamalad7/mixinextras/sugar/Share;"; // Adapter custom mixin injectors diff --git a/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java b/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java index 16a655fc..0f02097e 100644 --- a/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java +++ b/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java @@ -32,7 +32,9 @@ public enum ValueSource { /// An argument of the call the `@At` selects, chosen by the injector CALL_ARG, /// A local variable of the target method, chosen by the injector - VARIABLE + VARIABLE, + /// Whether the wrapped instruction runs + CONDITION } public record Slot(ParamGroup group, ParamSource source) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java b/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java index b8c1656c..e9052f07 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java @@ -74,6 +74,7 @@ private static String validateSignature(MixinContext context, HandlerSignature s Type expectedReturn = switch (signature.returns()) { case VOID -> Type.VOID_TYPE; + case CONDITION -> Type.BOOLEAN_TYPE; case CALL_RETURN -> redirected != null ? redirected.result() : null; case TARGET_RETURN -> targetReturn; case CALL_ARG, VARIABLE -> null; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java index a027541a..b82dc8a9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java @@ -30,6 +30,7 @@ public final class HandlerSignatures { public static TxResult apply(MixinContext context, HandlerSignature signature, Configuration clean, MutableConfiguration dirty, Recipe recipe, @Nullable Type value) { Type valueType = switch (signature.returns()) { case VOID -> Type.VOID_TYPE; + case CONDITION -> Type.BOOLEAN_TYPE; case CALL_RETURN -> { RedirectedInsn redirected = isInvoke(dirty) ? null : findRedirected(context, recipe, dirty); MethodQualifier call = getCallQualifier(dirty); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java index 8943794e..43c07cdd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java @@ -14,7 +14,7 @@ public class MixinTypes { private static final Map MIXIN_TYPES = new HashMap<>(); // TODO: - // MixinConstants.MODIFY_ARGS, MixinConstants.MODIFY_CONST, MixinConstants.WRAP_WITH_CONDITION, + // MixinConstants.MODIFY_ARGS, MixinConstants.MODIFY_CONST, // MixinConstants.OVERWRITE (dont forget special modifyTarget handling) // MixinConstants.ACCESSOR public static final MixinType INJECT = new InjectMixin(); @@ -24,6 +24,7 @@ public class MixinTypes { public static final MixinType REDIRECT = new RedirectMixin(); public static final MixinType WRAP_OP = new WrapOperationMixin(); public static final MixinType MODIFY_EXPR_VAL = new ModifyExpressionValueMixin(); + public static final MixinType WRAP_WITH_CONDITION = new WrapWithConditionMixin(); static { registerMixinType(Inject.class, INJECT); @@ -33,6 +34,8 @@ public class MixinTypes { registerMixinType(Redirect.class, REDIRECT); registerMixinType(MixinAnnotations.WRAP_OPERATION_INTERNAL_NAME, WRAP_OP); registerMixinType(MixinAnnotations.MODIFY_EXPR_VAL_INTERNAL_NAME, MODIFY_EXPR_VAL); + registerMixinType(MixinAnnotations.WRAP_WITH_CONDITION_INTERNAL_NAME, WRAP_WITH_CONDITION); + registerMixinType(MixinAnnotations.WRAP_WITH_CONDITION_V2_INTERNAL_NAME, WRAP_WITH_CONDITION); } @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java new file mode 100644 index 00000000..d51c80b1 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java @@ -0,0 +1,76 @@ +package org.sinytra.adapter.pipeline.injector; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; +import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameter; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.model.param.RedirectedInsn; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.Resolvers; +import org.sinytra.adapter.util.AdapterUtil; + +import java.util.List; + +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.*; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.*; + +public class WrapWithConditionMixin extends AbstractMixinType { + private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_AT.extend() + .pluralKeys(MixinKeys.TARGET_METHOD, MixinKeys.TARGET_AT) + .build(); + + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.CONDITION, + slot(METHOD_PARAMS, CALL_OPERANDS), + slot(CAPTURED_PARAMS, CAPTURED_TARGET_ARGS), + slot(LOCALS, KEEP) + ); + + public WrapWithConditionMixin() { + super(TEMPLATE, MixinFlag.AT_TARGET_SENSITIVE, MixinFlag.ACCEPTS_INSTANCE); + } + + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + + @Override + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + TargetPair cleanTarget = clean.getTargetMethod() != null ? context.cleanMethods().resolve(clean.getTargetMethod()) : null; + AbstractInsnNode insn = cleanTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)) : null; + RedirectedInsn wrapped = insn != null ? RedirectedInsn.of(insn) : null; + List params = Parameters.parse(context.methodNode()); + if (wrapped == null || wrapped.operands().size() > params.size()) { + return TxResult.FAIL; + } + int operands = wrapped.operands().size(); + MethodParameters rest = MethodParameters.create(params.subList(operands, params.size()), List.of(CAPTURED_PARAMS, LOCALS)); + clean.setParameters(MethodParameters.builder() + .put(METHOD_PARAMS, params.subList(0, operands)) + .put(CAPTURED_PARAMS, rest.get(CAPTURED_PARAMS)) + .put(LOCALS, rest.get(LOCALS)) + .putRemaining(rest) + .build()); + return TxResult.SUCCESS; + } + + @Override + public TxResult postProcess(MixinContext context, Configuration clean, MutableConfiguration dirty, Recipe recipe) { + if (dirty.getTargetMethod() == null || dirty.getAtData() == null) return TxResult.FAIL; + + return HandlerSignatures.apply(context, SIGNATURE, clean, dirty, recipe, null); + } +} diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 0cab2200..2cc85830 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -104,6 +104,16 @@ void testModifyVariableCapturedTargetArgs() throws Exception { ); } + @Test + void testWrapWithConditionReplacedCall() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin", + "addRoll2", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin.java new file mode 100644 index 00000000..2cdb79b6 --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin.java @@ -0,0 +1,19 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import net.minecraft.client.Camera; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(Camera.class) +public class CameraMixin { + @WrapWithCondition(method = "alignWithEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 2)) + private boolean addRoll2(Camera instance, float yRot, float xRot) { + return true; + } + + @WrapWithCondition(method = "alignWithEntity(F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FFF)V", ordinal = 1)) + private boolean addRoll2Expected(Camera instance, float yRot, float xRot, float adapter_injected_3) { + return true; + } +} From 5202e80e314441a98843dfc3bfe8dba0495eab22 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 22:10:00 +0200 Subject: [PATCH 29/44] Cleanup code --- .../analysis/compare/InstructionMatcher.java | 4 -- .../analysis/injection/InjectionLocator.java | 9 ++-- .../analysis/injection/InjectionQuery.java | 23 +++----- .../analysis/locals/LocalsAnalyzer.java | 52 ++----------------- .../params/LayeredParamsDiffSnapshot.java | 19 ------- .../analysis/params/ParamDiffResolver.java | 2 - .../sinytra/adapter/api/DynamicPatches.java | 6 +-- .../param/InlineParameterTransformer.java | 52 ------------------- .../param/SubstituteParameterTransformer.java | 46 ---------------- .../adapter/model/MethodQualifier.java | 2 +- .../org/sinytra/adapter/model/TargetPair.java | 5 ++ .../adapter/model/ann/AnnotationHandle.java | 10 ---- .../model/ann/AnnotationValueHandle.java | 19 ------- .../org/sinytra/adapter/model/ann/AtData.java | 2 +- .../adapter/model/param/MethodParameters.java | 4 -- .../adapter/pipeline/AppliedLocalsCheck.java | 2 +- .../adapter/pipeline/MultiTargetSplitter.java | 2 +- .../adapter/pipeline/audit/MixinGroups.java | 2 +- .../adapter/pipeline/ctx/MixinContext.java | 4 -- .../pipeline/injector/InjectMixin.java | 6 +-- .../injector/ModifyExpressionValueMixin.java | 2 +- .../injector/ModifyVariableMixin.java | 4 +- .../pipeline/injector/RedirectMixin.java | 4 +- .../pipeline/injector/WrapOperationMixin.java | 2 +- .../adapter/pipeline/locate/Describer.java | 3 +- .../adapter/pipeline/locate/LocalLocator.java | 6 +-- .../adapter/pipeline/locate/PassedLocals.java | 6 +-- .../DivertRedirectProcessor.java | 3 +- .../processor/SugarLocalProcessor.java | 5 +- .../processor/{wrapop => }/WrapOpSurgeon.java | 2 +- .../processor/relocate/RelocationSolver.java | 2 +- .../signature/OperationCallStep.java | 2 +- .../pipeline/resolver/CompoundResolver.java | 5 -- .../InjectionPointResolver.java | 3 +- .../resolver/{special => }/LocalResolver.java | 3 +- .../adapter/pipeline/resolver/Resolvers.java | 2 - .../{special => }/SliceBoundaryResolver.java | 3 +- .../target/TargetMethodSubResolvers.java | 2 +- .../rewrite/InstanceofToConstant.java | 2 +- .../DynamicAnonClassIndexPatch.java | 2 +- .../DynamicAnonymousShadowFieldTypePatch.java | 2 +- .../LocalCaptureUpgradeTransformer.java | 4 +- .../types/FieldAccessorTypeTransformer.java | 2 +- .../org/sinytra/adapter/util/AdapterUtil.java | 20 ------- .../adapter/{mixin => util}/MixinBridge.java | 2 +- .../org/sinytra/adapter/util/OpcodeUtil.java | 4 -- 46 files changed, 55 insertions(+), 313 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/edit/param/InlineParameterTransformer.java delete mode 100644 core/src/main/java/org/sinytra/adapter/edit/param/SubstituteParameterTransformer.java rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{redirect => }/DivertRedirectProcessor.java (95%) rename core/src/main/java/org/sinytra/adapter/pipeline/processor/{wrapop => }/WrapOpSurgeon.java (99%) rename core/src/main/java/org/sinytra/adapter/pipeline/resolver/{injection => }/InjectionPointResolver.java (97%) rename core/src/main/java/org/sinytra/adapter/pipeline/resolver/{special => }/LocalResolver.java (95%) rename core/src/main/java/org/sinytra/adapter/pipeline/resolver/{special => }/SliceBoundaryResolver.java (97%) rename core/src/main/java/org/sinytra/adapter/pipeline/{cls => transform}/DynamicAnonClassIndexPatch.java (98%) rename core/src/main/java/org/sinytra/adapter/pipeline/{cls => transform}/DynamicAnonymousShadowFieldTypePatch.java (98%) rename core/src/main/java/org/sinytra/adapter/pipeline/{preprocess => transform}/LocalCaptureUpgradeTransformer.java (98%) rename core/src/main/java/org/sinytra/adapter/{mixin => util}/MixinBridge.java (99%) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/compare/InstructionMatcher.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/InstructionMatcher.java index 7f244ef0..f7c12033 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/compare/InstructionMatcher.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/InstructionMatcher.java @@ -7,10 +7,6 @@ import java.util.List; public record InstructionMatcher(AbstractInsnNode insn, List before, List after) { - public InstructionMatcher inverse() { - return new InstructionMatcher(insn, after.reversed(), before.reversed()); - } - public boolean test(InstructionMatcher other) { return test(other, 0); } diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java index f91f1b60..f6b90e15 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java @@ -5,11 +5,11 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.InsnList; -import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.SliceData; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.MixinBridge; import java.util.*; @@ -19,7 +19,7 @@ public final class InjectionLocator { private final MixinContext context; private final Map> cache; - private record CacheKey(TargetPair target, boolean ignoreOrdinal, boolean keepShift, boolean ignoreSlice, + private record CacheKey(TargetPair target, boolean keepShift, boolean ignoreSlice, @Nullable Object injectionPoint, @Nullable Object methodAnnotation ) { } @@ -43,7 +43,7 @@ public List find(@Nullable TargetPair target, InjectionQuery q return compute(target, query); } CacheKey key = new CacheKey( - target, query.ignoreOrdinal(), query.keepShift(), query.ignoreSlice(), + target, query.keepShift(), query.ignoreSlice(), annotationKey(this.context.injectionPointAnnotation()), annotationKey(this.context.methodAnnotation()) ); List insns = this.cache.get(key); @@ -96,9 +96,6 @@ private List compute(TargetPair target, InjectionQuery query) if (!query.keepShift()) { node.removeValues(AT_SHIFT); } - if (query.ignoreOrdinal()) { - node.removeValues(PROPERTY_ORDINAL); - } MixinBridge.Session session = session(target); AnnotationNode slice = query.ignoreSlice() ? null : getSlice(); diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java index 79743213..41738800 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java @@ -18,51 +18,45 @@ public enum Source { private final Source source; @Nullable private final AtData at; - private final boolean ignoreOrdinal; private final boolean keepShift; private final boolean ignoreSlice; - private InjectionQuery(Source source, @Nullable AtData at, boolean ignoreOrdinal, boolean keepShift, boolean ignoreSlice) { + private InjectionQuery(Source source, @Nullable AtData at, boolean keepShift, boolean ignoreSlice) { this.source = source; this.at = at; - this.ignoreOrdinal = ignoreOrdinal; this.keepShift = keepShift; this.ignoreSlice = ignoreSlice; } /// {@return a query for the mixin's own @At} public static InjectionQuery current() { - return new InjectionQuery(Source.CURRENT, null, false, false, false); + return new InjectionQuery(Source.CURRENT, null, false, false); } /// {@return a query for the given @At} public static InjectionQuery of(AtData at) { - return new InjectionQuery(Source.AT, at, false, false, false); + return new InjectionQuery(Source.AT, at, false, false); } /// {@return a query for an @At within the mixin's slice, including shift} public static InjectionQuery sliceBoundary(AtData at) { - return new InjectionQuery(Source.SLICE_BOUNDARY, at, false, true, true); + return new InjectionQuery(Source.SLICE_BOUNDARY, at, true, true); } /// {@return a query for the mixin's @Constant} public static InjectionQuery constant() { - return new InjectionQuery(Source.CONSTANT, null, false, false, false); + return new InjectionQuery(Source.CONSTANT, null, false, false); } - /// {@return this query ignoring the ordinal} - public InjectionQuery ignoringOrdinal() { - return new InjectionQuery(this.source, this.at, true, this.keepShift, this.ignoreSlice); - } /// {@return this query with the injection point's shift applied} public InjectionQuery keepingShift() { - return new InjectionQuery(this.source, this.at, this.ignoreOrdinal, true, this.ignoreSlice); + return new InjectionQuery(this.source, this.at, true, this.ignoreSlice); } /// {@return this query ignoring the mixin's slice} public InjectionQuery ignoringSlice() { - return new InjectionQuery(this.source, this.at, this.ignoreOrdinal, this.keepShift, true); + return new InjectionQuery(this.source, this.at, this.keepShift, true); } public Source source() { @@ -74,9 +68,6 @@ public AtData at() { return this.at; } - public boolean ignoreOrdinal() { - return this.ignoreOrdinal; - } public boolean keepShift() { return this.keepShift; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index 15ac7a97..71fcaf28 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -1,22 +1,16 @@ package org.sinytra.adapter.analysis.locals; import com.mojang.logging.LogUtils; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.InsnTraversal; import org.sinytra.adapter.analysis.compare.InstructionMatcher; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.analysis.params.ParamsEditDistance; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; import org.sinytra.adapter.analysis.params.ParamDiffResolver; +import org.sinytra.adapter.analysis.params.ParamsEditDistance; import org.sinytra.adapter.edit.param.TransformParameters; -import org.sinytra.adapter.mixin.MixinBridge; -import org.sinytra.adapter.mixin.MixinBridge.LocalCandidate; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.param.MethodParameters; @@ -24,6 +18,8 @@ import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; +import org.sinytra.adapter.util.MixinBridge; +import org.sinytra.adapter.util.MixinBridge.LocalCandidate; import org.sinytra.adapter.util.OpcodeUtil; import org.slf4j.Logger; @@ -154,38 +150,9 @@ private static InsnList collectInitializerInsns(MethodNode methodNode, int index return insns; } - public record CapturedLocalsUsage( - @Nullable LocalVariableLookup targetTable, - Int2ObjectMap usedLocals, - Int2IntMap usageCount, - Int2ObjectMap varInsnLists - ) { - } - public record CapturedLocalsTransform(Collection used, TransformParameters remover, Collection usedLocalNodes) { } - public CapturedLocalsUsage resolveUsage(CapturedLocals captured, CapturedLocalsTransform transform) { - LocalVariableLookup targetTable = LocalVariableLookup.of(captured.target().methodNode()); - Int2ObjectMap usedLocals = new Int2ObjectOpenHashMap<>(); - Int2ObjectMap varInsnLists = new Int2ObjectOpenHashMap<>(); - Int2IntMap usageCount = new Int2IntOpenHashMap(); - - // Captured locals are the locals available at the injection point, in order - List available = resolveAvailableLocals(captured.target()); - if (targetTable != null && available != null) { - transform.used().forEach(ordinal -> { - int localIndex = ordinal - captured.paramLocalStart(); - if (localIndex >= 0 && localIndex < available.size()) { - LocalVariable local = available.get(localIndex); - usedLocals.put((int) ordinal, local); - findVariableInitializerInsns(captured.target().methodNode(), captured.isStatic(), local.index(), varInsnLists, usageCount); - } - }); - } - return new CapturedLocalsUsage(targetTable, usedLocals, usageCount, varInsnLists); - } - public List findSelectableLocals(TargetPair target, AbstractInsnNode insn, Type type) { return MixinBridge.session(this.context.classNode().name, this.context.methodNode(), target.classNode(), target.methodNode(), this.context.environment()) .findLocalCandidates(type, insn, false); @@ -298,17 +265,4 @@ public static CapturedLocalsTransform analyzeCapturedLocals(CapturedLocals captu .build(); return new CapturedLocalsTransform(used, remover, usedLocalNodes); } - - public static void findVariableInitializerInsns(MethodNode methodNode, boolean isStatic, int index, Int2ObjectMap varInsnLists, Int2IntMap usageCount) { - InsnList insns = collectInitializerInsns(methodNode, index, load -> { - // TODO Handle method params - if (isStatic || load.var != 0) { - if (!varInsnLists.containsKey(load.var)) { - findVariableInitializerInsns(methodNode, isStatic, load.var, varInsnLists, usageCount); - } - usageCount.compute(load.var, (key, existing) -> existing == null ? 1 : existing + 1); - } - }); - varInsnLists.put(index, insns); - } } diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/LayeredParamsDiffSnapshot.java b/core/src/main/java/org/sinytra/adapter/analysis/params/LayeredParamsDiffSnapshot.java index 7e03b25b..08c91412 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/LayeredParamsDiffSnapshot.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/LayeredParamsDiffSnapshot.java @@ -2,20 +2,16 @@ import com.google.errorprone.annotations.CheckReturnValue; import org.objectweb.asm.Type; -import org.objectweb.asm.commons.InstructionAdapter; import org.sinytra.adapter.edit.param.InjectParameterTransform; -import org.sinytra.adapter.edit.param.InlineParameterTransformer; import org.sinytra.adapter.edit.param.MoveParametersTransformer; import org.sinytra.adapter.edit.param.ParameterTransformer; import org.sinytra.adapter.edit.param.RemoveParameterTransformer; import org.sinytra.adapter.edit.param.ReplaceParametersTransformer; -import org.sinytra.adapter.edit.param.SubstituteParameterTransformer; import org.sinytra.adapter.edit.param.SwapParametersTransformer; import org.sinytra.adapter.edit.param.TransformParameters; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; public record LayeredParamsDiffSnapshot(List modifications) { public static final LayeredParamsDiffSnapshot EMPTY = new LayeredParamsDiffSnapshot(List.of()); @@ -81,10 +77,6 @@ public Builder swap(int from, int to) { return add(new SwapParametersTransformer(from, to)); } - public Builder substitute(int target, int substitute) { - return add(new SubstituteParameterTransformer(target, substitute)); - } - public Builder move(int from, int to) { return add(new MoveParametersTransformer(from, to)); } @@ -94,10 +86,6 @@ public Builder remove(int index) { return add(new RemoveParameterTransformer(index, false)); } - public Builder inline(int target, Consumer adapter) { - return add(new InlineParameterTransformer(target, adapter)); - } - public Builder merge(LayeredParamsDiffSnapshot diff) { return merge(diff, 0); } @@ -107,13 +95,6 @@ public Builder merge(LayeredParamsDiffSnapshot diff, int indexOffset) { return this; } - public List getRemovals() { - return this.modifications.stream() - .filter(RemoveParameterTransformer.class::isInstance) - .map(m -> ((RemoveParameterTransformer) m).index()) - .toList(); - } - private Builder add(ParameterTransformer modification) { this.modifications.add(modification); return this; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java index 06898c55..43541445 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamDiffResolver.java @@ -80,8 +80,6 @@ case SwapParametersTransformer(int from, int to) -> { } case MoveParametersTransformer(int from, int to) -> state.add(to, state.remove(from)); case RemoveParameterTransformer remove -> state.remove(remove.index()); - case InlineParameterTransformer inline -> state.remove(inline.target()); - case SubstituteParameterTransformer substitute -> state.remove(substitute.target()); case null, default -> throw new RuntimeException("Unknown parameter modification: " + modification); } } diff --git a/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java b/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java index 0b7a0cdc..eacbb31a 100644 --- a/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java +++ b/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java @@ -3,9 +3,9 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import org.sinytra.adapter.pipeline.PipelineMethodTransformer; -import org.sinytra.adapter.pipeline.cls.DynamicAnonClassIndexPatch; -import org.sinytra.adapter.pipeline.cls.DynamicAnonymousShadowFieldTypePatch; -import org.sinytra.adapter.pipeline.preprocess.LocalCaptureUpgradeTransformer; +import org.sinytra.adapter.pipeline.transform.DynamicAnonClassIndexPatch; +import org.sinytra.adapter.pipeline.transform.DynamicAnonymousShadowFieldTypePatch; +import org.sinytra.adapter.pipeline.transform.LocalCaptureUpgradeTransformer; import org.sinytra.adapter.types.FieldAccessorTypeTransformer; import org.sinytra.adapter.types.FieldTypeUsageTransformer; diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/InlineParameterTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/InlineParameterTransformer.java deleted file mode 100644 index a38948c8..00000000 --- a/core/src/main/java/org/sinytra/adapter/edit/param/InlineParameterTransformer.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.sinytra.adapter.edit.param; - -import com.mojang.logging.LogUtils; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.InstructionAdapter; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.api.PatchResult; -import org.sinytra.adapter.edit.LVTSnapshot; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.util.AdapterUtil; -import org.slf4j.Logger; - -import java.util.Comparator; -import java.util.List; -import java.util.function.Consumer; - -import static org.sinytra.adapter.util.AdapterUtil.MIXINPATCH; - -public record InlineParameterTransformer(int target, Consumer adapter) implements ParameterTransformer { - private static final Logger LOGGER = LogUtils.getLogger(); - - @Override - public InlineParameterTransformer offset(int offset) { - return new InlineParameterTransformer(this.target + offset, this.adapter); - } - - @Override - public PatchResult apply(ClassNode classNode, MethodNode methodNode, MixinContext context, List parameters, int offset) { - final int index = this.target + offset; - LOGGER.info(MIXINPATCH, "Inlining parameter {} of method {}.{}", index, classNode.name, methodNode.name); - final int replaceIndex = -999 + index; - - LVTSnapshot.with(methodNode, () -> { - methodNode.localVariables.sort(Comparator.comparingInt(lvn -> lvn.index)); - LocalVariableNode lvn = methodNode.localVariables.remove(index + (context.isStatic() ? 0 : 1)); - AdapterUtil.replaceLVT(methodNode, idx -> idx == lvn.index ? replaceIndex : idx); - }); - - ParamTransformationUtil.updateParameterAnnotationArrays(methodNode, parameters.size(), annotations -> annotations.remove(index)); - parameters.remove(index); - - for (AbstractInsnNode insn : methodNode.instructions) { - if (insn instanceof VarInsnNode varInsn && varInsn.var == replaceIndex) { - InsnList replacementInsns = AdapterUtil.insnsWithAdapter(adapter); - methodNode.instructions.insert(varInsn, replacementInsns); - methodNode.instructions.remove(varInsn); - } - } - - return PatchResult.COMPUTE_FRAMES; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/edit/param/SubstituteParameterTransformer.java b/core/src/main/java/org/sinytra/adapter/edit/param/SubstituteParameterTransformer.java deleted file mode 100644 index 5164cbd6..00000000 --- a/core/src/main/java/org/sinytra/adapter/edit/param/SubstituteParameterTransformer.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.sinytra.adapter.edit.param; - -import com.mojang.logging.LogUtils; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.api.PatchResult; -import org.sinytra.adapter.edit.LVTSnapshot; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.util.AdapterUtil; -import org.slf4j.Logger; - -import java.util.List; - -public record SubstituteParameterTransformer(int target, int substitute) implements ParameterTransformer { - private static final Logger LOGGER = LogUtils.getLogger(); - - @Override - public SubstituteParameterTransformer offset(int offset) { - return new SubstituteParameterTransformer(this.target + offset, this.substitute + offset); - } - - @Override - public PatchResult apply(ClassNode classNode, MethodNode methodNode, MixinContext context, List parameters, int offset) { - int paramIndex = this.target + offset; - int substituteParamIndex = this.substitute + offset; - boolean isNonStatic = !context.isStatic(); - if (paramIndex >= parameters.size() || substituteParamIndex >= parameters.size() || paramIndex == substituteParamIndex) { - return PatchResult.PASS; - } - - int localIndex = ParamTransformationUtil.getLVTIndex(parameters, isNonStatic, paramIndex); - int substituteIndex = ParamTransformationUtil.getLVTIndex(parameters, isNonStatic, substituteParamIndex); - - LVTSnapshot.with(methodNode, () -> { - LOGGER.info("Substituting parameter {} for {} in {}.{}", paramIndex, substituteParamIndex, classNode.name, methodNode.name); - ParamTransformationUtil.updateParameterAnnotationArrays(methodNode, parameters.size(), annotations -> annotations.remove(paramIndex)); - parameters.remove(paramIndex); - methodNode.localVariables.removeIf(lvn -> lvn.index == localIndex); - - AdapterUtil.replaceLVT(methodNode, idx -> idx == localIndex ? substituteIndex : idx); - }); - - return PatchResult.COMPUTE_FRAMES; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java b/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java index c0433df1..d43b5bb9 100644 --- a/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java +++ b/core/src/main/java/org/sinytra/adapter/model/MethodQualifier.java @@ -5,7 +5,7 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.mixin.MixinBridge; +import org.sinytra.adapter.util.MixinBridge; import java.util.Optional; diff --git a/core/src/main/java/org/sinytra/adapter/model/TargetPair.java b/core/src/main/java/org/sinytra/adapter/model/TargetPair.java index e9de9b91..c08c0198 100644 --- a/core/src/main/java/org/sinytra/adapter/model/TargetPair.java +++ b/core/src/main/java/org/sinytra/adapter/model/TargetPair.java @@ -4,4 +4,9 @@ import org.objectweb.asm.tree.MethodNode; public record TargetPair(ClassNode classNode, MethodNode methodNode) { + /// {@return whether both name the same method of the same class, which may come from different class lookups} + public boolean isSameMethod(TargetPair other) { + return this.classNode.name.equals(other.classNode.name) && this.methodNode.name.equals(other.methodNode.name) + && this.methodNode.desc.equals(other.methodNode.desc); + } } diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationHandle.java b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationHandle.java index 19e1aa7c..786d3651 100644 --- a/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationHandle.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationHandle.java @@ -32,16 +32,6 @@ public AnnotationHandle copy() { return new AnnotationHandle(copy); } - public AnnotationHandle getNestedOrAppend(String key, String desc) { - AnnotationHandle existing = getNested(key).orElse(null); - if (existing != null) { - return existing; - } - AnnotationNode node = new AnnotationNode(desc); - appendValue(key, node); - return getNested(key).orElseThrow(); - } - public Optional getNested(String key) { return getValue(key) .flatMap(AnnotationValueHandle::maybeUnwrap) diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationValueHandle.java b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationValueHandle.java index c9403abf..b53c0ef7 100644 --- a/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationValueHandle.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AnnotationValueHandle.java @@ -62,25 +62,6 @@ public void set(T value) { this.origin.set(index, value); } - public Optional findNested(String name) { - assertValid(); - Object value = unwrap(); - if (value instanceof AnnotationNode annotationNode) { - return create(annotationNode, name).map(h -> h.unwrap() instanceof AnnotationNode ann ? new AnnotationHandle(ann) : null); - } - throw new IllegalArgumentException("Expected value to be an AnnotationNode, was " + value.getClass()); - } - - public void refresh(AnnotationNode annotationNode) { - AnnotationValueHandle.create(annotationNode, this.key).ifPresentOrElse( - v -> { - this.origin = v.origin; - this.index = v.index; - }, - () -> this.invalid = true - ); - } - private void assertValid() { if (this.invalid) { throw new RuntimeException("Annotation value handle for " + this.key + " is no longer valid."); diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java index d793bcd1..646a9b5b 100644 --- a/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java @@ -2,13 +2,13 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.MethodInsnNode; -import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.config.MutablePropertyContainer; import org.sinytra.adapter.model.config.PropertyContainer; import org.sinytra.adapter.model.config.PropertyContainerTemplate; import org.sinytra.adapter.model.config.PropertyKey; import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.util.MixinBridge; import org.spongepowered.asm.mixin.injection.At; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/model/param/MethodParameters.java b/core/src/main/java/org/sinytra/adapter/model/param/MethodParameters.java index 52fc63f6..a049cb1a 100644 --- a/core/src/main/java/org/sinytra/adapter/model/param/MethodParameters.java +++ b/core/src/main/java/org/sinytra/adapter/model/param/MethodParameters.java @@ -76,10 +76,6 @@ public void set(ParamGroup group, Parameter param) { set(group, List.of(param)); } - public void setTypes(ParamGroup group, List params) { - set(group, params.stream().map(Parameter::simple).toList()); - } - public void set(ParamGroup group, List params) { if (!this.groups.containsKey(group)) { throw new IllegalArgumentException("Group %s is not available".formatted(group.name())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java b/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java index b033b67a..c1899d5b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java @@ -4,13 +4,13 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.injector.MixinFlag; import org.sinytra.adapter.pipeline.locate.LocalLocator; import org.sinytra.adapter.pipeline.processor.SugarLocalProcessor; import org.sinytra.adapter.util.AdapterUtil; +import org.sinytra.adapter.util.MixinBridge; public final class AppliedLocalsCheck { public static void check(Object actor, MixinContext context, TargetPair cleanTarget, TargetPair dirtyTarget) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java b/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java index bc872fc3..c06f6b26 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/MultiTargetSplitter.java @@ -5,13 +5,13 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.api.PatchEnvironment; -import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.ClassTarget; import org.sinytra.adapter.model.config.key.ControlKeys; import org.sinytra.adapter.model.config.key.MixinKeys; import org.sinytra.adapter.util.AdapterUtil; +import org.sinytra.adapter.util.MixinBridge; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java b/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java index 87c43108..f61f2f73 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/audit/MixinGroups.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.mixin.MixinBridge; +import org.sinytra.adapter.util.MixinBridge; import java.util.Collection; import java.util.HashMap; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java index a67ea1ac..65331cc5 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/ctx/MixinContext.java @@ -160,10 +160,6 @@ public Targets targets(MethodQualifier qualifier) { return clean != null && dirty != null ? new Targets(clean, dirty) : null; } - public ClassLookup cleanLookup() { - return this.patchContext.environment().cleanClassLookup(); - } - public ClassLookup dirtyLookup() { return this.patchContext.environment().dirtyClassLookup(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index ea15f392..245a43ad 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -16,11 +16,11 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processors; -import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; import org.sinytra.adapter.pipeline.locate.NearestCallLocator; -import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; +import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.Resolvers; import java.util.List; import java.util.Objects; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java index ea1db61f..c273b057 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java @@ -12,8 +12,8 @@ import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import org.sinytra.adapter.pipeline.rewrite.InstanceofToModifyInstanceofValue; import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index d7714aec..fd55df1c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -18,9 +18,9 @@ import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; +import org.sinytra.adapter.pipeline.resolver.LocalResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.pipeline.resolver.special.LocalResolver; import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; import org.sinytra.adapter.pipeline.rewrite.ShiftedVariableToExpression; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index 36cd2ec4..019fd78d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -20,11 +20,11 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.processor.DivertRedirectProcessor; import org.sinytra.adapter.pipeline.processor.Processors; -import org.sinytra.adapter.pipeline.processor.redirect.DivertRedirectProcessor; import org.sinytra.adapter.pipeline.processor.signature.SignatureProcessor; +import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import org.sinytra.adapter.pipeline.rewrite.InstanceofMakesRedirectRedundant; import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; import org.sinytra.adapter.util.AdapterUtil; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index 0e288b95..a598f9d0 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -13,10 +13,10 @@ import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.rewrite.InstanceofToConstant; import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; -import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java index 6e61160d..8ff3f98d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -41,8 +41,7 @@ public final class Describer { public static Configuration describe(MixinContext context, Recipe recipe, Candidate candidate) { AtData cleanAt = recipe.clean().getAtData(); TargetPair origin = recipe.getDirtyTarget(); - boolean otherMethod = origin == null || !candidate.target().classNode().name.equals(origin.classNode().name) - || !candidate.target().methodNode().name.equals(origin.methodNode().name) || !candidate.target().methodNode().desc.equals(origin.methodNode().desc); + boolean otherMethod = origin == null || !candidate.target().isSameMethod(origin); Function query = otherMethod ? at -> InjectionQuery.of(at).ignoringSlice() : InjectionQuery::of; AtData described = cleanAt != null ? describeAt(context, cleanAt, candidate, query) : null; if (described == null) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocalLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocalLocator.java index b0a4a893..b53c803a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocalLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocalLocator.java @@ -25,7 +25,7 @@ public static int resolve(MixinContext context, TargetPair cleanTarget, @Nullabl && Describer.isParameter(cleanTarget.methodNode(), cleanSlot)) { originSlot = cleanSlot; } - if (origin != null && isSame(origin, dirtyTarget)) { + if (origin != null && origin.isSameMethod(dirtyTarget)) { return originSlot; } if (originSlot != -1) { @@ -46,10 +46,6 @@ public static int resolve(MixinContext context, TargetPair cleanTarget, @Nullabl .orElse(-1); } - private static boolean isSame(TargetPair a, TargetPair b) { - return a.classNode().name.equals(b.classNode().name) && a.methodNode().name.equals(b.methodNode().name) - && a.methodNode().desc.equals(b.methodNode().desc); - } private LocalLocator() { } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/PassedLocals.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/PassedLocals.java index 5b40b50e..69ce201d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/PassedLocals.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/PassedLocals.java @@ -32,7 +32,7 @@ public static int resolve(MixinContext context, TargetPair caller, int slot, Tar List next = new ArrayList<>(); for (Step step : frontier) { for (Step passed : findPassed(context, step)) { - if (isSame(passed.method(), callee)) { + if (passed.method().isSameMethod(callee)) { return passed.slot(); } if (visited.add(passed.method().methodNode())) { @@ -94,10 +94,6 @@ private static boolean isTrackedLoad(SourceValue value, Set tracked) { && OpcodeUtil.isLoadOpcode(load.getOpcode()) && tracked.contains(load.var); } - private static boolean isSame(TargetPair a, TargetPair b) { - return a.classNode().name.equals(b.classNode().name) && a.methodNode().name.equals(b.methodNode().name) - && a.methodNode().desc.equals(b.methodNode().desc); - } private PassedLocals() { } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/DivertRedirectProcessor.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/DivertRedirectProcessor.java index 0d0f6179..a9e1c1fd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/redirect/DivertRedirectProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/DivertRedirectProcessor.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.processor.redirect; +package org.sinytra.adapter.pipeline.processor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.InstructionAdapter; @@ -11,7 +11,6 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processor; import java.util.function.Consumer; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java index 4de60150..44983efe 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java @@ -10,7 +10,6 @@ import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.method.InheritanceHandler; import org.sinytra.adapter.edit.param.ParamTransformationUtil; -import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; @@ -24,6 +23,7 @@ import org.sinytra.adapter.pipeline.locate.Describer; import org.sinytra.adapter.pipeline.locate.LocalLocator; import org.sinytra.adapter.util.AdapterUtil; +import org.sinytra.adapter.util.MixinBridge; import java.util.ArrayList; import java.util.List; @@ -59,8 +59,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe MixinBridge.Session cleanSession = MixinBridge.session(mixin, cleanHandler, cleanTarget.classNode(), cleanTarget.methodNode(), context.environment()); MixinBridge.Session dirtySession = MixinBridge.session(mixin, handler, dirtyTarget.classNode(), dirtyTarget.methodNode(), context.environment()); TargetPair origin = recipe.getNewCleanTarget(); - boolean sameMethod = origin != null && origin.classNode().name.equals(dirtyTarget.classNode().name) - && origin.methodNode().name.equals(dirtyTarget.methodNode().name) && origin.methodNode().desc.equals(dirtyTarget.methodNode().desc); + boolean sameMethod = origin != null && origin.isSameMethod(dirtyTarget); boolean changed = false; for (int i = 0; i < locals.size(); i++) { int cleanIndex = cleanLocals.get(i); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/WrapOpSurgeon.java similarity index 99% rename from core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/WrapOpSurgeon.java index 207a564a..aebdeee7 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/wrapop/WrapOpSurgeon.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/WrapOpSurgeon.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.processor.wrapop; +package org.sinytra.adapter.pipeline.processor; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java index 1551e895..bae85792 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java @@ -5,7 +5,6 @@ import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.TargetPair; @@ -20,6 +19,7 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; +import org.sinytra.adapter.util.MixinBridge; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java index 12f60915..c0da0544 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java @@ -12,7 +12,7 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.wrapop.WrapOpSurgeon; +import org.sinytra.adapter.pipeline.processor.WrapOpSurgeon; import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java index db07a380..75f32842 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java @@ -11,11 +11,6 @@ public abstract class CompoundResolver implements Resolver { protected final List subResolvers = new ArrayList<>(); - public CompoundResolver addSubResolverFirst(SubResolver subResolver) { - this.subResolvers.addFirst(subResolver); - return this; - } - public CompoundResolver addSubResolver(SubResolver subResolver) { this.subResolvers.add(subResolver); return this; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java index 9d8bce07..1d71ec01 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.resolver.injection; +package org.sinytra.adapter.pipeline.resolver; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; @@ -17,7 +17,6 @@ import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; -import org.sinytra.adapter.pipeline.resolver.CompoundResolver; import org.sinytra.adapter.util.AdapterUtil; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/LocalResolver.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/LocalResolver.java index 88257e29..ebaed16d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/LocalResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/LocalResolver.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.resolver.special; +package org.sinytra.adapter.pipeline.resolver; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; @@ -12,7 +12,6 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.locate.Describer; import org.sinytra.adapter.pipeline.locate.LocalLocator; -import org.sinytra.adapter.pipeline.resolver.Resolver; import org.sinytra.adapter.util.AdapterUtil; public final class LocalResolver implements Resolver { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java index 6263c5cc..e5a91580 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java @@ -1,7 +1,5 @@ package org.sinytra.adapter.pipeline.resolver; -import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; -import org.sinytra.adapter.pipeline.resolver.special.SliceBoundaryResolver; import org.sinytra.adapter.pipeline.resolver.target.TargetMethodResolver; import org.sinytra.adapter.util.OrderedRegistry; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java index 622409f0..425df26d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/special/SliceBoundaryResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.resolver.special; +package org.sinytra.adapter.pipeline.resolver; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; @@ -15,7 +15,6 @@ import org.sinytra.adapter.pipeline.locate.Locator; import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; -import org.sinytra.adapter.pipeline.resolver.Resolver; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java index b7cb39c7..5bdf8b0e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java @@ -12,9 +12,9 @@ import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolver; import org.sinytra.adapter.pipeline.resolver.SubResolver; -import org.sinytra.adapter.pipeline.resolver.injection.InjectionPointResolver; import java.util.Comparator; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java index 6812a433..a5f0e2ab 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java @@ -17,7 +17,7 @@ import org.sinytra.adapter.model.param.Parameter; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.wrapop.WrapOpSurgeon; +import org.sinytra.adapter.pipeline.processor.WrapOpSurgeon; import java.util.List; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java b/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonClassIndexPatch.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java rename to core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonClassIndexPatch.java index 438bb84e..7b5d075e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonClassIndexPatch.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonClassIndexPatch.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.cls; +package org.sinytra.adapter.pipeline.transform; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java b/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonymousShadowFieldTypePatch.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java rename to core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonymousShadowFieldTypePatch.java index 6732111b..410d02e8 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/cls/DynamicAnonymousShadowFieldTypePatch.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonymousShadowFieldTypePatch.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.cls; +package org.sinytra.adapter.pipeline.transform; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/transform/LocalCaptureUpgradeTransformer.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java rename to core/src/main/java/org/sinytra/adapter/pipeline/transform/LocalCaptureUpgradeTransformer.java index 42ef66cb..528c45bd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/preprocess/LocalCaptureUpgradeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/transform/LocalCaptureUpgradeTransformer.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.preprocess; +package org.sinytra.adapter.pipeline.transform; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; @@ -12,13 +12,13 @@ import org.sinytra.adapter.api.MethodPatchContext; import org.sinytra.adapter.api.MethodTransformer; import org.sinytra.adapter.api.PatchResult; -import org.sinytra.adapter.mixin.MixinBridge.LocalCandidate; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.key.MixinKeys; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.MixinBridge.LocalCandidate; import java.util.*; diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java index d462b5fd..72b15df5 100644 --- a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java @@ -14,11 +14,11 @@ import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.api.TypeAdapter; -import org.sinytra.adapter.mixin.MixinBridge; import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.AnnotationValueHandle; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.util.MixinBridge; import org.sinytra.adapter.util.OpcodeUtil; import java.util.Optional; diff --git a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java index 1685c593..ffd6b341 100644 --- a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java +++ b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java @@ -17,7 +17,6 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; -import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Predicate; @@ -73,11 +72,6 @@ public static boolean canHandleLocalVarInsnValue(AbstractInsnNode insn) { return insn instanceof VarInsnNode || insn instanceof IincInsnNode; } - public static int getInsnIntConstValue(AbstractInsnNode insn) { - return getIntConstValue(insn) - .orElseThrow(() -> new IllegalArgumentException("Not an int constant opcode: " + insn.getOpcode())); - } - public static OptionalInt getIntConstValue(AbstractInsnNode insn) { int opcode = insn.getOpcode(); if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) { @@ -192,20 +186,6 @@ public static Optional findOne(Collection candidates, Predicate boolean allElementsEqual(Collection list, BiPredicate equalityFn) { - Iterator iterator = list.iterator(); - if (!iterator.hasNext()) { - return true; - } - T first = iterator.next(); - while (iterator.hasNext()) { - if (!equalityFn.test(first, iterator.next())) { - return false; - } - } - return true; - } - public static void replaceRangeInclusive(InsnList list, AbstractInsnNode start, AbstractInsnNode end, List replacement) { InsnList newInsns = AdapterUtil.insnList(replacement); list.insertBefore(start, newInsns); diff --git a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java b/core/src/main/java/org/sinytra/adapter/util/MixinBridge.java similarity index 99% rename from core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java rename to core/src/main/java/org/sinytra/adapter/util/MixinBridge.java index a33d3684..fb35eb2e 100644 --- a/core/src/main/java/org/sinytra/adapter/mixin/MixinBridge.java +++ b/core/src/main/java/org/sinytra/adapter/util/MixinBridge.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.mixin; +package org.sinytra.adapter.util; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; diff --git a/core/src/main/java/org/sinytra/adapter/util/OpcodeUtil.java b/core/src/main/java/org/sinytra/adapter/util/OpcodeUtil.java index 99055010..f54a2298 100644 --- a/core/src/main/java/org/sinytra/adapter/util/OpcodeUtil.java +++ b/core/src/main/java/org/sinytra/adapter/util/OpcodeUtil.java @@ -22,10 +22,6 @@ public static int getLoadOpcode(Type type) { return type.getOpcode(Opcodes.ILOAD); } - public static int getStoreOpcode(Type type) { - return type.getOpcode(Opcodes.ISTORE); - } - public static int getReturnOpcode(MethodNode methodNode) { return getReturnOpcode(Type.getReturnType(methodNode.desc)); } From 9b725a91409b3224204b1494f30cb140b81df6bc Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 22:54:33 +0200 Subject: [PATCH 30/44] Replace target method subresolvers with Origin Locator --- .../analysis/compare/MethodAlignment.java | 5 + .../analysis/method/MethodLocator.java | 5 + .../adapter/model/config/key/IntentKeys.java | 3 - .../pipeline/locate/OriginLocator.java | 53 +++++++ .../processor/SplitCancellationProcessor.java | 3 +- .../adapter/pipeline/resolver/Resolvers.java | 1 - .../resolver/TargetMethodResolver.java | 36 +++++ .../target/SplitTargetMethodSubResolver.java | 134 ----------------- .../resolver/target/TargetMethodResolver.java | 40 ----- .../target/TargetMethodSubResolvers.java | 138 ------------------ 10 files changed, 100 insertions(+), 318 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java index 40fabde7..617808a1 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java @@ -55,6 +55,11 @@ public static MethodAlignment align(MethodNode cleanMethod, MethodNode dirtyMeth return alignment; } + public double similarity() { + int total = this.clean.size() + this.dirty.size(); + return total == 0 ? 1 : 2.0 * this.pairings.size() / total; + } + public List find(AbstractInsnNode cleanInsn) { Integer index = indexOf(cleanInsn); if (index == null) { diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java index 34ed104f..c0663870 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodLocator.java @@ -71,6 +71,11 @@ public List findByName(MethodQualifier qualifier) { return pair != null ? pair.getSecond() : List.of(); } + public List findTargetsByName(MethodQualifier qualifier) { + Pair> pair = findCandidates(qualifier, true, true); + return pair != null ? pair.getSecond().stream().map(method -> new TargetPair(pair.getFirst(), method)).toList() : List.of(); + } + @Nullable private TargetPair resolveSingle(MethodQualifier qualifier, boolean useDefaultOwner) { Pair> pair = findCandidates(qualifier, useDefaultOwner, false); diff --git a/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java index 13c1b879..058c1013 100644 --- a/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java @@ -10,9 +10,6 @@ public final class IntentKeys { /// The mixin moves to the dirty target class public static final PropertyKey RELOCATION = PropertyKey.create("relocation"); - /// The target method was split, so cancelling in one of them must also apply to the rest - public static final PropertyKey PROPAGATE_CANCELLATION = PropertyKey.create("propagate_cancellation"); - /// After each redirected call in the handler, run this code instead of the rest of the original statement public static final PropertyKey> DIVERT_REDIRECT = PropertyKey.create("divert_redirect"); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java new file mode 100644 index 00000000..3c6f1e68 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java @@ -0,0 +1,53 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.param.Parameters; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.Comparator; +import java.util.List; + +public final class OriginLocator { + public record Origin(TargetPair method, boolean matches, double similarity, boolean deprecated) { + } + + private static final Comparator RANKING = Comparator.comparing(Origin::matches).reversed() + .thenComparing(Comparator.comparingDouble(Origin::similarity).reversed()) + .thenComparing(Origin::deprecated) + .thenComparing(Comparator.comparingInt(o -> Parameters.getParameterTypes(o.method().methodNode().desc).size()).reversed()); + + public static List locate(MixinContext context, TargetPair cleanTarget, String targetClass, MethodQualifier cleanQualifier) { + List candidates; + if (MethodAnalyzer.isLambda(cleanQualifier)) { + ClassNode dirtyClass = context.dirtyLookup().getClass(targetClass).orElse(null); + candidates = dirtyClass == null ? List.of() : dirtyClass.methods.stream() + .filter(method -> MethodAnalyzer.isLambda(method) && method.desc.equals(cleanTarget.methodNode().desc)) + .map(method -> new TargetPair(dirtyClass, method)) + .toList(); + } else { + candidates = context.dirtyMethods().findTargetsByName(cleanQualifier); + } + return candidates.stream() + .map(candidate -> new Origin( + candidate, + context.injections().matches(candidate), + MethodAlignment.align(cleanTarget.methodNode(), candidate.methodNode()).similarity(), + isDirtyDeprecated(context, candidate.methodNode()) + )) + .sorted(RANKING) + .toList(); + } + + private static boolean isDirtyDeprecated(MixinContext context, MethodNode dirty) { + TargetPair clean = context.cleanMethods().resolve(MethodQualifier.create(dirty)); + return clean != null ? MethodAnalyzer.isDirtyDeprecatedMethod(clean.methodNode(), dirty) : MethodAnalyzer.isDeprecated(dirty); + } + + private OriginLocator() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java index 76135fa5..0c61c340 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java @@ -6,7 +6,6 @@ import org.sinytra.adapter.edit.RelocationGenerator; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -17,7 +16,7 @@ public class SplitCancellationProcessor implements Processor { @Override public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { - if (!dirty.getProperty(IntentKeys.PROPAGATE_CANCELLATION).orElse(false)) { + if (!recipe.clean().isCancellable()) { return TxResult.PASS; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java index e5a91580..a39acb76 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/Resolvers.java @@ -1,6 +1,5 @@ package org.sinytra.adapter.pipeline.resolver; -import org.sinytra.adapter.pipeline.resolver.target.TargetMethodResolver; import org.sinytra.adapter.util.OrderedRegistry; public class Resolvers extends OrderedRegistry { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java new file mode 100644 index 00000000..8b51989c --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java @@ -0,0 +1,36 @@ +package org.sinytra.adapter.pipeline.resolver; + +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.locate.OriginLocator; + +import java.util.List; + +public class TargetMethodResolver implements Resolver { + @Override + public ResolutionResult resolve(MixinContext context, Recipe recipe) { + if (recipe.dirty().getTargetMethod() != null) { + return ResolutionResult.pass(); + } + TargetPair same = recipe.getNewCleanTarget(); + if (same != null && context.injections().matches(same)) { + return ResolutionResult.success(recipe.dirty().copyClean().inheritTargetMethod()); + } + + TargetPair cleanTarget = recipe.getCleanTarget(); + MethodQualifier cleanQualifier = recipe.clean().getTargetMethod(); + List origins = cleanTarget != null && cleanQualifier != null + ? OriginLocator.locate(context, cleanTarget, recipe.clean().getTargetClass(), cleanQualifier) + : List.of(); + OriginLocator.Origin origin = origins.isEmpty() ? null : origins.getFirst(); + if (origin == null || same != null && origin.method().isSameMethod(same)) { + return same != null ? ResolutionResult.success(recipe.dirty().copyClean().inheritTargetMethod()) : ResolutionResult.fail(); + } + context.recordCtxAudit("Located target method %s (similarity %.2f%s)", MethodQualifier.create(origin.method().methodNode()).asDescriptor(), + origin.similarity(), origin.matches() ? ", injection point matches" : ""); + return ResolutionResult.success(MutableConfiguration.create().setTargetMethod(origin.method().methodNode())); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java deleted file mode 100644 index d0cc521d..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/SplitTargetMethodSubResolver.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.target; - -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.compare.InsnComparator; -import org.sinytra.adapter.analysis.compare.InstructionMatcher; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; -import org.sinytra.adapter.analysis.method.MethodInsnMatcher; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.MutableConfiguration; -import org.sinytra.adapter.model.config.key.IntentKeys; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.SubResolver; - -import java.util.List; -import java.util.Objects; - -/** - * Handle cases where a single method is split into multiple smaller pieces. - * For an example, see net.minecraft.client.gui.Gui#renderPlayerHealth - */ -public class SplitTargetMethodSubResolver implements SubResolver { - @Override - public Configuration resolve(MixinContext context, Recipe recipe) { - TargetPair cleanTarget = recipe.getCleanTarget(); - TargetPair dirtyTarget = recipe.getNewCleanTarget(); - if (cleanTarget == null || dirtyTarget == null) return null; - - List candidates = disambiguate(locateCandidates(context, cleanTarget, dirtyTarget), context, cleanTarget); - - if (candidates.size() == 1) { - MethodNode method = candidates.getFirst().method(); - context.recordCtxAudit("Adjusting split method target to %s", MethodQualifier.create(method).asDescriptor()); - - MutableConfiguration config = MutableConfiguration.create() - .setTargetMethod(method); - if (recipe.clean().isCancellable()) { - config.setProperty(IntentKeys.PROPAGATE_CANCELLATION, true); - } - return config; - } - - return null; - } - - private static List locateCandidates(MixinContext context, TargetPair cleanTarget, TargetPair dirtyTarget) { - MethodNode cleanTargetMethod = cleanTarget.methodNode(); - ClassNode dirtyTargetClass = dirtyTarget.classNode(); - MethodNode dirtyTargetMethod = dirtyTarget.methodNode(); - - // Check that a Deprecated annotation was added to the dirty method - if (!MethodAnalyzer.isDirtyDeprecatedMethod(cleanTargetMethod, dirtyTargetMethod)) { - return tryFindPartialCandidates(cleanTargetMethod, dirtyTargetClass, dirtyTargetMethod, context); - } - - List invocations = context.dataFlow(dirtyTarget).findTopLevelOwnCalls(); - if (invocations == null) { - return List.of(); - } - - List candidates = findInsnsCalls(invocations.stream() - .map(m -> new TargetPair(dirtyTargetClass, m)).toList(), context); - - // Attempt to find matching insns in lambdas - if (candidates.isEmpty()) { - List nestedLambdas = invocations.stream() - .flatMap(m -> MethodAnalyzer.findOwnIndyHandles(dirtyTargetClass, m).stream()) - .map(IndyHandle::method) - .filter(MethodAnalyzer::isLambda) - .map(m -> new TargetPair(dirtyTargetClass, m)) - .toList(); - return findInsnsCalls(nestedLambdas, context); - } - - return candidates; - } - - // Handle cases where only part of the method is moved away - private static List tryFindPartialCandidates(MethodNode cleanTargetMethod, ClassNode dirtyTargetClass, MethodNode dirtyTargetMethod, MixinContext context) { - List dirtyOnlyCalls = MethodAnalyzer.findDirtyOnlyCalls(cleanTargetMethod, dirtyTargetMethod, false).stream() - .filter(i -> i.owner.equals(dirtyTargetClass.name)) - .map(i -> context.dirtyMethods().resolve(MethodQualifier.create(i))) - .filter(Objects::nonNull) - .toList(); - - return findInsnsCalls(dirtyOnlyCalls, context); - } - - // If multiple candidates have been found, try comparing the surrounding method instructions to find one match - private static List disambiguate(List candidates, MixinContext context, TargetPair cleanTarget) { - if (candidates.size() <= 1) { - return candidates; - } - - List cleanInsns = context.injections().find(cleanTarget); - if (cleanInsns.size() != 1) { - return candidates; - } - - InstructionMatcher cleanMatcher = MethodInsnMatcher.findSurroundingInstructions(cleanInsns.getFirst(), 5); - - List matchingCandidates = candidates.stream() - .filter(method -> method.insns().size() == 1) - .filter(method -> { - InstructionMatcher matcher = MethodInsnMatcher.findSurroundingInstructions(method.insns().getFirst(), 5); - return cleanMatcher.test(matcher, InsnComparator.IGNORE_VAR_INDEX); - }) - .toList(); - - if (matchingCandidates.size() == 1) { - return matchingCandidates; - } - - return candidates; - } - - private static List findInsnsCalls(List methods, MixinContext context) { - return methods.stream() - .map(pair -> { - List insns = context.injections().find(pair); - return !insns.isEmpty() ? new CandidateMethod(pair.methodNode(), insns) : null; - }) - .filter(Objects::nonNull) - .toList(); - } - - private record CandidateMethod(MethodNode method, List insns) { - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java deleted file mode 100644 index 26b9fc16..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodResolver.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.target; - -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.CompoundResolver; - -public class TargetMethodResolver extends CompoundResolver { - public TargetMethodResolver() { - addSubResolver(new SplitTargetMethodSubResolver()); - addSubResolver(TargetMethodSubResolvers.CHANGED_LAMBDA_INDEX); - addSubResolver(TargetMethodSubResolvers.CHANGED_METHOD_PARAMS); - } - - @Override - protected boolean canApply(Recipe recipe) { - return recipe.dirty().getTargetMethod() == null; - } - - @Override - protected Configuration tryReuse(MixinContext context, Recipe recipe) { - TargetPair target = recipe.getNewCleanTarget(); - if (target != null && context.injections().matches(target)) { - return recipe.dirty().copyClean() - .inheritTargetMethod(); - } - return null; - } - - @Override - protected Configuration useFallback(MixinContext context, Recipe recipe) { - TargetPair target = recipe.getNewCleanTarget(); - if (target != null) { - return recipe.dirty().copyClean() - .inheritTargetMethod(); - } - return null; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java deleted file mode 100644 index 5bdf8b0e..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/target/TargetMethodSubResolvers.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver.target; - -import com.mojang.datafixers.util.Pair; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.MutableConfiguration; -import org.sinytra.adapter.model.param.Parameters; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; -import org.sinytra.adapter.pipeline.resolver.Resolver; -import org.sinytra.adapter.pipeline.resolver.SubResolver; - -import java.util.Comparator; -import java.util.List; - -public class TargetMethodSubResolvers { - /** - * For example: - * CLEAN: net/minecraft/server/MinecraftServer.lambda$reloadResources$28(Lcom/google/common/collect/ImmutableList;)Ljava/util/concurrent/CompletionStage; - * DIRTY: net/minecraft/server/MinecraftServer.lambda$reloadResources$29(Lcom/google/common/collect/ImmutableList;)Ljava/util/concurrent/CompletionStage; - */ - public static final SubResolver CHANGED_LAMBDA_INDEX = (MixinContext context, Recipe recipe) -> { - MethodQualifier cleanQualifier = recipe.clean().getTargetMethod(); - if (!MethodAnalyzer.isLambda(cleanQualifier)) return null; - - ClassNode targetClass = context.dirtyLookup().getClass(recipe.clean().getTargetClass()).orElse(null); - if (targetClass == null) return null; - - List candidateMethods = targetClass.methods.stream() - .filter(m -> MethodAnalyzer.isLambda(m) && m.desc.equals(cleanQualifier.desc())) - .toList(); - if (candidateMethods.size() != 1) return null; - - MethodNode newTarget = candidateMethods.getFirst(); - return MutableConfiguration.create() - .setTargetMethod(newTarget); - }; - - /** - * Handle cases where the target method's parameters have changed - *

- * For example: - * CLEAN: Lnet/minecraft/server/level/ServerEntity;sendPairingData(Lnet/minecraft/server/level/ServerPlayer;Ljava/util/function/Consumer;)V - *
- * DIRTY: Lnet/minecraft/server/level/ServerEntity;sendPairingData(Lnet/minecraft/server/level/ServerPlayer;Lnet/neoforged/neoforge/network/bundle/PacketAndPayloadAcceptor;)V - */ - public static final SubResolver CHANGED_METHOD_PARAMS = (MixinContext context, Recipe recipe) -> { - Configuration overloaded = resolveOverloadedReplacement(context, recipe); - if (overloaded != null) return overloaded; - - MethodQualifier cleanQualifier = recipe.clean().getTargetMethod(); - - List candidates = context.dirtyMethods().findByName(cleanQualifier); - if (candidates.isEmpty()) return null; - - // Find single matching candidate - Configuration resolved = resolveReplacementCandidate(context, recipe, candidates); - if (resolved == null) return null; - - // Only apply single candidate change when the target desc has changed - if (resolved.getTargetMethod() != null && !resolved.getTargetMethod().matches(cleanQualifier)) { - return resolved; - } - - return null; - }; - - // Resolve overloaded method by call for when the name doesn't match. Original method must be marked @Deprecated - // Example: BoneMealItem#growCrop -> applyBonemeal - @Nullable - private static Configuration resolveOverloadedReplacement(MixinContext context, Recipe recipe) { - TargetPair dirtyTarget = recipe.getNewCleanTarget(); - if (dirtyTarget == null || !MethodAnalyzer.isDeprecated(dirtyTarget.methodNode())) return null; - - List invocations = context.dataFlow(dirtyTarget).findTopLevelOwnCalls(); - for (MethodNode invocation : invocations) { - if (context.injections().matches(new TargetPair(dirtyTarget.classNode(), invocation))) { - return MutableConfiguration.create() - .setTargetMethod(invocation); - } - } - - return null; - } - - @Nullable - private static Configuration resolveReplacementCandidate(MixinContext context, Recipe recipe, List methods) { - if (methods.size() == 1) { - return MutableConfiguration.create() - .setTargetMethod(methods.getFirst()); - } - - Resolver resolver = recipe.resolvers().get(InjectionPointResolver.class); - - List> valid = methods.stream() - .sorted(Comparator.comparingInt(m -> Parameters.getParameterTypes(m.desc).size()).reversed()) - .>flatMap(m -> { - Configuration dirtyCopy = recipe.dirty().copy().setTargetMethod(m); - Resolver.ResolutionResult result = context.audited(resolver, () -> resolver.resolve(context, recipe.withDirtyConfig(dirtyCopy))); - return result - .maybePatch() - .stream() - .map(c -> Pair.of(m, c.copyClean().setTargetMethod(m))); - }) - .toList(); - if (valid.size() == 1) { - return valid.getFirst().getSecond(); - } - - List nonDeprecated = valid.stream() - .map(Pair::getFirst) - .filter(m -> !isDirtyDeprecatedMethod(context, m)) - .toList(); - if (nonDeprecated.size() == 1) { - return MutableConfiguration.create() - .setTargetMethod(nonDeprecated.getFirst()); - } - - // Best effort: Handle cases where the target method is overloaded, but the mixin does not specify the target descriptor, - // resulting in ambigous targets. So we just pick the one with the most parameters; - if (!valid.isEmpty()) { - return valid.getFirst().getSecond(); - } - - return null; - } - - public static boolean isDirtyDeprecatedMethod(MixinContext context, MethodNode dirty) { - TargetPair pair = context.cleanMethods().resolve(MethodQualifier.create(dirty)); - return pair != null ? MethodAnalyzer.isDirtyDeprecatedMethod(pair.methodNode(), dirty) : MethodAnalyzer.isDeprecated(dirty); - } -} From 5437ed70636661a02402cffebf8f3976caec5a88 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 23:41:49 +0200 Subject: [PATCH 31/44] Replace CalledMethodLocator with ScopeSearch --- .../pipeline/injector/InjectMixin.java | 3 +- .../pipeline/locate/CalledMethodLocator.java | 148 ---------------- .../adapter/pipeline/locate/Describer.java | 2 +- .../pipeline/locate/LocatingSubResolver.java | 69 +++++--- .../adapter/pipeline/locate/Locator.java | 3 +- .../pipeline/locate/NearestCallLocator.java | 7 +- .../pipeline/locate/OriginLocator.java | 7 +- .../pipeline/locate/ReplacedCallLocator.java | 13 +- .../pipeline/locate/SameMethodLocator.java | 24 ++- .../adapter/pipeline/locate/Scope.java | 32 ++++ .../adapter/pipeline/locate/ScopeSearch.java | 167 ++++++++++++++++++ .../pipeline/locate/SelectedLocator.java | 23 +++ .../pipeline/resolver/CompoundResolver.java | 56 ------ .../resolver/InjectionPointResolver.java | 42 ++++- .../resolver/SliceBoundaryResolver.java | 4 +- .../pipeline/resolver/SubResolver.java | 11 -- .../resolver/TargetMethodResolver.java | 8 +- .../mixin/DynamicMixinPatchTest.java | 10 ++ .../adapter/test/mixin/pipeline/GuiMixin.java | 14 ++ .../mixin/DynamicMixinPatchTest.java | 10 ++ .../mc_26_1_2/mixin/LevelRendererMixin.java | 16 ++ 21 files changed, 388 insertions(+), 281 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/Scope.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/locate/SelectedLocator.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/resolver/SubResolver.java diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index 245a43ad..54e47d70 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -18,6 +18,7 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; import org.sinytra.adapter.pipeline.locate.NearestCallLocator; +import org.sinytra.adapter.pipeline.locate.ScopeSearch; import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; @@ -55,7 +56,7 @@ public HandlerSignature signature() { public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { // An @Inject only needs a position, so it can fall back to the nearest call resolvers.getOrThrow(InjectionPointResolver.class) - .addSubResolver(new LocatingSubResolver(List.of(new NearestCallLocator()))); + .addSubResolver(new LocatingSubResolver(List.of(new NearestCallLocator()), ScopeSearch.Reach.TARGET)); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java deleted file mode 100644 index 8a18b3b9..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/CalledMethodLocator.java +++ /dev/null @@ -1,148 +0,0 @@ -package org.sinytra.adapter.pipeline.locate; - -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Handle; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.compare.MethodAlignment; -import org.sinytra.adapter.analysis.flow.DataFlow; -import org.sinytra.adapter.analysis.flow.DataFlow.ImplicitCalls; -import org.sinytra.adapter.analysis.injection.InjectionQuery; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.Relocation; -import org.sinytra.adapter.model.TargetPair; -import org.sinytra.adapter.model.Targets; -import org.sinytra.adapter.model.ann.AtData; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.util.AdapterUtil; - -import java.util.*; -import java.util.function.Predicate; - -/// Finds the injection point in a method the dirty target calls: a helper or lambda the code moved into, or another class it was extracted to. -/// The first call must be new in the dirty target. The nearest methods with a match win. -public final class CalledMethodLocator implements Locator { - private static final int MAX_DEPTH = 3; - private static final List IGNORED_PACKAGES = List.of("java/", "javax/", "jdk/", "sun/", "com/sun/"); - - /// A method reached from the dirty target - /// @param originLocals the local slots that hold the dirty target's instance - /// @param calledFrom the dirty target's call of a static method in another class that doesn't receive the instance - private record Callee(TargetPair method, Set originLocals, @Nullable MethodInsnNode calledFrom) { - } - - @Override - public List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { - AtData cleanAt = recipe.clean().getAtData(); - if (cleanAt == null) { - return List.of(); - } - - TargetPair origin = targets.dirty(); - Set visited = new HashSet<>(); - visited.add(origin.methodNode()); - Set newCalls = new HashSet<>(MethodAnalyzer.findDirtyOnlyCalls(targets.clean().methodNode(), origin.methodNode(), false)); - Callee start = new Callee(origin, context.isStatic() ? Set.of() : Set.of(0), null); - - List frontier = findCallees(context, start, newCalls::contains, visited, true); - for (int depth = 1; !frontier.isEmpty(); depth++) { - List candidates = new ArrayList<>(); - for (Callee callee : frontier) { - candidates.addAll(findIn(context, targets, cleanAt, cleanInsn, callee)); - } - if (!candidates.isEmpty() || depth == MAX_DEPTH) { - return candidates; - } - - List next = new ArrayList<>(); - for (Callee callee : frontier) { - next.addAll(findCallees(context, callee, call -> true, visited, false)); - } - frontier = next; - } - return List.of(); - } - - /// {@return the instructions the clean `@At` selects in the callee, matched to the clean instruction where the alignment can} - private static List findIn(MixinContext context, Targets targets, AtData cleanAt, AbstractInsnNode cleanInsn, Callee callee) { - List selected = Describer.findSelectable(context, callee.method(), cleanAt, at -> InjectionQuery.of(at).ignoringSlice()); - if (selected.isEmpty()) { - return List.of(); - } - - Relocation relocation = relocation(targets.dirty(), callee); - List aligned = MethodAlignment.align(targets.clean().methodNode(), callee.method().methodNode()).find(cleanInsn).stream() - .filter(match -> selected.contains(match.dirty())) - .map(match -> new Candidate(callee.method(), match.dirty(), match.confidence(), relocation)) - .toList(); - if (!aligned.isEmpty()) { - return aligned; - } - return selected.stream() - .map(insn -> new Candidate(callee.method(), insn, MethodAlignment.Confidence.POSITIONAL, relocation)) - .toList(); - } - - /// {@return how a handler moved into the callee's class reaches the origin instance, or null if it stays in the origin class or can't} - @Nullable - private static Relocation relocation(TargetPair origin, Callee callee) { - MethodNode method = callee.method().methodNode(); - if (callee.method().classNode().name.equals(origin.classNode().name) || !MethodAnalyzer.isStatic(method)) { - return null; - } - Type[] args = Type.getArgumentTypes(method.desc); - int slot = 0; - for (int i = 0; i < args.length; i++) { - if (callee.originLocals().contains(slot)) { - return new Relocation.ReceivesOrigin(i); - } - slot += args[i].getSize(); - } - return callee.calledFrom() != null ? new Relocation.CalledFrom() : null; - } - - /// {@return the methods the caller calls at the top level and the lambdas it creates, that weren't visited yet} - /// When the handler is an instance method, a callee must receive the origin instance, unless the origin calls it statically. - private static List findCallees(MixinContext context, Callee caller, Predicate filter, Set visited, boolean fromOrigin) { - TargetPair from = caller.method(); - DataFlow flow = context.dataFlow(from); - List callees = new ArrayList<>(); - - for (MethodInsnNode call : flow.findTopLevelCalls(ImplicitCalls.UNWRAP)) { - if (!filter.test(call) - || IGNORED_PACKAGES.stream().anyMatch(call.owner::startsWith) - || !context.patchContext().environment().isKnownPackage(AdapterUtil.internalNameToPkg(call.owner)) - ) { - continue; - } - TargetPair method = context.dirtyMethods().resolveInherited(MethodQualifier.create(call)); - if (method != null && !visited.contains(method.methodNode())) { - Set locals = PassedLocals.findPassedSlots(flow, call, call.getOpcode() != Opcodes.INVOKESTATIC, call.desc, caller.originLocals()); - boolean mirrorable = fromOrigin && call.getOpcode() == Opcodes.INVOKESTATIC && !method.classNode().name.equals(from.classNode().name); - addCallee(context, callees, visited, new Callee(method, locals, mirrorable ? call : null)); - } - } - - for (IndyHandle lambda : MethodAnalyzer.findOwnIndyHandles(from.classNode(), from.methodNode())) { - if (!visited.contains(lambda.method())) { - Handle handle = lambda.handle(); - Set locals = PassedLocals.findPassedSlots(flow, lambda.indy(), handle.getTag() != Opcodes.H_INVOKESTATIC, handle.getDesc(), caller.originLocals()); - addCallee(context, callees, visited, new Callee(new TargetPair(from.classNode(), lambda.method()), locals, null)); - } - } - return callees; - } - - private static void addCallee(MixinContext context, List callees, Set visited, Callee callee) { - if (context.isStatic() || !callee.originLocals().isEmpty() || callee.calledFrom() != null) { - visited.add(callee.method().methodNode()); - callees.add(callee); - } - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java index 8ff3f98d..a09e8cdf 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -64,7 +64,7 @@ public static Configuration describe(MixinContext context, Recipe recipe, Candid config.mergeFrom(local); } - if (candidate.target() != recipe.getDirtyTarget()) { + if (otherMethod) { config.setTargetMethod(candidate.target().methodNode()); String targetClass = candidate.target().classNode().name; if (!targetClass.equals(recipe.dirty().getTargetClass())) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java index 3fb3c5f7..2ac312cd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java @@ -3,47 +3,47 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; import org.sinytra.adapter.analysis.compare.MethodAlignment; -import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AtData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.resolver.SubResolver; -import java.util.Comparator; -import java.util.List; +import java.util.*; import java.util.function.Function; -public final class LocatingSubResolver implements SubResolver { +public final class LocatingSubResolver { private final List locators; + private final ScopeSearch.Reach reach; - public LocatingSubResolver(List locators) { + public LocatingSubResolver(List locators, ScopeSearch.Reach reach) { this.locators = locators; + this.reach = reach; } @Nullable - @Override public Configuration resolve(MixinContext context, Recipe recipe) { - Targets targets = recipe.targets(); - if (targets == null) { + TargetPair cleanTarget = recipe.getCleanTarget(); + if (cleanTarget == null) { return null; } AtData cleanAt = recipe.clean().getAtData(); - List cleanInsns = context.injections().find(targets.clean()).stream() + List cleanInsns = context.injections().find(cleanTarget).stream() .map(insn -> cleanAt != null ? Describer.toLocatable(context, cleanAt, insn) : insn) .toList(); - return locateAll(context, recipe, targets, cleanInsns, this.locators, candidate -> Describer.describe(context, recipe, candidate)); + ScopeSearch search = ScopeSearch.of(context, recipe, this.reach); + return locateAll(context, recipe, search, cleanInsns, this.locators, candidate -> Describer.describe(context, recipe, candidate)); } @Nullable - public static Configuration locateAll(MixinContext context, Recipe recipe, Targets targets, List cleanInsns, + public static Configuration locateAll(MixinContext context, Recipe recipe, ScopeSearch search, List cleanInsns, List locators, Function describer) { if (cleanInsns.isEmpty()) { return null; } Configuration shared = null; for (AbstractInsnNode cleanInsn : cleanInsns) { - Configuration config = locate(context, recipe, targets, cleanInsn, locators, describer); + Configuration config = locate(context, recipe, search, cleanInsn, locators, describer); if (config == null || shared != null && !Describer.isSame(config, shared)) { return null; } @@ -53,20 +53,31 @@ public static Configuration locateAll(MixinContext context, Recipe recipe, Targe } @Nullable - private static Configuration locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn, - List locators, Function describer) { + private static Configuration locate(MixinContext context, Recipe recipe, ScopeSearch search, AbstractInsnNode cleanInsn, List locators, + Function describer) { for (Locator locator : locators) { - List candidates = locator.locate(context, recipe, targets, cleanInsn); - if (candidates.isEmpty()) { - continue; + for (int tier = 0; tier < search.tierCount(); tier++) { + Map candidates = new HashMap<>(); + List ordered = new ArrayList<>(); + for (Scope scope : search.tier(tier)) { + for (Candidate candidate : locator.locate(context, recipe, scope, cleanInsn)) { + candidates.put(candidate, scope); + ordered.add(candidate); + } + } + if (ordered.isEmpty()) { + continue; + } + List best = pick(ordered, candidates); + Configuration config = best.size() == 1 ? describer.apply(best.getFirst()) : describeAll(best, describer); + if (config != null) { + Candidate first = best.getFirst(); + context.recordCtxAudit("Located injection point with %s (%s) in %s", locator.getClass().getSimpleName(), + best.size() == 1 ? first.confidence() : best.size() + " candidates with one @At", + candidates.get(first).kind().name().toLowerCase(Locale.ROOT) + " " + first.target().methodNode().name); + } + return config; } - Candidate candidate = pick(candidates); - Configuration config = candidate != null ? describer.apply(candidate) : describeAll(candidates, describer); - if (config != null) { - context.recordCtxAudit("Located injection point with %s (%s)", locator.getClass().getSimpleName(), - candidate != null ? candidate.confidence() : candidates.size() + " candidates with one @At"); - } - return config; } return null; } @@ -84,8 +95,7 @@ private static Configuration describeAll(List candidates, Function candidates) { + private static List pick(List candidates, Map scopes) { MethodAlignment.Confidence best = candidates.stream() .map(Candidate::confidence) .min(Comparator.naturalOrder()) @@ -93,6 +103,9 @@ private static Candidate pick(List candidates) { List confident = candidates.stream() .filter(c -> c.confidence() == best) .toList(); - return confident.size() == 1 ? confident.getFirst() : null; + double similarity = confident.stream().mapToDouble(c -> scopes.get(c).similarity()).max().orElseThrow(); + return confident.stream() + .filter(c -> scopes.get(c).similarity() == similarity) + .toList(); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Locator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Locator.java index 806f4de0..2012aab0 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Locator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Locator.java @@ -1,12 +1,11 @@ package org.sinytra.adapter.pipeline.locate; import org.objectweb.asm.tree.AbstractInsnNode; -import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.List; public interface Locator { - List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn); + List locate(MixinContext context, Recipe recipe, Scope scope, AbstractInsnNode cleanInsn); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/NearestCallLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/NearestCallLocator.java index 9847bee5..942e5a6c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/NearestCallLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/NearestCallLocator.java @@ -5,7 +5,6 @@ import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.sinytra.adapter.analysis.compare.MethodAlignment; -import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.OpcodeUtil; @@ -14,14 +13,14 @@ public final class NearestCallLocator implements Locator { @Override - public List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { - MethodAlignment alignment = MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()); + public List locate(MixinContext context, Recipe recipe, Scope scope, AbstractInsnNode cleanInsn) { + MethodAlignment alignment = scope.alignment(); List around = alignment.findDirtyAround(cleanInsn); AbstractInsnNode start = !around.isEmpty() ? around.getFirst() : findAlignedAfter(alignment, cleanInsn); for (AbstractInsnNode insn = start; insn != null; insn = insn.getNext()) { if (insn instanceof MethodInsnNode call) { - return List.of(new Candidate(targets.dirty(), call, MethodAlignment.Confidence.POSITIONAL)); + return List.of(scope.candidate(call, MethodAlignment.Confidence.POSITIONAL)); } if (insn instanceof JumpInsnNode || OpcodeUtil.isReturnOpcode(insn.getOpcode())) { break; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java index 3c6f1e68..537f9d7a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java @@ -13,7 +13,10 @@ import java.util.List; public final class OriginLocator { - public record Origin(TargetPair method, boolean matches, double similarity, boolean deprecated) { + public record Origin(TargetPair method, boolean matches, MethodAlignment alignment, boolean deprecated) { + public double similarity() { + return this.alignment.similarity(); + } } private static final Comparator RANKING = Comparator.comparing(Origin::matches).reversed() @@ -36,7 +39,7 @@ public static List locate(MixinContext context, TargetPair cleanTarget, .map(candidate -> new Origin( candidate, context.injections().matches(candidate), - MethodAlignment.align(cleanTarget.methodNode(), candidate.methodNode()).similarity(), + MethodAlignment.align(cleanTarget.methodNode(), candidate.methodNode()), isDirtyDeprecated(context, candidate.methodNode()) )) .sorted(RANKING) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java index 10deb285..06bf3aed 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ReplacedCallLocator.java @@ -10,7 +10,6 @@ import org.sinytra.adapter.analysis.InsnTraversal; import org.sinytra.adapter.analysis.compare.MethodAlignment; import org.sinytra.adapter.analysis.flow.DataFlow; -import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.injector.MixinFlag; @@ -20,13 +19,13 @@ public final class ReplacedCallLocator implements Locator { @Override - public List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { + public List locate(MixinContext context, Recipe recipe, Scope scope, AbstractInsnNode cleanInsn) { if (!(cleanInsn instanceof MethodInsnNode cleanCall)) { return List.of(); } Type returnType = Type.getReturnType(cleanCall.desc); - MethodAlignment alignment = MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()); + MethodAlignment alignment = scope.alignment(); List calls = alignment.findDirtyAround(cleanCall).stream() .filter(insn -> insn instanceof MethodInsnNode call && Type.getReturnType(call.desc).equals(returnType)) .map(MethodInsnNode.class::cast) @@ -41,17 +40,17 @@ public List locate(MixinContext context, Recipe recipe, Targets targe } } if (calls.isEmpty() && !context.hasFlag(MixinFlag.RETURN_TYPE_SENSITIVE) && !context.hasFlag(MixinFlag.AT_TARGET_SENSITIVE)) { - MethodInsnNode storing = findStoringCall(context, targets, alignment, cleanCall); + MethodInsnNode storing = findStoringCall(context, scope, alignment, cleanCall); calls = storing != null ? List.of(storing) : List.of(); } return calls.stream() - .map(call -> new Candidate(targets.dirty(), call, MethodAlignment.Confidence.POSITIONAL)) + .map(call -> scope.candidate(call, MethodAlignment.Confidence.POSITIONAL)) .toList(); } /// {@return the dirty call whose value goes into the local the clean call's result was stored into, or null} @Nullable - private static MethodInsnNode findStoringCall(MixinContext context, Targets targets, MethodAlignment alignment, MethodInsnNode cleanCall) { + private static MethodInsnNode findStoringCall(MixinContext context, Scope scope, MethodAlignment alignment, MethodInsnNode cleanCall) { AbstractInsnNode cleanStore = skipCasts(nextReal(cleanCall)); if (cleanStore == null || !OpcodeUtil.isStoreOpcode(cleanStore.getOpcode()) || !(alignment.resolve(cleanStore) instanceof VarInsnNode dirtyStore) @@ -59,7 +58,7 @@ private static MethodInsnNode findStoringCall(MixinContext context, Targets targ return null; } - DataFlow flow = context.dataFlow(targets.dirty()); + DataFlow flow = context.dataFlow(scope.method()); AbstractInsnNode producer = dirtyStore; do { List operands = flow.operands(producer); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java index 5d52637f..89e2608d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java @@ -8,38 +8,48 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.sinytra.adapter.analysis.compare.MethodAlignment; import org.sinytra.adapter.analysis.method.InheritanceHandler; -import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.OpcodeUtil; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public final class SameMethodLocator implements Locator { @Override - public List locate(MixinContext context, Recipe recipe, Targets targets, AbstractInsnNode cleanInsn) { - MethodAlignment alignment = MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()); + public List locate(MixinContext context, Recipe recipe, Scope scope, AbstractInsnNode cleanInsn) { + MethodAlignment alignment = scope.alignment(); List matches = alignment.find(cleanInsn); if (!matches.isEmpty()) { return matches.stream() - .map(match -> new Candidate(targets.dirty(), match.dirty(), match.confidence())) + .map(match -> scope.candidate(match.dirty(), match.confidence())) .toList(); } if (OpcodeUtil.isReturnOpcode(cleanInsn.getOpcode())) { AbstractInsnNode dirtyReturn = findReturnReached(alignment, cleanInsn); - return dirtyReturn != null ? List.of(new Candidate(targets.dirty(), dirtyReturn, MethodAlignment.Confidence.POSITIONAL)) : List.of(); + return dirtyReturn != null ? List.of(scope.candidate(dirtyReturn, MethodAlignment.Confidence.POSITIONAL)) : List.of(); } if (!(cleanInsn instanceof MethodInsnNode cleanCall)) { return List.of(); } - return alignment.findDirtyAround(cleanInsn).stream() + List replacements = alignment.findDirtyAround(cleanInsn).stream() .filter(insn -> insn instanceof MethodInsnNode dirtyCall && isReplacement(context, cleanCall, dirtyCall)) - .map(insn -> new Candidate(targets.dirty(), insn, MethodAlignment.Confidence.POSITIONAL)) + .map(insn -> scope.candidate(insn, MethodAlignment.Confidence.POSITIONAL)) .toList(); + if (!replacements.isEmpty() || scope.kind() == Scope.Kind.SAME) { + return replacements; + } + List anywhere = new ArrayList<>(); + for (AbstractInsnNode insn : scope.method().methodNode().instructions) { + if (insn instanceof MethodInsnNode dirtyCall && isReplacement(context, cleanCall, dirtyCall)) { + anywhere.add(scope.candidate(insn, MethodAlignment.Confidence.POSITIONAL)); + } + } + return anywhere; } @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Scope.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Scope.java new file mode 100644 index 00000000..ea31d60a --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Scope.java @@ -0,0 +1,32 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.model.Relocation; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; + +public record Scope(Targets targets, Kind kind, int depth, MethodAlignment alignment, @Nullable Relocation relocation) { + public enum Kind { + SAME, + REPLACEMENT, + CALLED + } + + public static Scope of(Targets targets) { + return new Scope(targets, Kind.SAME, 0, MethodAlignment.align(targets.clean().methodNode(), targets.dirty().methodNode()), null); + } + + public TargetPair method() { + return this.targets.dirty(); + } + + public double similarity() { + return this.alignment.similarity(); + } + + public Candidate candidate(AbstractInsnNode insn, MethodAlignment.Confidence confidence) { + return new Candidate(method(), insn, confidence, this.relocation); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java new file mode 100644 index 00000000..52ed7575 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java @@ -0,0 +1,167 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.analysis.flow.DataFlow; +import org.sinytra.adapter.analysis.flow.DataFlow.ImplicitCalls; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; +import org.sinytra.adapter.analysis.method.MethodAnalyzer.IndyHandle; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.Relocation; +import org.sinytra.adapter.model.TargetPair; +import org.sinytra.adapter.model.Targets; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.AdapterUtil; + +import java.util.*; +import java.util.function.Predicate; + +public final class ScopeSearch { + public enum Reach { + TARGET, + CALLS + } + + private static final int MAX_DEPTH = 3; + private static final List IGNORED_PACKAGES = List.of("java/", "javax/", "jdk/", "sun/", "com/sun/"); + + private record Callee(TargetPair root, TargetPair method, Set originLocals, @Nullable MethodInsnNode calledFrom) { + } + + private final MixinContext context; + @Nullable + private final TargetPair cleanTarget; + private final List> tiers = new ArrayList<>(); + private final Set visited = new HashSet<>(); + private final int maxTier; + private List frontier; + + private ScopeSearch(MixinContext context, @Nullable TargetPair cleanTarget, List same, List replacements, Reach reach) { + this.context = context; + this.cleanTarget = cleanTarget; + this.tiers.add(same); + this.tiers.add(replacements); + this.maxTier = reach == Reach.CALLS ? 1 + MAX_DEPTH : 1; + same.forEach(scope -> this.visited.add(scope.method().methodNode())); + replacements.forEach(scope -> this.visited.add(scope.method().methodNode())); + } + + public static ScopeSearch of(MixinContext context, Recipe recipe, Reach reach) { + TargetPair cleanTarget = recipe.getCleanTarget(); + if (cleanTarget == null) { + return new ScopeSearch(context, null, List.of(), List.of(), Reach.TARGET); + } + TargetPair same = recipe.getNewCleanTarget(); + List sameScopes = same != null ? List.of(Scope.of(new Targets(cleanTarget, same))) : List.of(); + MethodQualifier qualifier = recipe.clean().getTargetMethod(); + List replacements = qualifier == null ? List.of() : OriginLocator.locate(context, cleanTarget, recipe.clean().getTargetClass(), qualifier).stream() + .filter(origin -> same == null || !origin.method().isSameMethod(same)) + .map(origin -> new Scope(new Targets(cleanTarget, origin.method()), Scope.Kind.REPLACEMENT, 0, origin.alignment(), null)) + .toList(); + return new ScopeSearch(context, cleanTarget, sameScopes, replacements, reach); + } + + public static ScopeSearch of(MixinContext context, Scope scope) { + return new ScopeSearch(context, scope.targets().clean(), List.of(scope), List.of(), Reach.TARGET); + } + + public int tierCount() { + return this.maxTier + 1; + } + + public List tier(int tier) { + while (this.tiers.size() <= tier && this.tiers.size() <= this.maxTier) { + this.tiers.add(nextCalledTier()); + } + return tier < this.tiers.size() ? this.tiers.get(tier) : List.of(); + } + + private List nextCalledTier() { + int depth = this.tiers.size() - 1; + if (this.frontier == null) { + this.frontier = new ArrayList<>(); + for (Scope root : this.tiers.get(0)) { + this.frontier.addAll(findRootCallees(root)); + } + for (Scope root : this.tiers.get(1)) { + this.frontier.addAll(findRootCallees(root)); + } + } else { + List next = new ArrayList<>(); + for (Callee callee : this.frontier) { + next.addAll(findCallees(callee, call -> true, false)); + } + this.frontier = next; + } + return this.frontier.stream() + .map(callee -> new Scope(new Targets(this.cleanTarget, callee.method()), Scope.Kind.CALLED, depth, + MethodAlignment.align(this.cleanTarget.methodNode(), callee.method().methodNode()), relocation(callee))) + .toList(); + } + + private List findRootCallees(Scope root) { + Set newCalls = new HashSet<>(MethodAnalyzer.findDirtyOnlyCalls(this.cleanTarget.methodNode(), root.method().methodNode(), false)); + Callee start = new Callee(root.method(), root.method(), this.context.isStatic() ? Set.of() : Set.of(0), null); + return findCallees(start, newCalls::contains, true); + } + + @Nullable + private static Relocation relocation(Callee callee) { + MethodNode method = callee.method().methodNode(); + if (callee.method().classNode().name.equals(callee.root().classNode().name) || !MethodAnalyzer.isStatic(method)) { + return null; + } + Type[] args = Type.getArgumentTypes(method.desc); + int slot = 0; + for (int i = 0; i < args.length; i++) { + if (callee.originLocals().contains(slot)) { + return new Relocation.ReceivesOrigin(i); + } + slot += args[i].getSize(); + } + return callee.calledFrom() != null ? new Relocation.CalledFrom() : null; + } + + private List findCallees(Callee caller, Predicate filter, boolean fromRoot) { + TargetPair from = caller.method(); + DataFlow flow = this.context.dataFlow(from); + List callees = new ArrayList<>(); + + for (MethodInsnNode call : flow.findTopLevelCalls(ImplicitCalls.UNWRAP)) { + if (!filter.test(call) + || IGNORED_PACKAGES.stream().anyMatch(call.owner::startsWith) + || !this.context.patchContext().environment().isKnownPackage(AdapterUtil.internalNameToPkg(call.owner)) + ) { + continue; + } + TargetPair method = this.context.dirtyMethods().resolveInherited(MethodQualifier.create(call)); + if (method != null && !this.visited.contains(method.methodNode())) { + Set locals = PassedLocals.findPassedSlots(flow, call, call.getOpcode() != Opcodes.INVOKESTATIC, call.desc, caller.originLocals()); + boolean mirrorable = fromRoot && call.getOpcode() == Opcodes.INVOKESTATIC && !method.classNode().name.equals(from.classNode().name); + addCallee(callees, new Callee(caller.root(), method, locals, mirrorable ? call : null)); + } + } + + for (IndyHandle lambda : MethodAnalyzer.findOwnIndyHandles(from.classNode(), from.methodNode())) { + if (!this.visited.contains(lambda.method())) { + Handle handle = lambda.handle(); + Set locals = PassedLocals.findPassedSlots(flow, lambda.indy(), handle.getTag() != Opcodes.H_INVOKESTATIC, handle.getDesc(), caller.originLocals()); + addCallee(callees, new Callee(caller.root(), new TargetPair(from.classNode(), lambda.method()), locals, null)); + } + } + return callees; + } + + private void addCallee(List callees, Callee callee) { + if (this.context.isStatic() || !callee.originLocals().isEmpty() || callee.calledFrom() != null) { + this.visited.add(callee.method().methodNode()); + callees.add(callee); + } + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SelectedLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SelectedLocator.java new file mode 100644 index 00000000..8cc70e0a --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SelectedLocator.java @@ -0,0 +1,23 @@ +package org.sinytra.adapter.pipeline.locate; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.sinytra.adapter.analysis.compare.MethodAlignment; +import org.sinytra.adapter.analysis.injection.InjectionQuery; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.List; + +public final class SelectedLocator implements Locator { + @Override + public List locate(MixinContext context, Recipe recipe, Scope scope, AbstractInsnNode cleanInsn) { + AtData cleanAt = recipe.clean().getAtData(); + if (cleanAt == null || scope.kind() == Scope.Kind.SAME) { + return List.of(); + } + return Describer.findSelectable(context, scope.method(), cleanAt, at -> InjectionQuery.of(at).ignoringSlice()).stream() + .map(insn -> scope.candidate(insn, MethodAlignment.Confidence.POSITIONAL)) + .toList(); + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java deleted file mode 100644 index 75f32842..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/CompoundResolver.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver; - -import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; - -import java.util.ArrayList; -import java.util.List; - -public abstract class CompoundResolver implements Resolver { - protected final List subResolvers = new ArrayList<>(); - - public CompoundResolver addSubResolver(SubResolver subResolver) { - this.subResolvers.add(subResolver); - return this; - } - - protected abstract boolean canApply(Recipe recipe); - - @Nullable - protected Configuration tryReuse(MixinContext context, Recipe recipe) { - return null; - } - - @Nullable - protected Configuration useFallback(MixinContext context, Recipe recipe) { - return null; - } - - @Override - public ResolutionResult resolve(MixinContext context, Recipe recipe) { - if (!canApply(recipe)) { - return ResolutionResult.pass(); - } - - Configuration resused = tryReuse(context, recipe); - if (resused != null) { - return ResolutionResult.success(resused); - } - - for (SubResolver subResolver : this.subResolvers) { - Configuration result = context.audited(subResolver, () -> subResolver.resolve(context, recipe)); - if (result != null) { - return ResolutionResult.success(result); - } - } - - Configuration fallback = useFallback(context, recipe); - if (fallback != null) { - return ResolutionResult.success(fallback); - } - - return ResolutionResult.fail(); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java index 1d71ec01..f700c3f4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java @@ -10,32 +10,56 @@ import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.locate.CalledMethodLocator; import org.sinytra.adapter.pipeline.locate.Candidate; import org.sinytra.adapter.pipeline.locate.Describer; import org.sinytra.adapter.pipeline.locate.LocalLocator; import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; +import org.sinytra.adapter.pipeline.locate.Scope; +import org.sinytra.adapter.pipeline.locate.ScopeSearch; +import org.sinytra.adapter.pipeline.locate.SelectedLocator; import org.sinytra.adapter.util.AdapterUtil; +import java.util.ArrayList; import java.util.List; -public class InjectionPointResolver extends CompoundResolver { +public class InjectionPointResolver implements Resolver { private static final SameMethodLocator SAME_METHOD = new SameMethodLocator(); + private final List subResolvers = new ArrayList<>(); + public InjectionPointResolver() { - addSubResolver(new LocatingSubResolver(List.of(SAME_METHOD, new CalledMethodLocator(), new ReplacedCallLocator()))); + addSubResolver(new LocatingSubResolver(List.of(SAME_METHOD, new SelectedLocator(), new ReplacedCallLocator()), ScopeSearch.Reach.CALLS)); + } + + public InjectionPointResolver addSubResolver(LocatingSubResolver subResolver) { + this.subResolvers.add(subResolver); + return this; } @Override - protected boolean canApply(Recipe recipe) { - return recipe.dirty().getAtData() == null; + public ResolutionResult resolve(MixinContext context, Recipe recipe) { + if (recipe.dirty().getAtData() != null) { + return ResolutionResult.pass(); + } + + Configuration reused = tryReuse(context, recipe); + if (reused != null) { + return ResolutionResult.success(reused); + } + + for (LocatingSubResolver subResolver : this.subResolvers) { + Configuration result = context.audited(subResolver, () -> subResolver.resolve(context, recipe)); + if (result != null) { + return ResolutionResult.success(result); + } + } + return ResolutionResult.fail(); } @Nullable - @Override - protected Configuration tryReuse(MixinContext context, Recipe recipe) { + private static Configuration tryReuse(MixinContext context, Recipe recipe) { Configuration dirty = recipe.dirty(); String targetClass = dirty.getTargetClass(); @@ -80,9 +104,9 @@ private static boolean selectsLocatedInsns(MixinContext context, Recipe recipe, ) { return true; } - Targets targets = new Targets(cleanTarget, dirtyTarget); + Scope scope = Scope.of(new Targets(cleanTarget, dirtyTarget)); for (AbstractInsnNode cleanInsn : context.injections().find(cleanTarget)) { - List candidates = SAME_METHOD.locate(context, recipe, targets, cleanInsn); + List candidates = SAME_METHOD.locate(context, recipe, scope, cleanInsn); if (!candidates.isEmpty() && candidates.stream().noneMatch(c -> dirtyInsns.contains(c.insn()))) { return false; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java index 425df26d..77d73524 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java @@ -15,6 +15,8 @@ import org.sinytra.adapter.pipeline.locate.Locator; import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; +import org.sinytra.adapter.pipeline.locate.Scope; +import org.sinytra.adapter.pipeline.locate.ScopeSearch; import java.util.ArrayList; import java.util.List; @@ -67,7 +69,7 @@ private static AtData fixBoundary(MixinContext context, Recipe recipe, Targets t } List cleanInsns = context.injections().find(targets.clean(), InjectionQuery.of(boundary).ignoringSlice()); - Configuration located = LocatingSubResolver.locateAll(context, recipe, targets, cleanInsns, LOCATORS, candidate -> { + Configuration located = LocatingSubResolver.locateAll(context, recipe, ScopeSearch.of(context, Scope.of(targets)), cleanInsns, LOCATORS, candidate -> { if (candidate.target() != targets.dirty()) { return null; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SubResolver.java deleted file mode 100644 index bf1a8235..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SubResolver.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sinytra.adapter.pipeline.resolver; - -import org.jetbrains.annotations.Nullable; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.ctx.MixinContext; - -public interface SubResolver { - @Nullable - Configuration resolve(MixinContext context, Recipe recipe); -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java index 8b51989c..6df1a72e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java @@ -15,8 +15,7 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { if (recipe.dirty().getTargetMethod() != null) { return ResolutionResult.pass(); } - TargetPair same = recipe.getNewCleanTarget(); - if (same != null && context.injections().matches(same)) { + if (recipe.getNewCleanTarget() != null) { return ResolutionResult.success(recipe.dirty().copyClean().inheritTargetMethod()); } @@ -26,8 +25,9 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { ? OriginLocator.locate(context, cleanTarget, recipe.clean().getTargetClass(), cleanQualifier) : List.of(); OriginLocator.Origin origin = origins.isEmpty() ? null : origins.getFirst(); - if (origin == null || same != null && origin.method().isSameMethod(same)) { - return same != null ? ResolutionResult.success(recipe.dirty().copyClean().inheritTargetMethod()) : ResolutionResult.fail(); + boolean hasAt = recipe.clean().getAtData() != null; + if (origin == null || hasAt && !origin.matches()) { + return hasAt ? ResolutionResult.pass() : ResolutionResult.fail(); } context.recordCtxAudit("Located target method %s (similarity %.2f%s)", MethodQualifier.create(origin.method().methodNode()).asDescriptor(), origin.similarity(), origin.matches() ? ", injection point matches" : ""); diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java index ab465c0e..875db713 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java @@ -719,6 +719,16 @@ void testTargetInheritedDelegatingDefault() throws Exception { ); } + @Test + void testMovedAndChangedCall() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/pipeline/GuiMixin", + "beforeSelectedItemName", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/GuiMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/GuiMixin.java index cb4b60ae..6783fa7b 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/GuiMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/GuiMixin.java @@ -24,4 +24,18 @@ public void renderSelectedItemName(GuiGraphics guiGraphics, CallbackInfo ci) { public void renderSelectedItemNameExpected(GuiGraphics guiGraphics, int adapter_injected_1, CallbackInfo ci) { } + + @Inject( + method = "renderHotbarAndDecorations", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;renderSelectedItemName(Lnet/minecraft/client/gui/GuiGraphics;)V") + ) + private void beforeSelectedItemName(CallbackInfo ci) { + } + + @Inject( + method = "maybeRenderSelectedItemName(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;renderSelectedItemName(Lnet/minecraft/client/gui/GuiGraphics;I)V") + ) + private void beforeSelectedItemNameExpected(CallbackInfo ci) { + } } diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 2cc85830..5472dbce 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -114,6 +114,16 @@ void testWrapWithConditionReplacedCall() throws Exception { ); } + @Test + void testMovedAndChangedCall() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/LevelRendererMixin", + "beforeExtractRenderState", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LevelRendererMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LevelRendererMixin.java index 0c2fb24d..9053f644 100644 --- a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LevelRendererMixin.java +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LevelRendererMixin.java @@ -4,7 +4,9 @@ import net.minecraft.client.renderer.LevelRenderer; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(LevelRenderer.class) public class LevelRendererMixin { @@ -29,4 +31,18 @@ private GpuBufferSlice clampMaxValue(GpuBufferSlice fogBuffer) { private GpuBufferSlice clampMaxValueExpected(GpuBufferSlice fogBuffer) { return fogBuffer; } + + @Inject( + method = "extractVisibleBlockEntities(Lnet/minecraft/client/Camera;FLnet/minecraft/client/renderer/state/level/LevelRenderState;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/blockentity/BlockEntityRenderDispatcher;tryExtractRenderState(Lnet/minecraft/world/level/block/entity/BlockEntity;FLnet/minecraft/client/renderer/feature/ModelFeatureRenderer$CrumblingOverlay;)Lnet/minecraft/client/renderer/blockentity/state/BlockEntityRenderState;") + ) + private void beforeExtractRenderState(CallbackInfo ci) { + } + + @Inject( + method = "extractVisibleBlockEntities(Lnet/minecraft/client/Camera;FLnet/minecraft/client/renderer/state/level/LevelRenderState;Lnet/minecraft/client/renderer/culling/Frustum;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/blockentity/BlockEntityRenderDispatcher;tryExtractRenderState(Lnet/minecraft/world/level/block/entity/BlockEntity;FLnet/minecraft/client/renderer/feature/ModelFeatureRenderer$CrumblingOverlay;Lnet/minecraft/client/renderer/culling/Frustum;)Lnet/minecraft/client/renderer/blockentity/state/BlockEntityRenderState;") + ) + private void beforeExtractRenderStateExpected(CallbackInfo ci) { + } } From 043cc3c8abb900c1b0f5ab5cc08c657703f54a2a Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 13 Oct 2026 23:45:33 +0200 Subject: [PATCH 32/44] Check for the correct ordinal in passing mixins --- .../pipeline/PipelineMethodTransformer.java | 25 ++++++++++++++++++- .../pipeline/locate/SameMethodLocator.java | 4 +++ .../resolver/InjectionPointResolver.java | 16 ++++++------ .../mixin/DynamicMixinPatchTest.java | 21 ++++++++++++++++ .../test/mc_26_1_2/mixin/CameraMixin.java | 20 +++++++++++++++ .../mixin/MinecraftMixinPatchTest.java | 2 +- 6 files changed, 78 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java index 4b6ba5d8..b2c893ff 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PipelineMethodTransformer.java @@ -3,6 +3,7 @@ import com.mojang.logging.LogUtils; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.injection.InjectionQuery; import org.sinytra.adapter.api.AuditTrail; @@ -23,6 +24,7 @@ import org.sinytra.adapter.pipeline.manual.MethodPatchResolver; import org.sinytra.adapter.pipeline.processor.Processor; import org.sinytra.adapter.pipeline.processor.Processors; +import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.slf4j.Logger; @@ -55,7 +57,9 @@ public PatchResult apply(MixinContext context, Configuration config) { if (cleanTarget == null) return PatchResult.PASS; TargetPair dirtyTarget = context.dirtyMethods().resolve(config.getTargetMethod()); - if (!this.patchResolver.matches(config) && !failsDirtyInjectionCheck(context, config, dirtyTarget) && hasValidSlice(context, config, dirtyTarget)) { + if (!this.patchResolver.matches(config) && !failsDirtyInjectionCheck(context, config, dirtyTarget) && hasValidSlice(context, config, dirtyTarget) + && !selectsShiftedInsn(context, config, cleanTarget, dirtyTarget) + ) { if (dirtyTarget != null && !this.patchesOnly) { try { AppliedLocalsCheck.check(this, context, cleanTarget, dirtyTarget); @@ -174,6 +178,25 @@ private PatchResult execute(MixinContext context, Configuration config) { return PatchResult.APPLY; } + private boolean selectsShiftedInsn(MixinContext context, Configuration config, TargetPair cleanTarget, @Nullable TargetPair dirtyTarget) { + AtData at = config.getAtData(); + if (this.patchesOnly || dirtyTarget == null || at == null || at.getOrdinal().isEmpty()) { + return false; + } + try { + List insns = context.injections().find(dirtyTarget); + if (insns.isEmpty() || InjectionPointResolver.selectsLocatedInsns(context, cleanTarget, dirtyTarget, insns)) { + return false; + } + context.recordAudit(this, "The @At still applies, but ordinal %s now selects another instruction than the clean one became", + at.getOrdinal().get()); + return true; + } catch (Exception e) { + LOGGER.debug(MIXINPATCH, "Failed to check the injection point of applying mixin {}", context.getMixinId(), e); + return false; + } + } + public boolean failsDirtyInjectionCheck(MixinContext context, Configuration config, TargetPair dirtyTarget) { return !context.getMixinType().canInject(context, config) || dirtyTarget == null diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java index 89e2608d..495ca3e5 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/SameMethodLocator.java @@ -20,6 +20,10 @@ public final class SameMethodLocator implements Locator { @Override public List locate(MixinContext context, Recipe recipe, Scope scope, AbstractInsnNode cleanInsn) { + return find(context, scope, cleanInsn); + } + + public static List find(MixinContext context, Scope scope, AbstractInsnNode cleanInsn) { MethodAlignment alignment = scope.alignment(); List matches = alignment.find(cleanInsn); if (!matches.isEmpty()) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java index f700c3f4..e7567c50 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java @@ -25,12 +25,10 @@ import java.util.List; public class InjectionPointResolver implements Resolver { - private static final SameMethodLocator SAME_METHOD = new SameMethodLocator(); - private final List subResolvers = new ArrayList<>(); public InjectionPointResolver() { - addSubResolver(new LocatingSubResolver(List.of(SAME_METHOD, new SelectedLocator(), new ReplacedCallLocator()), ScopeSearch.Reach.CALLS)); + addSubResolver(new LocatingSubResolver(List.of(new SameMethodLocator(), new SelectedLocator(), new ReplacedCallLocator()), ScopeSearch.Reach.CALLS)); } public InjectionPointResolver addSubResolver(LocatingSubResolver subResolver) { @@ -73,7 +71,10 @@ private static Configuration tryReuse(MixinContext context, Recipe recipe) { // Try reusing the original List insns = context.injections().find(dirtyTarget); - if (!insns.isEmpty() && selectsLocatedInsns(context, recipe, dirtyTarget, insns) && selectsSameLocal(context, recipe, dirtyTarget, insns.getFirst())) { + TargetPair cleanTarget = recipe.getCleanTarget(); + if (!insns.isEmpty() && (cleanTarget == null || selectsLocatedInsns(context, cleanTarget, dirtyTarget, insns)) + && selectsSameLocal(context, recipe, dirtyTarget, insns.getFirst()) + ) { return dirty.copyClean() .inheritAtData(); } @@ -97,16 +98,15 @@ private static boolean selectsSameLocal(MixinContext context, Recipe recipe, Tar return expected == -1 || context.locals().resolveSelectedLocal(dirtyTarget, dirtyInsn, type) == expected; } - private static boolean selectsLocatedInsns(MixinContext context, Recipe recipe, TargetPair dirtyTarget, List dirtyInsns) { - TargetPair cleanTarget = recipe.getCleanTarget(); - if (cleanTarget == null || !cleanTarget.methodNode().name.equals(dirtyTarget.methodNode().name) + public static boolean selectsLocatedInsns(MixinContext context, TargetPair cleanTarget, TargetPair dirtyTarget, List dirtyInsns) { + if (!cleanTarget.methodNode().name.equals(dirtyTarget.methodNode().name) || !cleanTarget.methodNode().desc.equals(dirtyTarget.methodNode().desc) ) { return true; } Scope scope = Scope.of(new Targets(cleanTarget, dirtyTarget)); for (AbstractInsnNode cleanInsn : context.injections().find(cleanTarget)) { - List candidates = SAME_METHOD.locate(context, recipe, scope, cleanInsn); + List candidates = SameMethodLocator.find(context, scope, cleanInsn); if (!candidates.isEmpty() && candidates.stream().noneMatch(c -> dirtyInsns.contains(c.insn()))) { return false; } diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 5472dbce..9dd51cb5 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -104,6 +104,27 @@ void testModifyVariableCapturedTargetArgs() throws Exception { ); } + @Test + void testAppliedOrdinalShift() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin", + "addRoll1", + assertTargetMethod(), + assertInjectionPoint(), + assertAudit("ordinal 1 now selects another instruction") + ); + } + + @Test + void testShiftedOrdinal() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin", + "addRoll3", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Test void testWrapWithConditionReplacedCall() throws Exception { assertSameCode( diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin.java index 2cdb79b6..7e173a16 100644 --- a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin.java +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/CameraMixin.java @@ -16,4 +16,24 @@ private boolean addRoll2(Camera instance, float yRot, float xRot) { private boolean addRoll2Expected(Camera instance, float yRot, float xRot, float adapter_injected_3) { return true; } + + @WrapWithCondition(method = "alignWithEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1)) + private boolean addRoll1(Camera instance, float yRot, float xRot) { + return true; + } + + @WrapWithCondition(method = "alignWithEntity(F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FFF)V", ordinal = 0)) + private boolean addRoll1Expected(Camera instance, float yRot, float xRot, float adapter_injected_3) { + return true; + } + + @WrapWithCondition(method = "alignWithEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 3)) + private boolean addRoll3(Camera instance, float yRot, float xRot) { + return true; + } + + @WrapWithCondition(method = "alignWithEntity(F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FF)V", ordinal = 1)) + private boolean addRoll3Expected(Camera instance, float yRot, float xRot) { + return true; + } } diff --git a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java index 199e46ea..0abb0388 100644 --- a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java +++ b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java @@ -265,7 +265,7 @@ protected AssertCallback assertLocalSelectors() { protected AssertCallback assertAudit(String text) { return (patched, expected, env) -> { boolean found = env.auditTrail().getAuditTrail().values().stream() - .filter(log -> (patched.name + patched.desc).equals(log.originalMethod())) + .filter(log -> log.originalMethod() != null && log.originalMethod().startsWith(patched.name + "(")) .flatMap(log -> log.entries().stream()) .anyMatch(entry -> entry.second().toString().contains(text)); Assertions.assertThat(found).as("Audit log of " + patched.name + " contains \"" + text + "\"").isTrue(); From cefc14708ebcb2cc396a776ed9b962cae997c20a Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 00:08:22 +0200 Subject: [PATCH 33/44] Remove DivertRedirectProcessor --- .../adapter/api/MethodPatchBuilder.java | 4 -- .../adapter/model/config/key/IntentKeys.java | 6 --- .../pipeline/injector/RedirectMixin.java | 4 -- .../manual/MethodPatchBuilderImpl.java | 9 ---- .../processor/DivertRedirectProcessor.java | 54 ------------------- .../processor/SplitCancellationProcessor.java | 2 +- .../relocate}/RelocationGenerator.java | 2 +- .../relocate/RelocationProcessor.java | 1 - .../processor/relocate/RelocationSolver.java | 4 +- 9 files changed, 4 insertions(+), 82 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/DivertRedirectProcessor.java rename core/src/main/java/org/sinytra/adapter/{edit => pipeline/processor/relocate}/RelocationGenerator.java (99%) diff --git a/core/src/main/java/org/sinytra/adapter/api/MethodPatchBuilder.java b/core/src/main/java/org/sinytra/adapter/api/MethodPatchBuilder.java index daafaada..f6e73db3 100644 --- a/core/src/main/java/org/sinytra/adapter/api/MethodPatchBuilder.java +++ b/core/src/main/java/org/sinytra/adapter/api/MethodPatchBuilder.java @@ -1,10 +1,8 @@ package org.sinytra.adapter.api; import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.commons.InstructionAdapter; import org.sinytra.adapter.model.param.MethodParameters; -import java.util.function.Consumer; import java.util.function.UnaryOperator; public interface MethodPatchBuilder { @@ -42,8 +40,6 @@ public interface MethodPatchBuilder { MethodPatchBuilder modifyMixinType(String newType); - MethodPatchBuilder divertRedirector(Consumer patcher); - MethodPatchBuilder disable(); MethodPatchBuilder transform(MethodTransformer transformer); diff --git a/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java index 058c1013..678c573a 100644 --- a/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java @@ -1,18 +1,12 @@ package org.sinytra.adapter.model.config.key; -import org.objectweb.asm.commons.InstructionAdapter; import org.sinytra.adapter.model.Relocation; import org.sinytra.adapter.model.config.PropertyKey; -import java.util.function.Consumer; - public final class IntentKeys { /// The mixin moves to the dirty target class public static final PropertyKey RELOCATION = PropertyKey.create("relocation"); - /// After each redirected call in the handler, run this code instead of the rest of the original statement - public static final PropertyKey> DIVERT_REDIRECT = PropertyKey.create("divert_redirect"); - private IntentKeys() { } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index 019fd78d..cf18e2a6 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -20,9 +20,7 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.DivertRedirectProcessor; import org.sinytra.adapter.pipeline.processor.Processors; -import org.sinytra.adapter.pipeline.processor.signature.SignatureProcessor; import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.rewrite.InstanceofMakesRedirectRedundant; @@ -60,8 +58,6 @@ public HandlerSignature signature() { public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { resolvers. addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofMakesRedirectRedundant()))); - processors - .addAfter(SignatureProcessor.class, new DivertRedirectProcessor()); if (clean.getAtData() == null) return TxResult.FAIL; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java index a35b48ef..af11badf 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/manual/MethodPatchBuilderImpl.java @@ -1,7 +1,6 @@ package org.sinytra.adapter.pipeline.manual; import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.commons.InstructionAdapter; import org.sinytra.adapter.api.MethodPatch; import org.sinytra.adapter.api.MethodPatchBuilder; import org.sinytra.adapter.api.MethodTransformer; @@ -10,7 +9,6 @@ import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.key.ControlKeys; -import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.config.key.MixinKeys; import org.sinytra.adapter.model.constant.MixinAnnotationConstants; import org.sinytra.adapter.model.param.MethodParameters; @@ -20,7 +18,6 @@ import java.util.Objects; import java.util.Optional; import java.util.function.BiConsumer; -import java.util.function.Consumer; import java.util.function.UnaryOperator; import java.util.stream.Stream; @@ -165,12 +162,6 @@ public MethodPatchBuilder disable() { return this; } - @Override - public MethodPatchBuilder divertRedirector(Consumer patcher) { - this.config.setProperty(IntentKeys.DIVERT_REDIRECT, patcher); - return this; - } - @Override public MethodPatchBuilder transform(MethodTransformer transformer) { this.transforms.add(transformer); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/DivertRedirectProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/DivertRedirectProcessor.java deleted file mode 100644 index a9e1c1fd..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/DivertRedirectProcessor.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.sinytra.adapter.pipeline.processor; - -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.commons.InstructionAdapter; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.analysis.InsnTraversal; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.config.Configuration; -import org.sinytra.adapter.model.config.key.IntentKeys; -import org.sinytra.adapter.pipeline.Recipe; -import org.sinytra.adapter.pipeline.TxResult; -import org.sinytra.adapter.pipeline.ctx.MixinContext; - -import java.util.function.Consumer; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_INVOKE; - -public class DivertRedirectProcessor implements Processor { - @Override - public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { - Consumer patcher = dirty.getProperty(IntentKeys.DIVERT_REDIRECT).orElse(null); - if (patcher == null) return TxResult.PASS; - - if (!recipe.hasInjectionPointValue(AT_VAL_INVOKE)) return TxResult.PASS; - MethodQualifier target = dirty.getAtData().getTargetQualifier().orElse(null); - - if (target != null) { - MethodNode methodNode = context.methodNode(); - boolean applied = false; - for (MethodInsnNode minsn : MethodAnalyzer.findCalls(methodNode, target)) { - AbstractInsnNode previousLabel = InsnTraversal.find(minsn, InsnTraversal.BACKWARD, LabelNode.class::isInstance); - if (previousLabel != null) { - MethodNode dummy = new MethodNode(); - InstructionAdapter adapter = new InstructionAdapter(dummy); - - LabelNode gotoTarget = new LabelNode(); - dummy.instructions.add(gotoTarget); - - patcher.accept(adapter); - - methodNode.instructions.insert(minsn, dummy.instructions); - methodNode.instructions.insert(previousLabel, new JumpInsnNode(Opcodes.GOTO, gotoTarget)); - applied = true; - } - } - if (applied) { - return TxResult.SUCCESS; - } - } - - return TxResult.PASS; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java index 0c61c340..506ced53 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SplitCancellationProcessor.java @@ -3,7 +3,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.edit.RelocationGenerator; +import org.sinytra.adapter.pipeline.processor.relocate.RelocationGenerator; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; diff --git a/core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationGenerator.java similarity index 99% rename from core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java rename to core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationGenerator.java index 7cdf5bac..ab69ad0b 100644 --- a/core/src/main/java/org/sinytra/adapter/edit/RelocationGenerator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationGenerator.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.edit; +package org.sinytra.adapter.pipeline.processor.relocate; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.AnnotationVisitor; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationProcessor.java index f7fac5bf..c23fef8f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationProcessor.java @@ -1,6 +1,5 @@ package org.sinytra.adapter.pipeline.processor.relocate; -import org.sinytra.adapter.edit.RelocationGenerator; import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java index bae85792..efe84eb2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java @@ -63,7 +63,7 @@ public static Result solve(MixinContext context, Recipe recipe, Configuration di if (params.stream().anyMatch(Parameter::isShare)) { return new Result.Unbound("the handler has @Share parameters, which the generated injector can't share"); } - if (!selectsLocals(context, recipe, dirty, newTarget, params)) { + if (!selectsLocals(context, dirty, newTarget, params)) { return new Result.Unbound("a @Local parameter selects nothing at the new target"); } @@ -125,7 +125,7 @@ private static void addWidened(List widened, T member, @Nullable List params) { + private static boolean selectsLocals(MixinContext context, Configuration dirty, TargetPair newTarget, List params) { if (params.stream().noneMatch(Parameter::isLocal)) { return true; } From c163c69d551809088062b0e2aebf7dde3850034d Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 00:11:25 +0200 Subject: [PATCH 34/44] Rename locator subresolver to LocatorChain --- .../org/sinytra/adapter/pipeline/PatcherImpl.java | 2 +- .../adapter/pipeline/injector/InjectMixin.java | 7 +++---- .../adapter/pipeline/injector/MixinType.java | 3 +-- .../adapter/pipeline/injector/ModifyArgMixin.java | 3 +-- .../injector/ModifyExpressionValueMixin.java | 7 +++---- .../pipeline/injector/ModifyReturnValueMixin.java | 3 +-- .../pipeline/injector/ModifyVariableMixin.java | 7 +++---- .../adapter/pipeline/injector/RedirectMixin.java | 7 +++---- .../pipeline/injector/WrapOperationMixin.java | 7 +++---- .../pipeline/injector/WrapWithConditionMixin.java | 3 +-- ...{LocatingSubResolver.java => LocatorChain.java} | 4 ++-- .../pipeline/resolver/InjectionPointResolver.java | 14 +++++++------- .../pipeline/resolver/SliceBoundaryResolver.java | 4 ++-- .../rewrite/InstanceofMakesRedirectRedundant.java | 2 +- .../rewrite/InstanceofReplacement.java | 2 +- .../rewrite/InstanceofToConstant.java | 2 +- .../rewrite/InstanceofToModifyInstanceofValue.java | 2 +- .../pipeline/{ => resolver}/rewrite/Rewrite.java | 2 +- .../{ => resolver}/rewrite/RewriteResolver.java | 2 +- .../{ => resolver}/rewrite/RewriteRule.java | 2 +- .../rewrite/ShiftedVariableToExpression.java | 2 +- 21 files changed, 39 insertions(+), 48 deletions(-) rename core/src/main/java/org/sinytra/adapter/pipeline/locate/{LocatingSubResolver.java => LocatorChain.java} (97%) rename core/src/main/java/org/sinytra/adapter/pipeline/{ => resolver}/rewrite/InstanceofMakesRedirectRedundant.java (96%) rename core/src/main/java/org/sinytra/adapter/pipeline/{ => resolver}/rewrite/InstanceofReplacement.java (97%) rename core/src/main/java/org/sinytra/adapter/pipeline/{ => resolver}/rewrite/InstanceofToConstant.java (98%) rename core/src/main/java/org/sinytra/adapter/pipeline/{ => resolver}/rewrite/InstanceofToModifyInstanceofValue.java (96%) rename core/src/main/java/org/sinytra/adapter/pipeline/{ => resolver}/rewrite/Rewrite.java (80%) rename core/src/main/java/org/sinytra/adapter/pipeline/{ => resolver}/rewrite/RewriteResolver.java (95%) rename core/src/main/java/org/sinytra/adapter/pipeline/{ => resolver}/rewrite/RewriteRule.java (81%) rename core/src/main/java/org/sinytra/adapter/pipeline/{ => resolver}/rewrite/ShiftedVariableToExpression.java (97%) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java index 5af439ad..6e237838 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/PatcherImpl.java @@ -130,7 +130,7 @@ private PatchResult applyPatches(MixinContext mixinContext, MutableConfiguration PatchResult result = runPhase(TxPhase.EARLY, mixinContext); // Complete clean config - TxResult preResult = mixinType.preProcess(mixinContext, configuration, mixinContext.getResolvers(), mixinContext.getProcessors()); + TxResult preResult = mixinType.preProcess(mixinContext, configuration, mixinContext.getResolvers()); if (preResult == TxResult.FAIL) { LOGGER.debug(MIXINPATCH, "Skipping mixin {} due to failed preProcess", mixinId); return PatchResult.PASS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index 54e47d70..c8b44cd5 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -16,10 +16,9 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; +import org.sinytra.adapter.pipeline.locate.LocatorChain; import org.sinytra.adapter.pipeline.locate.NearestCallLocator; import org.sinytra.adapter.pipeline.locate.ScopeSearch; -import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; @@ -53,10 +52,10 @@ public HandlerSignature signature() { } @Override - public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { // An @Inject only needs a position, so it can fall back to the nearest call resolvers.getOrThrow(InjectionPointResolver.class) - .addSubResolver(new LocatingSubResolver(List.of(new NearestCallLocator()), ScopeSearch.Reach.TARGET)); + .addChain(new LocatorChain(List.of(new NearestCallLocator()), ScopeSearch.Reach.TARGET)); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java index 7bd6668b..e2dbf6b9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java @@ -7,7 +7,6 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; import java.util.EnumSet; @@ -29,7 +28,7 @@ default boolean canInject(MixinContext context, Configuration config) { return true; } - TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors); + TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers); TxResult postProcess(MixinContext context, Configuration clean, MutableConfiguration dirty, Recipe recipe); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java index 911335d2..cbd69850 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java @@ -17,7 +17,6 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; import java.util.List; @@ -48,7 +47,7 @@ public HandlerSignature signature() { } @Override - public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { clean.setParameters(SIGNATURE.parse(context.methodNode())); return TxResult.SUCCESS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java index c273b057..edd816af 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java @@ -11,11 +11,10 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.rewrite.InstanceofToModifyInstanceofValue; -import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; +import org.sinytra.adapter.pipeline.resolver.rewrite.InstanceofToModifyInstanceofValue; +import org.sinytra.adapter.pipeline.resolver.rewrite.RewriteResolver; import java.util.List; @@ -48,7 +47,7 @@ public HandlerSignature signature() { } @Override - public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { resolvers .addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToModifyInstanceofValue()))); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java index 6547a847..a152d404 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java @@ -14,7 +14,6 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_RETURN; @@ -56,7 +55,7 @@ public boolean canInject(MixinContext context, Configuration config) { } @Override - public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { clean.setParameters(SIGNATURE.parse(context.methodNode())); return TxResult.SUCCESS; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index fd55df1c..06ed45a1 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -17,12 +17,11 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.LocalResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; -import org.sinytra.adapter.pipeline.rewrite.ShiftedVariableToExpression; +import org.sinytra.adapter.pipeline.resolver.rewrite.RewriteResolver; +import org.sinytra.adapter.pipeline.resolver.rewrite.ShiftedVariableToExpression; import java.util.List; @@ -55,7 +54,7 @@ public HandlerSignature signature() { } @Override - public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { resolvers .addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new ShiftedVariableToExpression()))) .addAfter(InjectionPointResolver.class, new LocalResolver()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index cf18e2a6..bed851e2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -20,11 +20,10 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.rewrite.InstanceofMakesRedirectRedundant; -import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; +import org.sinytra.adapter.pipeline.resolver.rewrite.InstanceofMakesRedirectRedundant; +import org.sinytra.adapter.pipeline.resolver.rewrite.RewriteResolver; import org.sinytra.adapter.util.AdapterUtil; import java.util.List; @@ -55,7 +54,7 @@ public HandlerSignature signature() { } @Override - public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { resolvers. addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofMakesRedirectRedundant()))); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index a598f9d0..51004a75 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -12,11 +12,10 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.rewrite.InstanceofToConstant; -import org.sinytra.adapter.pipeline.rewrite.RewriteResolver; +import org.sinytra.adapter.pipeline.resolver.rewrite.InstanceofToConstant; +import org.sinytra.adapter.pipeline.resolver.rewrite.RewriteResolver; import java.util.List; @@ -49,7 +48,7 @@ public HandlerSignature signature() { } @Override - public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { resolvers.addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToConstant()))); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java index d51c80b1..a982916d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java @@ -16,7 +16,6 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.pipeline.processor.Processors; import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.util.AdapterUtil; @@ -48,7 +47,7 @@ public HandlerSignature signature() { } @Override - public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { TargetPair cleanTarget = clean.getTargetMethod() != null ? context.cleanMethods().resolve(clean.getTargetMethod()) : null; AbstractInsnNode insn = cleanTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)) : null; RedirectedInsn wrapped = insn != null ? RedirectedInsn.of(insn) : null; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatorChain.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatorChain.java index 2ac312cd..99b2ce90 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatingSubResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/LocatorChain.java @@ -12,11 +12,11 @@ import java.util.*; import java.util.function.Function; -public final class LocatingSubResolver { +public final class LocatorChain { private final List locators; private final ScopeSearch.Reach reach; - public LocatingSubResolver(List locators, ScopeSearch.Reach reach) { + public LocatorChain(List locators, ScopeSearch.Reach reach) { this.locators = locators; this.reach = reach; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java index e7567c50..18bb0ccf 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java @@ -13,7 +13,7 @@ import org.sinytra.adapter.pipeline.locate.Candidate; import org.sinytra.adapter.pipeline.locate.Describer; import org.sinytra.adapter.pipeline.locate.LocalLocator; -import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; +import org.sinytra.adapter.pipeline.locate.LocatorChain; import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; import org.sinytra.adapter.pipeline.locate.Scope; @@ -25,14 +25,14 @@ import java.util.List; public class InjectionPointResolver implements Resolver { - private final List subResolvers = new ArrayList<>(); + private final List chains = new ArrayList<>(); public InjectionPointResolver() { - addSubResolver(new LocatingSubResolver(List.of(new SameMethodLocator(), new SelectedLocator(), new ReplacedCallLocator()), ScopeSearch.Reach.CALLS)); + addChain(new LocatorChain(List.of(new SameMethodLocator(), new SelectedLocator(), new ReplacedCallLocator()), ScopeSearch.Reach.CALLS)); } - public InjectionPointResolver addSubResolver(LocatingSubResolver subResolver) { - this.subResolvers.add(subResolver); + public InjectionPointResolver addChain(LocatorChain chain) { + this.chains.add(chain); return this; } @@ -47,8 +47,8 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { return ResolutionResult.success(reused); } - for (LocatingSubResolver subResolver : this.subResolvers) { - Configuration result = context.audited(subResolver, () -> subResolver.resolve(context, recipe)); + for (LocatorChain chain : this.chains) { + Configuration result = context.audited(chain, () -> chain.resolve(context, recipe)); if (result != null) { return ResolutionResult.success(result); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java index 77d73524..ab3a8b9e 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java @@ -11,7 +11,7 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.locate.Describer; -import org.sinytra.adapter.pipeline.locate.LocatingSubResolver; +import org.sinytra.adapter.pipeline.locate.LocatorChain; import org.sinytra.adapter.pipeline.locate.Locator; import org.sinytra.adapter.pipeline.locate.ReplacedCallLocator; import org.sinytra.adapter.pipeline.locate.SameMethodLocator; @@ -69,7 +69,7 @@ private static AtData fixBoundary(MixinContext context, Recipe recipe, Targets t } List cleanInsns = context.injections().find(targets.clean(), InjectionQuery.of(boundary).ignoringSlice()); - Configuration located = LocatingSubResolver.locateAll(context, recipe, ScopeSearch.of(context, Scope.of(targets)), cleanInsns, LOCATORS, candidate -> { + Configuration located = LocatorChain.locateAll(context, recipe, ScopeSearch.of(context, Scope.of(targets)), cleanInsns, LOCATORS, candidate -> { if (candidate.target() != targets.dirty()) { return null; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofMakesRedirectRedundant.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofMakesRedirectRedundant.java similarity index 96% rename from core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofMakesRedirectRedundant.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofMakesRedirectRedundant.java index 84793973..9383c7e7 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofMakesRedirectRedundant.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofMakesRedirectRedundant.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.rewrite; +package org.sinytra.adapter.pipeline.resolver.rewrite; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofReplacement.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofReplacement.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofReplacement.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofReplacement.java index 4b7ead90..e5c07a10 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofReplacement.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofReplacement.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.rewrite; +package org.sinytra.adapter.pipeline.resolver.rewrite; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToConstant.java similarity index 98% rename from core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToConstant.java index a5f0e2ab..b6c23df1 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToConstant.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToConstant.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.rewrite; +package org.sinytra.adapter.pipeline.resolver.rewrite; import com.google.common.collect.Multimap; import org.jetbrains.annotations.Nullable; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToModifyInstanceofValue.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToModifyInstanceofValue.java similarity index 96% rename from core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToModifyInstanceofValue.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToModifyInstanceofValue.java index fb50fd32..f412a14d 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/InstanceofToModifyInstanceofValue.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToModifyInstanceofValue.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.rewrite; +package org.sinytra.adapter.pipeline.resolver.rewrite; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/Rewrite.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/Rewrite.java similarity index 80% rename from core/src/main/java/org/sinytra/adapter/pipeline/rewrite/Rewrite.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/Rewrite.java index bc836d3d..3363eddc 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/Rewrite.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/Rewrite.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.rewrite; +package org.sinytra.adapter.pipeline.resolver.rewrite; import org.sinytra.adapter.model.config.Configuration; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/RewriteResolver.java similarity index 95% rename from core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteResolver.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/RewriteResolver.java index 1e6d1e7c..1fd7b1c0 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/RewriteResolver.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.rewrite; +package org.sinytra.adapter.pipeline.resolver.rewrite; import org.sinytra.adapter.model.config.Configurations; import org.sinytra.adapter.pipeline.Recipe; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteRule.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/RewriteRule.java similarity index 81% rename from core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteRule.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/RewriteRule.java index 78f37900..9b74f66c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/RewriteRule.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/RewriteRule.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.rewrite; +package org.sinytra.adapter.pipeline.resolver.rewrite; import org.jetbrains.annotations.Nullable; import org.sinytra.adapter.pipeline.Recipe; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/ShiftedVariableToExpression.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/ShiftedVariableToExpression.java similarity index 97% rename from core/src/main/java/org/sinytra/adapter/pipeline/rewrite/ShiftedVariableToExpression.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/ShiftedVariableToExpression.java index 9d3fee75..ea2d1c23 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/rewrite/ShiftedVariableToExpression.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/ShiftedVariableToExpression.java @@ -1,4 +1,4 @@ -package org.sinytra.adapter.pipeline.rewrite; +package org.sinytra.adapter.pipeline.resolver.rewrite; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.AbstractInsnNode; From 0c65497f6a4e79fa43ca06a534ae40fff8685fdd Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 16:41:16 +0200 Subject: [PATCH 35/44] Replace custom instanceof injection point with mixinextras expressions --- core/build.gradle.kts | 1 - .../adapter/model/ann/ExpressionData.java | 38 +++++++ .../adapter/model/config/key/IntentKeys.java | 3 + .../constant/MixinAnnotationConstants.java | 2 +- .../model/constant/MixinAnnotations.java | 6 +- .../injector/ModifyExpressionValueMixin.java | 4 +- .../processor/ExpressionProcessor.java | 30 +++++ .../pipeline/processor/Processors.java | 2 + ...Value.java => InstanceofToExpression.java} | 20 +++- .../org/sinytra/adapter/util/MixinBridge.java | 106 +++++++++++++++++- runtime/build.gradle.kts | 21 ---- .../adapter/runtime/AdapterMixinPlugin.java | 30 ----- .../inject/InstanceOfInjectionPoint.java | 38 ------- .../runtime/inject/ModifyInstanceofValue.java | 27 ----- .../ModifyInstanceofValueInjectionInfo.java | 22 ---- .../inject/ModifyInstanceofValueInjector.java | 47 -------- .../main/resources/adapter.init.mixins.json | 5 - settings.gradle.kts | 2 +- .../mixin/DynamicMixinPatchTest.java | 3 +- .../pipeline/ItemInHandRendererMixin.java | 12 +- .../mixin/MinecraftMixinPatchTest.java | 29 +++++ 21 files changed, 233 insertions(+), 215 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/model/ann/ExpressionData.java create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/processor/ExpressionProcessor.java rename core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/{InstanceofToModifyInstanceofValue.java => InstanceofToExpression.java} (59%) delete mode 100644 runtime/build.gradle.kts delete mode 100644 runtime/src/main/java/org/sinytra/adapter/runtime/AdapterMixinPlugin.java delete mode 100644 runtime/src/main/java/org/sinytra/adapter/runtime/inject/InstanceOfInjectionPoint.java delete mode 100644 runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValue.java delete mode 100644 runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjectionInfo.java delete mode 100644 runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjector.java delete mode 100644 runtime/src/main/resources/adapter.init.mixins.json diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 20b1a2d2..b6e62b59 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -42,7 +42,6 @@ dependencies { implementation("com.google.guava:guava:32.1.2-jre") implementation("org.slf4j:slf4j-api:2.0.0") implementation("net.fabricmc:sponge-mixin:0.14.0+mixin.0.8.6") - implementation("io.github.llamalad7:mixinextras-common:0.3.1") compileOnly("org.jetbrains:annotations:24.0.1") api(platform("org.ow2.asm:asm-bom:9.8")) diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/ExpressionData.java b/core/src/main/java/org/sinytra/adapter/model/ann/ExpressionData.java new file mode 100644 index 00000000..a8fbeeec --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/model/ann/ExpressionData.java @@ -0,0 +1,38 @@ +package org.sinytra.adapter.model.ann; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.sinytra.adapter.model.constant.MixinAnnotations; + +import java.util.List; + +public record ExpressionData(String expression, List definitions) { + public record TypeDefinition(String id, Type type) { + } + + public List toAnnotationNodes() { + AnnotationNode expression = new AnnotationNode(MixinAnnotations.EXPRESSION); + expression.visit("value", List.of(this.expression)); + List definitions = this.definitions.stream() + .map(definition -> { + AnnotationNode node = new AnnotationNode(MixinAnnotations.DEFINITION); + node.visit("id", definition.id()); + node.visit("type", List.of(definition.type())); + return node; + }) + .toList(); + if (definitions.size() == 1) { + return List.of(definitions.getFirst(), expression); + } + AnnotationNode container = new AnnotationNode(MixinAnnotations.DEFINITIONS); + container.visit("value", definitions); + return List.of(container, expression); + } + + public static boolean isExpressionAnnotation(AnnotationNode annotation) { + return switch (annotation.desc) { + case MixinAnnotations.EXPRESSION, MixinAnnotations.EXPRESSIONS, MixinAnnotations.DEFINITION, MixinAnnotations.DEFINITIONS -> true; + default -> false; + }; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java b/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java index 678c573a..da866c29 100644 --- a/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/key/IntentKeys.java @@ -1,12 +1,15 @@ package org.sinytra.adapter.model.config.key; import org.sinytra.adapter.model.Relocation; +import org.sinytra.adapter.model.ann.ExpressionData; import org.sinytra.adapter.model.config.PropertyKey; public final class IntentKeys { /// The mixin moves to the dirty target class public static final PropertyKey RELOCATION = PropertyKey.create("relocation"); + public static final PropertyKey EXPRESSION = PropertyKey.create("expression"); + private IntentKeys() { } } diff --git a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java index d4eeb989..cefa1a17 100644 --- a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java @@ -12,7 +12,7 @@ public class MixinAnnotationConstants { public static final String AT_VAL_STORE = "STORE"; public static final String AT_VAL_LOAD = "LOAD"; public static final String AT_VAL_CONST = "CONSTANT"; - public static final String AT_VAL_SINYTRA_INSTANCEOF = "sinytra:INSTANCEOF"; + public static final String AT_VAL_EXPRESSION = "MIXINEXTRAS:EXPRESSION"; public static final String AT_SHIFT = "shift"; public static final String PROPERTY_AT = "at"; diff --git a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java index 9a09f69b..95b2be18 100644 --- a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotations.java @@ -24,8 +24,10 @@ public class MixinAnnotations { public static final String WRAP_WITH_CONDITION_V2_INTERNAL_NAME = "com/llamalad7/mixinextras/injector/v2/WrapWithCondition"; public static final String LOCAL = "Lcom/llamalad7/mixinextras/sugar/Local;"; public static final String SHARE = "Lcom/llamalad7/mixinextras/sugar/Share;"; - // Adapter custom mixin injectors - public static final String MODIFY_INSTANCEOF_VAL = "Lorg/sinytra/adapter/runtime/inject/ModifyInstanceofValue;"; + public static final String EXPRESSION = "Lcom/llamalad7/mixinextras/expression/Expression;"; + public static final String EXPRESSIONS = "Lcom/llamalad7/mixinextras/expression/Expressions;"; + public static final String DEFINITION = "Lcom/llamalad7/mixinextras/expression/Definition;"; + public static final String DEFINITIONS = "Lcom/llamalad7/mixinextras/expression/Definitions;"; // Misc public static final String MIXIN = "Lorg/spongepowered/asm/mixin/Mixin;"; public static final String AT = "Lorg/spongepowered/asm/mixin/injection/At;"; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java index edd816af..5e353133 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java @@ -13,7 +13,7 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.resolver.InjectionPointResolver; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.pipeline.resolver.rewrite.InstanceofToModifyInstanceofValue; +import org.sinytra.adapter.pipeline.resolver.rewrite.InstanceofToExpression; import org.sinytra.adapter.pipeline.resolver.rewrite.RewriteResolver; import java.util.List; @@ -49,7 +49,7 @@ public HandlerSignature signature() { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { resolvers - .addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToModifyInstanceofValue()))); + .addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToExpression()))); clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/ExpressionProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ExpressionProcessor.java new file mode 100644 index 00000000..4a6420f3 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/ExpressionProcessor.java @@ -0,0 +1,30 @@ +package org.sinytra.adapter.pipeline.processor; + +import org.objectweb.asm.tree.MethodNode; +import org.sinytra.adapter.model.ann.ExpressionData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.key.IntentKeys; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; + +import java.util.ArrayList; + +public class ExpressionProcessor implements Processor { + @Override + public TxResult process(MixinContext context, Configuration dirty, Recipe recipe) { + ExpressionData expression = dirty.getProperty(IntentKeys.EXPRESSION).orElse(null); + if (expression == null) { + return TxResult.PASS; + } + + MethodNode handler = context.methodNode(); + if (handler.invisibleAnnotations == null) { + handler.invisibleAnnotations = new ArrayList<>(); + } + handler.invisibleAnnotations.removeIf(ExpressionData::isExpressionAnnotation); + handler.invisibleAnnotations.addAll(expression.toAnnotationNodes()); + context.recordCtxAudit("Set expression to: %s", expression.expression()); + return TxResult.SUCCESS; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java index b25dc01c..f1836837 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/Processors.java @@ -20,6 +20,8 @@ private void registerDefaultProcessors() { // === Standard changes === // General annotation props add(new PropertyProcessor()); + // MixinExtras expression of an expression @At before anything evaluates the dirty @At + add(new ExpressionProcessor()); // Target method post processor add(new TargetMethodProcessor()); // Mixin method signature diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToModifyInstanceofValue.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToExpression.java similarity index 59% rename from core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToModifyInstanceofValue.java rename to core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToExpression.java index f412a14d..f992493b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToModifyInstanceofValue.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/rewrite/InstanceofToExpression.java @@ -4,16 +4,18 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.TypeInsnNode; import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.ann.ExpressionData; import org.sinytra.adapter.model.config.Configurations; +import org.sinytra.adapter.model.config.key.IntentKeys; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import java.util.List; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_SINYTRA_INSTANCEOF; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_EXPRESSION; -public final class InstanceofToModifyInstanceofValue implements RewriteRule { +public final class InstanceofToExpression implements RewriteRule { @Nullable @Override public Rewrite apply(MixinContext context, Recipe recipe) { @@ -25,11 +27,19 @@ public Rewrite apply(MixinContext context, Recipe recipe) { return null; } + Type type = Type.getObjectType(instanceOf.desc); + String id = getIdentifier(type); + ExpressionData expression = new ExpressionData("? instanceof " + id, List.of(new ExpressionData.TypeDefinition(id, type))); List checks = InstanceofReplacement.findChecks(recipe.getDirtyTarget().methodNode(), instanceOf.desc); - AtData at = AtData.builder(AT_VAL_SINYTRA_INSTANCEOF) - .target(instanceOf.desc) + AtData at = AtData.builder(AT_VAL_EXPRESSION) .ordinal(checks.size() == 1 ? null : checks.indexOf(instanceOf)) .build(); - return new Rewrite.Replace(Configurations.changeMixinType(recipe.dirty(), MixinAnnotations.MODIFY_INSTANCEOF_VAL, at)); + return new Rewrite.Replace(Configurations.changeMixinType(recipe.dirty(), MixinAnnotations.MODIFY_EXPR_VAL, at) + .setProperty(IntentKeys.EXPRESSION, expression)); + } + + private static String getIdentifier(Type type) { + String name = type.getInternalName(); + return name.substring(name.lastIndexOf('/') + 1).replace('$', '_'); } } diff --git a/core/src/main/java/org/sinytra/adapter/util/MixinBridge.java b/core/src/main/java/org/sinytra/adapter/util/MixinBridge.java index fb35eb2e..a35ad4d1 100644 --- a/core/src/main/java/org/sinytra/adapter/util/MixinBridge.java +++ b/core/src/main/java/org/sinytra/adapter/util/MixinBridge.java @@ -55,6 +55,8 @@ public final class MixinBridge { private static final MethodHandle LOCAL_VARIABLE_INJECTION_POINT_FIND; private static final VarHandle INJECTION_POINT_TYPES; private static final String CLS_REGEX = "^([A-Za-z_][A-Za-z0-9_]*[\\.\\$])+[A-Za-z_][A-Za-z0-9_]*$"; + private static final String EXPRESSION_POINT_CLASS = "com.llamalad7.mixinextras.expression.impl.point.ExpressionInjectionPoint"; + private static final String CONFIG_NAME = "adapter.mixinbridge.json"; private static final Object LOCALS_LOCK = new Object(); static { @@ -172,7 +174,7 @@ private Session(IMixinContext mixinContext, MethodNode mixinMethod, ClassNode ta public List findInjectionPoint(AnnotationNode parent, AnnotationNode at, @Nullable AnnotationNode slice) { try { InjectionPoint point = InjectionPoint.parse(this.mixinContext, this.mixinMethod, parent, at); - return find(point, slice); + return find(point, parent, slice); } catch (Throwable e) { LOGGER.error("Error finding injection insns", e); return List.of(); @@ -181,7 +183,7 @@ public List findInjectionPoint(AnnotationNode parent, Annotati public List findConstant(AnnotationNode constant, String returnDesc, @Nullable AnnotationNode slice) { try { - return find(new BeforeConstant(this.mixinContext, constant, returnDesc), slice); + return find(new BeforeConstant(this.mixinContext, constant, returnDesc), null, slice); } catch (Throwable e) { LOGGER.error("Error finding injection insns", e); return List.of(); @@ -223,7 +225,7 @@ private LocalVariableDiscriminator.Context localsContext(Type type, boolean args } } - private List find(InjectionPoint point, @Nullable AnnotationNode slice) throws Throwable { + private List find(InjectionPoint point, @Nullable AnnotationNode parent, @Nullable AnnotationNode slice) throws Throwable { List targetInsns = new ArrayList<>(); if (LOCAL_VARIABLE_INJECTION_POINT_CLASS.isInstance(point)) { // Local variable injection points need a ModifyVariable injector, and always search the whole method @@ -231,7 +233,11 @@ private List find(InjectionPoint point, @Nullable AnnotationNo LOCAL_VARIABLE_INJECTION_POINT_FIND.invoke(point, info, this.targetMethod.instructions, targetInsns, target()); } else { InsnList instructions = slice != null ? slice(slice) : this.targetMethod.instructions; - point.find(this.targetMethod.desc, instructions, targetInsns); + if (parent != null && point.getClass().getName().equals(EXPRESSION_POINT_CLASS)) { + Expressions.find(this, point, parent, instructions, targetInsns); + } else { + point.find(this.targetMethod.desc, instructions, targetInsns); + } } return targetInsns; } @@ -249,6 +255,92 @@ private Target target() { } } + private static final class Expressions { + private static final Object LOCK = new Object(); + private static final String MIN_VERSION = "0.5.0"; + private static final Map INJECTION_INFOS = Map.ofEntries( + Map.entry("Lorg/spongepowered/asm/mixin/injection/Inject;", "org.spongepowered.asm.mixin.injection.struct.CallbackInjectionInfo"), + Map.entry("Lorg/spongepowered/asm/mixin/injection/ModifyArg;", "org.spongepowered.asm.mixin.injection.struct.ModifyArgInjectionInfo"), + Map.entry("Lorg/spongepowered/asm/mixin/injection/ModifyArgs;", "org.spongepowered.asm.mixin.injection.struct.ModifyArgsInjectionInfo"), + Map.entry("Lorg/spongepowered/asm/mixin/injection/ModifyConstant;", "org.spongepowered.asm.mixin.injection.struct.ModifyConstantInjectionInfo"), + Map.entry("Lorg/spongepowered/asm/mixin/injection/ModifyVariable;", "org.spongepowered.asm.mixin.injection.struct.ModifyVariableInjectionInfo"), + Map.entry("Lorg/spongepowered/asm/mixin/injection/Redirect;", "org.spongepowered.asm.mixin.injection.struct.RedirectInjectionInfo"), + Map.entry("Lcom/llamalad7/mixinextras/injector/ModifyExpressionValue;", "com.llamalad7.mixinextras.injector.ModifyExpressionValueInjectionInfo"), + Map.entry("Lcom/llamalad7/mixinextras/injector/ModifyReceiver;", "com.llamalad7.mixinextras.injector.ModifyReceiverInjectionInfo"), + Map.entry("Lcom/llamalad7/mixinextras/injector/ModifyReturnValue;", "com.llamalad7.mixinextras.injector.ModifyReturnValueInjectionInfo"), + Map.entry("Lcom/llamalad7/mixinextras/injector/wrapoperation/WrapOperation;", "com.llamalad7.mixinextras.injector.wrapoperation.WrapOperationInjectionInfo"), + Map.entry("Lcom/llamalad7/mixinextras/injector/WrapWithCondition;", "com.llamalad7.mixinextras.injector.v2.WrapWithConditionInjectionInfo"), + Map.entry("Lcom/llamalad7/mixinextras/injector/v2/WrapWithCondition;", "com.llamalad7.mixinextras.injector.v2.WrapWithConditionInjectionInfo") + ); + private static final VarHandle INFO_METHOD; + private static final VarHandle INFO_ANNOTATION; + private static final VarHandle INFO_CLASS_NODE; + private static final Map, Handles> HANDLES = new ConcurrentHashMap<>(); + + private record Handles(VarHandle currentInfo, VarHandle currentTargets, Map> injectionInfos) { + } + + static { + try { + INFO_METHOD = TRUSTED_LOOKUP.findVarHandle(AnnotatedMethodInfo.class, "method", MethodNode.class); + INFO_ANNOTATION = TRUSTED_LOOKUP.findVarHandle(AnnotatedMethodInfo.class, "annotation", AnnotationNode.class); + INFO_CLASS_NODE = TRUSTED_LOOKUP.findVarHandle(SpecialMethodInfo.class, "classNode", ClassNode.class); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + public static void find(Session session, InjectionPoint point, AnnotationNode parent, InsnList instructions, List found) { + Handles handles = HANDLES.computeIfAbsent(point.getClass(), Expressions::createHandles); + Class infoType = handles.injectionInfos().getOrDefault(parent.desc, CallbackInjectionInfo.class); + InjectionInfo info = createInjectionInfo(infoType, session.mixinContext, session.lvtCompatibility); + INFO_METHOD.set(info, session.mixinMethod); + INFO_ANNOTATION.set(info, parent); + INFO_CLASS_NODE.set(info, session.targetClass); + synchronized (LOCK) { + Object previousInfo = handles.currentInfo().get(); + Object previousTargets = handles.currentTargets().get(); + try { + handles.currentInfo().set(info); + handles.currentTargets().set(new ArrayList<>(List.of(session.target()))); + point.find(session.targetMethod.desc, instructions, found); + } finally { + handles.currentInfo().set(previousInfo); + handles.currentTargets().set(previousTargets); + } + } + } + + @SuppressWarnings("unchecked") + private static Handles createHandles(Class pointClass) { + try { + ClassLoader loader = pointClass.getClassLoader(); + Class configUtils = Class.forName("com.llamalad7.mixinextras.utils.MixinConfigUtils", true, loader); + Class config = Class.forName("com.llamalad7.mixinextras.config.MixinExtrasConfig", true, loader); + Map configs = (Map) TRUSTED_LOOKUP.unreflectVarHandle(configUtils.getDeclaredField("CONFIG_CACHE")).get(); + Object declared = config.getConstructor(String.class, config, String.class).newInstance(CONFIG_NAME, null, MIN_VERSION); + synchronized (configs) { + configs.putIfAbsent(CONFIG_NAME, declared); + } + + Map> injectionInfos = new HashMap<>(); + for (Map.Entry entry : INJECTION_INFOS.entrySet()) { + try { + injectionInfos.put(entry.getKey(), (Class) Class.forName(entry.getValue(), false, loader)); + } catch (ClassNotFoundException ignored) { + } + } + return new Handles( + TRUSTED_LOOKUP.unreflectVarHandle(pointClass.getDeclaredField("CURRENT_INFO")), + TRUSTED_LOOKUP.unreflectVarHandle(pointClass.getDeclaredField("CURRENT_TARGETS")), + injectionInfos + ); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } + private static InjectionInfo createInjectionInfo(Class type, IMixinContext mixinContext, int lvtCompatibility) { try { InjectionInfo info = (InjectionInfo) UNSAFE.allocateInstance(type); @@ -278,7 +370,8 @@ private static final class CompatibilityContexts { TRUSTED_LOOKUP.findVarHandle(SpecialMethodInfo.class, "mixin", targetContext), TRUSTED_LOOKUP.findVarHandle(targetContext, "mixin", mixinInfo), TRUSTED_LOOKUP.findVarHandle(mixinInfo, "parent", mixinConfig), - TRUSTED_LOOKUP.findVarHandle(mixinConfig, "decorations", Map.class) + TRUSTED_LOOKUP.findVarHandle(mixinConfig, "decorations", Map.class), + TRUSTED_LOOKUP.findVarHandle(mixinConfig, "name", String.class) }; classes = new Class[]{targetContext, mixinInfo, mixinConfig}; } catch (Throwable t) { @@ -298,6 +391,7 @@ private static Object create(int lvtCompatibility) { try { Object config = UNSAFE.allocateInstance(CLASSES[2]); HANDLES[3].set(config, new HashMap<>(Map.of(FabricUtil.KEY_COMPATIBILITY, lvtCompatibility))); + HANDLES[4].set(config, CONFIG_NAME); Object mixinInfo = UNSAFE.allocateInstance(CLASSES[1]); HANDLES[2].set(mixinInfo, config); Object context = UNSAFE.allocateInstance(CLASSES[0]); @@ -369,7 +463,7 @@ public V getDecoration(String key) { //@formatter:off @Override public IMixinConfigSource getSource() {throw new UnsupportedOperationException();} @Override public String getCleanSourceId() {return "dummy";} - @Override public String getName() {throw new UnsupportedOperationException();} + @Override public String getName() {return CONFIG_NAME;} @Override public String getMixinPackage() {throw new UnsupportedOperationException();} @Override public int getPriority() {return 0;} @Override public IMixinConfigPlugin getPlugin() {return null;} diff --git a/runtime/build.gradle.kts b/runtime/build.gradle.kts deleted file mode 100644 index 33011ba0..00000000 --- a/runtime/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -val runtimeVersion = project.property("runtimeVersion") as String -val versionMc = project.property("versionMc") as String - -version = "$runtimeVersion+$versionMc" - -println("Runtime version: $version") - -dependencies { - implementation("net.fabricmc:sponge-mixin:0.14.0+mixin.0.8.6") - implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:0.3.6")!!) -} - -tasks.jar { - manifest { - manifest.attributes( - "Implementation-Version" to project.version, - "MixinConfigs" to "adapter.init.mixins.json", - "FMLModType" to "GAMELIBRARY" - ) - } -} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/AdapterMixinPlugin.java b/runtime/src/main/java/org/sinytra/adapter/runtime/AdapterMixinPlugin.java deleted file mode 100644 index 19dac980..00000000 --- a/runtime/src/main/java/org/sinytra/adapter/runtime/AdapterMixinPlugin.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.sinytra.adapter.runtime; - -import org.objectweb.asm.tree.ClassNode; -import org.sinytra.adapter.runtime.inject.InstanceOfInjectionPoint; -import org.sinytra.adapter.runtime.inject.ModifyInstanceofValueInjectionInfo; -import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; -import org.spongepowered.asm.mixin.extensibility.IMixinInfo; -import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; - -import java.util.List; -import java.util.Set; - -public class AdapterMixinPlugin implements IMixinConfigPlugin { - - static { - InjectionPoint.register(InstanceOfInjectionPoint.class, null); - InjectionInfo.register(ModifyInstanceofValueInjectionInfo.class); - } - - //@formatter:off - @Override public void onLoad(String mixinPackage) {} - @Override public String getRefMapperConfig() {return "";} - @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {return true;} - @Override public void acceptTargets(Set myTargets, Set otherTargets) {} - @Override public List getMixins() {return List.of();} - @Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {} - @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {} - //@formatter:on -} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/InstanceOfInjectionPoint.java b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/InstanceOfInjectionPoint.java deleted file mode 100644 index aafd3a1e..00000000 --- a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/InstanceOfInjectionPoint.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.sinytra.adapter.runtime.inject; - -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.TypeInsnNode; -import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorByName; -import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -@InjectionPoint.AtCode(value = "INSTANCEOF", namespace = "sinytra") -public class InstanceOfInjectionPoint extends InjectionPoint { - private final String target; - - public InstanceOfInjectionPoint(InjectionPointData data) { - super(data); - - this.target = ((ITargetSelectorByName) data.getTarget()).getOwner(); - } - - @Override - public boolean find(String s, InsnList insns, Collection nodes) { - List found = new ArrayList<>(); - - for (AbstractInsnNode insn : insns) { - if (insn instanceof TypeInsnNode type && insn.getOpcode() == Opcodes.INSTANCEOF && type.desc.equals(this.target)) { - found.add(insn); - } - } - - nodes.addAll(found); - return !found.isEmpty(); - } -} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValue.java b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValue.java deleted file mode 100644 index 5c1437c2..00000000 --- a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValue.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.sinytra.adapter.runtime.inject; - -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Slice; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface ModifyInstanceofValue { - String[] method(); - - At[] at(); - - Slice[] slice() default {}; - - boolean remap() default true; - - int require() default -1; - - int expect() default 1; - - int allow() default -1; -} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjectionInfo.java b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjectionInfo.java deleted file mode 100644 index d8516449..00000000 --- a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjectionInfo.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.sinytra.adapter.runtime.inject; - -import com.llamalad7.mixinextras.injector.MixinExtrasInjectionInfo; -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.MethodNode; -import org.spongepowered.asm.mixin.injection.code.Injector; -import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; -import org.spongepowered.asm.mixin.transformer.MixinTargetContext; - -@InjectionInfo.AnnotationType(ModifyInstanceofValue.class) -@InjectionInfo.HandlerPrefix("modifyInstanceofValue") -public class ModifyInstanceofValueInjectionInfo extends MixinExtrasInjectionInfo { - - public ModifyInstanceofValueInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { - super(mixin, method, annotation); - } - - @Override - protected Injector parseInjector(AnnotationNode injectAnnotation) { - return new ModifyInstanceofValueInjector(this); - } -} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjector.java b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjector.java deleted file mode 100644 index a3d9612c..00000000 --- a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjector.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.sinytra.adapter.runtime.inject; - -import com.llamalad7.mixinextras.injector.StackExtension; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.InsnNode; -import org.objectweb.asm.tree.VarInsnNode; -import org.spongepowered.asm.mixin.injection.code.Injector; -import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; -import org.spongepowered.asm.mixin.injection.struct.InjectionNodes; -import org.spongepowered.asm.mixin.injection.struct.Target; - -public class ModifyInstanceofValueInjector extends Injector { - - public ModifyInstanceofValueInjector(InjectionInfo info) { - super(info, "@ModifyInstanceofValue"); - } - - @Override - protected void inject(Target target, InjectionNodes.InjectionNode node) { - AbstractInsnNode valueNode = node.getCurrentTarget(); - StackExtension stack = new StackExtension(target); - - InjectorData handler = new InjectorData(target, "instanceof value modifier"); - validateParams(handler, Type.BOOLEAN_TYPE, Type.BOOLEAN_TYPE); - - InsnList insns = new InsnList(); - - if (!this.isStatic) { - insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); - insns.add(new InsnNode(Opcodes.SWAP)); - } - - if (handler.captureTargetArgs > 0) { - pushArgs(target.arguments, insns, target.getArgIndices(), 0, handler.captureTargetArgs); - } - - stack.receiver(this.isStatic); - stack.capturedArgs(target.arguments, handler.captureTargetArgs); - - invokeHandler(insns); - - target.insns.insert(valueNode, insns); - } -} diff --git a/runtime/src/main/resources/adapter.init.mixins.json b/runtime/src/main/resources/adapter.init.mixins.json deleted file mode 100644 index 1b02b285..00000000 --- a/runtime/src/main/resources/adapter.init.mixins.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "minVersion": "0.8.5", - "plugin": "org.sinytra.adapter.runtime.AdapterMixinPlugin", - "package": "org.sinytra.adapter.runtime.mixin" -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 136b29bd..d96ad6b8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,7 +26,7 @@ pluginManagement { rootProject.name = "Adapter" includeBuild("core") -include("runtime", "test") +include("test") include("test:mc-1.21.1") project(":test:mc-1.21.1").projectDir = file("test/mc-1.21.1") diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java index 875db713..e9eb9434 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java @@ -555,7 +555,8 @@ void testSyntheticInstanceofMEV() throws Exception { "renderFirstPersonItemMEV", assertType(), assertTargetMethod(), - assertInjectionPoint() + assertInjectionPoint(), + assertExpression() ); } diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/ItemInHandRendererMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/ItemInHandRendererMixin.java index 35a93e8a..b8acdf05 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/ItemInHandRendererMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/pipeline/ItemInHandRendererMixin.java @@ -1,11 +1,12 @@ package org.sinytra.adapter.test.mixin.pipeline; +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import net.minecraft.client.renderer.ItemInHandRenderer; import net.minecraft.world.item.CrossbowItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import org.sinytra.adapter.runtime.inject.ModifyInstanceofValue; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; @@ -37,12 +38,11 @@ private boolean renderFirstPersonItemMEV(boolean original) { return original; } - @ModifyInstanceofValue( + @Definition(id = "CrossbowItem", type = CrossbowItem.class) + @Expression("? instanceof CrossbowItem") + @ModifyExpressionValue( method = "renderArmWithItem(Lnet/minecraft/client/player/AbstractClientPlayer;FFLnet/minecraft/world/InteractionHand;FLnet/minecraft/world/item/ItemStack;FLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", - at = @At( - value = "sinytra:INSTANCEOF", - target = "net/minecraft/world/item/CrossbowItem" - ) + at = @At("MIXINEXTRAS:EXPRESSION") ) private boolean renderFirstPersonItemMEVExpected(boolean original) { return original; diff --git a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java index 0abb0388..97ce79e4 100644 --- a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java +++ b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java @@ -12,6 +12,7 @@ import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.ann.AnnotationValueHandle; import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.ann.ExpressionData; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; @@ -316,6 +317,34 @@ protected AssertCallback assertInjectionPoint() { }; } + protected AssertCallback assertExpression() { + Function> expressionExtractor = method -> method.invisibleAnnotations == null ? List.of() + : method.invisibleAnnotations.stream() + .filter(ExpressionData::isExpressionAnnotation) + .map(MinecraftMixinPatchTest::describeAnnotation) + .toList(); + + return (patched, expected, env) -> Assertions.assertThat(expressionExtractor.apply(patched)) + .as("Expression Annotations") + .isEqualTo(expressionExtractor.apply(expected)); + } + + private static String describeAnnotation(Object value) { + if (value instanceof AnnotationNode node) { + List values = new ArrayList<>(); + if (node.values != null) { + for (int i = 0; i + 1 < node.values.size(); i += 2) { + values.add(node.values.get(i) + "=" + describeAnnotation(node.values.get(i + 1))); + } + } + return node.desc + values; + } + if (value instanceof List list) { + return list.stream().map(MinecraftMixinPatchTest::describeAnnotation).toList().toString(); + } + return String.valueOf(value); + } + protected AssertCallback assertSliceRange() { BiFunction> sliceExtractor = (name, node) -> new AnnotationHandle(node).getNested("slice") .flatMap(ann -> ann.getNested(name)) From 2bfad279fc81d87b196503c4d25e3a79f3e9c30b Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 17:45:53 +0200 Subject: [PATCH 36/44] Cleanup class transformers --- .../adapter/api/ClassPatchContext.java | 7 + .../sinytra/adapter/api/DynamicPatches.java | 10 +- .../adapter/model/ann/ClassTarget.java | 7 +- .../sinytra/adapter/pipeline/MixinParser.java | 3 +- .../processor/SugarLocalProcessor.java | 27 +--- .../transform/AnonymousTargetTransformer.java | 119 ++++++++++++++ .../transform/DynamicAnonClassIndexPatch.java | 79 --------- .../DynamicAnonymousShadowFieldTypePatch.java | 70 -------- .../types/AccessorAdapterGenerator.java | 54 +++++++ .../types/FieldAccessorTypeTransformer.java | 143 ---------------- .../types/FieldTypeUsageTransformer.java | 152 ++++++++++-------- .../org/sinytra/adapter/util/AdapterUtil.java | 29 +++- test/build.gradle.kts | 1 - .../mixin/DynamicMixinPatchTest.java | 3 +- .../mixin/BytecodeFixerUpperTestFrontend.java | 2 + .../mixin/DynamicMixinPatchTest.java | 49 +++++- .../mixin/DispenseItemBehaviorMixin.java | 15 ++ .../mixin/FunctionCommandExecutorMixin.java | 16 ++ .../RangedCrossbowAttackGoalAccessor.java | 12 ++ .../mixin/RangedCrossbowAttackGoalMixin.java | 22 +++ .../mixin/MinecraftMixinPatchTest.java | 12 ++ 21 files changed, 428 insertions(+), 404 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/transform/AnonymousTargetTransformer.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonClassIndexPatch.java delete mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonymousShadowFieldTypePatch.java create mode 100644 core/src/main/java/org/sinytra/adapter/types/AccessorAdapterGenerator.java delete mode 100644 core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/DispenseItemBehaviorMixin.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/FunctionCommandExecutorMixin.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalAccessor.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalMixin.java diff --git a/core/src/main/java/org/sinytra/adapter/api/ClassPatchContext.java b/core/src/main/java/org/sinytra/adapter/api/ClassPatchContext.java index e376b940..f892c6dd 100644 --- a/core/src/main/java/org/sinytra/adapter/api/ClassPatchContext.java +++ b/core/src/main/java/org/sinytra/adapter/api/ClassPatchContext.java @@ -1,5 +1,6 @@ package org.sinytra.adapter.api; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.sinytra.adapter.pipeline.ctx.PatchContextImpl; @@ -15,6 +16,12 @@ static ClassPatchContext of(ClassNode classNode, List targetTypes, PatchEn List targetTypes(); + @Nullable + default Type singleTarget() { + List types = targetTypes(); + return types.size() == 1 ? types.getFirst() : null; + } + PatchEnvironment environment(); String remap(String reference); diff --git a/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java b/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java index eacbb31a..50c25ba5 100644 --- a/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java +++ b/core/src/main/java/org/sinytra/adapter/api/DynamicPatches.java @@ -3,20 +3,16 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import org.sinytra.adapter.pipeline.PipelineMethodTransformer; -import org.sinytra.adapter.pipeline.transform.DynamicAnonClassIndexPatch; -import org.sinytra.adapter.pipeline.transform.DynamicAnonymousShadowFieldTypePatch; +import org.sinytra.adapter.pipeline.transform.AnonymousTargetTransformer; import org.sinytra.adapter.pipeline.transform.LocalCaptureUpgradeTransformer; -import org.sinytra.adapter.types.FieldAccessorTypeTransformer; import org.sinytra.adapter.types.FieldTypeUsageTransformer; import java.util.List; public final class DynamicPatches { public static final List CLASS_PATCHES = List.of( - new DynamicAnonClassIndexPatch(), - new DynamicAnonymousShadowFieldTypePatch(), - new FieldTypeUsageTransformer(), - new FieldAccessorTypeTransformer() + new AnonymousTargetTransformer(), + new FieldTypeUsageTransformer() ); public static Multimap methodTransformers(List patches) { diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/ClassTarget.java b/core/src/main/java/org/sinytra/adapter/model/ann/ClassTarget.java index a7df0fe2..975346ca 100644 --- a/core/src/main/java/org/sinytra/adapter/model/ann/ClassTarget.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/ClassTarget.java @@ -23,11 +23,6 @@ public List getTypes() { return this.types; } - @Nullable - public Type getSingle() { - return this.types.size() != 1 ? null : this.types.getFirst(); - } - public void set(Type type) { this.types = List.of(type); this.either.ifLeft(h -> h.set(List.of(type))) @@ -41,7 +36,7 @@ public static ClassTarget parse(AnnotationHandle handle, RefMapper mapper) { .or(() -> handle.>getValue(MIXIN_TARGETS).map(v -> { List types = v.get().stream() .map(mapper::remap) - .map(Type::getObjectType) + .map(name -> Type.getObjectType(name.replace('.', '/'))) .toList(); return new ClassTarget(types, Either.right(v)); })) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java b/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java index 41f98922..6b1eaed3 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/MixinParser.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; public class MixinParser { @@ -63,7 +62,7 @@ public static MixinMethodHandle parseMixin(ClassTarget cls, MethodNode method, R MutablePropertyContainer properties = MutablePropertyContainer.parse(handle, template, mapper); properties.setProperty(ControlKeys.MIXIN_TYPE, annotation.desc); - String targetClass = Objects.requireNonNullElseGet(cls.getSingle(), () -> cls.getTypes().getFirst()).getInternalName(); + String targetClass = cls.getTypes().getFirst().getInternalName(); properties.setProperty(ControlKeys.TARGET_CLASS, targetClass); properties.setProperty(ControlKeys.RETURN_TYPE, Type.getReturnType(method.desc)); properties.setProperty(ControlKeys.STATIC, MethodAnalyzer.isStatic(method)); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java index 44983efe..a3e232b0 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java @@ -122,12 +122,9 @@ public static AnnotationNode getLocalAnnotation(MethodNode method, int index) { method.invisibleParameterAnnotations != null ? method.invisibleParameterAnnotations : new List[0], method.visibleParameterAnnotations != null ? method.visibleParameterAnnotations : new List[0] )) { - if (index < annotations.length && annotations[index] != null) { - for (AnnotationNode annotation : annotations[index]) { - if (annotation.desc.equals(MixinAnnotations.LOCAL)) { - return annotation; - } - } + AnnotationNode local = index < annotations.length ? AdapterUtil.getAnnotation(annotations[index], MixinAnnotations.LOCAL) : null; + if (local != null) { + return local; } } return null; @@ -164,16 +161,16 @@ private static Boolean compareTypes(MixinContext context, Type type, Type dirtyT @Nullable private static AnnotationNode describeSelector(MixinContext context, MixinBridge.Session session, AnnotationNode selector, TargetPair target, AbstractInsnNode insn, int slot, Type type) { - boolean argsOnly = Boolean.TRUE.equals(getValue(selector, PROPERTY_ARGS_ONLY)) && Describer.isParameter(target.methodNode(), slot); + boolean argsOnly = Boolean.TRUE.equals(AdapterUtil.getAnnotationValue(selector, PROPERTY_ARGS_ONLY)) && Describer.isParameter(target.methodNode(), slot); AnnotationNode adjusted = new AnnotationNode(selector.desc); if (argsOnly) { adjusted.visit(PROPERTY_ARGS_ONLY, true); } - if (getValue(selector, PROPERTY_INDEX) != null) { + if (AdapterUtil.getAnnotationValue(selector, PROPERTY_INDEX) != null) { adjusted.visit(PROPERTY_INDEX, slot); return session.findLocal(adjusted, type, insn) == slot ? adjusted : null; } - if (getValue(selector, PROPERTY_ORDINAL) == null && session.findLocal(adjusted, type, insn) == slot) { + if (AdapterUtil.getAnnotationValue(selector, PROPERTY_ORDINAL) == null && session.findLocal(adjusted, type, insn) == slot) { return adjusted; } int ordinal = argsOnly @@ -188,18 +185,6 @@ private static AnnotationNode describeSelector(MixinContext context, MixinBridge return null; } - @Nullable - private static Object getValue(AnnotationNode annotation, String key) { - if (annotation.values != null) { - for (int i = 0; i + 1 < annotation.values.size(); i += 2) { - if (key.equals(annotation.values.get(i))) { - return annotation.values.get(i + 1); - } - } - } - return null; - } - private static void updateModel(@Nullable MethodParameters params, MethodNode handler, int index) { if (params == null) { return; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/transform/AnonymousTargetTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/transform/AnonymousTargetTransformer.java new file mode 100644 index 00000000..71232d76 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/transform/AnonymousTargetTransformer.java @@ -0,0 +1,119 @@ +package org.sinytra.adapter.pipeline.transform; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.sinytra.adapter.api.ClassLookup; +import org.sinytra.adapter.api.ClassPatchContext; +import org.sinytra.adapter.api.ClassTransformer; +import org.sinytra.adapter.api.PatchEnvironment; +import org.sinytra.adapter.api.PatchResult; +import org.sinytra.adapter.model.ann.ClassTarget; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.pipeline.MixinParser; +import org.sinytra.adapter.pipeline.ctx.PatchContext; +import org.sinytra.adapter.util.AdapterUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class AnonymousTargetTransformer implements ClassTransformer { + @Override + public PatchResult apply(ClassNode classNode, ClassPatchContext context) { + Type target = context.singleTarget(); + if (target == null || !AdapterUtil.isAnonymousClass(target.getInternalName())) { + return PatchResult.PASS; + } + + PatchResult result = PatchResult.PASS; + Type renumbered = findRenumbered(context.environment(), target.getInternalName()); + if (renumbered != null && context instanceof PatchContext patchContext && patchContext.classTarget() != null) { + retarget(classNode, patchContext, target, renumbered); + target = renumbered; + result = PatchResult.APPLY; + } + + ClassNode targetClass = context.environment().dirtyClassLookup().getClass(target.getInternalName()).orElse(null); + if (targetClass != null && renameShadowFields(classNode, targetClass, context)) { + result = PatchResult.APPLY; + } + return result; + } + + @Nullable + private static Type findRenumbered(PatchEnvironment environment, String target) { + ClassNode cleanClass = environment.cleanClassLookup().getClass(target).orElse(null); + if (cleanClass == null || cleanClass.outerClass == null || cleanClass.outerMethod == null) { + return null; + } + List cleanClasses = findAnonymousClasses(environment.cleanClassLookup(), cleanClass); + List dirtyClasses = findAnonymousClasses(environment.dirtyClassLookup(), cleanClass); + int index = cleanClasses.indexOf(target); + if (index == -1 || cleanClasses.size() != dirtyClasses.size() || dirtyClasses.get(index).equals(target)) { + return null; + } + return Type.getObjectType(dirtyClasses.get(index)); + } + + private static List findAnonymousClasses(ClassLookup lookup, ClassNode anonymousClass) { + ClassNode outerClass = lookup.getClass(anonymousClass.outerClass).orElse(null); + if (outerClass == null) { + return List.of(); + } + List classes = new ArrayList<>(); + for (InnerClassNode innerClass : outerClass.innerClasses) { + if (AdapterUtil.isAnonymousClass(innerClass.name)) { + lookup.getClass(innerClass.name) + .filter(inner -> anonymousClass.outerClass.equals(inner.outerClass) && anonymousClass.outerMethod.equals(inner.outerMethod) + && anonymousClass.outerMethodDesc.equals(inner.outerMethodDesc)) + .ifPresent(inner -> classes.add(inner.name)); + } + } + classes.sort(Comparator.comparingInt(name -> Integer.parseInt(name.substring(name.lastIndexOf('$') + 1)))); + return classes; + } + + private void retarget(ClassNode classNode, PatchContext context, Type from, Type to) { + ClassTarget classTarget = context.classTarget(); + classTarget.set(to); + for (MethodNode method : classNode.methods) { + MixinParser.MixinMethodHandle handle = MixinParser.parseMixin(classTarget, method, context, context.environment()); + if (handle == null) continue; + + handle.properties().getProperty(MixinKeys.TARGET_METHOD) + .filter(q -> from.getDescriptor().equals(q.owner())) + .map(q -> handle.properties().serialize(MixinKeys.TARGET_METHOD, q.withOwner(to))) + .ifPresent(q -> handle.methodAnnotation().setOrAppendNonNull(MixinKeys.TARGET_METHOD.name(), q)); + } + context.environment().auditTrail().recordAudit(this, classNode, "Retarget anonymous class %s to %s", from.getInternalName(), to.getInternalName()); + } + + private boolean renameShadowFields(ClassNode classNode, ClassNode targetClass, ClassPatchContext context) { + Map> targetFields = targetClass.fields.stream().collect(Collectors.groupingBy(field -> field.desc)); + Map renames = new HashMap<>(); + for (FieldNode field : classNode.fields) { + List candidates = targetFields.getOrDefault(field.desc, List.of()); + if (AdapterUtil.isShadowField(field) && candidates.size() == 1 && !candidates.getFirst().name.equals(field.name)) { + renames.put(field.name, candidates.getFirst().name); + field.name = candidates.getFirst().name; + } + } + if (renames.isEmpty()) { + return false; + } + + renames.forEach((from, to) -> context.environment().auditTrail().recordAudit(this, classNode, "Rename anonymous class field %s to %s", from, to)); + for (MethodNode method : classNode.methods) { + for (AbstractInsnNode insn : method.instructions) { + if (insn instanceof FieldInsnNode fieldInsn && fieldInsn.owner.equals(classNode.name)) { + fieldInsn.name = renames.getOrDefault(fieldInsn.name, fieldInsn.name); + } + } + } + return true; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonClassIndexPatch.java b/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonClassIndexPatch.java deleted file mode 100644 index 7b5d075e..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonClassIndexPatch.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.sinytra.adapter.pipeline.transform; - -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.InnerClassNode; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.api.ClassPatchContext; -import org.sinytra.adapter.api.ClassTransformer; -import org.sinytra.adapter.api.PatchResult; -import org.sinytra.adapter.model.MethodQualifier; -import org.sinytra.adapter.model.ann.ClassTarget; -import org.sinytra.adapter.model.config.key.MixinKeys; -import org.sinytra.adapter.pipeline.MixinParser; -import org.sinytra.adapter.pipeline.ctx.PatchContext; -import org.sinytra.adapter.util.AdapterUtil; - - -/** - * Handle cases where a method targets an anonymous class whose index has changed. - */ -public class DynamicAnonClassIndexPatch implements ClassTransformer { - @Override - public PatchResult apply(ClassNode classNode, ClassPatchContext patchContext) { - if (!(patchContext instanceof PatchContext context) || context.classTarget() == null) { - return PatchResult.PASS; - } - ClassTarget classTarget = context.classTarget(); - Type singleTarget = classTarget.getSingle(); - if (singleTarget == null) return PatchResult.PASS; - - String target = singleTarget.getInternalName(); - if (!AdapterUtil.isAnonymousClass(target)) { - return PatchResult.PASS; - } - - ClassNode cleanClass = context.environment().cleanClassLookup().getClass(target).orElse(null); - if (cleanClass == null || cleanClass.outerClass == null || cleanClass.outerMethod == null) { - return PatchResult.PASS; - } - ClassNode outerDirtyClass = context.environment().dirtyClassLookup().getClass(cleanClass.outerClass).orElse(null); - if (outerDirtyClass == null) { - return PatchResult.PASS; - } - - // FIXME Cannot use remap here! - MethodQualifier cleanOuterMethod = MethodQualifier.parse(context.remap(cleanClass.outerMethod + cleanClass.outerMethodDesc)).orElse(null); - if (cleanOuterMethod == null) { - return PatchResult.PASS; - } - - for (InnerClassNode innerClass : outerDirtyClass.innerClasses) { - if (AdapterUtil.isAnonymousClass(innerClass.name)) { - ClassNode inner = context.environment().dirtyClassLookup().getClass(innerClass.name).orElse(null); - if (inner == null || inner.outerMethod == null || inner.outerMethodDesc == null) { - continue; - } - - if (!inner.name.equals(target) && cleanOuterMethod.matches(null, inner.outerMethod, inner.outerMethodDesc)) { - classTarget.set(Type.getObjectType(inner.name)); - updateMixinTargetOwners(classNode, classTarget, context, inner.name); - return PatchResult.APPLY; - } - } - } - - return PatchResult.PASS; - } - - private static void updateMixinTargetOwners(ClassNode classNode, ClassTarget classTarget, PatchContext context, String newOwner) { - for (MethodNode method : classNode.methods) { - MixinParser.MixinMethodHandle handle = MixinParser.parseMixin(classTarget, method, context, context.environment()); - if (handle == null) continue; - - handle.properties().getProperty(MixinKeys.TARGET_METHOD) - .map(q -> handle.properties().serialize(MixinKeys.TARGET_METHOD, q.withOwner(Type.getObjectType(newOwner)))) - .ifPresent(q -> handle.methodAnnotation().setOrAppendNonNull(MixinKeys.TARGET_METHOD.name(), q)); - } - } -} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonymousShadowFieldTypePatch.java b/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonymousShadowFieldTypePatch.java deleted file mode 100644 index 410d02e8..00000000 --- a/core/src/main/java/org/sinytra/adapter/pipeline/transform/DynamicAnonymousShadowFieldTypePatch.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.sinytra.adapter.pipeline.transform; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; -import org.sinytra.adapter.api.ClassPatchContext; -import org.sinytra.adapter.api.ClassTransformer; -import org.sinytra.adapter.api.PatchResult; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.util.AdapterUtil; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -public class DynamicAnonymousShadowFieldTypePatch implements ClassTransformer { - @Override - public PatchResult apply(ClassNode classNode, ClassPatchContext context) { - Type singleTarget = context.targetTypes().size() == 1 ? context.targetTypes().getFirst() : null; - if (singleTarget == null) return PatchResult.PASS; - - String target = singleTarget.getInternalName(); - if (!AdapterUtil.isAnonymousClass(target)) { - return PatchResult.PASS; - } - - ClassNode targetClass = context.environment().dirtyClassLookup().getClass(target).orElse(null); - if (targetClass == null) { - return PatchResult.PASS; - } - - Map renames = new HashMap<>(); - Multimap fields = HashMultimap.create(); - for (FieldNode targetField : targetClass.fields) { - fields.put(targetField.desc, targetField); - } - - for (FieldNode field : classNode.fields) { - if (field.visibleAnnotations != null) { - for (AnnotationNode ann : field.visibleAnnotations) { - if (MixinAnnotations.SHADOW.equals(ann.desc)) { - Collection targetFields = fields.get(field.desc); - if (targetFields.size() == 1) { - FieldNode targetField = targetFields.iterator().next(); - if (!field.name.equals(targetField.name)) { - renames.put(field.name, targetField.name); - field.name = targetField.name; - } - } - } - } - } - } - - if (!renames.isEmpty()) { - renames.forEach((from, to) -> context.environment().auditTrail().recordAudit(this, classNode, "Rename anonymous class field %s to %s", from, to)); - for (MethodNode method : classNode.methods) { - for (AbstractInsnNode insn : method.instructions) { - if (insn instanceof FieldInsnNode finsn && finsn.owner.equals(classNode.name)) { - finsn.name = renames.getOrDefault(finsn.name, finsn.name); - } - } - } - return PatchResult.APPLY; - } - - return PatchResult.PASS; - } -} diff --git a/core/src/main/java/org/sinytra/adapter/types/AccessorAdapterGenerator.java b/core/src/main/java/org/sinytra/adapter/types/AccessorAdapterGenerator.java new file mode 100644 index 00000000..6b21546d --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/types/AccessorAdapterGenerator.java @@ -0,0 +1,54 @@ +package org.sinytra.adapter.types; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.constant.MixinAnnotations; +import org.sinytra.adapter.model.constant.TypeConstants; +import org.sinytra.adapter.util.OpcodeUtil; + +import java.util.ArrayList; +import java.util.List; + +public final class AccessorAdapterGenerator { + private static final String PREFIX = "adapter$"; + private static final int PRIORITY_MAX = 9999; + + public static String getOrCreateGetter(BytecodeFixerJarGenerator generator, Type owner, String field, boolean isStatic, TypeAdapter adapter) { + ClassNode mixin = generator.getOrCreateClass(owner.getInternalName().replace('/', '_'), name -> createMixin(name, owner)); + String name = PREFIX + field; + String desc = Type.getMethodDescriptor(adapter.to()); + synchronized (mixin) { + if (mixin.methods.stream().noneMatch(m -> m.name.equals(name))) { + mixin.methods.add(createGetter(owner, field, isStatic, adapter, name, desc)); + } + } + return name + desc; + } + + private static MethodNode createGetter(Type owner, String field, boolean isStatic, TypeAdapter adapter, String name, String desc) { + MethodNode getter = new MethodNode(Opcodes.ACC_PUBLIC | (isStatic ? Opcodes.ACC_STATIC : 0), name, desc, null, null); + getter.visibleAnnotations = new ArrayList<>(List.of(new AnnotationNode(MixinAnnotations.UNIQUE))); + if (!isStatic) { + getter.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + } + getter.instructions.add(new FieldInsnNode(isStatic ? Opcodes.GETSTATIC : Opcodes.GETFIELD, owner.getInternalName(), field, adapter.from().getDescriptor())); + adapter.apply(getter.instructions, getter.instructions.getLast()); + getter.instructions.add(new InsnNode(OpcodeUtil.getReturnOpcode(adapter.to()))); + return getter; + } + + private static ClassNode createMixin(String name, Type target) { + ClassNode mixin = new ClassNode(); + mixin.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, name, null, TypeConstants.OBJECT_TYPE.getInternalName(), null); + AnnotationNode annotation = new AnnotationNode(MixinAnnotations.MIXIN); + annotation.visit("value", List.of(target)); + annotation.visit("priority", PRIORITY_MAX); + mixin.invisibleAnnotations = new ArrayList<>(List.of(annotation)); + return mixin; + } + + private AccessorAdapterGenerator() { + } +} diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java deleted file mode 100644 index 72b15df5..00000000 --- a/core/src/main/java/org/sinytra/adapter/types/FieldAccessorTypeTransformer.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.sinytra.adapter.types; - -import com.mojang.datafixers.util.Pair; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodNode; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; -import org.sinytra.adapter.api.BytecodeFixerUpper; -import org.sinytra.adapter.api.ClassPatchContext; -import org.sinytra.adapter.api.ClassTransformer; -import org.sinytra.adapter.api.PatchEnvironment; -import org.sinytra.adapter.api.PatchResult; -import org.sinytra.adapter.api.TypeAdapter; -import org.sinytra.adapter.model.ann.AnnotationHandle; -import org.sinytra.adapter.model.ann.AnnotationValueHandle; -import org.sinytra.adapter.model.constant.MixinAnnotations; -import org.sinytra.adapter.model.constant.TypeConstants; -import org.sinytra.adapter.util.MixinBridge; -import org.sinytra.adapter.util.OpcodeUtil; - -import java.util.Optional; - -public class FieldAccessorTypeTransformer implements ClassTransformer { - private static final String PREFIX = "adapter$"; - private static final int PRIORITY_MAX = 9999; - - @Override - public PatchResult apply(ClassNode classNode, ClassPatchContext context) { - BytecodeFixerUpper bfu = context.environment().bytecodeFixerUpper(); - if (bfu == null || context.targetTypes().size() != 1) return PatchResult.PASS; - - Type owner = context.targetTypes().getFirst(); - PatchResult result = PatchResult.PASS; - for (MethodNode methodNode : classNode.methods) { - AnnotationNode accessor = methodNode.visibleAnnotations == null ? null : methodNode.visibleAnnotations.stream() - .filter(a -> MixinAnnotations.ACCESSOR.equals(a.desc)) - .findFirst() - .orElse(null); - if (accessor == null || Type.getArgumentCount(methodNode.desc) != 0 || Type.getReturnType(methodNode.desc) == Type.VOID_TYPE) { - continue; - } - result = result.or(redirectAccessor(classNode, methodNode, accessor, owner, bfu, context)); - } - return result; - } - - private PatchResult redirectAccessor(ClassNode classNode, MethodNode methodNode, AnnotationNode accessor, Type owner, BytecodeFixerUpper bfu, ClassPatchContext context) { - String fieldFqn = getAccessorTargetFieldName(classNode.name, methodNode, new AnnotationHandle(accessor), context.environment()) - .orElse(null); - if (fieldFqn == null) return PatchResult.PASS; - - String fieldName = getFieldName(fieldFqn); - Pair updatedTypes = bfu.getFieldTypeChange(owner.getInternalName(), fieldName); - if (updatedTypes == null) return PatchResult.PASS; - - TypeAdapter typeAdapter = bfu.getTypeAdapter(updatedTypes.getSecond(), updatedTypes.getFirst()); - if (typeAdapter == null) return PatchResult.PASS; - - String targetMethod = addRedirectAcceptorField(owner, methodNode, fieldName, typeAdapter, bfu.getGenerator()); - - // Change Accessor to Invoker - methodNode.visibleAnnotations.remove(accessor); - AnnotationVisitor invokerAnn = methodNode.visitAnnotation(MixinAnnotations.INVOKER, true); - invokerAnn.visit("value", targetMethod); - context.environment().auditTrail().recordAudit(this, classNode, "Redirect accessor %s%s to invoker %s", methodNode.name, methodNode.desc, targetMethod); - - return PatchResult.APPLY; - } - - private String addRedirectAcceptorField(Type owner, MethodNode methodNode, String field, TypeAdapter adapter, BytecodeFixerJarGenerator generator) { - ClassNode node = getOrCreateMixinClass(owner, generator); - - String methodName = PREFIX + field; - Type to = adapter.to(); - String methodDesc = Type.getMethodDescriptor(to); - synchronized (node) { - addRedirectAcceptorMethod(node, owner, methodNode, field, adapter, methodName, to, methodDesc); - } - - return methodName + methodDesc; - } - - private static void addRedirectAcceptorMethod(ClassNode node, Type owner, MethodNode methodNode, String field, TypeAdapter adapter, String methodName, Type to, String methodDesc) { - if (node.methods.stream().noneMatch(m -> m.name.equals(methodName))) { - boolean isStatic = MethodAnalyzer.isStatic(methodNode); - MethodNode method = (MethodNode) node.visitMethod(Opcodes.ACC_PUBLIC | (isStatic ? Opcodes.ACC_STATIC : 0), methodName, methodDesc, null, null); - { - AnnotationVisitor annotationVisitor = method.visitAnnotation(MixinAnnotations.UNIQUE, true); - annotationVisitor.visitEnd(); - } - { - method.visitCode(); - if (!isStatic) { - method.visitVarInsn(Opcodes.ALOAD, 0); - } - method.visitFieldInsn(isStatic ? Opcodes.GETSTATIC : Opcodes.GETFIELD, owner.getInternalName(), field, adapter.from().getDescriptor()); - adapter.apply(method.instructions, method.instructions.getLast()); - method.visitInsn(OpcodeUtil.getReturnOpcode(to)); - method.visitEnd(); - } - method.visitEnd(); - } - } - - private ClassNode getOrCreateMixinClass(Type targetClass, BytecodeFixerJarGenerator generator) { - String className = targetClass.getInternalName().replace('/', '_'); - return generator.getOrCreateClass(className, s -> generateFieldAdapterMixin(s, targetClass)); - } - - private ClassNode generateFieldAdapterMixin(String className, Type targetClass) { - ClassNode node = new ClassNode(); - node.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, className, null, TypeConstants.OBJECT_TYPE.getInternalName(), null); - - { - AnnotationVisitor mixinAnnotation = node.visitAnnotation(MixinAnnotations.MIXIN, false); - { - AnnotationVisitor valueVisitor = mixinAnnotation.visitArray("value"); - valueVisitor.visit(null, targetClass); - valueVisitor.visitEnd(); - } - mixinAnnotation.visit("priority", PRIORITY_MAX); - mixinAnnotation.visitEnd(); - } - - return node; - } - - private static String getFieldName(String desc) { - int descIndex = desc.indexOf(':'); - return descIndex == -1 ? desc : desc.substring(0, descIndex); - } - - private static Optional getAccessorTargetFieldName(String owner, MethodNode method, AnnotationHandle annotationHandle, PatchEnvironment environment) { - return annotationHandle.getValue("value") - .map(AnnotationValueHandle::get) - .filter(str -> !str.isEmpty()) - .or(() -> Optional.ofNullable(MixinBridge.getAccessorFieldName(method.name)) - .map(name -> environment.refmapHolder().remap(owner, name))); - } -} diff --git a/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java b/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java index cfbe1204..383676bc 100644 --- a/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/types/FieldTypeUsageTransformer.java @@ -1,103 +1,117 @@ package org.sinytra.adapter.types; import com.mojang.datafixers.util.Pair; -import com.mojang.logging.LogUtils; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.flow.DataFlow; +import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.BytecodeFixerUpper; import org.sinytra.adapter.api.ClassPatchContext; import org.sinytra.adapter.api.ClassTransformer; import org.sinytra.adapter.api.PatchResult; import org.sinytra.adapter.api.TypeAdapter; +import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.util.AdapterUtil; -import org.slf4j.Logger; +import org.sinytra.adapter.util.MixinBridge; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.sinytra.adapter.util.AdapterUtil.MIXINPATCH; public class FieldTypeUsageTransformer implements ClassTransformer { - private static final Logger LOGGER = LogUtils.getLogger(); - @Override public PatchResult apply(ClassNode classNode, ClassPatchContext context) { BytecodeFixerUpper bfu = context.environment().bytecodeFixerUpper(); - boolean applied = false; - if (bfu != null) { - Map> classUpdatedTypes = new HashMap<>(); - // Update class field types - if (context.targetTypes().size() == 1) { - Type targetType = context.targetTypes().getFirst(); - for (FieldNode field : classNode.fields) { - if (AdapterUtil.isShadowField(field)) { - Pair updatedTypes = bfu.getFieldTypeChange(targetType.getInternalName(), field.name); - if (updatedTypes != null) { - field.desc = updatedTypes.getSecond().getDescriptor(); - // Update shadow field usages - classUpdatedTypes.put(field.name, updatedTypes); - applied = true; - } - } - } - } - // Update field insn types - for (MethodNode method : classNode.methods) { - for (AbstractInsnNode insn : method.instructions) { - if (insn instanceof FieldInsnNode finsn) { - // Update usages of fields that belong to this class - if (finsn.owner.equals(classNode.name)) { - Pair classUpdatedType = classUpdatedTypes.get(finsn.name); - if (classUpdatedType != null) { - applied |= runFieldFix(bfu, classUpdatedType, method, finsn); - continue; - } - } + if (bfu == null) { + return PatchResult.PASS; + } - // Update used fields of other classes - Pair updatedTypes = bfu.getFieldTypeChange(finsn.owner, finsn.name); - if (updatedTypes != null) { - applied |= runFieldFix(bfu, updatedTypes, method, finsn); - } - } - } - // Search for method calls made on modified class fields and update their owners to match the new field types - if (!classUpdatedTypes.isEmpty()) { - AtomicBoolean retargeted = new AtomicBoolean(); - DataFlow.of(method).forEachReceiver((call, receiver) -> { - if (call.getOpcode() == Opcodes.INVOKEVIRTUAL - && AdapterUtil.getSingleOrNull(receiver) instanceof FieldInsnNode finsn - && finsn.owner.equals(classNode.name) - ) { - Pair classUpdatedType = classUpdatedTypes.get(finsn.name); - if (classUpdatedType != null) { - call.owner = classUpdatedType.getSecond().getInternalName(); - retargeted.set(true); - } - } - }); - applied |= retargeted.get(); + Type target = context.singleTarget(); + Map> shadowChanges = target != null ? retypeShadowFields(classNode, target, bfu) : Map.of(); + boolean applied = !shadowChanges.isEmpty(); + for (MethodNode method : classNode.methods) { + applied |= target != null && redirectAccessor(classNode, method, target, bfu, context); + for (AbstractInsnNode insn : method.instructions) { + if (insn instanceof FieldInsnNode fieldInsn) { + Pair change = fieldInsn.owner.equals(classNode.name) ? shadowChanges.get(fieldInsn.name) + : bfu.getFieldTypeChange(fieldInsn.owner, fieldInsn.name); + applied |= change != null && adaptFieldInsn(bfu, change, method, fieldInsn, context, classNode); } } + if (!shadowChanges.isEmpty()) { + applied |= retargetReceivers(classNode, method, shadowChanges); + } } return applied ? PatchResult.APPLY : PatchResult.PASS; } - private static boolean runFieldFix(BytecodeFixerUpper bfu, Pair updatedTypes, MethodNode method, FieldInsnNode finsn) { - boolean isStore = finsn.getOpcode() == Opcodes.PUTFIELD || finsn.getOpcode() == Opcodes.PUTSTATIC; - TypeAdapter typeAdapter = isStore - ? bfu.getTypeAdapter(updatedTypes.getFirst(), updatedTypes.getSecond()) - : bfu.getTypeAdapter(updatedTypes.getSecond(), updatedTypes.getFirst()); - if (typeAdapter == null || isStore && finsn.getPrevious() == null) { + private static Map> retypeShadowFields(ClassNode classNode, Type target, BytecodeFixerUpper bfu) { + Map> changes = new HashMap<>(); + for (FieldNode field : classNode.fields) { + Pair change = AdapterUtil.isShadowField(field) ? bfu.getFieldTypeChange(target.getInternalName(), field.name) : null; + if (change != null && field.desc.equals(change.getFirst().getDescriptor())) { + field.desc = change.getSecond().getDescriptor(); + changes.put(field.name, change); + } + } + return changes; + } + + private boolean adaptFieldInsn(BytecodeFixerUpper bfu, Pair change, MethodNode method, FieldInsnNode fieldInsn, ClassPatchContext context, + ClassNode classNode) { + boolean isStore = fieldInsn.getOpcode() == Opcodes.PUTFIELD || fieldInsn.getOpcode() == Opcodes.PUTSTATIC; + TypeAdapter adapter = isStore ? bfu.getTypeAdapter(change.getFirst(), change.getSecond()) : bfu.getTypeAdapter(change.getSecond(), change.getFirst()); + if (adapter == null || !fieldInsn.desc.equals(change.getFirst().getDescriptor()) || isStore && fieldInsn.getPrevious() == null) { return false; } - LOGGER.debug(MIXINPATCH, "Running fixup for field {}.{}{} in method {}{}", finsn.owner, finsn.name, finsn.desc, method.name, method.desc); - finsn.desc = updatedTypes.getSecond().getDescriptor(); - typeAdapter.apply(method.instructions, isStore ? finsn.getPrevious() : finsn); + fieldInsn.desc = change.getSecond().getDescriptor(); + adapter.apply(method.instructions, isStore ? fieldInsn.getPrevious() : fieldInsn); + context.environment().auditTrail().recordAudit(this, classNode, "Adapt %s of field %s.%s in %s%s to type %s", isStore ? "store" : "load", + fieldInsn.owner, fieldInsn.name, method.name, method.desc, change.getSecond()); return true; } + + private static boolean retargetReceivers(ClassNode classNode, MethodNode method, Map> shadowChanges) { + boolean[] retargeted = {false}; + DataFlow.of(method).forEachReceiver((call, receiver) -> { + if (call.getOpcode() == Opcodes.INVOKEVIRTUAL && AdapterUtil.getSingleOrNull(receiver) instanceof FieldInsnNode fieldInsn + && fieldInsn.owner.equals(classNode.name) && shadowChanges.containsKey(fieldInsn.name) + ) { + call.owner = shadowChanges.get(fieldInsn.name).getSecond().getInternalName(); + retargeted[0] = true; + } + }); + return retargeted[0]; + } + + private boolean redirectAccessor(ClassNode classNode, MethodNode method, Type target, BytecodeFixerUpper bfu, ClassPatchContext context) { + AnnotationNode accessor = AdapterUtil.getAnnotation(method.visibleAnnotations, MixinAnnotations.ACCESSOR); + String field = accessor != null && Type.getArgumentCount(method.desc) == 0 && Type.getReturnType(method.desc) != Type.VOID_TYPE + ? getAccessorField(classNode, method, accessor, context) : null; + Pair change = field != null ? bfu.getFieldTypeChange(target.getInternalName(), field) : null; + TypeAdapter adapter = change != null ? bfu.getTypeAdapter(change.getSecond(), change.getFirst()) : null; + if (adapter == null) { + return false; + } + + String getter = AccessorAdapterGenerator.getOrCreateGetter(bfu.getGenerator(), target, field, MethodAnalyzer.isStatic(method), adapter); + method.visibleAnnotations.remove(accessor); + method.visitAnnotation(MixinAnnotations.INVOKER, true).visit("value", getter); + context.environment().auditTrail().recordAudit(this, classNode, "Redirect accessor %s%s to invoker %s", method.name, method.desc, getter); + return true; + } + + @Nullable + private static String getAccessorField(ClassNode classNode, MethodNode method, AnnotationNode accessor, ClassPatchContext context) { + String value = AdapterUtil.getAnnotationValue(accessor, "value"); + String name = value != null && !value.isEmpty() ? value : MixinBridge.getAccessorFieldName(method.name); + if (name == null) { + return null; + } + String remapped = context.environment().refmapHolder().remap(classNode.name, name); + int descIndex = remapped.indexOf(':'); + return descIndex == -1 ? remapped : remapped.substring(0, descIndex); + } } diff --git a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java index ffd6b341..fcc3ca88 100644 --- a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java +++ b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java @@ -113,8 +113,33 @@ public static boolean isShadowField(FieldNode field) { return AdapterUtil.hasAnnotation(field.visibleAnnotations, MixinAnnotations.SHADOW); } - public static boolean hasAnnotation(List annotations, String desc) { - return annotations != null && annotations.stream().anyMatch(an -> desc.equals(an.desc)); + public static boolean hasAnnotation(@Nullable List annotations, String desc) { + return getAnnotation(annotations, desc) != null; + } + + @Nullable + public static AnnotationNode getAnnotation(@Nullable List annotations, String desc) { + if (annotations != null) { + for (AnnotationNode annotation : annotations) { + if (desc.equals(annotation.desc)) { + return annotation; + } + } + } + return null; + } + + @Nullable + @SuppressWarnings("unchecked") + public static T getAnnotationValue(AnnotationNode annotation, String key) { + if (annotation.values != null) { + for (int i = 0; i + 1 < annotation.values.size(); i += 2) { + if (key.equals(annotation.values.get(i))) { + return (T) annotation.values.get(i + 1); + } + } + } + return null; } // Adapted from org.spongepowered.asm.mixin.injection.callback.CallbackInjector summariseLocals diff --git a/test/build.gradle.kts b/test/build.gradle.kts index 82799222..41d48221 100644 --- a/test/build.gradle.kts +++ b/test/build.gradle.kts @@ -36,7 +36,6 @@ allprojects { testImplementation(group = "org.sinytra.adapter", name = "core") - testImplementation(project(":runtime")) testImplementation(platform("org.junit:junit-bom:5.9.1")) testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.assertj:assertj-core:3.25.1") diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java index e9eb9434..ec552852 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java @@ -24,7 +24,6 @@ import org.sinytra.adapter.api.TxPhase; import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.patch.test_main.mixin.MinecraftMixinPatchTest; -import org.sinytra.adapter.types.FieldTypeUsageTransformer; import org.slf4j.Logger; import org.spongepowered.asm.mixin.FabricUtil; @@ -58,7 +57,7 @@ public void copyEntries(String from, String to) { FabricUtil.COMPATIBILITY_LATEST ); patcher = Patcher.builder(patchEnvironment) - .classTransformer(new FieldTypeUsageTransformer()) + .classTransformers(DynamicPatches.CLASS_PATCHES) .methodTransformers(ImmutableMultimap.builder() .putAll(DynamicPatches.methodTransformers(List.of())) .put(TxPhase.VALIDATED, context -> { diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/BytecodeFixerUpperTestFrontend.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/BytecodeFixerUpperTestFrontend.java index bf65a003..6f871929 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/BytecodeFixerUpperTestFrontend.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/BytecodeFixerUpperTestFrontend.java @@ -17,6 +17,8 @@ public class BytecodeFixerUpperTestFrontend { private static final List FIELD_TYPE_ADAPTERS = List.of( + new SimpleTypeAdapter(Type.getObjectType("net/minecraft/world/entity/Mob"), Type.getObjectType("net/minecraft/world/entity/monster/Monster"), (list, insn) -> + list.insert(insn, new TypeInsnNode(Opcodes.CHECKCAST, "net/minecraft/world/entity/monster/Monster"))), new SimpleTypeAdapter(Type.getObjectType("net/minecraft/world/item/ItemStack"), Type.getObjectType("net/minecraft/world/item/Item"), (list, insn) -> list.insert(insn, new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "net/minecraft/world/item/ItemStack", "getItem", "()Lnet/minecraft/world/item/Item;"))), new SimpleTypeAdapter( diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 9dd51cb5..77f07ef5 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -4,19 +4,28 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceMethodVisitor; import org.sinytra.adapter.api.ClassLookup; import org.sinytra.adapter.api.DynamicPatches; import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.Patcher; import org.sinytra.adapter.api.RefmapHolder; +import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.patch.test_main.mixin.MinecraftMixinPatchTest; -import org.sinytra.adapter.types.FieldTypeUsageTransformer; +import org.sinytra.adapter.util.AdapterUtil; import org.slf4j.Logger; import org.spongepowered.asm.mixin.FabricUtil; import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + public class DynamicMixinPatchTest extends MinecraftMixinPatchTest { private static final Logger LOGGER = LogUtils.getLogger(); @@ -44,7 +53,7 @@ public void copyEntries(String from, String to) { FabricUtil.COMPATIBILITY_LATEST ); patcher = Patcher.builder(patchEnvironment) - .classTransformer(new FieldTypeUsageTransformer()) + .classTransformers(DynamicPatches.CLASS_PATCHES) .methodTransformers(DynamicPatches.methodTransformers(List.of())) .build(); } @@ -54,6 +63,42 @@ static void postTest() { LOGGER.info("Complete report:\n\n{}", patchEnvironment.auditTrail().getCompleteReport()); } + @Test + void testAnonymousTargetUnchanged() throws Exception { + assertMixinTarget("org/sinytra/adapter/test/mc_26_1_2/mixin/DispenseItemBehaviorMixin", "dispense", "net/minecraft/core/dispenser/DispenseItemBehavior$10"); + } + + @Test + void testAnonymousTargetRenumbered() throws Exception { + assertMixinTarget("org/sinytra/adapter/test/mc_26_1_2/mixin/FunctionCommandExecutorMixin", "readArguments", "net/minecraft/server/commands/FunctionCommand$2"); + } + + @Test + void testFieldTypeAdaptedOnce() throws Exception { + ClassNode patched = load("org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalMixin", List.of("readMob")).patched(); + String once = describeMethod(patched, "readMob"); + patcher.process(patched); + assertEquals(once, describeMethod(patched, "readMob")); + assertEquals(1, once.split("CHECKCAST net/minecraft/world/entity/monster/Monster", -1).length - 1, once); + } + + @Test + void testFieldTypeChangedAccessor() throws Exception { + ClassNode patched = load("org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalAccessor", List.of("getMob")).patched(); + MethodNode accessor = patched.methods.stream().filter(m -> m.name.equals("getMob")).findFirst().orElseThrow(); + assertNull(AdapterUtil.getAnnotation(accessor.visibleAnnotations, MixinAnnotations.ACCESSOR)); + AnnotationNode invoker = AdapterUtil.getAnnotation(accessor.visibleAnnotations, MixinAnnotations.INVOKER); + assertNotNull(invoker); + assertEquals("adapter$mob()Lnet/minecraft/world/entity/monster/Monster;", AdapterUtil.getAnnotationValue(invoker, "value")); + } + + private static String describeMethod(ClassNode classNode, String name) { + MethodNode method = classNode.methods.stream().filter(m -> m.name.equals(name)).findFirst().orElseThrow(); + Textifier textifier = new Textifier(); + method.accept(new TraceMethodVisitor(textifier)); + return String.join("", textifier.getText().stream().map(String::valueOf).toList()); + } + @Test void testExtractUsingCodePath() throws Exception { assertSameCodeExtracted( diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/DispenseItemBehaviorMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/DispenseItemBehaviorMixin.java new file mode 100644 index 00000000..501f0742 --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/DispenseItemBehaviorMixin.java @@ -0,0 +1,15 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import net.minecraft.core.dispenser.BlockSource; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(targets = "net.minecraft.core.dispenser.DispenseItemBehavior$10") +public class DispenseItemBehaviorMixin { + @Inject(method = "execute", at = @At("HEAD")) + private void dispense(BlockSource source, ItemStack dispensed, CallbackInfoReturnable cir) { + } +} diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/FunctionCommandExecutorMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/FunctionCommandExecutorMixin.java new file mode 100644 index 00000000..b4e3f57b --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/FunctionCommandExecutorMixin.java @@ -0,0 +1,16 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.nbt.CompoundTag; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(targets = "net.minecraft.server.commands.FunctionCommand$1") +public class FunctionCommandExecutorMixin { + @Inject(method = "arguments", at = @At("HEAD")) + private void readArguments(CommandContext context, CallbackInfoReturnable cir) { + } +} diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalAccessor.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalAccessor.java new file mode 100644 index 00000000..9feb6242 --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalAccessor.java @@ -0,0 +1,12 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal; +import net.minecraft.world.entity.monster.Monster; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(RangedCrossbowAttackGoal.class) +public interface RangedCrossbowAttackGoalAccessor { + @Accessor + Monster getMob(); +} diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalMixin.java new file mode 100644 index 00000000..5e53942e --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/RangedCrossbowAttackGoalMixin.java @@ -0,0 +1,22 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal; +import net.minecraft.world.entity.monster.Monster; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(RangedCrossbowAttackGoal.class) +public class RangedCrossbowAttackGoalMixin { + @Shadow + @Final + private Monster mob; + + @Inject(method = "tick", at = @At("HEAD")) + private void readMob(CallbackInfo ci) { + this.mob.getTarget(); + } +} diff --git a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java index 97ce79e4..04437290 100644 --- a/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java +++ b/test/src/main/java/org/sinytra/adapter/patch/test_main/mixin/MinecraftMixinPatchTest.java @@ -4,6 +4,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.assertj.core.api.Assertions; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.api.ClassLookup; import org.sinytra.adapter.api.MixinClassGenerator; @@ -148,6 +149,17 @@ protected final void assertSameField( assertEquals(patched.desc, expected.desc, "Field types differ"); } + protected final void assertMixinTarget(String className, String testName, String expectedTarget) throws Exception { + ClassNode patched = load(className, List.of(testName)).patched(); + AnnotationNode mixin = patched.invisibleAnnotations.stream().filter(a -> a.desc.equals(MixinAnnotations.MIXIN)).findFirst().orElseThrow(); + Object targets = new AnnotationHandle(mixin).getValue("targets").or(() -> new AnnotationHandle(mixin).getValue("value")) + .orElseThrow().get(); + List names = ((List) targets).stream() + .map(t -> t instanceof Type type ? type.getInternalName() : ((String) t).replace('.', '/')) + .toList(); + assertEquals(List.of(expectedTarget), names, "Mixin targets"); + } + public static class InsnComparator implements Comparator { @Override public int compare(AbstractInsnNode o1, AbstractInsnNode o2) { From 36feff647b70b5a460660dfd7414ba9975a3f8e1 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 18:05:51 +0200 Subject: [PATCH 37/44] Cleanup local capture upgrade --- .../analysis/injection/InjectionLocator.java | 13 ++ .../analysis/locals/LocalsAnalyzer.java | 10 +- .../adapter/pipeline/AppliedLocalsCheck.java | 5 +- .../pipeline/injector/HandlerSignatures.java | 8 +- .../pipeline/injector/RedirectMixin.java | 3 +- .../injector/WrapWithConditionMixin.java | 3 +- .../adapter/pipeline/locate/Describer.java | 33 +++++ .../processor/SugarLocalProcessor.java | 36 +----- .../processor/relocate/RelocationSolver.java | 2 +- .../resolver/InjectionPointResolver.java | 3 +- .../pipeline/resolver/LocalResolver.java | 5 +- .../LocalCaptureUpgradeTransformer.java | 116 +++++++----------- .../org/sinytra/adapter/util/AdapterUtil.java | 5 - .../mixin/DynamicMixinPatchTest.java | 11 ++ .../sinytra/adapter/test/mixin/GuiMixin.java | 29 +++++ 15 files changed, 146 insertions(+), 136 deletions(-) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java index f6b90e15..c346ed9d 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java @@ -67,6 +67,19 @@ public AbstractInsnNode resolve(@Nullable TargetPair target, InjectionQuery quer return insns.size() == 1 ? insns.getFirst() : null; } + /// {@return the first instruction the mixin's @At matches in the target method, or null if it matches none} + @Nullable + public AbstractInsnNode resolveFirst(@Nullable TargetPair target) { + return resolveFirst(target, InjectionQuery.current()); + } + + /// {@return the first instruction the query matches in the target method, or null if it matches none} + @Nullable + public AbstractInsnNode resolveFirst(@Nullable TargetPair target, InjectionQuery query) { + List insns = find(target, query); + return insns.isEmpty() ? null : insns.getFirst(); + } + /// {@return whether the mixin's @At matches any instructions in the target method} public boolean matches(@Nullable TargetPair target) { return !find(target).isEmpty(); diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index 71fcaf28..e4a58ed6 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -94,7 +94,7 @@ public CapturedLocals resolveCapturedLocals(@Nullable TargetPair dirtyTarget) { return new CapturedLocals(dirtyTarget, isStatic, captured.start(), end, isStatic ? 0 : 1, captured.types(), lvt); } - public record CapturedLocalsInfo(CapturedLocals capturedLocals, LayeredParamsDiffSnapshot diff, List availableTypes) { + public record CapturedLocalsInfo(CapturedLocals capturedLocals, LayeredParamsDiffSnapshot diff) { } @Nullable @@ -110,7 +110,7 @@ public CapturedLocalsInfo resolveCapturedLocalsInfo(TargetPair dirtyTarget) { // Compare expected and available params LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(capturedLocals.expected(), availableTypes, this.context.typeRelation()).snapshot(); - return new CapturedLocalsInfo(capturedLocals, diff, availableTypes); + return new CapturedLocalsInfo(capturedLocals, diff); } public static List findStoreInsns(MethodNode methodNode, int index) { @@ -150,7 +150,7 @@ private static InsnList collectInitializerInsns(MethodNode methodNode, int index return insns; } - public record CapturedLocalsTransform(Collection used, TransformParameters remover, Collection usedLocalNodes) { + public record CapturedLocalsTransform(Collection used, TransformParameters remover) { } public List findSelectableLocals(TargetPair target, AbstractInsnNode insn, Type type) { @@ -241,7 +241,6 @@ public static CapturedLocalsTransform analyzeCapturedLocals(CapturedLocals captu int paramLocalStart = capturedLocals.paramLocalStart(); LocalVariableLookup table = capturedLocals.lvt(); Set used = new HashSet<>(); - Set usedLocalNodes = new HashSet<>(); for (AbstractInsnNode insn : methodNode.instructions) { if (insn instanceof VarInsnNode varInsn) { LocalVariableNode node = table.getByIndexOrNull(varInsn.var); @@ -252,7 +251,6 @@ public static CapturedLocalsTransform analyzeCapturedLocals(CapturedLocals captu continue; if (ordinal >= paramLocalStart && ordinal <= capturedLocals.paramLocalEnd()) { used.add(ordinal); - usedLocalNodes.add(node); } } } @@ -263,6 +261,6 @@ public static CapturedLocalsTransform analyzeCapturedLocals(CapturedLocals captu .boxed().sorted(Collections.reverseOrder()) .forEach(b::remove)) .build(); - return new CapturedLocalsTransform(used, remover, usedLocalNodes); + return new CapturedLocalsTransform(used, remover); } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java b/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java index c1899d5b..e5f756e2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/AppliedLocalsCheck.java @@ -9,13 +9,12 @@ import org.sinytra.adapter.pipeline.injector.MixinFlag; import org.sinytra.adapter.pipeline.locate.LocalLocator; import org.sinytra.adapter.pipeline.processor.SugarLocalProcessor; -import org.sinytra.adapter.util.AdapterUtil; import org.sinytra.adapter.util.MixinBridge; public final class AppliedLocalsCheck { public static void check(Object actor, MixinContext context, TargetPair cleanTarget, TargetPair dirtyTarget) { - AbstractInsnNode cleanInsn = AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)); - AbstractInsnNode dirtyInsn = AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget)); + AbstractInsnNode cleanInsn = context.injections().resolveFirst(cleanTarget); + AbstractInsnNode dirtyInsn = context.injections().resolveFirst(dirtyTarget); if (cleanInsn == null || dirtyInsn == null) { return; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java index b82dc8a9..96128885 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java @@ -21,7 +21,6 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.util.AdapterUtil; import java.util.List; @@ -86,10 +85,7 @@ private static boolean isInvoke(Configuration dirty) { @Nullable private static RedirectedInsn findRedirected(MixinContext context, Recipe recipe, Configuration dirty) { - TargetPair dirtyTarget = recipe.getDirtyTarget(); - AbstractInsnNode insn = dirtyTarget != null && dirty.getAtData() != null - ? AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget, InjectionQuery.of(dirty.getAtData()))) - : null; + AbstractInsnNode insn = dirty.getAtData() != null ? context.injections().resolveFirst(recipe.getDirtyTarget(), InjectionQuery.of(dirty.getAtData())) : null; return insn != null ? RedirectedInsn.of(insn) : null; } @@ -99,7 +95,7 @@ private static List findCallOperands(MixinContext context, Recipe recipe, if (call == null) return null; TargetPair dirtyTarget = recipe.getDirtyTarget(); - AbstractInsnNode insn = dirtyTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget, InjectionQuery.of(dirty.getAtData()))) : null; + AbstractInsnNode insn = context.injections().resolveFirst(dirtyTarget, InjectionQuery.of(dirty.getAtData())); boolean isStatic; String owner; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java index bed851e2..e933cec0 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/RedirectMixin.java @@ -24,7 +24,6 @@ import org.sinytra.adapter.pipeline.resolver.Resolvers; import org.sinytra.adapter.pipeline.resolver.rewrite.InstanceofMakesRedirectRedundant; import org.sinytra.adapter.pipeline.resolver.rewrite.RewriteResolver; -import org.sinytra.adapter.util.AdapterUtil; import java.util.List; @@ -74,7 +73,7 @@ public TxResult preProcess(MixinContext context, MutableConfiguration clean, Res callTypes = Parameters.getCallOperandTypes(isStatic ? null : methodParams.getFirst(), targetDesc.desc()); } else { TargetPair cleanTarget = clean.getTargetMethod() != null ? context.cleanMethods().resolve(clean.getTargetMethod()) : null; - AbstractInsnNode insn = cleanTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)) : null; + AbstractInsnNode insn = context.injections().resolveFirst(cleanTarget); RedirectedInsn redirected = insn != null ? RedirectedInsn.of(insn) : null; if (redirected == null) return TxResult.FAIL; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java index a982916d..d1d81b65 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java @@ -17,7 +17,6 @@ import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.resolver.Resolvers; -import org.sinytra.adapter.util.AdapterUtil; import java.util.List; @@ -49,7 +48,7 @@ public HandlerSignature signature() { @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { TargetPair cleanTarget = clean.getTargetMethod() != null ? context.cleanMethods().resolve(clean.getTargetMethod()) : null; - AbstractInsnNode insn = cleanTarget != null ? AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)) : null; + AbstractInsnNode insn = context.injections().resolveFirst(cleanTarget); RedirectedInsn wrapped = insn != null ? RedirectedInsn.of(insn) : null; List params = Parameters.parse(context.methodNode()); if (wrapped == null || wrapped.operands().size() > params.size()) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java index a09e8cdf..72cfda09 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -4,6 +4,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; @@ -22,6 +23,8 @@ import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.injector.MixinFlag; +import org.sinytra.adapter.util.AdapterUtil; +import org.sinytra.adapter.util.MixinBridge; import org.sinytra.adapter.util.OpcodeUtil; import java.util.ArrayList; @@ -35,6 +38,9 @@ import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_LOAD; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_NEW; import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.AT_VAL_STORE; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ARGS_ONLY; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_INDEX; +import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ORDINAL; public final class Describer { @Nullable @@ -188,6 +194,33 @@ public static boolean isLocalAt(MixinContext context, AtData at) { return context.hasFlag(MixinFlag.TARGETS_VARIABLE) && (AT_VAL_STORE.equals(at.getValue()) || AT_VAL_LOAD.equals(at.getValue())); } + @Nullable + public static AnnotationNode describeLocalSelector(MixinContext context, MixinBridge.Session session, AnnotationNode selector, TargetPair target, + AbstractInsnNode insn, int slot, Type type) { + boolean argsOnly = Boolean.TRUE.equals(AdapterUtil.getAnnotationValue(selector, PROPERTY_ARGS_ONLY)) && isParameter(target.methodNode(), slot); + AnnotationNode adjusted = new AnnotationNode(selector.desc); + if (argsOnly) { + adjusted.visit(PROPERTY_ARGS_ONLY, true); + } + if (AdapterUtil.getAnnotationValue(selector, PROPERTY_INDEX) != null) { + adjusted.visit(PROPERTY_INDEX, slot); + return session.findLocal(adjusted, type, insn) == slot ? adjusted : null; + } + if (AdapterUtil.getAnnotationValue(selector, PROPERTY_ORDINAL) == null && session.findLocal(adjusted, type, insn) == slot) { + return adjusted; + } + int ordinal = argsOnly + ? getParameterOrdinal(target.methodNode(), slot, type) + : context.locals().resolveLocalOrdinal(target, insn, type, slot); + if (ordinal != -1) { + adjusted.visit(PROPERTY_ORDINAL, ordinal); + if (session.findLocal(adjusted, type, insn) == slot) { + return adjusted; + } + } + return null; + } + public static boolean isParameter(MethodNode method, int slot) { return slot < (MethodAnalyzer.isStatic(method) ? 0 : 1) + Arrays.stream(Type.getArgumentTypes(method.desc)).mapToInt(Type::getSize).sum(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java index a3e232b0..71665e6f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/SugarLocalProcessor.java @@ -28,9 +28,6 @@ import java.util.ArrayList; import java.util.List; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ARGS_ONLY; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_INDEX; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ORDINAL; public class SugarLocalProcessor implements Processor { @Override @@ -49,8 +46,8 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe if (locals.isEmpty() || cleanLocals.size() != locals.size()) { return TxResult.PASS; } - AbstractInsnNode cleanInsn = AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)); - AbstractInsnNode dirtyInsn = AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget, InjectionQuery.of(dirtyAt))); + AbstractInsnNode cleanInsn = context.injections().resolveFirst(cleanTarget); + AbstractInsnNode dirtyInsn = context.injections().resolveFirst(dirtyTarget, InjectionQuery.of(dirtyAt)); if (cleanInsn == null || dirtyInsn == null) { return TxResult.PASS; } @@ -88,7 +85,7 @@ public TxResult process(MixinContext context, Configuration dirty, Recipe recipe context.recordCtxAudit("Can't select local %s for @Local parameter %s of type %s", slot, index, type); continue; } - AnnotationNode newSelector = describeSelector(context, dirtySession, selector, dirtyTarget, dirtyInsn, slot, dirtyType); + AnnotationNode newSelector = Describer.describeLocalSelector(context, dirtySession, selector, dirtyTarget, dirtyInsn, slot, dirtyType); if (newSelector == null) { context.recordCtxAudit("Can't select local %s for @Local parameter %s", slot, index); continue; @@ -158,33 +155,6 @@ private static Boolean compareTypes(MixinContext context, Type type, Type dirtyT return null; } - @Nullable - private static AnnotationNode describeSelector(MixinContext context, MixinBridge.Session session, AnnotationNode selector, TargetPair target, - AbstractInsnNode insn, int slot, Type type) { - boolean argsOnly = Boolean.TRUE.equals(AdapterUtil.getAnnotationValue(selector, PROPERTY_ARGS_ONLY)) && Describer.isParameter(target.methodNode(), slot); - AnnotationNode adjusted = new AnnotationNode(selector.desc); - if (argsOnly) { - adjusted.visit(PROPERTY_ARGS_ONLY, true); - } - if (AdapterUtil.getAnnotationValue(selector, PROPERTY_INDEX) != null) { - adjusted.visit(PROPERTY_INDEX, slot); - return session.findLocal(adjusted, type, insn) == slot ? adjusted : null; - } - if (AdapterUtil.getAnnotationValue(selector, PROPERTY_ORDINAL) == null && session.findLocal(adjusted, type, insn) == slot) { - return adjusted; - } - int ordinal = argsOnly - ? Describer.getParameterOrdinal(target.methodNode(), slot, type) - : context.locals().resolveLocalOrdinal(target, insn, type, slot); - if (ordinal != -1) { - adjusted.visit(PROPERTY_ORDINAL, ordinal); - if (session.findLocal(adjusted, type, insn) == slot) { - return adjusted; - } - } - return null; - } - private static void updateModel(@Nullable MethodParameters params, MethodNode handler, int index) { if (params == null) { return; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java index efe84eb2..e2d8a9c4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java @@ -130,7 +130,7 @@ private static boolean selectsLocals(MixinContext context, Configuration dirty, return true; } AtData at = dirty.getAtData(); - AbstractInsnNode insn = at != null ? AdapterUtil.getFirstOrNull(context.injections().find(newTarget, InjectionQuery.of(at))) : null; + AbstractInsnNode insn = at != null ? context.injections().resolveFirst(newTarget, InjectionQuery.of(at)) : null; if (insn == null) { return false; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java index 18bb0ccf..b3095f9a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java @@ -19,7 +19,6 @@ import org.sinytra.adapter.pipeline.locate.Scope; import org.sinytra.adapter.pipeline.locate.ScopeSearch; import org.sinytra.adapter.pipeline.locate.SelectedLocator; -import org.sinytra.adapter.util.AdapterUtil; import java.util.ArrayList; import java.util.List; @@ -88,7 +87,7 @@ private static boolean selectsSameLocal(MixinContext context, Recipe recipe, Tar if (cleanTarget == null || cleanAt == null || !Describer.isLocalAt(context, cleanAt)) { return true; } - AbstractInsnNode cleanInsn = AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)); + AbstractInsnNode cleanInsn = context.injections().resolveFirst(cleanTarget); if (cleanInsn == null) { return true; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/LocalResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/LocalResolver.java index ebaed16d..733e2ae2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/LocalResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/LocalResolver.java @@ -12,7 +12,6 @@ import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.pipeline.locate.Describer; import org.sinytra.adapter.pipeline.locate.LocalLocator; -import org.sinytra.adapter.util.AdapterUtil; public final class LocalResolver implements Resolver { @Override @@ -30,8 +29,8 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { if (cleanTarget == null || dirtyTarget == null) { return ResolutionResult.pass(); } - AbstractInsnNode cleanInsn = AdapterUtil.getFirstOrNull(context.injections().find(cleanTarget)); - AbstractInsnNode dirtyInsn = AdapterUtil.getFirstOrNull(context.injections().find(dirtyTarget, InjectionQuery.of(at))); + AbstractInsnNode cleanInsn = context.injections().resolveFirst(cleanTarget); + AbstractInsnNode dirtyInsn = context.injections().resolveFirst(dirtyTarget, InjectionQuery.of(at)); if (cleanInsn == null || dirtyInsn == null) { return ResolutionResult.pass(); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/transform/LocalCaptureUpgradeTransformer.java b/core/src/main/java/org/sinytra/adapter/pipeline/transform/LocalCaptureUpgradeTransformer.java index 528c45bd..454f0ede 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/transform/LocalCaptureUpgradeTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/transform/LocalCaptureUpgradeTransformer.java @@ -1,12 +1,10 @@ package org.sinytra.adapter.pipeline.transform; -import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.locals.LocalVariable; -import org.sinytra.adapter.analysis.locals.LocalVariableLookup; import org.sinytra.adapter.analysis.locals.LocalsAnalyzer; import org.sinytra.adapter.api.AuditTrail; import org.sinytra.adapter.api.MethodPatchContext; @@ -18,11 +16,14 @@ import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import org.sinytra.adapter.util.MixinBridge.LocalCandidate; +import org.sinytra.adapter.pipeline.locate.Describer; +import org.sinytra.adapter.pipeline.locate.LocalLocator; +import org.sinytra.adapter.util.MixinBridge; -import java.util.*; - -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.PROPERTY_ORDINAL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class LocalCaptureUpgradeTransformer implements MethodTransformer { @Override @@ -32,85 +33,54 @@ public PatchResult apply(MethodPatchContext patchContext) { } public PatchResult apply(MixinContext context, Configuration config) { - // Check requirements + MethodNode handler = context.methodNode(); Targets targets = context.targets(config.getTargetMethod()); - if (targets == null) return PatchResult.PASS; - - if (!config.hasProperty(MixinKeys.LOCALS)) return PatchResult.PASS; - - // Analyze locals - MethodNode methodNode = context.methodNode(); - // Already upgraded - if (Parameters.parse(methodNode).stream().anyMatch(p -> p.hasAnnotation(MixinAnnotations.LOCAL))) return PatchResult.PASS; + if (targets == null || !config.hasProperty(MixinKeys.LOCALS) || Parameters.parse(handler).stream().anyMatch(p -> p.hasAnnotation(MixinAnnotations.LOCAL))) { + return PatchResult.PASS; + } LocalsAnalyzer.CapturedLocalsInfo info = context.locals().resolveCapturedLocalsInfo(targets.dirty()); - if (info == null || info.diff().isEmpty()) return PatchResult.PASS; - - LocalsAnalyzer.CapturedLocalsTransform transform = LocalsAnalyzer.analyzeCapturedLocals(info.capturedLocals(), methodNode); - - List cleanInsns = context.injections().find(targets.clean()); - List dirtyInsns = context.injections().find(targets.dirty()); - List cleanAvailable = context.locals().resolveAvailableLocals(targets.clean()); - if (cleanInsns.isEmpty() || dirtyInsns.isEmpty() || cleanAvailable == null) return PatchResult.PASS; - - LocalVariableLookup lookup = info.capturedLocals().lvt(); - int capturedStart = info.capturedLocals().paramLocalStart(); - Type[] args = Type.getArgumentTypes(methodNode.desc); - Map parameterToOrdinal = new HashMap<>(); - Set usedOrdinals = new HashSet<>(); - for (LocalVariableNode node : transform.usedLocalNodes()) { - Type expected = Type.getType(node.desc); - boolean addOrdinal = true; - - int paramOrdinal = lookup.getParameterOrdinal(node); - if (paramOrdinal < capturedStart) return PatchResult.PASS; - long typedPosition = Arrays.stream(args, capturedStart, paramOrdinal).filter(expected::equals).count(); - LocalVariable cleanLocal = cleanAvailable.stream() - .filter(l -> l.type().equals(expected)) - .skip(typedPosition) - .findFirst() - .orElse(null); - if (cleanLocal == null) return PatchResult.PASS; + if (info == null || info.diff().isEmpty()) { + return PatchResult.PASS; + } - List cleanLocals = context.locals().findSelectableLocals(targets.clean(), cleanInsns.getFirst(), expected); - List dirtyLocals = context.locals().findSelectableLocals(targets.dirty(), dirtyInsns.getFirst(), expected); - if (cleanLocals.size() != dirtyLocals.size()) { - long matching = info.availableTypes().stream() - .filter(expected::equals) - .count(); - if (matching != 1) - return PatchResult.PASS; + AbstractInsnNode cleanInsn = context.injections().resolveFirst(targets.clean()); + AbstractInsnNode dirtyInsn = context.injections().resolveFirst(targets.dirty()); + List cleanLocals = context.locals().resolveAvailableLocals(targets.clean()); + if (cleanInsn == null || dirtyInsn == null || cleanLocals == null) { + return PatchResult.PASS; + } - addOrdinal = false; + LocalsAnalyzer.CapturedLocals captured = info.capturedLocals(); + LocalsAnalyzer.CapturedLocalsTransform transform = LocalsAnalyzer.analyzeCapturedLocals(captured, handler); + MixinBridge.Session dirtySession = MixinBridge.session(context.classNode().name, handler, targets.dirty().classNode(), targets.dirty().methodNode(), context.environment()); + Type[] params = Type.getArgumentTypes(handler.desc); + Map selectors = new HashMap<>(); + + for (int param : transform.used()) { + Type type = params[param]; + long rank = Arrays.stream(params, captured.paramLocalStart(), param).filter(type::equals).count(); + LocalVariable cleanLocal = cleanLocals.stream().filter(local -> local.type().equals(type)).skip(rank).findFirst().orElse(null); + if (cleanLocal == null) { + return PatchResult.PASS; } - int localOrdinal = cleanLocals.stream() - .filter(c -> c.index() == cleanLocal.index()) - .mapToInt(LocalCandidate::ordinal) - .findFirst() - .orElse(-1); - if (localOrdinal == -1) return PatchResult.PASS; - - usedOrdinals.add(paramOrdinal); - - if (addOrdinal && cleanLocals.size() > 1) { - parameterToOrdinal.put(paramOrdinal, localOrdinal); + int slot = LocalLocator.resolve(context, targets.clean(), targets.dirty(), cleanLocal.index(), cleanInsn, targets.dirty()); + AnnotationNode selector = slot == -1 ? null : Describer.describeLocalSelector(context, dirtySession, new AnnotationNode(MixinAnnotations.LOCAL), targets.dirty(), dirtyInsn, slot, type); + if (selector == null) { + return PatchResult.PASS; } - } - - int start = info.capturedLocals().paramLocalStart(); - for (int i = start; i < args.length; i++) { - if (!usedOrdinals.contains(i)) continue; - AnnotationVisitor visitor = methodNode.visitParameterAnnotation(i, MixinAnnotations.LOCAL, false); - if (parameterToOrdinal.containsKey(i)) { - visitor.visit(PROPERTY_ORDINAL, parameterToOrdinal.get(i)); - } + selectors.put(param, selector); } + selectors.forEach((param, selector) -> selector.accept(handler.visitParameterAnnotation(param, MixinAnnotations.LOCAL, false))); PatchResult result = transform.remover().isEmpty() ? PatchResult.APPLY : transform.remover().apply(context); - if (result == PatchResult.PASS) return PatchResult.PASS; + if (result == PatchResult.PASS) { + return PatchResult.PASS; + } + context.methodAnnotation().setOrAppendNonNull(MixinKeys.TARGET_METHOD.name(), config.serialize(MixinKeys.TARGET_METHOD, config.getTargetMethod())); context.recordCtxAudit("Upgrade captured locals"); context.environment().auditTrail().recordResult(context, config, AuditTrail.Match.FULL); return result; diff --git a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java index fcc3ca88..7570ef10 100644 --- a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java +++ b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java @@ -232,11 +232,6 @@ public static AbstractInsnNode getSingleInsn(SourceValue value) { return value.insns.size() == 1 ? value.insns.iterator().next() : null; } - @Nullable - public static T getFirstOrNull(List list) { - return list.isEmpty() ? null : list.getFirst(); - } - @Nullable public static T getSingleOrNull(Collection collection) { return collection.size() == 1 ? collection.iterator().next() : null; diff --git a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java index ec552852..2ac40644 100644 --- a/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java +++ b/test/mc-1.21.1/src/test/java/org/sinytra/adapter/patch/test/mc_1_21_1/mixin/DynamicMixinPatchTest.java @@ -539,6 +539,17 @@ void testDynamicLocalCaptureupgrade() throws Exception { ); } + @Test + void testLocalCaptureUpgradeOrdinal() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/GuiMixin", + "captureSecondInt", + assertTargetMethod(), + assertInjectionPoint(), + assertLocalSelectors() + ); + } + @Test void testSyntheticInstanceof() throws Exception { assertSameCode( diff --git a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java index e7eff6fa..dfa1bbc6 100644 --- a/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java +++ b/test/mc-1.21.1/src/testClasses/java/org/sinytra/adapter/test/mixin/GuiMixin.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Iterator; +import java.util.List; @Mixin(Gui.class) public class GuiMixin { @@ -99,6 +100,34 @@ private void cozyBackgroundExpected(GuiGraphics context, DeltaTracker tickCounte boolean something = registryEntry == null; } + @Inject( + method = "renderEffects", + at = @At( + value = "INVOKE_ASSIGN", + target = "Lnet/minecraft/world/effect/MobEffectInstance;getEffect()Lnet/minecraft/core/Holder;", + shift = At.Shift.AFTER + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void captureSecondInt(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci, Collection collection, int i, int j, + MobEffectTextureManager statusEffectSpriteManager, List list, Iterator var8, MobEffectInstance statusEffectInstance, Holder registryEntry + ) { + int y = j; + } + + @Inject( + method = "renderEffects(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", + at = @At( + value = "INVOKE_ASSIGN", + target = "Lnet/minecraft/world/effect/MobEffectInstance;getEffect()Lnet/minecraft/core/Holder;", + shift = At.Shift.AFTER + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void captureSecondIntExpected(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci, @Local(ordinal = 1) int j) { + int y = j; + } + // https://github.com/SkyblockerMod/Skyblocker/blob/fa67224da0cdcd2325b24f41732e820ff7ef04e8/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java#L97 @ModifyExpressionValue( method = "renderPlayerHealth(Lnet/minecraft/client/gui/GuiGraphics;)V", From 776452c867bfb4644cf56e0c9b7c8bd7ec3cc39e Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 18:20:05 +0200 Subject: [PATCH 38/44] Unwrap ParamsDiff --- .../analysis/locals/LocalsAnalyzer.java | 4 +-- .../adapter/analysis/params/ParamsDiff.java | 4 --- .../analysis/params/ParamsEditDistance.java | 11 +++--- .../signature/ParameterCastStep.java | 2 +- .../processor/signature/ParametersStep.java | 2 +- .../signature/RedirectedCallStep.java | 2 +- .../patch/test/ParamDiffResolverTest.java | 2 +- .../patch/test/ParameterComparisonTest.java | 4 +-- .../patch/test/ParamsDiffRegressionTest.java | 36 +++++++++---------- 9 files changed, 31 insertions(+), 36 deletions(-) delete mode 100644 core/src/main/java/org/sinytra/adapter/analysis/params/ParamsDiff.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java index e4a58ed6..fb6d1034 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/locals/LocalsAnalyzer.java @@ -108,7 +108,7 @@ public CapturedLocalsInfo resolveCapturedLocalsInfo(TargetPair dirtyTarget) { List availableTypes = available.stream().map(LocalVariable::type).toList(); // Compare expected and available params - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(capturedLocals.expected(), availableTypes, this.context.typeRelation()).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(capturedLocals.expected(), availableTypes, this.context.typeRelation()); return new CapturedLocalsInfo(capturedLocals, diff); } @@ -210,7 +210,7 @@ public List findDirtyCapturedParams(Configuration clean, Configuration dir MethodNode cleanTarget = cleanPair.methodNode(); MethodNode dirtyTarget = dirtyPair.methodNode(); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffParameters(cleanTarget, dirtyTarget, this.context.typeRelation()).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffParameters(cleanTarget, dirtyTarget, this.context.typeRelation()); List cleanTargetParams = Parameters.getParameterTypes(cleanTarget.desc); if (cleanCaptured.size() > cleanTargetParams.size()) return dirtyCaptured; diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsDiff.java b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsDiff.java deleted file mode 100644 index 055349b0..00000000 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsDiff.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.sinytra.adapter.analysis.params; - -public record ParamsDiff(LayeredParamsDiffSnapshot snapshot, double cost) { -} diff --git a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java index 2fd9ee2b..7d125c57 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/params/ParamsEditDistance.java @@ -43,21 +43,20 @@ private record Table(int[][] costs, int[][][] swapFrom) { private record Slot(Type type, int target) { } - public static ParamsDiff diff(List clean, List dirty, TypeRelation relation) { + public static LayeredParamsDiffSnapshot diff(List clean, List dirty, TypeRelation relation) { Table table = computeCosts(clean, dirty, relation); - List steps = backtrack(clean, dirty, relation, table); - return new ParamsDiff(toSnapshot(clean, dirty, steps), table.costs()[clean.size()][dirty.size()] / 100.0); + return toSnapshot(clean, dirty, backtrack(clean, dirty, relation, table)); } - public static ParamsDiff diffTypes(List clean, List dirty) { + public static LayeredParamsDiffSnapshot diffTypes(List clean, List dirty) { return diffTypes(clean, dirty, TypeRelation.STATIC); } - public static ParamsDiff diffTypes(List clean, List dirty, TypeRelation relation) { + public static LayeredParamsDiffSnapshot diffTypes(List clean, List dirty, TypeRelation relation) { return diff(Param.of(clean), Param.of(dirty), relation); } - public static ParamsDiff diffParameters(MethodNode clean, MethodNode dirty, TypeRelation relation) { + public static LayeredParamsDiffSnapshot diffParameters(MethodNode clean, MethodNode dirty, TypeRelation relation) { return diff(getParameters(clean), getParameters(dirty), relation); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java index e53696d7..a5519c13 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java @@ -48,7 +48,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) List cleanTypes = cleanParams.getTypes(MethodParameters.ParamGroup.METHOD_PARAMS); List dirtyTypes = dirtyParams.getTypes(MethodParameters.ParamGroup.METHOD_PARAMS); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanTypes, dirtyTypes, context.typeRelation()).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanTypes, dirtyTypes, context.typeRelation()); MethodNode methodNode = context.methodNode(); LocalVariableLookup lookup = LocalVariableLookup.of(context.methodNode()); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java index a806d1f3..c777ad03 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java @@ -55,7 +55,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) } private boolean applyDiff(List clean, List dirty, MixinContext context, int offset) { - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty, context.typeRelation()).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty, context.typeRelation()); if (!diff.isEmpty()) { PatchResult result = diff.offset(offset).asParameterTransformer(false) .apply(context); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java index 1cf6e7c9..f95d5fcb 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java @@ -37,7 +37,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) List cleanParams = recipe.clean().getParameters().getTypes(ParamGroup.METHOD_PARAMS); List dirtyParams = dirtyMethodParams.getTypes(ParamGroup.METHOD_PARAMS); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanParams, dirtyParams, context.typeRelation()).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanParams, dirtyParams, context.typeRelation()); List> callInsns = DataFlow.of(context.methodNode(), DataFlow.Producers.COMPUTER).findCallsWithProducers(cleanTargetQual); if (callInsns.isEmpty()) { diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java index 3d5d68cf..d08f21cc 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/ParamDiffResolverTest.java @@ -65,7 +65,7 @@ private static void testParams(List clean, List dirty, int oldPos, i private static void testParams(List clean, List dirty, int oldPos, int newPos, Type newType) { Type original = clean.get(oldPos); - LayeredParamsDiffSnapshot snapshot = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); + LayeredParamsDiffSnapshot snapshot = ParamsEditDistance.diffTypes(clean, dirty); ParamEvalResult result = ParamDiffResolver.resolve(clean, snapshot); ParamState updated = result.getUpdated(original); diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/ParameterComparisonTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/ParameterComparisonTest.java index 307b73b2..4d64c51c 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/ParameterComparisonTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/ParameterComparisonTest.java @@ -65,7 +65,7 @@ public void testMethodParameterNameComparison() throws IOException { MethodNode cleanMethod = cleanNode.methods.stream().filter(m -> m.name.equals("namedInsertionTest")).findFirst().orElseThrow(); MethodNode dirtyMethod = dirtyNode.methods.stream().filter(m -> m.name.equals("namedInsertionTest")).findFirst().orElseThrow(); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffParameters(cleanMethod, dirtyMethod, TypeRelation.STATIC).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffParameters(cleanMethod, dirtyMethod, TypeRelation.STATIC); assertEquals(1, diff.insertions().size()); assertEquals(7, diff.insertions().getFirst().index()); assertEquals(Type.FLOAT_TYPE, diff.insertions().getFirst().type()); @@ -85,6 +85,6 @@ public void testCompareRemovedParameters() { } private static LayeredParamsDiffSnapshot diff(List clean, List dirty) { - return ParamsEditDistance.diffTypes(clean, dirty).snapshot(); + return ParamsEditDistance.diffTypes(clean, dirty); } } diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/ParamsDiffRegressionTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/ParamsDiffRegressionTest.java index b2443abb..20251604 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/ParamsDiffRegressionTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/ParamsDiffRegressionTest.java @@ -37,7 +37,7 @@ void testMovedParameter() { Type.BOOLEAN_TYPE ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty); assertEquals(1, diff.insertions().size()); assertTrue(diff.replacements().isEmpty()); assertTrue(diff.removals().isEmpty()); @@ -60,7 +60,7 @@ void testReplacedParameters() { Type.getObjectType("com/llamalad7/mixinextras/sugar/ref/LocalRef") ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty); assertTrue(diff.insertions().isEmpty()); assertEquals(2, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -81,7 +81,7 @@ void testRemovedParameter() { Type.getType("Lnet/minecraft/world/item/Item;") ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty); assertTrue(diff.insertions().isEmpty()); assertTrue(diff.replacements().isEmpty()); assertEquals(1, diff.removals().size()); @@ -105,7 +105,7 @@ void testCompareSwappedParameters() { Type.getObjectType("net/minecraft/world/item/ItemStack") ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertTrue(diff.insertions().isEmpty()); assertTrue(diff.replacements().isEmpty()); assertTrue(diff.removals().isEmpty()); @@ -128,7 +128,7 @@ void testCompareInsertedParameters() { Type.getType(Object.class) ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(2, diff.insertions().size()); assertTrue(diff.replacements().isEmpty()); assertTrue(diff.removals().isEmpty()); @@ -152,7 +152,7 @@ void testCompareInsertedParameters2() { Type.getType(Object.class) ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(1, diff.insertions().size()); assertEquals(2, diff.insertions().getFirst().index()); assertTrue(diff.replacements().isEmpty()); @@ -186,7 +186,7 @@ void testCompareInsertedParameters3() { new InjectParameterTransform(7, Type.INT_TYPE) ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(6, diff.insertions().size()); assertEquals(expectedInsertions, diff.insertions()); assertTrue(diff.replacements().isEmpty()); @@ -230,7 +230,7 @@ void testCompareInsertedParametersOrder() { new InjectParameterTransform(12, Type.getType(String.class)) ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(9, diff.insertions().size()); assertEquals(expectedInsertions, diff.insertions()); assertTrue(diff.replacements().isEmpty()); @@ -271,7 +271,7 @@ void testCompareInsertedParametersIndexOffset() { Type.INT_TYPE ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(5, diff.insertions().size()); assertEquals(1, diff.insertions().get(0).index()); assertEquals(9, diff.insertions().get(1).index()); @@ -297,7 +297,7 @@ void testCompareReplacedParameters() { Type.getType(List.class) ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertTrue(diff.insertions().isEmpty()); assertEquals(1, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -318,7 +318,7 @@ void testCompareReplacedParameters2() { Type.getType(List.class) ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertTrue(diff.insertions().isEmpty()); assertEquals(1, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -339,7 +339,7 @@ void testCompareReplacedParameters3() { Type.getObjectType("com/llamalad7/mixinextras/injector/wrapoperation/Operation") ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertTrue(diff.insertions().isEmpty()); assertEquals(1, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -362,7 +362,7 @@ void testCompareCombinedParameters() { Type.DOUBLE_TYPE ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(2, diff.insertions().size()); assertEquals(1, diff.replacements().size()); assertTrue(diff.removals().isEmpty()); @@ -386,7 +386,7 @@ void testCompareInsertedParametersAtDifferentPlaces() { Type.DOUBLE_TYPE ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(3, diff.insertions().size()); assertEquals(new InjectParameterTransform(1, Type.FLOAT_TYPE), diff.insertions().get(0)); assertEquals(new InjectParameterTransform(4, Type.FLOAT_TYPE), diff.insertions().get(1)); @@ -412,7 +412,7 @@ void testCompareRemovedParameters() { Type.DOUBLE_TYPE ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(1, diff.insertions().size()); assertEquals(new InjectParameterTransform(3, Type.DOUBLE_TYPE), diff.insertions().getFirst()); assertTrue(diff.replacements().isEmpty()); @@ -441,7 +441,7 @@ void testCompareReorderedParameters() { Type.getType(Set.class) ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(1, diff.insertions().size()); assertEquals(new InjectParameterTransform(2, Type.getType(Deque.class)), diff.insertions().getFirst()); assertTrue(diff.replacements().isEmpty()); @@ -459,7 +459,7 @@ void testCompareComplexChanges() { Type.getType(Object.class) ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(1, diff.replacements().size()); assertEquals(new ReplaceParametersTransformer(0, Type.getType(Object.class)), diff.replacements().getFirst()); assertEquals(1, diff.removals().size()); @@ -480,7 +480,7 @@ void testRemovedParamsOrder() { Type.getType(String.class) ); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified).snapshot(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(original, modified); assertEquals(List.of(5, 4, 3, 2, 1), diff.removals().stream().map(RemoveParameterTransformer::index).toList()); } } From 2de1cf4f2014247ce845d94403b7f9f450ba28fa Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 18:22:32 +0200 Subject: [PATCH 39/44] Move findReturnInsns to DataFlow --- .../adapter/analysis/flow/DataFlow.java | 22 +++++++++++++++++++ .../analysis/method/MethodAnalyzer.java | 11 ---------- .../injector/ModifyReturnValueMixin.java | 3 +-- .../processor/signature/ReturnTypeStep.java | 10 +-------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java b/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java index 25d42010..a0a1940e 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/flow/DataFlow.java @@ -9,6 +9,7 @@ import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.util.AdapterUtil; +import org.sinytra.adapter.util.OpcodeUtil; import java.util.*; import java.util.function.BiConsumer; @@ -87,6 +88,27 @@ public List operands(AbstractInsnNode insn) { return frame != null ? operands(frame, insn) : List.of(); } + public List findReturns() { + List returns = new ArrayList<>(); + for (AbstractInsnNode insn : this.method.instructions) { + if (OpcodeUtil.isReturnOpcode(insn.getOpcode())) { + returns.add(insn); + } + } + return returns; + } + + public List findReturnedValues() { + List producers = new ArrayList<>(); + Set seen = new HashSet<>(); + for (AbstractInsnNode ret : findReturns()) { + for (SourceValue value : operands(ret)) { + value.insns.stream().filter(seen::add).forEach(producers::add); + } + } + return producers; + } + /// {@return a single producer of each argument of the call or null if an arg has more producers} @Nullable public List resolveCallOperandProducers(MethodInsnNode call) { diff --git a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java index 73645646..0c9f0c47 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/method/MethodAnalyzer.java @@ -8,7 +8,6 @@ import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.OpcodeUtil; import java.util.*; @@ -55,16 +54,6 @@ public static List findOwnIndyHandles(ClassNode cls, MethodNode meth return handles; } - public static List findReturnInsns(MethodNode methodNode) { - List insns = new ArrayList<>(); - for (AbstractInsnNode insn : methodNode.instructions) { - if (OpcodeUtil.isReturnOpcode(insn.getOpcode())) { - insns.add(insn); - } - } - return insns; - } - public static Multimap getMethodCalls(MethodNode node) { ImmutableMultimap.Builder calls = ImmutableMultimap.builder(); for (AbstractInsnNode insn : node.instructions) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java index a152d404..8d4ffff4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java @@ -1,6 +1,5 @@ package org.sinytra.adapter.pipeline.injector; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.ann.AtData; @@ -51,7 +50,7 @@ public boolean canInject(MixinContext context, Configuration config) { } Targets targets = context.targets(target); - return targets == null || MethodAnalyzer.findReturnInsns(targets.clean().methodNode()).size() == MethodAnalyzer.findReturnInsns(targets.dirty().methodNode()).size(); + return targets == null || context.dataFlow(targets.clean()).findReturns().size() == context.dataFlow(targets.dirty()).findReturns().size(); } @Override diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ReturnTypeStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ReturnTypeStep.java index fbeb531b..5e4a6e68 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ReturnTypeStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ReturnTypeStep.java @@ -5,15 +5,12 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.flow.DataFlow; -import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.api.TypeAdapter; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; -import java.util.LinkedHashSet; -import java.util.Set; public class ReturnTypeStep implements SignatureStep { @Override @@ -31,12 +28,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) } MethodNode methodNode = context.methodNode(); - DataFlow flow = DataFlow.of(methodNode); - Set sources = new LinkedHashSet<>(); - for (AbstractInsnNode ret : MethodAnalyzer.findReturnInsns(methodNode)) { - flow.operands(ret).forEach(value -> sources.addAll(value.insns)); - } - for (AbstractInsnNode insn : sources) { + for (AbstractInsnNode insn : DataFlow.of(methodNode).findReturnedValues()) { if (insn.getOpcode() != Opcodes.ACONST_NULL) { adapter.apply(methodNode.instructions, insn); } From 45c9208cfeb2689b9ff2ea8b0b5910450e03d209 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 18:27:44 +0200 Subject: [PATCH 40/44] Cleanup SignatureSteps --- .../signature/OperationCallStep.java | 50 ++++++-------- .../signature/ParameterCastStep.java | 66 +++++++++---------- .../processor/signature/ParametersStep.java | 39 ++++------- .../signature/RedirectedCallStep.java | 9 +-- .../processor/signature/StaticAccessStep.java | 64 +++++++----------- 5 files changed, 89 insertions(+), 139 deletions(-) diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java index c0da0544..d5989ab3 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/OperationCallStep.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.IntStream; import static org.sinytra.adapter.util.AdapterUtil.MIXINPATCH; @@ -37,9 +36,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) } for (int i = 0; i < dirtyCalls.size(); i++) { - List cleanCall = cleanCalls.get(i); - List dirtyCall = dirtyCalls.get(i); - if (!upgradeOriginalCall(context, dirty, cleanCall, dirtyCall)) { + if (!upgradeOriginalCall(context, dirty, cleanCalls.get(i), dirtyCalls.get(i))) { return TxResult.FAIL; } } @@ -60,40 +57,31 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) } private static boolean upgradeOriginalCall(MixinContext context, Configuration dirty, List cleanCallInsns, List dirtyCallInsns) { - WrapOpOriginalCall cleanCall = WrapOpOriginalCall.parse(context.unmodifiedMethodNode(), cleanCallInsns); - if (cleanCall == null) return false; - - WrapOpOriginalCall dirtyCall = WrapOpOriginalCall.parse(context.methodNode(), dirtyCallInsns); - if (dirtyCall == null) return false; - - AbstractInsnNode arrayInit = dirtyCallInsns.get(1); - List params = dirty.getParameters().get(MethodParameters.ParamGroup.METHOD_PARAMS); - LocalVariableLookup lookup = LocalVariableLookup.of(context.methodNode()); - if (lookup == null || IntStream.range(0, params.size()).anyMatch(i -> lookup.getByParameterOrdinalOrNull(i) == null)) { + if (lookup == null || !(dirtyCallInsns.getLast() instanceof MethodInsnNode dirtyInvoke) + || WrapOpOriginalCall.parse(context.unmodifiedMethodNode(), cleanCallInsns) == null + || WrapOpOriginalCall.parse(context.methodNode(), dirtyCallInsns) == null) { return false; } - List args = IntStream.range(0, params.size()) - .mapToObj(i -> { - Parameter param = params.get(i); - int index = lookup.getByParameterOrdinalOrNull(i).index; - Type type = param.type(); - - List loadInsns = new ArrayList<>(); - loadInsns.add(AdapterUtil.loadType(type, index)); - if (type.getSort() < Type.ARRAY) { - loadInsns.add(AdapterUtil.box(type)); - } - - return WrapOpOriginalCall.CallArg.create(i, loadInsns); - }) - .toList(); + List params = dirty.getParameters().get(MethodParameters.ParamGroup.METHOD_PARAMS); + List args = new ArrayList<>(); + for (int i = 0; i < params.size(); i++) { + LocalVariableNode local = lookup.getByParameterOrdinalOrNull(i); + if (local == null) { + return false; + } + Type type = params.get(i).type(); + List loadInsns = new ArrayList<>(List.of(AdapterUtil.loadType(type, local.index))); + if (type.getSort() < Type.ARRAY) { + loadInsns.add(AdapterUtil.box(type)); + } + args.add(WrapOpOriginalCall.CallArg.create(i, loadInsns)); + } WrapOpOriginalCall reconstruct = new WrapOpOriginalCall(args); + AbstractInsnNode arrayInit = dirtyCallInsns.get(1); AbstractInsnNode start = arrayInit.getNext(); // After ANEWARRAY - AbstractInsnNode dirtyInvoke = dirtyCallInsns.getLast(); - if (!(dirtyInvoke instanceof MethodInsnNode)) return false; AbstractInsnNode end = dirtyInvoke.getPrevious(); // Before INVOKEVIRTUAL call() InsnList methodInsns = context.methodNode().instructions; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java index a5519c13..b2fc7e5c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParameterCastStep.java @@ -10,12 +10,16 @@ import org.sinytra.adapter.edit.param.ReplaceParametersTransformer; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.MethodParameters.ParamGroup; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * If a mixin has its parameter changed to a higher class e.g. ServerPlayer -> LivingEntity but @@ -38,52 +42,46 @@ */ // TODO Add type safety check public class ParameterCastStep implements SignatureStep { + private record Cast(VarInsnNode load, Type type) { + } + @Override public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { MethodParameters cleanParams = recipe.clean().getParameters(); MethodParameters dirtyParams = dirty.getParameters(); - if (dirtyParams == null || !cleanParams.has(MethodParameters.ParamGroup.METHOD_PARAMS) || !dirtyParams.has(MethodParameters.ParamGroup.METHOD_PARAMS)) { + MethodNode methodNode = context.methodNode(); + LocalVariableLookup lookup = LocalVariableLookup.of(methodNode); + if (dirtyParams == null || lookup == null || !cleanParams.has(ParamGroup.METHOD_PARAMS) || !dirtyParams.has(ParamGroup.METHOD_PARAMS)) { return TxResult.PASS; } - List cleanTypes = cleanParams.getTypes(MethodParameters.ParamGroup.METHOD_PARAMS); - List dirtyTypes = dirtyParams.getTypes(MethodParameters.ParamGroup.METHOD_PARAMS); - LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanTypes, dirtyTypes, context.typeRelation()); - - MethodNode methodNode = context.methodNode(); - LocalVariableLookup lookup = LocalVariableLookup.of(context.methodNode()); - if (lookup == null) return TxResult.PASS; - - boolean matched = false; + Map replaced = new HashMap<>(); + LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(cleanParams.getTypes(ParamGroup.METHOD_PARAMS), dirtyParams.getTypes(ParamGroup.METHOD_PARAMS), + context.typeRelation()); for (ReplaceParametersTransformer replacement : diff.replacements()) { - int ordinal = replacement.index(); - LocalVariableNode node = lookup.getByParameterOrdinalOrNull(ordinal); - if (node == null) continue; - - Type currentType = Type.getType(node.desc); - - for (AbstractInsnNode insn : methodNode.instructions) { - if (!(insn instanceof MethodInsnNode minsn)) continue; + LocalVariableNode node = lookup.getByParameterOrdinalOrNull(replacement.index()); + if (node != null) { + replaced.put(node.index, Type.getType(node.desc)); + } + } - // Add casts to usage in method calls - List methodArgs = Parameters.getParameterTypes(minsn.desc); - List callArgs = DataFlow.of(methodNode).resolveCallOperandProducers(minsn); - if (callArgs == null) continue; + List casts = new ArrayList<>(); + DataFlow flow = DataFlow.of(methodNode); + for (AbstractInsnNode insn : methodNode.instructions) { + List operands = insn instanceof MethodInsnNode call ? flow.resolveCallOperandProducers(call) : null; + if (operands == null) continue; - int instanceOffset = minsn.getOpcode() == Opcodes.INVOKESTATIC ? 0 : 1; - for (int i = instanceOffset; i < callArgs.size(); i++) { - AbstractInsnNode arg = callArgs.get(i); - if (arg instanceof VarInsnNode varInsn && varInsn.var == node.index) { - Type type = methodArgs.get(i - instanceOffset); - if (!currentType.equals(type)) { - methodNode.instructions.insert(varInsn, new TypeInsnNode(Opcodes.CHECKCAST, type.getInternalName())); - matched = true; - } - } + MethodInsnNode call = (MethodInsnNode) insn; + List args = Parameters.getParameterTypes(call.desc); + int instanceOffset = call.getOpcode() == Opcodes.INVOKESTATIC ? 0 : 1; + for (int i = instanceOffset; i < operands.size(); i++) { + Type expected = args.get(i - instanceOffset); + if (operands.get(i) instanceof VarInsnNode load && replaced.containsKey(load.var) && !replaced.get(load.var).equals(expected)) { + casts.add(new Cast(load, expected)); } } } - - return matched ? TxResult.SUCCESS : TxResult.PASS; + casts.forEach(cast -> methodNode.instructions.insert(cast.load(), new TypeInsnNode(Opcodes.CHECKCAST, cast.type().getInternalName()))); + return casts.isEmpty() ? TxResult.PASS : TxResult.SUCCESS; } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java index c777ad03..87c66be0 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/ParametersStep.java @@ -2,6 +2,7 @@ import com.mojang.datafixers.util.Pair; import org.objectweb.asm.Type; +import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; import org.sinytra.adapter.analysis.params.ParamsEditDistance; import org.sinytra.adapter.analysis.params.LayeredParamsDiffSnapshot; @@ -22,45 +23,33 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) MethodParameters cleanParams = recipe.clean().getParameters(); MethodParameters dirtyParams = dirty.getParameters(); if (dirtyParams == null) return TxResult.FAIL; - if (cleanParams.merge().equals(dirtyParams.merge())) - return TxResult.PASS; + if (cleanParams.merge().equals(dirtyParams.merge())) return TxResult.PASS; // Apply mappings of params that will be removed in dirty - Map> oldVarMap = ParamTransformationUtil.gatherVarMappings(context.methodNode(), cleanParams.merge(), dirtyParams.merge(), dirtyParams.getMapping()); + MethodNode method = context.methodNode(); + Map> oldVarMap = ParamTransformationUtil.gatherVarMappings(method, cleanParams.merge(), dirtyParams.merge(), dirtyParams.getMapping()); if (cleanParams.getOrder().equals(dirtyParams.getOrder())) { - List order = dirtyParams.getOrder(); int offset = 0; - for (MethodParameters.ParamGroup group : order) { - List cleanList = cleanParams.getTypes(group); - List dirtyList = dirtyParams.getTypes(group); - - if (!applyDiff(cleanList, dirtyList, context, offset)) { + for (MethodParameters.ParamGroup group : dirtyParams.getOrder()) { + if (!applyDiff(cleanParams.getTypes(group), dirtyParams.getTypes(group), context, offset)) { return TxResult.FAIL; } - offset += dirtyList.size(); - } - } else { - if (!applyDiff(cleanParams.mergeTypes(), dirtyParams.mergeTypes(), context, 0)) { - return TxResult.FAIL; + offset += dirtyParams.getTypes(group).size(); } + } else if (!applyDiff(cleanParams.mergeTypes(), dirtyParams.mergeTypes(), context, 0)) { + return TxResult.FAIL; } // Apply mappings of params that only exist in dirty - ParamTransformationUtil.applyVarMappings(context.methodNode(), ParamTransformationUtil.gatherVarMappings(context.methodNode(), dirtyParams.merge(), dirtyParams.merge(), dirtyParams.getMapping())); - ParamTransformationUtil.applyVarMappings(context.methodNode(), oldVarMap); - ParamTransformationUtil.applyAnnotations(context.methodNode(), dirtyParams.merge()); - + ParamTransformationUtil.applyVarMappings(method, ParamTransformationUtil.gatherVarMappings(method, dirtyParams.merge(), dirtyParams.merge(), dirtyParams.getMapping())); + ParamTransformationUtil.applyVarMappings(method, oldVarMap); + ParamTransformationUtil.applyAnnotations(method, dirtyParams.merge()); return TxResult.SUCCESS; } - private boolean applyDiff(List clean, List dirty, MixinContext context, int offset) { + private static boolean applyDiff(List clean, List dirty, MixinContext context, int offset) { LayeredParamsDiffSnapshot diff = ParamsEditDistance.diffTypes(clean, dirty, context.typeRelation()); - if (!diff.isEmpty()) { - PatchResult result = diff.offset(offset).asParameterTransformer(false) - .apply(context); - return result != PatchResult.PASS; - } - return true; + return diff.isEmpty() || diff.offset(offset).asParameterTransformer(false).apply(context) != PatchResult.PASS; } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java index f95d5fcb..4b02b59a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/RedirectedCallStep.java @@ -23,13 +23,8 @@ public class RedirectedCallStep implements SignatureStep { @Override public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { - String cleanTarget = recipe.clean().getAtData().getTarget().orElse(null); - if (cleanTarget == null) return TxResult.PASS; - String dirtyTarget = dirty.getAtData().getTarget().orElse(null); - if (dirtyTarget == null) return TxResult.PASS; - - MethodQualifier cleanTargetQual = MethodQualifier.parse(cleanTarget).orElse(null); - MethodQualifier dirtyTargetQual = MethodQualifier.parse(dirtyTarget).orElse(null); + MethodQualifier cleanTargetQual = recipe.clean().getAtData().getTargetQualifier().orElse(null); + MethodQualifier dirtyTargetQual = dirty.getAtData().getTargetQualifier().orElse(null); if (cleanTargetQual == null || dirtyTargetQual == null) return TxResult.PASS; MethodParameters dirtyMethodParams = dirty.getParameters(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java index d6ff9095..a5212bbf 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java @@ -2,7 +2,6 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; import org.sinytra.adapter.analysis.method.MethodAnalyzer; @@ -12,79 +11,60 @@ import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; import org.sinytra.adapter.util.AdapterUtil; -import org.sinytra.adapter.util.SingleValueHandle; import java.util.List; -import java.util.Objects; +import java.util.stream.StreamSupport; public class StaticAccessStep implements SignatureStep { @Override public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) { - if (!recipe.clean().hasProperty(ControlKeys.STATIC) || !dirty.hasProperty(ControlKeys.STATIC)) + Boolean cleanStatic = recipe.clean().getProperty(ControlKeys.STATIC).orElse(null); + Boolean dirtyStatic = dirty.getProperty(ControlKeys.STATIC).orElse(null); + if (cleanStatic == null || dirtyStatic == null || cleanStatic.equals(dirtyStatic)) { return TxResult.PASS; - - boolean cleanStatic = recipe.clean().getProperty(ControlKeys.STATIC).orElseThrow(); - boolean dirtyStatic = dirty.getProperty(ControlKeys.STATIC).orElseThrow(); - if (cleanStatic == dirtyStatic) return TxResult.PASS; + } MethodNode method = context.methodNode(); - boolean actuallyStatic = MethodAnalyzer.isStatic(context.methodNode()); - // Add static - if (!actuallyStatic && !cleanStatic && dirtyStatic) { + boolean actuallyStatic = MethodAnalyzer.isStatic(method); + if (dirtyStatic && !actuallyStatic) { // Mixin types add the target instance as the first parameter, which replaces usages of "this" Type[] args = Type.getArgumentTypes(method.desc); List targetTypes = context.targetTypes(); boolean hasInstanceParam = args.length > 0 && targetTypes.size() == 1 && args[0].equals(targetTypes.getFirst()); - boolean usesThis = false; - for (AbstractInsnNode insn : method.instructions) { - SingleValueHandle handle = AdapterUtil.handleLocalVarInsnValue(insn); - if (handle != null && handle.get() == 0) { - usesThis = true; - break; - } - } + boolean usesThis = StreamSupport.stream(method.instructions.spliterator(), false) + .map(AdapterUtil::handleLocalVarInsnValue) + .anyMatch(handle -> handle != null && handle.get() == 0); if (usesThis && !hasInstanceParam) { return TxResult.FAIL; } context.recordCtxAudit("Made method static"); method.access |= Opcodes.ACC_STATIC; - - // Remove "this" and shift all other locals down by one slot if (method.localVariables != null) { method.localVariables.removeIf(lvn -> lvn.index == 0); - for (LocalVariableNode lvn : method.localVariables) { - lvn.index--; - } } - AdapterUtil.replaceLVT(method, idx -> idx == 0 ? 0 : idx - 1); - + shiftLocals(method, -1); return TxResult.SUCCESS; } - // Remove static - else if (actuallyStatic && cleanStatic && !dirtyStatic) { + if (!dirtyStatic && actuallyStatic) { context.recordCtxAudit("Made method non-static"); method.access &= ~Opcodes.ACC_STATIC; - - List localVariables = Objects.requireNonNullElseGet(method.localVariables, List::of); - LocalVariableNode firstParam = localVariables.stream().filter(lvn -> lvn.index == 0) - .findFirst() - .orElse(null); - // Offset everything by 1 - for (LocalVariableNode lvn : localVariables) { - lvn.index++; - } - AdapterUtil.replaceLVT(method, idx -> idx + 1); - - // Insert instance local variable + LocalVariableNode firstParam = method.localVariables == null ? null + : method.localVariables.stream().filter(lvn -> lvn.index == 0).findFirst().orElse(null); + shiftLocals(method, 1); if (firstParam != null) { method.localVariables.add(new LocalVariableNode("this", Type.getObjectType(context.classNode().name).getDescriptor(), null, firstParam.start, firstParam.end, 0)); } - // TODO Must compute frames return TxResult.SUCCESS; } - return TxResult.PASS; } + + private static void shiftLocals(MethodNode method, int delta) { + if (method.localVariables != null) { + method.localVariables.forEach(lvn -> lvn.index += delta); + } + AdapterUtil.replaceLVT(method, index -> Math.max(index + delta, 0)); + } } From f8846b89d637d50fa153f8cf9c71626b9e8305b5 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 18:50:24 +0200 Subject: [PATCH 41/44] Add support for ModifyConstant --- .../analysis/injection/InjectionLocator.java | 8 ++- .../analysis/injection/InjectionQuery.java | 30 +++++++--- .../org/sinytra/adapter/model/ann/AtData.java | 3 +- .../adapter/model/ann/ConstantData.java | 19 +++++- .../model/config/BasePropertyContainer.java | 24 +++++++- .../adapter/model/param/HandlerSignature.java | 4 +- .../adapter/pipeline/HandlerValidator.java | 2 +- .../pipeline/injector/HandlerSignatures.java | 2 +- .../adapter/pipeline/injector/MixinTypes.java | 5 +- .../injector/ModifyConstantMixin.java | 58 +++++++++++++++++++ .../adapter/pipeline/locate/Describer.java | 34 +++++++++-- .../resolver/InjectionPointResolver.java | 4 +- .../mixin/DynamicMixinPatchTest.java | 21 +++++++ .../mc_26_1_2/mixin/LivingEntityMixin.java | 12 ++++ .../mc_26_1_2/mixin/PowderSnowBlockMixin.java | 24 ++++++++ 15 files changed, 224 insertions(+), 26 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyConstantMixin.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/PowderSnowBlockMixin.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java index c346ed9d..5d409748 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionLocator.java @@ -97,10 +97,12 @@ public InsnList slice(TargetPair target, SliceData slice) { private List compute(TargetPair target, InjectionQuery query) { AnnotationHandle injectionPoint = this.context.injectionPointAnnotation(); - AnnotationHandle source = switch (query.source()) { + InjectionQuery.Source kind = query.source() == InjectionQuery.Source.CURRENT && injectionPoint == null ? InjectionQuery.Source.CONSTANT : query.source(); + AnnotationHandle source = switch (kind) { case CURRENT -> injectionPoint; case AT, SLICE_BOUNDARY -> injectionPoint != null ? new AnnotationHandle(Objects.requireNonNull(query.at()).toAnnotationNode()) : null; - case CONSTANT -> this.context.methodAnnotation().getNested(PROPERTY_CONSTANT).orElse(null); + case CONSTANT -> query.constantData() != null ? new AnnotationHandle(query.constantData().toAnnotationNode()) + : this.context.methodAnnotation().getNested(PROPERTY_CONSTANT).orElse(null); }; if (source == null) { return List.of(); @@ -113,7 +115,7 @@ private List compute(TargetPair target, InjectionQuery query) MixinBridge.Session session = session(target); AnnotationNode slice = query.ignoreSlice() ? null : getSlice(); AnnotationHandle methodAnnotation = this.context.methodAnnotation(); - return switch (query.source()) { + return switch (kind) { case CURRENT, SLICE_BOUNDARY -> session.findInjectionPoint(methodAnnotation.unwrap(), node.unwrap(), slice); case AT -> { AnnotationHandle parent = methodAnnotation.copy(); diff --git a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java index 41738800..3a8d241b 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/injection/InjectionQuery.java @@ -2,6 +2,7 @@ import org.jetbrains.annotations.Nullable; import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.ann.ConstantData; public final class InjectionQuery { public enum Source { @@ -11,52 +12,60 @@ public enum Source { AT, /// The supplied @At within the mixin's slice SLICE_BOUNDARY, - /// The mixin's @Constant + /// The mixin's @Constant or the supplied one CONSTANT } private final Source source; @Nullable private final AtData at; + @Nullable + private final ConstantData constant; private final boolean keepShift; private final boolean ignoreSlice; - private InjectionQuery(Source source, @Nullable AtData at, boolean keepShift, boolean ignoreSlice) { + private InjectionQuery(Source source, @Nullable AtData at, @Nullable ConstantData constant, boolean keepShift, boolean ignoreSlice) { this.source = source; this.at = at; + this.constant = constant; this.keepShift = keepShift; this.ignoreSlice = ignoreSlice; } /// {@return a query for the mixin's own @At} public static InjectionQuery current() { - return new InjectionQuery(Source.CURRENT, null, false, false); + return new InjectionQuery(Source.CURRENT, null, null, false, false); } /// {@return a query for the given @At} public static InjectionQuery of(AtData at) { - return new InjectionQuery(Source.AT, at, false, false); + return new InjectionQuery(Source.AT, at, null, false, false); } /// {@return a query for an @At within the mixin's slice, including shift} public static InjectionQuery sliceBoundary(AtData at) { - return new InjectionQuery(Source.SLICE_BOUNDARY, at, true, true); + return new InjectionQuery(Source.SLICE_BOUNDARY, at, null, true, true); } /// {@return a query for the mixin's @Constant} public static InjectionQuery constant() { - return new InjectionQuery(Source.CONSTANT, null, false, false); + return new InjectionQuery(Source.CONSTANT, null, null, false, false); + } + + /// {@return a query for the given @Constant} + public static InjectionQuery of(ConstantData constant) { + return new InjectionQuery(Source.CONSTANT, null, constant, false, false); } /// {@return this query with the injection point's shift applied} public InjectionQuery keepingShift() { - return new InjectionQuery(this.source, this.at, true, this.ignoreSlice); + return new InjectionQuery(this.source, this.at, this.constant, true, this.ignoreSlice); } /// {@return this query ignoring the mixin's slice} public InjectionQuery ignoringSlice() { - return new InjectionQuery(this.source, this.at, this.keepShift, true); + return new InjectionQuery(this.source, this.at, this.constant, this.keepShift, true); } public Source source() { @@ -68,6 +77,11 @@ public AtData at() { return this.at; } + @Nullable + public ConstantData constantData() { + return this.constant; + } + public boolean keepShift() { return this.keepShift; diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java index 646a9b5b..a98e15f3 100644 --- a/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/AtData.java @@ -17,7 +17,7 @@ public class AtData extends AnnotationData { public static final PropertyContainerTemplate TEMPLATE = PropertyContainerTemplate.builder() .require(Keys.VALUE) - .keys(Keys.TARGET, Keys.ORDINAL, Keys.OPCODE, Keys.SHIFT, Keys.BY, Keys.ARGS) + .keys(Keys.TARGET, Keys.ORDINAL, Keys.SHIFT, Keys.BY, Keys.ARGS) .addConstraint(AtData::validateInjectionPointType) .build(); @@ -135,7 +135,6 @@ public static class Keys { .serializable() .build(); public static final PropertyKey ORDINAL = PropertyKey.create("ordinal", Integer.class); - public static final PropertyKey OPCODE = PropertyKey.create("opcode", Integer.class); public static final PropertyKey SHIFT = PropertyKey.create("shift", At.Shift.class); public static final PropertyKey BY = PropertyKey.create("by", Integer.class); public static final PropertyKey> ARGS = (PropertyKey) PropertyKey.create("args", List.class); diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/ConstantData.java b/core/src/main/java/org/sinytra/adapter/model/ann/ConstantData.java index fd04a94a..d8c6c355 100644 --- a/core/src/main/java/org/sinytra/adapter/model/ann/ConstantData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/ConstantData.java @@ -1,5 +1,6 @@ package org.sinytra.adapter.model.ann; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.sinytra.adapter.model.config.MutablePropertyContainer; import org.sinytra.adapter.model.config.PropertyContainer; @@ -11,7 +12,7 @@ public class ConstantData extends AnnotationData { private static final PropertyContainerTemplate TEMPLATE = PropertyContainerTemplate.builder() - .requireOne(Keys.DOUBLE_VALUE, Keys.CLASS_VALUE) + .keys(Keys.DOUBLE_VALUE, Keys.CLASS_VALUE, Keys.ORDINAL, Keys.SLICE) .build(); private ConstantData(PropertyContainer properties) { @@ -22,6 +23,20 @@ public Optional doubleValue() { return getProperty(Keys.DOUBLE_VALUE); } + public Optional getOrdinal() { + return getProperty(Keys.ORDINAL); + } + + public ConstantData withOrdinal(@Nullable Integer ordinal) { + return withProperty(Keys.ORDINAL, ordinal); + } + + public ConstantData withProperty(PropertyKey key, @Nullable T value) { + MutablePropertyContainer copy = this.properties.copy(); + copy.setProperty(key, value); + return new ConstantData(copy); + } + public static ConstantData classValue(Type value) { PropertyContainer container = MutablePropertyContainer.create().setProperty(Keys.CLASS_VALUE, value); return new ConstantData(container); @@ -35,5 +50,7 @@ public static Optional parse(AnnotationHandle handle) { public static class Keys { public static final PropertyKey DOUBLE_VALUE = PropertyKey.create("doubleValue", Double.class); public static final PropertyKey CLASS_VALUE = PropertyKey.create("classValue", Type.class); + public static final PropertyKey ORDINAL = PropertyKey.create("ordinal", Integer.class); + public static final PropertyKey SLICE = PropertyKey.create("slice", String.class); } } diff --git a/core/src/main/java/org/sinytra/adapter/model/config/BasePropertyContainer.java b/core/src/main/java/org/sinytra/adapter/model/config/BasePropertyContainer.java index 3e8fb42b..2b7d8fe4 100644 --- a/core/src/main/java/org/sinytra/adapter/model/config/BasePropertyContainer.java +++ b/core/src/main/java/org/sinytra/adapter/model/config/BasePropertyContainer.java @@ -11,6 +11,7 @@ public class BasePropertyContainer implements MutablePropertyContainer { private final Map, Object> properties = new HashMap<>(); + private final List unknownValues = new ArrayList<>(); @Nullable protected final PropertyContainerTemplate template; @@ -61,6 +62,9 @@ public Map, Object> getProperties() { public MutablePropertyContainer copy() { MutablePropertyContainer copy = createCopyImpl(); this.properties.forEach((p, v) -> copy.setProperty((PropertyKey) p, v)); + if (copy instanceof BasePropertyContainer base) { + base.unknownValues.addAll(this.unknownValues); + } return copy; } @@ -91,6 +95,7 @@ public Object serialize(PropertyKey key, T value) { public AnnotationNode toAnnotationNode(String desc) { AnnotationNode node = new AnnotationNode(desc); this.properties.forEach((key, value) -> node.visit(key.name(), serialize((PropertyKey) key, value))); + this.unknownValues.forEach(value -> node.visit(value.name(), value.value())); return node; } @@ -98,12 +103,16 @@ public AnnotationNode toAnnotationNode(String desc) { public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; BasePropertyContainer container = (BasePropertyContainer) o; - return Objects.equals(properties, container.properties) && Objects.equals(template, container.template); + return Objects.equals(properties, container.properties) && Objects.equals(unknownValues, container.unknownValues) + && Objects.equals(template, container.template); } @Override public int hashCode() { - return Objects.hash(properties, template); + return Objects.hash(properties, unknownValues, template); + } + + private record UnknownValue(String name, Object value) { } protected MutablePropertyContainer createCopyImpl() { @@ -117,9 +126,18 @@ public static MutablePropertyContainer parseValid(AnnotationHandle handle, @Null @SuppressWarnings({"rawtypes", "unchecked"}) public static MutablePropertyContainer parse(AnnotationHandle handle, @Nullable PropertyContainerTemplate template, RefMapper mapper) { - MutablePropertyContainer container = new BasePropertyContainer(template); + BasePropertyContainer container = new BasePropertyContainer(template); if (template == null) return container; + Set known = new HashSet<>(); + template.getKeys().forEach(key -> known.add(key.name())); + List values = Objects.requireNonNullElse(handle.unwrap().values, List.of()); + for (int i = 0; i + 1 < values.size(); i += 2) { + if (!known.contains((String) values.get(i))) { + container.unknownValues.add(new UnknownValue((String) values.get(i), values.get(i + 1))); + } + } + for (PropertyKey key : template.getKeys()) { Object value = handle.getValue(key.name()).map(AnnotationValueHandle::get).orElse(null); if (value == null) continue; diff --git a/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java b/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java index 0f02097e..86b33d18 100644 --- a/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java +++ b/core/src/main/java/org/sinytra/adapter/model/param/HandlerSignature.java @@ -34,7 +34,9 @@ public enum ValueSource { /// A local variable of the target method, chosen by the injector VARIABLE, /// Whether the wrapped instruction runs - CONDITION + CONDITION, + /// The constant the `@Constant` selects + CONSTANT } public record Slot(ParamGroup group, ParamSource source) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java b/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java index e9052f07..45ec7f1c 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/HandlerValidator.java @@ -77,7 +77,7 @@ private static String validateSignature(MixinContext context, HandlerSignature s case CONDITION -> Type.BOOLEAN_TYPE; case CALL_RETURN -> redirected != null ? redirected.result() : null; case TARGET_RETURN -> targetReturn; - case CALL_ARG, VARIABLE -> null; + case CALL_ARG, VARIABLE, CONSTANT -> null; }; if (expectedReturn != null && !expectedReturn.equals(handlerReturn)) { return "handler returns %s but the injector expects %s".formatted(handlerReturn, expectedReturn); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java index 96128885..c58eac00 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/HandlerSignatures.java @@ -37,7 +37,7 @@ public static TxResult apply(MixinContext context, HandlerSignature signature, C } case TARGET_RETURN -> dirty.getTargetMethod() != null && dirty.getTargetMethod().desc() != null ? Type.getReturnType(dirty.getTargetMethod().desc()) : null; - case CALL_ARG, VARIABLE -> value; + case CALL_ARG, VARIABLE, CONSTANT -> value; }; if (valueType == null) { return TxResult.FAIL; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java index 43c07cdd..5df2c40a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java @@ -4,6 +4,7 @@ import org.sinytra.adapter.model.constant.MixinAnnotations; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.Redirect; @@ -14,7 +15,7 @@ public class MixinTypes { private static final Map MIXIN_TYPES = new HashMap<>(); // TODO: - // MixinConstants.MODIFY_ARGS, MixinConstants.MODIFY_CONST, + // MixinConstants.MODIFY_ARGS, // MixinConstants.OVERWRITE (dont forget special modifyTarget handling) // MixinConstants.ACCESSOR public static final MixinType INJECT = new InjectMixin(); @@ -24,12 +25,14 @@ public class MixinTypes { public static final MixinType REDIRECT = new RedirectMixin(); public static final MixinType WRAP_OP = new WrapOperationMixin(); public static final MixinType MODIFY_EXPR_VAL = new ModifyExpressionValueMixin(); + public static final MixinType MODIFY_CONST = new ModifyConstantMixin(); public static final MixinType WRAP_WITH_CONDITION = new WrapWithConditionMixin(); static { registerMixinType(Inject.class, INJECT); registerMixinType(ModifyVariable.class, MODIFY_VAR); registerMixinType(ModifyArg.class, MODIFY_ARG); + registerMixinType(ModifyConstant.class, MODIFY_CONST); registerMixinType(MixinAnnotations.MODIFY_RETURN_VAL_INTERNAL_NAME, MODIFY_RET); registerMixinType(Redirect.class, REDIRECT); registerMixinType(MixinAnnotations.WRAP_OPERATION_INTERNAL_NAME, WRAP_OP); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyConstantMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyConstantMixin.java new file mode 100644 index 00000000..8f20b69f --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyConstantMixin.java @@ -0,0 +1,58 @@ +package org.sinytra.adapter.pipeline.injector; + +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.Resolvers; + +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.CAPTURED_TARGET_ARGS; +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.VALUE; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.SINGLE_ANY; + +public class ModifyConstantMixin extends AbstractMixinType { + private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_BASE.extend() + .require(MixinKeys.TARGET_CONSTANT) + .keys(MixinKeys.SLICES) + .pluralKeys(MixinKeys.TARGET_METHOD, MixinKeys.TARGET_CONSTANT) + .build(); + + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.CONSTANT, + slot(SINGLE_ANY, VALUE), + slot(CAPTURED_PARAMS, CAPTURED_TARGET_ARGS) + ); + + public ModifyConstantMixin() { + super(TEMPLATE); + } + + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + + @Override + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { + clean.setParameters(SIGNATURE.parse(context.methodNode())); + return TxResult.SUCCESS; + } + + @Override + public TxResult postProcess(MixinContext context, Configuration clean, MutableConfiguration dirty, Recipe recipe) { + dirty.inheritProperyIfAbsent(MixinKeys.TARGET_CONSTANT); + if (dirty.getTargetMethod() == null || !dirty.hasProperty(MixinKeys.TARGET_CONSTANT)) { + return TxResult.FAIL; + } + dirty.inheritSignature(); + return TxResult.SUCCESS; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java index 72cfda09..9c33d1ad 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -14,6 +14,7 @@ import org.sinytra.adapter.analysis.method.MethodAnalyzer; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.ann.ConstantData; import org.sinytra.adapter.model.ann.SliceData; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; @@ -46,14 +47,27 @@ public final class Describer { @Nullable public static Configuration describe(MixinContext context, Recipe recipe, Candidate candidate) { AtData cleanAt = recipe.clean().getAtData(); + ConstantData cleanConstant = recipe.clean().getProperty(MixinKeys.TARGET_CONSTANT).orElse(null); TargetPair origin = recipe.getDirtyTarget(); boolean otherMethod = origin == null || !candidate.target().isSameMethod(origin); Function query = otherMethod ? at -> InjectionQuery.of(at).ignoringSlice() : InjectionQuery::of; - AtData described = cleanAt != null ? describeAt(context, cleanAt, candidate, query) : null; - if (described == null) { + MutableConfiguration config = MutableConfiguration.create(); + AtData described = null; + if (cleanAt != null) { + described = describeAt(context, cleanAt, candidate, query); + if (described == null) { + return null; + } + config.setAtData(described); + } else if (cleanConstant != null) { + ConstantData constant = describeConstant(context, cleanConstant, candidate, otherMethod); + if (constant == null) { + return null; + } + config.setProperty(MixinKeys.TARGET_CONSTANT, constant); + } else { return null; } - MutableConfiguration config = MutableConfiguration.create().setAtData(described); if (otherMethod && recipe.clean().getProperty(MixinKeys.SLICES).map(slices -> !slices.isEmpty()).orElse(false)) { config.setProperty(MixinKeys.SLICES, List.of()); } @@ -61,7 +75,7 @@ public static Configuration describe(MixinContext context, Recipe recipe, Candid config.setProperty(MixinKeys.SLICE, new SliceData(null, null, null)); } - if (isLocalAt(context, described) && candidate.insn() instanceof VarInsnNode var) { + if (described != null && isLocalAt(context, described) && candidate.insn() instanceof VarInsnNode var) { AbstractInsnNode position = OpcodeUtil.isStoreOpcode(var.getOpcode()) ? var.getNext() : var; MutableConfiguration local = describeLocal(context, recipe, candidate.target(), position, var.var); if (local == null) { @@ -83,6 +97,18 @@ public static Configuration describe(MixinContext context, Recipe recipe, Candid return config; } + @Nullable + private static ConstantData describeConstant(MixinContext context, ConstantData cleanConstant, Candidate candidate, boolean otherMethod) { + ConstantData constant = otherMethod ? cleanConstant.withProperty(ConstantData.Keys.SLICE, null) : cleanConstant; + InjectionQuery query = InjectionQuery.of(constant.withOrdinal(null)); + List selected = context.injections().find(candidate.target(), otherMethod ? query.ignoringSlice() : query); + int ordinal = selected.indexOf(candidate.insn()); + if (ordinal == -1) { + return null; + } + return constant.getOrdinal().isPresent() ? constant.withOrdinal(ordinal) : constant; + } + @Nullable public static AtData describeAt(MixinContext context, AtData cleanAt, Candidate candidate, Function query) { if (isLocalAt(context, cleanAt)) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java index b3095f9a..31ecfa28 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/InjectionPointResolver.java @@ -7,6 +7,7 @@ import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.key.MixinKeys; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -75,7 +76,8 @@ private static Configuration tryReuse(MixinContext context, Recipe recipe) { && selectsSameLocal(context, recipe, dirtyTarget, insns.getFirst()) ) { return dirty.copyClean() - .inheritAtData(); + .inheritAtData() + .inheritProperty(MixinKeys.TARGET_CONSTANT); } return null; diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 77f07ef5..5cc407b8 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -63,6 +63,27 @@ static void postTest() { LOGGER.info("Complete report:\n\n{}", patchEnvironment.auditTrail().getCompleteReport()); } + @Test + void testModifyConstantMovedIntoExtension() throws Exception { + assertSameCodeExtracted( + "org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityMixin", + "sinkSpeed", + "org/sinytra/adapter/test/mc_26_1_2/mixin/adapter_generated_net_neoforged_neoforge_common_extensions_ILivingEntityExtension", + assertTargetMethod() + ); + } + + @Test + void testWrapOperationMovedIntoExtension() throws Exception { + assertSameCodeExtracted( + "org/sinytra/adapter/test/mc_26_1_2/mixin/PowderSnowBlockMixin", + "walkOnSnow", + "org/sinytra/adapter/test/mc_26_1_2/mixin/adapter_generated_net_neoforged_neoforge_common_extensions_IItemExtension", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Test void testAnonymousTargetUnchanged() throws Exception { assertMixinTarget("org/sinytra/adapter/test/mc_26_1_2/mixin/DispenseItemBehaviorMixin", "dispense", "net/minecraft/core/dispenser/DispenseItemBehavior$10"); diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityMixin.java index 0344d3af..0afee97e 100644 --- a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityMixin.java +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityMixin.java @@ -8,6 +8,8 @@ import net.minecraft.world.level.block.state.BlockState; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; @Mixin(LivingEntity.class) public class LivingEntityMixin { @@ -35,4 +37,14 @@ private void playSoundCorrectlyForBlocks(LivingEntity instance, SoundEvent sound private void playSoundCorrectlyForBlocksExpected(LivingEntity instance, SoundEvent sound, float volume, float pitch, Operation original, @Local BlockState state) { original.call(instance, sound, volume, pitch); } + + @ModifyConstant(method = "goDownInWater", constant = @Constant(doubleValue = -0.03999999910593033D)) + private double sinkSpeed(double speed) { + return speed; + } + + @ModifyConstant(method = "sinkInFluid(Lnet/neoforged/neoforge/fluids/FluidType;)V", constant = @Constant(doubleValue = -0.03999999910593033D)) + private double sinkSpeedExpected(double speed) { + return speed; + } } diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/PowderSnowBlockMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/PowderSnowBlockMixin.java new file mode 100644 index 00000000..f8bd9b0d --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/PowderSnowBlockMixin.java @@ -0,0 +1,24 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.PowderSnowBlock; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(PowderSnowBlock.class) +public class PowderSnowBlockMixin { + @WrapOperation(method = "canEntityWalkOnPowderSnow", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;is(Ljava/lang/Object;)Z")) + private static boolean walkOnSnow(ItemStack stack, Object item, Operation original) { + return original.call(stack, item); + } + + @WrapOperation( + method = "canWalkOnPowderedSnow(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/entity/LivingEntity;)Z", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;is(Ljava/lang/Object;)Z") + ) + private static boolean walkOnSnowExpected(ItemStack stack, Object item, Operation original) { + return original.call(stack, item); + } +} From a2c1039bef7dffb9e6f08016ad2f8ae2a9f53a8d Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 14 Oct 2026 19:05:12 +0200 Subject: [PATCH 42/44] Simplify SliceData --- .../sinytra/adapter/model/ann/SliceData.java | 81 ++++++++----------- .../constant/MixinAnnotationConstants.java | 4 - .../adapter/pipeline/locate/Describer.java | 2 +- .../resolver/SliceBoundaryResolver.java | 2 +- 4 files changed, 36 insertions(+), 53 deletions(-) diff --git a/core/src/main/java/org/sinytra/adapter/model/ann/SliceData.java b/core/src/main/java/org/sinytra/adapter/model/ann/SliceData.java index 6eabc97e..bf97210c 100644 --- a/core/src/main/java/org/sinytra/adapter/model/ann/SliceData.java +++ b/core/src/main/java/org/sinytra/adapter/model/ann/SliceData.java @@ -1,69 +1,56 @@ package org.sinytra.adapter.model.ann; import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.tree.AnnotationNode; +import org.sinytra.adapter.model.config.MutablePropertyContainer; +import org.sinytra.adapter.model.config.PropertyContainer; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.PropertyKey; import org.sinytra.adapter.model.constant.MixinAnnotations; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.SLICE_FROM; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.SLICE_ID; -import static org.sinytra.adapter.model.constant.MixinAnnotationConstants.SLICE_TO; +public class SliceData extends AnnotationData { + private static final PropertyContainerTemplate TEMPLATE = PropertyContainerTemplate.builder() + .keys(Keys.FROM, Keys.TO) + .build(); -public class SliceData { - @Nullable - private final String id; - @Nullable - private final AtData from; - @Nullable - private final AtData to; - - public SliceData(@Nullable String id, @Nullable AtData from, @Nullable AtData to) { - this.id = id; - this.from = from; - this.to = to; - } - - @Nullable - public String id() { - return this.id; + private SliceData(PropertyContainer properties) { + super(MixinAnnotations.SLICE, properties); } @Nullable public AtData from() { - return this.from; + return getProperty(Keys.FROM).orElse(null); } @Nullable public AtData to() { - return this.to; + return getProperty(Keys.TO).orElse(null); } - public static SliceData parse(AnnotationHandle handle, RefMapper context) { - AtData from = handle.getNested(SLICE_FROM) - .flatMap(s -> AtData.parse(s, context)) - .orElse(null); - AtData to = handle.getNested(SLICE_TO) - .flatMap(s -> AtData.parse(s, context)) - .orElse(null); - String id = handle.getValue(SLICE_ID) - .map(AnnotationValueHandle::get) - .orElse(null); - return new SliceData(id, from, to); + public SliceData withBoundaries(@Nullable AtData from, @Nullable AtData to) { + MutablePropertyContainer copy = this.properties.copy(); + copy.setProperty(Keys.FROM, from); + copy.setProperty(Keys.TO, to); + return new SliceData(copy); } - public AnnotationNode toAnnotationNode() { - AnnotationNode slice = new AnnotationNode(MixinAnnotations.SLICE); - if (this.id != null) { - slice.visit(SLICE_ID, this.id); - } - if (this.from != null) { - AnnotationVisitor fromNode = slice.visitAnnotation(SLICE_FROM, MixinAnnotations.AT); - this.from.toAnnotationNode().accept(fromNode); - } - if (this.to != null) { - AnnotationVisitor fromNode = slice.visitAnnotation(SLICE_TO, MixinAnnotations.AT); - this.to.toAnnotationNode().accept(fromNode); + public static SliceData empty() { + return new SliceData(MutablePropertyContainer.create(TEMPLATE)); + } + + public static SliceData parse(AnnotationHandle handle, RefMapper mapper) { + return new SliceData(MutablePropertyContainer.parse(handle, TEMPLATE, mapper)); + } + + public static class Keys { + public static final PropertyKey FROM = boundary("from"); + public static final PropertyKey TO = boundary("to"); + + private static PropertyKey boundary(String name) { + return PropertyKey.builder(name) + .parser((value, mapper) -> AtData.parse(new AnnotationHandle((AnnotationNode) value), mapper).orElse(null)) + .serializer(AtData::toAnnotationNode) + .build(); } - return slice; } } diff --git a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java index cefa1a17..30d88b8a 100644 --- a/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java +++ b/core/src/main/java/org/sinytra/adapter/model/constant/MixinAnnotationConstants.java @@ -21,8 +21,4 @@ public class MixinAnnotationConstants { public static final String PROPERTY_SLICE = "slice"; public static final String PROPERTY_INDEX = "index"; public static final String PROPERTY_CONSTANT = "constant"; - - public static final String SLICE_FROM = "from"; - public static final String SLICE_TO = "to"; - public static final String SLICE_ID = "id"; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java index 9c33d1ad..4626a236 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/Describer.java @@ -72,7 +72,7 @@ public static Configuration describe(MixinContext context, Recipe recipe, Candid config.setProperty(MixinKeys.SLICES, List.of()); } if (otherMethod && recipe.clean().hasProperty(MixinKeys.SLICE)) { - config.setProperty(MixinKeys.SLICE, new SliceData(null, null, null)); + config.setProperty(MixinKeys.SLICE, SliceData.empty()); } if (described != null && isLocalAt(context, described) && candidate.insn() instanceof VarInsnNode var) { diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java index ab3a8b9e..24dba56f 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/SliceBoundaryResolver.java @@ -59,7 +59,7 @@ private static SliceData fixSlice(MixinContext context, Recipe recipe, Targets t if (slice.from() != null && from == null || slice.to() != null && to == null) { return null; } - return from == slice.from() && to == slice.to() ? slice : new SliceData(slice.id(), from, to); + return from == slice.from() && to == slice.to() ? slice : slice.withBoundaries(from, to); } @Nullable From 4734a98bffa407cffe93edbb761c9da243136ec4 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Thu, 22 Oct 2026 20:02:14 +0200 Subject: [PATCH 43/44] Support locating partial method replacements --- .../analysis/compare/MethodAlignment.java | 20 +++++-- .../pipeline/locate/OriginLocator.java | 31 +++++++++- .../adapter/pipeline/locate/ScopeSearch.java | 5 +- .../resolver/TargetMethodResolver.java | 3 +- .../mixin/DynamicMixinPatchTest.java | 49 ++++++++++++++++ .../test/mc_26_1_2/mixin/EntityMixin.java | 23 ++++++++ .../test/mc_26_1_2/mixin/GuiMixin.java | 57 +++++++++++++++++++ .../mixin/LivingEntityItemMixin.java | 23 ++++++++ 8 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/GuiMixin.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityItemMixin.java diff --git a/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java b/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java index 617808a1..ce360373 100644 --- a/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java +++ b/core/src/main/java/org/sinytra/adapter/analysis/compare/MethodAlignment.java @@ -5,6 +5,7 @@ import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.analysis.SourceValue; import org.sinytra.adapter.analysis.flow.DataFlow; +import org.sinytra.adapter.util.OpcodeUtil; import java.util.*; import java.util.function.IntFunction; @@ -60,6 +61,10 @@ public double similarity() { return total == 0 ? 1 : 2.0 * this.pairings.size() / total; } + public double dirtyCoverage() { + return this.dirty.isEmpty() ? 0 : (double) this.pairings.size() / this.dirty.size(); + } + public List find(AbstractInsnNode cleanInsn) { Integer index = indexOf(cleanInsn); if (index == null) { @@ -98,23 +103,26 @@ public List findDirtyAround(AbstractInsnNode cleanInsn) { private int[] dirtyRangeAround(int index) { int from = 0; for (int i = index - 1; i >= 0; i--) { - Pairing before = this.pairings.get(i); - if (before != null && before.inOrder()) { - from = before.dirty() + 1; + if (isAnchor(i)) { + from = this.pairings.get(i).dirty() + 1; break; } } int to = this.dirty.size(); for (int i = index + 1; i < this.clean.size(); i++) { - Pairing after = this.pairings.get(i); - if (after != null && after.inOrder()) { - to = after.dirty(); + if (isAnchor(i)) { + to = this.pairings.get(i).dirty(); break; } } return new int[]{from, to}; } + private boolean isAnchor(int cleanIndex) { + Pairing pairing = this.pairings.get(cleanIndex); + return pairing != null && pairing.inOrder() && !OpcodeUtil.isLoadOpcode(this.clean.get(cleanIndex).getOpcode()); + } + @Nullable public AbstractInsnNode resolve(AbstractInsnNode cleanInsn) { List matches = find(cleanInsn); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java index 537f9d7a..6ffa0ccd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/OriginLocator.java @@ -8,17 +8,20 @@ import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.util.AdapterUtil; import java.util.Comparator; import java.util.List; public final class OriginLocator { - public record Origin(TargetPair method, boolean matches, MethodAlignment alignment, boolean deprecated) { + /// @param piece a new method that took over part of a removed target + public record Origin(TargetPair method, boolean matches, MethodAlignment alignment, boolean deprecated, boolean piece) { public double similarity() { return this.alignment.similarity(); } } + private static final double MIN_PIECE_COVERAGE = 0.5; private static final Comparator RANKING = Comparator.comparing(Origin::matches).reversed() .thenComparing(Comparator.comparingDouble(Origin::similarity).reversed()) .thenComparing(Origin::deprecated) @@ -34,18 +37,42 @@ public static List locate(MixinContext context, TargetPair cleanTarget, .toList(); } else { candidates = context.dirtyMethods().findTargetsByName(cleanQualifier); + if (candidates.isEmpty()) { + return findPieces(context, cleanTarget); + } } return candidates.stream() .map(candidate -> new Origin( candidate, context.injections().matches(candidate), MethodAlignment.align(cleanTarget.methodNode(), candidate.methodNode()), - isDirtyDeprecated(context, candidate.methodNode()) + isDirtyDeprecated(context, candidate.methodNode()), + false )) .sorted(RANKING) .toList(); } + private static List findPieces(MixinContext context, TargetPair cleanTarget) { + ClassNode cleanClass = cleanTarget.classNode(); + ClassNode dirtyClass = context.dirtyLookup().getClass(cleanClass.name).orElse(null); + if (dirtyClass == null) { + return List.of(); + } + boolean isStatic = MethodAnalyzer.isStatic(cleanTarget.methodNode()); + return dirtyClass.methods.stream() + .filter(method -> !MethodAnalyzer.isLambda(method) && MethodAnalyzer.isStatic(method) == isStatic + && AdapterUtil.findMethod(cleanClass, method.name, method.desc) == null) + .map(method -> { + TargetPair candidate = new TargetPair(dirtyClass, method); + MethodAlignment alignment = MethodAlignment.align(cleanTarget.methodNode(), method); + return new Origin(candidate, context.injections().matches(candidate), alignment, MethodAnalyzer.isDeprecated(method), true); + }) + .filter(origin -> origin.alignment().dirtyCoverage() >= MIN_PIECE_COVERAGE) + .sorted(RANKING) + .toList(); + } + private static boolean isDirtyDeprecated(MixinContext context, MethodNode dirty) { TargetPair clean = context.cleanMethods().resolve(MethodQualifier.create(dirty)); return clean != null ? MethodAnalyzer.isDirtyDeprecatedMethod(clean.methodNode(), dirty) : MethodAnalyzer.isDeprecated(dirty); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java index 52ed7575..b169425a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java @@ -114,11 +114,12 @@ private List findRootCallees(Scope root) { @Nullable private static Relocation relocation(Callee callee) { MethodNode method = callee.method().methodNode(); - if (callee.method().classNode().name.equals(callee.root().classNode().name) || !MethodAnalyzer.isStatic(method)) { + boolean isStatic = MethodAnalyzer.isStatic(method); + if (callee.method().classNode().name.equals(callee.root().classNode().name) || !isStatic && callee.originLocals().contains(0)) { return null; } Type[] args = Type.getArgumentTypes(method.desc); - int slot = 0; + int slot = isStatic ? 0 : 1; for (int i = 0; i < args.length; i++) { if (callee.originLocals().contains(slot)) { return new Relocation.ReceivesOrigin(i); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java index 6df1a72e..70f4dacd 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/resolver/TargetMethodResolver.java @@ -26,7 +26,8 @@ public ResolutionResult resolve(MixinContext context, Recipe recipe) { : List.of(); OriginLocator.Origin origin = origins.isEmpty() ? null : origins.getFirst(); boolean hasAt = recipe.clean().getAtData() != null; - if (origin == null || hasAt && !origin.matches()) { + boolean ambiguousPiece = origin != null && origin.piece() && (!hasAt || origins.stream().filter(OriginLocator.Origin::matches).count() > 1); + if (origin == null || hasAt && !origin.matches() || ambiguousPiece) { return hasAt ? ResolutionResult.pass() : ResolutionResult.fail(); } context.recordCtxAudit("Located target method %s (similarity %.2f%s)", MethodQualifier.create(origin.method().methodNode()).asDescriptor(), diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 5cc407b8..e8db2de3 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -1,6 +1,7 @@ package org.sinytra.adapter.patch.test.mc_26_1_2.mixin; import com.mojang.logging.LogUtils; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -14,6 +15,7 @@ import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.api.Patcher; import org.sinytra.adapter.api.RefmapHolder; +import org.sinytra.adapter.model.ann.AnnotationHandle; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.patch.test_main.mixin.MinecraftMixinPatchTest; import org.sinytra.adapter.util.AdapterUtil; @@ -211,6 +213,53 @@ void testMovedAndChangedCall() throws Exception { ); } + @Test + void testSplitPieceOfRemovedTarget() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/GuiMixin", + "skipContextualBar", + assertTargetMethod(), + assertInjectionPoint() + ); + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/GuiMixin", + "skipContextualBarBackground", + assertTargetMethod(), + assertInjectionPoint() + ); + } + + @Test + void testMirrorIntoInstanceCallee() throws Exception { + String generatedName = "org/sinytra/adapter/test/mc_26_1_2/mixin/adapter_generated_net_neoforged_neoforge_common_extensions_IBlockExtension"; + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin", + "adjustStepSoundVolume", + assertUnique(), + assertHasGeneratedMethod(generatedName), + (patched, expected, env) -> { + ClassNode generated = env.classGenerator().getGeneratedMixinClasses().get(generatedName).node(); + MethodNode mirror = generated.methods.stream().filter(m -> m.name.startsWith("adjustStepSoundVolume$adapter$mirror")).findFirst().orElseThrow(); + Assertions.assertThat(mirror.desc).isEqualTo("(FLnet/minecraft/world/entity/Entity;)F"); + AnnotationHandle modifyArg = new AnnotationHandle(mirror.visibleAnnotations.getFirst()); + Assertions.assertThat(modifyArg.>getValue("method").orElseThrow().get()) + .containsExactly("playStepSound(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/Entity;FF)V"); + AnnotationNode local = mirror.invisibleParameterAnnotations[1].getFirst(); + Assertions.assertThat(local.values).containsExactly("argsOnly", true, "index", 4); + } + ); + } + + @Test + void testModifyExpressionValueReplacedHook() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityItemMixin", + "cancelItemUsing", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin.java new file mode 100644 index 00000000..e67a194b --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin.java @@ -0,0 +1,23 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(Entity.class) +public class EntityMixin { + @Unique + private final Entity self = (Entity) (Object) this; + + @ModifyArg(method = "playStepSound", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V"), index = 1) + private float adjustStepSoundVolume(float original) { + return original * self.getBbWidth(); + } + + @Unique + public float adjustStepSoundVolumeExpected(float original) { + return original * self.getBbWidth(); + } +} diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/GuiMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/GuiMixin.java new file mode 100644 index 00000000..2cde3288 --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/GuiMixin.java @@ -0,0 +1,57 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.contextualbar.ContextualBarRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(Gui.class) +public class GuiMixin { + @WrapOperation( + method = "extractHotbarAndDecorations", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/contextualbar/ContextualBarRenderer;extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V" + ) + ) + private void skipContextualBar(ContextualBarRenderer instance, GuiGraphicsExtractor graphics, DeltaTracker tracker, Operation original) { + original.call(instance, graphics, tracker); + } + + @WrapOperation( + method = "extractContextualInfoBar(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/contextualbar/ContextualBarRenderer;extractRenderState(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V" + ) + ) + private void skipContextualBarExpected(ContextualBarRenderer instance, GuiGraphicsExtractor graphics, DeltaTracker tracker, Operation original) { + original.call(instance, graphics, tracker); + } + + @WrapOperation( + method = "extractHotbarAndDecorations", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/contextualbar/ContextualBarRenderer;extractBackground(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V" + ) + ) + private void skipContextualBarBackground(ContextualBarRenderer instance, GuiGraphicsExtractor graphics, DeltaTracker tracker, Operation original) { + original.call(instance, graphics, tracker); + } + + @WrapOperation( + method = "extractContextualInfoBarBackground(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/gui/contextualbar/ContextualBarRenderer;extractBackground(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V" + ) + ) + private void skipContextualBarBackgroundExpected(ContextualBarRenderer instance, GuiGraphicsExtractor graphics, DeltaTracker tracker, Operation original) { + original.call(instance, graphics, tracker); + } +} diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityItemMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityItemMixin.java new file mode 100644 index 00000000..7b34d432 --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/LivingEntityItemMixin.java @@ -0,0 +1,23 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.world.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(LivingEntity.class) +public class LivingEntityItemMixin { + @Unique + private final LivingEntity entity = (LivingEntity) (Object) this; + + @ModifyExpressionValue(method = "updatingUsingItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;isSameItem(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Z")) + private boolean cancelItemUsing(boolean original) { + return original && !this.entity.isShiftKeyDown(); + } + + @ModifyExpressionValue(method = "updatingUsingItem()V", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/common/CommonHooks;canContinueUsing(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Z")) + private boolean cancelItemUsingExpected(boolean original) { + return original && !this.entity.isShiftKeyDown(); + } +} From d8800b30e1c336fa4dbadbe4da63da99e35f3a0e Mon Sep 17 00:00:00 2001 From: Su5eD Date: Thu, 22 Oct 2026 20:52:08 +0200 Subject: [PATCH 44/44] Add support for ModifyArgs mixins --- .../org/sinytra/adapter/model/Relocation.java | 8 +- .../sinytra/adapter/model/RelocationPlan.java | 20 ++- .../pipeline/injector/InjectMixin.java | 6 + .../adapter/pipeline/injector/MixinType.java | 7 + .../adapter/pipeline/injector/MixinTypes.java | 4 +- .../pipeline/injector/ModifyArgMixin.java | 15 ++ .../pipeline/injector/ModifyArgsMixin.java | 73 ++++++++ .../injector/ModifyConstantMixin.java | 6 + .../injector/ModifyExpressionValueMixin.java | 6 + .../injector/ModifyReturnValueMixin.java | 6 + .../injector/ModifyVariableMixin.java | 6 + .../pipeline/injector/WrapOperationMixin.java | 9 + .../injector/WrapWithConditionMixin.java | 6 + .../adapter/pipeline/locate/ScopeSearch.java | 11 +- .../relocate/RelocationGenerator.java | 159 +++++++++++++++++- .../processor/relocate/RelocationSolver.java | 43 ++++- .../processor/signature/StaticAccessStep.java | 14 +- .../org/sinytra/adapter/util/AdapterUtil.java | 15 ++ .../mixin/DynamicMixinPatchTest.java | 85 +++++++++- .../test/mc_26_1_2/mixin/EntityMixin.java | 47 ++++++ .../mc_26_1_2/mixin/FogRendererMixin.java | 18 ++ 21 files changed, 528 insertions(+), 36 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgsMixin.java create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/FogRendererMixin.java diff --git a/core/src/main/java/org/sinytra/adapter/model/Relocation.java b/core/src/main/java/org/sinytra/adapter/model/Relocation.java index deccdbb8..15182290 100644 --- a/core/src/main/java/org/sinytra/adapter/model/Relocation.java +++ b/core/src/main/java/org/sinytra/adapter/model/Relocation.java @@ -1,9 +1,15 @@ package org.sinytra.adapter.model; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.MethodInsnNode; + public sealed interface Relocation { record CalledFrom() implements Relocation { } - record ReceivesOrigin(int argIndex) implements Relocation { + record ReceivesOrigin(int argIndex, @Nullable OriginCall via) implements Relocation { + } + + record OriginCall(TargetPair method, MethodInsnNode call) { } } diff --git a/core/src/main/java/org/sinytra/adapter/model/RelocationPlan.java b/core/src/main/java/org/sinytra/adapter/model/RelocationPlan.java index e0d204e9..9329d49d 100644 --- a/core/src/main/java/org/sinytra/adapter/model/RelocationPlan.java +++ b/core/src/main/java/org/sinytra/adapter/model/RelocationPlan.java @@ -8,9 +8,25 @@ import java.util.List; public sealed interface RelocationPlan { - record Move(List lambdas, List widenedFields, List widenedMethods) implements RelocationPlan { + record Move(List lambdas, List widenedFields, List widenedMethods, boolean makeStatic) implements RelocationPlan { } - record Mirror(ClassNode handlerOwner, boolean isStatic, @Nullable ValueSource receiver, List params) implements RelocationPlan { + record Mirror(ClassNode handlerOwner, boolean isStatic, @Nullable ValueSource receiver, List params, @Nullable Guard guard) implements RelocationPlan { + } + + /// Limits a mirror to calls that come from the original method, through the given call + record Guard(Relocation.OriginCall via, Passthrough passthrough) { + } + + /// What a guarded mirror returns when it doesn't run the handler + enum Passthrough { + /// Nothing, the handler returns void + SKIP, + /// The value the handler would modify, its first parameter + KEEP_VALUE, + /// True, so the wrapped instruction still runs + PROCEED, + /// The result of the wrapped operation, called with the operands the handler received before it + CALL_ORIGINAL } } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java index c8b44cd5..86559396 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/InjectMixin.java @@ -1,5 +1,6 @@ package org.sinytra.adapter.pipeline.injector; +import org.sinytra.adapter.model.RelocationPlan; import org.objectweb.asm.Type; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.Targets; @@ -51,6 +52,11 @@ public HandlerSignature signature() { return SIGNATURE; } + @Override + public RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + return RelocationPlan.Passthrough.SKIP; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { // An @Inject only needs a position, so it can fall back to the nearest call diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java index e2dbf6b9..0815cc14 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinType.java @@ -1,5 +1,7 @@ package org.sinytra.adapter.pipeline.injector; +import org.jetbrains.annotations.Nullable; +import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; @@ -24,6 +26,11 @@ default Set getFlags() { return EnumSet.noneOf(MixinFlag.class); } + @Nullable + default RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + return null; + } + default boolean canInject(MixinContext context, Configuration config) { return true; } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java index 5df2c40a..1c5c7060 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/MixinTypes.java @@ -4,6 +4,7 @@ import org.sinytra.adapter.model.constant.MixinAnnotations; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyArgs; import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.Redirect; @@ -15,12 +16,12 @@ public class MixinTypes { private static final Map MIXIN_TYPES = new HashMap<>(); // TODO: - // MixinConstants.MODIFY_ARGS, // MixinConstants.OVERWRITE (dont forget special modifyTarget handling) // MixinConstants.ACCESSOR public static final MixinType INJECT = new InjectMixin(); public static final MixinType MODIFY_VAR = new ModifyVariableMixin(); public static final MixinType MODIFY_ARG = new ModifyArgMixin(); + public static final MixinType MODIFY_ARGS = new ModifyArgsMixin(); public static final MixinType MODIFY_RET = new ModifyReturnValueMixin(); public static final MixinType REDIRECT = new RedirectMixin(); public static final MixinType WRAP_OP = new WrapOperationMixin(); @@ -32,6 +33,7 @@ public class MixinTypes { registerMixinType(Inject.class, INJECT); registerMixinType(ModifyVariable.class, MODIFY_VAR); registerMixinType(ModifyArg.class, MODIFY_ARG); + registerMixinType(ModifyArgs.class, MODIFY_ARGS); registerMixinType(ModifyConstant.class, MODIFY_CONST); registerMixinType(MixinAnnotations.MODIFY_RETURN_VAL_INTERNAL_NAME, MODIFY_RET); registerMixinType(Redirect.class, REDIRECT); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java index cbd69850..373b97ca 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgMixin.java @@ -1,5 +1,6 @@ package org.sinytra.adapter.pipeline.injector; +import org.sinytra.adapter.model.RelocationPlan; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; import org.sinytra.adapter.api.TypeAdapter; @@ -46,6 +47,20 @@ public HandlerSignature signature() { return SIGNATURE; } + @Override + public RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + AtData at = config.getAtData(); + String desc = at != null ? at.getTargetQualifier().map(MethodQualifier::desc).orElse(null) : null; + if (desc == null) { + return null; + } + + List callArgs = Parameters.getParameterTypes(desc); + List handlerArgs = Parameters.getParameterTypes(context.methodNode().desc); + boolean allArgs = callArgs.size() > 1 && handlerArgs.size() >= callArgs.size() && handlerArgs.subList(0, callArgs.size()).equals(callArgs); + return allArgs ? null : RelocationPlan.Passthrough.KEEP_VALUE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgsMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgsMixin.java new file mode 100644 index 00000000..4e973f6b --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyArgsMixin.java @@ -0,0 +1,73 @@ +package org.sinytra.adapter.pipeline.injector; + +import org.sinytra.adapter.model.RelocationPlan; +import org.sinytra.adapter.model.MethodQualifier; +import org.sinytra.adapter.model.ann.AtData; +import org.sinytra.adapter.model.config.Configuration; +import org.sinytra.adapter.model.config.ConfigurationTemplates; +import org.sinytra.adapter.model.config.MutableConfiguration; +import org.sinytra.adapter.model.config.PropertyContainerTemplate; +import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.param.HandlerSignature; +import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; +import org.sinytra.adapter.pipeline.Recipe; +import org.sinytra.adapter.pipeline.TxResult; +import org.sinytra.adapter.pipeline.ctx.MixinContext; +import org.sinytra.adapter.pipeline.resolver.Resolvers; + +import java.util.Objects; + +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.CAPTURED_TARGET_ARGS; +import static org.sinytra.adapter.model.param.HandlerSignature.ParamSource.KEEP; +import static org.sinytra.adapter.model.param.HandlerSignature.slot; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; +import static org.sinytra.adapter.model.param.MethodParameters.ParamGroup.SINGLE_ANY; + +public class ModifyArgsMixin extends AbstractMixinType { + private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_AT.extend() + .pluralKeys(MixinKeys.TARGET_METHOD) + .build(); + + private static final HandlerSignature SIGNATURE = HandlerSignature.of( + ValueSource.VOID, + slot(SINGLE_ANY, KEEP), + slot(CAPTURED_PARAMS, CAPTURED_TARGET_ARGS) + ); + + public ModifyArgsMixin() { + super(TEMPLATE); + } + + @Override + public HandlerSignature signature() { + return SIGNATURE; + } + + @Override + public RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + return RelocationPlan.Passthrough.SKIP; + } + + @Override + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { + clean.setParameters(SIGNATURE.parse(context.methodNode())); + return TxResult.SUCCESS; + } + + @Override + public TxResult postProcess(MixinContext context, Configuration clean, MutableConfiguration dirty, Recipe recipe) { + dirty.inheritProperyIfAbsent(MixinKeys.ORDINAL); + AtData cleanAt = clean.getAtData(); + AtData dirtyAt = dirty.getAtData(); + if (cleanAt == null || dirtyAt == null) { + return TxResult.FAIL; + } + String cleanDesc = cleanAt.getTargetQualifier().map(MethodQualifier::desc).orElse(null); + String dirtyDesc = dirtyAt.getTargetQualifier().map(MethodQualifier::desc).orElse(null); + if (cleanDesc == null || !Objects.equals(cleanDesc, dirtyDesc)) { + return TxResult.FAIL; + } + dirty.inheritSignature(); + return TxResult.SUCCESS; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyConstantMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyConstantMixin.java index 8f20b69f..058a07e5 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyConstantMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyConstantMixin.java @@ -1,5 +1,6 @@ package org.sinytra.adapter.pipeline.injector; +import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.ConfigurationTemplates; import org.sinytra.adapter.model.config.MutableConfiguration; @@ -40,6 +41,11 @@ public HandlerSignature signature() { return SIGNATURE; } + @Override + public RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + return RelocationPlan.Passthrough.KEEP_VALUE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { clean.setParameters(SIGNATURE.parse(context.methodNode())); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java index 5e353133..19835dd9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyExpressionValueMixin.java @@ -1,5 +1,6 @@ package org.sinytra.adapter.pipeline.injector; +import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.ConfigurationTemplates; @@ -46,6 +47,11 @@ public HandlerSignature signature() { return SIGNATURE; } + @Override + public RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + return RelocationPlan.Passthrough.KEEP_VALUE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { resolvers diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java index 8d4ffff4..b66513c4 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyReturnValueMixin.java @@ -1,5 +1,6 @@ package org.sinytra.adapter.pipeline.injector; +import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.Targets; import org.sinytra.adapter.model.ann.AtData; @@ -41,6 +42,11 @@ public HandlerSignature signature() { return SIGNATURE; } + @Override + public RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + return RelocationPlan.Passthrough.KEEP_VALUE; + } + @Override public boolean canInject(MixinContext context, Configuration config) { AtData at = config.getAtData(); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java index 06ed45a1..fa7fea0a 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/ModifyVariableMixin.java @@ -1,5 +1,6 @@ package org.sinytra.adapter.pipeline.injector; +import org.sinytra.adapter.model.RelocationPlan; import org.objectweb.asm.Type; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; @@ -53,6 +54,11 @@ public HandlerSignature signature() { return SIGNATURE; } + @Override + public RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + return RelocationPlan.Passthrough.KEEP_VALUE; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { resolvers diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java index 51004a75..7eeb8370 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapOperationMixin.java @@ -1,14 +1,17 @@ package org.sinytra.adapter.pipeline.injector; +import org.sinytra.adapter.model.RelocationPlan; import org.sinytra.adapter.model.config.Configuration; import org.sinytra.adapter.model.config.ConfigurationTemplates; import org.sinytra.adapter.model.config.MutableConfiguration; import org.sinytra.adapter.model.config.PropertyContainerTemplate; import org.sinytra.adapter.model.config.key.MixinKeys; +import org.sinytra.adapter.model.constant.TypeConstants; import org.sinytra.adapter.model.param.HandlerSignature; import org.sinytra.adapter.model.param.HandlerSignature.ParamSource; import org.sinytra.adapter.model.param.HandlerSignature.ValueSource; import org.sinytra.adapter.model.param.MethodParameters; +import org.sinytra.adapter.model.param.Parameters; import org.sinytra.adapter.pipeline.Recipe; import org.sinytra.adapter.pipeline.TxResult; import org.sinytra.adapter.pipeline.ctx.MixinContext; @@ -47,6 +50,12 @@ public HandlerSignature signature() { return SIGNATURE; } + @Override + public RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + boolean hasOperation = Parameters.getParameterTypes(context.methodNode().desc).contains(TypeConstants.OPERATION_TYPE); + return hasOperation ? RelocationPlan.Passthrough.CALL_ORIGINAL : null; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { resolvers.addBefore(InjectionPointResolver.class, new RewriteResolver(List.of(new InstanceofToConstant()))); diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java index d1d81b65..6119905b 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/injector/WrapWithConditionMixin.java @@ -1,5 +1,6 @@ package org.sinytra.adapter.pipeline.injector; +import org.sinytra.adapter.model.RelocationPlan; import org.objectweb.asm.tree.AbstractInsnNode; import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.config.Configuration; @@ -45,6 +46,11 @@ public HandlerSignature signature() { return SIGNATURE; } + @Override + public RelocationPlan.Passthrough passthrough(MixinContext context, Configuration config) { + return RelocationPlan.Passthrough.PROCEED; + } + @Override public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers) { TargetPair cleanTarget = clean.getTargetMethod() != null ? context.cleanMethods().resolve(clean.getTargetMethod()) : null; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java index b169425a..138faea2 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/locate/ScopeSearch.java @@ -31,7 +31,7 @@ public enum Reach { private static final int MAX_DEPTH = 3; private static final List IGNORED_PACKAGES = List.of("java/", "javax/", "jdk/", "sun/", "com/sun/"); - private record Callee(TargetPair root, TargetPair method, Set originLocals, @Nullable MethodInsnNode calledFrom) { + private record Callee(TargetPair root, TargetPair method, Set originLocals, @Nullable MethodInsnNode calledFrom, @Nullable MethodInsnNode rootCall) { } private final MixinContext context; @@ -107,7 +107,7 @@ private List nextCalledTier() { private List findRootCallees(Scope root) { Set newCalls = new HashSet<>(MethodAnalyzer.findDirtyOnlyCalls(this.cleanTarget.methodNode(), root.method().methodNode(), false)); - Callee start = new Callee(root.method(), root.method(), this.context.isStatic() ? Set.of() : Set.of(0), null); + Callee start = new Callee(root.method(), root.method(), this.context.isStatic() ? Set.of() : Set.of(0), null, null); return findCallees(start, newCalls::contains, true); } @@ -122,7 +122,8 @@ private static Relocation relocation(Callee callee) { int slot = isStatic ? 0 : 1; for (int i = 0; i < args.length; i++) { if (callee.originLocals().contains(slot)) { - return new Relocation.ReceivesOrigin(i); + Relocation.OriginCall via = callee.rootCall() != null ? new Relocation.OriginCall(callee.root(), callee.rootCall()) : null; + return new Relocation.ReceivesOrigin(i, via); } slot += args[i].getSize(); } @@ -145,7 +146,7 @@ private List findCallees(Callee caller, Predicate filter if (method != null && !this.visited.contains(method.methodNode())) { Set locals = PassedLocals.findPassedSlots(flow, call, call.getOpcode() != Opcodes.INVOKESTATIC, call.desc, caller.originLocals()); boolean mirrorable = fromRoot && call.getOpcode() == Opcodes.INVOKESTATIC && !method.classNode().name.equals(from.classNode().name); - addCallee(callees, new Callee(caller.root(), method, locals, mirrorable ? call : null)); + addCallee(callees, new Callee(caller.root(), method, locals, mirrorable ? call : null, fromRoot ? call : caller.rootCall())); } } @@ -153,7 +154,7 @@ private List findCallees(Callee caller, Predicate filter if (!this.visited.contains(lambda.method())) { Handle handle = lambda.handle(); Set locals = PassedLocals.findPassedSlots(flow, lambda.indy(), handle.getTag() != Opcodes.H_INVOKESTATIC, handle.getDesc(), caller.originLocals()); - addCallee(callees, new Callee(caller.root(), new TargetPair(from.classNode(), lambda.method()), locals, null)); + addCallee(callees, new Callee(caller.root(), new TargetPair(from.classNode(), lambda.method()), locals, null, fromRoot ? null : caller.rootCall())); } } return callees; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationGenerator.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationGenerator.java index ab69ad0b..1a0e4434 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationGenerator.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationGenerator.java @@ -13,6 +13,7 @@ import org.sinytra.adapter.api.PatchEnvironment; import org.sinytra.adapter.model.MethodQualifier; import org.sinytra.adapter.model.RelocationPlan; +import org.sinytra.adapter.model.TargetPair; import org.sinytra.adapter.model.ValueSource; import org.sinytra.adapter.model.constant.MixinAnnotations; import org.sinytra.adapter.model.constant.TypeConstants; @@ -75,12 +76,25 @@ public static MethodNode generateMirror(MixinContext context, String destination GeneratorAdapter gen = new GeneratorAdapter(mirror, mirror.access, mirror.name, mirror.desc); gen.newLabel(); int stack = 0; - if (plan.receiver() != null) { - stack += load(gen, plan.receiver(), paramIndices); - Type ownerType = Type.getObjectType(handlerOwner.name); - if (!params.get(argIndex(plan.receiver(), paramIndices)).equals(ownerType)) { - gen.checkCast(ownerType); + int guardStack = 0; + if (plan.receiver() != null && plan.guard() != null) { + String active = generateGuard(context, plan.guard()); + Label run = new Label(); + guardStack = loadReceiver(gen, plan.receiver(), paramIndices, params, handlerOwner); + gen.visitMethodInsn(Opcodes.INVOKEVIRTUAL, handlerOwner.name, active, "()Z", false); + gen.visitJumpInsn(Opcodes.IFNE, run); + switch (plan.guard().passthrough()) { + case SKIP -> { + } + case KEEP_VALUE -> gen.loadArg(0); + case PROCEED -> gen.push(true); + case CALL_ORIGINAL -> guardStack = Math.max(guardStack, callOriginal(gen, params)); } + gen.returnValue(); + gen.visitLabel(run); + } + if (plan.receiver() != null) { + stack += loadReceiver(gen, plan.receiver(), paramIndices, params, handlerOwner); } for (ValueSource source : plan.params()) { stack += load(gen, source, paramIndices); @@ -90,7 +104,7 @@ public static MethodNode generateMirror(MixinContext context, String destination gen.returnValue(); gen.newLabel(); gen.endMethod(); - mirror.maxStack = Math.max(stack, Type.getReturnType(handler.desc).getSize()); + mirror.maxStack = Math.max(Math.max(stack, guardStack), Type.getReturnType(handler.desc).getSize()); mirror.maxLocals = (Type.getArgumentsAndReturnSizes(desc) >> 2) - (isStatic ? 1 : 0); handler.access = OpcodeUtil.setAccessVisibility(handler.access, Opcodes.ACC_PUBLIC); @@ -110,6 +124,129 @@ public static MethodNode generateMirror(MixinContext context, String destination return mirror; } + private static int loadReceiver(GeneratorAdapter gen, ValueSource receiver, Map paramIndices, List params, ClassNode handlerOwner) { + int size = load(gen, receiver, paramIndices); + Type ownerType = Type.getObjectType(handlerOwner.name); + if (!params.get(argIndex(receiver, paramIndices)).equals(ownerType)) { + gen.checkCast(ownerType); + } + return size; + } + + private static int callOriginal(GeneratorAdapter gen, List params) { + int operation = params.indexOf(TypeConstants.OPERATION_TYPE); + gen.loadArg(operation); + gen.push(operation); + gen.newArray(TypeConstants.OBJECT_TYPE); + int operandSize = 1; + for (int i = 0; i < operation; i++) { + gen.dup(); + gen.push(i); + gen.loadArg(i); + gen.box(params.get(i)); + gen.arrayStore(TypeConstants.OBJECT_TYPE); + operandSize = Math.max(operandSize, params.get(i).getSize()); + } + gen.invokeInterface(TypeConstants.OPERATION_TYPE, new Method("call", TypeConstants.OBJECT_TYPE, new Type[]{Type.getType(Object[].class)})); + Type returnType = gen.getReturnType(); + if (returnType == Type.VOID_TYPE) { + gen.pop(); + } else { + gen.unbox(returnType); + } + return 4 + operandSize + 2; + } + + private static String generateGuard(MixinContext context, RelocationPlan.Guard guard) { + TargetPair origin = guard.via().method(); + MethodInsnNode call = guard.via().call(); + int ordinal = 0; + for (AbstractInsnNode insn = origin.methodNode().instructions.getFirst(); insn != call; insn = insn.getNext()) { + if (insn instanceof MethodInsnNode other && other.owner.equals(call.owner) && other.name.equals(call.name) && other.desc.equals(call.desc)) { + ordinal++; + } + } + + ClassNode generatedTarget = getGeneratedTarget(context, origin.classNode().name); + String base = "adapter$via$" + simpleName(context.classNode()) + "$" + context.methodNode().name; + FieldNode depth; + List methods; + synchronized (generatedTarget) { + String name = base; + for (int i = 1; hasMethodNamed(generatedTarget, name) || hasFieldNamed(generatedTarget, name); i++) { + name = base + "$" + i; + } + depth = new FieldNode(Opcodes.ACC_PRIVATE, name, Type.INT_TYPE.getDescriptor(), null, null); + methods = List.of( + generateIsActive(generatedTarget, depth), + generateDepthChange(origin, call, ordinal, generatedTarget, depth, 1), + generateDepthChange(origin, call, ordinal, generatedTarget, depth, -1) + ); + generatedTarget.fields.add(depth); + generatedTarget.methods.addAll(methods); + } + context.patchContext().onRollback(() -> { + synchronized (generatedTarget) { + generatedTarget.fields.remove(depth); + generatedTarget.methods.removeAll(methods); + } + }); + return depth.name; + } + + private static MethodNode generateIsActive(ClassNode generatedTarget, FieldNode depth) { + MethodNode active = new MethodNode(Opcodes.ACC_PUBLIC, depth.name, "()Z", null, null); + GeneratorAdapter gen = new GeneratorAdapter(active, active.access, active.name, active.desc); + Label inactive = new Label(); + gen.loadThis(); + gen.getField(Type.getObjectType(generatedTarget.name), depth.name, Type.INT_TYPE); + gen.visitJumpInsn(Opcodes.IFLE, inactive); + gen.push(true); + gen.returnValue(); + gen.visitLabel(inactive); + gen.push(false); + gen.returnValue(); + gen.endMethod(); + active.maxStack = 1; + active.maxLocals = 1; + return active; + } + + private static MethodNode generateDepthChange(TargetPair origin, MethodInsnNode call, int ordinal, ClassNode generatedTarget, FieldNode depth, int change) { + Type callback = Type.getReturnType(origin.methodNode().desc) == Type.VOID_TYPE ? TypeConstants.CI_TYPE : TypeConstants.CIR_TYPE; + String name = depth.name + (change > 0 ? "$enter" : "$exit"); + MethodNode inject = new MethodNode(Opcodes.ACC_PRIVATE, name, Type.getMethodDescriptor(Type.VOID_TYPE, callback), null, null); + AnnotationVisitor annotation = inject.visitAnnotation(MixinAnnotations.INJECT, true); + AnnotationVisitor method = annotation.visitArray("method"); + method.visit(null, origin.methodNode().name + origin.methodNode().desc); + method.visitEnd(); + AnnotationVisitor ats = annotation.visitArray("at"); + AnnotationVisitor at = ats.visitAnnotation(null, MixinAnnotations.AT); + at.visit("value", "INVOKE"); + at.visit("target", Type.getObjectType(call.owner).getDescriptor() + call.name + call.desc); + at.visit("ordinal", ordinal); + if (change < 0) { + at.visitEnum("shift", "Lorg/spongepowered/asm/mixin/injection/At$Shift;", "AFTER"); + } + at.visitEnd(); + ats.visitEnd(); + annotation.visitEnd(); + + Type owner = Type.getObjectType(generatedTarget.name); + GeneratorAdapter gen = new GeneratorAdapter(inject, inject.access, inject.name, inject.desc); + gen.loadThis(); + gen.dup(); + gen.getField(owner, depth.name, Type.INT_TYPE); + gen.push(change); + gen.math(GeneratorAdapter.ADD, Type.INT_TYPE); + gen.putField(owner, depth.name, Type.INT_TYPE); + gen.returnValue(); + gen.endMethod(); + inject.maxStack = 3; + inject.maxLocals = 2; + return inject; + } + public static void generateMove(MixinContext context, String destinationClass, RelocationPlan.Move plan) { ClassNode mixin = context.classNode(); ClassNode generatedTarget = getGeneratedTarget(context, destinationClass); @@ -117,6 +254,12 @@ public static void generateMove(MixinContext context, String destinationClass, R moved.addFirst(context.methodNode()); registerMoveRollback(context, generatedTarget, plan, moved); + if (plan.makeStatic()) { + AdapterUtil.makeStatic(context.methodNode()); + } + if (MethodAnalyzer.isStatic(context.methodNode()) && (context.methodNode().access & Opcodes.ACC_SYNTHETIC) == 0) { + context.methodNode().access = OpcodeUtil.setAccessVisibility(context.methodNode().access, Opcodes.ACC_PRIVATE); + } plan.widenedFields().forEach(field -> field.access = widenAccess(field.access)); plan.widenedMethods().forEach(method -> method.access = widenAccess(method.access)); for (MethodNode method : moved) { @@ -243,6 +386,10 @@ private static boolean hasMethodNamed(ClassNode cls, String name) { return cls.methods.stream().anyMatch(m -> m.name.equals(name)); } + private static boolean hasFieldNamed(ClassNode cls, String name) { + return cls.fields.stream().anyMatch(f -> f.name.equals(name)); + } + private static String simpleName(ClassNode cls) { return cls.name.substring(cls.name.lastIndexOf('/') + 1); } diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java index e2d8a9c4..d17237d9 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/relocate/RelocationSolver.java @@ -1,6 +1,7 @@ package org.sinytra.adapter.pipeline.processor.relocate; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.sinytra.adapter.analysis.injection.InjectionQuery; @@ -55,9 +56,9 @@ public static Result solve(MixinContext context, Recipe recipe, Configuration di return new Result.Unbound("the new target method doesn't exist"); } - TargetPair handlerTarget = recipe.getNewCleanTarget(); - if (handlerTarget == null) { - return new Result.Unbound("the original target method doesn't exist"); + ClassNode handlerOwner = context.dirtyLookup().getClass(recipe.clean().getTargetClass()).orElse(null); + if (handlerOwner == null) { + return new Result.Unbound("the original target class doesn't exist"); } boolean isStatic = MethodAnalyzer.isStatic(newTarget.methodNode()); if (params.stream().anyMatch(Parameter::isShare)) { @@ -69,13 +70,33 @@ public static Result solve(MixinContext context, Recipe recipe, Configuration di List forwarded = IntStream.range(0, params.size()).mapToObj(ValueSource.Forwarded::new).toList(); if (context.isStatic()) { - return new Result.Solved(new RelocationPlan.Mirror(handlerTarget.classNode(), isStatic, null, forwarded)); + return new Result.Solved(new RelocationPlan.Mirror(handlerOwner, isStatic, null, forwarded, null)); } ValueSource receiver = findReceiver(relocation, newTarget); if (receiver == null) { return new Result.Unbound("nothing at the new target holds the original instance"); } - return new Result.Solved(new RelocationPlan.Mirror(handlerTarget.classNode(), isStatic, receiver, forwarded)); + return new Result.Solved(new RelocationPlan.Mirror(handlerOwner, isStatic, receiver, forwarded, findGuard(context, dirty, relocation, handlerOwner))); + } + + /// {@return a guard that limits the mirror to calls from the original method, or null if it can't be limited} + /// Several original methods may now reach the new target, and the handler only ran for one of them. + @Nullable + private static RelocationPlan.Guard findGuard(MixinContext context, Configuration dirty, Relocation relocation, ClassNode handlerOwner) { + Relocation.OriginCall via = relocation instanceof Relocation.ReceivesOrigin receives ? receives.via() : null; + if (via == null) { + return null; + } + ClassNode origin = via.method().classNode(); + boolean reachable = handlerOwner.name.equals(origin.name) + || context.environment().inheritanceHandler(context.dirtyLookup()).isClassInherited(handlerOwner.name, origin.name); + if (!reachable || (origin.access & Opcodes.ACC_INTERFACE) != 0 || MethodAnalyzer.isStatic(via.method().methodNode()) + || !via.method().methodNode().instructions.contains(via.call()) + ) { + return null; + } + RelocationPlan.Passthrough passthrough = context.getMixinType().passthrough(context, dirty); + return passthrough != null ? new RelocationPlan.Guard(via, passthrough) : null; } @Nullable @@ -85,6 +106,8 @@ private static RelocationPlan.Move findMove(MixinContext context, Recipe recipe, TargetPair cleanTarget = recipe.getCleanTarget(); String origin = cleanTarget != null ? cleanTarget.classNode().name : destination; boolean inherited = context.environment().inheritanceHandler(context.dirtyLookup()).isClassInherited(destination, origin); + TargetPair newTarget = recipe.getDirtyTarget(); + boolean staticTarget = newTarget != null && MethodAnalyzer.isStatic(newTarget.methodNode()); List lambdas = MethodAnalyzer.findOwnIndyHandles(mixin, handler).stream() .filter(lambda -> lambda.handle().getName().startsWith(MethodAnalyzer.LAMBDA_PREFIX + handler.name)) @@ -96,7 +119,12 @@ private static RelocationPlan.Move findMove(MixinContext context, Recipe recipe, List widenedFields = new ArrayList<>(); List widenedMethods = new ArrayList<>(); for (MethodNode method : moved) { + boolean ownInstance = (!inherited || staticTarget) && !MethodAnalyzer.isStatic(method); for (AbstractInsnNode insn : method.instructions) { + // The moved method's instance becomes the destination's, which isn't the origin class's, or none at a static target + if (ownInstance && insn instanceof VarInsnNode varInsn && varInsn.getOpcode() == Opcodes.ALOAD && varInsn.var == 0) { + return null; + } if (insn instanceof FieldInsnNode fieldInsn && fieldInsn.owner.equals(mixin.name)) { FieldNode field = AdapterUtil.findOne(mixin.fields, f -> f.name.equals(fieldInsn.name)).orElse(null); if (field == null || !isAvailable(field.visibleAnnotations, inherited)) { @@ -112,7 +140,7 @@ private static RelocationPlan.Move findMove(MixinContext context, Recipe recipe, } } } - return new RelocationPlan.Move(lambdas, widenedFields, widenedMethods); + return new RelocationPlan.Move(lambdas, widenedFields, widenedMethods, staticTarget && !context.isStatic()); } private static boolean isAvailable(@Nullable List annotations, boolean inherited) { @@ -147,7 +175,8 @@ private static boolean selectsLocals(MixinContext context, Configuration dirty, @Nullable private static ValueSource findReceiver(Relocation relocation, TargetPair newTarget) { - if (relocation instanceof Relocation.ReceivesOrigin(int argIndex)) { + if (relocation instanceof Relocation.ReceivesOrigin receives) { + int argIndex = receives.argIndex(); Type[] args = Type.getArgumentTypes(newTarget.methodNode().desc); if (argIndex >= args.length) { return null; diff --git a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java index a5212bbf..a2d91226 100644 --- a/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java +++ b/core/src/main/java/org/sinytra/adapter/pipeline/processor/signature/StaticAccessStep.java @@ -39,11 +39,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) } context.recordCtxAudit("Made method static"); - method.access |= Opcodes.ACC_STATIC; - if (method.localVariables != null) { - method.localVariables.removeIf(lvn -> lvn.index == 0); - } - shiftLocals(method, -1); + AdapterUtil.makeStatic(method); return TxResult.SUCCESS; } if (!dirtyStatic && actuallyStatic) { @@ -51,7 +47,7 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) method.access &= ~Opcodes.ACC_STATIC; LocalVariableNode firstParam = method.localVariables == null ? null : method.localVariables.stream().filter(lvn -> lvn.index == 0).findFirst().orElse(null); - shiftLocals(method, 1); + AdapterUtil.shiftLocals(method, 1); if (firstParam != null) { method.localVariables.add(new LocalVariableNode("this", Type.getObjectType(context.classNode().name).getDescriptor(), null, firstParam.start, firstParam.end, 0)); } @@ -61,10 +57,4 @@ public TxResult apply(MixinContext context, Configuration dirty, Recipe recipe) return TxResult.PASS; } - private static void shiftLocals(MethodNode method, int delta) { - if (method.localVariables != null) { - method.localVariables.forEach(lvn -> lvn.index += delta); - } - AdapterUtil.replaceLVT(method, index -> Math.max(index + delta, 0)); - } } diff --git a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java index 7570ef10..d8afe9bd 100644 --- a/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java +++ b/core/src/main/java/org/sinytra/adapter/util/AdapterUtil.java @@ -68,6 +68,21 @@ public static void replaceLVT(MethodNode methodNode, Int2IntFunction operator) { } } + public static void shiftLocals(MethodNode methodNode, int delta) { + if (methodNode.localVariables != null) { + methodNode.localVariables.forEach(lvn -> lvn.index += delta); + } + replaceLVT(methodNode, index -> Math.max(index + delta, 0)); + } + + public static void makeStatic(MethodNode methodNode) { + methodNode.access |= Opcodes.ACC_STATIC; + if (methodNode.localVariables != null) { + methodNode.localVariables.removeIf(lvn -> lvn.index == 0); + } + shiftLocals(methodNode, -1); + } + public static boolean canHandleLocalVarInsnValue(AbstractInsnNode insn) { return insn instanceof VarInsnNode || insn instanceof IincInsnNode; } diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index e8db2de3..b4fdca9d 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -5,9 +5,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.BasicVerifier; import org.objectweb.asm.util.Textifier; import org.objectweb.asm.util.TraceMethodVisitor; import org.sinytra.adapter.api.ClassLookup; @@ -23,6 +28,7 @@ import org.spongepowered.asm.mixin.FabricUtil; import java.util.List; +import java.util.stream.StreamSupport; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -231,21 +237,83 @@ void testSplitPieceOfRemovedTarget() throws Exception { @Test void testMirrorIntoInstanceCallee() throws Exception { + assertMirroredStepSound("adjustStepSoundVolume"); + } + + @Test + void testMirrorFromChangedTarget() throws Exception { + assertMirroredStepSound("adjustMuffledStepSoundVolume"); + } + + @Test + void testHandlerUsingThisNotMoved() throws Exception { + assertMirroredStepSound("adjustStepSoundVolumeOfThis"); + } + + @Test + void testWrapOperationMirrorGuarded() throws Exception { + String generatedName = "org/sinytra/adapter/test/mc_26_1_2/mixin/adapter_generated_net_neoforged_neoforge_common_extensions_IBlockExtension"; + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin", + "wrapStepSound", + assertUnique(), + assertHasGeneratedMethod(generatedName), + (patched, expected, env) -> { + ClassNode generated = env.classGenerator().getGeneratedMixinClasses().get(generatedName).node(); + MethodNode mirror = generated.methods.stream().filter(m -> m.name.startsWith("wrapStepSound$adapter$mirror")).findFirst().orElseThrow(); + assertVerifies(generated, mirror); + Assertions.assertThat(AdapterUtil.methodNodeToString(mirror)) + .as("Mirror calls the original operation when it doesn't run the handler") + .contains("INVOKEVIRTUAL net/minecraft/world/entity/Entity.adapter$via$EntityMixin$wrapStepSound ()Z") + .contains("INVOKEINTERFACE com/llamalad7/mixinextras/injector/wrapoperation/Operation.call ([Ljava/lang/Object;)Ljava/lang/Object;"); + } + ); + } + + private static void assertVerifies(ClassNode owner, MethodNode method) { + try { + new Analyzer<>(new BasicVerifier()).analyze(owner.name, method); + } catch (AnalyzerException e) { + throw new AssertionError("Invalid bytecode in " + method.name + "\n" + AdapterUtil.methodNodeToString(method), e); + } + } + + @Test + void testMirrorGuardedByOriginCall() throws Exception { + assertMirroredStepSound("adjustCombinationStepSoundVolume"); + ClassNode guards = patchEnvironment.classGenerator().getGeneratedMixinClasses() + .get("org/sinytra/adapter/test/mc_26_1_2/mixin/adapter_generated_net_minecraft_world_entity_Entity").node(); + MethodNode enter = guards.methods.stream().filter(m -> m.name.equals("adapter$via$EntityMixin$adjustCombinationStepSoundVolume$enter")).findFirst().orElseThrow(); + AnnotationHandle inject = new AnnotationHandle(enter.visibleAnnotations.getFirst()); + Assertions.assertThat(inject.>getValue("method").orElseThrow().get()) + .containsExactly("playCombinationStepSounds(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/BlockPos;)V"); + Assertions.assertThat(AdapterUtil.methodNodeToString(enter)).contains("target=\"Lnet/minecraft/world/level/block/state/BlockState;playStepSound("); + } + + private void assertMirroredStepSound(String handler) throws Exception { String generatedName = "org/sinytra/adapter/test/mc_26_1_2/mixin/adapter_generated_net_neoforged_neoforge_common_extensions_IBlockExtension"; assertSameCode( "org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin", - "adjustStepSoundVolume", + handler, assertUnique(), assertHasGeneratedMethod(generatedName), (patched, expected, env) -> { ClassNode generated = env.classGenerator().getGeneratedMixinClasses().get(generatedName).node(); - MethodNode mirror = generated.methods.stream().filter(m -> m.name.startsWith("adjustStepSoundVolume$adapter$mirror")).findFirst().orElseThrow(); + MethodNode mirror = generated.methods.stream().filter(m -> m.name.startsWith(handler + "$adapter$mirror")).findFirst().orElseThrow(); + assertVerifies(generated, mirror); Assertions.assertThat(mirror.desc).isEqualTo("(FLnet/minecraft/world/entity/Entity;)F"); AnnotationHandle modifyArg = new AnnotationHandle(mirror.visibleAnnotations.getFirst()); Assertions.assertThat(modifyArg.>getValue("method").orElseThrow().get()) .containsExactly("playStepSound(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/Entity;FF)V"); AnnotationNode local = mirror.invisibleParameterAnnotations[1].getFirst(); Assertions.assertThat(local.values).containsExactly("argsOnly", true, "index", 4); + MethodInsnNode active = (MethodInsnNode) StreamSupport.stream(mirror.instructions.spliterator(), false) + .filter(MethodInsnNode.class::isInstance) + .findFirst() + .orElseThrow(); + Assertions.assertThat(active.owner + "." + active.name + active.desc) + .as("Mirror runs only for calls from the original method") + .isEqualTo("net/minecraft/world/entity/Entity.adapter$via$EntityMixin$" + handler + "()Z"); } ); } @@ -260,6 +328,19 @@ void testModifyExpressionValueReplacedHook() throws Exception { ); } + @Test + void testModifyArgsMovedIntoHook() throws Exception { + LoadResult result = load("org/sinytra/adapter/test/mc_26_1_2/mixin/FogRendererMixin", List.of("darkenFogColor")); + ClassNode generated = result.env().classGenerator().getGeneratedMixinClasses() + .get("org/sinytra/adapter/test/mc_26_1_2/mixin/adapter_generated_net_neoforged_neoforge_client_ClientHooks").node(); + MethodNode moved = generated.methods.stream().filter(m -> m.name.equals("darkenFogColor")).findFirst().orElseThrow(); + Assertions.assertThat(moved.access & Opcodes.ACC_STATIC).as("Static handler").isNotZero(); + Assertions.assertThat(moved.access & Opcodes.ACC_PRIVATE).as("Private static handler").isNotZero(); + AnnotationHandle modifyArgs = new AnnotationHandle(moved.visibleAnnotations.getFirst()); + Assertions.assertThat(modifyArgs.>getValue("method").orElseThrow().get()) + .containsExactly("getFogColor(Lnet/minecraft/client/Camera;FLnet/minecraft/client/multiplayer/ClientLevel;IFFFFLorg/joml/Vector4f;)V"); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin.java index e67a194b..d2cd014c 100644 --- a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin.java +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/EntityMixin.java @@ -1,5 +1,8 @@ package org.sinytra.adapter.test.mc_26_1_2.mixin; +import net.minecraft.sounds.SoundEvent; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.world.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -20,4 +23,48 @@ private float adjustStepSoundVolume(float original) { public float adjustStepSoundVolumeExpected(float original) { return original * self.getBbWidth(); } + + @ModifyArg(method = "playMuffledStepSound", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V"), index = 1) + private float adjustMuffledStepSoundVolume(float original) { + return original * self.getBbWidth(); + } + + @Unique + public float adjustMuffledStepSoundVolumeExpected(float original) { + return original * self.getBbWidth(); + } + + @ModifyArg(method = "playStepSound", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V"), index = 1) + private float adjustStepSoundVolumeOfThis(float original) { + return original * ((Entity) (Object) this).getBbWidth(); + } + + @Unique + public float adjustStepSoundVolumeOfThisExpected(float original) { + return original * ((Entity) (Object) this).getBbWidth(); + } + + @ModifyArg(method = "playCombinationStepSounds", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V"), index = 1) + private float adjustCombinationStepSoundVolume(float original) { + return original * self.getBbWidth(); + } + + @Unique + public float adjustCombinationStepSoundVolumeExpected(float original) { + return original * self.getBbWidth(); + } + + @WrapOperation(method = "playStepSound", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V")) + private void wrapStepSound(Entity instance, SoundEvent sound, float volume, float pitch, Operation original) { + if (!self.isSilent()) { + original.call(instance, sound, volume, pitch); + } + } + + @Unique + public void wrapStepSoundExpected(Entity instance, SoundEvent sound, float volume, float pitch, Operation original) { + if (!self.isSilent()) { + original.call(instance, sound, volume, pitch); + } + } } diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/FogRendererMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/FogRendererMixin.java new file mode 100644 index 00000000..b855a4da --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/FogRendererMixin.java @@ -0,0 +1,18 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.fog.FogRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArgs; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +@Mixin(FogRenderer.class) +public class FogRendererMixin { + @ModifyArgs(method = "computeFogColor", at = @At(value = "INVOKE", target = "Lorg/joml/Vector4f;set(FFFF)Lorg/joml/Vector4f;")) + public void darkenFogColor(Args args) { + if (Minecraft.getInstance().level != null) { + args.set(0, (float) args.get(0) * 0.5F); + } + } +}