Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 55 additions & 20 deletions src/main/java/io/papermc/codebook/pages/FixJarPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import dev.denwav.hypo.asm.AsmMethodData;
import dev.denwav.hypo.core.HypoContext;
import dev.denwav.hypo.hydrate.generic.HypoHydration;
import dev.denwav.hypo.model.data.ClassData;
import dev.denwav.hypo.model.data.ClassKind;
import dev.denwav.hypo.model.data.MethodData;
import dev.denwav.hypo.model.data.Visibility;
import jakarta.inject.Inject;
Expand Down Expand Up @@ -55,33 +57,17 @@ protected void processClass(final AsmClassData classData) throws IOException {
private static final class OverrideAnnotationAdder {
private OverrideAnnotationAdder() {}

private static void addAnnotations(final AsmClassData classData) {
private static void addAnnotations(final AsmClassData classData) throws IOException {
for (final MethodData method : classData.methods()) {
if (method.isStatic() || method.visibility() == Visibility.PRIVATE) {
// Synthetic methods have no declaration in source, so @Override doesn't apply to them
if (method.isStatic() || method.visibility() == Visibility.PRIVATE || method.isSynthetic()) {
continue;
}
if (isInitializer(method)) {
continue;
}

final MethodData baseMethod;
final @Nullable Set<MethodData> syntheticSources = method.get(HypoHydration.SYNTHETIC_SOURCES);
if (syntheticSources == null) {
baseMethod = method;
} else {
outer:
{
for (final MethodData targetMethod : syntheticSources) {
if (targetMethod.parentClass().equals(method.parentClass())) {
baseMethod = targetMethod;
break outer;
}
}
return;
}
}

if (baseMethod.superMethod() != null) {
if (overrides(method)) {
final MethodNode node = ((AsmMethodData) method).getNode();
final var annoClass = "Ljava/lang/Override;";
if (node.invisibleAnnotations == null
Expand All @@ -93,6 +79,55 @@ private static void addAnnotations(final AsmClassData classData) {
}
}

private static boolean overrides(final MethodData method) throws IOException {
if (hasSuperMethod(method)) {
return true;
}

// Overrides with a different descriptor are only linked to the super method through a bridge in this class.
// A bridge in another class means that class inherits this method to implement something, which doesn't
// make this method an override.
final @Nullable Set<MethodData> syntheticSources = method.get(HypoHydration.SYNTHETIC_SOURCES);
if (syntheticSources != null) {
for (final MethodData source : syntheticSources) {
if (source.parentClass().equals(method.parentClass()) && hasSuperMethod(source)) {
return true;
}
}
}
return false;
}

private static boolean hasSuperMethod(final MethodData method) throws IOException {
final @Nullable MethodData superMethod = method.superMethod();
if (superMethod == null) {
return false;
}

// Hypo treats Object as the super class of interfaces, but interfaces only have its public methods as
// members, so @Override isn't allowed for just clone() or finalize() (JLS 9.2, 9.6.4.4). superMethod() only
// gives a single method though, so the method could also be declared by a superinterface.
final ClassData parentClass = method.parentClass();
if (parentClass.is(ClassKind.INTERFACE) && superMethod.visibility() != Visibility.PUBLIC) {
return isDeclaredBySuperInterface(method, parentClass);
}
return true;
}

private static boolean isDeclaredBySuperInterface(final MethodData method, final ClassData classData)
throws IOException {
for (final ClassData superInterface : classData.interfaces()) {
final @Nullable MethodData superMethod = superInterface.method(method.name(), method.descriptor());
if (superMethod != null && method.overrides(superMethod)) {
return true;
}
if (isDeclaredBySuperInterface(method, superInterface)) {
return true;
}
}
return false;
}

private static boolean isInitializer(final MethodData method) {
return method.name().equals("<init>") || method.name().equals("<clinit>");
}
Expand Down
Loading