Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unwrap checkcasts for blockstate & loot contexts #27

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* codebook is a remapper utility for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 3 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/

package io.papermc.codebook.lvt;

import com.google.inject.Injector;
import io.papermc.codebook.report.ReportType;
import io.papermc.codebook.report.Reports;
import io.papermc.codebook.report.type.CheckCastWraps;
import java.util.Set;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

public class InstructionUnwrapper {

private final Reports reports;
private final Injector reportsInjector;

private static final MethodMatcher BOX_METHODS = new MethodMatcher(Set.of(
new Method("java/lang/Byte", "byteValue", "()B"),
new Method("java/lang/Short", "shortValue", "()S"),
new Method("java/lang/Integer", "intValue", "()I"),
new Method("java/lang/Long", "longValue", "()J"),
new Method("java/lang/Float", "floatValue", "()F"),
new Method("java/lang/Double", "doubleValue", "()D"),
new Method("java/lang/Boolean", "booleanValue", "()Z"),
new Method("java/lang/Character", "charValue", "()C")));

private static final MethodMatcher UNWRAP_AFTER_CAST = new MethodMatcher(Set.of(
new Method(
"net/minecraft/world/level/block/state/BlockState",
"getValue",
"(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;"),
new Method(
"net/minecraft/world/level/storage/loot/LootContext",
"getParamOrNull",
"(Lnet/minecraft/world/level/storage/loot/parameters/LootContextParam;)Ljava/lang/Object;"),
new Method(
"net/minecraft/world/level/storage/loot/LootParams$Builder",
"getOptionalParameter",
"(Lnet/minecraft/world/level/storage/loot/parameters/LootContextParam;)Ljava/lang/Object;")));

public InstructionUnwrapper(final Reports reports, final Injector reportsInjector) {
this.reports = reports;
this.reportsInjector = reportsInjector;
}

public @Nullable AbstractInsnNode unwrapFromAssignment(final VarInsnNode assignment) {
@Nullable AbstractInsnNode prev = assignment.getPrevious();
if (prev == null) {
return null;
}

// unwrap unboxing methods and the subsequent checkcast to the boxed type
if (prev.getOpcode() == Opcodes.INVOKEVIRTUAL && BOX_METHODS.matches(prev)) {
prev = prev.getPrevious();
if (prev != null && prev.getOpcode() == Opcodes.CHECKCAST) {
prev = prev.getPrevious();
}
}
if (prev == null) {
return null;
}

if (prev.getOpcode() == Opcodes.CHECKCAST) {
final AbstractInsnNode tempPrev = prev.getPrevious();
if (tempPrev.getOpcode() == Opcodes.INVOKEVIRTUAL
|| tempPrev.getOpcode() == Opcodes.INVOKEINTERFACE
|| tempPrev.getOpcode() == Opcodes.INVOKESTATIC) {
final MethodInsnNode methodInsn = (MethodInsnNode) tempPrev;
if (UNWRAP_AFTER_CAST.matches(methodInsn)) {
prev = methodInsn;
} else {
if (this.reports.shouldGenerate(ReportType.CHECK_CAST_WRAPS)) {
this.reportsInjector.getInstance(CheckCastWraps.class).report(methodInsn);
}
return null;
}
}
}

return prev;
}

private record MethodMatcher(Set<Method> methods, Set<String> methodNames) {

private MethodMatcher(final Set<Method> methods) {
this(methods, methods.stream().map(Method::name).collect(Collectors.toUnmodifiableSet()));
}

boolean matches(final AbstractInsnNode insn) {
return insn instanceof final MethodInsnNode methodInsnNode
&& this.methodNames.contains(methodInsnNode.name)
&& this.methods.stream().anyMatch(m -> m.matches(methodInsnNode));
}
}

private record Method(String owner, String name, String desc, boolean itf) {

private Method(final String owner, final String name, final String desc) {
this(owner, name, desc, false);
}

boolean matches(final MethodInsnNode insn) {
return this.owner.equals(insn.owner)
&& this.name.equals(insn.name)
&& this.desc.equals(insn.desc)
&& this.itf == insn.itf;
}
}
}
Original file line number Diff line number Diff line change
@@ -70,7 +70,8 @@ public LvtNamer(final HypoContext context, final MappingSet mappings, final Repo
this.lvtTypeSuggester = new LvtTypeSuggester(context);
this.reports = reports;
this.reportsInjector = Guice.createInjector(reports);
this.lvtAssignSuggester = new RootLvtSuggester(context, this.lvtTypeSuggester, this.reportsInjector);
this.lvtAssignSuggester =
new RootLvtSuggester(context, this.lvtTypeSuggester, this.reports, this.reportsInjector);
}

