diff --git a/docs/language/Attributes.md b/docs/language/Attributes.md index 582cf29..632a498 100644 --- a/docs/language/Attributes.md +++ b/docs/language/Attributes.md @@ -121,4 +121,44 @@ Appends an internal type to the list of members this class is a nest host for. .signature "generic signature" # Optional attribute to apply to the record component .record-component name descriptor ``` -Appends an entry the list of the record class's components. \ No newline at end of file +Appends an entry the list of the record class's components. + +## Method + +### AnnotationDefault + +In a method declaration inside an annotation class, default values are provided via the `AnnotationDefault` attribute. + +Example with an `int` +``` +.method public abstract number ()I { + parameters: { this }, + default-value: 0 +} +``` + +Example with an `int[]` +``` +.method public abstract array ()[I { + parameters: { this }, + default-value: { 0, 1, 2 } +} +``` + +Example with an `ElementType.FIELD` enum value reference +``` +.method public abstract enumeration ()Ljava/lang/annotation/ElementType; { + parameters: { this }, + default-value: .enum java/lang/annotation/ElementType FIELD +} +``` + +Example with a `@Retention(value = RetentionPolicy.CLASS)` annotation value declaration +``` +.method public abstract subanno ()Ljava/lang/annotation/Retention; { + parameters: { this }, + default-value: .annotation java/lang/annotation/Retention { + value: .enum java/lang/annotation/RetentionPolicy CLASS + } +} +``` \ No newline at end of file diff --git a/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwAnnotationVisitor.java b/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwAnnotationVisitor.java index 68e9a14..061587b 100644 --- a/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwAnnotationVisitor.java +++ b/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwAnnotationVisitor.java @@ -6,10 +6,7 @@ import me.darknet.assembler.visitor.ASTAnnotationVisitor; import dev.xdark.blw.annotation.AnnotationBuilder; -import dev.xdark.blw.annotation.ElementEnum; -import dev.xdark.blw.annotation.ElementType; import dev.xdark.blw.type.InstanceType; -import dev.xdark.blw.type.ObjectType; import dev.xdark.blw.type.Types; public class BlwAnnotationVisitor implements ASTAnnotationVisitor, BlwElementAdapter { @@ -28,14 +25,12 @@ public void visitValue(ASTIdentifier name, ASTValue value) { @Override public void visitTypeValue(ASTIdentifier name, ASTIdentifier className) { String nameLiteral = name.literal(); - ObjectType type = Types.objectTypeFromInternalName(className.literal()); - builder.element(nameLiteral, new ElementType(type)); + builder.element(nameLiteral, elementFromTypeIdentifier(className)); } @Override public void visitEnumValue(ASTIdentifier name, ASTIdentifier className, ASTIdentifier enumName) { - InstanceType type = Types.instanceTypeFromInternalName(className.literal()); - builder.element(name.literal(), new ElementEnum(type, enumName.literal())); + builder.element(name.literal(), elementFromEnum(className, enumName)); } @Override diff --git a/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwElementAdapter.java b/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwElementAdapter.java index 6268ef2..0c7b550 100644 --- a/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwElementAdapter.java +++ b/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwElementAdapter.java @@ -1,10 +1,20 @@ package me.darknet.assembler.compile.visitor; +import dev.xdark.blw.annotation.generic.GenericArrayBuilder; +import dev.xdark.blw.type.InstanceType; +import dev.xdark.blw.type.ObjectType; +import dev.xdark.blw.type.Types; +import me.darknet.assembler.ast.ASTElement; import me.darknet.assembler.ast.ElementType; +import me.darknet.assembler.ast.primitive.ASTArray; +import me.darknet.assembler.ast.primitive.ASTIdentifier; import me.darknet.assembler.ast.primitive.ASTNumber; import me.darknet.assembler.ast.specific.ASTValue; import dev.xdark.blw.annotation.*; +import me.darknet.assembler.error.ErrorCollectionException; +import me.darknet.assembler.error.ErrorCollector; +import me.darknet.assembler.visitor.ASTAnnotationArrayVisitor; import org.jetbrains.annotations.NotNull; public interface BlwElementAdapter { @@ -34,4 +44,26 @@ default Element elementFromValue(@NotNull ASTValue value) { default -> throw new UnsupportedOperationException("Enum value of type not supported yet: " + valueType); }; } + + @NotNull + default Element elementFromTypeIdentifier(@NotNull ASTIdentifier className) { + ObjectType type = Types.objectTypeFromInternalName(className.literal()); + return new dev.xdark.blw.annotation.ElementType(type); + } + + @NotNull + default Element elementFromEnum(@NotNull ASTIdentifier className, @NotNull ASTIdentifier enumName) { + InstanceType type = Types.instanceTypeFromInternalName(className.literal()); + return new ElementEnum(type, enumName.literal()); + } + + @NotNull + default Element elementFromArray(@NotNull ASTArray array) { + GenericArrayBuilder builder = new GenericArrayBuilder(); + ErrorCollector collector = new ErrorCollector(); + ASTAnnotationArrayVisitor.accept(new BlwAnnotationArrayVisitor(builder), array, collector); + if (collector.hasErr()) + throw new ErrorCollectionException("Failed building array element from ast", collector); + return builder.build(); + } } diff --git a/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwMethodVisitor.java b/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwMethodVisitor.java index e55ebbd..045a105 100644 --- a/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwMethodVisitor.java +++ b/jasm-composition-jvm/src/main/java/me/darknet/assembler/compile/visitor/BlwMethodVisitor.java @@ -1,12 +1,24 @@ package me.darknet.assembler.compile.visitor; +import dev.xdark.blw.annotation.Element; +import dev.xdark.blw.annotation.ElementByte; +import dev.xdark.blw.annotation.generic.GenericAnnotationBuilder; +import me.darknet.assembler.ast.ASTElement; +import me.darknet.assembler.ast.primitive.ASTArray; +import me.darknet.assembler.ast.primitive.ASTDeclaration; import me.darknet.assembler.ast.primitive.ASTIdentifier; +import me.darknet.assembler.ast.specific.ASTType; +import me.darknet.assembler.ast.specific.ASTValue; import me.darknet.assembler.compile.JvmCompilerOptions; import me.darknet.assembler.compile.analysis.AnalysisResults; import me.darknet.assembler.compile.analysis.Local; import me.darknet.assembler.compile.builder.BlwReplaceMethodBuilder; +import me.darknet.assembler.error.ErrorCollectionException; import me.darknet.assembler.error.ErrorCollector; import me.darknet.assembler.util.CastUtil; +import me.darknet.assembler.util.Pair; +import me.darknet.assembler.visitor.ASTAnnotatedVisitor; +import me.darknet.assembler.visitor.ASTAnnotationVisitor; import me.darknet.assembler.visitor.ASTJvmInstructionVisitor; import me.darknet.assembler.visitor.ASTMethodVisitor; @@ -20,10 +32,11 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; -public class BlwMethodVisitor extends BlwMemberVisitor implements ASTMethodVisitor { +public class BlwMethodVisitor extends BlwMemberVisitor implements BlwElementAdapter, ASTMethodVisitor { private final BlwReplaceMethodBuilder builder; private final JvmCompilerOptions options; private final Consumer analysisResultsConsumer; @@ -51,6 +64,20 @@ public void visitParameter(int index, ASTIdentifier name) { parameterNames.add(literal); } + @Override + public void visitAnnotationDefaultValue(ASTElement defaultValue) { + // Create a dummy annotation visitor and extract the first element to get our value to pass along + GenericAnnotationBuilder elementBuilder = new GenericAnnotationBuilder(Types.OBJECT); + ErrorCollector collector = new ErrorCollector(); + ASTAnnotationVisitor.accept(new BlwAnnotationVisitor(elementBuilder), + Collections.singleton(new Pair<>(ASTIdentifier.STUB, defaultValue)), + collector); + if (collector.hasErr()) + throw new ErrorCollectionException("Failed building array element from ast", collector); + Element element = elementBuilder.elements().values().iterator().next().reflectAs(); + builder.annotationDefault(element); + } + @Override public ASTJvmInstructionVisitor visitJvmCode(@NotNull ErrorCollector collector) { List parameters = new ArrayList<>(); diff --git a/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/ConstantPrinter.java b/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/ConstantPrinter.java index 8f1e5bb..45c477b 100644 --- a/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/ConstantPrinter.java +++ b/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/ConstantPrinter.java @@ -66,7 +66,7 @@ public void acceptDouble(OfDouble value) { ctx.print(content); // Skip 'D' suffix for things like 'NaN' where it is implied - if (!content.matches("[\\D]+")) + if (!content.matches("\\D+")) ctx.print("D"); } diff --git a/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/JvmAnnotationPrinter.java b/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/JvmAnnotationPrinter.java index 3f40c4d..410f877 100644 --- a/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/JvmAnnotationPrinter.java +++ b/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/JvmAnnotationPrinter.java @@ -2,6 +2,7 @@ import dev.xdark.blw.annotation.*; import me.darknet.assembler.util.EscapeUtil; +import org.jetbrains.annotations.NotNull; import java.util.Map; @@ -10,18 +11,52 @@ public class JvmAnnotationPrinter implements AnnotationPrinter { private final Annotation annotation; private final Boolean visible; - public JvmAnnotationPrinter(Annotation annotation, boolean visible) { + protected JvmAnnotationPrinter(Annotation annotation, boolean visible) { this.annotation = annotation; this.visible = visible; } - /** Used internally to denote an embedded annotation */ - private JvmAnnotationPrinter(Annotation annotation) { + protected JvmAnnotationPrinter(Annotation annotation) { this.annotation = annotation; this.visible = null; } - void printElement(PrintContext ctx, Element element) { + public static JvmAnnotationPrinter forTopLevelAnno(Annotation annotation, boolean visible) { + return new JvmAnnotationPrinter(annotation, visible); + } + + public static JvmAnnotationPrinter forEmbeddedAnno(Annotation annotation) { + return new JvmAnnotationPrinter(annotation); + } + + @Override + public void print(PrintContext ctx) { + // For embedded annotations (an annotation inside another) we do not have any concept + // of 'visible' vs 'invisible' annotations, so we'll shorten the name. + String token = visible == null ? ".annotation" : + visible ? ".visible-annotation" : ".invisible-annotation"; + + Annotation annotation = this.annotation; + ctx.begin().element(token).literal(annotation.type().internalName()).print(" "); + if (annotation.names().isEmpty()) { + ctx.print("{}"); + return; + } + var obj = ctx.object(); + obj.print(annotation, this::printEntry); + obj.end(); + } + + public void printAnnotation(@NotNull PrintContext ctx, @NotNull Annotation annotation) { + forEmbeddedAnno(annotation).print(ctx); + } + + private void printEntry(@NotNull PrintContext.ObjectPrint ctx, @NotNull Map.Entry entry) { + ctx.literalValue(entry.getKey()); + printElement(ctx, entry.getValue()); + } + + public void printElement(@NotNull PrintContext ctx, @NotNull Element element) { if (element instanceof ElementInt ei) { ctx.print(Integer.toString(ei.value())); } else if (element instanceof ElementLong el) { @@ -51,8 +86,7 @@ void printElement(PrintContext ctx, Element element) { } else if (element instanceof ElementType et) { ctx.literal(et.value().internalName()); } else if (element instanceof Annotation ea) { - JvmAnnotationPrinter printer = new JvmAnnotationPrinter(ea); - printer.print(ctx); + printAnnotation(ctx, ea); } else if (element instanceof ElementArray ea) { var array = ctx.array(); array.print(ea, this::printElement); @@ -61,27 +95,4 @@ void printElement(PrintContext ctx, Element element) { throw new IllegalStateException("Unexpected value: " + element); } } - - @Override - public void print(PrintContext ctx) { - // For embedded annotations (an annotation inside another) we do not have any concept - // of 'visible' vs 'invisible' annotations, so we'll shorten the name. - String token = visible == null ? ".annotation" : - visible ? ".visible-annotation" : ".invisible-annotation"; - - Annotation annotation = this.annotation; - ctx.begin().element(token).literal(annotation.type().internalName()).print(" "); - if (annotation.names().isEmpty()) { - ctx.print("{}"); - return; - } - var obj = ctx.object(); - obj.print(annotation, this::printEntry); - obj.end(); - } - - private void printEntry(PrintContext.ObjectPrint ctx, Map.Entry entry) { - ctx.literalValue(entry.getKey()); - printElement(ctx, entry.getValue()); - } } diff --git a/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/JvmMethodPrinter.java b/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/JvmMethodPrinter.java index 84689ab..2311ca7 100644 --- a/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/JvmMethodPrinter.java +++ b/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/JvmMethodPrinter.java @@ -1,5 +1,6 @@ package me.darknet.assembler.printer; +import dev.xdark.blw.annotation.Element; import dev.xdark.blw.code.Code; import dev.xdark.blw.code.generic.GenericLabel; import dev.xdark.blw.type.Type; @@ -125,12 +126,19 @@ public void print(PrintContext ctx) { var obj = memberPrinter.printDeclaration(ctx).literal(method.name()).print(" ") .literal(method.type().descriptor()).print(" ").object(); Variables variables = buildVariables(); - boolean hasParameters = !variables.parameters().isEmpty(); - if (hasParameters) { + boolean hasPrior = !variables.parameters().isEmpty(); + if (hasPrior) { var arr = obj.value("parameters").array(); arr.print(variables.parameters().values(), (arrayCtx, parameter) -> arr.print(parameter.name())); arr.end(); } + Element annotationDefault = method.annotationDefault(); + if (annotationDefault != null) { + if (hasPrior) obj.next(); + obj.value("default-value"); + JvmAnnotationPrinter.forEmbeddedAnno(null).printElement(obj, annotationDefault); + hasPrior = true; + } var methodCode = method.code(); if (methodCode != null) { // Ensure there are labels at the absolute start/end of the method so variable ranges won't be wonky. @@ -141,7 +149,7 @@ public void print(PrintContext ctx) { elements.add(new GenericLabel()); // Separator between code and parameters element - if (hasParameters) obj.next(); + if (hasPrior) obj.next(); // Populate label names Map labelNames = getLabelNames(elements); diff --git a/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/MemberPrinter.java b/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/MemberPrinter.java index fbd7e3e..aa9e8fe 100644 --- a/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/MemberPrinter.java +++ b/jasm-composition-jvm/src/main/java/me/darknet/assembler/printer/MemberPrinter.java @@ -20,12 +20,12 @@ public MemberPrinter(Member member, Type type) { public void printAttributes(PrintContext ctx) { if (annotated != null) { for (Annotation visibleRuntimeAnnotation : annotated.visibleRuntimeAnnotations()) { - JvmAnnotationPrinter printer = new JvmAnnotationPrinter(visibleRuntimeAnnotation, true); + JvmAnnotationPrinter printer = JvmAnnotationPrinter.forTopLevelAnno(visibleRuntimeAnnotation, true); printer.print(ctx); ctx.next(); } - for (Annotation invisibleRuntimeAnnotation : annotated.invisibleRuntimeAnnotations()) { - JvmAnnotationPrinter printer = new JvmAnnotationPrinter(invisibleRuntimeAnnotation, false); + for (Annotation invisibleRuntimeAnnotation : annotated.invisibleRuntimeAnnotations()) { + JvmAnnotationPrinter printer = JvmAnnotationPrinter.forTopLevelAnno(invisibleRuntimeAnnotation, false); printer.print(ctx); ctx.next(); } diff --git a/jasm-composition-jvm/src/test/java/me/darknet/assembler/SampleCompilerTest.java b/jasm-composition-jvm/src/test/java/me/darknet/assembler/SampleCompilerTest.java index 1ea50ed..c23b246 100644 --- a/jasm-composition-jvm/src/test/java/me/darknet/assembler/SampleCompilerTest.java +++ b/jasm-composition-jvm/src/test/java/me/darknet/assembler/SampleCompilerTest.java @@ -670,6 +670,27 @@ public static List getValidSources() { @Nested class AttributeSupport { + @Test + void defaultAnnotationValue() throws Throwable { + BinaryTestArgument arg = BinaryTestArgument.fromName("AnnoDefaultValues.sample"); + byte[] raw = arg.source.get(); + + // Print the initial raw + String source = dissassemble(raw); + + // Assert all the default values are there + assertTrue(source.contains("default-value: 0")); + assertTrue(source.contains("default-value: 'c'")); + assertTrue(source.contains("default-value: \"hello\"")); + assertTrue(source.contains("default-value: { 0, 1, 2 }")); + assertTrue(source.contains("default-value: .enum java/lang/annotation/ElementType FIELD")); + assertTrue(source.contains("default-value: .annotation java/lang/annotation/Retention {")); + assertTrue(source.contains(" value: .enum java/lang/annotation/RetentionPolicy CLASS")); + + // Round-trip it + roundTrip(source, arg); + } + @Test void outerClassInfo() throws Throwable { BinaryTestArgument arg = BinaryTestArgument.fromName("Outside$1.sample"); diff --git a/jasm-composition-jvm/src/test/resources/samples/binary/AnnoDefaultValues.sample b/jasm-composition-jvm/src/test/resources/samples/binary/AnnoDefaultValues.sample new file mode 100644 index 0000000..7e09dc4 Binary files /dev/null and b/jasm-composition-jvm/src/test/resources/samples/binary/AnnoDefaultValues.sample differ diff --git a/jasm-composition-jvm/src/test/resources/samples/binary/Outside.class b/jasm-composition-jvm/src/test/resources/samples/binary/Outside.class deleted file mode 100644 index f102237..0000000 Binary files a/jasm-composition-jvm/src/test/resources/samples/binary/Outside.class and /dev/null differ diff --git a/jasm-core/src/main/java/me/darknet/assembler/ast/primitive/ASTIdentifier.java b/jasm-core/src/main/java/me/darknet/assembler/ast/primitive/ASTIdentifier.java index 852928a..fe29d01 100644 --- a/jasm-core/src/main/java/me/darknet/assembler/ast/primitive/ASTIdentifier.java +++ b/jasm-core/src/main/java/me/darknet/assembler/ast/primitive/ASTIdentifier.java @@ -2,10 +2,15 @@ import me.darknet.assembler.ast.ElementType; import me.darknet.assembler.parser.Token; +import me.darknet.assembler.parser.TokenType; import me.darknet.assembler.util.EscapeUtil; +import me.darknet.assembler.util.Location; +import me.darknet.assembler.util.Range; public class ASTIdentifier extends ASTLiteral { - public ASTIdentifier(Token value) { + public static final ASTIdentifier STUB = new ASTIdentifier(new Token(Range.EMPTY, Location.UNKNOWN, TokenType.IDENTIFIER, "")); + + public ASTIdentifier(Token value) { super(ElementType.IDENTIFIER, value); } diff --git a/jasm-core/src/main/java/me/darknet/assembler/ast/specific/ASTAnnotation.java b/jasm-core/src/main/java/me/darknet/assembler/ast/specific/ASTAnnotation.java index 8190b9c..38d5565 100644 --- a/jasm-core/src/main/java/me/darknet/assembler/ast/specific/ASTAnnotation.java +++ b/jasm-core/src/main/java/me/darknet/assembler/ast/specific/ASTAnnotation.java @@ -2,16 +2,11 @@ import me.darknet.assembler.ast.ASTElement; import me.darknet.assembler.ast.ElementType; -import me.darknet.assembler.ast.primitive.ASTArray; -import me.darknet.assembler.ast.primitive.ASTEmpty; import me.darknet.assembler.ast.primitive.ASTIdentifier; import me.darknet.assembler.error.ErrorCollector; import me.darknet.assembler.util.CollectionUtil; import me.darknet.assembler.util.ElementMap; -import me.darknet.assembler.util.Pair; -import me.darknet.assembler.visitor.ASTAnnotationArrayVisitor; import me.darknet.assembler.visitor.ASTAnnotationVisitor; - import org.jetbrains.annotations.Nullable; public class ASTAnnotation extends ASTElement { @@ -43,73 +38,9 @@ public T value(String name) { return values.get(name); } - void accept(ErrorCollector collector, @Nullable ASTAnnotationArrayVisitor visitor, ASTArray array) { - // TODO: Do we want to keep visiting, but just not call the visitor? - // - This does not feed into the collector - if (visitor == null) - return; - - // TODO: due to huge annotations, i would advice for a process queue. - // But this is not a problem for now until xxDark notices this code, which i hope - // he does not. - for (ASTElement arrayValue : array.values()) { - if (arrayValue instanceof ASTValue val) { - visitor.visitValue(val); - } else if (arrayValue instanceof ASTIdentifier identifier) { - visitor.visitTypeValue(identifier); - } else if (arrayValue instanceof ASTEnum astEnum) { - visitor.visitEnumValue(astEnum.enumType(), astEnum.enumValue()); - } else if (arrayValue instanceof ASTAnnotation annotation) { - ASTAnnotationVisitor anno = visitor.visitAnnotationValue(annotation.classType()); - annotation.accept(collector, anno); - } else if (arrayValue instanceof ASTArray astArray) { - ASTAnnotationArrayVisitor arrayVisitor = visitor.visitArrayValue(); - accept(collector, arrayVisitor, astArray); - } else if (arrayValue instanceof ASTEmpty) { - ASTAnnotationArrayVisitor arrayVisitor = visitor.visitArrayValue(); - accept(collector, arrayVisitor, ASTEmpty.EMPTY_ARRAY); - } else { - if (arrayValue == null) { - collector.addError("Unprocessable value in array", array.location()); - continue; - } - collector.addError("Don't know how to process: " + arrayValue.type(), arrayValue.location()); - } - } - - visitor.visitEnd(); - } - public void accept(ErrorCollector collector, @Nullable ASTAnnotationVisitor visitor) { - // TODO: Do we want to keep visiting, but just not call the visitor? - // - This does not feed into the collector if (visitor == null) return; - - for (Pair pair : values.pairs()) { - ASTElement value = pair.second(); - if (value instanceof ASTValue val) { - visitor.visitValue(pair.first(), val); - } else if (value instanceof ASTIdentifier identifier) { - visitor.visitTypeValue(pair.first(), identifier); - } else if (value instanceof ASTEnum astEnum) { - visitor.visitEnumValue(pair.first(), astEnum.enumType(), astEnum.enumValue()); - } else if (value instanceof ASTArray array) { - ASTAnnotationArrayVisitor arrayVisitor = visitor.visitArrayValue(pair.first()); - accept(collector, arrayVisitor, array); - } else if (value instanceof ASTAnnotation annotation) { - ASTAnnotationVisitor anno = visitor.visitAnnotationValue(pair.first(), annotation.classType()); - annotation.accept(collector, anno); - } else { - if (value == null) { - collector.addError("Unprocessable value in annotation", location()); - continue; - } - collector.addError("Don't know how to process: " + value.type(), value.location()); - } - } - - visitor.visitEnd(); + ASTAnnotationVisitor.accept(visitor, values.pairs(), collector); } - } diff --git a/jasm-core/src/main/java/me/darknet/assembler/ast/specific/ASTMethod.java b/jasm-core/src/main/java/me/darknet/assembler/ast/specific/ASTMethod.java index fb1faaa..1ec9876 100644 --- a/jasm-core/src/main/java/me/darknet/assembler/ast/specific/ASTMethod.java +++ b/jasm-core/src/main/java/me/darknet/assembler/ast/specific/ASTMethod.java @@ -1,5 +1,6 @@ package me.darknet.assembler.ast.specific; +import me.darknet.assembler.ast.ASTElement; import me.darknet.assembler.ast.ElementType; import me.darknet.assembler.ast.primitive.ASTCode; import me.darknet.assembler.ast.primitive.ASTIdentifier; @@ -11,6 +12,7 @@ import me.darknet.assembler.visitor.ASTInstructionVisitor; import me.darknet.assembler.visitor.ASTMethodVisitor; import me.darknet.assembler.visitor.Modifiers; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -18,23 +20,31 @@ public class ASTMethod extends ASTMember { private final List parameters; private final List exceptions; + private final ASTElement defaultValue; private final ASTCode code; private final List> instructions; private final BytecodeFormat format; public ASTMethod(Modifiers modifiers, ASTIdentifier name, ASTIdentifier descriptor, List parameters, - List exceptions, ASTCode code, List> instructions, BytecodeFormat format) { + ASTElement defaultValue, List exceptions, ASTCode code, + List> instructions, BytecodeFormat format) { super(ElementType.METHOD, modifiers, name, descriptor); this.parameters = parameters; this.exceptions = exceptions; + this.defaultValue = defaultValue; this.code = code; this.instructions = instructions; this.format = format; addChildren(parameters); addChildren(exceptions); + if (defaultValue != null) addChild(defaultValue); if (code != null) addChild(code); } + public @Nullable ASTElement getAnnotationDefaultValue() { + return defaultValue; + } + public List parameters() { return parameters; } @@ -58,10 +68,14 @@ public void accept(ErrorCollector collector, ASTMethodVisitor visitor) { for (int i = 0; i < localParams.size(); i++) { visitor.visitParameter(i, localParams.get(i)); } + if (this.defaultValue != null) { + visitor.visitAnnotationDefaultValue(defaultValue); + } if (this.code == null) { visitor.visitEnd(); return; } + ASTInstructionVisitor instructionVisitor = switch (format) { case JVM -> visitor.visitJvmCode(collector); case DALVIK -> null; diff --git a/jasm-core/src/main/java/me/darknet/assembler/error/ErrorCollectionException.java b/jasm-core/src/main/java/me/darknet/assembler/error/ErrorCollectionException.java new file mode 100644 index 0000000..dc9e51f --- /dev/null +++ b/jasm-core/src/main/java/me/darknet/assembler/error/ErrorCollectionException.java @@ -0,0 +1,23 @@ +package me.darknet.assembler.error; + +import java.util.ArrayList; +import java.util.List; + +public class ErrorCollectionException extends RuntimeException { + private final List errors = new ArrayList<>(); + private final List warns = new ArrayList<>(); + + public ErrorCollectionException(String message, ErrorCollector collector) { + super(message); + errors.addAll(collector.getErrors()); + warns.addAll(collector.getWarns()); + } + + public List getErrors() { + return errors; + } + + public List getWarns() { + return warns; + } +} diff --git a/jasm-core/src/main/java/me/darknet/assembler/parser/processor/ASTProcessor.java b/jasm-core/src/main/java/me/darknet/assembler/parser/processor/ASTProcessor.java index 868f2ad..d7b9ce6 100644 --- a/jasm-core/src/main/java/me/darknet/assembler/parser/processor/ASTProcessor.java +++ b/jasm-core/src/main/java/me/darknet/assembler/parser/processor/ASTProcessor.java @@ -223,6 +223,15 @@ private static ASTMethod parseMethod(ParserContext ctx, ASTDeclaration declarati if (array != null) parameters = ctx.validateArray(array, ElementType.IDENTIFIER, "method parameter", declaration); } + ASTElement defaultValueElement = null; + if (body.values().containsKey("default-value")) { + defaultValueElement = body.values().get("default-value"); + + // We treat the default-value contents as being in an annotation as the potential contents are the same. + ctx.enterState(State.IN_ANNOTATION); + validateElementValue(ctx, defaultValueElement); + ctx.leaveState(State.IN_ANNOTATION); + } List exceptions = new ArrayList<>(); if (body.values().containsKey("exceptions")) { ASTArray array = ctx.validateEmptyableElement( @@ -268,7 +277,7 @@ private static ASTMethod parseMethod(ParserContext ctx, ASTDeclaration declarati ASTIdentifier desc = ctx.validateIdentifier(elements.get(descIndex), "method descriptor", declaration); Modifiers modifiers = parseModifiers(ctx, nameIndex, declaration); ProcessorAttributes attributes = ctx.result.collectAttributes(); - return new ASTMethod(modifiers, name, desc, parameters, exceptions, code, instructions, ctx.format) + return new ASTMethod(modifiers, name, desc, parameters, defaultValueElement, exceptions, code, instructions, ctx.format) .accept(attributes); } diff --git a/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTAnnotationArrayVisitor.java b/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTAnnotationArrayVisitor.java index 23a626a..fdb4db3 100644 --- a/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTAnnotationArrayVisitor.java +++ b/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTAnnotationArrayVisitor.java @@ -1,10 +1,16 @@ package me.darknet.assembler.visitor; +import me.darknet.assembler.ast.ASTElement; +import me.darknet.assembler.ast.primitive.ASTArray; +import me.darknet.assembler.ast.primitive.ASTEmpty; import me.darknet.assembler.ast.primitive.ASTIdentifier; +import me.darknet.assembler.ast.specific.ASTAnnotation; +import me.darknet.assembler.ast.specific.ASTEnum; import me.darknet.assembler.ast.specific.ASTValue; +import me.darknet.assembler.error.ErrorCollector; +import org.jetbrains.annotations.NotNull; public interface ASTAnnotationArrayVisitor { - void visitValue(ASTValue value); void visitTypeValue(ASTIdentifier className); @@ -17,4 +23,35 @@ public interface ASTAnnotationArrayVisitor { void visitEnd(); + static void accept(@NotNull ASTAnnotationArrayVisitor visitor, @NotNull ASTArray array, @NotNull ErrorCollector collector) { + // TODO: due to huge annotations, i would advice for a process queue. + // But this is not a problem for now until xxDark notices this code, which i hope + // he does not. + for (ASTElement arrayValue : array.values()) { + if (arrayValue instanceof ASTValue val) { + visitor.visitValue(val); + } else if (arrayValue instanceof ASTIdentifier identifier) { + visitor.visitTypeValue(identifier); + } else if (arrayValue instanceof ASTEnum astEnum) { + visitor.visitEnumValue(astEnum.enumType(), astEnum.enumValue()); + } else if (arrayValue instanceof ASTAnnotation annotation) { + ASTAnnotationVisitor anno = visitor.visitAnnotationValue(annotation.classType()); + annotation.accept(collector, anno); + } else if (arrayValue instanceof ASTArray astArray) { + ASTAnnotationArrayVisitor arrayVisitor = visitor.visitArrayValue(); + accept(arrayVisitor, astArray, collector); + } else if (arrayValue instanceof ASTEmpty) { + ASTAnnotationArrayVisitor arrayVisitor = visitor.visitArrayValue(); + accept(arrayVisitor, ASTEmpty.EMPTY_ARRAY, collector); + } else { + if (arrayValue == null) { + collector.addError("Unprocessable value in array", array.location()); + continue; + } + collector.addError("Don't know how to process: " + arrayValue.type(), arrayValue.location()); + } + } + + visitor.visitEnd(); + } } diff --git a/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTAnnotationVisitor.java b/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTAnnotationVisitor.java index a1491f8..62c49f1 100644 --- a/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTAnnotationVisitor.java +++ b/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTAnnotationVisitor.java @@ -1,10 +1,22 @@ package me.darknet.assembler.visitor; +import me.darknet.assembler.ast.ASTElement; +import me.darknet.assembler.ast.primitive.ASTArray; +import me.darknet.assembler.ast.primitive.ASTDeclaration; import me.darknet.assembler.ast.primitive.ASTIdentifier; +import me.darknet.assembler.ast.primitive.ASTObject; +import me.darknet.assembler.ast.specific.ASTAnnotation; +import me.darknet.assembler.ast.specific.ASTEnum; import me.darknet.assembler.ast.specific.ASTValue; +import me.darknet.assembler.error.ErrorCollector; +import me.darknet.assembler.util.ElementMap; +import me.darknet.assembler.util.Pair; +import org.jetbrains.annotations.NotNull; -public interface ASTAnnotationVisitor { +import java.util.Collection; +import java.util.Collections; +public interface ASTAnnotationVisitor { void visitValue(ASTIdentifier name, ASTValue value); void visitTypeValue(ASTIdentifier name, ASTIdentifier className); @@ -17,4 +29,57 @@ public interface ASTAnnotationVisitor { void visitEnd(); + static void accept(@NotNull ASTAnnotationVisitor visitor, @NotNull Collection> pairs, @NotNull ErrorCollector collector) { + for (Pair pair : pairs) { + ASTElement value = pair.second(); + ASTIdentifier key = pair.first(); + if (value instanceof ASTValue val) { + visitor.visitValue(key, val); + } else if (value instanceof ASTIdentifier identifier) { + visitor.visitTypeValue(key, identifier); + } else if (value instanceof ASTEnum astEnum) { + visitor.visitEnumValue(key, astEnum.enumType(), astEnum.enumValue()); + } else if (value instanceof ASTArray array) { + ASTAnnotationArrayVisitor arrayVisitor = visitor.visitArrayValue(key); + if (arrayVisitor == null) { + continue; + } + ASTAnnotationArrayVisitor.accept(arrayVisitor, array, collector); + } else if (value instanceof ASTAnnotation annotation) { + ASTAnnotationVisitor anno = visitor.visitAnnotationValue(key, annotation.classType()); + annotation.accept(collector, anno); + } else { + if (value instanceof ASTDeclaration declaration) { + try { + // Attempt to parse declaration as an enum/annotation + if (declaration.elements().size() == 2) { + String keyword = declaration.keyword().content(); + if (keyword.equals(".enum")) { + visitor.visitEnumValue(key, (ASTIdentifier) declaration.element(0), (ASTIdentifier) declaration.element(1)); + continue; + } else if (keyword.equals(".annotation")) { + ASTIdentifier annoType = (ASTIdentifier) declaration.element(0); + ASTObject annoObject = (ASTObject) declaration.element(1); + ASTAnnotationVisitor anno = visitor.visitAnnotationValue(key, annoType); + ElementMap map = new ElementMap<>(); + for (var subPair : annoObject.values().pairs()) + map.put(subPair.first(), subPair.second()); + ASTAnnotationVisitor.accept(anno, map.pairs(), collector); + continue; + } + } + } catch (Exception ex) { + collector.addError("Unprocessable declaration (enum?) in annotation", key.location()); + continue; + } + } else if (value == null) { + collector.addError("Unprocessable value in annotation", key.location()); + continue; + } + collector.addError("Don't know how to process: " + value.type(), value.location()); + } + } + + visitor.visitEnd(); + } } diff --git a/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTMethodVisitor.java b/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTMethodVisitor.java index 6a15e0b..c4d0bc3 100644 --- a/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTMethodVisitor.java +++ b/jasm-core/src/main/java/me/darknet/assembler/visitor/ASTMethodVisitor.java @@ -1,5 +1,6 @@ package me.darknet.assembler.visitor; +import me.darknet.assembler.ast.ASTElement; import me.darknet.assembler.ast.primitive.ASTIdentifier; import me.darknet.assembler.error.ErrorCollector; import org.jetbrains.annotations.NotNull; @@ -8,6 +9,7 @@ public interface ASTMethodVisitor extends ASTDeclarationVisitor { void visitParameter(int index, ASTIdentifier name); - ASTJvmInstructionVisitor visitJvmCode(@NotNull ErrorCollector collector); + void visitAnnotationDefaultValue(ASTElement defaultValue); + ASTJvmInstructionVisitor visitJvmCode(@NotNull ErrorCollector collector); }