Skip to content

Commit

Permalink
feat: Annotation-default support in methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Col-E committed Oct 13, 2024
1 parent 663bcb4 commit cef7aee
Show file tree
Hide file tree
Showing 19 changed files with 341 additions and 121 deletions.
42 changes: 41 additions & 1 deletion docs/language/Attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<MethodType, Method> implements ASTMethodVisitor {
public class BlwMethodVisitor extends BlwMemberVisitor<MethodType, Method> implements BlwElementAdapter, ASTMethodVisitor {
private final BlwReplaceMethodBuilder builder;
private final JvmCompilerOptions options;
private final Consumer<AnalysisResults> analysisResultsConsumer;
Expand Down Expand Up @@ -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<Local> parameters = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dev.xdark.blw.annotation.*;
import me.darknet.assembler.util.EscapeUtil;
import org.jetbrains.annotations.NotNull;

import java.util.Map;

Expand All @@ -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<String, Element> 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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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<String, Element> entry) {
ctx.literalValue(entry.getKey());
printElement(ctx, entry.getValue());
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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<Integer, String> labelNames = getLabelNames(elements);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,27 @@ public static List<TestArgument> 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");
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading

0 comments on commit cef7aee

Please sign in to comment.