public void processClass(final AsmClassData classData) throws IOException {
Original file line number Diff line number Diff line change
@@ -44,18 +44,20 @@
import io.papermc.codebook.lvt.suggestion.SingleVerbSuggester;
import io.papermc.codebook.lvt.suggestion.StringSuggester;
import io.papermc.codebook.lvt.suggestion.VerbPrefixBooleanSuggester;
import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.field.FieldCallContext;
import io.papermc.codebook.lvt.suggestion.context.field.FieldInsnContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import io.papermc.codebook.lvt.suggestion.numbers.MthRandomSuggester;
import io.papermc.codebook.lvt.suggestion.numbers.RandomSourceSuggester;
import io.papermc.codebook.report.Reports;
import io.papermc.codebook.report.type.MissingMethodLvtSuggestion;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
@@ -85,16 +87,21 @@ public final class RootLvtSuggester extends AbstractModule implements LvtSuggest
GenericSuggester.class);

private final HypoContext hypoContext;
private final LvtTypeSuggester lvtTypeSuggester;
final LvtTypeSuggester lvtTypeSuggester;
private final Injector injector;
private final List<? extends LvtSuggester> suggesters;
private final InstructionUnwrapper unwrapper;

public RootLvtSuggester(
final HypoContext hypoContext, final LvtTypeSuggester lvtTypeSuggester, final Injector reports) {
final HypoContext hypoContext,
final LvtTypeSuggester lvtTypeSuggester,
final Reports reports,
final Injector reportsInjector) {
this.hypoContext = hypoContext;
this.lvtTypeSuggester = lvtTypeSuggester;
this.injector = reports.createChildInjector(this);
this.injector = reportsInjector.createChildInjector(this);
this.suggesters = SUGGESTERS.stream().map(this.injector::getInstance).toList();
this.unwrapper = new InstructionUnwrapper(reports, reportsInjector);
}

@Override
@@ -139,7 +146,8 @@ public String suggestName(
}

if (assignmentNode != null) {
final @Nullable String suggestedName = this.suggestNameFromFirstAssignment(parent, assignmentNode);
final @Nullable String suggestedName = this.suggestNameFromFirstAssignment(
ContainerContext.from(parent), new AssignmentContext(assignmentNode, lvt));
if (suggestedName != null) {
return determineFinalName(suggestedName, scopedNames);
}
@@ -172,50 +180,9 @@ public static String determineFinalName(final String suggestedName, final Set<St
}
}

private static final Set<BoxMethod> BOX_METHODS = Set.of(
new BoxMethod("java/lang/Byte", "byteValue", "()B"),
new BoxMethod("java/lang/Short", "shortValue", "()S"),
new BoxMethod("java/lang/Integer", "intValue", "()I"),
new BoxMethod("java/lang/Long", "longValue", "()J"),
new BoxMethod("java/lang/Float", "floatValue", "()F"),
new BoxMethod("java/lang/Double", "doubleValue", "()D"),
new BoxMethod("java/lang/Boolean", "booleanValue", "()Z"),
new BoxMethod("java/lang/Character", "charValue", "()C"));
private static final Set<String> BOX_METHOD_NAMES =
BOX_METHODS.stream().map(BoxMethod::name).collect(Collectors.toUnmodifiableSet());

private record BoxMethod(String owner, String name, String desc) {
boolean is(final MethodInsnNode node) {
return this.owner.equals(node.owner)
&& this.name.equals(node.name)
&& this.desc.equals(node.desc)
&& !node.itf;
}
}

