From 2e7a9fde4a9fda1a9d75463a7cc633520ec9e85b Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 16 Jun 2025 14:11:24 +0100 Subject: [PATCH 1/7] core: Don't use try-with-resources for NotifyStack --- .../dasm/annotation/AnnotationParser.java | 119 +++++----- .../annotation/parse/RedirectSetImpl.java | 222 +++++++++--------- .../notstirred/dasm/data/DasmContext.java | 203 ++++++++-------- .../dasm/transformer/Transformer.java | 96 ++++---- .../notstirred/dasm/util/NotifyStack.java | 40 ++-- 5 files changed, 334 insertions(+), 346 deletions(-) diff --git a/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java b/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java index e29143e..ae3bcbf 100644 --- a/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java +++ b/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java @@ -82,77 +82,74 @@ public NotifyStack findDasmAnnotations(ClassNode targetClass) { Type targetClassType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name)); boolean isTargetInterface = (targetClass.access & Opcodes.ACC_INTERFACE) != 0; - try (NotifyStack classExceptions = NotifyStack.of(targetClass)) { - - findRedirectSetsForAnnotation(targetClass.invisibleAnnotations, Dasm.class, "value", classExceptions); - findRedirectSetsForAnnotation(targetClass.invisibleAnnotations, TransformFromClass.class, "sets", classExceptions); - - for (FieldNode fieldNode : targetClass.fields) { - try (NotifyStack fieldExceptions = classExceptions.push(fieldNode)) { - findOuterRedirectSetsForAnnotation(fieldNode.invisibleAnnotations, AddFieldToSets.class, "containers", fieldExceptions); - - try { - Optional optAddToSets = AddFieldToSetsImpl.parse(targetClassType, fieldNode); - if (optAddToSets.isPresent()) { - AddFieldToSetsImpl addToSets = optAddToSets.get(); - // All containers for this method must already exist, so we can just use the map - for (Type containerType : addToSets.containers()) { - RedirectSetImpl.Container container = this.containers.get(containerType); - if (container == null) { - fieldExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType)); - continue; - } - FieldRedirectImpl fieldRedirect = new FieldRedirectImpl( - new ClassField(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcField().type(), addToSets.srcField().name()), - addToSets.dstOwner(), - addToSets.dstMethodName() - ); - container.fieldRedirects().add(fieldRedirect); - } + NotifyStack classExceptions = NotifyStack.of(targetClass); + + findRedirectSetsForAnnotation(targetClass.invisibleAnnotations, Dasm.class, "value", classExceptions); + findRedirectSetsForAnnotation(targetClass.invisibleAnnotations, TransformFromClass.class, "sets", classExceptions); + + for (FieldNode fieldNode : targetClass.fields) { + NotifyStack fieldExceptions = classExceptions.push(fieldNode); + findOuterRedirectSetsForAnnotation(fieldNode.invisibleAnnotations, AddFieldToSets.class, "containers", fieldExceptions); + + try { + Optional optAddToSets = AddFieldToSetsImpl.parse(targetClassType, fieldNode); + if (optAddToSets.isPresent()) { + AddFieldToSetsImpl addToSets = optAddToSets.get(); + // All containers for this method must already exist, so we can just use the map + for (Type containerType : addToSets.containers()) { + RedirectSetImpl.Container container = this.containers.get(containerType); + if (container == null) { + fieldExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType)); + continue; } - } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | - MethodSigImpl.EmptySrcName e) { - fieldExceptions.notifyFromException(e); + FieldRedirectImpl fieldRedirect = new FieldRedirectImpl( + new ClassField(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcField().type(), addToSets.srcField().name()), + addToSets.dstOwner(), + addToSets.dstMethodName() + ); + container.fieldRedirects().add(fieldRedirect); } } + } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | + MethodSigImpl.EmptySrcName e) { + fieldExceptions.notifyFromException(e); } + } + + for (MethodNode methodNode : targetClass.methods) { + NotifyStack methodExceptions = classExceptions.push(methodNode); + findRedirectSetsForAnnotation(methodNode.invisibleAnnotations, TransformFromMethod.class, "useRedirectSets", methodExceptions); + findOuterRedirectSetsForAnnotation(methodNode.invisibleAnnotations, AddTransformToSets.class, "value", methodExceptions); + findOuterRedirectSetsForAnnotation(methodNode.invisibleAnnotations, AddMethodToSets.class, "containers", methodExceptions); - for (MethodNode methodNode : targetClass.methods) { - try (NotifyStack methodExceptions = classExceptions.push(methodNode)) { - findRedirectSetsForAnnotation(methodNode.invisibleAnnotations, TransformFromMethod.class, "useRedirectSets", methodExceptions); - findOuterRedirectSetsForAnnotation(methodNode.invisibleAnnotations, AddTransformToSets.class, "value", methodExceptions); - findOuterRedirectSetsForAnnotation(methodNode.invisibleAnnotations, AddMethodToSets.class, "containers", methodExceptions); - - try { - Optional optAddToSets = AddMethodToSetsImpl.parse(targetClassType, isTargetInterface, methodNode); - if (optAddToSets.isPresent()) { - AddMethodToSetsImpl addToSets = optAddToSets.get(); - // All containers for this method must already exist, so we can just use the map - for (Type containerType : addToSets.containers()) { - RedirectSetImpl.Container container = this.containers.get(containerType); - if (container == null) { - methodExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType)); - continue; - } - - MethodRedirectImpl methodRedirect = new MethodRedirectImpl( - new ClassMethod(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcMethod()), - addToSets.dstOwner(), - addToSets.dstMethodName(), - addToSets.isStatic(), - addToSets.isDstInterface() - ); - container.methodRedirects().add(methodRedirect); - } + try { + Optional optAddToSets = AddMethodToSetsImpl.parse(targetClassType, isTargetInterface, methodNode); + if (optAddToSets.isPresent()) { + AddMethodToSetsImpl addToSets = optAddToSets.get(); + // All containers for this method must already exist, so we can just use the map + for (Type containerType : addToSets.containers()) { + RedirectSetImpl.Container container = this.containers.get(containerType); + if (container == null) { + methodExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType)); + continue; } - } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | - MethodSigImpl.EmptySrcName e) { - methodExceptions.notifyFromException(e); + + MethodRedirectImpl methodRedirect = new MethodRedirectImpl( + new ClassMethod(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcMethod()), + addToSets.dstOwner(), + addToSets.dstMethodName(), + addToSets.isStatic(), + addToSets.isDstInterface() + ); + container.methodRedirects().add(methodRedirect); } } + } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | + MethodSigImpl.EmptySrcName e) { + methodExceptions.notifyFromException(e); } - return classExceptions; } + return classExceptions; } private void findRedirectSetsForAnnotation(List annotations, Class annotationClass, String setsAnnotationField, diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java index a33154e..4b84173 100644 --- a/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java +++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java @@ -55,68 +55,66 @@ public enum Kind { public static Optional parse(ClassNode redirectSetClassNode, ClassNodeProvider provider, NotifyStack exceptions) { List superRedirectSets = new ArrayList<>(); - try (NotifyStack redirectSetExceptions = exceptions.push(redirectSetClassNode)) { + NotifyStack redirectSetExceptions = exceptions.push(redirectSetClassNode); - AnnotationNode annotationNode = getAnnotationIfPresent(redirectSetClassNode.invisibleAnnotations, RedirectSet.class); - if (annotationNode == null) { - redirectSetExceptions.notify(new MissingRedirectSetAnnotationException(Type.getObjectType(redirectSetClassNode.name))); - } + AnnotationNode annotationNode = getAnnotationIfPresent(redirectSetClassNode.invisibleAnnotations, RedirectSet.class); + if (annotationNode == null) { + redirectSetExceptions.notify(new MissingRedirectSetAnnotationException(Type.getObjectType(redirectSetClassNode.name))); + } - if ((redirectSetClassNode.access & ACC_INTERFACE) == 0) { - redirectSetExceptions.notify(new NonInterfaceIsUsedAsRedirectSetException(Type.getObjectType(redirectSetClassNode.name))); - } + if ((redirectSetClassNode.access & ACC_INTERFACE) == 0) { + redirectSetExceptions.notify(new NonInterfaceIsUsedAsRedirectSetException(Type.getObjectType(redirectSetClassNode.name))); + } + + // Add inherited redirect sets + for (String itf : redirectSetClassNode.interfaces) { + superRedirectSets.add(Type.getObjectType(itf)); + } + + Map innerClassData = new HashMap<>(); - // Add inherited redirect sets - for (String itf : redirectSetClassNode.interfaces) { - superRedirectSets.add(Type.getObjectType(itf)); + // Discover type/field/method redirects in innerClass + for (InnerClassNode innerClass : redirectSetClassNode.innerClasses) { + if (!innerClass.outerName.equals(redirectSetClassNode.name) || innerClass.name.equals(redirectSetClassNode.name)) { + // `innerClasses` contains a list of all inner classes of the root class, exclude any not a direct child + // also seems to contain the outer class too. + continue; } - Map innerClassData = new HashMap<>(); + ClassNode innerClassNode; + try { + innerClassNode = provider.classNode(Type.getObjectType(innerClass.name)); + } catch (NoSuchTypeExists e) { + redirectSetExceptions.notifyFromException(e); + // The inner class doesn't exist, we can't begin parsing it. + continue; + } + NotifyStack innerClassExceptions = redirectSetExceptions.push(innerClassNode); + parseInnerClass(innerClassNode, innerClassExceptions).ifPresent(superNameContainerPair -> + innerClassData.put(superNameContainerPair.container().type(), superNameContainerPair) + ); + } - // Discover type/field/method redirects in innerClass - for (InnerClassNode innerClass : redirectSetClassNode.innerClasses) { - if (!innerClass.outerName.equals(redirectSetClassNode.name) || innerClass.name.equals(redirectSetClassNode.name)) { - // `innerClasses` contains a list of all inner classes of the root class, exclude any not a direct child - // also seems to contain the outer class too. - continue; + innerClassData.values().forEach(innerClassInfo -> { + innerClassInfo.superNames.forEach(superName -> { + Type superContainerType = Type.getObjectType(superName); + if (superContainerType.equals(Type.getType(Object.class))) { // having no super type is always OK + return; } - - ClassNode innerClassNode; - try { - innerClassNode = provider.classNode(Type.getObjectType(innerClass.name)); - } catch (NoSuchTypeExists e) { - redirectSetExceptions.notifyFromException(e); - // The inner class doesn't exist, we can't begin parsing it. - continue; + InnerClassInfo superContainerInfo = innerClassData.get(superContainerType); + if (superContainerInfo == null) { + redirectSetExceptions.notify(new SuperTypeInInvalidRedirectSet(innerClassInfo.container.type.getClassName(), superContainerType.getClassName())); + return; } - try (NotifyStack innerClassExceptions = redirectSetExceptions.push(innerClassNode)) { - parseInnerClass(innerClassNode, innerClassExceptions).ifPresent(superNameContainerPair -> - innerClassData.put(superNameContainerPair.container().type(), superNameContainerPair) - ); + if (innerClassInfo.container.superContainer != null) { + redirectSetExceptions.notify(new MultipleContainerInheritanceNotImplemented(innerClassInfo.container)); + return; } - } - - innerClassData.values().forEach(innerClassInfo -> { - innerClassInfo.superNames.forEach(superName -> { - Type superContainerType = Type.getObjectType(superName); - if (superContainerType.equals(Type.getType(Object.class))) { // having no super type is always OK - return; - } - InnerClassInfo superContainerInfo = innerClassData.get(superContainerType); - if (superContainerInfo == null) { - redirectSetExceptions.notify(new SuperTypeInInvalidRedirectSet(innerClassInfo.container.type.getClassName(), superContainerType.getClassName())); - return; - } - if (innerClassInfo.container.superContainer != null) { - redirectSetExceptions.notify(new MultipleContainerInheritanceNotImplemented(innerClassInfo.container)); - return; - } - innerClassInfo.container.superContainer = superContainerInfo.container(); - }); + innerClassInfo.container.superContainer = superContainerInfo.container(); }); + }); - return Optional.of(new RedirectSetImpl(superRedirectSets, innerClassData.values().stream().map(InnerClassInfo::container).collect(Collectors.toList()))); - } + return Optional.of(new RedirectSetImpl(superRedirectSets, innerClassData.values().stream().map(InnerClassInfo::container).collect(Collectors.toList()))); } @Data @@ -216,17 +214,16 @@ private static Optional parseInnerClass(ClassNode innerClassNode private static void parseFields(ClassNode innerClassNode, Type srcType, Type dstType, Set fieldRedirects, Set fieldToMethodRedirects, NotifyStack exceptions) { for (FieldNode fieldNode : innerClassNode.fields) { - try (NotifyStack fieldExceptions = exceptions.push(fieldNode)) { - try { - Optional fieldRedirect = FieldRedirectImpl.parseFieldRedirect(srcType, fieldNode, dstType); - if (fieldRedirect.isPresent()) { - fieldRedirects.add(fieldRedirect.get()); - } else { - fieldExceptions.notify(new FieldMissingFieldRedirectAnnotationException(fieldNode)); - } - } catch (RefImpl.RefAnnotationGivenNoArguments | FieldRedirectImpl.FieldRedirectHasEmptySrcName e) { - fieldExceptions.notifyFromException(e); + NotifyStack fieldExceptions = exceptions.push(fieldNode); + try { + Optional fieldRedirect = FieldRedirectImpl.parseFieldRedirect(srcType, fieldNode, dstType); + if (fieldRedirect.isPresent()) { + fieldRedirects.add(fieldRedirect.get()); + } else { + fieldExceptions.notify(new FieldMissingFieldRedirectAnnotationException(fieldNode)); } + } catch (RefImpl.RefAnnotationGivenNoArguments | FieldRedirectImpl.FieldRedirectHasEmptySrcName e) { + fieldExceptions.notifyFromException(e); } } } @@ -239,60 +236,59 @@ private static void parseMethods(ClassNode innerClassNode, Type srcType, Type ds continue; // Skip default empty constructor } - try (NotifyStack methodExceptions = exceptions.push(methodNode)) { - - Optional methodRedirect = Optional.empty(); - Optional fieldToMethodRedirect = Optional.empty(); - Optional constructorToFactoryRedirect = Optional.empty(); - try { - methodRedirect = MethodRedirectImpl.parseMethodRedirect( - srcType, - (innerClassNode.access & ACC_INTERFACE) != 0, - methodNode, - dstType - ); - } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | - MethodSigImpl.EmptySrcName e) { - methodExceptions.notifyFromException(e); - } - try { - fieldToMethodRedirect = FieldToMethodRedirectImpl.parse( - srcType, - (innerClassNode.access & ACC_INTERFACE) != 0, - methodNode, - dstType - ); - } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | - MethodSigImpl.EmptySrcName e) { - methodExceptions.notifyFromException(e); - } - try { - constructorToFactoryRedirect = ConstructorToFactoryRedirectImpl.parse( - srcType, - (innerClassNode.access & ACC_INTERFACE) != 0, - methodNode, - dstType - ); - } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | - MethodSigImpl.EmptySrcName e) { - methodExceptions.notifyFromException(e); - } - - if (atLeastTwoOf(methodRedirect.isPresent(), fieldToMethodRedirect.isPresent(), constructorToFactoryRedirect.isPresent())) { - // if both are present, add exception and return - methodExceptions.notify(new MoreThanOneMethodRedirect(methodNode)); - return; - } else if (!methodRedirect.isPresent() && !fieldToMethodRedirect.isPresent() && !constructorToFactoryRedirect.isPresent()) { - // if none are present, add exception and return - methodExceptions.notify(new MissingMethodRedirectRedirect(methodNode)); - return; - } + NotifyStack methodExceptions = exceptions.push(methodNode); + + Optional methodRedirect = Optional.empty(); + Optional fieldToMethodRedirect = Optional.empty(); + Optional constructorToFactoryRedirect = Optional.empty(); + try { + methodRedirect = MethodRedirectImpl.parseMethodRedirect( + srcType, + (innerClassNode.access & ACC_INTERFACE) != 0, + methodNode, + dstType + ); + } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | + MethodSigImpl.EmptySrcName e) { + methodExceptions.notifyFromException(e); + } + try { + fieldToMethodRedirect = FieldToMethodRedirectImpl.parse( + srcType, + (innerClassNode.access & ACC_INTERFACE) != 0, + methodNode, + dstType + ); + } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | + MethodSigImpl.EmptySrcName e) { + methodExceptions.notifyFromException(e); + } + try { + constructorToFactoryRedirect = ConstructorToFactoryRedirectImpl.parse( + srcType, + (innerClassNode.access & ACC_INTERFACE) != 0, + methodNode, + dstType + ); + } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | + MethodSigImpl.EmptySrcName e) { + methodExceptions.notifyFromException(e); + } - // only one must be present by this point - methodRedirect.ifPresent(methodRedirects::add); - fieldToMethodRedirect.ifPresent(fieldToMethodRedirects::add); - constructorToFactoryRedirect.ifPresent(constructorToFactoryRedirects::add); + if (atLeastTwoOf(methodRedirect.isPresent(), fieldToMethodRedirect.isPresent(), constructorToFactoryRedirect.isPresent())) { + // if both are present, add exception and return + methodExceptions.notify(new MoreThanOneMethodRedirect(methodNode)); + return; + } else if (!methodRedirect.isPresent() && !fieldToMethodRedirect.isPresent() && !constructorToFactoryRedirect.isPresent()) { + // if none are present, add exception and return + methodExceptions.notify(new MissingMethodRedirectRedirect(methodNode)); + return; } + + // only one must be present by this point + methodRedirect.ifPresent(methodRedirects::add); + fieldToMethodRedirect.ifPresent(fieldToMethodRedirects::add); + constructorToFactoryRedirect.ifPresent(constructorToFactoryRedirects::add); } } diff --git a/src/main/java/io/github/notstirred/dasm/data/DasmContext.java b/src/main/java/io/github/notstirred/dasm/data/DasmContext.java index 35c6a05..b78147c 100644 --- a/src/main/java/io/github/notstirred/dasm/data/DasmContext.java +++ b/src/main/java/io/github/notstirred/dasm/data/DasmContext.java @@ -37,121 +37,118 @@ public class DasmContext { public Pair, List> buildClassTarget(ClassNode targetClass) throws DasmException { Type targetType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name)); - try (NotifyStack classExceptions = NotifyStack.of(targetClass)) { - - AnnotationNode transformFromClassNode = getAnnotationIfPresent(targetClass.invisibleAnnotations, TransformFromClass.class); - if (transformFromClassNode != null) { - Map values = getAnnotationValues(transformFromClassNode, TransformFromClass.class); - - try { - Type srcType = RefImpl.parseRefAnnotation("value", values); - ApplicationStage stage = (ApplicationStage) values.get("stage"); - - List sets = unrollSets( - AnnotationUtil.annotationElementAsList(values.get("sets")).orElseGet(ArrayList::new).stream() - .map(this.redirectSetsByType::get) - .collect(Collectors.toList()) - ); - - // FIXME: this should verify that there are no method transforms inside this class, - return new Pair<>(Optional.of(new ClassTransform(srcType, targetType, sets, stage)), classExceptions.notifications()); - } catch (RefImpl.RefAnnotationGivenNoArguments e) { - classExceptions.notifyFromException(e); - } - } + NotifyStack classExceptions = NotifyStack.of(targetClass); + + AnnotationNode transformFromClassNode = getAnnotationIfPresent(targetClass.invisibleAnnotations, TransformFromClass.class); + if (transformFromClassNode != null) { + Map values = getAnnotationValues(transformFromClassNode, TransformFromClass.class); + + try { + Type srcType = RefImpl.parseRefAnnotation("value", values); + ApplicationStage stage = (ApplicationStage) values.get("stage"); - return new Pair<>(Optional.empty(), classExceptions.notifications()); + List sets = unrollSets( + AnnotationUtil.annotationElementAsList(values.get("sets")).orElseGet(ArrayList::new).stream() + .map(this.redirectSetsByType::get) + .collect(Collectors.toList()) + ); + + // FIXME: this should verify that there are no method transforms inside this class, + return new Pair<>(Optional.of(new ClassTransform(srcType, targetType, sets, stage)), classExceptions.notifications()); + } catch (RefImpl.RefAnnotationGivenNoArguments e) { + classExceptions.notifyFromException(e); + } } + + return new Pair<>(Optional.empty(), classExceptions.notifications()); } public Pair>, List> buildMethodTargets(ClassNode dasmClass, String methodPrefix) { boolean isTargetTypeInterface = (dasmClass.access & Opcodes.ACC_INTERFACE) != 0; - try (NotifyStack classExceptions = NotifyStack.of(dasmClass)) { - - AnnotationNode dasmNode = getAnnotationIfPresent(dasmClass.invisibleAnnotations, Dasm.class); - if (dasmNode != null) { - Map values = getAnnotationValues(dasmNode, Dasm.class); - Type targetType = RefImpl.parseOptionalRefAnnotation((AnnotationNode) values.get("target")) - .map(type -> type.getClassName().equals(Dasm.SELF_TARGET.class.getName()) ? null : type) - .orElseGet(() -> Type.getType(TypeUtil.typeNameToDescriptor(dasmClass.name))); - - @SuppressWarnings("unchecked") - List defaultRedirectSets = unrollSets(((List) values.get("value")).stream() - .map(type -> { - RedirectSetImpl redirectSet = this.redirectSetsByType.get(type); - if (redirectSet == null) { - classExceptions.notifyFromException(new NoSuchTypeExists(type)); - } - return redirectSet; - }).filter(Objects::nonNull) - .collect(Collectors.toList()) - ); - - List methodTransforms = new ArrayList<>(); - - for (MethodNode method : dasmClass.methods) { - try (NotifyStack methodExceptions = classExceptions.push(method)) { - - TransformMethodImpl transformMethod = parseTransformMethod(method, methodExceptions); - if (transformMethod == null) - continue; - - Type methodOwner = transformMethod.owner().orElse(targetType); - - List redirectSets = transformMethod.overriddenRedirectSets().map(types -> - types.stream().map(this.redirectSetsByType::get).collect(Collectors.toList())) - .map(this::unrollSets) - .orElse(defaultRedirectSets); - - // FIXME: figure out if there is a way to avoid this with mixin. - // Name is modified here to prevent mixin from overwriting it. We remove this prefix in postApply. - // We redirect to the non-prefixed name, but create the method with the prefix, for later removal. - String nonPrefixedMethodName = method.name; - String prefixedMethodName = methodPrefix + nonPrefixedMethodName; - - List addedParameters = getAddedParameters(method, methodExceptions); - - Pair visibility = getRequestedVisibility(method, transformMethod.visibility(), methodExceptions); - - MethodTransform transform = new MethodTransform( - new ClassMethod(methodOwner, methodOwner, transformMethod.srcMethod()), - prefixedMethodName // We have to rename constructors because we add a prefix, and mixin expects that anything with <> is either init, or clinit - .replace("", "__init__") - .replace("", "__clinit__"), - redirectSets, - transformMethod.stage(), - transformMethod.inPlace(), - new MethodTransform.TransformChanges( - addedParameters, - visibility.first, - visibility.second - ), - new MethodTransform.OriginalTransformData(targetType.getInternalName(), method) - ); - - AnnotationNode addToSetsAnnotation = getAnnotationIfPresent(method.invisibleAnnotations, AddTransformToSets.class); - if (addToSetsAnnotation != null) { - Map addToSets = getAnnotationValues(addToSetsAnnotation, AddTransformToSets.class); - List sets = (List) addToSets.get("value"); - - sets.forEach(set -> this.containersByType.get(set).methodRedirects().add(new MethodRedirectImpl( - transform.srcMethod(), - targetType, - nonPrefixedMethodName, - (method.access & ACC_STATIC) != 0, - isTargetTypeInterface - ))); + NotifyStack classExceptions = NotifyStack.of(dasmClass); + + AnnotationNode dasmNode = getAnnotationIfPresent(dasmClass.invisibleAnnotations, Dasm.class); + if (dasmNode != null) { + Map values = getAnnotationValues(dasmNode, Dasm.class); + Type targetType = RefImpl.parseOptionalRefAnnotation((AnnotationNode) values.get("target")) + .map(type -> type.getClassName().equals(Dasm.SELF_TARGET.class.getName()) ? null : type) + .orElseGet(() -> Type.getType(TypeUtil.typeNameToDescriptor(dasmClass.name))); + + @SuppressWarnings("unchecked") + List defaultRedirectSets = unrollSets(((List) values.get("value")).stream() + .map(type -> { + RedirectSetImpl redirectSet = this.redirectSetsByType.get(type); + if (redirectSet == null) { + classExceptions.notifyFromException(new NoSuchTypeExists(type)); } + return redirectSet; + }).filter(Objects::nonNull) + .collect(Collectors.toList()) + ); + + List methodTransforms = new ArrayList<>(); + + for (MethodNode method : dasmClass.methods) { + NotifyStack methodExceptions = classExceptions.push(method); + + TransformMethodImpl transformMethod = parseTransformMethod(method, methodExceptions); + if (transformMethod == null) + continue; + + Type methodOwner = transformMethod.owner().orElse(targetType); + + List redirectSets = transformMethod.overriddenRedirectSets().map(types -> + types.stream().map(this.redirectSetsByType::get).collect(Collectors.toList())) + .map(this::unrollSets) + .orElse(defaultRedirectSets); + + // FIXME: figure out if there is a way to avoid this with mixin. + // Name is modified here to prevent mixin from overwriting it. We remove this prefix in postApply. + // We redirect to the non-prefixed name, but create the method with the prefix, for later removal. + String nonPrefixedMethodName = method.name; + String prefixedMethodName = methodPrefix + nonPrefixedMethodName; + + List addedParameters = getAddedParameters(method, methodExceptions); + + Pair visibility = getRequestedVisibility(method, transformMethod.visibility(), methodExceptions); + + MethodTransform transform = new MethodTransform( + new ClassMethod(methodOwner, methodOwner, transformMethod.srcMethod()), + prefixedMethodName // We have to rename constructors because we add a prefix, and mixin expects that anything with <> is either init, or clinit + .replace("", "__init__") + .replace("", "__clinit__"), + redirectSets, + transformMethod.stage(), + transformMethod.inPlace(), + new MethodTransform.TransformChanges( + addedParameters, + visibility.first, + visibility.second + ), + new MethodTransform.OriginalTransformData(targetType.getInternalName(), method) + ); - methodTransforms.add(transform); - } + AnnotationNode addToSetsAnnotation = getAnnotationIfPresent(method.invisibleAnnotations, AddTransformToSets.class); + if (addToSetsAnnotation != null) { + Map addToSets = getAnnotationValues(addToSetsAnnotation, AddTransformToSets.class); + List sets = (List) addToSets.get("value"); + + sets.forEach(set -> this.containersByType.get(set).methodRedirects().add(new MethodRedirectImpl( + transform.srcMethod(), + targetType, + nonPrefixedMethodName, + (method.access & ACC_STATIC) != 0, + isTargetTypeInterface + ))); } - return new Pair<>(Optional.of(methodTransforms), classExceptions.notifications()); - } - return new Pair<>(Optional.empty(), classExceptions.notifications()); + methodTransforms.add(transform); + } + return new Pair<>(Optional.of(methodTransforms), classExceptions.notifications()); } + + return new Pair<>(Optional.empty(), classExceptions.notifications()); } private Pair getRequestedVisibility(MethodNode method, Visibility annotationVisibility, NotifyStack methodExceptions) { diff --git a/src/main/java/io/github/notstirred/dasm/transformer/Transformer.java b/src/main/java/io/github/notstirred/dasm/transformer/Transformer.java index 21387fe..18063a1 100644 --- a/src/main/java/io/github/notstirred/dasm/transformer/Transformer.java +++ b/src/main/java/io/github/notstirred/dasm/transformer/Transformer.java @@ -121,37 +121,35 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str } public List transform(ClassNode targetClass, Collection transforms) { - try (NotifyStack dasmClassExceptions = NotifyStack.of(targetClass)) { - - Type targetClassType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name)); - for (MethodTransform transform : transforms) { - try (NotifyStack methodExceptions = dasmClassExceptions.push(transform.originalTransformData().methodNode())) { - Type methodSrcOwner = transform.srcMethod().owner(); - - ClassNode srcClass; - if (methodSrcOwner.equals(targetClassType)) { - srcClass = targetClass; - } else { - try { - srcClass = this.classNodeProvider.classNode(methodSrcOwner); - } catch (NoSuchTypeExists e) { - methodExceptions.notifyFromException(e); - continue; - } - } + NotifyStack dasmClassExceptions = NotifyStack.of(targetClass); - TransformRedirects transformRedirects = new TransformRedirects(transform.redirectSets(), this.mappingsProvider); - if (transform.inPlace()) { - applyRedirects(srcClass, transform.srcMethod(), transformRedirects, transform.transformChanges(), transform.originalTransformData(), methodExceptions, true); - } else { - // FIXME: java 8 synthetic accessor methods - cloneAndApplyRedirects(srcClass, targetClass, transform.srcMethod(), transform.dstMethodName(), transformRedirects, transform.transformChanges(), transform.originalTransformData(), methodExceptions, true); - } + Type targetClassType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name)); + for (MethodTransform transform : transforms) { + NotifyStack methodExceptions = dasmClassExceptions.push(transform.originalTransformData().methodNode()); + Type methodSrcOwner = transform.srcMethod().owner(); + + ClassNode srcClass; + if (methodSrcOwner.equals(targetClassType)) { + srcClass = targetClass; + } else { + try { + srcClass = this.classNodeProvider.classNode(methodSrcOwner); + } catch (NoSuchTypeExists e) { + methodExceptions.notifyFromException(e); + continue; } } - return dasmClassExceptions.notifications(); + TransformRedirects transformRedirects = new TransformRedirects(transform.redirectSets(), this.mappingsProvider); + if (transform.inPlace()) { + applyRedirects(srcClass, transform.srcMethod(), transformRedirects, transform.transformChanges(), transform.originalTransformData(), methodExceptions, true); + } else { + // FIXME: java 8 synthetic accessor methods + cloneAndApplyRedirects(srcClass, targetClass, transform.srcMethod(), transform.dstMethodName(), transformRedirects, transform.transformChanges(), transform.originalTransformData(), methodExceptions, true); + } } + + return dasmClassExceptions.notifications(); } /** @@ -296,19 +294,18 @@ private void cloneAndApplyLambdaRedirects(ClassNode srcClass, ClassNode targetCl forEachLambdaInvocation(srcClass, method, (classMethod, handle, lambdaNode) -> { String newName = "dasm$redirect$" + classMethod.method().getName(); lambdaRedirects.put(handle, newName); - try (NotifyStack lambdaExceptions = methodExceptions.push(lambdaNode)) { - cloneAndApplyRedirects( - srcClass, - targetClass, - classMethod, - newName, - redirects, - new MethodTransform.TransformChanges(Collections.emptyList(), Visibility.PRIVATE, Visibility.SAME_AS_TARGET), - new MethodTransform.OriginalTransformData(srcClass.name, method), - lambdaExceptions, - debugLogging - ); - } + NotifyStack lambdaExceptions = methodExceptions.push(lambdaNode); + cloneAndApplyRedirects( + srcClass, + targetClass, + classMethod, + newName, + redirects, + new MethodTransform.TransformChanges(Collections.emptyList(), Visibility.PRIVATE, Visibility.SAME_AS_TARGET), + new MethodTransform.OriginalTransformData(srcClass.name, method), + lambdaExceptions, + debugLogging + ); }); Type targetClassType = Type.getType(TypeUtil.typeNameToDescriptor(targetClass.name)); @@ -327,17 +324,16 @@ private void cloneAndApplyLambdaRedirects(ClassNode srcClass, ClassNode targetCl private void applyLambdaRedirects(ClassNode srcClass, MethodNode method, TransformRedirects redirects, NotifyStack methodExceptions, boolean debugLogging) { forEachLambdaInvocation(srcClass, method, (classMethod, handle, lambdaNode) -> { - try (NotifyStack lambdaExceptions = methodExceptions.push(lambdaNode)) { - applyRedirects( - srcClass, - classMethod, - redirects, - new MethodTransform.TransformChanges(Collections.emptyList(), Visibility.PRIVATE, Visibility.SAME_AS_TARGET), - new MethodTransform.OriginalTransformData(srcClass.name, method), // Assume lambas are always private, the passed in method here is therefore irrelevant - lambdaExceptions, - debugLogging - ); - } + NotifyStack lambdaExceptions = methodExceptions.push(lambdaNode); + applyRedirects( + srcClass, + classMethod, + redirects, + new MethodTransform.TransformChanges(Collections.emptyList(), Visibility.PRIVATE, Visibility.SAME_AS_TARGET), + new MethodTransform.OriginalTransformData(srcClass.name, method), // Assume lambas are always private, the passed in method here is therefore irrelevant + lambdaExceptions, + debugLogging + ); }); } diff --git a/src/main/java/io/github/notstirred/dasm/util/NotifyStack.java b/src/main/java/io/github/notstirred/dasm/util/NotifyStack.java index 010f72a..fcc18e0 100644 --- a/src/main/java/io/github/notstirred/dasm/util/NotifyStack.java +++ b/src/main/java/io/github/notstirred/dasm/util/NotifyStack.java @@ -14,10 +14,19 @@ import static io.github.notstirred.dasm.util.Format.format; -public class NotifyStack implements AutoCloseable { - private final List stack = new ArrayList<>(); +public class NotifyStack { + private final List stack; @Getter - private final List notifications = new ArrayList<>(); + private final List notifications; + + public NotifyStack() { + this(new ArrayList<>(), new ArrayList<>()); + } + + private NotifyStack(List stack, List notifications) { + this.stack = stack; + this.notifications = notifications; + } public static NotifyStack of(ClassNode classNode) { NotifyStack notifyStack = new NotifyStack(); @@ -41,23 +50,21 @@ public void notifyFromException(DasmException e) { } public NotifyStack push(ClassNode classNode) { - this.stack.add(format(classNode)); - return this; + ArrayList newStack = new ArrayList<>(this.stack); + newStack.add(format(classNode)); + return new NotifyStack(newStack, this.notifications); } public NotifyStack push(MethodNode methodNode) { - this.stack.add(format(methodNode)); - return this; + ArrayList newStack = new ArrayList<>(this.stack); + newStack.add(format(methodNode)); + return new NotifyStack(newStack, this.notifications); } public NotifyStack push(FieldNode fieldNode) { - this.stack.add(format(fieldNode)); - return this; - } - - public NotifyStack pop() { - this.stack.remove(this.stack.size() - 1); - return this; + ArrayList newStack = new ArrayList<>(this.stack); + newStack.add(format(fieldNode)); + return new NotifyStack(newStack, this.notifications); } public boolean hasError() { @@ -76,9 +83,4 @@ public NotifyStack join(NotifyStack other) { public static Collector joining() { return Collector.of(NotifyStack::new, NotifyStack::join, NotifyStack::join, Collector.Characteristics.IDENTITY_FINISH); } - - @Override - public void close() { - this.pop(); - } } From 924bd11c634c224f6fa46776314ce4d415b46149 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 16 Jun 2025 15:56:29 +0100 Subject: [PATCH 2/7] core: remove duplicate information from notification messages --- .../dasm/annotation/AnnotationParser.java | 7 ++++--- .../annotation/parse/RedirectSetImpl.java | 20 +++++++++---------- .../parse/redirects/FieldRedirectImpl.java | 2 +- .../notstirred/dasm/data/DasmContext.java | 4 ++-- .../github/notstirred/dasm/util/Format.java | 4 ++++ .../notstirred/dasm/util/NotifyStack.java | 9 +++++++++ 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java b/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java index ae3bcbf..7615eb3 100644 --- a/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java +++ b/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java @@ -34,6 +34,7 @@ import java.util.*; import static io.github.notstirred.dasm.annotation.AnnotationUtil.*; +import static io.github.notstirred.dasm.util.Format.formatObjectType; public class AnnotationParser { private final ClassNodeProvider provider; @@ -259,19 +260,19 @@ private ClassNode getContainingRedirectSetClassNode(Type containerType) throws N public static class ContainerNotWithinRedirectSet extends DasmException { public ContainerNotWithinRedirectSet(Type containerType) { - super(String.format("Container `" + containerType.getClassName() + "` must be within a @RedirectSet interface")); + super(String.format("Container `" + formatObjectType(containerType) + "` must be within a @RedirectSet interface")); } } public static class TypeIsNotAContainer extends DasmException { public TypeIsNotAContainer(Type type) { - super(String.format("Type `" + type.getClassName() + "` is not a container but is used as one")); + super(String.format("Type `" + formatObjectType(type) + "` is not a container but is used as one")); } } public static class NoValidRedirectSetExists extends Notification { public NoValidRedirectSetExists(Type redirectSetType) { - super(String.format("No valid redirect set exists matching `" + redirectSetType.getClassName() + "`")); + super(String.format("No valid redirect set exists matching `" + formatObjectType(redirectSetType) + "`")); } } } diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java index 4b84173..b72ff37 100644 --- a/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java +++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/RedirectSetImpl.java @@ -18,7 +18,6 @@ import java.util.stream.Stream; import static io.github.notstirred.dasm.annotation.AnnotationUtil.getAnnotationIfPresent; -import static io.github.notstirred.dasm.util.TypeUtil.simpleClassNameOf; import static io.github.notstirred.dasm.util.Util.atLeastTwoOf; import static org.objectweb.asm.Opcodes.ACC_INTERFACE; import static org.objectweb.asm.Opcodes.ACC_STATIC; @@ -96,6 +95,7 @@ public static Optional parse(ClassNode redirectSetClassNode, Cl } innerClassData.values().forEach(innerClassInfo -> { + NotifyStack innerClassExceptions = redirectSetExceptions.push(innerClassInfo.container.type); innerClassInfo.superNames.forEach(superName -> { Type superContainerType = Type.getObjectType(superName); if (superContainerType.equals(Type.getType(Object.class))) { // having no super type is always OK @@ -103,11 +103,11 @@ public static Optional parse(ClassNode redirectSetClassNode, Cl } InnerClassInfo superContainerInfo = innerClassData.get(superContainerType); if (superContainerInfo == null) { - redirectSetExceptions.notify(new SuperTypeInInvalidRedirectSet(innerClassInfo.container.type.getClassName(), superContainerType.getClassName())); + innerClassExceptions.notify(new SuperTypeInInvalidRedirectSet(innerClassInfo.container.type.getClassName(), superContainerType.getClassName())); return; } if (innerClassInfo.container.superContainer != null) { - redirectSetExceptions.notify(new MultipleContainerInheritanceNotImplemented(innerClassInfo.container)); + innerClassExceptions.notify(new MultipleContainerInheritanceNotImplemented(innerClassInfo.container)); return; } innerClassInfo.container.superContainer = superContainerInfo.container(); @@ -300,44 +300,44 @@ public SuperTypeInInvalidRedirectSet(String containerName, String superContainer public static class InterOwnerContainerHasNonStaticRedirects extends Notification { public InterOwnerContainerHasNonStaticRedirects(Type type) { - super("InterOwnerContainer " + simpleClassNameOf(type) + " contains non-static redirects which is invalid." + + super("InterOwnerContainer contains non-static redirects which is invalid." + "Consider using @TypeRedirect instead."); } } public static class MoreThanOneContainerException extends Notification { public MoreThanOneContainerException(Type type) { - super(simpleClassNameOf(type) + " has more than one of @TypeRedirect, @InterOwnerContainer, @IntraOwnerContainer"); + super("Type has more than one of @TypeRedirect, @InterOwnerContainer, @IntraOwnerContainer"); } } public static class MissingContainerException extends Notification { public MissingContainerException(Type redirectSetType) { - super(simpleClassNameOf(redirectSetType) + " is missing one of @TypeRedirect, @InterOwnerContainer, @IntraOwnerContainer."); + super("Type is missing one of @TypeRedirect, @InterOwnerContainer, @IntraOwnerContainer."); } } public static class MissingMethodRedirectRedirect extends Notification { public MissingMethodRedirectRedirect(MethodNode methodNode) { - super("Method `" + methodNode.name + "` is missing a @MethodRedirect, @FieldToMethodRedirect or a @ConstructorToFactoryRedirect annotation."); + super("Method is missing a @MethodRedirect, @FieldToMethodRedirect or a @ConstructorToFactoryRedirect annotation."); } } public static class MoreThanOneMethodRedirect extends Notification { public MoreThanOneMethodRedirect(MethodNode methodNode) { - super("Method `" + methodNode.name + "` has more than one of @MethodRedirect, @FieldToMethodRedirect and @ConstructorToFactoryRedirect annotations."); + super("Method has more than one of @MethodRedirect, @FieldToMethodRedirect and @ConstructorToFactoryRedirect annotations."); } } public static class MissingRedirectSetAnnotationException extends Notification { public MissingRedirectSetAnnotationException(Type redirectSetType) { - super(simpleClassNameOf(redirectSetType) + " is missing @RedirectSet annotation"); + super("Type is missing @RedirectSet annotation"); } } public static class NonInterfaceIsUsedAsRedirectSetException extends Notification { public NonInterfaceIsUsedAsRedirectSetException(Type redirectSetType) { - super("Non-interface " + simpleClassNameOf(redirectSetType) + " is used as a redirect set"); + super("Non-interface is used as a redirect set"); } } diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldRedirectImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldRedirectImpl.java index b3b66da..7167cee 100644 --- a/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldRedirectImpl.java +++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldRedirectImpl.java @@ -58,7 +58,7 @@ public FieldRedirectHasEmptySrcName(FieldNode fieldNode) { public static class FieldMissingFieldRedirectAnnotationException extends Notification { public FieldMissingFieldRedirectAnnotationException(FieldNode fieldNode) { - super("Field `" + fieldNode.name + "` is missing a @FieldRedirect annotation."); + super("Field is missing a @FieldRedirect annotation."); } } } diff --git a/src/main/java/io/github/notstirred/dasm/data/DasmContext.java b/src/main/java/io/github/notstirred/dasm/data/DasmContext.java index b78147c..decef55 100644 --- a/src/main/java/io/github/notstirred/dasm/data/DasmContext.java +++ b/src/main/java/io/github/notstirred/dasm/data/DasmContext.java @@ -232,13 +232,13 @@ private void unrollSetsInner(RedirectSetImpl redirectSet, Collection stack; @@ -67,6 +69,13 @@ public NotifyStack push(FieldNode fieldNode) { return new NotifyStack(newStack, this.notifications); } + public NotifyStack push(Type type) { + assert type.getSort() == Type.OBJECT; + ArrayList newStack = new ArrayList<>(this.stack); + newStack.add(formatObjectType(type)); + return new NotifyStack(newStack, this.notifications); + } + public boolean hasError() { return this.hasMessage(Notification.Kind.ERROR); } From 71bd625eea4b2b14a1bc28687985ffc767625a58 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 16 Jun 2025 15:58:38 +0100 Subject: [PATCH 3/7] test: Merge class and method transforms test codepaths --- .../notstirred/dasm/test/TestHarness.java | 97 ++++++++++--------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/src/test/java/io/github/notstirred/dasm/test/TestHarness.java b/src/test/java/io/github/notstirred/dasm/test/TestHarness.java index 487c730..342767c 100644 --- a/src/test/java/io/github/notstirred/dasm/test/TestHarness.java +++ b/src/test/java/io/github/notstirred/dasm/test/TestHarness.java @@ -11,7 +11,6 @@ import io.github.notstirred.dasm.test.targets.inherited_transforms.Foo; import io.github.notstirred.dasm.test.utils.ByteArrayClassLoader; import io.github.notstirred.dasm.transformer.Transformer; -import io.github.notstirred.dasm.transformer.data.ClassTransform; import io.github.notstirred.dasm.transformer.data.MethodTransform; import io.github.notstirred.dasm.util.CachingClassProvider; import io.github.notstirred.dasm.util.Pair; @@ -38,55 +37,42 @@ import static org.objectweb.asm.Opcodes.ASM9; public class TestHarness { + private static final Path DASM_OUT = Path.of(".dasm.out"); + /** * Verifies that the actualClass equals the expectedClass after transforms in dasmClass+actualClass have been applied */ public static void verifyMethodTransformsValid(Class actualClass, Class expectedClass, Class dasmClass) { - ClassNode actual = classNodeFromClass(actualClass); - ClassNode expected = classNodeFromClass(expectedClass); - ClassNode dasm = classNodeFromClass(dasmClass); - - CachingClassProvider classProvider = new CachingClassProvider(TestHarness::getBytesForClassName); - Transformer transformer = new Transformer(classProvider, MappingsProvider.IDENTITY); - - AnnotationParser annotationParser = new AnnotationParser(classProvider); - - try { - Collection methodTransforms = annotationParser.parseDasmClassNodes(Lists.newArrayList(dasm, actual)).first() - .buildMethodTargets(dasm, "").first().get(); - - transformer.transform(actual, methodTransforms); - } catch (DasmException e) { - throw new Error("", e); - } - - // Write and re-read class bytes to fix issues with label nodes being wonky - byte[] bytecode = classNodeToBytes(actual); - ClassNode reparsedClassNode = classNodeFromBytes(bytecode); + verifyTransformValid(actualClass, expectedClass, dasmClass, DASM_OUT.resolve("method_transforms"), + (context, classNode) -> context.buildMethodTargets(classNode, ""), + Transformer::transform + ); + } - try { - Path path = Path.of(".dasm.out/method_transforms/" + reparsedClassNode.name.replace('.', '/') + ".class").toAbsolutePath(); - Path actualPath = path.getParent().resolve("ACTUAL").resolve(path.getFileName()); - createDirectoriesIfNotExists(actualPath.getParent()); - Files.write(actualPath, bytecode); - Path expectedPath = path.getParent().resolve("EXPECTED").resolve(expectedClass.getSimpleName() + ".class"); - createDirectoriesIfNotExists(expectedPath.getParent()); - Files.write(expectedPath, classNodeToBytes(expected)); - } catch (IOException ex) { - throw new RuntimeException(ex); - } + /** + * Verifies that the actualClass equals the expectedClass after transforms in dasmClass+actualClass have been applied + */ + public static void verifyClassTransformValid(Class actualClass, Class expectedClass, Class dasmClass) { + verifyTransformValid(actualClass, expectedClass, dasmClass, DASM_OUT.resolve("class_transforms"), + DasmContext::buildClassTarget, + Transformer::transform + ); + } - expected.methods.sort(Comparator.comparing(methodNode -> methodNode.name)); - reparsedClassNode.methods.sort(Comparator.comparing(methodNode -> methodNode.name)); + @FunctionalInterface + interface BuildTargets { + Pair, List> buildTargets(DasmContext context, ClassNode classNode) throws DasmException; + } - assertClassNodesEqual(reparsedClassNode, expected); - callAllMethodsWithDummies(actualClass, expectedClass, reparsedClassNode); + @FunctionalInterface + interface DoTransform { + void doTransform(Transformer transformer, ClassNode classNode, T transforms) throws DasmException; } /** * Verifies that the actualClass equals the expectedClass after transforms in dasmClass+actualClass have been applied */ - public static void verifyClassTransformValid(Class actualClass, Class expectedClass, Class dasmClass) { + private static void verifyTransformValid(Class actualClass, Class expectedClass, Class dasmClass, Path basePath, BuildTargets buildTargets, DoTransform doTransform) { ClassNode actual = classNodeFromClass(actualClass); ClassNode expected = classNodeFromClass(expectedClass); ClassNode dasm = classNodeFromClass(dasmClass); @@ -97,13 +83,33 @@ public static void verifyClassTransformValid(Class actualClass, Class expe AnnotationParser annotationParser = new AnnotationParser(classProvider); try { - DasmContext context = annotationParser.parseDasmClassNodes(Lists.newArrayList(dasm, actual)).first(); + Pair> dasmContextListPair = annotationParser.parseDasmClassNodes(Lists.newArrayList(dasm, actual)); + DasmContext context; + if (dasmContextListPair.first() != null) { + context = dasmContextListPair.first(); + } else { + DasmException dasmException = new DasmException(""); + dasmContextListPair.second().forEach(notification -> { + dasmException.addSuppressed(new Exception(notification.message)); + }); + throw dasmException; + } + dasm.name = actual.name; - ClassTransform methodTransforms = context.buildClassTarget(dasm).first().get(); + Pair, List> targetsAndNotifications = buildTargets.buildTargets(context, dasm); + if (targetsAndNotifications.first().isPresent()) { + T transforms = targetsAndNotifications.first().get(); - transformer.transform(actual, methodTransforms); + doTransform.doTransform(transformer, actual, transforms); + } else { + DasmException dasmException = new DasmException(""); + targetsAndNotifications.second().forEach(notification -> { + dasmException.addSuppressed(new Exception(notification.message)); + }); + throw dasmException; + } } catch (DasmException e) { - throw new Error("foo", e); + throw new Error("Dasm Exception in testing", e); } // Write and re-read class bytes to fix issues with label nodes being wonky @@ -111,17 +117,20 @@ public static void verifyClassTransformValid(Class actualClass, Class expe ClassNode reparsedClassNode = classNodeFromBytes(bytecode); try { - Path path = Path.of(".dasm.out/class_transforms/" + reparsedClassNode.name.replace('.', '/') + ".class").toAbsolutePath(); + Path path = basePath.resolve(reparsedClassNode.name.replace('.', '/') + ".class").toAbsolutePath(); Path actualPath = path.getParent().resolve("ACTUAL").resolve(path.getFileName()); createDirectoriesIfNotExists(actualPath.getParent()); Files.write(actualPath, bytecode); - Path expectedPath = path.getParent().resolve("EXPECTED").resolve(expectedClass.getSimpleName()); + Path expectedPath = path.getParent().resolve("EXPECTED").resolve(expectedClass.getSimpleName() + ".class"); createDirectoriesIfNotExists(expectedPath.getParent()); Files.write(expectedPath, classNodeToBytes(expected)); } catch (IOException ex) { throw new RuntimeException(ex); } + expected.methods.sort(Comparator.comparing(methodNode -> methodNode.name)); + reparsedClassNode.methods.sort(Comparator.comparing(methodNode -> methodNode.name)); + assertClassNodesEqual(reparsedClassNode, expected); callAllMethodsWithDummies(actualClass, expectedClass, reparsedClassNode); } From 68203020f8cf487d6a9fe3d5aae6f9b37c756e87 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 16 Jun 2025 15:59:13 +0100 Subject: [PATCH 4/7] transformer: Output to trace logs instead of info --- .../io/github/notstirred/dasm/transformer/TypeRemapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/notstirred/dasm/transformer/TypeRemapper.java b/src/main/java/io/github/notstirred/dasm/transformer/TypeRemapper.java index 7f9649a..2893d26 100644 --- a/src/main/java/io/github/notstirred/dasm/transformer/TypeRemapper.java +++ b/src/main/java/io/github/notstirred/dasm/transformer/TypeRemapper.java @@ -28,7 +28,7 @@ public TypeRemapper(Map typeRedirectsIn, boolean debug mappingsProvider.remapType(type).getInternalName(), mappingsProvider.remapType(typeRedirectsIn.get(type).type()).getInternalName()); } - typeRedirects.forEach((old, n) -> LOGGER.info("Type mapping: " + old + " -> " + n)); + typeRedirects.forEach((old, n) -> LOGGER.trace("Type mapping: " + old + " -> " + n)); } @@ -40,7 +40,7 @@ public String map(final String key) { String mapped = typeRedirects.get(key); if (mapped == null) { if (this.debugLogging) { - LOGGER.info("NOTE: handling CLASS redirect to self: " + key); + LOGGER.trace("NOTE: handling CLASS redirect to self: " + key); } typeRedirects.put(key, key); return key; From b37810dd69f2a185ebd761942b5732b37a31f2a6 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 16 Jun 2025 18:27:29 +0100 Subject: [PATCH 5/7] transformer: Fix static FieldToMethodRedirects generating wrong INVOKE instruction --- .../io/github/notstirred/dasm/transformer/RedirectVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/notstirred/dasm/transformer/RedirectVisitor.java b/src/main/java/io/github/notstirred/dasm/transformer/RedirectVisitor.java index 4e15198..c3bbb9a 100644 --- a/src/main/java/io/github/notstirred/dasm/transformer/RedirectVisitor.java +++ b/src/main/java/io/github/notstirred/dasm/transformer/RedirectVisitor.java @@ -146,7 +146,7 @@ private void doFieldToMethodRedirect(int opcode, String currentOwner, String nam if (fieldToMethodRedirect.isStatic()) { // Static field to method redirects are allowed to change owner super.visitMethodInsn( - fieldToMethodRedirect.isDstOwnerInterface() ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, + Opcodes.INVOKESTATIC, SKIP_TYPE_REDIRECT_PREFIX + method.owner().getInternalName(), method.method().getName(), method.method().getDescriptor(), From b143585dcc94623aa59e96e39ea9551c677eb767 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 16 Jun 2025 18:32:43 +0100 Subject: [PATCH 6/7] parser: Add AddFieldToMethodToSets --- .../redirects/AddFieldToMethodToSets.java | 71 +++++++++++++++++++ .../dasm/annotation/AnnotationParser.java | 30 +++++++- .../addtosets/AddFieldToMethodToSetsImpl.java | 63 ++++++++++++++++ .../parse/addtosets/AddFieldToSetsImpl.java | 8 +-- .../redirects/FieldToMethodRedirectImpl.java | 2 +- .../AddFieldToMethodToSetsInput.java | 11 +++ .../AddFieldToMethodToSetsOutput.java | 17 +++++ .../TestAddFieldToMethodToSets.java | 46 ++++++++++++ 8 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 src/api/java/io/github/notstirred/dasm/api/annotations/redirect/redirects/AddFieldToMethodToSets.java create mode 100644 src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToMethodToSetsImpl.java create mode 100644 src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsInput.java create mode 100644 src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsOutput.java create mode 100644 src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/TestAddFieldToMethodToSets.java diff --git a/src/api/java/io/github/notstirred/dasm/api/annotations/redirect/redirects/AddFieldToMethodToSets.java b/src/api/java/io/github/notstirred/dasm/api/annotations/redirect/redirects/AddFieldToMethodToSets.java new file mode 100644 index 0000000..bbb5fe1 --- /dev/null +++ b/src/api/java/io/github/notstirred/dasm/api/annotations/redirect/redirects/AddFieldToMethodToSets.java @@ -0,0 +1,71 @@ +package io.github.notstirred.dasm.api.annotations.redirect.redirects; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.sets.InterOwnerContainer; +import io.github.notstirred.dasm.api.annotations.redirect.sets.IntraOwnerContainer; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * May be added to a field within a {@link Dasm} class. + *