private @Nullable AbstractInsnNode walkBack(final VarInsnNode assignmentNode) {
AbstractInsnNode prev = assignmentNode.getPrevious();
if (prev != null) {
final int op = prev.getOpcode();
if (op == Opcodes.INVOKEVIRTUAL) {
final MethodInsnNode methodInsnNode = (MethodInsnNode) prev;
if (BOX_METHOD_NAMES.contains(methodInsnNode.name)
&& BOX_METHODS.stream().anyMatch(bm -> bm.is(methodInsnNode))) {
prev = prev.getPrevious();
if (prev != null && prev.getOpcode() == Opcodes.CHECKCAST) {
return prev.getPrevious();
}
return prev;
}
}
return prev;
}
return null;
}

private @Nullable String suggestNameFromFirstAssignment(final MethodData parent, final VarInsnNode varInsn)
throws IOException {
final @Nullable AbstractInsnNode prev = this.walkBack(varInsn);
private @Nullable String suggestNameFromFirstAssignment(
final ContainerContext container, final AssignmentContext assignment) throws IOException {
final @Nullable AbstractInsnNode prev = this.unwrapper.unwrapFromAssignment(assignment.assignmentNode());
if (prev == null) {
return null;
}
@@ -236,36 +203,49 @@ boolean is(final MethodInsnNode node) {
return null;
}

return this.suggestFromMethod(
final @Nullable String suggestion = this.suggestFromMethod(
MethodCallContext.create(method),
MethodInsnContext.create(owner, methodInsnNode),
ContainerContext.from(parent));
container,
assignment,
new SuggesterContext(this.hypoContext, this.lvtTypeSuggester));
if (suggestion == null) {
this.injector
.getInstance(MissingMethodLvtSuggestion.class)
.reportMissingMethodLvtSuggestion(method, methodInsnNode);
}
return suggestion;
}

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
@Nullable String suggestion;
for (final LvtSuggester delegate : this.suggesters) {
suggestion = delegate.suggestFromMethod(call, insn, container);
suggestion = delegate.suggestFromMethod(call, insn, container, assignment, suggester);
if (suggestion != null) {
return suggestion;
}
}
this.injector
.getInstance(MissingMethodLvtSuggestion.class)
.reportMissingMethodLvtSuggestion(call.data(), insn.node());
return null;
}

@Override
public @Nullable String suggestFromField(
final FieldCallContext call, final FieldInsnContext insn, final ContainerContext container)
final FieldCallContext call,
final FieldInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
@Nullable String suggestion;
for (final LvtSuggester delegate : this.suggesters) {
suggestion = delegate.suggestFromField(call, insn, container);
suggestion = delegate.suggestFromField(call, insn, container, assignment, suggester);
if (suggestion != null) {
return suggestion;
}
Original file line number Diff line number Diff line change
@@ -23,8 +23,11 @@
package io.papermc.codebook.lvt.suggestion;

import static io.papermc.codebook.lvt.LvtUtil.staticFinalFieldNameToLocalName;
import static io.papermc.codebook.lvt.LvtUtil.toJvmType;

import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.io.IOException;
@@ -49,16 +52,39 @@ public class ComplexGetSuggester implements LvtSuggester {
"getValue", "(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;")),
Set.of(
"Lnet/minecraft/world/level/block/state/properties/IntegerProperty;",
"Lnet/minecraft/world/level/block/state/properties/BooleanProperty;"),
"Lnet/minecraft/world/level/block/state/properties/BooleanProperty;",
"Lnet/minecraft/world/level/block/state/properties/EnumProperty;",
"Lnet/minecraft/world/level/block/state/properties/DirectionProperty;"),
"Value");

private static final StaticFieldEntry LOOT_CONTEXT_PARAM = new StaticFieldEntry(
Set.of(
"net/minecraft/world/level/storage/loot/LootContext",
"net/minecraft/world/level/storage/loot/LootParams$Builder"),
Set.of(
Map.entry(
"getParamOrNull",
"(Lnet/minecraft/world/level/storage/loot/parameters/LootContextParam;)Ljava/lang/Object;"),
Map.entry(
"getOptionalParameter",
"(Lnet/minecraft/world/level/storage/loot/parameters/LootContextParam;)Ljava/lang/Object;")),
Set.of("Lnet/minecraft/world/level/storage/loot/parameters/LootContextParam;"),
"Param");

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
final MethodInsnNode node = insn.node();
if (BLOCK_STATE_PROPERTY.test(node)) {
return BLOCK_STATE_PROPERTY.transform(node);
return BLOCK_STATE_PROPERTY.transform(node, assignment, suggester);
}
if (LOOT_CONTEXT_PARAM.test(node)) {
return LOOT_CONTEXT_PARAM.transform(node, assignment, suggester);
}
return null;
}
@@ -67,21 +93,29 @@ private record StaticFieldEntry(
Set<String> owners, Set<Entry<String, String>> methods, Set<String> fieldTypes, @Nullable String suffix) {

boolean test(final MethodInsnNode node) {
return this.owners.contains(node.owner)
&& this.methods.stream()
.anyMatch(e ->
e.getKey().equals(node.name) && e.getValue().equals(node.desc));
return matches(this.owners, this.methods, node);
}

@Nullable
String transform(final MethodInsnNode node) {
String transform(
final MethodInsnNode node, final AssignmentContext assignment, final SuggesterContext suggester)
throws IOException {
final AbstractInsnNode prev = node.getPrevious();
if (prev instanceof final FieldInsnNode fieldInsnNode
&& fieldInsnNode.getOpcode() == Opcodes.GETSTATIC
&& this.fieldTypes.contains(fieldInsnNode.desc)) {
return staticFinalFieldNameToLocalName(fieldInsnNode.name) + (this.suffix == null ? "" : this.suffix);
}
return null;
// always use the type instead of any other suggesters
return suggester.typeSuggester().suggestNameFromType(toJvmType(assignment.lvt().desc))
+ (this.suffix == null ? "" : this.suffix);
}
}

private static boolean matches(
final Set<String> owners, final Set<Entry<String, String>> methods, final MethodInsnNode node) {
return owners.contains(node.owner)
&& methods.stream()
.anyMatch(e ->
e.getKey().equals(node.name) && e.getValue().equals(node.desc));
}
}
Original file line number Diff line number Diff line change
@@ -23,7 +23,9 @@
package io.papermc.codebook.lvt.suggestion;

import dev.denwav.hypo.model.data.types.PrimitiveType;
import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.io.IOException;
@@ -55,7 +57,11 @@ public class FluentGetterSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
// I think it's best to only work with primitive types here, as other types should already have names
// and this dramatically cuts down on the number of methods analyzed because we aren't filtering by
Original file line number Diff line number Diff line change
@@ -22,7 +22,9 @@

package io.papermc.codebook.lvt.suggestion;