+ * Add a {@link FieldToMethodRedirect} to multiple containers inline at method definition, rather than duplicating the + * method definition inside the containers. + *

+ *

Important notes:

+ * See {@link FieldToMethodRedirect}'s important notes for more information. + *

Example:

+ * This adds a {@link FieldToMethodRedirect} from {@code barX} -> {@code int getBarX()} and {@code void setBarX(int)} to {@code Bar_redirects} which is an {@link IntraOwnerContainer} + *
{@code
+ * @Dasm
+ * public class Foo {
+ *     private int foo = 0;
+ *
+ *     @AddFieldToMethodToSets(
+ *         containers = Bar_redirects.class,
+ *         field = @FieldSig(type = int.class, name = "barX"),
+ *         setter = "setBarX"
+ *     )
+ *     public int getBarX() {
+ *         return this.foo; // This method gets called for every replaced read
+ *     }
+ *     public void setBarX(int barX) {
+ *         this.foo = barX; // This method gets called for every replaced write
+ *     }
+ * }
+ * }
+ */ +@Target(METHOD) +@Retention(CLASS) +public @interface AddFieldToMethodToSets { + /** + * The containers to add the {@link FieldToMethodRedirect} to. + *

Valid container classes are {@link TypeRedirect}, {@link InterOwnerContainer}, {@link IntraOwnerContainer}

+ */ + Class[] containers(); + + /** + * The field to replace + */ + FieldSig field(); + + /** + * The name of the setter method, must have the same owner (be in the same class) as the getter method + * The argument will be inferred from the field and getter. + */ + String setter() default ""; + + /** + * The source field's mapping owner. + *

+ * Only useful if the codebase is remapped and method/field owners are moved
+ * Allows specifying the class which owns the field in the mappings + */ + Ref mappingsOwner() default @Ref; +} diff --git a/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java b/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java index 7615eb3..a5aff30 100644 --- a/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java +++ b/src/main/java/io/github/notstirred/dasm/annotation/AnnotationParser.java @@ -3,9 +3,11 @@ import io.github.notstirred.dasm.annotation.parse.MethodSigImpl; import io.github.notstirred.dasm.annotation.parse.RedirectSetImpl; import io.github.notstirred.dasm.annotation.parse.RefImpl; +import io.github.notstirred.dasm.annotation.parse.addtosets.AddFieldToMethodToSetsImpl; import io.github.notstirred.dasm.annotation.parse.addtosets.AddFieldToSetsImpl; import io.github.notstirred.dasm.annotation.parse.addtosets.AddMethodToSetsImpl; import io.github.notstirred.dasm.annotation.parse.redirects.FieldRedirectImpl; +import io.github.notstirred.dasm.annotation.parse.redirects.FieldToMethodRedirectImpl; import io.github.notstirred.dasm.annotation.parse.redirects.MethodRedirectImpl; import io.github.notstirred.dasm.api.annotations.Dasm; import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; @@ -106,7 +108,7 @@ public NotifyStack findDasmAnnotations(ClassNode targetClass) { FieldRedirectImpl fieldRedirect = new FieldRedirectImpl( new ClassField(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcField().type(), addToSets.srcField().name()), addToSets.dstOwner(), - addToSets.dstMethodName() + addToSets.dstFieldName() ); container.fieldRedirects().add(fieldRedirect); } @@ -149,6 +151,32 @@ public NotifyStack findDasmAnnotations(ClassNode targetClass) { MethodSigImpl.EmptySrcName e) { methodExceptions.notifyFromException(e); } + + try { + Optional optAddToSets = AddFieldToMethodToSetsImpl.parse(targetClassType, methodNode); + if (optAddToSets.isPresent()) { + AddFieldToMethodToSetsImpl addToSets = optAddToSets.get(); + // All containers for this method must already exist, so we can just use the map + for (Type containerType : addToSets.containers()) { + RedirectSetImpl.Container container = this.containers.get(containerType); + if (container == null) { + methodExceptions.notifyFromException(new ContainerNotWithinRedirectSet(containerType)); + continue; + } + FieldToMethodRedirectImpl fieldToMethodRedirect = new FieldToMethodRedirectImpl( + new ClassField(container.srcType(), addToSets.mappingsOwner().orElse(container.srcType()), addToSets.srcField().type(), addToSets.srcField().name()), + addToSets.dstMethod(), + addToSets.dstSetterMethod(), + addToSets.isStatic(), + (targetClass.access & Opcodes.ACC_INTERFACE) != 0 + ); + container.fieldToMethodRedirects().add(fieldToMethodRedirect); + } + } + } catch (RefImpl.RefAnnotationGivenNoArguments | MethodSigImpl.InvalidMethodSignature | + MethodSigImpl.EmptySrcName e) { + methodExceptions.notifyFromException(e); + } } return classExceptions; } diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToMethodToSetsImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToMethodToSetsImpl.java new file mode 100644 index 0000000..9f1fb42 --- /dev/null +++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToMethodToSetsImpl.java @@ -0,0 +1,63 @@ +package io.github.notstirred.dasm.annotation.parse.addtosets; + +import io.github.notstirred.dasm.annotation.AnnotationUtil; +import io.github.notstirred.dasm.annotation.parse.FieldSigImpl; +import io.github.notstirred.dasm.annotation.parse.MethodSigImpl; +import io.github.notstirred.dasm.annotation.parse.RefImpl; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToMethodToSets; +import io.github.notstirred.dasm.data.ClassMethod; +import io.github.notstirred.dasm.data.Field; +import lombok.Data; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.Method; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static io.github.notstirred.dasm.annotation.parse.RefImpl.parseOptionalRefAnnotation; +import static io.github.notstirred.dasm.annotation.parse.redirects.FieldToMethodRedirectImpl.setterDescriptorFor; + +@Data +public class AddFieldToMethodToSetsImpl { + private final List containers; + + private final Field srcField; + private final Optional mappingsOwner; + + private final ClassMethod dstMethod; + private final Optional dstSetterMethod; + + private final boolean isStatic; + + public static Optional parse(Type dstOwner, MethodNode methodNode) + throws RefImpl.RefAnnotationGivenNoArguments, MethodSigImpl.InvalidMethodSignature, MethodSigImpl.EmptySrcName { + AnnotationNode annotation = AnnotationUtil.getAnnotationIfPresent(methodNode.invisibleAnnotations, AddFieldToMethodToSets.class); + if (annotation == null) { + return Optional.empty(); + } + + Map values = AnnotationUtil.getAnnotationValues(annotation, AddFieldToMethodToSets.class); + + Field srcField = FieldSigImpl.parse((AnnotationNode) values.get("field")); + + String setter = (String) values.get("setter"); + + Optional mappingsOwner = parseOptionalRefAnnotation((AnnotationNode) values.get("mappingsOwner")); + + List sets = (List) values.get("containers"); + + Method getter = new Method(methodNode.name, methodNode.desc); + return Optional.of(new AddFieldToMethodToSetsImpl( + sets, + new Field(srcField.type(), srcField.name()), + mappingsOwner, + new ClassMethod(dstOwner, getter), + setter.isEmpty() ? Optional.empty() : Optional.of(new ClassMethod(dstOwner, new Method(setter, setterDescriptorFor(getter)))), + (methodNode.access & Opcodes.ACC_STATIC) != 0 + )); + } +} diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToSetsImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToSetsImpl.java index e838238..9118fb8 100644 --- a/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToSetsImpl.java +++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/addtosets/AddFieldToSetsImpl.java @@ -25,11 +25,11 @@ public class AddFieldToSetsImpl { private final Optional mappingsOwner; private final Type dstOwner; - private final String dstMethodName; + private final String dstFieldName; - public static Optional parse(Type dstOwner, FieldNode methodNode) + public static Optional parse(Type dstOwner, FieldNode fieldNode) throws RefImpl.RefAnnotationGivenNoArguments, MethodSigImpl.InvalidMethodSignature, MethodSigImpl.EmptySrcName { - AnnotationNode annotation = AnnotationUtil.getAnnotationIfPresent(methodNode.invisibleAnnotations, AddFieldToSets.class); + AnnotationNode annotation = AnnotationUtil.getAnnotationIfPresent(fieldNode.invisibleAnnotations, AddFieldToSets.class); if (annotation == null) { return Optional.empty(); } @@ -47,7 +47,7 @@ public static Optional parse(Type dstOwner, FieldNode method srcField, mappingsOwner, dstOwner, - methodNode.name + fieldNode.name )); } } diff --git a/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldToMethodRedirectImpl.java b/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldToMethodRedirectImpl.java index c97d4b7..518d70c 100644 --- a/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldToMethodRedirectImpl.java +++ b/src/main/java/io/github/notstirred/dasm/annotation/parse/redirects/FieldToMethodRedirectImpl.java @@ -55,7 +55,7 @@ public static Optional parse(Type fieldOwner, boolean )); } - private static @NotNull String setterDescriptorFor(Method getter) { + public static @NotNull String setterDescriptorFor(Method getter) { return "(" + getter.getReturnType().getDescriptor() + ")V"; } } diff --git a/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsInput.java b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsInput.java new file mode 100644 index 0000000..a9516d8 --- /dev/null +++ b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsInput.java @@ -0,0 +1,11 @@ +package io.github.notstirred.dasm.test.tests.integration.add_field_to_method_to_sets; + +import io.github.notstirred.dasm.test.targets.CubePos; + +public class AddFieldToMethodToSetsInput { + void method1() { + System.out.println(CubePos.MASK); + CubePos.MASK = 34; + System.out.println(CubePos.MASK); + } +} diff --git a/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsOutput.java b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsOutput.java new file mode 100644 index 0000000..1cff80a --- /dev/null +++ b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/AddFieldToMethodToSetsOutput.java @@ -0,0 +1,17 @@ +package io.github.notstirred.dasm.test.tests.integration.add_field_to_method_to_sets; + +import io.github.notstirred.dasm.test.targets.CubePos; + +public class AddFieldToMethodToSetsOutput { + void method1() { + System.out.println(CubePos.MASK); + CubePos.MASK = 34; + System.out.println(CubePos.MASK); + } + + void method1out() { + System.out.println(TestAddFieldToMethodToSets.getMask()); + TestAddFieldToMethodToSets.setMask(34); + System.out.println(TestAddFieldToMethodToSets.getMask()); + } +} \ No newline at end of file diff --git a/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/TestAddFieldToMethodToSets.java b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/TestAddFieldToMethodToSets.java new file mode 100644 index 0000000..94c405c --- /dev/null +++ b/src/test/java/io/github/notstirred/dasm/test/tests/integration/add_field_to_method_to_sets/TestAddFieldToMethodToSets.java @@ -0,0 +1,46 @@ +package io.github.notstirred.dasm.test.tests.integration.add_field_to_method_to_sets; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToMethodToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.redirect.sets.InterOwnerContainer; +import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.notstirred.dasm.test.targets.CubePos; +import io.github.notstirred.dasm.test.tests.integration.BaseMethodTest; + +import static io.github.notstirred.dasm.test.tests.integration.TestData.single; + +/** + * A trivial test for a static {@link AddMethodToSets} + */ +@Dasm(value = TestAddFieldToMethodToSets.Set.class, target = @Ref(AddFieldToMethodToSetsInput.class)) +public class TestAddFieldToMethodToSets extends BaseMethodTest { + public TestAddFieldToMethodToSets() { + super(single(AddFieldToMethodToSetsInput.class, AddFieldToMethodToSetsOutput.class, TestAddFieldToMethodToSets.class)); + } + + @TransformFromMethod(value = @MethodSig("method1()V")) + native String method1out(); + + @RedirectSet + public interface Set { + @InterOwnerContainer(from = @Ref(CubePos.class), to = @Ref(TestAddFieldToMethodToSets.class)) + abstract class CubePos_to_TestAddToSets_redirects { + } + } + + static int mask = 123; + + @AddFieldToMethodToSets(containers = Set.CubePos_to_TestAddToSets_redirects.class, setter = "setMask", field = @FieldSig(type = @Ref(int.class), name = "MASK")) + public static int getMask() { + return mask; + } + + public static void setMask(int i) { + mask = i; + } +} \ No newline at end of file From 3ea7dfb713ebca821b5296e3749ab8f52d801062 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 16 Jun 2025 18:40:44 +0100 Subject: [PATCH 7/7] release: 2.5.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 873a258..2bdb385 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { group 'io.github.notstirred' archivesBaseName = "dasm" -version '2.5.2' +version '2.5.3' ext.ossrhUsername = project.findProperty("ossrhUsername") ?: "" // Enter in ~/.gradle/gradle.properties, not here. ext.ossrhPassword = project.findProperty("ossrhPassword") ?: "" // Enter in ~/.gradle/gradle.properties, not here.