import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -31,7 +33,11 @@ public class GenericSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container) {
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester) {
return switch (call.data().name()) {
case "hashCode" -> "hashCode";
case "size" -> "size";
Original file line number Diff line number Diff line change
@@ -22,7 +22,9 @@

package io.papermc.codebook.lvt.suggestion;

import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.field.FieldCallContext;
import io.papermc.codebook.lvt.suggestion.context.field.FieldInsnContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
@@ -33,13 +35,21 @@
public interface LvtSuggester {

default @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
return null;
}

default @Nullable String suggestFromField(
final FieldCallContext call, final FieldInsnContext insn, final ContainerContext container)
final FieldCallContext call,
final FieldInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
return null;
}
Original file line number Diff line number Diff line change
@@ -22,7 +22,9 @@

package io.papermc.codebook.lvt.suggestion;

import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.io.IOException;
@@ -32,7 +34,11 @@ public class MathSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
final String methodName = call.data().name();

Original file line number Diff line number Diff line change
@@ -25,7 +25,9 @@
import static io.papermc.codebook.lvt.LvtUtil.hasPrefix;
import static io.papermc.codebook.lvt.LvtUtil.parseSimpleTypeNameFromMethod;

import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.io.IOException;
@@ -35,7 +37,11 @@ public class NewPrefixSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
final String methodName = call.data().name();
if (!hasPrefix(methodName, "new")) {
Original file line number Diff line number Diff line change
@@ -29,7 +29,9 @@

import dev.denwav.hypo.model.data.MethodData;
import dev.denwav.hypo.model.data.types.PrimitiveType;
import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.io.IOException;
@@ -52,7 +54,11 @@ public class PositionsSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
if ("net/minecraft/core/SectionPos".equals(insn.owner().name())) {
return suggestNameForSectionPos(container.node(), call.data(), insn.node());
Original file line number Diff line number Diff line change
@@ -24,7 +24,9 @@

import dev.denwav.hypo.model.data.ClassKind;
import dev.denwav.hypo.model.data.FieldData;
import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.util.List;
@@ -34,7 +36,11 @@ public class RecordComponentSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container) {
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester) {
if (insn.owner().is(ClassKind.RECORD)) {
return null;
}
Original file line number Diff line number Diff line change
@@ -31,7 +31,9 @@
import dev.denwav.hypo.model.data.types.JvmType;
import dev.denwav.hypo.model.data.types.PrimitiveType;
import io.papermc.codebook.lvt.LvtTypeSuggester;
import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import jakarta.inject.Inject;
@@ -57,7 +59,11 @@ public class SingleVerbBooleanSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
if (call.data().returnType() != PrimitiveType.BOOLEAN) {
return null;
Original file line number Diff line number Diff line change
@@ -25,7 +25,9 @@
import static io.papermc.codebook.lvt.LvtUtil.parseSimpleTypeNameFromMethod;
import static io.papermc.codebook.lvt.LvtUtil.tryMatchPrefix;

import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.util.List;
@@ -44,7 +46,11 @@ public class SingleVerbSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container) {
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester) {
final String methodName = call.data().name();

final @Nullable String prefix = tryMatchPrefix(methodName, SINGLE_VERB_PREFIXES);
Original file line number Diff line number Diff line change
@@ -22,7 +22,9 @@

package io.papermc.codebook.lvt.suggestion;

import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.io.IOException;
@@ -32,7 +34,11 @@ public class StringSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester)
throws IOException {
final String methodName = call.data().name();

Original file line number Diff line number Diff line change
@@ -25,7 +25,9 @@
import static io.papermc.codebook.lvt.LvtUtil.tryMatchPrefix;

import dev.denwav.hypo.model.data.types.PrimitiveType;
import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.util.List;
@@ -41,7 +43,11 @@ public class VerbPrefixBooleanSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container) {
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester) {
if (call.data().returnType() != PrimitiveType.BOOLEAN) {
return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* codebook is a remapper utility for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 3 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/

package io.papermc.codebook.lvt.suggestion.context;

import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.VarInsnNode;

public record AssignmentContext(VarInsnNode assignmentNode, LocalVariableNode lvt) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* codebook is a remapper utility for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 3 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/

package io.papermc.codebook.lvt.suggestion.context;

import dev.denwav.hypo.core.HypoContext;
import io.papermc.codebook.lvt.LvtTypeSuggester;

public record SuggesterContext(HypoContext hypoContext, LvtTypeSuggester typeSuggester) {}
Original file line number Diff line number Diff line change
@@ -27,7 +27,9 @@

import dev.denwav.hypo.model.data.types.JvmType;
import io.papermc.codebook.lvt.suggestion.LvtSuggester;
import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import java.util.List;
@@ -40,7 +42,11 @@ public class MthRandomSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container) {
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester) {
final String methodName = call.data().name();
if (!insn.ownerEqualTo(MTH_NAME)) {
return null;
Original file line number Diff line number Diff line change
@@ -29,7 +29,9 @@
import dev.denwav.hypo.model.data.types.ClassType;
import dev.denwav.hypo.model.data.types.JvmType;
import io.papermc.codebook.lvt.suggestion.LvtSuggester;
import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import jakarta.inject.Inject;
@@ -53,7 +55,11 @@ public class RandomSourceSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container) {
final MethodCallContext call,
final MethodInsnContext insn,
final ContainerContext container,
final AssignmentContext assignment,
final SuggesterContext suggester) {
if (this.randomSourceClass == null) {
return null;
}
Original file line number Diff line number Diff line change
@@ -24,5 +24,6 @@

public enum ReportType {
MISSING_METHOD_LVT_SUGGESTION,
MISSING_METHOD_PARAM;
MISSING_METHOD_PARAM,
CHECK_CAST_WRAPS;
}
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@
package io.papermc.codebook.report;

import com.google.inject.AbstractModule;
import io.papermc.codebook.report.type.CheckCastWraps;
import io.papermc.codebook.report.type.MissingMethodLvtSuggestion;
import io.papermc.codebook.report.type.MissingMethodParam;
import io.papermc.codebook.report.type.Report;
@@ -58,7 +59,8 @@ public Reports(final Path reportsDir, final Set<ReportType> typesToGenerate) {
this.typesToGenerate = typesToGenerate;
this.reports = Map.of(
ReportType.MISSING_METHOD_LVT_SUGGESTION, new MissingMethodLvtSuggestion(),
ReportType.MISSING_METHOD_PARAM, new MissingMethodParam());
ReportType.MISSING_METHOD_PARAM, new MissingMethodParam(),
ReportType.CHECK_CAST_WRAPS, new CheckCastWraps());
}

public void generateReports() throws IOException {
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* codebook is a remapper utility for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 3 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/

package io.papermc.codebook.report.type;

import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.objectweb.asm.tree.MethodInsnNode;

public class CheckCastWraps implements Report {

private final Map<CacheKey, Integer> cache = new ConcurrentHashMap<>();

@Override
public String generate() {
final StringBuilder sb = new StringBuilder();
this.cache.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.forEachOrdered(entry -> {
sb.append(entry.getKey().className)
.append("#")
.append(entry.getKey().methodName)
.append(" ")
.append(entry.getKey().descriptor)
.append(" ")
.append(entry.getKey().itf)
.append(" ")
.append(entry.getValue())
.append("\n");
});
return sb.toString();
}

public void report(final MethodInsnNode insn) {
final var key = new CacheKey(insn.owner, insn.name, insn.desc, insn.itf);
this.cache.compute(key, (k, v) -> v == null ? 1 : v + 1);
}

private record CacheKey(String className, String methodName, String descriptor, boolean itf) {}
}
Original file line number Diff line number Diff line change
@@ -40,7 +40,9 @@
import dev.denwav.hypo.model.data.MethodDescriptor;
import dev.denwav.hypo.model.data.types.ClassType;
import dev.denwav.hypo.model.data.types.JvmType;
import io.papermc.codebook.lvt.suggestion.context.AssignmentContext;
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
import io.papermc.codebook.lvt.suggestion.context.SuggesterContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
import io.papermc.codebook.report.Reports;
@@ -68,6 +70,7 @@ class LvtAssignmentSuggesterTest {
static final JvmType RANDOM_SOURCE_TYPE = new ClassType("net/minecraft/util/RandomSource");

private static final MockSettings LENIENT = withSettings().strictness(Strictness.LENIENT);
private HypoContext context;
private RootLvtSuggester suggester;

@Mock
@@ -88,10 +91,12 @@ class LvtAssignmentSuggesterTest {
@Mock
private Reports reports;

@Mock
private AssignmentContext assignment;

@BeforeEach
void setup() throws Exception {
final HypoContext context =
HypoContext.builder().withContextProviders(this.provider).build();
this.context = HypoContext.builder().withContextProviders(this.provider).build();

when(this.provider.findClass("java/util/List")).thenReturn(this.listClass);
when(this.provider.findClass("java/util/Set")).thenReturn(this.setClass);
@@ -101,8 +106,8 @@ void setup() throws Exception {

when(this.randomSourceClass.name()).thenReturn(RANDOM_SOURCE_TYPE.asInternalName());

this.suggester =
new RootLvtSuggester(context, new LvtTypeSuggester(context), Guice.createInjector(this.reports));
this.suggester = new RootLvtSuggester(
this.context, new LvtTypeSuggester(this.context), this.reports, Guice.createInjector(this.reports));
}

@ParameterizedTest
@@ -142,7 +147,11 @@ void testSuggester(
final MethodInsnNode insn =
new MethodInsnNode(Opcodes.INVOKEVIRTUAL, methodOwner, methodName, methodDescriptor);
final @Nullable String result = this.suggester.suggestFromMethod(
MethodCallContext.create(method), MethodInsnContext.create(owner, insn), context);
MethodCallContext.create(method),
MethodInsnContext.create(owner, insn),
context,
this.assignment,
new SuggesterContext(this.context, this.suggester.lvtTypeSuggester));

assertEquals(expectedName, result);
}